mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Merge branch 'develop' into feature/universal_link_navigation
This commit is contained in:
commit
6dd4d4d906
227 changed files with 10551 additions and 3262 deletions
17
CHANGES.md
17
CHANGES.md
|
@ -2,31 +2,44 @@ Changes in Element 1.0.6 (2020-XX-XX)
|
|||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
- List phone numbers and emails added to the Matrix account, and add emails and phone numbers to account (#44, #45)
|
||||
|
||||
Improvements 🙌:
|
||||
- You can now join room through permalink and within room directory search
|
||||
- Add long click gesture to copy userId, user display name, room name, room topic and room alias (#1774)
|
||||
- Fix several issues when uploading bug files (#1889)
|
||||
- Do not propose to verify session if there is only one session and 4S is not configured (#1901)
|
||||
- Call screen does not use proximity sensor (#1735)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Display name not shown under Settings/General (#1926)
|
||||
- Editing message forgets line breaks and markdown (#1939)
|
||||
- Words containing my name should not trigger notifications (#1781)
|
||||
- Fix changing language issue
|
||||
- Fix FontSize issue (#1483, #1787)
|
||||
- Fix bad color for settings icon on Android < 24 (#1786)
|
||||
- Change user or room avatar: when selecting Gallery, I'm not proposed to crop the selected image (#1590)
|
||||
- Loudspeaker is always used (#1685)
|
||||
- Fix uploads still don't work with room v6 (#1879)
|
||||
- Can't handle ongoing call events in background (#1992)
|
||||
- Handle room, user and group links by the Element app (#1795)
|
||||
- Crash / Attachment viewer: Cannot draw a recycled Bitmap #2034
|
||||
- Login with Matrix-Id | Autodiscovery fails if identity server is invalid and Homeserver ok (#2027)
|
||||
- Support for image compression on Android 10
|
||||
- Verification popup won't show
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
- The SDK is now using SAS string translations from [Weblate Matrix-doc project](https://translate.riot.im/projects/matrix-doc/) (#1909)
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
- Some dependencies have been upgraded (coroutine, recyclerView, appCompat, core-ktx, firebase-messaging)
|
||||
- Buildkite:
|
||||
New pipeline location: https://github.com/matrix-org/pipelines/blob/master/element-android/pipeline.yml
|
||||
New build location: https://buildkite.com/matrix-dot-org/element-android
|
||||
|
||||
|
||||
Other changes:
|
||||
- Use File extension functions to make code more concise (#1996)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
[![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop)
|
||||
[![Weblate](https://translate.riot.im/widgets/element-android/-/svg-badge.svg)](https://translate.riot.im/engage/element-android/?utm_source=widget)
|
||||
[![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org)
|
||||
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=alert_status)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
|
||||
|
@ -14,7 +14,7 @@ It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-androi
|
|||
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app)
|
||||
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app)
|
||||
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop)
|
||||
|
||||
# New Android SDK
|
||||
|
||||
|
|
|
@ -27,4 +27,9 @@ class AnimatedImageViewHolder constructor(itemView: View) :
|
|||
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
|
||||
|
||||
internal val target = DefaultImageLoaderTarget(this, this.touchImageView)
|
||||
|
||||
override fun onRecycled() {
|
||||
super.onRecycled()
|
||||
touchImageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
|
|||
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = false
|
||||
holder.touchImageView.setImageDrawable(errorDrawable)
|
||||
}
|
||||
|
||||
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
||||
|
@ -77,11 +78,13 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
|
|||
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = true
|
||||
holder.touchImageView.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = false
|
||||
holder.touchImageView.setImageDrawable(errorDrawable)
|
||||
}
|
||||
|
||||
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
||||
|
|
|
@ -35,6 +35,7 @@ interface VideoLoaderTarget {
|
|||
fun onVideoFileLoading(uid: String)
|
||||
fun onVideoFileLoadFailed(uid: String)
|
||||
fun onVideoFileReady(uid: String, file: File)
|
||||
fun onVideoURLReady(uid: String, path: String)
|
||||
}
|
||||
|
||||
internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val contextView: ImageView) : VideoLoaderTarget {
|
||||
|
@ -47,6 +48,8 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
|
|||
}
|
||||
|
||||
override fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.thumbnailImage.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onThumbnailResourceReady(uid: String, resource: Drawable) {
|
||||
|
@ -68,9 +71,19 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
|
|||
|
||||
override fun onVideoFileReady(uid: String, file: File) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
arrangeForVideoReady()
|
||||
holder.videoReady(file)
|
||||
}
|
||||
|
||||
override fun onVideoURLReady(uid: String, path: String) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
arrangeForVideoReady()
|
||||
holder.videoReady(path)
|
||||
}
|
||||
|
||||
private fun arrangeForVideoReady() {
|
||||
holder.thumbnailImage.isVisible = false
|
||||
holder.loaderProgressBar.isVisible = false
|
||||
holder.videoView.isVisible = true
|
||||
holder.videoReady(file)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.lib.attachmentviewer
|
||||
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
|
@ -65,6 +66,13 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
}
|
||||
}
|
||||
|
||||
fun videoReady(path: String) {
|
||||
mVideoPath = path
|
||||
if (isSelected) {
|
||||
startPlaying()
|
||||
}
|
||||
}
|
||||
|
||||
fun videoFileLoadError() {
|
||||
}
|
||||
|
||||
|
@ -118,8 +126,13 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
||||
}
|
||||
}
|
||||
try {
|
||||
videoView.setVideoPath(mVideoPath)
|
||||
} catch (failure: Throwable) {
|
||||
// Couldn't open
|
||||
Log.v(VideoViewHolder::class.java.name, "Failed to start video")
|
||||
}
|
||||
|
||||
videoView.setVideoPath(mVideoPath)
|
||||
if (!wasPaused) {
|
||||
videoView.start()
|
||||
if (progress > 0) {
|
||||
|
|
|
@ -39,4 +39,9 @@ class ZoomableImageViewHolder constructor(itemView: View) :
|
|||
}
|
||||
|
||||
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, touchImageView)
|
||||
|
||||
override fun onRecycled() {
|
||||
super.onRecycled()
|
||||
touchImageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ sonarqube {
|
|||
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
|
||||
property "sonar.sourceEncoding", "UTF-8"
|
||||
property "sonar.links.homepage", "https://github.com/vector-im/element-android/"
|
||||
property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/riotx-android"
|
||||
property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/element-android"
|
||||
property "sonar.links.scm", "https://github.com/vector-im/element-android/"
|
||||
property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
|
||||
property "sonar.organization", "new_vector_ltd_organization"
|
||||
|
|
285
docs/add_threePids.md
Normal file
285
docs/add_threePids.md
Normal file
|
@ -0,0 +1,285 @@
|
|||
# Adding and removing ThreePids to an account
|
||||
|
||||
## Add email
|
||||
|
||||
### User enter the email
|
||||
|
||||
> POST https://homeserver.org/_matrix/client/r0/account/3pid/email/requestToken
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "alice@email-provider.org",
|
||||
"client_secret": "TixzvOnw7nLEUdiQEmkHzkXKrY4HhiGh",
|
||||
"send_attempt": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### The email is already added to an account
|
||||
|
||||
400
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_IN_USE",
|
||||
"error": "Email is already in use"
|
||||
}
|
||||
```
|
||||
|
||||
#### The email is free
|
||||
|
||||
Wording: "We've sent you an email to verify your address. Please follow the instructions there and then click the button below."
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "bxyDHuJKsdkjMlTJ"
|
||||
}
|
||||
```
|
||||
|
||||
## User receive an e-mail
|
||||
|
||||
> [homeserver.org] Validate your email
|
||||
>
|
||||
> A request to add an email address to your Matrix account has been received. If this was you, please click the link below to confirm adding this email:
|
||||
https://homeserver.org/_matrix/client/unstable/add_threepid/email/submit_token?token=WUnEhQAmJrXupdEbXgdWvnVIKaGYZFsU&client_secret=TixzvOnw7nLEUdiQEmkHzkXKrY4HhiGh&sid=bxyDHuJKsdkjMlTJ
|
||||
>
|
||||
> If this was not you, you can safely ignore this email. Thank you.
|
||||
|
||||
### User clicks on the link
|
||||
|
||||
The browser displays the following message:
|
||||
|
||||
> Your email has now been validated, please return to your client. You may now close this window.
|
||||
|
||||
### User returns on Element
|
||||
|
||||
User clicks on CONTINUE
|
||||
|
||||
> POST https://homeserver.org/_matrix/client/r0/account/3pid/add
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "bxyDHuJKsdkjMlTJ",
|
||||
"client_secret": "TixzvOnw7nLEUdiQEmkHzkXKrY4HhiGh"
|
||||
}
|
||||
```
|
||||
|
||||
401 User Interactive Authentication
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "ppvvnozXCQZFaggUBlHJYPjA",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.password"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### User enters his password
|
||||
|
||||
POST https://homeserver.org/_matrix/client/r0/account/3pid/add
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "bxyDHuJKsdkjMlTJ",
|
||||
"client_secret": "TixzvOnw7nLEUdiQEmkHzkXKrY4HhiGh",
|
||||
"auth": {
|
||||
"session": "ppvvnozXCQZFaggUBlHJYPjA",
|
||||
"type": "m.login.password",
|
||||
"user": "@benoitx:matrix.org",
|
||||
"password": "weak_password"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### The link has not been clicked
|
||||
|
||||
400
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_AUTH_FAILED",
|
||||
"error": "No validated 3pid session found"
|
||||
}
|
||||
```
|
||||
|
||||
#### Wrong password
|
||||
|
||||
401
|
||||
|
||||
```json
|
||||
{
|
||||
"session": "fXHOvoQsPMhEebVqTnIrzZJN",
|
||||
"flows": [
|
||||
{
|
||||
"stages": [
|
||||
"m.login.password"
|
||||
]
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
},
|
||||
"completed":[
|
||||
],
|
||||
"error": "Invalid password",
|
||||
"errcode": "M_FORBIDDEN"
|
||||
}
|
||||
```
|
||||
|
||||
#### The link has been clicked and the account password is correct
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
## Remove email
|
||||
|
||||
### User want to remove an email from his account
|
||||
|
||||
> POST https://homeserver.org/_matrix/client/r0/account/3pid/delete
|
||||
|
||||
```json
|
||||
{
|
||||
"medium": "email",
|
||||
"address": "alice@email-provider.org"
|
||||
}
|
||||
```
|
||||
|
||||
#### Email was not bound to an identity server
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"id_server_unbind_result": "no-support"
|
||||
}
|
||||
```
|
||||
|
||||
#### Email was bound to an identity server
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"id_server_unbind_result": "success"
|
||||
}
|
||||
```
|
||||
|
||||
## Add phone number
|
||||
|
||||
> POST https://homeserver.org/_matrix/client/r0/account/3pid/msisdn/requestToken
|
||||
|
||||
```json
|
||||
{
|
||||
"country": "FR",
|
||||
"phone_number": "611223344",
|
||||
"client_secret": "f1K29wFZBEr4RZYatu7xj8nEbXiVpr7J",
|
||||
"send_attempt": 1
|
||||
}
|
||||
```
|
||||
|
||||
Note that the phone number is sent without `+` and without the country code
|
||||
|
||||
#### The phone number is already added to an account
|
||||
|
||||
400
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_THREEPID_IN_USE",
|
||||
"error": "MSISDN is already in use"
|
||||
}
|
||||
```
|
||||
|
||||
#### The phone number is free
|
||||
|
||||
Wording: "A text message has been sent to +33611223344. Please enter the verification code it contains."
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"msisdn": "33651547677",
|
||||
"intl_fmt": "+33 6 51 54 76 77",
|
||||
"success": true,
|
||||
"sid": "253299954",
|
||||
"submit_url": "https://homeserver.org/_matrix/client/unstable/add_threepid/msisdn/submit_token"
|
||||
}
|
||||
```
|
||||
|
||||
## User receive a text message
|
||||
|
||||
> Riot
|
||||
|
||||
> Your Riot validation code is 892541, please enter this into the app
|
||||
|
||||
### User enter the code to the app
|
||||
|
||||
#### Wrong code
|
||||
|
||||
> POST https://homeserver.org/_matrix/client/unstable/add_threepid/msisdn/submit_token
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "253299954",
|
||||
"client_secret": "f1K29wFZBEr4RZYatu7xj8nEbXiVpr7J",
|
||||
"token": "111111"
|
||||
}
|
||||
```
|
||||
|
||||
400
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": "M_UNKNOWN",
|
||||
"error": "Error contacting the identity server"
|
||||
}
|
||||
```
|
||||
|
||||
This is not an ideal, but the client will display a hint to check the entered code to the user.
|
||||
|
||||
#### Correct code
|
||||
|
||||
> POST https://homeserver.org/_matrix/client/unstable/add_threepid/msisdn/submit_token
|
||||
|
||||
```json
|
||||
{
|
||||
"sid": "253299954",
|
||||
"client_secret": "f1K29wFZBEr4RZYatu7xj8nEbXiVpr7J",
|
||||
"token": "892541"
|
||||
}
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
Then the app call `https://homeserver.org/_matrix/client/r0/account/3pid/add` as per adding an email and follow the same UIS flow
|
||||
|
||||
## Remove phone number
|
||||
|
||||
### User wants to remove a phone number from his account
|
||||
|
||||
This is the same request and response than to remove email, but with this body:
|
||||
|
||||
```json
|
||||
{
|
||||
"medium": "msisdn",
|
||||
"address": "33611223344"
|
||||
}
|
||||
```
|
||||
|
||||
Note that the phone number is provided without `+`, but with the country code.
|
|
@ -8,7 +8,9 @@ This document describes the flow of signin to a homeserver, and also the flow wh
|
|||
|
||||
Client request the sign-in flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
|
||||
|
||||
> curl -X GET 'https://matrix.org/_matrix/client/r0/login'
|
||||
```shell script
|
||||
curl -X GET 'https://matrix.org/_matrix/client/r0/login'
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
|
@ -26,7 +28,9 @@ Client request the sign-in flows, once the homeserver is chosen by the user and
|
|||
|
||||
The user is able to connect using `m.login.password`
|
||||
|
||||
> curl -X POST --data $'{"identifier":{"type":"m.id.user","user":"alice"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
```shell script
|
||||
curl -X POST --data $'{"identifier":{"type":"m.id.user","user":"alice"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -73,14 +77,16 @@ We get credential (200)
|
|||
|
||||
If the user has associated an email with its account, he can signin using the email.
|
||||
|
||||
> curl -X POST --data $'{"identifier":{"type":"m.id.thirdparty","medium":"email","address":"alice@yopmail.com"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
```shell script
|
||||
curl -X POST --data $'{"identifier":{"type":"m.id.thirdparty","medium":"email","address":"alice@email-provider.org"},"password":"weak_password","type":"m.login.password","initial_device_display_name":"Portable"}' 'https://matrix.org/_matrix/client/r0/login'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"identifier": {
|
||||
"type": "m.id.thirdparty",
|
||||
"medium": "email",
|
||||
"address": "alice@yopmail.com"
|
||||
"address": "alice@email-provider.org"
|
||||
},
|
||||
"password": "weak_password",
|
||||
"type": "m.login.password",
|
||||
|
@ -136,7 +142,9 @@ Not supported yet in Element
|
|||
|
||||
### Login with SSO
|
||||
|
||||
> curl -X GET 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
```shell script
|
||||
curl -X GET 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
```
|
||||
|
||||
200
|
||||
|
||||
|
@ -171,7 +179,9 @@ Once the process is finished, the web page will call the `redirectUrl` with an e
|
|||
|
||||
This navigation is intercepted by Element by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
||||
|
||||
> curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
```shell script
|
||||
curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -214,7 +224,9 @@ We display a warning regarding e2e.
|
|||
|
||||
At the first step, we do not send the password, only the email and a client secret, generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","send_attempt":0,"email":"user@domain.com"}' 'https://matrix.org/_matrix/client/r0/account/password/email/requestToken'
|
||||
```shell script
|
||||
curl -X POST --data $'{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","send_attempt":0,"email":"user@domain.com"}' 'https://matrix.org/_matrix/client/r0/account/password/email/requestToken'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -251,7 +263,9 @@ During this step, the new password is sent to the homeserver.
|
|||
|
||||
If the user confirms before the link is clicked, we get an error:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -285,7 +299,9 @@ It contains the client secret, a token and the sid
|
|||
|
||||
When the user click the link, if validate his ownership and the new password can now be ent by the application (on user demand):
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"type":"m.login.email.identity","threepid_creds":{"client_secret":"6c57f284-85e2-421b-8270-fb1795a120a7","sid":"tQNbrREDACTEDldA"}},"new_password":"weak_password"}' 'https://matrix.org/_matrix/client/r0/account/password'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
@ -10,7 +10,9 @@ This document describes the flow of registration to a homeserver. Examples come
|
|||
|
||||
Client request the sign-up flows, once the homeserver is chosen by the user and its url is known (in the example it's `https://matrix.org`)
|
||||
|
||||
> curl -X POST --data $'{}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -70,7 +72,9 @@ If the registration is not possible, we get a 403
|
|||
|
||||
The app is displaying a form to enter username and password.
|
||||
|
||||
> curl -X POST --data $'{"initial_device_display_name":"Mobile device","username":"alice","password": "weak_password"}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"initial_device_display_name":"Mobile device","username":"alice","password": "weak_password"}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -133,9 +137,11 @@ We get a 400:
|
|||
|
||||
### Step 2: entering email
|
||||
|
||||
User is proposed to enter an email. We skip this step.
|
||||
User is proposed to enter an email. User skips this step.
|
||||
|
||||
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.dummy"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.dummy"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -189,16 +195,18 @@ User is proposed to enter an email. We skip this step.
|
|||
}
|
||||
```
|
||||
|
||||
### Step 2 bis: we enter an email
|
||||
### Step 2 bis: user enters an email
|
||||
|
||||
We request a token to the homeserver. The `client_secret` is generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","email":"alice@yopmail.com","send_attempt":0}' 'https://matrix.org/_matrix/client/r0/register/email/requestToken'
|
||||
```shell script
|
||||
curl -X POST --data $'{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","email":"alice@email-provider.org","send_attempt":0}' 'https://matrix.org/_matrix/client/r0/register/email/requestToken'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"client_secret": "53e679ea-oRED-ACTED-92b8-3012c49c6cfa",
|
||||
"email": "alice@yopmail.com",
|
||||
"email": "alice@email-provider.org",
|
||||
"send_attempt": 0
|
||||
}
|
||||
```
|
||||
|
@ -213,7 +221,9 @@ We request a token to the homeserver. The `client_secret` is generated by the ap
|
|||
|
||||
And
|
||||
|
||||
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -239,7 +249,9 @@ We get 401 since the email is not validated yet:
|
|||
|
||||
The app is now polling on
|
||||
|
||||
> curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"threepid_creds":{"client_secret":"53e679ea-oRED-ACTED-92b8-3012c49c6cfa","sid":"qlBCREDACTEDEtgxD"},"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.email.identity"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -254,7 +266,7 @@ The app is now polling on
|
|||
}
|
||||
```
|
||||
|
||||
We click on the link received by email `https://matrix.org/_matrix/client/unstable/registration/email/submit_token?token=vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ&client_secret=53e679ea-oRED-ACTED-92b8-3012c49c6cfa&sid=qlBCREDACTEDEtgxD` which contains:
|
||||
User clicks on the link received by email `https://matrix.org/_matrix/client/unstable/registration/email/submit_token?token=vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ&client_secret=53e679ea-oRED-ACTED-92b8-3012c49c6cfa&sid=qlBCREDACTEDEtgxD` which contains:
|
||||
- A `token` vtQjQIZfwdoREDACTEDozrmKYSWlCXsJ
|
||||
- The `client_secret`: 53e679ea-oRED-ACTED-92b8-3012c49c6cfa
|
||||
- A `sid`: qlBCREDACTEDEtgxD
|
||||
|
@ -306,7 +318,9 @@ Once the link is clicked, the registration request (polling) returns a 401 with
|
|||
|
||||
User is proposed to accept T&C and he accepts them
|
||||
|
||||
> curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.terms"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.terms"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -365,7 +379,9 @@ User is proposed to accept T&C and he accepts them
|
|||
|
||||
User is proposed to prove he is not a robot and he does it:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"response":"03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q","session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.recaptcha"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"response":"03AOLTBLSiGS9GhFDpAMblJ2nlXOmHXqAYJ5OvHCPUjiVLBef3k9snOYI_BDC32-t4D2jv-tpvkaiEI_uloobFd9RUTPpJ7con2hMddbKjSCYqXqcUQFhzhbcX6kw8uBnh2sbwBe80_ihrHGXEoACXQkL0ki1Q0uEtOeW20YBRjbNABsZPpLNZhGIWC0QVXnQ4FouAtZrl3gOAiyM-oG3cgP6M9pcANIAC_7T2P2amAHbtsTlSR9CsazNyS-rtDR9b5MywdtnWN9Aw8fTJb8cXQk_j7nvugMxzofPjSOrPKcr8h5OqPlpUCyxxnFtag6cuaPSUwh43D2L0E-ZX7djzaY2Yh_U2n6HegFNPOQ22CJmfrKwDlodmAfMPvAXyq77n3HpoREDACTEDo3830RHF4BfkGXUaZjctgg-A1mvC17hmQmQpkG7IhDqyw0onU-0vF_-ehCjq_CcQEDpS_O3uiHJaG5xGf-0rhLm57v_wA3deugbsZuO4uTuxZZycN_mKxZ97jlDVBetl9hc_5REPbhcT1w3uzTCSx7Q","session":"xptUYoREDACTEDogOWAGVnbJQ","type":"m.login.recaptcha"}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -396,9 +412,11 @@ Some homeservers may require the user to enter MSISDN.
|
|||
|
||||
On matrix.org, it's not required, and not even optional, but it's still possible for the app to add a MSISDN during the registration.
|
||||
|
||||
The user enter a phone number and select a country, the `client_secret` is generated by the application
|
||||
The user enters a phone number and selects a country, the `client_secret` is generated by the application
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","send_attempt":1,"country":"FR","phone_number":"+33611223344"}' 'https://matrix.org/_matrix/client/r0/register/msisdn/requestToken'
|
||||
```shell script
|
||||
curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","send_attempt":1,"country":"FR","phone_number":"+33611223344"}' 'https://matrix.org/_matrix/client/r0/register/msisdn/requestToken'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -430,10 +448,11 @@ If it is not the case, the homeserver send the SMS and returns some data, especi
|
|||
}
|
||||
```
|
||||
|
||||
When you execute the register request, with the received `sid`, you get an error since the MSISDN is not validated yet:
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
When we execute the register request, with the received `sid`, we get an error since the MSISDN is not validated yet:
|
||||
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
"auth": {
|
||||
|
@ -492,7 +511,9 @@ There is an issue on Synapse, which return a 401, it sends too much data along w
|
|||
|
||||
The user receive the SMS, he can enter the SMS code in the app, which is sent using the "submit_url" received ie the response of the `requestToken` request:
|
||||
|
||||
> curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798","token":"123456"}' 'https://matrix.org/_matrix/client/unstable/add_threepid/msisdn/submit_token'
|
||||
```shell script
|
||||
curl -X POST --data $'{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798","token":"123456"}' 'https://matrix.org/_matrix/client/unstable/add_threepid/msisdn/submit_token'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -520,7 +541,9 @@ And if the code is correct we get a 200 with:
|
|||
|
||||
We can now execute the registration request, to the homeserver
|
||||
|
||||
> curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```shell script
|
||||
curl -X POST --data $'{"auth":{"type":"m.login.msisdn","session":"xptUYoREDACTEDogOWAGVnbJQ","threepid_creds":{"client_secret":"d3e285f6-972a-496c-9a22-7915a2db57c7","sid":"1678881798"}}}' 'https://matrix.org/_matrix/client/r0/register'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -535,7 +558,7 @@ We can now execute the registration request, to the homeserver
|
|||
}
|
||||
```
|
||||
|
||||
Now the homeserver consider that the `m.login.msisdn` step is completed (401):
|
||||
Now the homeserver considers that the `m.login.msisdn` step is completed (401):
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
package org.matrix.android.sdk.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function3
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
|
@ -43,10 +47,6 @@ import org.matrix.android.sdk.api.util.toOptional
|
|||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function3
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
|
@ -110,6 +110,11 @@ class RxSession(private val session: Session) {
|
|||
.startWithCallable { session.getThreePids() }
|
||||
}
|
||||
|
||||
fun livePendingThreePIds(): Observable<List<ThreePid>> {
|
||||
return session.getPendingThreePidsLive().asObservable()
|
||||
.startWithCallable { session.getPendingThreePids() }
|
||||
}
|
||||
|
||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||
session.createRoom(roomParams, it)
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ dependencies {
|
|||
def coroutines_version = "1.3.8"
|
||||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.25.4'
|
||||
def work_version = '2.3.3'
|
||||
def work_version = '2.4.0'
|
||||
def retrofit_version = '2.6.2'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
@ -144,7 +144,6 @@ dependencies {
|
|||
|
||||
// Image
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01'
|
||||
implementation 'id.zelory:compressor:3.0.0'
|
||||
|
||||
// Database
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||
|
|
|
@ -23,15 +23,12 @@ import androidx.work.WorkManager
|
|||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import org.matrix.android.sdk.internal.network.UserAgentHolder
|
||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
@ -96,9 +93,5 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
|
||||
fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? {
|
||||
return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.util.Base64
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -29,6 +28,8 @@ import org.junit.runners.MethodSorters
|
|||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
|
@ -52,17 +53,14 @@ class AttachmentEncryptionTest {
|
|||
memoryFile.inputStream
|
||||
}
|
||||
|
||||
val decryptedStream = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo)
|
||||
val decryptedStream = ByteArrayOutputStream()
|
||||
val result = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo.toElementToDecrypt()!!, decryptedStream)
|
||||
|
||||
assertNotNull(decryptedStream)
|
||||
assert(result)
|
||||
|
||||
val buffer = ByteArray(100)
|
||||
val toByteArray = decryptedStream.toByteArray()
|
||||
|
||||
val len = decryptedStream!!.read(buffer)
|
||||
|
||||
decryptedStream.close()
|
||||
|
||||
return Base64.encodeToString(buffer, 0, len, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "")
|
||||
return Base64.encodeToString(toByteArray, 0, toByteArray.size, Base64.DEFAULT).replace("\n".toRegex(), "").replace("=".toRegex(), "")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -17,23 +17,22 @@
|
|||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
|
||||
/**
|
||||
* It will not be possible to test all combinations. For the moment I add a few tests, then, depending on the problem discovered in the wild,
|
||||
* we can add more tests to cover the edge cases.
|
||||
* Some tests are suffixed with `_not_passing`, maybe one day we will fix them...
|
||||
* Riot-Web should be used as a reference for expected results, but not always. Especially Riot-Web add lots of `\n` in the
|
||||
* formatted body, which is quite useless.
|
||||
* Also Riot-Web does not provide plain text body when formatted text is provided. The body contains what the user has entered.
|
||||
* Element Web should be used as a reference for expected results, but not always.
|
||||
* Also Element Web does not provide plain text body when formatted text is provided. The body contains what the user has entered. We are doing
|
||||
* the same to be able to edit messages (See #1939)
|
||||
* See https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
|
@ -46,8 +45,7 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
*/
|
||||
private val markdownParser = MarkdownParser(
|
||||
Parser.builder().build(),
|
||||
HtmlRenderer.builder().build(),
|
||||
TextContentRenderer.builder().build()
|
||||
HtmlRenderer.builder().build()
|
||||
)
|
||||
|
||||
@Test
|
||||
|
@ -83,6 +81,15 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldNewLines() {
|
||||
testTypeNewLines(
|
||||
name = "bold",
|
||||
markdownPattern = "**",
|
||||
htmlExpectedTag = "strong"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalic() {
|
||||
testType(
|
||||
|
@ -92,14 +99,23 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalicNewLines() {
|
||||
testTypeNewLines(
|
||||
name = "italic",
|
||||
markdownPattern = "*",
|
||||
htmlExpectedTag = "em"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseItalic2() {
|
||||
// Riot-Web format
|
||||
"_italic_".let { markdownParser.parse(it) }.expect("italic", "<em>italic</em>")
|
||||
// Element Web format
|
||||
"_italic_".let { markdownParser.parse(it).expect(it, "<em>italic</em>") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the test is not passing, it does not work on Riot-Web neither
|
||||
* Note: the test is not passing, it does not work on Element Web neither
|
||||
*/
|
||||
@Test
|
||||
fun parseStrike_not_passing() {
|
||||
|
@ -110,14 +126,30 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseStrikeNewLines() {
|
||||
testTypeNewLines(
|
||||
name = "strike",
|
||||
markdownPattern = "~~",
|
||||
htmlExpectedTag = "del"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode() {
|
||||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "`",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
htmlExpectedTag = "code"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCodeNewLines() {
|
||||
testTypeNewLines(
|
||||
name = "code",
|
||||
markdownPattern = "`",
|
||||
htmlExpectedTag = "code"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -126,9 +158,16 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "``",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
htmlExpectedTag = "code"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode2NewLines() {
|
||||
testTypeNewLines(
|
||||
name = "code",
|
||||
markdownPattern = "``",
|
||||
htmlExpectedTag = "code"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -137,78 +176,85 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
testType(
|
||||
name = "code",
|
||||
markdownPattern = "```",
|
||||
htmlExpectedTag = "code",
|
||||
plainTextPrefix = "\"",
|
||||
plainTextSuffix = "\""
|
||||
htmlExpectedTag = "code"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCode3NewLines() {
|
||||
testTypeNewLines(
|
||||
name = "code",
|
||||
markdownPattern = "```",
|
||||
htmlExpectedTag = "code"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseUnorderedList() {
|
||||
"- item1".let { markdownParser.parse(it).expect(it, "<ul><li>item1</li></ul>") }
|
||||
"- item1\n- item2".let { markdownParser.parse(it).expect(it, "<ul><li>item1</li><li>item2</li></ul>") }
|
||||
"- item1".let { markdownParser.parse(it).expect(it, "<ul>\n<li>item1</li>\n</ul>") }
|
||||
"- item1\n- item2".let { markdownParser.parse(it).expect(it, "<ul>\n<li>item1</li>\n<li>item2</li>\n</ul>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseOrderedList() {
|
||||
"1. item1".let { markdownParser.parse(it).expect(it, "<ol><li>item1</li></ol>") }
|
||||
"1. item1\n2. item2".let { markdownParser.parse(it).expect(it, "<ol><li>item1</li><li>item2</li></ol>") }
|
||||
"1. item1".let { markdownParser.parse(it).expect(it, "<ol>\n<li>item1</li>\n</ol>") }
|
||||
"1. item1\n2. item2".let { markdownParser.parse(it).expect(it, "<ol>\n<li>item1</li>\n<li>item2</li>\n</ol>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHorizontalLine() {
|
||||
"---".let { markdownParser.parse(it) }.expect("***", "<hr />")
|
||||
"---".let { markdownParser.parse(it).expect(it, "<hr />") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseH2AndContent() {
|
||||
"a\n---\nb".let { markdownParser.parse(it) }.expect("a\nb", "<h2>a</h2><p>b</p>")
|
||||
"a\n---\nb".let { markdownParser.parse(it).expect(it, "<h2>a</h2>\n<p>b</p>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQuote() {
|
||||
"> quoted".let { markdownParser.parse(it) }.expect("«quoted»", "<blockquote><p>quoted</p></blockquote>")
|
||||
"> quoted".let { markdownParser.parse(it).expect(it, "<blockquote>\n<p>quoted</p>\n</blockquote>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseQuote_not_passing() {
|
||||
"> quoted\nline2".let { markdownParser.parse(it) }.expect("«quoted\nline2»", "<blockquote><p>quoted<br/>line2</p></blockquote>")
|
||||
"> quoted\nline2".let { markdownParser.parse(it).expect(it, "<blockquote><p>quoted<br />line2</p></blockquote>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldItalic() {
|
||||
"*italic* **bold**".let { markdownParser.parse(it) }.expect("italic bold", "<em>italic</em> <strong>bold</strong>")
|
||||
"**bold** *italic*".let { markdownParser.parse(it) }.expect("bold italic", "<strong>bold</strong> <em>italic</em>")
|
||||
"*italic* **bold**".let { markdownParser.parse(it).expect(it, "<em>italic</em> <strong>bold</strong>") }
|
||||
"**bold** *italic*".let { markdownParser.parse(it).expect(it, "<strong>bold</strong> <em>italic</em>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHead() {
|
||||
"# head1".let { markdownParser.parse(it) }.expect("head1", "<h1>head1</h1>")
|
||||
"## head2".let { markdownParser.parse(it) }.expect("head2", "<h2>head2</h2>")
|
||||
"### head3".let { markdownParser.parse(it) }.expect("head3", "<h3>head3</h3>")
|
||||
"#### head4".let { markdownParser.parse(it) }.expect("head4", "<h4>head4</h4>")
|
||||
"##### head5".let { markdownParser.parse(it) }.expect("head5", "<h5>head5</h5>")
|
||||
"###### head6".let { markdownParser.parse(it) }.expect("head6", "<h6>head6</h6>")
|
||||
"# head1".let { markdownParser.parse(it).expect(it, "<h1>head1</h1>") }
|
||||
"## head2".let { markdownParser.parse(it).expect(it, "<h2>head2</h2>") }
|
||||
"### head3".let { markdownParser.parse(it).expect(it, "<h3>head3</h3>") }
|
||||
"#### head4".let { markdownParser.parse(it).expect(it, "<h4>head4</h4>") }
|
||||
"##### head5".let { markdownParser.parse(it).expect(it, "<h5>head5</h5>") }
|
||||
"###### head6".let { markdownParser.parse(it).expect(it, "<h6>head6</h6>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseHeads() {
|
||||
"# head1\n# head2".let { markdownParser.parse(it) }.expect("head1\nhead2", "<h1>head1</h1><h1>head2</h1>")
|
||||
"# head1\n# head2".let { markdownParser.parse(it).expect(it, "<h1>head1</h1>\n<h1>head2</h1>") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseBoldNewLines_not_passing() {
|
||||
"**bold**\nline2".let { markdownParser.parse(it) }.expect("bold\nline2", "<strong>bold</strong><br />line2")
|
||||
"**bold**\nline2".let { markdownParser.parse(it).expect(it, "<strong>bold</strong><br />line2") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseLinks() {
|
||||
"[link](target)".let { markdownParser.parse(it) }.expect(""""link" (target)""", """<a href="target">link</a>""")
|
||||
"[link](target)".let { markdownParser.parse(it).expect(it, """<a href="target">link</a>""") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseParagraph() {
|
||||
"# head\ncontent".let { markdownParser.parse(it) }.expect("head\ncontent", "<h1>head</h1><p>content</p>")
|
||||
"# head\ncontent".let { markdownParser.parse(it).expect(it, "<h1>head</h1>\n<p>content</p>") }
|
||||
}
|
||||
|
||||
private fun testIdentity(text: String) {
|
||||
|
@ -217,59 +263,93 @@ class MarkdownParserTest : InstrumentedTest {
|
|||
|
||||
private fun testType(name: String,
|
||||
markdownPattern: String,
|
||||
htmlExpectedTag: String,
|
||||
plainTextPrefix: String = "",
|
||||
plainTextSuffix: String = "") {
|
||||
htmlExpectedTag: String) {
|
||||
// Test simple case
|
||||
"$markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
}
|
||||
|
||||
// Test twice the same tag
|
||||
"$markdownPattern$name$markdownPattern and $markdownPattern$name bis$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix and $plainTextPrefix$name bis$plainTextSuffix",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> and <$htmlExpectedTag>$name bis</$htmlExpectedTag>")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> and <$htmlExpectedTag>$name bis</$htmlExpectedTag>")
|
||||
}
|
||||
|
||||
val textBefore = "a"
|
||||
val textAfter = "b"
|
||||
|
||||
// With sticked text before
|
||||
"$textBefore$markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore$plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "$textBefore<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "$textBefore<$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
}
|
||||
|
||||
// With text before and space
|
||||
"$textBefore $markdownPattern$name$markdownPattern"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore $plainTextPrefix$name$plainTextSuffix",
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
}
|
||||
|
||||
// With sticked text after
|
||||
"$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix$textAfter",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
}
|
||||
|
||||
// With space and text after
|
||||
"$markdownPattern$name$markdownPattern $textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$plainTextPrefix$name$plainTextSuffix $textAfter",
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
}
|
||||
|
||||
// With sticked text before and text after
|
||||
"$textBefore$markdownPattern$name$markdownPattern$textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore$plainTextPrefix$name$plainTextSuffix$textAfter",
|
||||
expectedFormattedText = "a<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "a<$htmlExpectedTag>$name</$htmlExpectedTag>$textAfter")
|
||||
}
|
||||
|
||||
// With text before and after, with spaces
|
||||
"$textBefore $markdownPattern$name$markdownPattern $textAfter"
|
||||
.let { markdownParser.parse(it) }
|
||||
.expect(expectedText = "$textBefore $plainTextPrefix$name$plainTextSuffix $textAfter",
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "$textBefore <$htmlExpectedTag>$name</$htmlExpectedTag> $textAfter")
|
||||
}
|
||||
}
|
||||
|
||||
private fun testTypeNewLines(name: String,
|
||||
markdownPattern: String,
|
||||
htmlExpectedTag: String) {
|
||||
// With new line inside the block
|
||||
"$markdownPattern$name\n$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name<br />$name</$htmlExpectedTag>")
|
||||
}
|
||||
|
||||
// With new line between two blocks
|
||||
"$markdownPattern$name$markdownPattern\n$markdownPattern$name$markdownPattern"
|
||||
.let {
|
||||
markdownParser.parse(it)
|
||||
.expect(expectedText = it,
|
||||
expectedFormattedText = "<$htmlExpectedTag>$name</$htmlExpectedTag><$htmlExpectedTag>$name</$htmlExpectedTag>")
|
||||
}
|
||||
}
|
||||
|
||||
private fun TextContent.expect(expectedText: String, expectedFormattedText: String?) {
|
||||
|
|
|
@ -26,13 +26,10 @@ import org.matrix.android.sdk.BuildConfig
|
|||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
|
||||
import org.matrix.android.sdk.internal.network.UserAgentHolder
|
||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
@ -97,9 +94,5 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
fun getSdkVersion(): String {
|
||||
return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
||||
}
|
||||
|
||||
fun decryptStream(inputStream: InputStream?, elementToDecrypt: ElementToDecrypt): InputStream? {
|
||||
return MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ sealed class WellknownResult {
|
|||
/**
|
||||
* Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter.
|
||||
*/
|
||||
object FailPrompt : WellknownResult()
|
||||
data class FailPrompt(val homeServerUrl: String?, val wellKnown: WellKnown?) : WellknownResult()
|
||||
|
||||
/**
|
||||
* Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process.
|
||||
|
|
|
@ -33,7 +33,7 @@ interface ContentUploadStateTracker {
|
|||
object Idle : State()
|
||||
object EncryptingThumbnail : State()
|
||||
data class UploadingThumbnail(val current: Long, val total: Long) : State()
|
||||
object Encrypting : State()
|
||||
data class Encrypting(val current: Long, val total: Long) : State()
|
||||
data class Uploading(val current: Long, val total: Long) : State()
|
||||
object Success : State()
|
||||
data class Failure(val throwable: Throwable) : State()
|
||||
|
|
|
@ -239,6 +239,14 @@ fun Event.isVideoMessage(): Boolean {
|
|||
}
|
||||
}
|
||||
|
||||
fun Event.isAudioMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_AUDIO -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isFileMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
|
@ -246,6 +254,16 @@ fun Event.isFileMessage(): Boolean {
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
fun Event.isAttachmentMessage(): Boolean {
|
||||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_IMAGE,
|
||||
MessageType.MSGTYPE_AUDIO,
|
||||
MessageType.MSGTYPE_VIDEO,
|
||||
MessageType.MSGTYPE_FILE -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.getRelationContent(): RelationDefaultContent? {
|
||||
return if (isEncrypted()) {
|
||||
|
|
|
@ -83,4 +83,43 @@ interface ProfileService {
|
|||
* @param refreshData set to true to fetch data from the homeserver
|
||||
*/
|
||||
fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>>
|
||||
|
||||
/**
|
||||
* Get the pending 3Pids, i.e. ThreePids that have requested a token, but not yet validated by the user.
|
||||
*/
|
||||
fun getPendingThreePids(): List<ThreePid>
|
||||
|
||||
/**
|
||||
* Get the pending 3Pids Live
|
||||
*/
|
||||
fun getPendingThreePidsLive(): LiveData<List<ThreePid>>
|
||||
|
||||
/**
|
||||
* Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list.
|
||||
*/
|
||||
fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Validate a code received by text message
|
||||
*/
|
||||
fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
|
||||
*/
|
||||
fun finalizeAddingThreePid(threePid: ThreePid,
|
||||
uiaSession: String?,
|
||||
accountPassword: String?,
|
||||
matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Cancel adding a threepid. It will remove locally stored data about this ThreePid
|
||||
*/
|
||||
fun cancelAddingThreePid(threePid: ThreePid,
|
||||
matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Remove a 3Pid from the Matrix account.
|
||||
*/
|
||||
fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
||||
|
|
|
@ -110,13 +110,13 @@ interface SendService {
|
|||
* Schedule this message to be resent
|
||||
* @param localEcho the unsent local echo
|
||||
*/
|
||||
fun resendTextMessage(localEcho: TimelineEvent): Cancelable?
|
||||
fun resendTextMessage(localEcho: TimelineEvent): Cancelable
|
||||
|
||||
/**
|
||||
* Schedule this message to be resent
|
||||
* @param localEcho the unsent local echo
|
||||
*/
|
||||
fun resendMediaMessage(localEcho: TimelineEvent): Cancelable?
|
||||
fun resendMediaMessage(localEcho: TimelineEvent): Cancelable
|
||||
|
||||
/**
|
||||
* Remove this failed message from the timeline
|
||||
|
@ -124,8 +124,16 @@ interface SendService {
|
|||
*/
|
||||
fun deleteFailedEcho(localEcho: TimelineEvent)
|
||||
|
||||
/**
|
||||
* Delete all the events in one of the sending states
|
||||
*/
|
||||
fun clearSendingQueue()
|
||||
|
||||
/**
|
||||
* Cancel sending a specific event. It has to be in one of the sending states
|
||||
*/
|
||||
fun cancelSend(eventId: String)
|
||||
|
||||
/**
|
||||
* Resend all failed messages one by one (and keep order)
|
||||
*/
|
||||
|
|
|
@ -37,7 +37,8 @@ enum class SendState {
|
|||
internal companion object {
|
||||
val HAS_FAILED_STATES = listOf(UNDELIVERED, FAILED_UNKNOWN_DEVICES)
|
||||
val IS_SENT_STATES = listOf(SENT, SYNCED)
|
||||
val IS_SENDING_STATES = listOf(UNSENT, ENCRYPTING, SENDING)
|
||||
val IS_PROGRESSING_STATES = listOf(ENCRYPTING, SENDING)
|
||||
val IS_SENDING_STATES = IS_PROGRESSING_STATES + UNSENT
|
||||
val PENDING_STATES = IS_SENDING_STATES + HAS_FAILED_STATES
|
||||
}
|
||||
|
||||
|
@ -45,5 +46,7 @@ enum class SendState {
|
|||
|
||||
fun hasFailed() = HAS_FAILED_STATES.contains(this)
|
||||
|
||||
fun isInProgress() = IS_PROGRESSING_STATES.contains(this)
|
||||
|
||||
fun isSending() = IS_SENDING_STATES.contains(this)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.auth.registration
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
|
@ -33,9 +36,6 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
|||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
/**
|
||||
* This class execute the registration request and is responsible to keep the session of interactive authentication
|
||||
|
@ -193,7 +193,7 @@ internal class DefaultRegistrationWizard(
|
|||
val registrationParams = pendingSessionData.currentThreePidData?.registrationParams
|
||||
?: throw IllegalStateException("developer error, no pending three pid")
|
||||
val safeCurrentData = pendingSessionData.currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url the send the code")
|
||||
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url to send the code")
|
||||
val validationBody = ValidationCodeBody(
|
||||
clientSecret = pendingSessionData.clientSecret,
|
||||
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
|
||||
|
|
|
@ -20,9 +20,14 @@ package org.matrix.android.sdk.internal.crypto.attachments
|
|||
import android.util.Base64
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
|
||||
import org.matrix.android.sdk.internal.util.base64ToBase64Url
|
||||
import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64
|
||||
import org.matrix.android.sdk.internal.util.base64UrlToBase64
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
|
@ -35,8 +40,121 @@ internal object MXEncryptedAttachments {
|
|||
private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
|
||||
private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
|
||||
|
||||
fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
|
||||
val t0 = System.currentTimeMillis()
|
||||
val secureRandom = SecureRandom()
|
||||
val initVectorBytes = ByteArray(16) { 0.toByte() }
|
||||
|
||||
val ivRandomPart = ByteArray(8)
|
||||
secureRandom.nextBytes(ivRandomPart)
|
||||
|
||||
System.arraycopy(ivRandomPart, 0, initVectorBytes, 0, ivRandomPart.size)
|
||||
|
||||
val key = ByteArray(32)
|
||||
secureRandom.nextBytes(key)
|
||||
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
|
||||
outputFile.outputStream().use { outputStream ->
|
||||
val encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
|
||||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var read: Int
|
||||
var encodedBytes: ByteArray
|
||||
clearStream.use { inputStream ->
|
||||
val estimatedSize = inputStream.available()
|
||||
progress.invoke(0, estimatedSize)
|
||||
read = inputStream.read(data)
|
||||
var totalRead = read
|
||||
while (read != -1) {
|
||||
progress.invoke(totalRead, estimatedSize)
|
||||
encodedBytes = encryptCipher.update(data, 0, read)
|
||||
messageDigest.update(encodedBytes, 0, encodedBytes.size)
|
||||
outputStream.write(encodedBytes)
|
||||
read = inputStream.read(data)
|
||||
totalRead += read
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt the latest chunk
|
||||
encodedBytes = encryptCipher.doFinal()
|
||||
messageDigest.update(encodedBytes, 0, encodedBytes.size)
|
||||
outputStream.write(encodedBytes)
|
||||
}
|
||||
|
||||
return EncryptedFileInfo(
|
||||
url = null,
|
||||
mimetype = mimetype,
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
ext = true,
|
||||
keyOps = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
k = base64ToBase64Url(Base64.encodeToString(key, Base64.DEFAULT))
|
||||
),
|
||||
iv = Base64.encodeToString(initVectorBytes, Base64.DEFAULT).replace("\n", "").replace("=", ""),
|
||||
hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
|
||||
v = "v2"
|
||||
)
|
||||
.also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
}
|
||||
|
||||
// fun cipherInputStream(attachmentStream: InputStream, mimetype: String?): Pair<DigestInputStream, EncryptedFileInfo> {
|
||||
// val secureRandom = SecureRandom()
|
||||
//
|
||||
// // generate a random iv key
|
||||
// // Half of the IV is random, the lower order bits are zeroed
|
||||
// // such that the counter never wraps.
|
||||
// // See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
|
||||
// val initVectorBytes = ByteArray(16) { 0.toByte() }
|
||||
//
|
||||
// val ivRandomPart = ByteArray(8)
|
||||
// secureRandom.nextBytes(ivRandomPart)
|
||||
//
|
||||
// System.arraycopy(ivRandomPart, 0, initVectorBytes, 0, ivRandomPart.size)
|
||||
//
|
||||
// val key = ByteArray(32)
|
||||
// secureRandom.nextBytes(key)
|
||||
//
|
||||
// val encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
// val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
// val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
// encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
//
|
||||
// val cipherInputStream = CipherInputStream(attachmentStream, encryptCipher)
|
||||
//
|
||||
// // Could it be possible to get the digest on the fly instead of
|
||||
// val info = EncryptedFileInfo(
|
||||
// url = null,
|
||||
// mimetype = mimetype,
|
||||
// key = EncryptedFileKey(
|
||||
// alg = "A256CTR",
|
||||
// ext = true,
|
||||
// key_ops = listOf("encrypt", "decrypt"),
|
||||
// kty = "oct",
|
||||
// k = base64ToBase64Url(Base64.encodeToString(key, Base64.DEFAULT))
|
||||
// ),
|
||||
// iv = Base64.encodeToString(initVectorBytes, Base64.DEFAULT).replace("\n", "").replace("=", ""),
|
||||
// //hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
|
||||
// v = "v2"
|
||||
// )
|
||||
//
|
||||
// val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
// return DigestInputStream(cipherInputStream, messageDigest) to info
|
||||
// }
|
||||
//
|
||||
// fun updateInfoWithDigest(digestInputStream: DigestInputStream, info: EncryptedFileInfo): EncryptedFileInfo {
|
||||
// return info.copy(
|
||||
// hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(digestInputStream.messageDigest.digest(), Base64.DEFAULT)))
|
||||
// )
|
||||
// }
|
||||
|
||||
/***
|
||||
* Encrypt an attachment stream.
|
||||
* DO NOT USE for big files, it will load all in memory
|
||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||
* @param mimetype the mime type
|
||||
* @return the encryption file info
|
||||
|
@ -59,14 +177,14 @@ internal object MXEncryptedAttachments {
|
|||
val key = ByteArray(32)
|
||||
secureRandom.nextBytes(key)
|
||||
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
byteArrayOutputStream.use { outputStream ->
|
||||
val encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
|
||||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var read: Int
|
||||
var encodedBytes: ByteArray
|
||||
|
@ -85,44 +203,26 @@ internal object MXEncryptedAttachments {
|
|||
encodedBytes = encryptCipher.doFinal()
|
||||
messageDigest.update(encodedBytes, 0, encodedBytes.size)
|
||||
outputStream.write(encodedBytes)
|
||||
|
||||
return EncryptionResult(
|
||||
encryptedFileInfo = EncryptedFileInfo(
|
||||
url = null,
|
||||
mimetype = mimetype,
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
ext = true,
|
||||
keyOps = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
k = base64ToBase64Url(Base64.encodeToString(key, Base64.DEFAULT))
|
||||
),
|
||||
iv = Base64.encodeToString(initVectorBytes, Base64.DEFAULT).replace("\n", "").replace("=", ""),
|
||||
hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
|
||||
v = "v2"
|
||||
),
|
||||
encryptedByteArray = outputStream.toByteArray()
|
||||
)
|
||||
.also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an attachment
|
||||
*
|
||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||
* @param encryptedFileInfo the encryption file info
|
||||
* @return the decrypted attachment stream
|
||||
*/
|
||||
fun decryptAttachment(attachmentStream: InputStream?, encryptedFileInfo: EncryptedFileInfo?): InputStream? {
|
||||
if (encryptedFileInfo?.isValid() != true) {
|
||||
Timber.e("## decryptAttachment() : some fields are not defined, or invalid key fields")
|
||||
return null
|
||||
}
|
||||
|
||||
val elementToDecrypt = encryptedFileInfo.toElementToDecrypt()
|
||||
|
||||
return decryptAttachment(attachmentStream, elementToDecrypt)
|
||||
return EncryptionResult(
|
||||
encryptedFileInfo = EncryptedFileInfo(
|
||||
url = null,
|
||||
mimetype = mimetype,
|
||||
key = EncryptedFileKey(
|
||||
alg = "A256CTR",
|
||||
ext = true,
|
||||
keyOps = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
k = base64ToBase64Url(Base64.encodeToString(key, Base64.DEFAULT))
|
||||
),
|
||||
iv = Base64.encodeToString(initVectorBytes, Base64.DEFAULT).replace("\n", "").replace("=", ""),
|
||||
hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
|
||||
v = "v2"
|
||||
),
|
||||
encryptedByteArray = byteArrayOutputStream.toByteArray()
|
||||
)
|
||||
.also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,84 +230,61 @@ internal object MXEncryptedAttachments {
|
|||
*
|
||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||
* @param elementToDecrypt the elementToDecrypt info
|
||||
* @return the decrypted attachment stream
|
||||
* @param outputStream the outputStream where the decrypted attachment will be write.
|
||||
* @return true in case of success, false in case of error
|
||||
*/
|
||||
fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?): InputStream? {
|
||||
fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean {
|
||||
// sanity checks
|
||||
if (null == attachmentStream || elementToDecrypt == null) {
|
||||
Timber.e("## decryptAttachment() : null stream")
|
||||
return null
|
||||
return false
|
||||
}
|
||||
|
||||
val t0 = System.currentTimeMillis()
|
||||
|
||||
ByteArrayOutputStream().use { outputStream ->
|
||||
try {
|
||||
val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
|
||||
val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT)
|
||||
try {
|
||||
val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
|
||||
val initVectorBytes = Base64.decode(elementToDecrypt.iv, Base64.DEFAULT)
|
||||
|
||||
val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
val decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(key, SECRET_KEY_SPEC_ALGORITHM)
|
||||
val ivParameterSpec = IvParameterSpec(initVectorBytes)
|
||||
decryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
|
||||
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
val messageDigest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM)
|
||||
|
||||
var read: Int
|
||||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var decodedBytes: ByteArray
|
||||
var read: Int
|
||||
val data = ByteArray(CRYPTO_BUFFER_SIZE)
|
||||
var decodedBytes: ByteArray
|
||||
|
||||
attachmentStream.use { inputStream ->
|
||||
attachmentStream.use { inputStream ->
|
||||
read = inputStream.read(data)
|
||||
while (read != -1) {
|
||||
messageDigest.update(data, 0, read)
|
||||
decodedBytes = decryptCipher.update(data, 0, read)
|
||||
outputStream.write(decodedBytes)
|
||||
read = inputStream.read(data)
|
||||
while (read != -1) {
|
||||
messageDigest.update(data, 0, read)
|
||||
decodedBytes = decryptCipher.update(data, 0, read)
|
||||
outputStream.write(decodedBytes)
|
||||
read = inputStream.read(data)
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt the last chunk
|
||||
decodedBytes = decryptCipher.doFinal()
|
||||
outputStream.write(decodedBytes)
|
||||
|
||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||
|
||||
if (elementToDecrypt.sha256 != currentDigestValue) {
|
||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||
return null
|
||||
}
|
||||
|
||||
return outputStream.toByteArray().inputStream()
|
||||
.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## decryptAttachment() failed: OOM")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptAttachment() failed")
|
||||
}
|
||||
|
||||
// decrypt the last chunk
|
||||
decodedBytes = decryptCipher.doFinal()
|
||||
outputStream.write(decodedBytes)
|
||||
|
||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))
|
||||
|
||||
if (elementToDecrypt.sha256 != currentDigestValue) {
|
||||
Timber.e("## decryptAttachment() : Digest value mismatch")
|
||||
return false
|
||||
}
|
||||
|
||||
return true.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## decryptAttachment() failed: OOM")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptAttachment() failed")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL conversion methods
|
||||
*/
|
||||
|
||||
private fun base64UrlToBase64(base64Url: String): String {
|
||||
return base64Url.replace('-', '+')
|
||||
.replace('_', '/')
|
||||
}
|
||||
|
||||
internal fun base64ToBase64Url(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("\\+".toRegex(), "-")
|
||||
.replace('/', '_')
|
||||
.replace("=", "")
|
||||
}
|
||||
|
||||
private fun base64ToUnpaddedBase64(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("=", "")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.attachments
|
||||
|
||||
import android.util.Base64
|
||||
import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
class MatrixDigestCheckInputStream(
|
||||
inputStream: InputStream?,
|
||||
private val expectedDigest: String
|
||||
) : FilterInputStream(inputStream) {
|
||||
|
||||
private val digest = MessageDigest.getInstance("SHA-256")
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(): Int {
|
||||
val b = `in`.read()
|
||||
if (b >= 0) {
|
||||
digest.update(b.toByte())
|
||||
}
|
||||
|
||||
if (b == -1) {
|
||||
ensureDigest()
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun read(
|
||||
b: ByteArray,
|
||||
off: Int,
|
||||
len: Int): Int {
|
||||
val n = `in`.read(b, off, len)
|
||||
if (n > 0) {
|
||||
digest.update(b, off, n)
|
||||
}
|
||||
|
||||
if (n == -1) {
|
||||
ensureDigest()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun ensureDigest() {
|
||||
val currentDigestValue = base64ToUnpaddedBase64(Base64.encodeToString(digest.digest(), Base64.DEFAULT))
|
||||
if (currentDigestValue != expectedDigest) {
|
||||
throw IOException("Bad digest")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,14 +55,14 @@ internal fun getEmojiForCode(code: Int): EmojiRepresentation {
|
|||
31 -> EmojiRepresentation("🤖", R.string.verification_emoji_robot, R.drawable.ic_verification_robot)
|
||||
32 -> EmojiRepresentation("🎩", R.string.verification_emoji_hat, R.drawable.ic_verification_hat)
|
||||
33 -> EmojiRepresentation("👓", R.string.verification_emoji_glasses, R.drawable.ic_verification_glasses)
|
||||
34 -> EmojiRepresentation("🔧", R.string.verification_emoji_wrench, R.drawable.ic_verification_wrench)
|
||||
34 -> EmojiRepresentation("🔧", R.string.verification_emoji_spanner, R.drawable.ic_verification_spanner)
|
||||
35 -> EmojiRepresentation("🎅", R.string.verification_emoji_santa, R.drawable.ic_verification_santa)
|
||||
36 -> EmojiRepresentation("👍", R.string.verification_emoji_thumbsup, R.drawable.ic_verification_thumbs_up)
|
||||
36 -> EmojiRepresentation("👍", R.string.verification_emoji_thumbs_up, R.drawable.ic_verification_thumbs_up)
|
||||
37 -> EmojiRepresentation("☂️", R.string.verification_emoji_umbrella, R.drawable.ic_verification_umbrella)
|
||||
38 -> EmojiRepresentation("⌛", R.string.verification_emoji_hourglass, R.drawable.ic_verification_hourglass)
|
||||
39 -> EmojiRepresentation("⏰", R.string.verification_emoji_clock, R.drawable.ic_verification_clock)
|
||||
40 -> EmojiRepresentation("🎁", R.string.verification_emoji_gift, R.drawable.ic_verification_gift)
|
||||
41 -> EmojiRepresentation("💡", R.string.verification_emoji_lightbulb, R.drawable.ic_verification_light_bulb)
|
||||
41 -> EmojiRepresentation("💡", R.string.verification_emoji_light_bulb, R.drawable.ic_verification_light_bulb)
|
||||
42 -> EmojiRepresentation("📕", R.string.verification_emoji_book, R.drawable.ic_verification_book)
|
||||
43 -> EmojiRepresentation("✏️", R.string.verification_emoji_pencil, R.drawable.ic_verification_pencil)
|
||||
44 -> EmojiRepresentation("📎", R.string.verification_emoji_paperclip, R.drawable.ic_verification_paperclip)
|
||||
|
@ -74,7 +74,7 @@ internal fun getEmojiForCode(code: Int): EmojiRepresentation {
|
|||
50 -> EmojiRepresentation("🏁", R.string.verification_emoji_flag, R.drawable.ic_verification_flag)
|
||||
51 -> EmojiRepresentation("🚂", R.string.verification_emoji_train, R.drawable.ic_verification_train)
|
||||
52 -> EmojiRepresentation("🚲", R.string.verification_emoji_bicycle, R.drawable.ic_verification_bicycle)
|
||||
53 -> EmojiRepresentation("✈️", R.string.verification_emoji_airplane, R.drawable.ic_verification_airplane)
|
||||
53 -> EmojiRepresentation("✈️", R.string.verification_emoji_aeroplane, R.drawable.ic_verification_aeroplane)
|
||||
54 -> EmojiRepresentation("🚀", R.string.verification_emoji_rocket, R.drawable.ic_verification_rocket)
|
||||
55 -> EmojiRepresentation("🏆", R.string.verification_emoji_trophy, R.drawable.ic_verification_trophy)
|
||||
56 -> EmojiRepresentation("⚽", R.string.verification_emoji_ball, R.drawable.ic_verification_ball)
|
||||
|
@ -82,7 +82,7 @@ internal fun getEmojiForCode(code: Int): EmojiRepresentation {
|
|||
58 -> EmojiRepresentation("🎺", R.string.verification_emoji_trumpet, R.drawable.ic_verification_trumpet)
|
||||
59 -> EmojiRepresentation("🔔", R.string.verification_emoji_bell, R.drawable.ic_verification_bell)
|
||||
60 -> EmojiRepresentation("⚓", R.string.verification_emoji_anchor, R.drawable.ic_verification_anchor)
|
||||
61 -> EmojiRepresentation("🎧", R.string.verification_emoji_headphone, R.drawable.ic_verification_headphone)
|
||||
61 -> EmojiRepresentation("🎧", R.string.verification_emoji_headphones, R.drawable.ic_verification_headphones)
|
||||
62 -> EmojiRepresentation("📁", R.string.verification_emoji_folder, R.drawable.ic_verification_folder)
|
||||
/* 63 */ else -> EmojiRepresentation("📌", R.string.verification_emoji_pin, R.drawable.ic_verification_pin)
|
||||
}
|
||||
|
|
|
@ -20,18 +20,24 @@ package org.matrix.android.sdk.internal.database
|
|||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 4L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
|
@ -63,4 +69,17 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo4(realm: DynamicRealm) {
|
||||
Timber.d("Step 3 -> 4")
|
||||
realm.schema.create("PendingThreePidEntity")
|
||||
.addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
|
||||
.addField(PendingThreePidEntityFields.EMAIL, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.MSISDN, String::class.java)
|
||||
.addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
|
||||
.addField(PendingThreePidEntityFields.SID, String::class.java)
|
||||
.setRequired(PendingThreePidEntityFields.SID, true)
|
||||
.addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,14 @@ package org.matrix.android.sdk.internal.database
|
|||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.internal.database.model.SessionRealmModule
|
||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.di.UserMd5
|
||||
import org.matrix.android.sdk.internal.session.SessionModule
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
@ -46,20 +47,16 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||
val migration: RealmSessionStoreMigration,
|
||||
context: Context) {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 3L
|
||||
}
|
||||
|
||||
// Keep legacy preferences name for compatibility reason
|
||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||
|
||||
fun create(): RealmConfiguration {
|
||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||
if (shouldClearRealm) {
|
||||
Timber.v("************************************************************")
|
||||
Timber.v("The realm file session was corrupted and couldn't be loaded.")
|
||||
Timber.v("The file has been deleted to recover.")
|
||||
Timber.v("************************************************************")
|
||||
Timber.e("************************************************************")
|
||||
Timber.e("The realm file session was corrupted and couldn't be loaded.")
|
||||
Timber.e("The file has been deleted to recover.")
|
||||
Timber.e("************************************************************")
|
||||
deleteRealmFiles()
|
||||
}
|
||||
sharedPreferences.edit {
|
||||
|
@ -74,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.modules(SessionRealmModule())
|
||||
.schemaVersion(SESSION_STORE_SCHEMA_VERSION)
|
||||
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
||||
.migration(migration)
|
||||
.build()
|
||||
|
||||
|
@ -90,6 +87,11 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||
|
||||
// Delete all the realm files of the session
|
||||
private fun deleteRealmFiles() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.e("No op because it is a debug build")
|
||||
return
|
||||
}
|
||||
|
||||
listOf(REALM_NAME, "$REALM_NAME.lock", "$REALM_NAME.note", "$REALM_NAME.management").forEach { file ->
|
||||
try {
|
||||
File(directory, file).deleteRecursively()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
|
||||
/**
|
||||
* This class is used to store pending threePid data, when user wants to add a threePid to his account
|
||||
*/
|
||||
internal open class PendingThreePidEntity(
|
||||
var email: String? = null,
|
||||
var msisdn: String? = null,
|
||||
var clientSecret: String = "",
|
||||
var sendAttempt: Int = 0,
|
||||
var sid: String = "",
|
||||
var submitUrl: String? = null
|
||||
) : RealmObject()
|
|
@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule
|
|||
RoomSummaryEntity::class,
|
||||
RoomTagEntity::class,
|
||||
SyncEntity::class,
|
||||
PendingThreePidEntity::class,
|
||||
UserEntity::class,
|
||||
IgnoredUserEntity::class,
|
||||
BreadcrumbsEntity::class,
|
||||
|
|
|
@ -24,6 +24,7 @@ import okio.BufferedSink
|
|||
import okio.ForwardingSink
|
||||
import okio.Sink
|
||||
import okio.buffer
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import java.io.IOException
|
||||
|
||||
internal class ProgressRequestBody(private val delegate: RequestBody,
|
||||
|
@ -35,15 +36,13 @@ internal class ProgressRequestBody(private val delegate: RequestBody,
|
|||
return delegate.contentType()
|
||||
}
|
||||
|
||||
override fun contentLength(): Long {
|
||||
try {
|
||||
return delegate.contentLength()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
override fun isOneShot() = delegate.isOneShot()
|
||||
|
||||
return -1
|
||||
}
|
||||
override fun isDuplex() = delegate.isDuplex()
|
||||
|
||||
val length = tryThis { delegate.contentLength() } ?: -1
|
||||
|
||||
override fun contentLength() = length
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
|
|
|
@ -143,20 +143,22 @@ internal class DefaultFileService @Inject constructor(
|
|||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
|
||||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## decrypt file")
|
||||
val decryptedStream = MXEncryptedAttachments.decryptAttachment(source.inputStream(), elementToDecrypt)
|
||||
Timber.v("## FileService: decrypt file")
|
||||
val decryptSuccess = MXEncryptedAttachments.decryptAttachment(
|
||||
source.inputStream(),
|
||||
elementToDecrypt,
|
||||
destFile.outputStream().buffered()
|
||||
)
|
||||
response.close()
|
||||
if (decryptedStream == null) {
|
||||
if (!decryptSuccess) {
|
||||
return@flatMap Try.Failure(IllegalStateException("Decryption error"))
|
||||
} else {
|
||||
decryptedStream.use {
|
||||
writeToFile(decryptedStream, destFile)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeToFile(source.inputStream(), destFile)
|
||||
response.close()
|
||||
}
|
||||
} else {
|
||||
Timber.v("## FileService: cache hit for $url")
|
||||
}
|
||||
|
||||
Try.just(copyFile(destFile, downloadMode))
|
||||
|
|
|
@ -74,8 +74,8 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
|||
updateState(key, progressData)
|
||||
}
|
||||
|
||||
internal fun setEncrypting(key: String) {
|
||||
val progressData = ContentUploadStateTracker.State.Encrypting
|
||||
internal fun setEncrypting(key: String, current: Long, total: Long) {
|
||||
val progressData = ContentUploadStateTracker.State.Encrypting(current, total)
|
||||
updateState(key, progressData)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,13 +23,16 @@ import com.squareup.moshi.Moshi
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.BufferedSink
|
||||
import okio.source
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.internal.di.Authenticated
|
||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||
|
@ -38,6 +41,7 @@ import org.matrix.android.sdk.internal.network.toFailure
|
|||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class FileUploader @Inject constructor(@Authenticated
|
||||
|
@ -54,7 +58,21 @@ internal class FileUploader @Inject constructor(@Authenticated
|
|||
filename: String?,
|
||||
mimeType: String?,
|
||||
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||
val uploadBody = file.asRequestBody(mimeType?.toMediaTypeOrNull())
|
||||
val uploadBody = object : RequestBody() {
|
||||
override fun contentLength() = file.length()
|
||||
|
||||
// Disable okhttp auto resend for 'large files'
|
||||
override fun isOneShot() = contentLength() == 0L || contentLength() >= 1_000_000
|
||||
|
||||
override fun contentType(): MediaType? {
|
||||
return mimeType?.toMediaTypeOrNull()
|
||||
}
|
||||
|
||||
override fun writeTo(sink: BufferedSink) {
|
||||
file.source().use { sink.writeAll(it) }
|
||||
}
|
||||
}
|
||||
|
||||
return upload(uploadBody, filename, progressListener)
|
||||
}
|
||||
|
||||
|
@ -70,14 +88,18 @@ internal class FileUploader @Inject constructor(@Authenticated
|
|||
filename: String?,
|
||||
mimeType: String?,
|
||||
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val inputStream = context.contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
||||
|
||||
inputStream.use {
|
||||
uploadByteArray(it.readBytes(), filename, mimeType, progressListener)
|
||||
}
|
||||
val inputStream = withContext(Dispatchers.IO) {
|
||||
context.contentResolver.openInputStream(uri)
|
||||
} ?: throw FileNotFoundException()
|
||||
val workingFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
||||
workingFile.outputStream().use {
|
||||
inputStream.copyTo(it)
|
||||
}
|
||||
return uploadFile(workingFile, filename, mimeType, progressListener).also {
|
||||
tryThis { workingFile.delete() }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
|
||||
val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
|
||||
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.content
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ImageCompressor @Inject constructor() {
|
||||
suspend fun compress(
|
||||
context: Context,
|
||||
imageFile: File,
|
||||
desiredWidth: Int,
|
||||
desiredHeight: Int,
|
||||
desiredQuality: Int = 80): File {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val compressedBitmap = BitmapFactory.Options().run {
|
||||
inJustDecodeBounds = true
|
||||
decodeBitmap(imageFile, this)
|
||||
inSampleSize = calculateInSampleSize(outWidth, outHeight, desiredWidth, desiredHeight)
|
||||
inJustDecodeBounds = false
|
||||
decodeBitmap(imageFile, this)?.let {
|
||||
rotateBitmap(imageFile, it)
|
||||
}
|
||||
} ?: return@withContext imageFile
|
||||
|
||||
val destinationFile = createDestinationFile(context)
|
||||
|
||||
runCatching {
|
||||
destinationFile.outputStream().use {
|
||||
compressedBitmap.compress(Bitmap.CompressFormat.JPEG, desiredQuality, it)
|
||||
}
|
||||
}
|
||||
|
||||
return@withContext destinationFile
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotateBitmap(file: File, bitmap: Bitmap): Bitmap {
|
||||
file.inputStream().use { inputStream ->
|
||||
try {
|
||||
ExifInterface(inputStream).let { exifInfo ->
|
||||
val orientation = exifInfo.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
|
||||
val matrix = Matrix()
|
||||
when (orientation) {
|
||||
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
|
||||
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
|
||||
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
|
||||
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
|
||||
ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
|
||||
ExifInterface.ORIENTATION_TRANSPOSE -> {
|
||||
matrix.preRotate(-90f)
|
||||
matrix.preScale(-1f, 1f)
|
||||
}
|
||||
ExifInterface.ORIENTATION_TRANSVERSE -> {
|
||||
matrix.preRotate(90f)
|
||||
matrix.preScale(-1f, 1f)
|
||||
}
|
||||
else -> return bitmap
|
||||
}
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Cannot read orientation")
|
||||
}
|
||||
}
|
||||
return bitmap
|
||||
}
|
||||
|
||||
// https://developer.android.com/topic/performance/graphics/load-bitmap
|
||||
private fun calculateInSampleSize(width: Int, height: Int, desiredWidth: Int, desiredHeight: Int): Int {
|
||||
var inSampleSize = 1
|
||||
|
||||
if (width > desiredWidth || height > desiredHeight) {
|
||||
val halfHeight: Int = height / 2
|
||||
val halfWidth: Int = width / 2
|
||||
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while (halfHeight / inSampleSize >= desiredHeight && halfWidth / inSampleSize >= desiredWidth) {
|
||||
inSampleSize *= 2
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize
|
||||
}
|
||||
|
||||
private fun decodeBitmap(file: File, options: BitmapFactory.Options = BitmapFactory.Options()): Bitmap? {
|
||||
return try {
|
||||
file.inputStream().use { inputStream ->
|
||||
BitmapFactory.decodeStream(inputStream, null, options)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Cannot decode Bitmap")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDestinationFile(context: Context): File {
|
||||
return File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
||||
}
|
||||
}
|
|
@ -22,8 +22,7 @@ import android.graphics.BitmapFactory
|
|||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import id.zelory.compressor.Compressor
|
||||
import id.zelory.compressor.constraint.default
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
|
@ -37,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
|
@ -71,6 +71,8 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
@Inject lateinit var fileUploader: FileUploader
|
||||
@Inject lateinit var contentUploadStateTracker: DefaultContentUploadStateTracker
|
||||
@Inject lateinit var fileService: DefaultFileService
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
@Inject lateinit var imageCompressor: ImageCompressor
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
|
@ -98,9 +100,15 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
val attachment = params.attachment
|
||||
val allCancelled = params.events.all { cancelSendTracker.isCancelRequestedFor(it.eventId, it.roomId) }
|
||||
if (allCancelled) {
|
||||
// there is no point in uploading the image!
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("## Send: Work cancelled by user") }
|
||||
}
|
||||
|
||||
var newImageAttributes: NewImageAttributes? = null
|
||||
val attachment = params.attachment
|
||||
val filesToDelete = mutableListOf<File>()
|
||||
|
||||
try {
|
||||
val inputStream = context.contentResolver.openInputStream(attachment.queryUri)
|
||||
|
@ -112,124 +120,100 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
)
|
||||
)
|
||||
|
||||
inputStream.use {
|
||||
var uploadedThumbnailUrl: String? = null
|
||||
var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null
|
||||
|
||||
ThumbnailExtractor.extractThumbnail(context, params.attachment)?.let { thumbnailData ->
|
||||
val thumbnailProgressListener = object : ProgressRequestBody.Listener {
|
||||
override fun onProgress(current: Long, total: Long) {
|
||||
notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("Encrypt thumbnail")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
|
||||
uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo
|
||||
fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
|
||||
"thumb_${attachment.name}",
|
||||
"application/octet-stream",
|
||||
thumbnailProgressListener)
|
||||
} else {
|
||||
fileUploader.uploadByteArray(thumbnailData.bytes,
|
||||
"thumb_${attachment.name}",
|
||||
thumbnailData.mimeType,
|
||||
thumbnailProgressListener)
|
||||
}
|
||||
|
||||
uploadedThumbnailUrl = contentUploadResponse.contentUri
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Thumbnail update failed")
|
||||
}
|
||||
}
|
||||
|
||||
val progressListener = object : ProgressRequestBody.Listener {
|
||||
override fun onProgress(current: Long, total: Long) {
|
||||
notifyTracker(params) {
|
||||
if (isStopped) {
|
||||
contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
|
||||
} else {
|
||||
contentUploadStateTracker.setProgress(it, current, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
|
||||
|
||||
return try {
|
||||
// Compressor library works with File instead of Uri for now. Since Scoped Storage doesn't allow us to access files directly, we should
|
||||
// copy it to a cache folder by using InputStream and OutputStream.
|
||||
// https://github.com/zetbaitsu/Compressor/pull/150
|
||||
// As soon as the above PR is merged, we can use attachment.queryUri instead of creating a cacheFile.
|
||||
var cacheFile = File.createTempFile(attachment.name ?: UUID.randomUUID().toString(), ".jpg", context.cacheDir)
|
||||
cacheFile.parentFile?.mkdirs()
|
||||
if (cacheFile.exists()) {
|
||||
cacheFile.delete()
|
||||
}
|
||||
cacheFile.createNewFile()
|
||||
cacheFile.deleteOnExit()
|
||||
|
||||
val outputStream = cacheFile.outputStream()
|
||||
outputStream.use {
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
|
||||
if (attachment.type == ContentAttachmentData.Type.IMAGE && params.compressBeforeSending) {
|
||||
cacheFile = Compressor.compress(context, cacheFile) {
|
||||
default(
|
||||
width = MAX_IMAGE_SIZE,
|
||||
height = MAX_IMAGE_SIZE
|
||||
)
|
||||
}.also { compressedFile ->
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeFile(compressedFile.absolutePath, options)
|
||||
val fileSize = compressedFile.length().toInt()
|
||||
newImageAttributes = NewImageAttributes(
|
||||
options.outWidth,
|
||||
options.outHeight,
|
||||
fileSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("Encrypt file")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) }
|
||||
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(cacheFile.inputStream(), attachment.getSafeMimeType())
|
||||
uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo
|
||||
|
||||
fileUploader
|
||||
.uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener)
|
||||
} else {
|
||||
fileUploader
|
||||
.uploadFile(cacheFile, attachment.name, attachment.getSafeMimeType(), progressListener)
|
||||
}
|
||||
|
||||
// If it's a file update the file service so that it does not redownload?
|
||||
if (params.attachment.type == ContentAttachmentData.Type.FILE) {
|
||||
context.contentResolver.openInputStream(attachment.queryUri)?.let {
|
||||
fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it)
|
||||
}
|
||||
}
|
||||
|
||||
handleSuccess(params,
|
||||
contentUploadResponse.contentUri,
|
||||
uploadedFileEncryptedFileInfo,
|
||||
uploadedThumbnailUrl,
|
||||
uploadedThumbnailEncryptedFileInfo,
|
||||
newImageAttributes)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t)
|
||||
handleFailure(params, t)
|
||||
// always use a temporary file, it guaranties that we could report progress on upload and simplifies the flows
|
||||
val workingFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
||||
.also { filesToDelete.add(it) }
|
||||
workingFile.outputStream().use { outputStream ->
|
||||
inputStream.use { inputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
val uploadThumbnailResult = dealWithThumbnail(params)
|
||||
|
||||
val progressListener = object : ProgressRequestBody.Listener {
|
||||
override fun onProgress(current: Long, total: Long) {
|
||||
notifyTracker(params) {
|
||||
if (isStopped) {
|
||||
contentUploadStateTracker.setFailure(it, Throwable("Cancelled"))
|
||||
} else {
|
||||
contentUploadStateTracker.setProgress(it, current, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null
|
||||
|
||||
return try {
|
||||
val fileToUpload: File
|
||||
var newImageAttributes: NewImageAttributes? = null
|
||||
|
||||
if (attachment.type == ContentAttachmentData.Type.IMAGE && params.compressBeforeSending) {
|
||||
fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
|
||||
.also { compressedFile ->
|
||||
// Get new Bitmap size
|
||||
compressedFile.inputStream().use {
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
val bitmap = BitmapFactory.decodeStream(it, null, options)
|
||||
val fileSize = bitmap?.byteCount ?: 0
|
||||
newImageAttributes = NewImageAttributes(
|
||||
options.outWidth,
|
||||
options.outHeight,
|
||||
fileSize
|
||||
)
|
||||
}
|
||||
}
|
||||
.also { filesToDelete.add(it) }
|
||||
} else {
|
||||
fileToUpload = workingFile
|
||||
}
|
||||
|
||||
val contentUploadResponse = if (params.isEncrypted) {
|
||||
Timber.v("## FileService: Encrypt file")
|
||||
|
||||
val tmpEncrypted = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
||||
.also { filesToDelete.add(it) }
|
||||
|
||||
uploadedFileEncryptedFileInfo =
|
||||
MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), tmpEncrypted) { read, total ->
|
||||
notifyTracker(params) {
|
||||
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
Timber.v("## FileService: Uploading file")
|
||||
|
||||
fileUploader
|
||||
.uploadFile(tmpEncrypted, attachment.name, "application/octet-stream", progressListener)
|
||||
} else {
|
||||
Timber.v("## FileService: Clear file")
|
||||
fileUploader
|
||||
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
|
||||
}
|
||||
|
||||
Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}")
|
||||
try {
|
||||
context.contentResolver.openInputStream(attachment.queryUri)?.let {
|
||||
fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it)
|
||||
}
|
||||
Timber.v("## FileService: cache storage updated")
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## FileService: Failed to update file cache")
|
||||
}
|
||||
|
||||
handleSuccess(params,
|
||||
contentUploadResponse.contentUri,
|
||||
uploadedFileEncryptedFileInfo,
|
||||
uploadThumbnailResult?.uploadedThumbnailUrl,
|
||||
uploadThumbnailResult?.uploadedThumbnailEncryptedFileInfo,
|
||||
newImageAttributes)
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "## FileService: ERROR ${t.localizedMessage}")
|
||||
handleFailure(params, t)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
Timber.e(e, "## FileService: ERROR")
|
||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
||||
return Result.success(
|
||||
WorkerParamsFactory.toData(
|
||||
|
@ -238,9 +222,61 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
)
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
// Delete all temporary files
|
||||
filesToDelete.forEach {
|
||||
tryThis { it.delete() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class UploadThumbnailResult(
|
||||
val uploadedThumbnailUrl: String,
|
||||
val uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo?
|
||||
)
|
||||
|
||||
/**
|
||||
* If appropriate, it will create and upload a thumbnail
|
||||
*/
|
||||
private suspend fun dealWithThumbnail(params: Params): UploadThumbnailResult? {
|
||||
return ThumbnailExtractor.extractThumbnail(context, params.attachment)
|
||||
?.let { thumbnailData ->
|
||||
val thumbnailProgressListener = object : ProgressRequestBody.Listener {
|
||||
override fun onProgress(current: Long, total: Long) {
|
||||
notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) }
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (params.isEncrypted) {
|
||||
Timber.v("Encrypt thumbnail")
|
||||
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
|
||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
|
||||
val contentUploadResponse = fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
|
||||
"thumb_${params.attachment.name}",
|
||||
"application/octet-stream",
|
||||
thumbnailProgressListener)
|
||||
UploadThumbnailResult(
|
||||
contentUploadResponse.contentUri,
|
||||
encryptionResult.encryptedFileInfo
|
||||
)
|
||||
} else {
|
||||
val contentUploadResponse = fileUploader.uploadByteArray(thumbnailData.bytes,
|
||||
"thumb_${params.attachment.name}",
|
||||
thumbnailData.mimeType,
|
||||
thumbnailProgressListener)
|
||||
UploadThumbnailResult(
|
||||
contentUploadResponse.contentUri,
|
||||
null
|
||||
)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "Thumbnail upload failed")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFailure(params: Params, failure: Throwable): Result {
|
||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, failure) }
|
||||
|
||||
|
@ -259,7 +295,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
thumbnailUrl: String?,
|
||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||
newImageAttributes: NewImageAttributes?): Result {
|
||||
Timber.v("handleSuccess $attachmentUrl, work is stopped $isStopped")
|
||||
notifyTracker(params) { contentUploadStateTracker.setSuccess(it) }
|
||||
|
||||
val updatedEvents = params.events
|
||||
|
@ -268,7 +303,9 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
}
|
||||
|
||||
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isEncrypted)
|
||||
return Result.success(WorkerParamsFactory.toData(sendParams))
|
||||
return Result.success(WorkerParamsFactory.toData(sendParams)).also {
|
||||
Timber.v("## handleSuccess $attachmentUrl, work is stopped $isStopped")
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEvent(event: Event,
|
||||
|
|
|
@ -61,19 +61,23 @@ internal class DefaultContentDownloadStateTracker @Inject constructor() : Progre
|
|||
// private fun URL.toKey() = toString()
|
||||
|
||||
override fun update(url: String, bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
Timber.v("## DL Progress url:$url read:$bytesRead total:$contentLength done:$done")
|
||||
if (done) {
|
||||
updateState(url, ContentDownloadStateTracker.State.Success)
|
||||
} else {
|
||||
updateState(url, ContentDownloadStateTracker.State.Downloading(bytesRead, contentLength, contentLength == -1L))
|
||||
mainHandler.post {
|
||||
Timber.v("## DL Progress url:$url read:$bytesRead total:$contentLength done:$done")
|
||||
if (done) {
|
||||
updateState(url, ContentDownloadStateTracker.State.Success)
|
||||
} else {
|
||||
updateState(url, ContentDownloadStateTracker.State.Downloading(bytesRead, contentLength, contentLength == -1L))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun error(url: String, errorCode: Int) {
|
||||
Timber.v("## DL Progress Error code:$errorCode")
|
||||
updateState(url, ContentDownloadStateTracker.State.Failure(errorCode))
|
||||
listeners[url]?.forEach {
|
||||
tryThis { it.onDownloadStateUpdate(ContentDownloadStateTracker.State.Failure(errorCode)) }
|
||||
mainHandler.post {
|
||||
Timber.v("## DL Progress Error code:$errorCode")
|
||||
updateState(url, ContentDownloadStateTracker.State.Failure(errorCode))
|
||||
listeners[url]?.forEach {
|
||||
tryThis { it.onDownloadStateUpdate(ContentDownloadStateTracker.State.Failure(errorCode)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
|||
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.identity.toMedium
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments.base64ToBase64Url
|
||||
import org.matrix.android.sdk.internal.crypto.tools.withOlmUtility
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
|
@ -32,6 +31,7 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityHashDetail
|
|||
import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpParams
|
||||
import org.matrix.android.sdk.internal.session.identity.model.IdentityLookUpResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.base64ToBase64Url
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddEmailBody(
|
||||
/**
|
||||
* Required. A unique string generated by the client, and used to identify the validation attempt.
|
||||
* It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must not exceed
|
||||
* 255 characters and it must not be empty.
|
||||
*/
|
||||
@Json(name = "client_secret")
|
||||
val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The email address to validate.
|
||||
*/
|
||||
@Json(name = "email")
|
||||
val email: String,
|
||||
|
||||
/**
|
||||
* Required. The server will only send an email if the send_attempt is a number greater than the most
|
||||
* recent one which it has seen, scoped to that email + client_secret pair. This is to avoid repeatedly
|
||||
* sending the same email in the case of request retries between the POSTing user and the identity server.
|
||||
* The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
|
||||
* If they do not, the server should respond with success but not resend the email.
|
||||
*/
|
||||
@Json(name = "send_attempt")
|
||||
val sendAttempt: Int
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddEmailResponse(
|
||||
/**
|
||||
* Required. The session ID. Session IDs are opaque strings that must consist entirely
|
||||
* of the characters [0-9a-zA-Z.=_-]. Their length must not exceed 255 characters and they must not be empty.
|
||||
*/
|
||||
@Json(name = "sid")
|
||||
val sid: String
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddMsisdnBody(
|
||||
/**
|
||||
* Required. A unique string generated by the client, and used to identify the validation attempt.
|
||||
* It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must not exceed
|
||||
* 255 characters and it must not be empty.
|
||||
*/
|
||||
@Json(name = "client_secret")
|
||||
val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The two-letter uppercase ISO-3166-1 alpha-2 country code that the number in
|
||||
* phone_number should be parsed as if it were dialled from.
|
||||
*/
|
||||
@Json(name = "country")
|
||||
val country: String,
|
||||
|
||||
/**
|
||||
* Required. The phone number to validate.
|
||||
*/
|
||||
@Json(name = "phone_number")
|
||||
val phoneNumber: String,
|
||||
|
||||
/**
|
||||
* Required. The server will only send an SMS if the send_attempt is a number greater than the most
|
||||
* recent one which it has seen, scoped to that country + phone_number + client_secret triple. This
|
||||
* is to avoid repeatedly sending the same SMS in the case of request retries between the POSTing user
|
||||
* and the identity server. The client should increment this value if they desire a new SMS (e.g. a
|
||||
* reminder) to be sent.
|
||||
*/
|
||||
@Json(name = "send_attempt")
|
||||
val sendAttempt: Int
|
||||
)
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AddMsisdnResponse(
|
||||
/**
|
||||
* Required. The session ID. Session IDs are opaque strings that must consist entirely of the characters [0-9a-zA-Z.=_-].
|
||||
* Their length must not exceed 255 characters and they must not be empty.
|
||||
*/
|
||||
@Json(name = "sid")
|
||||
val sid: String,
|
||||
|
||||
/**
|
||||
* An optional field containing a URL where the client must submit the validation token to, with identical parameters to the Identity
|
||||
* Service API's POST /validate/email/submitToken endpoint (without the requirement for an access token).
|
||||
* The homeserver must send this token to the user (if applicable), who should then be prompted to provide it to the client.
|
||||
*
|
||||
* If this field is not present, the client can assume that verification will happen without the client's involvement provided
|
||||
* the homeserver advertises this specification version in the /versions response (ie: r0.5.0).
|
||||
*/
|
||||
@Json(name = "submit_url")
|
||||
val submitUrl: String? = null,
|
||||
|
||||
/* ==========================================================================================
|
||||
* It seems that the homeserver is sending more data, we may need it
|
||||
* ========================================================================================== */
|
||||
|
||||
@Json(name = "msisdn")
|
||||
val msisdn: String? = null,
|
||||
|
||||
@Json(name = "intl_fmt")
|
||||
val formattedMsisdn: String? = null,
|
||||
|
||||
@Json(name = "success")
|
||||
val success: Boolean? = null
|
||||
)
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal abstract class AddThreePidTask : Task<AddThreePidTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultAddThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
private val eventBus: EventBus) : AddThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
when (params.threePid) {
|
||||
is ThreePid.Email -> addEmail(params.threePid)
|
||||
is ThreePid.Msisdn -> addMsisdn(params.threePid)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun addEmail(threePid: ThreePid.Email) {
|
||||
val clientSecret = UUID.randomUUID().toString()
|
||||
val sendAttempt = 1
|
||||
|
||||
val result = executeRequest<AddEmailResponse>(eventBus) {
|
||||
val body = AddEmailBody(
|
||||
clientSecret = clientSecret,
|
||||
email = threePid.email,
|
||||
sendAttempt = sendAttempt
|
||||
)
|
||||
apiCall = profileAPI.addEmail(body)
|
||||
}
|
||||
|
||||
// Store as a pending three pid
|
||||
monarchy.awaitTransaction { realm ->
|
||||
PendingThreePid(
|
||||
threePid = threePid,
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
sid = result.sid,
|
||||
submitUrl = null
|
||||
)
|
||||
.let { pendingThreePidMapper.map(it) }
|
||||
.let { realm.copyToRealm(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun addMsisdn(threePid: ThreePid.Msisdn) {
|
||||
val clientSecret = UUID.randomUUID().toString()
|
||||
val sendAttempt = 1
|
||||
|
||||
// Get country code and national number from the phone number
|
||||
val phoneNumber = threePid.msisdn
|
||||
val phoneNumberUtil = PhoneNumberUtil.getInstance()
|
||||
val parsedNumber = phoneNumberUtil.parse(phoneNumber, null)
|
||||
val countryCode = parsedNumber.countryCode
|
||||
val country = phoneNumberUtil.getRegionCodeForCountryCode(countryCode)
|
||||
|
||||
val result = executeRequest<AddMsisdnResponse>(eventBus) {
|
||||
val body = AddMsisdnBody(
|
||||
clientSecret = clientSecret,
|
||||
country = country,
|
||||
phoneNumber = parsedNumber.nationalNumber.toString(),
|
||||
sendAttempt = sendAttempt
|
||||
)
|
||||
apiCall = profileAPI.addMsisdn(body)
|
||||
}
|
||||
|
||||
// Store as a pending three pid
|
||||
monarchy.awaitTransaction { realm ->
|
||||
PendingThreePid(
|
||||
threePid = threePid,
|
||||
clientSecret = clientSecret,
|
||||
sendAttempt = sendAttempt,
|
||||
sid = result.sid,
|
||||
submitUrl = result.submitUrl
|
||||
)
|
||||
.let { pendingThreePidMapper.map(it) }
|
||||
.let { realm.copyToRealm(it) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.profile.ProfileService
|
|||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.content.FileUploader
|
||||
|
@ -44,6 +45,11 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
|||
private val getProfileInfoTask: GetProfileInfoTask,
|
||||
private val setDisplayNameTask: SetDisplayNameTask,
|
||||
private val setAvatarUrlTask: SetAvatarUrlTask,
|
||||
private val addThreePidTask: AddThreePidTask,
|
||||
private val validateSmsCodeTask: ValidateSmsCodeTask,
|
||||
private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask,
|
||||
private val deleteThreePidTask: DeleteThreePidTask,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
private val fileUploader: FileUploader) : ProfileService {
|
||||
|
||||
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||
|
@ -116,9 +122,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
|||
override fun getThreePidsLive(refreshData: Boolean): LiveData<List<ThreePid>> {
|
||||
if (refreshData) {
|
||||
// Force a refresh of the values
|
||||
refreshUserThreePidsTask
|
||||
.configureWith()
|
||||
.executeBy(taskExecutor)
|
||||
refreshThreePids()
|
||||
}
|
||||
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
|
@ -126,6 +130,95 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
|||
{ it.asDomain() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun refreshThreePids() {
|
||||
refreshUserThreePidsTask
|
||||
.configureWith()
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getPendingThreePids(): List<ThreePid> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ it.where<PendingThreePidEntity>() },
|
||||
{ pendingThreePidMapper.map(it).threePid }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPendingThreePidsLive(): LiveData<List<ThreePid>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ it.where<PendingThreePidEntity>() },
|
||||
{ pendingThreePidMapper.map(it).threePid }
|
||||
)
|
||||
}
|
||||
|
||||
override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return addThreePidTask
|
||||
.configureWith(AddThreePidTask.Params(threePid)) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return validateSmsCodeTask
|
||||
.configureWith(ValidateSmsCodeTask.Params(threePid, code)) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun finalizeAddingThreePid(threePid: ThreePid,
|
||||
uiaSession: String?,
|
||||
accountPassword: String?,
|
||||
matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return finalizeAddingThreePidTask
|
||||
.configureWith(FinalizeAddingThreePidTask.Params(
|
||||
threePid = threePid,
|
||||
session = uiaSession,
|
||||
accountPassword = accountPassword,
|
||||
userWantsToCancel = false
|
||||
)) {
|
||||
callback = alsoRefresh(matrixCallback)
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun cancelAddingThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return finalizeAddingThreePidTask
|
||||
.configureWith(FinalizeAddingThreePidTask.Params(
|
||||
threePid = threePid,
|
||||
session = null,
|
||||
accountPassword = null,
|
||||
userWantsToCancel = true
|
||||
)) {
|
||||
callback = alsoRefresh(matrixCallback)
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the callback to fetch 3Pids from the server in case of success
|
||||
*/
|
||||
private fun alsoRefresh(callback: MatrixCallback<Unit>): MatrixCallback<Unit> {
|
||||
return object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
refreshThreePids()
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return deleteThreePidTask
|
||||
.configureWith(DeleteThreePidTask.Params(threePid)) {
|
||||
callback = alsoRefresh(matrixCallback)
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserThreePidEntity.asDomain(): ThreePid {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class DeleteThreePidBody(
|
||||
/**
|
||||
* Required. The medium of the third party identifier being removed. One of: ["email", "msisdn"]
|
||||
*/
|
||||
@Json(name = "medium") val medium: String,
|
||||
/**
|
||||
* Required. The third party address being removed.
|
||||
*/
|
||||
@Json(name = "address") val address: String
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class DeleteThreePidResponse(
|
||||
/**
|
||||
* Required. An indicator as to whether or not the homeserver was able to unbind the 3PID from
|
||||
* the identity server. success indicates that the identity server has unbound the identifier
|
||||
* whereas no-support indicates that the identity server refuses to support the request or the
|
||||
* homeserver was not able to determine an identity server to unbind from. One of: ["no-support", "success"]
|
||||
*/
|
||||
@Json(name = "id_server_unbind_result")
|
||||
val idServerUnbindResult: String? = null
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.identity.toMedium
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal abstract class DeleteThreePidTask : Task<DeleteThreePidTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultDeleteThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
private val eventBus: EventBus) : DeleteThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
executeRequest<DeleteThreePidResponse>(eventBus) {
|
||||
val body = DeleteThreePidBody(
|
||||
medium = params.threePid.toMedium(),
|
||||
address = params.threePid.value
|
||||
)
|
||||
apiCall = profileAPI.deleteThreePid(body)
|
||||
}
|
||||
|
||||
// We do not really care about the result for the moment
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class FinalizeAddThreePidBody(
|
||||
/**
|
||||
* Required. The client secret used in the session with the homeserver.
|
||||
*/
|
||||
@Json(name = "client_secret")
|
||||
val clientSecret: String,
|
||||
|
||||
/**
|
||||
* Required. The session identifier given by the homeserver.
|
||||
*/
|
||||
@Json(name = "sid")
|
||||
val sid: String,
|
||||
|
||||
/**
|
||||
* Additional authentication information for the user-interactive authentication API.
|
||||
*/
|
||||
@Json(name = "auth")
|
||||
val auth: UserPasswordAuth?
|
||||
)
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import javax.inject.Inject
|
||||
|
||||
internal abstract class FinalizeAddingThreePidTask : Task<FinalizeAddingThreePidTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid,
|
||||
val session: String?,
|
||||
val accountPassword: String?,
|
||||
val userWantsToCancel: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus) : FinalizeAddingThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
if (params.userWantsToCancel.not()) {
|
||||
// Get the required pending data
|
||||
val pendingThreePids = monarchy.fetchAllMappedSync(
|
||||
{ it.where(PendingThreePidEntity::class.java) },
|
||||
{ pendingThreePidMapper.map(it) }
|
||||
)
|
||||
.firstOrNull { it.threePid == params.threePid }
|
||||
?: throw IllegalArgumentException("unknown threepid")
|
||||
|
||||
try {
|
||||
executeRequest<Unit>(eventBus) {
|
||||
val body = FinalizeAddThreePidBody(
|
||||
clientSecret = pendingThreePids.clientSecret,
|
||||
sid = pendingThreePids.sid,
|
||||
auth = if (params.session != null && params.accountPassword != null) {
|
||||
UserPasswordAuth(
|
||||
session = params.session,
|
||||
user = userId,
|
||||
password = params.accountPassword
|
||||
)
|
||||
} else null
|
||||
)
|
||||
apiCall = profileAPI.finalizeAddThreePid(body)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
throw throwable.toRegistrationFlowResponse()
|
||||
?.let { Failure.RegistrationFlowError(it) }
|
||||
?: throwable
|
||||
}
|
||||
}
|
||||
|
||||
cleanupDatabase(params)
|
||||
}
|
||||
|
||||
private suspend fun cleanupDatabase(params: Params) {
|
||||
// Delete the pending three pid
|
||||
monarchy.awaitTransaction { realm ->
|
||||
realm.where(PendingThreePidEntity::class.java)
|
||||
.equalTo(PendingThreePidEntityFields.EMAIL, params.threePid.value)
|
||||
.or()
|
||||
.equalTo(PendingThreePidEntityFields.MSISDN, params.threePid.value)
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
|
||||
internal data class PendingThreePid(
|
||||
val threePid: ThreePid,
|
||||
val clientSecret: String,
|
||||
val sendAttempt: Int,
|
||||
// For Msisdn and Email
|
||||
val sid: String,
|
||||
// For Msisdn only
|
||||
val submitUrl: String?
|
||||
)
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class PendingThreePidMapper @Inject constructor() {
|
||||
|
||||
fun map(entity: PendingThreePidEntity): PendingThreePid {
|
||||
return PendingThreePid(
|
||||
threePid = entity.email?.let { ThreePid.Email(it) }
|
||||
?: entity.msisdn?.let { ThreePid.Msisdn(it) }
|
||||
?: error("Invalid data"),
|
||||
clientSecret = entity.clientSecret,
|
||||
sendAttempt = entity.sendAttempt,
|
||||
sid = entity.sid,
|
||||
submitUrl = entity.submitUrl
|
||||
)
|
||||
}
|
||||
|
||||
fun map(domain: PendingThreePid): PendingThreePidEntity {
|
||||
return PendingThreePidEntity(
|
||||
email = domain.threePid.takeIf { it is ThreePid.Email }?.value,
|
||||
msisdn = domain.threePid.takeIf { it is ThreePid.Msisdn }?.value,
|
||||
clientSecret = domain.clientSecret,
|
||||
sendAttempt = domain.sendAttempt,
|
||||
sid = domain.sid,
|
||||
submitUrl = domain.submitUrl
|
||||
)
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@
|
|||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.auth.registration.SuccessResult
|
||||
import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
|
||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
|
@ -26,9 +28,9 @@ import retrofit2.http.GET
|
|||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Url
|
||||
|
||||
internal interface ProfileAPI {
|
||||
|
||||
/**
|
||||
* Get the combined profile information for this user.
|
||||
* This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
|
||||
|
@ -71,4 +73,35 @@ internal interface ProfileAPI {
|
|||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind")
|
||||
fun unbindThreePid(@Body body: UnbindThreePidBody): Call<UnbindThreePidResponse>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken")
|
||||
fun addEmail(@Body body: AddEmailBody): Call<AddEmailResponse>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-msisdn-requesttoken
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken")
|
||||
fun addMsisdn(@Body body: AddMsisdnBody): Call<AddMsisdnResponse>
|
||||
|
||||
/**
|
||||
* Validate Msisdn code (same model than for Identity server API)
|
||||
*/
|
||||
@POST
|
||||
fun validateMsisdn(@Url url: String,
|
||||
@Body params: ValidationCodeBody): Call<SuccessResult>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add")
|
||||
fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call<Unit>
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete
|
||||
*/
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete")
|
||||
fun deleteThreePid(@Body body: DeleteThreePidBody): Call<DeleteThreePidResponse>
|
||||
}
|
||||
|
|
|
@ -58,4 +58,16 @@ internal abstract class ProfileModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindSetAvatarUrlTask(task: DefaultSetAvatarUrlTask): SetAvatarUrlTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindAddThreePidTask(task: DefaultAddThreePidTask): AddThreePidTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindValidateSmsCodeTask(task: DefaultValidateSmsCodeTask): ValidateSmsCodeTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFinalizeAddingThreePidTask(task: DefaultFinalizeAddingThreePidTask): FinalizeAddingThreePidTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindDeleteThreePidTask(task: DefaultDeleteThreePidTask): DeleteThreePidTask
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.profile
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.internal.auth.registration.SuccessResult
|
||||
import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface ValidateSmsCodeTask : Task<ValidateSmsCodeTask.Params, Unit> {
|
||||
data class Params(
|
||||
val threePid: ThreePid.Msisdn,
|
||||
val code: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultValidateSmsCodeTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
@SessionDatabase
|
||||
private val monarchy: Monarchy,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
private val eventBus: EventBus
|
||||
) : ValidateSmsCodeTask {
|
||||
|
||||
override suspend fun execute(params: ValidateSmsCodeTask.Params) {
|
||||
// Search the pending ThreePid
|
||||
val pendingThreePids = monarchy.fetchAllMappedSync(
|
||||
{ it.where(PendingThreePidEntity::class.java) },
|
||||
{ pendingThreePidMapper.map(it) }
|
||||
)
|
||||
.firstOrNull { it.threePid == params.threePid }
|
||||
?: throw IllegalArgumentException("unknown threepid")
|
||||
|
||||
val url = pendingThreePids.submitUrl ?: throw IllegalArgumentException("invalid threepid")
|
||||
val body = ValidationCodeBody(
|
||||
clientSecret = pendingThreePids.clientSecret,
|
||||
sid = pendingThreePids.sid,
|
||||
code = params.code
|
||||
)
|
||||
val result = executeRequest<SuccessResult>(eventBus) {
|
||||
apiCall = profileAPI.validateMsisdn(url, body)
|
||||
}
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
throw Failure.SuccessError
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,8 @@ package org.matrix.android.sdk.internal.session.room
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.matrix.android.sdk.api.session.file.FileService
|
||||
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
|
||||
import org.matrix.android.sdk.api.session.room.RoomService
|
||||
|
@ -75,9 +77,6 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
|
|||
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
@ -105,14 +104,6 @@ internal abstract class RoomModule {
|
|||
.builder()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesTextContentRenderer(): TextContentRenderer {
|
||||
return TextContentRenderer
|
||||
.builder()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* We cannot use work manager cancellation mechanism because cancelling a work will just ignore
|
||||
* any follow up send that was already queued.
|
||||
* We use this class to track cancel requests, the workers will look for this to check for cancellation request
|
||||
* and just ignore the work request and continue by returning success.
|
||||
*
|
||||
* Known limitation, for now requests are not persisted
|
||||
*/
|
||||
@SessionScope
|
||||
internal class CancelSendTracker @Inject constructor() {
|
||||
|
||||
data class Request(
|
||||
val localId: String,
|
||||
val roomId: String
|
||||
)
|
||||
|
||||
private val cancellingRequests = ArrayList<Request>()
|
||||
|
||||
fun markLocalEchoForCancel(eventId: String, roomId: String) {
|
||||
synchronized(cancellingRequests) {
|
||||
cancellingRequests.add(Request(eventId, roomId))
|
||||
}
|
||||
}
|
||||
|
||||
fun isCancelRequestedFor(eventId: String?, roomId: String?): Boolean {
|
||||
val index = synchronized(cancellingRequests) {
|
||||
cancellingRequests.indexOfFirst { it.localId == eventId && it.roomId == roomId }
|
||||
}
|
||||
return index != -1
|
||||
}
|
||||
|
||||
fun markCancelled(eventId: String, roomId: String) {
|
||||
synchronized(cancellingRequests) {
|
||||
val index = cancellingRequests.indexOfFirst { it.localId == eventId && it.roomId == roomId }
|
||||
if (index != -1) {
|
||||
cancellingRequests.removeAt(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,24 +17,35 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.Operation
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.isImageMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.OptionItem
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||
import org.matrix.android.sdk.api.session.room.send.SendService
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.CancelableBag
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||
|
@ -44,7 +55,6 @@ import org.matrix.android.sdk.internal.util.CancelableWork
|
|||
import org.matrix.android.sdk.internal.worker.AlwaysSuccessfulWorker
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.startChain
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -60,7 +70,8 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
private val cryptoService: CryptoService,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val roomEventSender: RoomEventSender
|
||||
private val roomEventSender: RoomEventSender,
|
||||
private val cancelSendTracker: CancelSendTracker
|
||||
) : SendService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
|
@ -127,48 +138,83 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
.let { timelineSendEventWorkCommon.postWork(roomId, it) }
|
||||
}
|
||||
|
||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable {
|
||||
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
return sendEvent(localEcho.root)
|
||||
}
|
||||
return null
|
||||
return NoOpCancellable
|
||||
}
|
||||
|
||||
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
|
||||
if (localEcho.root.isImageMessage() && localEcho.root.sendState.hasFailed()) {
|
||||
// TODO this need a refactoring of attachement sending
|
||||
// val clearContent = localEcho.root.getClearContent()
|
||||
// val messageContent = clearContent?.toModel<MessageContent>() ?: return null
|
||||
// when (messageContent.type) {
|
||||
// MessageType.MSGTYPE_IMAGE -> {
|
||||
// val imageContent = clearContent.toModel<MessageImageContent>() ?: return null
|
||||
// val url = imageContent.url ?: return null
|
||||
// if (url.startsWith("mxc://")) {
|
||||
// //TODO
|
||||
// } else {
|
||||
// //The image has not yet been sent
|
||||
// val attachmentData = ContentAttachmentData(
|
||||
// size = imageContent.info!!.size.toLong(),
|
||||
// mimeType = imageContent.info.mimeType!!,
|
||||
// width = imageContent.info.width.toLong(),
|
||||
// height = imageContent.info.height.toLong(),
|
||||
// name = imageContent.body,
|
||||
// path = imageContent.url,
|
||||
// type = ContentAttachmentData.Type.IMAGE
|
||||
// )
|
||||
// monarchy.runTransactionSync {
|
||||
// EventEntity.where(it,eventId = localEcho.root.eventId ?: "").findFirst()?.let {
|
||||
// it.sendState = SendState.UNSENT
|
||||
// }
|
||||
// }
|
||||
// return internalSendMedia(localEcho.root,attachmentData)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return null
|
||||
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable {
|
||||
if (localEcho.root.sendState.hasFailed()) {
|
||||
val clearContent = localEcho.root.getClearContent()
|
||||
val messageContent = clearContent?.toModel<MessageContent>() as? MessageWithAttachmentContent ?: return NoOpCancellable
|
||||
|
||||
val url = messageContent.getFileUrl() ?: return NoOpCancellable
|
||||
if (url.startsWith("mxc://")) {
|
||||
// We need to resend only the message as the attachment is ok
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
return sendEvent(localEcho.root)
|
||||
}
|
||||
|
||||
// we need to resend the media
|
||||
return when (messageContent) {
|
||||
is MessageImageContent -> {
|
||||
// The image has not yet been sent
|
||||
val attachmentData = ContentAttachmentData(
|
||||
size = messageContent.info!!.size.toLong(),
|
||||
mimeType = messageContent.info.mimeType!!,
|
||||
width = messageContent.info.width.toLong(),
|
||||
height = messageContent.info.height.toLong(),
|
||||
name = messageContent.body,
|
||||
queryUri = Uri.parse(messageContent.url),
|
||||
type = ContentAttachmentData.Type.IMAGE
|
||||
)
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||
}
|
||||
is MessageVideoContent -> {
|
||||
val attachmentData = ContentAttachmentData(
|
||||
size = messageContent.videoInfo?.size ?: 0L,
|
||||
mimeType = messageContent.mimeType,
|
||||
width = messageContent.videoInfo?.width?.toLong(),
|
||||
height = messageContent.videoInfo?.height?.toLong(),
|
||||
duration = messageContent.videoInfo?.duration?.toLong(),
|
||||
name = messageContent.body,
|
||||
queryUri = Uri.parse(messageContent.url),
|
||||
type = ContentAttachmentData.Type.VIDEO
|
||||
)
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||
}
|
||||
is MessageFileContent -> {
|
||||
val attachmentData = ContentAttachmentData(
|
||||
size = messageContent.info!!.size,
|
||||
mimeType = messageContent.info.mimeType!!,
|
||||
name = messageContent.body,
|
||||
queryUri = Uri.parse(messageContent.url),
|
||||
type = ContentAttachmentData.Type.FILE
|
||||
)
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||
}
|
||||
is MessageAudioContent -> {
|
||||
val attachmentData = ContentAttachmentData(
|
||||
size = messageContent.audioInfo?.size ?: 0,
|
||||
duration = messageContent.audioInfo?.duration?.toLong() ?: 0L,
|
||||
mimeType = messageContent.audioInfo?.mimeType,
|
||||
name = messageContent.body,
|
||||
queryUri = Uri.parse(messageContent.url),
|
||||
type = ContentAttachmentData.Type.AUDIO
|
||||
)
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||
}
|
||||
else -> NoOpCancellable
|
||||
}
|
||||
}
|
||||
return null
|
||||
return NoOpCancellable
|
||||
}
|
||||
|
||||
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
||||
|
@ -196,16 +242,34 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun cancelSend(eventId: String) {
|
||||
cancelSendTracker.markLocalEchoForCancel(eventId, roomId)
|
||||
taskExecutor.executorScope.launch {
|
||||
localEchoRepository.deleteFailedEcho(roomId, eventId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun resendAllFailedMessages() {
|
||||
taskExecutor.executorScope.launch {
|
||||
val eventsToResend = localEchoRepository.getAllFailedEventsToResend(roomId)
|
||||
eventsToResend.forEach {
|
||||
sendEvent(it)
|
||||
if (it.root.isTextMessage()) {
|
||||
resendTextMessage(it)
|
||||
} else if (it.root.isAttachmentMessage()) {
|
||||
resendMediaMessage(it)
|
||||
}
|
||||
}
|
||||
localEchoRepository.updateSendState(roomId, eventsToResend.mapNotNull { it.eventId }, SendState.UNSENT)
|
||||
localEchoRepository.updateSendState(roomId, eventsToResend.map { it.eventId }, SendState.UNSENT)
|
||||
}
|
||||
}
|
||||
|
||||
// override fun failAllPendingMessages() {
|
||||
// taskExecutor.executorScope.launch {
|
||||
// val eventsToResend = localEchoRepository.getAllEventsWithStates(roomId, SendState.PENDING_STATES)
|
||||
// localEchoRepository.updateSendState(roomId, eventsToResend.map { it.eventId }, SendState.UNDELIVERED)
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun sendMedia(attachment: ContentAttachmentData,
|
||||
compressBeforeSending: Boolean,
|
||||
roomIds: Set<String>): Cancelable {
|
||||
|
|
|
@ -54,6 +54,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
|
||||
@Inject lateinit var crypto: CryptoService
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.v("Start Encrypt work")
|
||||
|
@ -61,7 +62,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
Timber.v("Start Encrypt work for event ${params.event.eventId}")
|
||||
Timber.v("## SendEvent: Start Encrypt work for event ${params.event.eventId}")
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
|
@ -75,6 +76,12 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
if (localEvent.eventId == null) {
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (cancelSendTracker.isCancelRequestedFor(localEvent.eventId, localEvent.roomId)) {
|
||||
return Result.success()
|
||||
.also { Timber.e("## SendEvent: Event sending has been cancelled ${localEvent.eventId}") }
|
||||
}
|
||||
|
||||
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||
|
||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
|||
import org.matrix.android.sdk.internal.database.helper.nextId
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
||||
|
@ -88,7 +87,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
|
||||
fun updateSendState(eventId: String, sendState: SendState) {
|
||||
Timber.v("Update local state of $eventId to ${sendState.name}")
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
|
||||
monarchy.writeAsync { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
|
@ -114,9 +113,13 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
|
||||
suspend fun deleteFailedEcho(roomId: String, localEcho: TimelineEvent) {
|
||||
deleteFailedEcho(roomId, localEcho.eventId)
|
||||
}
|
||||
|
||||
suspend fun deleteFailedEcho(roomId: String, eventId: String?) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
||||
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId ?: "").findFirst()?.deleteFromRealm()
|
||||
EventEntity.where(realm, eventId = eventId ?: "").findFirst()?.deleteFromRealm()
|
||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
@ -142,45 +145,47 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
}
|
||||
|
||||
fun getAllFailedEventsToResend(roomId: String): List<Event> {
|
||||
fun getAllFailedEventsToResend(roomId: String): List<TimelineEvent> {
|
||||
return getAllEventsWithStates(roomId, SendState.HAS_FAILED_STATES)
|
||||
}
|
||||
|
||||
fun getAllEventsWithStates(roomId: String, states : List<SendState>): List<TimelineEvent> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
TimelineEventEntity
|
||||
.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES)
|
||||
.findAllInRoomWithSendStates(realm, roomId, states)
|
||||
.sortedByDescending { it.displayIndex }
|
||||
.mapNotNull { it.root?.asDomain() }
|
||||
.mapNotNull { it?.let { timelineEventMapper.map(it) } }
|
||||
.filter { event ->
|
||||
when (event.getClearType()) {
|
||||
when (event.root.getClearType()) {
|
||||
EventType.MESSAGE,
|
||||
EventType.REDACTION,
|
||||
EventType.REACTION -> {
|
||||
val content = event.getClearContent().toModel<MessageContent>()
|
||||
val content = event.root.getClearContent().toModel<MessageContent>()
|
||||
if (content != null) {
|
||||
when (content.msgType) {
|
||||
MessageType.MSGTYPE_EMOTE,
|
||||
MessageType.MSGTYPE_NOTICE,
|
||||
MessageType.MSGTYPE_LOCATION,
|
||||
MessageType.MSGTYPE_TEXT -> {
|
||||
true
|
||||
}
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
MessageType.MSGTYPE_FILE,
|
||||
MessageType.MSGTYPE_VIDEO,
|
||||
MessageType.MSGTYPE_IMAGE,
|
||||
MessageType.MSGTYPE_AUDIO -> {
|
||||
// need to resend the attachment
|
||||
false
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
Timber.e("Cannot resend message ${event.type} / ${content.msgType}")
|
||||
Timber.e("Cannot resend message ${event.root.getClearType()} / ${content.msgType}")
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.e("Unsupported message to resend ${event.type}")
|
||||
Timber.e("Unsupported message to resend ${event.root.getClearType()}")
|
||||
false
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.e("Unsupported message to resend ${event.type}")
|
||||
Timber.e("Unsupported message to resend ${event.root.getClearType()}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.send
|
|||
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.commonmark.renderer.text.TextContentRenderer
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -29,11 +28,10 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class MarkdownParser @Inject constructor(
|
||||
private val parser: Parser,
|
||||
private val htmlRenderer: HtmlRenderer,
|
||||
private val textContentRenderer: TextContentRenderer
|
||||
private val htmlRenderer: HtmlRenderer
|
||||
) {
|
||||
|
||||
private val mdSpecialChars = "[`_\\-\\*>\\.\\[\\]#~]".toRegex()
|
||||
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex()
|
||||
|
||||
fun parse(text: String): TextContent {
|
||||
// If no special char are detected, just return plain text
|
||||
|
@ -54,8 +52,8 @@ internal class MarkdownParser @Inject constructor(
|
|||
return if (isFormattedTextPertinent(text, cleanHtmlText)) {
|
||||
// According to https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes:
|
||||
// The plain text version of the HTML should be provided in the body.
|
||||
val plainText = textContentRenderer.render(document)
|
||||
TextContent(plainText, cleanHtmlText.postTreatment())
|
||||
// But it caused too many problems so it has been removed in #2002
|
||||
TextContent(text, cleanHtmlText.postTreatment())
|
||||
} else {
|
||||
TextContent(text)
|
||||
}
|
||||
|
@ -72,6 +70,7 @@ internal class MarkdownParser @Inject constructor(
|
|||
// Remove extra space before and after the content
|
||||
.trim()
|
||||
// There is no need to include new line in an html-like source
|
||||
.replace("\n", "")
|
||||
// But new line can be in embedded code block, so do not remove them
|
||||
// .replace("\n", "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.v("Start dispatch sending multiple event work")
|
||||
Timber.v("## SendEvent: Start dispatch sending multiple event work")
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
@ -72,18 +72,21 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||
}
|
||||
// Transmit the error if needed?
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
.also { Timber.e("## SendEvent: Work cancelled due to input error from parent ${params.lastFailureMessage}") }
|
||||
}
|
||||
|
||||
// Create a work for every event
|
||||
params.events.forEach { event ->
|
||||
if (params.isEncrypted) {
|
||||
Timber.v("Send event in encrypted room")
|
||||
localEchoRepository.updateSendState(event.eventId ?: "", SendState.ENCRYPTING)
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event ${event.eventId}")
|
||||
val encryptWork = createEncryptEventWork(params.sessionId, event, true)
|
||||
// Note that event will be replaced by the result of the previous work
|
||||
val sendWork = createSendEventWork(params.sessionId, event, false)
|
||||
timelineSendEventWorkCommon.postSequentialWorks(event.roomId!!, encryptWork, sendWork)
|
||||
} else {
|
||||
localEchoRepository.updateSendState(event.eventId ?: "", SendState.SENDING)
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event ${event.eventId}")
|
||||
val sendWork = createSendEventWork(params.sessionId, event, true)
|
||||
timelineSendEventWorkCommon.postWork(event.roomId!!, sendWork)
|
||||
}
|
||||
|
|
|
@ -39,13 +39,16 @@ internal class RoomEventSender @Inject constructor(
|
|||
) {
|
||||
fun sendEvent(event: Event): Cancelable {
|
||||
// Encrypted room handling
|
||||
return if (cryptoService.isRoomEncrypted(event.roomId ?: "")) {
|
||||
Timber.v("Send event in encrypted room")
|
||||
return if (cryptoService.isRoomEncrypted(event.roomId ?: "")
|
||||
&& !event.isEncrypted() // In case of resend where it's already encrypted so skip to send
|
||||
) {
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event ${event.eventId}")
|
||||
val encryptWork = createEncryptEventWork(event, true)
|
||||
// Note that event will be replaced by the result of the previous work
|
||||
val sendWork = createSendEventWork(event, false)
|
||||
timelineSendEventWorkCommon.postSequentialWorks(event.roomId ?: "", encryptWork, sendWork)
|
||||
} else {
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event ${event.eventId}")
|
||||
val sendWork = createSendEventWork(event, true)
|
||||
timelineSendEventWorkCommon.postWork(event.roomId ?: "", sendWork)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.matrix.android.sdk.internal.worker.getSessionComponent
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3
|
||||
// private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3
|
||||
|
||||
/**
|
||||
* Possible previous worker: [EncryptEventWorker] or first worker
|
||||
|
@ -56,12 +56,12 @@ internal class SendEventWorker(context: Context,
|
|||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var roomAPI: RoomAPI
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
.also { Timber.e("## SendEvent: Unable to parse work parameters") }
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
|
@ -75,22 +75,32 @@ internal class SendEventWorker(context: Context,
|
|||
.also { Timber.e("Work cancelled due to bad input data") }
|
||||
}
|
||||
|
||||
if (cancelSendTracker.isCancelRequestedFor(params.eventId, event.roomId)) {
|
||||
return Result.success()
|
||||
.also {
|
||||
cancelSendTracker.markCancelled(event.eventId, event.roomId)
|
||||
Timber.e("## SendEvent: Event sending has been cancelled ${params.eventId}")
|
||||
}
|
||||
}
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
localEchoRepository.updateSendState(event.eventId, SendState.UNDELIVERED)
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
|
||||
return try {
|
||||
sendEvent(event.eventId, event.roomId, event.type, event.content)
|
||||
Result.success()
|
||||
} catch (exception: Throwable) {
|
||||
// It does start from 0, we want it to stop if it fails the third time
|
||||
val currentAttemptCount = runAttemptCount + 1
|
||||
if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) {
|
||||
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
|
||||
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
|
||||
localEchoRepository.updateSendState(event.eventId, SendState.UNDELIVERED)
|
||||
return Result.success()
|
||||
} else {
|
||||
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ internal class DefaultTimeline(
|
|||
if (!results.isLoaded || !results.isValid) {
|
||||
return@OrderedRealmCollectionChangeListener
|
||||
}
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] DB update for room $roomId")
|
||||
handleUpdates(results, changeSet)
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ internal class TimelineSendEventWorkCommon @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun postWork(roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
|
||||
fun postWork(roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND_OR_REPLACE): Cancelable {
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork(buildWorkName(roomId), policy, workRequest)
|
||||
.enqueue()
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.util
|
||||
|
||||
/**
|
||||
* Base64 URL conversion methods
|
||||
*/
|
||||
|
||||
internal fun base64UrlToBase64(base64Url: String): String {
|
||||
return base64Url.replace('-', '+')
|
||||
.replace('_', '/')
|
||||
}
|
||||
|
||||
internal fun base64ToBase64Url(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("\\+".toRegex(), "-")
|
||||
.replace('/', '_')
|
||||
.replace("=", "")
|
||||
}
|
||||
|
||||
internal fun base64ToUnpaddedBase64(base64: String): String {
|
||||
return base64.replace("\n".toRegex(), "")
|
||||
.replace("=", "")
|
||||
}
|
|
@ -97,7 +97,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
|||
// Success
|
||||
val homeServerBaseUrl = wellKnown.homeServer?.baseURL
|
||||
if (homeServerBaseUrl.isNullOrBlank()) {
|
||||
WellknownResult.FailPrompt
|
||||
WellknownResult.FailPrompt(null, null)
|
||||
} else {
|
||||
if (homeServerBaseUrl.isValidUrl()) {
|
||||
// Check that HS is a real one
|
||||
|
@ -120,11 +120,11 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
|||
is Failure.OtherServerError -> {
|
||||
when (throwable.httpCode) {
|
||||
HttpsURLConnection.HTTP_NOT_FOUND -> WellknownResult.Ignore
|
||||
else -> WellknownResult.FailPrompt
|
||||
else -> WellknownResult.FailPrompt(null, null)
|
||||
}
|
||||
}
|
||||
is MalformedJsonException, is EOFException -> {
|
||||
WellknownResult.FailPrompt
|
||||
WellknownResult.FailPrompt(null, null)
|
||||
}
|
||||
else -> {
|
||||
throw throwable
|
||||
|
@ -162,7 +162,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
|||
// All is ok
|
||||
WellknownResult.Prompt(homeServerBaseUrl, identityServerBaseUrl, wellKnown)
|
||||
} else {
|
||||
WellknownResult.FailError
|
||||
WellknownResult.FailPrompt(homeServerBaseUrl, wellKnown)
|
||||
}
|
||||
} else {
|
||||
WellknownResult.FailError
|
||||
|
|
|
@ -79,72 +79,6 @@
|
|||
|
||||
<string name="room_displayname_empty_room">Boş otaq</string>
|
||||
|
||||
|
||||
<string name="verification_emoji_dog">It</string>
|
||||
<string name="verification_emoji_cat">Pişik</string>
|
||||
<string name="verification_emoji_lion">Aslan</string>
|
||||
<string name="verification_emoji_horse">At</string>
|
||||
<string name="verification_emoji_unicorn">Kərgədan</string>
|
||||
<string name="verification_emoji_pig">Donuz</string>
|
||||
<string name="verification_emoji_elephant">Fil</string>
|
||||
<string name="verification_emoji_rabbit">Dovşan</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Xoruz</string>
|
||||
<string name="verification_emoji_penguin">Pinqvin</string>
|
||||
<string name="verification_emoji_turtle">Tısbağa</string>
|
||||
<string name="verification_emoji_fish">Balıq</string>
|
||||
<string name="verification_emoji_octopus">Ahtapot</string>
|
||||
<string name="verification_emoji_butterfly">Kəpənək</string>
|
||||
<string name="verification_emoji_flower">Çiçək</string>
|
||||
<string name="verification_emoji_tree">Ağac</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Göbələk</string>
|
||||
<string name="verification_emoji_globe">Qlobus</string>
|
||||
<string name="verification_emoji_moon">Ay</string>
|
||||
<string name="verification_emoji_cloud">Bulud</string>
|
||||
<string name="verification_emoji_fire">Atəş</string>
|
||||
<string name="verification_emoji_banana">Banan</string>
|
||||
<string name="verification_emoji_apple">Alma</string>
|
||||
<string name="verification_emoji_strawberry">Çiyələk</string>
|
||||
<string name="verification_emoji_corn">Qarğıdalı</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Tort</string>
|
||||
<string name="verification_emoji_heart">Ürək</string>
|
||||
<string name="verification_emoji_smiley">Təbəssüm</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Papaq</string>
|
||||
<string name="verification_emoji_glasses">Eynəklər</string>
|
||||
<string name="verification_emoji_wrench">Açar</string>
|
||||
<string name="verification_emoji_santa">Santa</string>
|
||||
<string name="verification_emoji_thumbsup">Baş barmaqlar yuxarı</string>
|
||||
<string name="verification_emoji_umbrella">Çətir</string>
|
||||
<string name="verification_emoji_hourglass">Qum saatı</string>
|
||||
<string name="verification_emoji_clock">Saat</string>
|
||||
<string name="verification_emoji_gift">Hədiyyə</string>
|
||||
<string name="verification_emoji_lightbulb">Lampa</string>
|
||||
<string name="verification_emoji_book">Kitab</string>
|
||||
<string name="verification_emoji_pencil">Qələm</string>
|
||||
<string name="verification_emoji_paperclip">Kağız sancağı</string>
|
||||
<string name="verification_emoji_scissors">Qayçı</string>
|
||||
<string name="verification_emoji_lock">Qıfıl</string>
|
||||
<string name="verification_emoji_key">Açar</string>
|
||||
<string name="verification_emoji_hammer">Çəkic</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Bayraq</string>
|
||||
<string name="verification_emoji_train">Qatar</string>
|
||||
<string name="verification_emoji_bicycle">Velosiped</string>
|
||||
<string name="verification_emoji_airplane">Təyyarə</string>
|
||||
<string name="verification_emoji_rocket">Raket</string>
|
||||
<string name="verification_emoji_trophy">Kubok</string>
|
||||
<string name="verification_emoji_ball">Top</string>
|
||||
<string name="verification_emoji_guitar">Gitara</string>
|
||||
<string name="verification_emoji_trumpet">Saz</string>
|
||||
<string name="verification_emoji_bell">Zəng</string>
|
||||
<string name="verification_emoji_anchor">Anker</string>
|
||||
<string name="verification_emoji_headphone">Qulaqlıqlar</string>
|
||||
<string name="verification_emoji_folder">Qovluq</string>
|
||||
<string name="verification_emoji_pin">Sancaq</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">İlkin sinxronizasiya:
|
||||
\nHesab idxal olunur…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">İlkin sinxronizasiya:
|
||||
|
|
|
@ -78,70 +78,6 @@
|
|||
<string name="notice_event_redacted_by">Съобщение премахнато от %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Премахнато съобщение [причина: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Съобщение премахнато от %1$s [причина: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Куче</string>
|
||||
<string name="verification_emoji_cat">Котка</string>
|
||||
<string name="verification_emoji_lion">Лъв</string>
|
||||
<string name="verification_emoji_horse">Кон</string>
|
||||
<string name="verification_emoji_unicorn">Еднорог</string>
|
||||
<string name="verification_emoji_pig">Прасе</string>
|
||||
<string name="verification_emoji_elephant">Слон</string>
|
||||
<string name="verification_emoji_rabbit">Заек</string>
|
||||
<string name="verification_emoji_panda">Панда</string>
|
||||
<string name="verification_emoji_rooster">Петел</string>
|
||||
<string name="verification_emoji_penguin">Пингвин</string>
|
||||
<string name="verification_emoji_turtle">Костенурка</string>
|
||||
<string name="verification_emoji_fish">Риба</string>
|
||||
<string name="verification_emoji_octopus">Октопод</string>
|
||||
<string name="verification_emoji_butterfly">Пеперуда</string>
|
||||
<string name="verification_emoji_flower">Цвете</string>
|
||||
<string name="verification_emoji_tree">Дърво</string>
|
||||
<string name="verification_emoji_cactus">Кактус</string>
|
||||
<string name="verification_emoji_mushroom">Гъба</string>
|
||||
<string name="verification_emoji_globe">Глобус</string>
|
||||
<string name="verification_emoji_moon">Луна</string>
|
||||
<string name="verification_emoji_cloud">Облак</string>
|
||||
<string name="verification_emoji_fire">Огън</string>
|
||||
<string name="verification_emoji_banana">Банан</string>
|
||||
<string name="verification_emoji_apple">Ябълка</string>
|
||||
<string name="verification_emoji_strawberry">Ягода</string>
|
||||
<string name="verification_emoji_corn">Царевица</string>
|
||||
<string name="verification_emoji_pizza">Пица</string>
|
||||
<string name="verification_emoji_cake">Торта</string>
|
||||
<string name="verification_emoji_heart">Сърце</string>
|
||||
<string name="verification_emoji_smiley">Усмивка</string>
|
||||
<string name="verification_emoji_robot">Робот</string>
|
||||
<string name="verification_emoji_hat">Шапка</string>
|
||||
<string name="verification_emoji_glasses">Очила</string>
|
||||
<string name="verification_emoji_wrench">Гаечен ключ</string>
|
||||
<string name="verification_emoji_santa">Дядо Коледа</string>
|
||||
<string name="verification_emoji_thumbsup">Палец нагоре</string>
|
||||
<string name="verification_emoji_umbrella">Чадър</string>
|
||||
<string name="verification_emoji_hourglass">Пясъчен часовник</string>
|
||||
<string name="verification_emoji_clock">Часовник</string>
|
||||
<string name="verification_emoji_gift">Подарък</string>
|
||||
<string name="verification_emoji_lightbulb">Лампа</string>
|
||||
<string name="verification_emoji_book">Книга</string>
|
||||
<string name="verification_emoji_pencil">Молив</string>
|
||||
<string name="verification_emoji_paperclip">Кламер</string>
|
||||
<string name="verification_emoji_scissors">Ножици</string>
|
||||
<string name="verification_emoji_lock">Катинар</string>
|
||||
<string name="verification_emoji_key">Ключ</string>
|
||||
<string name="verification_emoji_hammer">Чук</string>
|
||||
<string name="verification_emoji_telephone">Телефон</string>
|
||||
<string name="verification_emoji_flag">Знаме</string>
|
||||
<string name="verification_emoji_train">Влак</string>
|
||||
<string name="verification_emoji_bicycle">Колело</string>
|
||||
<string name="verification_emoji_airplane">Самолет</string>
|
||||
<string name="verification_emoji_rocket">Ракета</string>
|
||||
<string name="verification_emoji_trophy">Трофей</string>
|
||||
<string name="verification_emoji_ball">Топка</string>
|
||||
<string name="verification_emoji_guitar">Китара</string>
|
||||
<string name="verification_emoji_trumpet">Тромпет</string>
|
||||
<string name="verification_emoji_bell">Звънец</string>
|
||||
<string name="verification_emoji_anchor">Котва</string>
|
||||
<string name="verification_emoji_headphone">Слушалки</string>
|
||||
<string name="verification_emoji_folder">Папка</string>
|
||||
<string name="verification_emoji_pin">Карфица</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Начална синхронизация:
|
||||
\nИмпортиране на профил…</string>
|
||||
|
@ -204,4 +140,16 @@
|
|||
<string name="key_verification_request_fallback_message">%s изпрати запитване за потвърждение на ключа ви, но клиентът ви не поддържа верифициране посредством чат. Ще трябва да използвате стария метод за верифициране на ключове.</string>
|
||||
|
||||
<string name="notice_room_created">%1$s създаде стаята</string>
|
||||
<string name="summary_you_sent_image">Изпратихте снимка.</string>
|
||||
<string name="summary_you_sent_sticker">Изпратихте стикер.</string>
|
||||
|
||||
<string name="notice_room_invite_no_invitee_by_you">Ваша покана</string>
|
||||
<string name="notice_room_created_by_you">Създадохте стаята</string>
|
||||
<string name="notice_room_invite_by_you">Поканихте %1$s</string>
|
||||
<string name="notice_room_join_by_you">Присъединихте се в стаята</string>
|
||||
<string name="notice_room_leave_by_you">Напуснахте стаята</string>
|
||||
<string name="notice_room_reject_by_you">Отхвърлихте поканата</string>
|
||||
<string name="notice_room_kick_by_you">Изгонихте %1$s</string>
|
||||
<string name="notice_room_unban_by_you">Отблокирахте %1$s</string>
|
||||
<string name="notice_room_ban_by_you">Блокирахте %1$s</string>
|
||||
</resources>
|
||||
|
|
|
@ -136,72 +136,6 @@
|
|||
|
||||
<string name="room_displayname_empty_room">খালি কক্ষ</string>
|
||||
|
||||
|
||||
<string name="verification_emoji_dog">কুকুর</string>
|
||||
<string name="verification_emoji_cat">বেড়াল</string>
|
||||
<string name="verification_emoji_lion">সিংহ</string>
|
||||
<string name="verification_emoji_horse">ঘোড়া</string>
|
||||
<string name="verification_emoji_unicorn">ইউনিকর্ন</string>
|
||||
<string name="verification_emoji_pig">শূকর</string>
|
||||
<string name="verification_emoji_elephant">হাতি</string>
|
||||
<string name="verification_emoji_rabbit">খরগোশ</string>
|
||||
<string name="verification_emoji_panda">পান্ডা</string>
|
||||
<string name="verification_emoji_rooster">গৃহপালিত মোরগ</string>
|
||||
<string name="verification_emoji_penguin">পেংগুইন</string>
|
||||
<string name="verification_emoji_turtle">কচ্ছপ</string>
|
||||
<string name="verification_emoji_fish">মাছ</string>
|
||||
<string name="verification_emoji_octopus">অক্টোপাস</string>
|
||||
<string name="verification_emoji_butterfly">প্রজাপতি</string>
|
||||
<string name="verification_emoji_flower">ফুল</string>
|
||||
<string name="verification_emoji_tree">গাছ</string>
|
||||
<string name="verification_emoji_cactus">ফণীমনসা</string>
|
||||
<string name="verification_emoji_mushroom">মাশরুম</string>
|
||||
<string name="verification_emoji_globe">পৃথিবী</string>
|
||||
<string name="verification_emoji_moon">চন্দ্র</string>
|
||||
<string name="verification_emoji_cloud">মেঘ</string>
|
||||
<string name="verification_emoji_fire">আগুন</string>
|
||||
<string name="verification_emoji_banana">কলা</string>
|
||||
<string name="verification_emoji_apple">আপেল</string>
|
||||
<string name="verification_emoji_strawberry">স্ট্রবেরি</string>
|
||||
<string name="verification_emoji_corn">ভূট্টা</string>
|
||||
<string name="verification_emoji_pizza">পিজা</string>
|
||||
<string name="verification_emoji_cake">কেক</string>
|
||||
<string name="verification_emoji_heart">হৃদয়</string>
|
||||
<string name="verification_emoji_smiley">স্মাইলি</string>
|
||||
<string name="verification_emoji_robot">রোবট</string>
|
||||
<string name="verification_emoji_hat">টুপি</string>
|
||||
<string name="verification_emoji_glasses">চশমা</string>
|
||||
<string name="verification_emoji_wrench">রেঞ্চ</string>
|
||||
<string name="verification_emoji_santa">সান্তা</string>
|
||||
<string name="verification_emoji_thumbsup">থাম্বস আপ</string>
|
||||
<string name="verification_emoji_umbrella">ছাতা</string>
|
||||
<string name="verification_emoji_hourglass">বালিঘড়ি</string>
|
||||
<string name="verification_emoji_clock">ঘড়ি</string>
|
||||
<string name="verification_emoji_gift">উপহার</string>
|
||||
<string name="verification_emoji_lightbulb">আলো বালব</string>
|
||||
<string name="verification_emoji_book">বই</string>
|
||||
<string name="verification_emoji_pencil">পেন্সিল</string>
|
||||
<string name="verification_emoji_paperclip">পেপার ক্লিপ</string>
|
||||
<string name="verification_emoji_scissors">কাঁচি</string>
|
||||
<string name="verification_emoji_lock">লক</string>
|
||||
<string name="verification_emoji_key">চাবি</string>
|
||||
<string name="verification_emoji_hammer">হাতুড়ি</string>
|
||||
<string name="verification_emoji_telephone">টেলিফোন</string>
|
||||
<string name="verification_emoji_flag">পতাকা</string>
|
||||
<string name="verification_emoji_train">রেলগাড়ি</string>
|
||||
<string name="verification_emoji_bicycle">সাইকেল</string>
|
||||
<string name="verification_emoji_airplane">বিমান</string>
|
||||
<string name="verification_emoji_rocket">রকেট</string>
|
||||
<string name="verification_emoji_trophy">ট্রফি</string>
|
||||
<string name="verification_emoji_ball">বল</string>
|
||||
<string name="verification_emoji_guitar">গিটার</string>
|
||||
<string name="verification_emoji_trumpet">ট্রাম্পেট</string>
|
||||
<string name="verification_emoji_bell">ঘণ্টা</string>
|
||||
<string name="verification_emoji_anchor">নোঙ্গর</string>
|
||||
<string name="verification_emoji_headphone">হেডফোন</string>
|
||||
<string name="verification_emoji_folder">ফোল্ডার</string>
|
||||
<string name="verification_emoji_pin">পিন</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">প্রাথমিক সিঙ্ক:
|
||||
\nঅ্যাকাউন্ট আমদানি করা হচ্ছে…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">প্রাথমিক সিঙ্ক:
|
||||
|
@ -288,8 +222,4 @@
|
|||
|
||||
<string name="key_verification_request_fallback_message">%s আপনার কীটি যাচাই করার জন্য অনুরোধ করছে, তবে আপনার ক্লায়েন্ট ইন-চ্যাট কী যাচাইকরণ সমর্থন করে না। কীগুলি যাচাই করতে আপনাকে লিগ্যাসি কী যাচাইকরণ ব্যবহার করতে হবে।</string>
|
||||
|
||||
<string name="call_notification_answer">গ্রহণ</string>
|
||||
<string name="call_notification_reject">পতন</string>
|
||||
<string name="call_notification_hangup">বন্ধ করুন</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -79,68 +79,7 @@
|
|||
<string name="notice_event_redacted_with_reason">Zpráva byla smazána [důvod: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Zpráva smazána uživatelem %1$s [důvod: %2$s]</string>
|
||||
<string name="notice_room_third_party_revoked_invite">Uživatel %1$s obnovil pozvánku do místnosti pro uživatele %2$s</string>
|
||||
<string name="verification_emoji_cat">Kočka</string>
|
||||
<string name="verification_emoji_lion">Lev</string>
|
||||
<string name="verification_emoji_horse">Kůň</string>
|
||||
<string name="verification_emoji_unicorn">Jednorožec</string>
|
||||
<string name="verification_emoji_pig">Prase</string>
|
||||
<string name="verification_emoji_elephant">Slon</string>
|
||||
<string name="verification_emoji_rabbit">Králík</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Kohout</string>
|
||||
<string name="verification_emoji_penguin">Tučňák</string>
|
||||
<string name="verification_emoji_turtle">Želva</string>
|
||||
<string name="verification_emoji_fish">Ryba</string>
|
||||
<string name="verification_emoji_octopus">Chobotnice</string>
|
||||
<string name="verification_emoji_butterfly">Motýl</string>
|
||||
<string name="verification_emoji_flower">Květina</string>
|
||||
<string name="verification_emoji_tree">Strom</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Houba</string>
|
||||
<string name="verification_emoji_globe">Zeměkoule</string>
|
||||
<string name="verification_emoji_moon">Měsíc</string>
|
||||
<string name="verification_emoji_cloud">Mrak</string>
|
||||
<string name="verification_emoji_fire">Oheň</string>
|
||||
<string name="verification_emoji_banana">Banán</string>
|
||||
<string name="verification_emoji_apple">Jablko</string>
|
||||
<string name="verification_emoji_strawberry">Jahoda</string>
|
||||
<string name="verification_emoji_corn">Kukuřice</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Dort</string>
|
||||
<string name="verification_emoji_heart">Srdce</string>
|
||||
<string name="verification_emoji_smiley">Smajlík</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Klobouk</string>
|
||||
<string name="verification_emoji_glasses">Brýle</string>
|
||||
<string name="verification_emoji_santa">Santa Klaus</string>
|
||||
<string name="verification_emoji_thumbsup">Zvednutý palec</string>
|
||||
<string name="verification_emoji_umbrella">Deštník</string>
|
||||
<string name="verification_emoji_hourglass">Přesípací hodiny</string>
|
||||
<string name="verification_emoji_clock">Hodiny</string>
|
||||
<string name="verification_emoji_gift">Dárek</string>
|
||||
<string name="verification_emoji_lightbulb">Žárovka</string>
|
||||
<string name="verification_emoji_book">Kniha</string>
|
||||
<string name="verification_emoji_pencil">Tužka</string>
|
||||
<string name="verification_emoji_paperclip">Sponka</string>
|
||||
<string name="verification_emoji_scissors">Nůžky</string>
|
||||
<string name="verification_emoji_lock">Zámek</string>
|
||||
<string name="verification_emoji_key">Klíč</string>
|
||||
<string name="verification_emoji_hammer">Kladivo</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Vlajka</string>
|
||||
<string name="verification_emoji_train">Vlak</string>
|
||||
<string name="verification_emoji_bicycle">Jízdní kolo</string>
|
||||
<string name="verification_emoji_airplane">Letadlo</string>
|
||||
<string name="verification_emoji_rocket">Raketa</string>
|
||||
<string name="verification_emoji_trophy">Trofej</string>
|
||||
<string name="verification_emoji_ball">Míč</string>
|
||||
<string name="verification_emoji_guitar">Kytara</string>
|
||||
<string name="verification_emoji_trumpet">Trumpeta</string>
|
||||
<string name="verification_emoji_bell">Zvon</string>
|
||||
<string name="verification_emoji_anchor">Kotva</string>
|
||||
<string name="verification_emoji_headphone">Sluchátka</string>
|
||||
<string name="verification_emoji_folder">Desky</string>
|
||||
<string name="initial_sync_start_importing_account">Úvodní synchronizace:
|
||||
<string name="initial_sync_start_importing_account">Úvodní synchronizace:
|
||||
\nImport účtu…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">Úvodní synchronizace:
|
||||
\nImport klíčů</string>
|
||||
|
@ -156,8 +95,6 @@
|
|||
\nImport dat účtu</string>
|
||||
|
||||
<string name="event_status_sending_message">Odesílání zprávy…</string>
|
||||
<string name="verification_emoji_wrench">Maticový klíč</string>
|
||||
<string name="verification_emoji_pin">Připínáček</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account_invited_rooms">Úvodní synchronizace:
|
||||
\nImport pozvánek</string>
|
||||
|
|
|
@ -89,73 +89,8 @@
|
|||
<string name="notice_event_redacted_by">Nachricht entfernt von %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Nachricht entfernt [Grund: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Nachricht entfernt von %1$s [Grund: %2$s]</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_dog">Hund</string>
|
||||
<string name="verification_emoji_cat">Katze</string>
|
||||
<string name="verification_emoji_lion">Löwe</string>
|
||||
<string name="verification_emoji_horse">Pferd</string>
|
||||
<string name="verification_emoji_unicorn">Einhorn</string>
|
||||
<string name="verification_emoji_pig">Schwein</string>
|
||||
<string name="verification_emoji_elephant">Elefant</string>
|
||||
<string name="verification_emoji_rabbit">Kaninchen</string>
|
||||
<string name="notice_room_update">%s hat diesen Raum aufgewertet.</string>
|
||||
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Hahn</string>
|
||||
<string name="verification_emoji_penguin">Pinguin</string>
|
||||
<string name="verification_emoji_turtle">Schildkröte</string>
|
||||
<string name="verification_emoji_fish">Fisch</string>
|
||||
<string name="verification_emoji_octopus">Oktopus</string>
|
||||
<string name="verification_emoji_butterfly">Schmetterling</string>
|
||||
<string name="verification_emoji_flower">Blume</string>
|
||||
<string name="verification_emoji_tree">Baum</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Pilz</string>
|
||||
<string name="verification_emoji_globe">Globus</string>
|
||||
<string name="verification_emoji_moon">Mond</string>
|
||||
<string name="verification_emoji_cloud">Wolke</string>
|
||||
<string name="verification_emoji_fire">Feuer</string>
|
||||
<string name="verification_emoji_banana">Banane</string>
|
||||
<string name="verification_emoji_apple">Apfel</string>
|
||||
<string name="verification_emoji_strawberry">Erdbeere</string>
|
||||
<string name="verification_emoji_corn">Mais</string>
|
||||
<string name="verification_emoji_cake">Kuchen</string>
|
||||
<string name="verification_emoji_heart">Herz</string>
|
||||
<string name="verification_emoji_smiley">Smiley</string>
|
||||
<string name="verification_emoji_robot">Roboter</string>
|
||||
<string name="verification_emoji_hat">Hut</string>
|
||||
<string name="verification_emoji_glasses">Brille</string>
|
||||
<string name="verification_emoji_wrench">Schraubenschlüssel</string>
|
||||
<string name="verification_emoji_santa">Weihnachtsmann</string>
|
||||
<string name="verification_emoji_thumbsup">Daumen hoch</string>
|
||||
<string name="verification_emoji_umbrella">Regenschirm</string>
|
||||
<string name="verification_emoji_hourglass">Sanduhr</string>
|
||||
<string name="verification_emoji_clock">Uhr</string>
|
||||
<string name="verification_emoji_gift">Geschenk</string>
|
||||
<string name="verification_emoji_lightbulb">Glühbirne</string>
|
||||
<string name="verification_emoji_book">Buch</string>
|
||||
<string name="verification_emoji_pencil">Bleistift</string>
|
||||
<string name="verification_emoji_paperclip">Büroklammer</string>
|
||||
<string name="verification_emoji_scissors">Schere</string>
|
||||
<string name="verification_emoji_lock">Schloss</string>
|
||||
<string name="verification_emoji_key">Schlüssel</string>
|
||||
<string name="verification_emoji_hammer">Hammer</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Flagge</string>
|
||||
<string name="verification_emoji_train">Zug</string>
|
||||
<string name="verification_emoji_bicycle">Fahrrad</string>
|
||||
<string name="verification_emoji_airplane">Flugzeug</string>
|
||||
<string name="verification_emoji_rocket">Rakete</string>
|
||||
<string name="verification_emoji_trophy">Pokal</string>
|
||||
<string name="verification_emoji_ball">Ball</string>
|
||||
<string name="verification_emoji_guitar">Gitarre</string>
|
||||
<string name="verification_emoji_trumpet">Trompete</string>
|
||||
<string name="verification_emoji_bell">Glocke</string>
|
||||
<string name="verification_emoji_anchor">Anker</string>
|
||||
<string name="verification_emoji_headphone">Kopfhörer</string>
|
||||
<string name="verification_emoji_folder">Ordner</string>
|
||||
<string name="verification_emoji_pin">Stecknadel</string>
|
||||
|
||||
<string name="event_status_sending_message">Sende eine Nachricht…</string>
|
||||
<string name="clear_timeline_send_queue">Sendewarteschlange leeren</string>
|
||||
|
||||
|
@ -297,10 +232,6 @@
|
|||
<string name="notice_end_to_end_ok_by_you">Du hast Ende-zu-Ende-Verschlüsselung aktiviert.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Du hast Ende-zu-Ende-Verschlüsselung aktiviert (unbekannter Algorithmus %1$s).</string>
|
||||
|
||||
<string name="call_notification_answer">Akzeptiere</string>
|
||||
<string name="call_notification_reject">Ablehnen</string>
|
||||
<string name="call_notification_hangup">Anruf beenden</string>
|
||||
|
||||
<string name="notice_call_candidates">%s hat Daten gesendet, um einen Anruf zu starten.</string>
|
||||
<string name="notice_call_candidates_by_you">Du hast Daten geschickt, um eine Anruf zu starten.</string>
|
||||
</resources>
|
||||
|
|
68
matrix-sdk-android/src/main/res/values-de/strings_sas.xml
Normal file
68
matrix-sdk-android/src/main/res/values-de/strings_sas.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Hund</string>
|
||||
<string name="verification_emoji_cat">Katze</string>
|
||||
<string name="verification_emoji_lion">Löwe</string>
|
||||
<string name="verification_emoji_horse">Pferd</string>
|
||||
<string name="verification_emoji_unicorn">Einhorn</string>
|
||||
<string name="verification_emoji_pig">Schwein</string>
|
||||
<string name="verification_emoji_elephant">Elefant</string>
|
||||
<string name="verification_emoji_rabbit">Hase</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Hahn</string>
|
||||
<string name="verification_emoji_penguin">Pinguin</string>
|
||||
<string name="verification_emoji_turtle">Schildkröte</string>
|
||||
<string name="verification_emoji_fish">Fisch</string>
|
||||
<string name="verification_emoji_octopus">Oktopus</string>
|
||||
<string name="verification_emoji_butterfly">Schmetterling</string>
|
||||
<string name="verification_emoji_flower">Blume</string>
|
||||
<string name="verification_emoji_tree">Baum</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Pilz</string>
|
||||
<string name="verification_emoji_globe">Globus</string>
|
||||
<string name="verification_emoji_moon">Mond</string>
|
||||
<string name="verification_emoji_cloud">Wolke</string>
|
||||
<string name="verification_emoji_fire">Feuer</string>
|
||||
<string name="verification_emoji_banana">Banane</string>
|
||||
<string name="verification_emoji_apple">Apfel</string>
|
||||
<string name="verification_emoji_strawberry">Erdbeere</string>
|
||||
<string name="verification_emoji_corn">Korn</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Kuchen</string>
|
||||
<string name="verification_emoji_heart">Herz</string>
|
||||
<string name="verification_emoji_smiley">Smiley</string>
|
||||
<string name="verification_emoji_robot">Roboter</string>
|
||||
<string name="verification_emoji_hat">Hut</string>
|
||||
<string name="verification_emoji_glasses">Brille</string>
|
||||
<string name="verification_emoji_spanner">Schraubenschlüssel</string>
|
||||
<string name="verification_emoji_santa">Nikolaus</string>
|
||||
<string name="verification_emoji_thumbs_up">Daumen Hoch</string>
|
||||
<string name="verification_emoji_umbrella">Regenschirm</string>
|
||||
<string name="verification_emoji_hourglass">Sanduhr</string>
|
||||
<string name="verification_emoji_clock">Wecker</string>
|
||||
<string name="verification_emoji_gift">Geschenk</string>
|
||||
<string name="verification_emoji_light_bulb">Glühbirne</string>
|
||||
<string name="verification_emoji_book">Buch</string>
|
||||
<string name="verification_emoji_pencil">Bleistift</string>
|
||||
<string name="verification_emoji_paperclip">Büroklammer</string>
|
||||
<string name="verification_emoji_scissors">Schere</string>
|
||||
<string name="verification_emoji_lock">Schloss</string>
|
||||
<string name="verification_emoji_key">Schlüssel</string>
|
||||
<string name="verification_emoji_hammer">Hammer</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Flagge</string>
|
||||
<string name="verification_emoji_train">Zug</string>
|
||||
<string name="verification_emoji_bicycle">Fahrrad</string>
|
||||
<string name="verification_emoji_aeroplane">Flugzeug</string>
|
||||
<string name="verification_emoji_rocket">Rakete</string>
|
||||
<string name="verification_emoji_trophy">Trophäe</string>
|
||||
<string name="verification_emoji_ball">Ball</string>
|
||||
<string name="verification_emoji_guitar">Gitarre</string>
|
||||
<string name="verification_emoji_trumpet">Trompete</string>
|
||||
<string name="verification_emoji_bell">Glocke</string>
|
||||
<string name="verification_emoji_anchor">Anker</string>
|
||||
<string name="verification_emoji_headphones">Kopfhörer</string>
|
||||
<string name="verification_emoji_folder">Ordner</string>
|
||||
<string name="verification_emoji_pin">Stecknadel</string>
|
||||
</resources>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="verification_emoji_wrench">Spanner</string>
|
||||
<string name="verification_emoji_airplane">Aeroplane</string>
|
||||
</resources>
|
|
@ -72,72 +72,6 @@
|
|||
|
||||
<string name="room_displayname_empty_room">Malplena ĉambro</string>
|
||||
|
||||
|
||||
<string name="verification_emoji_dog">Hundo</string>
|
||||
<string name="verification_emoji_cat">Kato</string>
|
||||
<string name="verification_emoji_lion">Leono</string>
|
||||
<string name="verification_emoji_horse">Ĉevalo</string>
|
||||
<string name="verification_emoji_unicorn">Unukorno</string>
|
||||
<string name="verification_emoji_pig">Porko</string>
|
||||
<string name="verification_emoji_elephant">Elefanto</string>
|
||||
<string name="verification_emoji_rabbit">Kuniklo</string>
|
||||
<string name="verification_emoji_panda">Pando</string>
|
||||
<string name="verification_emoji_rooster">Koko</string>
|
||||
<string name="verification_emoji_penguin">Pingveno</string>
|
||||
<string name="verification_emoji_turtle">Testudo</string>
|
||||
<string name="verification_emoji_fish">Fiŝo</string>
|
||||
<string name="verification_emoji_octopus">Polpo</string>
|
||||
<string name="verification_emoji_butterfly">Papilio</string>
|
||||
<string name="verification_emoji_flower">Floro</string>
|
||||
<string name="verification_emoji_tree">Arbo</string>
|
||||
<string name="verification_emoji_cactus">Kakto</string>
|
||||
<string name="verification_emoji_mushroom">Fungo</string>
|
||||
<string name="verification_emoji_globe">Globo</string>
|
||||
<string name="verification_emoji_moon">Luno</string>
|
||||
<string name="verification_emoji_cloud">Nubo</string>
|
||||
<string name="verification_emoji_fire">Fajro</string>
|
||||
<string name="verification_emoji_banana">Banano</string>
|
||||
<string name="verification_emoji_apple">Pomo</string>
|
||||
<string name="verification_emoji_strawberry">Frago</string>
|
||||
<string name="verification_emoji_corn">Maizo</string>
|
||||
<string name="verification_emoji_pizza">Pico</string>
|
||||
<string name="verification_emoji_cake">Kuko</string>
|
||||
<string name="verification_emoji_heart">Koro</string>
|
||||
<string name="verification_emoji_smiley">Mieneto</string>
|
||||
<string name="verification_emoji_robot">Roboto</string>
|
||||
<string name="verification_emoji_hat">Ĉapelo</string>
|
||||
<string name="verification_emoji_glasses">Okulvitroj</string>
|
||||
<string name="verification_emoji_wrench">Boltilo</string>
|
||||
<string name="verification_emoji_santa">Kristnaska viro</string>
|
||||
<string name="verification_emoji_thumbsup">Dikfingro supren</string>
|
||||
<string name="verification_emoji_umbrella">Ombrelo</string>
|
||||
<string name="verification_emoji_hourglass">Sablohorloĝo</string>
|
||||
<string name="verification_emoji_clock">Horloĝo</string>
|
||||
<string name="verification_emoji_gift">Donaco</string>
|
||||
<string name="verification_emoji_lightbulb">Lampo</string>
|
||||
<string name="verification_emoji_book">Libro</string>
|
||||
<string name="verification_emoji_pencil">Grifelo</string>
|
||||
<string name="verification_emoji_paperclip">Paperkuntenilo</string>
|
||||
<string name="verification_emoji_scissors">Tondilo</string>
|
||||
<string name="verification_emoji_lock">Seruro</string>
|
||||
<string name="verification_emoji_key">Ŝlosilo</string>
|
||||
<string name="verification_emoji_hammer">Martelo</string>
|
||||
<string name="verification_emoji_telephone">Telefono</string>
|
||||
<string name="verification_emoji_flag">Flago</string>
|
||||
<string name="verification_emoji_train">Vagonaro</string>
|
||||
<string name="verification_emoji_bicycle">Biciklo</string>
|
||||
<string name="verification_emoji_airplane">Aviadilo</string>
|
||||
<string name="verification_emoji_rocket">Raketo</string>
|
||||
<string name="verification_emoji_trophy">Trofeo</string>
|
||||
<string name="verification_emoji_ball">Pilko</string>
|
||||
<string name="verification_emoji_guitar">Gitaro</string>
|
||||
<string name="verification_emoji_trumpet">Trumpeto</string>
|
||||
<string name="verification_emoji_bell">Sonorilo</string>
|
||||
<string name="verification_emoji_anchor">Ankro</string>
|
||||
<string name="verification_emoji_headphone">Kapaŭdilo</string>
|
||||
<string name="verification_emoji_folder">Dosierujo</string>
|
||||
<string name="verification_emoji_pin">Pinglo</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Komenca spegulado:
|
||||
\nEnportante konton…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">Komenca spegulado:
|
||||
|
|
68
matrix-sdk-android/src/main/res/values-eo/strings_sas.xml
Normal file
68
matrix-sdk-android/src/main/res/values-eo/strings_sas.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Hundo</string>
|
||||
<string name="verification_emoji_cat">Kato</string>
|
||||
<string name="verification_emoji_lion">Leono</string>
|
||||
<string name="verification_emoji_horse">Ĉevalo</string>
|
||||
<string name="verification_emoji_unicorn">Unukorno</string>
|
||||
<string name="verification_emoji_pig">Porko</string>
|
||||
<string name="verification_emoji_elephant">Elefanto</string>
|
||||
<string name="verification_emoji_rabbit">Kuniklo</string>
|
||||
<string name="verification_emoji_panda">Pando</string>
|
||||
<string name="verification_emoji_rooster">Virkoko</string>
|
||||
<string name="verification_emoji_penguin">Pingveno</string>
|
||||
<string name="verification_emoji_turtle">Testudo</string>
|
||||
<string name="verification_emoji_fish">Fiŝo</string>
|
||||
<string name="verification_emoji_octopus">Polpo</string>
|
||||
<string name="verification_emoji_butterfly">Papilio</string>
|
||||
<string name="verification_emoji_flower">Floro</string>
|
||||
<string name="verification_emoji_tree">Arbo</string>
|
||||
<string name="verification_emoji_cactus">Kakto</string>
|
||||
<string name="verification_emoji_mushroom">Fungo</string>
|
||||
<string name="verification_emoji_globe">Globo</string>
|
||||
<string name="verification_emoji_moon">Luno</string>
|
||||
<string name="verification_emoji_cloud">Nubo</string>
|
||||
<string name="verification_emoji_fire">Fajro</string>
|
||||
<string name="verification_emoji_banana">Banano</string>
|
||||
<string name="verification_emoji_apple">Pomo</string>
|
||||
<string name="verification_emoji_strawberry">Frago</string>
|
||||
<string name="verification_emoji_corn">Maizo</string>
|
||||
<string name="verification_emoji_pizza">Pico</string>
|
||||
<string name="verification_emoji_cake">Torto</string>
|
||||
<string name="verification_emoji_heart">Koro</string>
|
||||
<string name="verification_emoji_smiley">Rideto</string>
|
||||
<string name="verification_emoji_robot">Roboto</string>
|
||||
<string name="verification_emoji_hat">Ĉapelo</string>
|
||||
<string name="verification_emoji_glasses">Okulvitroj</string>
|
||||
<string name="verification_emoji_spanner">Ŝraŭbŝlosilo</string>
|
||||
<string name="verification_emoji_santa">Kristnaska viro</string>
|
||||
<string name="verification_emoji_thumbs_up">Dikfingro supren</string>
|
||||
<string name="verification_emoji_umbrella">Ombrelo</string>
|
||||
<string name="verification_emoji_hourglass">Sablohorloĝo</string>
|
||||
<string name="verification_emoji_clock">Horloĝo</string>
|
||||
<string name="verification_emoji_gift">Donaco</string>
|
||||
<string name="verification_emoji_light_bulb">Lampo</string>
|
||||
<string name="verification_emoji_book">Libro</string>
|
||||
<string name="verification_emoji_pencil">Krajono</string>
|
||||
<string name="verification_emoji_paperclip">Paperkuntenilo</string>
|
||||
<string name="verification_emoji_scissors">Tondilo</string>
|
||||
<string name="verification_emoji_lock">Seruro</string>
|
||||
<string name="verification_emoji_key">Ŝlosilo</string>
|
||||
<string name="verification_emoji_hammer">Martelo</string>
|
||||
<string name="verification_emoji_telephone">Telefono</string>
|
||||
<string name="verification_emoji_flag">Flago</string>
|
||||
<string name="verification_emoji_train">Vagonaro</string>
|
||||
<string name="verification_emoji_bicycle">Biciklo</string>
|
||||
<string name="verification_emoji_aeroplane">Aviadilo</string>
|
||||
<string name="verification_emoji_rocket">Raketo</string>
|
||||
<string name="verification_emoji_trophy">Trofeo</string>
|
||||
<string name="verification_emoji_ball">Pilko</string>
|
||||
<string name="verification_emoji_guitar">Gitaro</string>
|
||||
<string name="verification_emoji_trumpet">Trumpeto</string>
|
||||
<string name="verification_emoji_bell">Sonorilo</string>
|
||||
<string name="verification_emoji_anchor">Ankro</string>
|
||||
<string name="verification_emoji_headphones">Kapaŭdilo</string>
|
||||
<string name="verification_emoji_folder">Dosierujo</string>
|
||||
<string name="verification_emoji_pin">Pinglo</string>
|
||||
</resources>
|
|
@ -89,8 +89,4 @@
|
|||
<string name="notice_event_redacted_by">Mensaje eliminado por %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Mensaje eliminado [motivo: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Mensaje eliminado por %1$s [motivo: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Perro</string>
|
||||
<string name="verification_emoji_cat">Gato</string>
|
||||
<string name="verification_emoji_lion">León</string>
|
||||
<string name="verification_emoji_horse">Caballo</string>
|
||||
</resources>
|
||||
|
|
|
@ -90,65 +90,6 @@
|
|||
<string name="notice_event_redacted_with_reason">Mensaje eliminado [motivo: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Mensaje eliminado por %1$s [motivo: %2$s]</string>
|
||||
<string name="notice_room_third_party_revoked_invite">%1$s ha revocado la invitación a unirse a la sala para %2$s</string>
|
||||
<string name="verification_emoji_dog">Perro</string>
|
||||
<string name="verification_emoji_cat">Gato</string>
|
||||
<string name="verification_emoji_lion">León</string>
|
||||
<string name="verification_emoji_horse">Caballo</string>
|
||||
<string name="verification_emoji_unicorn">Unicornio</string>
|
||||
<string name="verification_emoji_pig">Cerdo</string>
|
||||
<string name="verification_emoji_elephant">Elefante</string>
|
||||
<string name="verification_emoji_rabbit">Conejo</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Gallo</string>
|
||||
<string name="verification_emoji_penguin">Pingüino</string>
|
||||
<string name="verification_emoji_turtle">Tortuga</string>
|
||||
<string name="verification_emoji_fish">Pez</string>
|
||||
<string name="verification_emoji_octopus">Pulpo</string>
|
||||
<string name="verification_emoji_butterfly">Mariposa</string>
|
||||
<string name="verification_emoji_flower">Flor</string>
|
||||
<string name="verification_emoji_tree">Árbol</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Seta</string>
|
||||
<string name="verification_emoji_moon">Luna</string>
|
||||
<string name="verification_emoji_cloud">Nube</string>
|
||||
<string name="verification_emoji_fire">Fuego</string>
|
||||
<string name="verification_emoji_banana">Plátano</string>
|
||||
<string name="verification_emoji_apple">Manzana</string>
|
||||
<string name="verification_emoji_strawberry">Fresa</string>
|
||||
<string name="verification_emoji_corn">Maíz</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Pastel</string>
|
||||
<string name="verification_emoji_heart">Corazón</string>
|
||||
<string name="verification_emoji_hat">Sombrero</string>
|
||||
<string name="verification_emoji_glasses">Gafas</string>
|
||||
<string name="verification_emoji_wrench">Llave inglesa</string>
|
||||
<string name="verification_emoji_thumbsup">Pulgares arriba</string>
|
||||
<string name="verification_emoji_umbrella">Paraguas</string>
|
||||
<string name="verification_emoji_hourglass">Reloj de arena</string>
|
||||
<string name="verification_emoji_clock">Reloj</string>
|
||||
<string name="verification_emoji_gift">Regalo</string>
|
||||
<string name="verification_emoji_lightbulb">Bombilla</string>
|
||||
<string name="verification_emoji_book">Libro</string>
|
||||
<string name="verification_emoji_pencil">Lápiz</string>
|
||||
<string name="verification_emoji_paperclip">Clip</string>
|
||||
<string name="verification_emoji_scissors">Tijeras</string>
|
||||
<string name="verification_emoji_lock">Candado</string>
|
||||
<string name="verification_emoji_key">Llave</string>
|
||||
<string name="verification_emoji_hammer">Martillo</string>
|
||||
<string name="verification_emoji_telephone">Teléfono</string>
|
||||
<string name="verification_emoji_flag">Bandera</string>
|
||||
<string name="verification_emoji_train">Tren</string>
|
||||
<string name="verification_emoji_bicycle">Bicicleta</string>
|
||||
<string name="verification_emoji_airplane">Avión</string>
|
||||
<string name="verification_emoji_rocket">Cohete</string>
|
||||
<string name="verification_emoji_trophy">Trofeo</string>
|
||||
<string name="verification_emoji_ball">Pelota</string>
|
||||
<string name="verification_emoji_guitar">Guitarra</string>
|
||||
<string name="verification_emoji_trumpet">Trompeta</string>
|
||||
<string name="verification_emoji_bell">Campana</string>
|
||||
<string name="verification_emoji_anchor">Ancla</string>
|
||||
<string name="verification_emoji_headphone">Auriculares</string>
|
||||
<string name="verification_emoji_folder">Carpeta</string>
|
||||
<string name="initial_sync_start_importing_account">Sincronización Inicial
|
||||
\nImportando cuenta…</string>
|
||||
<string name="initial_sync_start_importing_account_rooms">Sincronización Inicial:
|
||||
|
@ -173,12 +114,6 @@
|
|||
|
||||
<string name="notice_room_update">%s ha actualizado la sala.</string>
|
||||
|
||||
<string name="verification_emoji_globe">Globo Terráqueo</string>
|
||||
<string name="verification_emoji_smiley">Cara sonriente</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_santa">Papá Noel</string>
|
||||
<string name="verification_emoji_pin">Pin</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account_crypto">Sincronización Inicial:
|
||||
\nImportando criptografía</string>
|
||||
<string name="initial_sync_start_importing_account_joined_rooms">Sincronización Inicial:
|
||||
|
|
53
matrix-sdk-android/src/main/res/values-es/strings_sas.xml
Normal file
53
matrix-sdk-android/src/main/res/values-es/strings_sas.xml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Perro</string>
|
||||
<string name="verification_emoji_cat">Gato</string>
|
||||
<string name="verification_emoji_lion">León</string>
|
||||
<string name="verification_emoji_horse">Caballo</string>
|
||||
<string name="verification_emoji_unicorn">Unicornio</string>
|
||||
<string name="verification_emoji_pig">Cerdo</string>
|
||||
<string name="verification_emoji_elephant">Elefante</string>
|
||||
<string name="verification_emoji_rabbit">Conejo</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Gallo</string>
|
||||
<string name="verification_emoji_penguin">Pingüino</string>
|
||||
<string name="verification_emoji_turtle">Tortuga</string>
|
||||
<string name="verification_emoji_fish">Pez</string>
|
||||
<string name="verification_emoji_octopus">Pulpo</string>
|
||||
<string name="verification_emoji_butterfly">Mariposa</string>
|
||||
<string name="verification_emoji_flower">Flor</string>
|
||||
<string name="verification_emoji_tree">Árbol</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Seta</string>
|
||||
<string name="verification_emoji_globe">Globo</string>
|
||||
<string name="verification_emoji_moon">Luna</string>
|
||||
<string name="verification_emoji_cloud">Nube</string>
|
||||
<string name="verification_emoji_fire">Fuego</string>
|
||||
<string name="verification_emoji_banana">Plátano</string>
|
||||
<string name="verification_emoji_apple">Manzana</string>
|
||||
<string name="verification_emoji_strawberry">Fresa</string>
|
||||
<string name="verification_emoji_corn">Maíz</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Tarta</string>
|
||||
<string name="verification_emoji_heart">Corazón</string>
|
||||
<string name="verification_emoji_smiley">Emoticono</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Sombrero</string>
|
||||
<string name="verification_emoji_glasses">Gafas</string>
|
||||
<string name="verification_emoji_spanner">Llave inglesa</string>
|
||||
<string name="verification_emoji_clock">Reloj</string>
|
||||
<string name="verification_emoji_gift">Regalo</string>
|
||||
<string name="verification_emoji_book">Libro</string>
|
||||
<string name="verification_emoji_pencil">Lápiz</string>
|
||||
<string name="verification_emoji_key">Llave</string>
|
||||
<string name="verification_emoji_hammer">Martillo</string>
|
||||
<string name="verification_emoji_telephone">Telefono</string>
|
||||
<string name="verification_emoji_train">Tren</string>
|
||||
<string name="verification_emoji_bicycle">Bicicleta</string>
|
||||
<string name="verification_emoji_ball">Bola</string>
|
||||
<string name="verification_emoji_guitar">Guitarra</string>
|
||||
<string name="verification_emoji_trumpet">Trompeta</string>
|
||||
<string name="verification_emoji_bell">Campana</string>
|
||||
<string name="verification_emoji_pin">Alfiler</string>
|
||||
</resources>
|
|
@ -77,72 +77,6 @@
|
|||
|
||||
<string name="room_displayname_empty_room">Tühi jututuba</string>
|
||||
|
||||
|
||||
<string name="verification_emoji_dog">Koer</string>
|
||||
<string name="verification_emoji_cat">Kass</string>
|
||||
<string name="verification_emoji_lion">Lõvi</string>
|
||||
<string name="verification_emoji_horse">Hobune</string>
|
||||
<string name="verification_emoji_unicorn">Ükssarvik</string>
|
||||
<string name="verification_emoji_pig">Siga</string>
|
||||
<string name="verification_emoji_elephant">Elevant</string>
|
||||
<string name="verification_emoji_rabbit">Jänes</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Kukk</string>
|
||||
<string name="verification_emoji_penguin">Pingviin</string>
|
||||
<string name="verification_emoji_turtle">Kilpkonn</string>
|
||||
<string name="verification_emoji_fish">Kala</string>
|
||||
<string name="verification_emoji_octopus">Kaheksajalg</string>
|
||||
<string name="verification_emoji_butterfly">Liblikas</string>
|
||||
<string name="verification_emoji_flower">Lill</string>
|
||||
<string name="verification_emoji_tree">Puu</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Seen</string>
|
||||
<string name="verification_emoji_globe">Maakera</string>
|
||||
<string name="verification_emoji_moon">Kuu</string>
|
||||
<string name="verification_emoji_cloud">Pilv</string>
|
||||
<string name="verification_emoji_fire">Tuli</string>
|
||||
<string name="verification_emoji_banana">Banaan</string>
|
||||
<string name="verification_emoji_apple">Õun</string>
|
||||
<string name="verification_emoji_strawberry">Maasikas</string>
|
||||
<string name="verification_emoji_corn">Mais</string>
|
||||
<string name="verification_emoji_pizza">Pitsa</string>
|
||||
<string name="verification_emoji_cake">Kook</string>
|
||||
<string name="verification_emoji_heart">Süda</string>
|
||||
<string name="verification_emoji_smiley">Smaili</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Kübar</string>
|
||||
<string name="verification_emoji_glasses">Prillid</string>
|
||||
<string name="verification_emoji_wrench">Mutrivõti</string>
|
||||
<string name="verification_emoji_santa">Jõuluvana</string>
|
||||
<string name="verification_emoji_thumbsup">Pöidlad püsti</string>
|
||||
<string name="verification_emoji_umbrella">Vihmavari</string>
|
||||
<string name="verification_emoji_hourglass">Liivakell</string>
|
||||
<string name="verification_emoji_clock">Kell</string>
|
||||
<string name="verification_emoji_gift">Kingitus</string>
|
||||
<string name="verification_emoji_lightbulb">Lambipirn</string>
|
||||
<string name="verification_emoji_book">Raamat</string>
|
||||
<string name="verification_emoji_pencil">Pliiats</string>
|
||||
<string name="verification_emoji_paperclip">Kirjaklamber</string>
|
||||
<string name="verification_emoji_scissors">Käärid</string>
|
||||
<string name="verification_emoji_lock">Lukk</string>
|
||||
<string name="verification_emoji_key">Võti</string>
|
||||
<string name="verification_emoji_hammer">Haamer</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Lipp</string>
|
||||
<string name="verification_emoji_train">Rong</string>
|
||||
<string name="verification_emoji_bicycle">Jalgratas</string>
|
||||
<string name="verification_emoji_airplane">Lennuk</string>
|
||||
<string name="verification_emoji_rocket">Rakett</string>
|
||||
<string name="verification_emoji_trophy">Auhind</string>
|
||||
<string name="verification_emoji_ball">Pall</string>
|
||||
<string name="verification_emoji_guitar">Kitarr</string>
|
||||
<string name="verification_emoji_trumpet">Trompet</string>
|
||||
<string name="verification_emoji_bell">Kelluke</string>
|
||||
<string name="verification_emoji_anchor">Ankur</string>
|
||||
<string name="verification_emoji_headphone">Kõrvaklapid</string>
|
||||
<string name="verification_emoji_folder">Kaust</string>
|
||||
<string name="verification_emoji_pin">Nööpnõel</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Alglaadimine:
|
||||
\nImpordin kontot…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">Alglaadimine:
|
||||
|
@ -295,8 +229,4 @@
|
|||
<string name="notice_end_to_end_ok_by_you">Sa lülitasid sisse läbiva krüptimise.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Sa lülitasid sisse läbiva krüptimise (kasutusel on tundmatu algoritm %1$s).</string>
|
||||
|
||||
<string name="call_notification_answer">Võta vastu</string>
|
||||
<string name="call_notification_reject">Keeldu</string>
|
||||
<string name="call_notification_hangup">Lõpeta kõne</string>
|
||||
|
||||
</resources>
|
||||
|
|
68
matrix-sdk-android/src/main/res/values-et/strings_sas.xml
Normal file
68
matrix-sdk-android/src/main/res/values-et/strings_sas.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Koer</string>
|
||||
<string name="verification_emoji_cat">Kass</string>
|
||||
<string name="verification_emoji_lion">Lõvi</string>
|
||||
<string name="verification_emoji_horse">Hobune</string>
|
||||
<string name="verification_emoji_unicorn">Ükssarvik</string>
|
||||
<string name="verification_emoji_pig">Siga</string>
|
||||
<string name="verification_emoji_elephant">Elevant</string>
|
||||
<string name="verification_emoji_rabbit">Jänes</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Kukk</string>
|
||||
<string name="verification_emoji_penguin">Pingviin</string>
|
||||
<string name="verification_emoji_turtle">Kilpkonn</string>
|
||||
<string name="verification_emoji_fish">Kala</string>
|
||||
<string name="verification_emoji_octopus">Kaheksajalg</string>
|
||||
<string name="verification_emoji_butterfly">Liblikas</string>
|
||||
<string name="verification_emoji_flower">Lill</string>
|
||||
<string name="verification_emoji_tree">Puu</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Seen</string>
|
||||
<string name="verification_emoji_globe">Maakera</string>
|
||||
<string name="verification_emoji_moon">Kuu</string>
|
||||
<string name="verification_emoji_cloud">Pilv</string>
|
||||
<string name="verification_emoji_fire">Tuli</string>
|
||||
<string name="verification_emoji_banana">Banaan</string>
|
||||
<string name="verification_emoji_apple">Õun</string>
|
||||
<string name="verification_emoji_strawberry">Maasikas</string>
|
||||
<string name="verification_emoji_corn">Mais</string>
|
||||
<string name="verification_emoji_pizza">Pitsa</string>
|
||||
<string name="verification_emoji_cake">Kook</string>
|
||||
<string name="verification_emoji_heart">Süda</string>
|
||||
<string name="verification_emoji_smiley">Smaili</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Kübar</string>
|
||||
<string name="verification_emoji_glasses">Prillid</string>
|
||||
<string name="verification_emoji_spanner">Mutrivõti</string>
|
||||
<string name="verification_emoji_santa">Jõuluvana</string>
|
||||
<string name="verification_emoji_thumbs_up">Pöidlad püsti</string>
|
||||
<string name="verification_emoji_umbrella">Vihmavari</string>
|
||||
<string name="verification_emoji_hourglass">Liivakell</string>
|
||||
<string name="verification_emoji_clock">Kell</string>
|
||||
<string name="verification_emoji_gift">Kingitus</string>
|
||||
<string name="verification_emoji_light_bulb">Lambipirn</string>
|
||||
<string name="verification_emoji_book">Raamat</string>
|
||||
<string name="verification_emoji_pencil">Pliiats</string>
|
||||
<string name="verification_emoji_paperclip">Kirjaklamber</string>
|
||||
<string name="verification_emoji_scissors">Käärid</string>
|
||||
<string name="verification_emoji_lock">Lukk</string>
|
||||
<string name="verification_emoji_key">Võti</string>
|
||||
<string name="verification_emoji_hammer">Haamer</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Lipp</string>
|
||||
<string name="verification_emoji_train">Rong</string>
|
||||
<string name="verification_emoji_bicycle">Jalgratas</string>
|
||||
<string name="verification_emoji_aeroplane">Lennuk</string>
|
||||
<string name="verification_emoji_rocket">Rakett</string>
|
||||
<string name="verification_emoji_trophy">Auhind</string>
|
||||
<string name="verification_emoji_ball">Pall</string>
|
||||
<string name="verification_emoji_guitar">Kitarr</string>
|
||||
<string name="verification_emoji_trumpet">Trompet</string>
|
||||
<string name="verification_emoji_bell">Kelluke</string>
|
||||
<string name="verification_emoji_anchor">Ankur</string>
|
||||
<string name="verification_emoji_headphones">Kõrvaklapid</string>
|
||||
<string name="verification_emoji_folder">Kaust</string>
|
||||
<string name="verification_emoji_pin">Nööpnõel</string>
|
||||
</resources>
|
|
@ -78,70 +78,6 @@
|
|||
<string name="notice_event_redacted_by">%1$s erabiltzaileak mezua kendu du</string>
|
||||
<string name="notice_event_redacted_with_reason">Mezua kendu da [arrazoia: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">%1$s erabiltzaileak mezua kendu du [arrazoia: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Txakurra</string>
|
||||
<string name="verification_emoji_cat">Katua</string>
|
||||
<string name="verification_emoji_lion">Lehoia</string>
|
||||
<string name="verification_emoji_horse">Zaldia</string>
|
||||
<string name="verification_emoji_unicorn">Unikornioa</string>
|
||||
<string name="verification_emoji_pig">Zerria</string>
|
||||
<string name="verification_emoji_elephant">Elefantea</string>
|
||||
<string name="verification_emoji_rabbit">Untxia</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Oilarra</string>
|
||||
<string name="verification_emoji_penguin">Pinguinoa</string>
|
||||
<string name="verification_emoji_turtle">Dortoka</string>
|
||||
<string name="verification_emoji_fish">Arraina</string>
|
||||
<string name="verification_emoji_octopus">Olagarroa</string>
|
||||
<string name="verification_emoji_butterfly">Tximeleta</string>
|
||||
<string name="verification_emoji_flower">Lorea</string>
|
||||
<string name="verification_emoji_tree">Zuhaitza</string>
|
||||
<string name="verification_emoji_cactus">Kaktusa</string>
|
||||
<string name="verification_emoji_mushroom">Perretxikoa</string>
|
||||
<string name="verification_emoji_globe">Lurra</string>
|
||||
<string name="verification_emoji_moon">Ilargia</string>
|
||||
<string name="verification_emoji_cloud">Hodeia</string>
|
||||
<string name="verification_emoji_fire">Sua</string>
|
||||
<string name="verification_emoji_banana">Banana</string>
|
||||
<string name="verification_emoji_apple">Sagarra</string>
|
||||
<string name="verification_emoji_strawberry">Marrubia</string>
|
||||
<string name="verification_emoji_corn">Artoa</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Pastela</string>
|
||||
<string name="verification_emoji_heart">Bihotza</string>
|
||||
<string name="verification_emoji_smiley">Irrifartxoa</string>
|
||||
<string name="verification_emoji_robot">Robota</string>
|
||||
<string name="verification_emoji_hat">Txanoa</string>
|
||||
<string name="verification_emoji_glasses">Betaurrekoak</string>
|
||||
<string name="verification_emoji_wrench">Giltza</string>
|
||||
<string name="verification_emoji_santa">Santa</string>
|
||||
<string name="verification_emoji_thumbsup">Ederto</string>
|
||||
<string name="verification_emoji_umbrella">Aterkia</string>
|
||||
<string name="verification_emoji_hourglass">Harea-erlojua</string>
|
||||
<string name="verification_emoji_clock">Erlojua</string>
|
||||
<string name="verification_emoji_gift">Oparia</string>
|
||||
<string name="verification_emoji_lightbulb">Bonbilla</string>
|
||||
<string name="verification_emoji_book">Liburua</string>
|
||||
<string name="verification_emoji_pencil">Arkatza</string>
|
||||
<string name="verification_emoji_paperclip">Klipa</string>
|
||||
<string name="verification_emoji_scissors">Artaziak</string>
|
||||
<string name="verification_emoji_lock">Giltzarrapoa</string>
|
||||
<string name="verification_emoji_key">Giltza</string>
|
||||
<string name="verification_emoji_hammer">Mailua</string>
|
||||
<string name="verification_emoji_telephone">Telefonoa</string>
|
||||
<string name="verification_emoji_flag">Bandera</string>
|
||||
<string name="verification_emoji_train">Trena</string>
|
||||
<string name="verification_emoji_bicycle">Bizikleta</string>
|
||||
<string name="verification_emoji_airplane">Hegazkina</string>
|
||||
<string name="verification_emoji_rocket">Kohetea</string>
|
||||
<string name="verification_emoji_trophy">Saria</string>
|
||||
<string name="verification_emoji_ball">Baloia</string>
|
||||
<string name="verification_emoji_guitar">Gitarra</string>
|
||||
<string name="verification_emoji_trumpet">Tronpeta</string>
|
||||
<string name="verification_emoji_bell">Kanpaia</string>
|
||||
<string name="verification_emoji_anchor">Aingura</string>
|
||||
<string name="verification_emoji_headphone">Aurikularrak</string>
|
||||
<string name="verification_emoji_folder">Karpeta</string>
|
||||
<string name="verification_emoji_pin">Txintxeta</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Hasierako sinkronizazioa:
|
||||
\nKontua inportatzen…</string>
|
||||
|
|
|
@ -77,72 +77,6 @@
|
|||
|
||||
<string name="room_displayname_empty_room">اتاق خالی</string>
|
||||
|
||||
|
||||
<string name="verification_emoji_dog">سگ</string>
|
||||
<string name="verification_emoji_cat">گربه</string>
|
||||
<string name="verification_emoji_lion">شیر</string>
|
||||
<string name="verification_emoji_horse">اسب</string>
|
||||
<string name="verification_emoji_unicorn">تکشاخ</string>
|
||||
<string name="verification_emoji_pig">خوک</string>
|
||||
<string name="verification_emoji_elephant">فیل</string>
|
||||
<string name="verification_emoji_rabbit">خرگوش</string>
|
||||
<string name="verification_emoji_panda">پاندا</string>
|
||||
<string name="verification_emoji_rooster">خروس</string>
|
||||
<string name="verification_emoji_penguin">پنگوئن</string>
|
||||
<string name="verification_emoji_turtle">لاکپشت</string>
|
||||
<string name="verification_emoji_fish">ماهی</string>
|
||||
<string name="verification_emoji_octopus">هشتپا</string>
|
||||
<string name="verification_emoji_butterfly">پروانه</string>
|
||||
<string name="verification_emoji_flower">گل</string>
|
||||
<string name="verification_emoji_tree">درخت</string>
|
||||
<string name="verification_emoji_cactus">کاکتوس</string>
|
||||
<string name="verification_emoji_mushroom">قارچ</string>
|
||||
<string name="verification_emoji_globe">جهان</string>
|
||||
<string name="verification_emoji_moon">ماه</string>
|
||||
<string name="verification_emoji_cloud">ابر</string>
|
||||
<string name="verification_emoji_fire">آتش</string>
|
||||
<string name="verification_emoji_banana">موز</string>
|
||||
<string name="verification_emoji_apple">سیب</string>
|
||||
<string name="verification_emoji_strawberry">توتفرنگی</string>
|
||||
<string name="verification_emoji_corn">بلال</string>
|
||||
<string name="verification_emoji_pizza">پیتزا</string>
|
||||
<string name="verification_emoji_cake">کیک</string>
|
||||
<string name="verification_emoji_heart">قلب</string>
|
||||
<string name="verification_emoji_smiley">لبخند</string>
|
||||
<string name="verification_emoji_robot">آدمآهنی</string>
|
||||
<string name="verification_emoji_hat">کلاه</string>
|
||||
<string name="verification_emoji_glasses">عینک</string>
|
||||
<string name="verification_emoji_wrench">آچار</string>
|
||||
<string name="verification_emoji_santa">بابانوئل</string>
|
||||
<string name="verification_emoji_thumbsup">شست</string>
|
||||
<string name="verification_emoji_umbrella">چتر</string>
|
||||
<string name="verification_emoji_hourglass">ساعت شنی</string>
|
||||
<string name="verification_emoji_clock">ساعت</string>
|
||||
<string name="verification_emoji_gift">هدیه</string>
|
||||
<string name="verification_emoji_lightbulb">لامپ</string>
|
||||
<string name="verification_emoji_book">کتاب</string>
|
||||
<string name="verification_emoji_pencil">مداد</string>
|
||||
<string name="verification_emoji_paperclip">گیره کاغذ</string>
|
||||
<string name="verification_emoji_scissors">قیچی</string>
|
||||
<string name="verification_emoji_lock">قفل</string>
|
||||
<string name="verification_emoji_key">کلید</string>
|
||||
<string name="verification_emoji_hammer">چکّش</string>
|
||||
<string name="verification_emoji_telephone">تلفن</string>
|
||||
<string name="verification_emoji_flag">پرچم</string>
|
||||
<string name="verification_emoji_train">قطار</string>
|
||||
<string name="verification_emoji_bicycle">دوچرخه</string>
|
||||
<string name="verification_emoji_airplane">هواپیما</string>
|
||||
<string name="verification_emoji_rocket">موشک</string>
|
||||
<string name="verification_emoji_trophy">جام</string>
|
||||
<string name="verification_emoji_ball">توپ</string>
|
||||
<string name="verification_emoji_guitar">گیتار</string>
|
||||
<string name="verification_emoji_trumpet">ترومپت</string>
|
||||
<string name="verification_emoji_bell">زنگ</string>
|
||||
<string name="verification_emoji_anchor">لنگر</string>
|
||||
<string name="verification_emoji_headphone">هدفون</string>
|
||||
<string name="verification_emoji_folder">پوشه</string>
|
||||
<string name="verification_emoji_pin">پونز</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">همگامسازی نخستین:
|
||||
\nدر حال درونریزی حساب…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">همگامسازی نخستین:
|
||||
|
|
|
@ -79,70 +79,6 @@
|
|||
<string name="notice_event_redacted_by">%1$s poisti viestin</string>
|
||||
<string name="notice_event_redacted_with_reason">Viesti poistettu [syy: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">%1$s poisti viestin [syy: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Koira</string>
|
||||
<string name="verification_emoji_cat">Kissa</string>
|
||||
<string name="verification_emoji_lion">Leijona</string>
|
||||
<string name="verification_emoji_horse">Hevonen</string>
|
||||
<string name="verification_emoji_unicorn">Yksisarvinen</string>
|
||||
<string name="verification_emoji_pig">Sika</string>
|
||||
<string name="verification_emoji_elephant">Norsu</string>
|
||||
<string name="verification_emoji_rabbit">Kani</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Kukko</string>
|
||||
<string name="verification_emoji_penguin">Pingviini</string>
|
||||
<string name="verification_emoji_turtle">Kilpikonna</string>
|
||||
<string name="verification_emoji_fish">Kala</string>
|
||||
<string name="verification_emoji_octopus">Tursas</string>
|
||||
<string name="verification_emoji_butterfly">Perhonen</string>
|
||||
<string name="verification_emoji_flower">Kukka</string>
|
||||
<string name="verification_emoji_tree">Puu</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Sieni</string>
|
||||
<string name="verification_emoji_globe">Maapallo</string>
|
||||
<string name="verification_emoji_moon">Kuu</string>
|
||||
<string name="verification_emoji_cloud">Pilvi</string>
|
||||
<string name="verification_emoji_fire">Tuli</string>
|
||||
<string name="verification_emoji_banana">Banaani</string>
|
||||
<string name="verification_emoji_apple">Omena</string>
|
||||
<string name="verification_emoji_strawberry">Mansikka</string>
|
||||
<string name="verification_emoji_corn">Maissi</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Kakku</string>
|
||||
<string name="verification_emoji_heart">Sydän</string>
|
||||
<string name="verification_emoji_smiley">Hymiö</string>
|
||||
<string name="verification_emoji_robot">Robotti</string>
|
||||
<string name="verification_emoji_hat">Hattu</string>
|
||||
<string name="verification_emoji_glasses">Silmälasit</string>
|
||||
<string name="verification_emoji_wrench">Jakoavain</string>
|
||||
<string name="verification_emoji_santa">Joulupukki</string>
|
||||
<string name="verification_emoji_thumbsup">Peukut ylös</string>
|
||||
<string name="verification_emoji_umbrella">Sateenvarjo</string>
|
||||
<string name="verification_emoji_hourglass">Tiimalasi</string>
|
||||
<string name="verification_emoji_clock">Kello</string>
|
||||
<string name="verification_emoji_gift">Lahja</string>
|
||||
<string name="verification_emoji_lightbulb">Hehkulamppu</string>
|
||||
<string name="verification_emoji_book">Kirja</string>
|
||||
<string name="verification_emoji_pencil">Lyijykynä</string>
|
||||
<string name="verification_emoji_paperclip">Klemmari</string>
|
||||
<string name="verification_emoji_scissors">Sakset</string>
|
||||
<string name="verification_emoji_lock">Lukko</string>
|
||||
<string name="verification_emoji_key">Avain</string>
|
||||
<string name="verification_emoji_hammer">Vasara</string>
|
||||
<string name="verification_emoji_telephone">Puhelin</string>
|
||||
<string name="verification_emoji_flag">Lippu</string>
|
||||
<string name="verification_emoji_train">Juna</string>
|
||||
<string name="verification_emoji_bicycle">Polkupyörä</string>
|
||||
<string name="verification_emoji_airplane">Lentokone</string>
|
||||
<string name="verification_emoji_rocket">Raketti</string>
|
||||
<string name="verification_emoji_trophy">Palkinto</string>
|
||||
<string name="verification_emoji_ball">Pallo</string>
|
||||
<string name="verification_emoji_guitar">Kitara</string>
|
||||
<string name="verification_emoji_trumpet">Trumpetti</string>
|
||||
<string name="verification_emoji_bell">Soittokello</string>
|
||||
<string name="verification_emoji_anchor">Ankkuri</string>
|
||||
<string name="verification_emoji_headphone">Kuulokkeet</string>
|
||||
<string name="verification_emoji_folder">Kansio</string>
|
||||
<string name="verification_emoji_pin">Nuppineula</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Alkusynkronointi:
|
||||
\nTuodaan tiliä…</string>
|
||||
|
|
68
matrix-sdk-android/src/main/res/values-fi/strings_sas.xml
Normal file
68
matrix-sdk-android/src/main/res/values-fi/strings_sas.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Koira</string>
|
||||
<string name="verification_emoji_cat">Kissa</string>
|
||||
<string name="verification_emoji_lion">Leijona</string>
|
||||
<string name="verification_emoji_horse">Hevonen</string>
|
||||
<string name="verification_emoji_unicorn">Yksisarvinen</string>
|
||||
<string name="verification_emoji_pig">Sika</string>
|
||||
<string name="verification_emoji_elephant">Norsu</string>
|
||||
<string name="verification_emoji_rabbit">Kani</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Kukko</string>
|
||||
<string name="verification_emoji_penguin">Pingviini</string>
|
||||
<string name="verification_emoji_turtle">Kilpikonna</string>
|
||||
<string name="verification_emoji_fish">Kala</string>
|
||||
<string name="verification_emoji_octopus">Tursas</string>
|
||||
<string name="verification_emoji_butterfly">Perhonen</string>
|
||||
<string name="verification_emoji_flower">Kukka</string>
|
||||
<string name="verification_emoji_tree">Puu</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Sieni</string>
|
||||
<string name="verification_emoji_globe">Maapallo</string>
|
||||
<string name="verification_emoji_moon">Kuu</string>
|
||||
<string name="verification_emoji_cloud">Pilvi</string>
|
||||
<string name="verification_emoji_fire">Tuli</string>
|
||||
<string name="verification_emoji_banana">Banaani</string>
|
||||
<string name="verification_emoji_apple">Omena</string>
|
||||
<string name="verification_emoji_strawberry">Mansikka</string>
|
||||
<string name="verification_emoji_corn">Maissi</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Kakku</string>
|
||||
<string name="verification_emoji_heart">Sydän</string>
|
||||
<string name="verification_emoji_smiley">Hymynaama</string>
|
||||
<string name="verification_emoji_robot">Robotti</string>
|
||||
<string name="verification_emoji_hat">Hattu</string>
|
||||
<string name="verification_emoji_glasses">Silmälasit</string>
|
||||
<string name="verification_emoji_spanner">Mutteriavain</string>
|
||||
<string name="verification_emoji_santa">Joulupukki</string>
|
||||
<string name="verification_emoji_thumbs_up">Peukalo ylös</string>
|
||||
<string name="verification_emoji_umbrella">Sateenvarjo</string>
|
||||
<string name="verification_emoji_hourglass">Tiimalasi</string>
|
||||
<string name="verification_emoji_clock">Pöytäkello</string>
|
||||
<string name="verification_emoji_gift">Lahja</string>
|
||||
<string name="verification_emoji_light_bulb">Hehkulamppu</string>
|
||||
<string name="verification_emoji_book">Kirja</string>
|
||||
<string name="verification_emoji_pencil">Lyijykynä</string>
|
||||
<string name="verification_emoji_paperclip">Paperiliitin</string>
|
||||
<string name="verification_emoji_scissors">Sakset</string>
|
||||
<string name="verification_emoji_lock">Lukko</string>
|
||||
<string name="verification_emoji_key">Avain</string>
|
||||
<string name="verification_emoji_hammer">Vasara</string>
|
||||
<string name="verification_emoji_telephone">Puhelin</string>
|
||||
<string name="verification_emoji_flag">Lippu</string>
|
||||
<string name="verification_emoji_train">Juna</string>
|
||||
<string name="verification_emoji_bicycle">Polkupyörä</string>
|
||||
<string name="verification_emoji_aeroplane">Lentokone</string>
|
||||
<string name="verification_emoji_rocket">Raketti</string>
|
||||
<string name="verification_emoji_trophy">Palkinto</string>
|
||||
<string name="verification_emoji_ball">Pallo</string>
|
||||
<string name="verification_emoji_guitar">Kitara</string>
|
||||
<string name="verification_emoji_trumpet">Trumpetti</string>
|
||||
<string name="verification_emoji_bell">Soittokello</string>
|
||||
<string name="verification_emoji_anchor">Ankkuri</string>
|
||||
<string name="verification_emoji_headphones">Kuulokkeet</string>
|
||||
<string name="verification_emoji_folder">Kansio</string>
|
||||
<string name="verification_emoji_pin">Nuppineula</string>
|
||||
</resources>
|
|
@ -78,70 +78,6 @@
|
|||
<string name="notice_event_redacted_by">Message supprimé par %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Message supprimé [motif : %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Message supprimé par %1$s [motif : %2$s]</string>
|
||||
<string name="verification_emoji_dog">Chien</string>
|
||||
<string name="verification_emoji_cat">Chat</string>
|
||||
<string name="verification_emoji_lion">Lion</string>
|
||||
<string name="verification_emoji_horse">Cheval</string>
|
||||
<string name="verification_emoji_unicorn">Licorne</string>
|
||||
<string name="verification_emoji_pig">Cochon</string>
|
||||
<string name="verification_emoji_elephant">Éléphant</string>
|
||||
<string name="verification_emoji_rabbit">Lapin</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Coq</string>
|
||||
<string name="verification_emoji_penguin">Manchot</string>
|
||||
<string name="verification_emoji_turtle">Tortue</string>
|
||||
<string name="verification_emoji_fish">Poisson</string>
|
||||
<string name="verification_emoji_octopus">Pieuvre</string>
|
||||
<string name="verification_emoji_butterfly">Papillon</string>
|
||||
<string name="verification_emoji_flower">Fleur</string>
|
||||
<string name="verification_emoji_tree">Arbre</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Champignon</string>
|
||||
<string name="verification_emoji_globe">Terre</string>
|
||||
<string name="verification_emoji_moon">Lune</string>
|
||||
<string name="verification_emoji_cloud">Nuage</string>
|
||||
<string name="verification_emoji_fire">Feu</string>
|
||||
<string name="verification_emoji_banana">Banane</string>
|
||||
<string name="verification_emoji_apple">Pomme</string>
|
||||
<string name="verification_emoji_strawberry">Fraise</string>
|
||||
<string name="verification_emoji_corn">Maïs</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Gâteau</string>
|
||||
<string name="verification_emoji_heart">Cœur</string>
|
||||
<string name="verification_emoji_smiley">Smiley</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Chapeau</string>
|
||||
<string name="verification_emoji_glasses">Lunettes</string>
|
||||
<string name="verification_emoji_wrench">Clé plate</string>
|
||||
<string name="verification_emoji_santa">Père Noël</string>
|
||||
<string name="verification_emoji_thumbsup">Pouce levé</string>
|
||||
<string name="verification_emoji_umbrella">Parapluie</string>
|
||||
<string name="verification_emoji_hourglass">Sablier</string>
|
||||
<string name="verification_emoji_clock">Horloge</string>
|
||||
<string name="verification_emoji_gift">Cadeau</string>
|
||||
<string name="verification_emoji_lightbulb">Ampoule</string>
|
||||
<string name="verification_emoji_book">Livre</string>
|
||||
<string name="verification_emoji_pencil">Crayon</string>
|
||||
<string name="verification_emoji_paperclip">Trombone</string>
|
||||
<string name="verification_emoji_scissors">Ciseaux</string>
|
||||
<string name="verification_emoji_lock">Cadenas</string>
|
||||
<string name="verification_emoji_key">Clé</string>
|
||||
<string name="verification_emoji_hammer">Marteau</string>
|
||||
<string name="verification_emoji_telephone">Téléphone</string>
|
||||
<string name="verification_emoji_flag">Drapeau</string>
|
||||
<string name="verification_emoji_train">Train</string>
|
||||
<string name="verification_emoji_bicycle">Vélo</string>
|
||||
<string name="verification_emoji_airplane">Avion</string>
|
||||
<string name="verification_emoji_rocket">Fusée</string>
|
||||
<string name="verification_emoji_trophy">Trophée</string>
|
||||
<string name="verification_emoji_ball">Balle</string>
|
||||
<string name="verification_emoji_guitar">Guitare</string>
|
||||
<string name="verification_emoji_trumpet">Trompette</string>
|
||||
<string name="verification_emoji_bell">Cloche</string>
|
||||
<string name="verification_emoji_anchor">Ancre</string>
|
||||
<string name="verification_emoji_headphone">Écouteurs</string>
|
||||
<string name="verification_emoji_folder">Dossier</string>
|
||||
<string name="verification_emoji_pin">Épingle</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Synchronisation initiale :
|
||||
\nImportation du compte…</string>
|
||||
|
|
68
matrix-sdk-android/src/main/res/values-fr/strings_sas.xml
Normal file
68
matrix-sdk-android/src/main/res/values-fr/strings_sas.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Chien</string>
|
||||
<string name="verification_emoji_cat">Chat</string>
|
||||
<string name="verification_emoji_lion">Lion</string>
|
||||
<string name="verification_emoji_horse">Cheval</string>
|
||||
<string name="verification_emoji_unicorn">Licorne</string>
|
||||
<string name="verification_emoji_pig">Cochon</string>
|
||||
<string name="verification_emoji_elephant">Éléphant</string>
|
||||
<string name="verification_emoji_rabbit">Lapin</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Coq</string>
|
||||
<string name="verification_emoji_penguin">Manchot</string>
|
||||
<string name="verification_emoji_turtle">Tortue</string>
|
||||
<string name="verification_emoji_fish">Poisson</string>
|
||||
<string name="verification_emoji_octopus">Poulpe</string>
|
||||
<string name="verification_emoji_butterfly">Papillon</string>
|
||||
<string name="verification_emoji_flower">Fleur</string>
|
||||
<string name="verification_emoji_tree">Arbre</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Champignon</string>
|
||||
<string name="verification_emoji_globe">Globe</string>
|
||||
<string name="verification_emoji_moon">Lune</string>
|
||||
<string name="verification_emoji_cloud">Nuage</string>
|
||||
<string name="verification_emoji_fire">Feu</string>
|
||||
<string name="verification_emoji_banana">Banane</string>
|
||||
<string name="verification_emoji_apple">Pomme</string>
|
||||
<string name="verification_emoji_strawberry">Fraise</string>
|
||||
<string name="verification_emoji_corn">Maïs</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Gâteau</string>
|
||||
<string name="verification_emoji_heart">Cœur</string>
|
||||
<string name="verification_emoji_smiley">Sourire</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Châpeau</string>
|
||||
<string name="verification_emoji_glasses">Lunettes</string>
|
||||
<string name="verification_emoji_spanner">Clé à molette</string>
|
||||
<string name="verification_emoji_santa">Père Noël</string>
|
||||
<string name="verification_emoji_thumbs_up">Pouce en l\'air</string>
|
||||
<string name="verification_emoji_umbrella">Parapluie</string>
|
||||
<string name="verification_emoji_hourglass">Sablier</string>
|
||||
<string name="verification_emoji_clock">Réveil</string>
|
||||
<string name="verification_emoji_gift">Cadeau</string>
|
||||
<string name="verification_emoji_light_bulb">Ampoule</string>
|
||||
<string name="verification_emoji_book">Livre</string>
|
||||
<string name="verification_emoji_pencil">Crayon</string>
|
||||
<string name="verification_emoji_paperclip">Trombone</string>
|
||||
<string name="verification_emoji_scissors">Ciseaux</string>
|
||||
<string name="verification_emoji_lock">Cadenas</string>
|
||||
<string name="verification_emoji_key">Clé</string>
|
||||
<string name="verification_emoji_hammer">Marteau</string>
|
||||
<string name="verification_emoji_telephone">Téléphone</string>
|
||||
<string name="verification_emoji_flag">Drapeau</string>
|
||||
<string name="verification_emoji_train">Train</string>
|
||||
<string name="verification_emoji_bicycle">Vélo</string>
|
||||
<string name="verification_emoji_aeroplane">Avion</string>
|
||||
<string name="verification_emoji_rocket">Fusée</string>
|
||||
<string name="verification_emoji_trophy">Trophée</string>
|
||||
<string name="verification_emoji_ball">Ballon</string>
|
||||
<string name="verification_emoji_guitar">Guitare</string>
|
||||
<string name="verification_emoji_trumpet">Trompette</string>
|
||||
<string name="verification_emoji_bell">Cloche</string>
|
||||
<string name="verification_emoji_anchor">Ancre</string>
|
||||
<string name="verification_emoji_headphones">Casque audio</string>
|
||||
<string name="verification_emoji_folder">Dossier</string>
|
||||
<string name="verification_emoji_pin">Punaise</string>
|
||||
</resources>
|
|
@ -77,70 +77,6 @@
|
|||
<string name="notice_event_redacted_by">Üzenetet eltávolította: %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Üzenet eltávolítva [ok: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Üzenetet eltávolította: %1$s [ok: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Kutya</string>
|
||||
<string name="verification_emoji_cat">Macska</string>
|
||||
<string name="verification_emoji_lion">Oroszlán</string>
|
||||
<string name="verification_emoji_horse">Ló</string>
|
||||
<string name="verification_emoji_unicorn">Egyszarvú</string>
|
||||
<string name="verification_emoji_pig">Malac</string>
|
||||
<string name="verification_emoji_elephant">Elefánt</string>
|
||||
<string name="verification_emoji_rabbit">Nyúl</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Kakas</string>
|
||||
<string name="verification_emoji_penguin">Pingvin</string>
|
||||
<string name="verification_emoji_turtle">Teknős</string>
|
||||
<string name="verification_emoji_fish">Hal</string>
|
||||
<string name="verification_emoji_octopus">Polip</string>
|
||||
<string name="verification_emoji_butterfly">Pillangó</string>
|
||||
<string name="verification_emoji_flower">Virág</string>
|
||||
<string name="verification_emoji_tree">Fa</string>
|
||||
<string name="verification_emoji_cactus">Kaktusz</string>
|
||||
<string name="verification_emoji_mushroom">Gomba</string>
|
||||
<string name="verification_emoji_globe">Föld</string>
|
||||
<string name="verification_emoji_moon">Hold</string>
|
||||
<string name="verification_emoji_cloud">Felhő</string>
|
||||
<string name="verification_emoji_fire">Tűz</string>
|
||||
<string name="verification_emoji_banana">Banán</string>
|
||||
<string name="verification_emoji_apple">Alma</string>
|
||||
<string name="verification_emoji_strawberry">Eper</string>
|
||||
<string name="verification_emoji_corn">Kukorica</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Süti</string>
|
||||
<string name="verification_emoji_heart">Szív</string>
|
||||
<string name="verification_emoji_smiley">Smiley</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Kalap</string>
|
||||
<string name="verification_emoji_glasses">Szemüveg</string>
|
||||
<string name="verification_emoji_wrench">Csavarkulcs</string>
|
||||
<string name="verification_emoji_santa">Télapó</string>
|
||||
<string name="verification_emoji_thumbsup">Hüvelykujj fel</string>
|
||||
<string name="verification_emoji_umbrella">Esernyő</string>
|
||||
<string name="verification_emoji_hourglass">Homokóra</string>
|
||||
<string name="verification_emoji_clock">Óra</string>
|
||||
<string name="verification_emoji_gift">Ajándék</string>
|
||||
<string name="verification_emoji_lightbulb">Égő</string>
|
||||
<string name="verification_emoji_book">Könyv</string>
|
||||
<string name="verification_emoji_pencil">Ceruza</string>
|
||||
<string name="verification_emoji_paperclip">Gémkapocs</string>
|
||||
<string name="verification_emoji_scissors">Olló</string>
|
||||
<string name="verification_emoji_lock">Zár</string>
|
||||
<string name="verification_emoji_key">Kulcs</string>
|
||||
<string name="verification_emoji_hammer">Kalapács</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Zászló</string>
|
||||
<string name="verification_emoji_train">Vonat</string>
|
||||
<string name="verification_emoji_bicycle">Kerékpár</string>
|
||||
<string name="verification_emoji_airplane">Repülő</string>
|
||||
<string name="verification_emoji_rocket">Rakéta</string>
|
||||
<string name="verification_emoji_trophy">Trófea</string>
|
||||
<string name="verification_emoji_ball">Labda</string>
|
||||
<string name="verification_emoji_guitar">Gitár</string>
|
||||
<string name="verification_emoji_trumpet">Trombita</string>
|
||||
<string name="verification_emoji_bell">Harang</string>
|
||||
<string name="verification_emoji_anchor">Vasmacska</string>
|
||||
<string name="verification_emoji_headphone">Fejhallgató</string>
|
||||
<string name="verification_emoji_folder">Mappa</string>
|
||||
<string name="verification_emoji_pin">Tű</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Induló szinkronizáció:
|
||||
\nFiók betöltése…</string>
|
||||
|
|
|
@ -78,70 +78,6 @@
|
|||
<string name="notice_event_redacted_by">Messaggio rimosso da %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Messaggio rimosso [motivo: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Messaggio rimosso da %1$s [motivo: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Cane</string>
|
||||
<string name="verification_emoji_cat">Gatto</string>
|
||||
<string name="verification_emoji_lion">Leone</string>
|
||||
<string name="verification_emoji_horse">Cavallo</string>
|
||||
<string name="verification_emoji_unicorn">Unicorno</string>
|
||||
<string name="verification_emoji_pig">Maiale</string>
|
||||
<string name="verification_emoji_elephant">Elefante</string>
|
||||
<string name="verification_emoji_rabbit">Coniglio</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Gallo</string>
|
||||
<string name="verification_emoji_penguin">Pinguino</string>
|
||||
<string name="verification_emoji_turtle">Tartaruga</string>
|
||||
<string name="verification_emoji_fish">Pesce</string>
|
||||
<string name="verification_emoji_octopus">Piovra</string>
|
||||
<string name="verification_emoji_butterfly">Farfalla</string>
|
||||
<string name="verification_emoji_flower">Fiore</string>
|
||||
<string name="verification_emoji_tree">Albero</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Fungo</string>
|
||||
<string name="verification_emoji_globe">Globo</string>
|
||||
<string name="verification_emoji_moon">Luna</string>
|
||||
<string name="verification_emoji_cloud">Nuvola</string>
|
||||
<string name="verification_emoji_fire">Fuoco</string>
|
||||
<string name="verification_emoji_banana">Banana</string>
|
||||
<string name="verification_emoji_apple">Mela</string>
|
||||
<string name="verification_emoji_strawberry">Fragola</string>
|
||||
<string name="verification_emoji_corn">Mais</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Torta</string>
|
||||
<string name="verification_emoji_heart">Cuore</string>
|
||||
<string name="verification_emoji_smiley">Sorriso</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Cappello</string>
|
||||
<string name="verification_emoji_glasses">Occhiali</string>
|
||||
<string name="verification_emoji_wrench">Chiave inglese</string>
|
||||
<string name="verification_emoji_santa">Babbo Natale</string>
|
||||
<string name="verification_emoji_thumbsup">Pollice in su</string>
|
||||
<string name="verification_emoji_umbrella">Ombrello</string>
|
||||
<string name="verification_emoji_hourglass">Clessidra</string>
|
||||
<string name="verification_emoji_clock">Orologio</string>
|
||||
<string name="verification_emoji_gift">Regalo</string>
|
||||
<string name="verification_emoji_lightbulb">Lampadina</string>
|
||||
<string name="verification_emoji_book">Libro</string>
|
||||
<string name="verification_emoji_pencil">Matita</string>
|
||||
<string name="verification_emoji_paperclip">Graffetta</string>
|
||||
<string name="verification_emoji_scissors">Forbici</string>
|
||||
<string name="verification_emoji_lock">Lucchetto</string>
|
||||
<string name="verification_emoji_key">Chiave</string>
|
||||
<string name="verification_emoji_hammer">Martello</string>
|
||||
<string name="verification_emoji_telephone">Telefono</string>
|
||||
<string name="verification_emoji_flag">Bandiera</string>
|
||||
<string name="verification_emoji_train">Treno</string>
|
||||
<string name="verification_emoji_bicycle">Bicicletta</string>
|
||||
<string name="verification_emoji_airplane">Aeroplano</string>
|
||||
<string name="verification_emoji_rocket">Razzo</string>
|
||||
<string name="verification_emoji_trophy">Trofeo</string>
|
||||
<string name="verification_emoji_ball">Palla</string>
|
||||
<string name="verification_emoji_guitar">Chitarra</string>
|
||||
<string name="verification_emoji_trumpet">Tromba</string>
|
||||
<string name="verification_emoji_bell">Campana</string>
|
||||
<string name="verification_emoji_anchor">Ancora</string>
|
||||
<string name="verification_emoji_headphone">Cuffie</string>
|
||||
<string name="verification_emoji_folder">Cartella</string>
|
||||
<string name="verification_emoji_pin">Spillo</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Sync iniziale:
|
||||
\nImportazione account…</string>
|
||||
|
@ -296,8 +232,4 @@
|
|||
<string name="notice_end_to_end_ok_by_you">Hai attivato la crittografia end-to-end.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Hai attivato la crittografia end-to-end (algoritmo %1$s sconosciuto).</string>
|
||||
|
||||
<string name="call_notification_answer">Accetta</string>
|
||||
<string name="call_notification_reject">Rifiuta</string>
|
||||
<string name="call_notification_hangup">Riaggancia</string>
|
||||
|
||||
</resources>
|
||||
|
|
20
matrix-sdk-android/src/main/res/values-ja/strings_sas.xml
Normal file
20
matrix-sdk-android/src/main/res/values-ja/strings_sas.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">犬</string>
|
||||
<string name="verification_emoji_cat">猫</string>
|
||||
<string name="verification_emoji_horse">馬</string>
|
||||
<string name="verification_emoji_octopus">たこ</string>
|
||||
<string name="verification_emoji_flower">花</string>
|
||||
<string name="verification_emoji_tree">木</string>
|
||||
<string name="verification_emoji_mushroom">きのこ</string>
|
||||
<string name="verification_emoji_moon">月</string>
|
||||
<string name="verification_emoji_apple">リンゴ</string>
|
||||
<string name="verification_emoji_cake">ケーキ</string>
|
||||
<string name="verification_emoji_robot">ロボと</string>
|
||||
<string name="verification_emoji_glasses">めがね</string>
|
||||
<string name="verification_emoji_book">本</string>
|
||||
<string name="verification_emoji_telephone">電話機</string>
|
||||
<string name="verification_emoji_train">電車</string>
|
||||
<string name="verification_emoji_bicycle">自転車</string>
|
||||
</resources>
|
225
matrix-sdk-android/src/main/res/values-kab/strings.xml
Normal file
225
matrix-sdk-android/src/main/res/values-kab/strings.xml
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="summary_message">%1$s: %2$s</string>
|
||||
<string name="summary_user_sent_image">%1$s t.yuzen tugna.</string>
|
||||
<string name="summary_you_sent_image">Tuzneḍ tugna.</string>
|
||||
<string name="notice_room_invite_no_invitee">Tinubga n %s</string>
|
||||
<string name="notice_room_invite_no_invitee_by_you">Tinubga-k•m</string>
|
||||
<string name="notice_room_created">%1$s yesnulfa-d taxxamt</string>
|
||||
<string name="notice_room_created_by_you">Tesnulfaḍ-d taxxamt-a</string>
|
||||
<string name="notice_room_invite">%1$s inced-d %2$s</string>
|
||||
<string name="notice_room_invite_by_you">Tnecdeḍ-d %1$s</string>
|
||||
<string name="notice_room_invite_you">%1$s inced-ik-id</string>
|
||||
<string name="notice_room_join">%1$s yedda ɣer texxamt</string>
|
||||
<string name="notice_room_join_by_you">Teddiḍ ɣer texxamt</string>
|
||||
<string name="notice_room_leave">%1$s yeǧǧa taxxamt</string>
|
||||
<string name="notice_room_leave_by_you">Teǧǧiḍ taxxamt</string>
|
||||
<string name="notice_room_reject">%1$s yugi/tugi tinubga</string>
|
||||
<string name="notice_room_reject_by_you">Tufiḍ tinubga</string>
|
||||
<string name="notice_room_kick">%1$s yessufeɣ %2$s</string>
|
||||
<string name="notice_room_kick_by_you">Tessufɣeḍ %1$s</string>
|
||||
<string name="notice_avatar_url_changed_by_you">Tbeddleḍ avatar-inek·inem</string>
|
||||
<string name="power_level_admin">Anedbal</string>
|
||||
<string name="power_level_moderator">Aseɣyad</string>
|
||||
<string name="power_level_default">Amezwer</string>
|
||||
<string name="power_level_custom_no_value">Sagen</string>
|
||||
|
||||
<string name="notice_power_level_diff">%1$s seg %2$s ɣer %3$s</string>
|
||||
|
||||
<string name="message_failed_to_upload">Tegguma ad d-tali tugna</string>
|
||||
|
||||
<string name="medium_email">Tansa n yimayl</string>
|
||||
|
||||
<string name="summary_user_sent_sticker">%1$s azen astiker.</string>
|
||||
<string name="summary_you_sent_sticker">Tuzneḍ amenṭaḍ.</string>
|
||||
|
||||
<string name="notice_room_unban">%1$s yekkes agdal i %2$s</string>
|
||||
<string name="notice_room_unban_by_you">Tekkseḍ agdal i %1$s</string>
|
||||
<string name="notice_room_ban">%1$s igdel %2$s</string>
|
||||
<string name="notice_room_ban_by_you">Tgedleḍ %1$s</string>
|
||||
<string name="notice_room_withdraw">%1$s issefsex tinubga n %2$s</string>
|
||||
<string name="notice_room_withdraw_by_you">Tesfesxeḍ tinubga n %1$s</string>
|
||||
<string name="notice_avatar_url_changed">%1$s ibeddel avatar-is</string>
|
||||
<string name="notice_display_name_set">%1$s isbadu isem-is i d-ittuseknen ɣer %2$s</string>
|
||||
<string name="notice_display_name_set_by_you">Tesbaduḍ isem-ik•im i d-ittuseknen ɣer %1$s</string>
|
||||
<string name="notice_display_name_changed_from">%1$s ibeddel isem-is i d-ittuseknen seg %2$s ɣer %3$s</string>
|
||||
<string name="notice_display_name_changed_from_by_you">Tbeddleḍ isem-ik•im i d-ittuseknen seg %1$s ɣer %2$s</string>
|
||||
<string name="notice_display_name_removed">%1$s yekkes isem-is i d-ittuseknen (yella %2$s)</string>
|
||||
<string name="notice_display_name_removed_by_you">Tekkseḍ isem-ik·im yettwaskanen (d %1$s)</string>
|
||||
<string name="notice_room_topic_changed">%1$S isnifel asentel s: %2$S</string>
|
||||
<string name="notice_room_topic_changed_by_you">Tesnifleḍ asentel s: %2$S</string>
|
||||
<string name="notice_room_avatar_changed">%1$s ibeddel avaṭar n texxamt</string>
|
||||
<string name="notice_room_avatar_changed_by_you">Tbeddleḍ avaṭar n texxamt</string>
|
||||
<string name="notice_room_name_changed">%1$s ibeddel isem n texxamt s: %2$s</string>
|
||||
<string name="notice_room_name_changed_by_you">Tbeddleḍ isem n texxamt s: %2$s</string>
|
||||
<string name="notice_placed_video_call">%s isɛedda siwel s tvidyut.</string>
|
||||
<string name="notice_placed_video_call_by_you">Tesɛeddaḍ siwel s tvidyut.</string>
|
||||
<string name="notice_placed_voice_call">%s isɛedda asiwel s taɣect.</string>
|
||||
<string name="notice_placed_voice_call_by_you">Tesɛeddaḍ siwel s taɣect.</string>
|
||||
<string name="notice_call_candidates">%s yuzen isefka i usbadu n usiwel.</string>
|
||||
<string name="notice_call_candidates_by_you">Tuzneḍ isefka i usbadu n usiwel.</string>
|
||||
<string name="notice_answered_call">%s yerra ɣef usiwel.</string>
|
||||
<string name="notice_answered_call_by_you">Terriḍ ɣef usiwel.</string>
|
||||
<string name="notice_ended_call">%s iḥbes asiwel.</string>
|
||||
<string name="notice_ended_call_by_you">Tḥebseḍ asiwel.</string>
|
||||
<string name="notice_room_visibility_invited">meṛṛa iɛeggalen n texxamt, segmi ara d-ttwanecden.</string>
|
||||
<string name="notice_room_visibility_joined">meṛṛa iɛeggalen n texamt, segmi ara d-rnun.</string>
|
||||
<string name="notice_room_visibility_shared">meṛṛa iɛeggalen n texxamt.</string>
|
||||
<string name="notice_room_visibility_world_readable">yal yiwen.</string>
|
||||
<string name="notice_room_visibility_unknown">arussin (%s).</string>
|
||||
<string name="notice_end_to_end">%1$s isermed awgelhen seg yixef ɣer yixef (%2$s)</string>
|
||||
<string name="notice_end_to_end_by_you">Tesremdeḍ awgelhen seg yixef ɣer yixef (%2$s)</string>
|
||||
<string name="notice_room_update">%s ileqqem taxxamt-a.</string>
|
||||
<string name="notice_room_update_by_you">Tleqqmeḍ taxxamt-a.</string>
|
||||
|
||||
<string name="notice_requested_voip_conference">%1$s isuter-d asarag VoIP</string>
|
||||
<string name="notice_requested_voip_conference_by_you">Tsutreḍ-d asarag VoIP</string>
|
||||
<string name="notice_voip_started">Asarag VoIP yebda</string>
|
||||
<string name="notice_voip_finished">Asarag VoIP yekfa</string>
|
||||
|
||||
<string name="notice_avatar_changed_too">(avatar daɣen ibeddel)</string>
|
||||
<string name="notice_room_name_removed">%1$s yekkes isem n texxamt</string>
|
||||
<string name="notice_room_name_removed_by_you">Tekkseḍ isem n texxamt</string>
|
||||
<string name="notice_room_topic_removed">%1$s yekkes asentel n texxamt</string>
|
||||
<string name="notice_room_topic_removed_by_you">Tekkseḍ asentel n texxamt</string>
|
||||
<string name="notice_room_avatar_removed">%1$s yekkes avatar n texxamt</string>
|
||||
<string name="notice_room_avatar_removed_by_you">Tekkseḍ avatar n texxamt</string>
|
||||
<string name="notice_event_redacted">Izen ittwakkes</string>
|
||||
<string name="notice_event_redacted_by">Izen ittwakkes sɣur %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Izen ittwakkes [tamentilt: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Izen ittwakkes sɣur %1$s [tamentilt: %2$s]</string>
|
||||
<string name="notice_profile_change_redacted">%1$s ileqqem amaɣnu-ines %2$s</string>
|
||||
<string name="notice_profile_change_redacted_by_you">Tleqqmeḍ amaɣnu-inek•inem %1$s</string>
|
||||
<string name="notice_room_third_party_invite">%1$s yuzen tinubga i %2$s akken ad yeddu ɣer texxamt</string>
|
||||
<string name="notice_room_third_party_invite_by_you">Tuzneḍ tinubga i %1$s akken ad yeddu ɣer texxamt</string>
|
||||
<string name="notice_room_third_party_registered_invite">%1$s iqbel tinubga i %2$s</string>
|
||||
<string name="notice_room_third_party_registered_invite_by_you">Tqebleḍ tinubga i %1$s</string>
|
||||
|
||||
<string name="notice_widget_added">%1$s yerna awiǧit %2$s</string>
|
||||
<string name="notice_widget_added_by_you">Terniḍ awiǧit %1$s</string>
|
||||
<string name="notice_widget_removed">%1$s yekkes awiǧit %2$s</string>
|
||||
<string name="notice_widget_removed_by_you">Tekkseḍ awiǧit %1$s</string>
|
||||
<string name="notice_widget_modified">%1$s ibeddel awiǧit %2$s</string>
|
||||
<string name="notice_widget_modified_by_you">Tbeddleḍ awiǧit %1$s</string>
|
||||
|
||||
<string name="power_level_custom">Sagen (%1$)</string>
|
||||
<string name="notice_power_level_changed_by_you">Tbeddleḍ aswir n tezmert n %1$s.</string>
|
||||
<string name="notice_power_level_changed">%1$s ibeddel aswir n tezmert n %2$s.</string>
|
||||
<string name="notice_crypto_unable_to_decrypt">** Awgelhen d awezɣi: %s **</string>
|
||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Ibenk n umazan ur aɣ-d-yuzin ara tisura i yizen-a.</string>
|
||||
|
||||
<string name="unable_to_send_message">Tuzna n yizen d tawezɣit</string>
|
||||
|
||||
<string name="network_error">Tuccḍa deg uẓeṭṭa</string>
|
||||
<string name="matrix_error">Tuccḍa deg Matrix</string>
|
||||
|
||||
<string name="notice_made_future_room_visibility">%1$s iga amazray n texxamyt i d-iteddun yettban i %2$s</string>
|
||||
<string name="notice_made_future_room_visibility_by_you">Tgiḍ amazray n texxamyt i d-iteddun yettban i %1$s</string>
|
||||
<string name="notice_room_third_party_revoked_invite">%1$s issefsax tinubga i %2$s i wakken ad d-yekcem ɣer texxamt</string>
|
||||
<string name="notice_room_third_party_revoked_invite_by_you">Tesfesxeḍ tinubga i %1$s i wakken ad d-yernu ɣer texxamt</string>
|
||||
<string name="room_error_join_failed_empty_room">D awezɣi tura ad nales ad nuɣal ɣer texxamt tilemt.</string>
|
||||
|
||||
<string name="encrypted_message">Izen yettwawgelhen</string>
|
||||
|
||||
<string name="medium_phone_number">Uṭṭun n tiliɣri</string>
|
||||
|
||||
<string name="room_displayname_invite_from">Tinubga sɣur %s</string>
|
||||
<string name="room_displayname_room_invite">Tinubga ɣer texxamt</string>
|
||||
|
||||
<string name="room_displayname_two_members">%1$s d %2$s</string>
|
||||
|
||||
<plurals name="room_displayname_three_and_more_members">
|
||||
<item quantity="one">%1$s d 1 wayeḍ</item>
|
||||
<item quantity="other">%1$s d %2$d wiyaḍ</item>
|
||||
</plurals>
|
||||
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Tremdeḍ awgelhen seg yixef ɣer yixef (alguritm %1$s ur yettwassen ara).</string>
|
||||
|
||||
<string name="key_verification_request_fallback_message">%s isuter-d ad isenqed tasarut-ik·im, maca amsaɣ-ik·im ur issefrak ara asenqed n tsura deg yidiwenniyen. Ilaq-ak·am useqdec asenqed iqdim n tsura i usenqed n tsura.</string>
|
||||
|
||||
<string name="room_displayname_empty_room">Taxxamt tilemt</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Amtawi n tazwara:
|
||||
\nAktar n umiḍan…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">Amtawi n tazwara:
|
||||
\nAktar n uwgelhen</string>
|
||||
<string name="initial_sync_start_importing_account_rooms">Amtawi n tazwara:
|
||||
\nAktar n texxamin</string>
|
||||
<string name="initial_sync_start_importing_account_joined_rooms">Amtawi n tazwara:
|
||||
\nAktar n texxamin iɣer terniḍ</string>
|
||||
<string name="initial_sync_start_importing_account_invited_rooms">Amtawi n tazwara:
|
||||
\nAktar n texxamin iɣer tettwanecdeḍ</string>
|
||||
<string name="initial_sync_start_importing_account_left_rooms">Amtawi n tazwara:
|
||||
\nAktar n texxamin i teǧǧiḍ</string>
|
||||
<string name="initial_sync_start_importing_account_groups">Amtawi n tazwara:
|
||||
\nAktar n tmezdagnutin</string>
|
||||
<string name="initial_sync_start_importing_account_data">Amtawi n tazwara:
|
||||
\nAktar n yisefka n umiḍan</string>
|
||||
|
||||
<string name="event_status_sending_message">Tuzzna n yizen…</string>
|
||||
<string name="notice_room_invite_no_invitee_with_reason">Tinubga n %1$s. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_invite_no_invitee_with_reason_by_you">Tinubga-k•m. Tamentilt: %1$s</string>
|
||||
<string name="notice_room_invite_with_reason">%1$s inced %2$s. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_invite_with_reason_by_you">Tnecdeḍ %1$s. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_invite_you_with_reason">%1$s inced-ik•ikem. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_join_with_reason">%1$s yedda ɣer texxamt. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_join_with_reason_by_you">Teddiḍ ɣer texxamt. Tamentilt: %1$s</string>
|
||||
<string name="notice_room_leave_with_reason">%1$s yeǧǧa taxxamt. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_leave_with_reason_by_you">Teǧǧiḍ taxxamt. Tamentilt: %1$s</string>
|
||||
<string name="notice_room_reject_with_reason">%1$s yugi tinubga. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_reject_with_reason_by_you">Tugiḍ tinubga. Tamentilt: %1$s</string>
|
||||
<string name="notice_room_kick_with_reason">%1$s yessufeɣ %2$s. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_kick_with_reason_by_you">Tessufɣeḍ %1$s. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_unban_with_reason">%1$s yekkes agdal i %2$s. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_unban_with_reason_by_you">Tekkseḍ agdal i %1$s. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_ban_with_reason">%1$s igdel %2$s. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_ban_with_reason_by_you">Tgedleḍ %1$s. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_third_party_invite_with_reason">%1$s yuzen tinubga i %2$s akken ad yeddu ɣer texxamt. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_third_party_invite_with_reason_by_you">Tuzneḍ tinubga i %1$s iwakken ad yeddu ɣer texxamt. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_third_party_registered_invite_with_reason">%1$s iqbel tinubga i %2$s. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_third_party_registered_invite_with_reason_by_you">Tqebleḍ tinubga i %1$s. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_withdraw_with_reason">%1$s issefsex tinubga n %2$s. Tamentilt: %3$s</string>
|
||||
<string name="notice_room_withdraw_with_reason_by_you">Tesfesxeḍ tinubga n %1$s. Tamentilt: %2$s</string>
|
||||
|
||||
<plurals name="notice_room_aliases_added">
|
||||
<item quantity="one">%1$s yerna %2$s d tansa i texxamt-a.</item>
|
||||
<item quantity="other">%1$s yerna %2$s d tansiwin i texxamt-a.</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="notice_room_aliases_added_by_you">
|
||||
<item quantity="one">Terniḍ %1$s d tansa i texxamt-a.</item>
|
||||
<item quantity="other">Terniḍ %1$s d tansiwin i texxamt-a.</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="notice_room_aliases_removed">
|
||||
<item quantity="one">%1$s yekkes %2$s am tansa i texxamt-a.</item>
|
||||
<item quantity="other">%1$s yekkes %3$s am tansiwin i texxamt-a.</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="notice_room_aliases_removed_by_you">
|
||||
<item quantity="one">Tekkseḍ %1$s am tansa i texxamt-a.</item>
|
||||
<item quantity="other">Tekkseḍ %2$s am tansiwin i texxamt-a.</item>
|
||||
</plurals>
|
||||
|
||||
<string name="notice_room_aliases_added_and_removed">%1$s yerna %2$s terniḍ tekkseḍ %3s am tansiwin i texxamt-a.</string>
|
||||
<string name="notice_room_aliases_added_and_removed_by_you">Terniḍ %1$s terniḍ tekkseḍ %2$s am tansiwin i texxamt-a.</string>
|
||||
|
||||
<string name="notice_room_canonical_alias_set">%1$s isbadu %2$s am tansa tagejdant i texxamt-a.</string>
|
||||
<string name="notice_room_canonical_alias_set_by_you">Tesbaduḍ %1$s am tansa tagejdant i texxamt-a.</string>
|
||||
<string name="notice_room_canonical_alias_unset">%1$s yekkes tansa tagejdant i texxamt-a.</string>
|
||||
<string name="notice_room_canonical_alias_unset_by_you">Tekkseḍ tansa tagejdant i texxamt-a.</string>
|
||||
|
||||
<string name="notice_room_guest_access_can_join">%1$s isireg inebgawen ad ddun ɣer texxamt.</string>
|
||||
<string name="notice_room_guest_access_can_join_by_you">Tsirgeḍ inebgawen ad ddun ɣer texxamt.</string>
|
||||
<string name="notice_room_guest_access_forbidden">%1$s issewḥel inebgawen iwakken ur tteddun ara ɣer texxamt.</string>
|
||||
<string name="notice_room_guest_access_forbidden_by_you">Tesweḥleḍ inebgawen iwakken ur tteddun ara ɣer texxamt.</string>
|
||||
|
||||
<string name="notice_end_to_end_ok">%1$s yermed awgelhen seg yixef ɣer yixef.</string>
|
||||
<string name="notice_end_to_end_ok_by_you">Tremdeḍ awgelhen seg yixef ɣer yixef.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm">%1$s yermed awgelhen seg yixef ɣer yixef (alguritm %2$s ur yettwassen ara).</string>
|
||||
<string name="clear_timeline_send_queue">Sfeḍ tabdart n uraǧu n tuzzna</string>
|
||||
|
||||
<string name="notice_room_third_party_revoked_invite_with_reason">%1$s issefsex tinubga n %2$s i tmerniwt ɣer texxamt. Tamentilt: %2$s</string>
|
||||
<string name="notice_room_third_party_revoked_invite_with_reason_by_you">Tesfesxeḍ tinubga n %1$s i tmerna ɣer texxamt. Tamentilt: %2$s</string>
|
||||
<string name="could_not_redact">Yegguma ad yaru</string>
|
||||
</resources>
|
|
@ -2,7 +2,6 @@
|
|||
<resources>
|
||||
<string name="summary_message">%1$s: %2$s</string>
|
||||
<string name="notice_room_invite_no_invitee">%s님의 초대</string>
|
||||
<string name="verification_emoji_headphone">헤드폰</string>
|
||||
<string name="summary_user_sent_image">%1$s님이 사진을 보냈습니다.</string>
|
||||
<string name="summary_user_sent_sticker">%1$s님이 스티커를 보냈습니다.</string>
|
||||
|
||||
|
@ -78,71 +77,6 @@
|
|||
|
||||
<string name="room_displayname_empty_room">빈 방</string>
|
||||
|
||||
|
||||
<string name="verification_emoji_dog">개</string>
|
||||
<string name="verification_emoji_cat">고양이</string>
|
||||
<string name="verification_emoji_lion">사자</string>
|
||||
<string name="verification_emoji_horse">말</string>
|
||||
<string name="verification_emoji_unicorn">유니콘</string>
|
||||
<string name="verification_emoji_pig">돼지</string>
|
||||
<string name="verification_emoji_elephant">코끼리</string>
|
||||
<string name="verification_emoji_rabbit">토끼</string>
|
||||
<string name="verification_emoji_panda">판다</string>
|
||||
<string name="verification_emoji_rooster">수탉</string>
|
||||
<string name="verification_emoji_penguin">펭귄</string>
|
||||
<string name="verification_emoji_turtle">거북</string>
|
||||
<string name="verification_emoji_fish">물고기</string>
|
||||
<string name="verification_emoji_octopus">문어</string>
|
||||
<string name="verification_emoji_butterfly">나비</string>
|
||||
<string name="verification_emoji_flower">꽃</string>
|
||||
<string name="verification_emoji_tree">나무</string>
|
||||
<string name="verification_emoji_cactus">선인장</string>
|
||||
<string name="verification_emoji_mushroom">버섯</string>
|
||||
<string name="verification_emoji_globe">지구본</string>
|
||||
<string name="verification_emoji_moon">달</string>
|
||||
<string name="verification_emoji_cloud">구름</string>
|
||||
<string name="verification_emoji_fire">불</string>
|
||||
<string name="verification_emoji_banana">바나나</string>
|
||||
<string name="verification_emoji_apple">사과</string>
|
||||
<string name="verification_emoji_strawberry">딸기</string>
|
||||
<string name="verification_emoji_corn">옥수수</string>
|
||||
<string name="verification_emoji_pizza">피자</string>
|
||||
<string name="verification_emoji_cake">케이크</string>
|
||||
<string name="verification_emoji_heart">하트</string>
|
||||
<string name="verification_emoji_smiley">웃음</string>
|
||||
<string name="verification_emoji_robot">로봇</string>
|
||||
<string name="verification_emoji_hat">모자</string>
|
||||
<string name="verification_emoji_glasses">안경</string>
|
||||
<string name="verification_emoji_wrench">스패너</string>
|
||||
<string name="verification_emoji_santa">산타클로스</string>
|
||||
<string name="verification_emoji_thumbsup">좋아요</string>
|
||||
<string name="verification_emoji_umbrella">우산</string>
|
||||
<string name="verification_emoji_hourglass">모래시계</string>
|
||||
<string name="verification_emoji_clock">시계</string>
|
||||
<string name="verification_emoji_gift">선물</string>
|
||||
<string name="verification_emoji_lightbulb">전구</string>
|
||||
<string name="verification_emoji_book">책</string>
|
||||
<string name="verification_emoji_pencil">연필</string>
|
||||
<string name="verification_emoji_paperclip">클립</string>
|
||||
<string name="verification_emoji_scissors">가위</string>
|
||||
<string name="verification_emoji_lock">자물쇠</string>
|
||||
<string name="verification_emoji_key">열쇠</string>
|
||||
<string name="verification_emoji_hammer">망치</string>
|
||||
<string name="verification_emoji_telephone">전화기</string>
|
||||
<string name="verification_emoji_flag">깃발</string>
|
||||
<string name="verification_emoji_train">기차</string>
|
||||
<string name="verification_emoji_bicycle">자전거</string>
|
||||
<string name="verification_emoji_airplane">비행기</string>
|
||||
<string name="verification_emoji_rocket">로켓</string>
|
||||
<string name="verification_emoji_trophy">트로피</string>
|
||||
<string name="verification_emoji_ball">공</string>
|
||||
<string name="verification_emoji_guitar">기타</string>
|
||||
<string name="verification_emoji_trumpet">트럼펫</string>
|
||||
<string name="verification_emoji_bell">종</string>
|
||||
<string name="verification_emoji_anchor">닻</string>
|
||||
<string name="verification_emoji_folder">폴더</string>
|
||||
<string name="verification_emoji_pin">핀</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">초기 동기화:
|
||||
\n계정 가져오는 중…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">초기 동기화:
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Hund</string>
|
||||
<string name="verification_emoji_cat">Katt</string>
|
||||
<string name="verification_emoji_lion">Løve</string>
|
||||
<string name="verification_emoji_horse">Hest</string>
|
||||
<string name="verification_emoji_unicorn">Enhjørning</string>
|
||||
<string name="verification_emoji_pig">Gris</string>
|
||||
<string name="verification_emoji_elephant">Elefant</string>
|
||||
<string name="verification_emoji_rabbit">Kanin</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Hane</string>
|
||||
<string name="verification_emoji_penguin">Pingvin</string>
|
||||
<string name="verification_emoji_turtle">Skilpadde</string>
|
||||
<string name="verification_emoji_fish">Fisk</string>
|
||||
<string name="verification_emoji_octopus">Blekksprut</string>
|
||||
<string name="verification_emoji_butterfly">Sommerfugl</string>
|
||||
<string name="verification_emoji_flower">Blomst</string>
|
||||
<string name="verification_emoji_tree">Tre</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Sopp</string>
|
||||
<string name="verification_emoji_globe">Globus</string>
|
||||
<string name="verification_emoji_moon">Måne</string>
|
||||
<string name="verification_emoji_cloud">Sky</string>
|
||||
<string name="verification_emoji_fire">Flamme</string>
|
||||
<string name="verification_emoji_banana">Banan</string>
|
||||
<string name="verification_emoji_apple">Eple</string>
|
||||
<string name="verification_emoji_strawberry">Jordbær</string>
|
||||
<string name="verification_emoji_corn">Mais</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Kake</string>
|
||||
<string name="verification_emoji_heart">Hjerte</string>
|
||||
<string name="verification_emoji_smiley">Smilefjes</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Hatt</string>
|
||||
<string name="verification_emoji_glasses">Briller</string>
|
||||
<string name="verification_emoji_spanner">Fastnøkkel</string>
|
||||
<string name="verification_emoji_santa">Julenisse</string>
|
||||
<string name="verification_emoji_thumbs_up">Tommel Opp</string>
|
||||
<string name="verification_emoji_umbrella">Paraply</string>
|
||||
<string name="verification_emoji_hourglass">Timeglass</string>
|
||||
<string name="verification_emoji_clock">Klokke</string>
|
||||
<string name="verification_emoji_gift">Gave</string>
|
||||
<string name="verification_emoji_light_bulb">Lyspære</string>
|
||||
<string name="verification_emoji_book">Bok</string>
|
||||
<string name="verification_emoji_pencil">Blyant</string>
|
||||
<string name="verification_emoji_paperclip">BInders</string>
|
||||
<string name="verification_emoji_scissors">Saks</string>
|
||||
<string name="verification_emoji_lock">Lås</string>
|
||||
<string name="verification_emoji_key">Nøkkel</string>
|
||||
<string name="verification_emoji_hammer">Hammer</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Flagg</string>
|
||||
<string name="verification_emoji_train">Tog</string>
|
||||
<string name="verification_emoji_bicycle">Sykkel</string>
|
||||
<string name="verification_emoji_aeroplane">Fly</string>
|
||||
<string name="verification_emoji_rocket">Rakett</string>
|
||||
<string name="verification_emoji_trophy">Pokal</string>
|
||||
<string name="verification_emoji_ball">Ball</string>
|
||||
<string name="verification_emoji_guitar">Gitar</string>
|
||||
<string name="verification_emoji_trumpet">Trompet</string>
|
||||
<string name="verification_emoji_bell">Bjelle</string>
|
||||
<string name="verification_emoji_anchor">Anker</string>
|
||||
<string name="verification_emoji_headphones">Hodetelefoner</string>
|
||||
<string name="verification_emoji_folder">Mappe</string>
|
||||
<string name="verification_emoji_pin">Tegnestift</string>
|
||||
</resources>
|
|
@ -87,70 +87,6 @@
|
|||
<string name="notice_event_redacted_by">Bericht verwijderd door %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Bericht verwijderd [reden: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">Bericht verwijderd door %1$s [reden: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Hond</string>
|
||||
<string name="verification_emoji_cat">Kat</string>
|
||||
<string name="verification_emoji_lion">Leeuw</string>
|
||||
<string name="verification_emoji_horse">Paard</string>
|
||||
<string name="verification_emoji_unicorn">Eenhoorn</string>
|
||||
<string name="verification_emoji_pig">Varken</string>
|
||||
<string name="verification_emoji_elephant">Olifant</string>
|
||||
<string name="verification_emoji_rabbit">Konijn</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Haan</string>
|
||||
<string name="verification_emoji_penguin">Pinguïn</string>
|
||||
<string name="verification_emoji_turtle">Schildpad</string>
|
||||
<string name="verification_emoji_fish">Vis</string>
|
||||
<string name="verification_emoji_octopus">Octopus</string>
|
||||
<string name="verification_emoji_butterfly">Vlinder</string>
|
||||
<string name="verification_emoji_flower">Bloem</string>
|
||||
<string name="verification_emoji_tree">Boom</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Paddenstoel</string>
|
||||
<string name="verification_emoji_globe">Aardbol</string>
|
||||
<string name="verification_emoji_moon">Maan</string>
|
||||
<string name="verification_emoji_cloud">Wolk</string>
|
||||
<string name="verification_emoji_fire">Vuur</string>
|
||||
<string name="verification_emoji_banana">Banaan</string>
|
||||
<string name="verification_emoji_apple">Appel</string>
|
||||
<string name="verification_emoji_strawberry">Aardbei</string>
|
||||
<string name="verification_emoji_corn">Maïs</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Taart</string>
|
||||
<string name="verification_emoji_heart">Hart</string>
|
||||
<string name="verification_emoji_smiley">Smiley</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Hoed</string>
|
||||
<string name="verification_emoji_glasses">Bril</string>
|
||||
<string name="verification_emoji_wrench">Moersleutel</string>
|
||||
<string name="verification_emoji_santa">Kerstman</string>
|
||||
<string name="verification_emoji_thumbsup">Duim omhoog</string>
|
||||
<string name="verification_emoji_umbrella">Paraplu</string>
|
||||
<string name="verification_emoji_hourglass">Zandloper</string>
|
||||
<string name="verification_emoji_clock">Klok</string>
|
||||
<string name="verification_emoji_gift">Cadeau</string>
|
||||
<string name="verification_emoji_lightbulb">Gloeilamp</string>
|
||||
<string name="verification_emoji_book">Boek</string>
|
||||
<string name="verification_emoji_pencil">Potlood</string>
|
||||
<string name="verification_emoji_paperclip">Paperclip</string>
|
||||
<string name="verification_emoji_scissors">Schaar</string>
|
||||
<string name="verification_emoji_lock">Slot</string>
|
||||
<string name="verification_emoji_key">Sleutel</string>
|
||||
<string name="verification_emoji_hammer">Hamer</string>
|
||||
<string name="verification_emoji_telephone">Telefoon</string>
|
||||
<string name="verification_emoji_flag">Vlag</string>
|
||||
<string name="verification_emoji_train">Trein</string>
|
||||
<string name="verification_emoji_bicycle">Fiets</string>
|
||||
<string name="verification_emoji_airplane">Vliegtuig</string>
|
||||
<string name="verification_emoji_rocket">Raket</string>
|
||||
<string name="verification_emoji_trophy">Trofee</string>
|
||||
<string name="verification_emoji_ball">Bal</string>
|
||||
<string name="verification_emoji_guitar">Gitaar</string>
|
||||
<string name="verification_emoji_trumpet">Trompet</string>
|
||||
<string name="verification_emoji_bell">Bel</string>
|
||||
<string name="verification_emoji_anchor">Anker</string>
|
||||
<string name="verification_emoji_headphone">Koptelefoon</string>
|
||||
<string name="verification_emoji_folder">Map</string>
|
||||
<string name="verification_emoji_pin">Speld</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Initiële synchronisatie:
|
||||
\nAccount wordt geïmporteerd…</string>
|
||||
|
|
68
matrix-sdk-android/src/main/res/values-nl/strings_sas.xml
Normal file
68
matrix-sdk-android/src/main/res/values-nl/strings_sas.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Hond</string>
|
||||
<string name="verification_emoji_cat">Kat</string>
|
||||
<string name="verification_emoji_lion">Leeuw</string>
|
||||
<string name="verification_emoji_horse">Paard</string>
|
||||
<string name="verification_emoji_unicorn">Eenhoorn</string>
|
||||
<string name="verification_emoji_pig">Varken</string>
|
||||
<string name="verification_emoji_elephant">Olifant</string>
|
||||
<string name="verification_emoji_rabbit">Konijn</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Haan</string>
|
||||
<string name="verification_emoji_penguin">Pinguïn</string>
|
||||
<string name="verification_emoji_turtle">Schildpad</string>
|
||||
<string name="verification_emoji_fish">Vis</string>
|
||||
<string name="verification_emoji_octopus">Octopus</string>
|
||||
<string name="verification_emoji_butterfly">Vlinder</string>
|
||||
<string name="verification_emoji_flower">Bloem</string>
|
||||
<string name="verification_emoji_tree">Boom</string>
|
||||
<string name="verification_emoji_cactus">Cactus</string>
|
||||
<string name="verification_emoji_mushroom">Paddenstoel</string>
|
||||
<string name="verification_emoji_globe">Wereldbol</string>
|
||||
<string name="verification_emoji_moon">Maan</string>
|
||||
<string name="verification_emoji_cloud">Wolk</string>
|
||||
<string name="verification_emoji_fire">Vuur</string>
|
||||
<string name="verification_emoji_banana">Banaan</string>
|
||||
<string name="verification_emoji_apple">Appel</string>
|
||||
<string name="verification_emoji_strawberry">Aardbei</string>
|
||||
<string name="verification_emoji_corn">Maïs</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Taart</string>
|
||||
<string name="verification_emoji_heart">Hart</string>
|
||||
<string name="verification_emoji_smiley">Smiley</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Hoed</string>
|
||||
<string name="verification_emoji_glasses">Bril</string>
|
||||
<string name="verification_emoji_spanner">Moersleutel</string>
|
||||
<string name="verification_emoji_santa">Kerstman</string>
|
||||
<string name="verification_emoji_thumbs_up">Duim omhoog</string>
|
||||
<string name="verification_emoji_umbrella">Paraplu</string>
|
||||
<string name="verification_emoji_hourglass">Zandloper</string>
|
||||
<string name="verification_emoji_clock">Wekker</string>
|
||||
<string name="verification_emoji_gift">Geschenk</string>
|
||||
<string name="verification_emoji_light_bulb">Gloeilamp</string>
|
||||
<string name="verification_emoji_book">Boek</string>
|
||||
<string name="verification_emoji_pencil">Potlood</string>
|
||||
<string name="verification_emoji_paperclip">Papierklemmetje</string>
|
||||
<string name="verification_emoji_scissors">Schaar</string>
|
||||
<string name="verification_emoji_lock">Slot</string>
|
||||
<string name="verification_emoji_key">Sleutel</string>
|
||||
<string name="verification_emoji_hammer">Hamer</string>
|
||||
<string name="verification_emoji_telephone">Telefoon</string>
|
||||
<string name="verification_emoji_flag">Vlag</string>
|
||||
<string name="verification_emoji_train">Trein</string>
|
||||
<string name="verification_emoji_bicycle">Fiets</string>
|
||||
<string name="verification_emoji_aeroplane">Vliegtuig</string>
|
||||
<string name="verification_emoji_rocket">Raket</string>
|
||||
<string name="verification_emoji_trophy">Trofee</string>
|
||||
<string name="verification_emoji_ball">Bal</string>
|
||||
<string name="verification_emoji_guitar">Gitaar</string>
|
||||
<string name="verification_emoji_trumpet">Trompet</string>
|
||||
<string name="verification_emoji_bell">Bel</string>
|
||||
<string name="verification_emoji_anchor">Anker</string>
|
||||
<string name="verification_emoji_headphones">Koptelefoon</string>
|
||||
<string name="verification_emoji_folder">Map</string>
|
||||
<string name="verification_emoji_pin">Duimspijker</string>
|
||||
</resources>
|
|
@ -77,70 +77,6 @@
|
|||
<string name="notice_event_redacted_by">%1$s strauk meldingi</string>
|
||||
<string name="notice_event_redacted_with_reason">Meldingi vart stroki [av di: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">%1$s strauk meldingi [av di: %2$s]</string>
|
||||
<string name="verification_emoji_dog">Hund</string>
|
||||
<string name="verification_emoji_cat">Katt</string>
|
||||
<string name="verification_emoji_lion">Løva</string>
|
||||
<string name="verification_emoji_horse">Hest</string>
|
||||
<string name="verification_emoji_unicorn">Einhyrning</string>
|
||||
<string name="verification_emoji_pig">Gris</string>
|
||||
<string name="verification_emoji_elephant">Elefant</string>
|
||||
<string name="verification_emoji_rabbit">Hare</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Hane</string>
|
||||
<string name="verification_emoji_penguin">Pingvin</string>
|
||||
<string name="verification_emoji_turtle">Skjoldpadda</string>
|
||||
<string name="verification_emoji_fish">Fisk</string>
|
||||
<string name="verification_emoji_octopus">Blekksprut</string>
|
||||
<string name="verification_emoji_butterfly">Fivrelde</string>
|
||||
<string name="verification_emoji_flower">Blome</string>
|
||||
<string name="verification_emoji_tree">Tre</string>
|
||||
<string name="verification_emoji_cactus">Kaktus</string>
|
||||
<string name="verification_emoji_mushroom">Sopp</string>
|
||||
<string name="verification_emoji_globe">Klote</string>
|
||||
<string name="verification_emoji_moon">Måne</string>
|
||||
<string name="verification_emoji_cloud">Sky</string>
|
||||
<string name="verification_emoji_fire">Eld</string>
|
||||
<string name="verification_emoji_banana">Banan</string>
|
||||
<string name="verification_emoji_apple">Eple</string>
|
||||
<string name="verification_emoji_strawberry">Jordbær</string>
|
||||
<string name="verification_emoji_corn">Mais</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Kaka</string>
|
||||
<string name="verification_emoji_heart">Hjarta</string>
|
||||
<string name="verification_emoji_smiley">Smilandlit</string>
|
||||
<string name="verification_emoji_robot">Robot</string>
|
||||
<string name="verification_emoji_hat">Hatt</string>
|
||||
<string name="verification_emoji_glasses">Brillor</string>
|
||||
<string name="verification_emoji_wrench">Skiftenykel</string>
|
||||
<string name="verification_emoji_santa">Nissen</string>
|
||||
<string name="verification_emoji_thumbsup">Tumalen Upp</string>
|
||||
<string name="verification_emoji_umbrella">Regnskjold</string>
|
||||
<string name="verification_emoji_hourglass">Timeglas</string>
|
||||
<string name="verification_emoji_clock">Ur</string>
|
||||
<string name="verification_emoji_gift">Gåva</string>
|
||||
<string name="verification_emoji_lightbulb">Ljospera</string>
|
||||
<string name="verification_emoji_book">Bok</string>
|
||||
<string name="verification_emoji_pencil">Blyant</string>
|
||||
<string name="verification_emoji_paperclip">Binders</string>
|
||||
<string name="verification_emoji_scissors">Saks</string>
|
||||
<string name="verification_emoji_lock">Lås</string>
|
||||
<string name="verification_emoji_key">Nykel</string>
|
||||
<string name="verification_emoji_hammer">Hamar</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Flagg</string>
|
||||
<string name="verification_emoji_train">Tog</string>
|
||||
<string name="verification_emoji_bicycle">Sykkel</string>
|
||||
<string name="verification_emoji_airplane">Flyg</string>
|
||||
<string name="verification_emoji_rocket">Rakett</string>
|
||||
<string name="verification_emoji_trophy">Pokal</string>
|
||||
<string name="verification_emoji_ball">Ball</string>
|
||||
<string name="verification_emoji_guitar">Gitar</string>
|
||||
<string name="verification_emoji_trumpet">Trompet</string>
|
||||
<string name="verification_emoji_bell">Klokka</string>
|
||||
<string name="verification_emoji_anchor">Ankar</string>
|
||||
<string name="verification_emoji_headphone">Hodetelefon</string>
|
||||
<string name="verification_emoji_folder">Mappa</string>
|
||||
<string name="verification_emoji_pin">Nål</string>
|
||||
|
||||
<string name="notice_room_update">%s oppgraderte rommet.</string>
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue