1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Compare commits

...

161 Commits

Author SHA1 Message Date
ganfra
4fa634a283 Merge branch 'release/1.6.6' into main 2023-10-05 16:57:37 +02:00
ganfra
7001f21330 Adding fastlane file for version 1.6.6 2023-10-05 16:57:21 +02:00
ganfra
d379cef0ba Changelog for version 1.6.6 2023-10-05 16:55:44 +02:00
Valere
59ddf1a107 Merge pull request #8654 from vector-im/bca/fix_8653_qr_code
Fix QR code login support in rust
2023-10-03 23:45:37 +02:00
Valere
a015eda72c code review 2023-10-03 11:29:50 +02:00
Valere
87df8ab6f6 Update matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt
Co-authored-by: Benoit Marty <benoitm@matrix.org>
2023-10-03 11:24:38 +02:00
Valere
1bd2da5c99 disable flacky test on legacy crypto 2023-10-02 16:39:08 +02:00
Valere
2709cb2973 missing deprecated 2023-10-01 22:19:54 +02:00
Valere
0d70f6eb54 missing mock 2023-10-01 21:59:46 +02:00
Valere
42eec4b557 update changelog 2023-10-01 19:41:46 +02:00
Valere
6ee438d7d5 bump crypto sdk 2023-10-01 19:25:12 +02:00
Valere
3b9daec869 Fix QR code login support in rust 2023-09-27 15:42:05 +02:00
Benoit Marty
1b3be240b3 Merge pull request #8645 from vector-im/hughns/oidc-device-logout-in-chrome-tab
Open OIDC account management URL in chrome tab
2023-09-14 21:54:23 +02:00
Benoit Marty
8c1cc44255 Merge pull request #8627 from vector-im/feature/bma/hideAccountDeactivation
Hide deactivate account section in case of account managed externally.
2023-09-14 21:53:13 +02:00
Hugh Nimmo-Smith
3f2f3860e1 Changelog 2023-09-14 11:27:49 +01:00
Hugh Nimmo-Smith
470557c59e Open OIDC account management URL in chrome tab
Not the external browser
2023-09-14 11:23:19 +01:00
Yoan Pintas
ff548d2f98 Fix crash when max shortcuts count is exceeded (#8644) 2023-09-13 10:43:03 +00:00
Benoit Marty
d31c741f9d Hide deactivate account section in case of account managed externally. 2023-09-12 16:28:28 +02:00
Benoit Marty
ec9a066900 Merge pull request #8620 from vector-im/feature/bma/oidcSessionEnd
Feature/bma/OIDC session end
2023-09-12 16:25:46 +02:00
Benoit Marty
52a06931f4 Change the test to hide multi signout of devices.
We do not need an external account management URL, which is optional, but we need to know if account management is delegate to Oidc.
2023-08-31 16:04:45 +02:00
Benoit Marty
a889d8d678 Store the authentication issuer into DB. 2023-08-31 09:57:47 +02:00
Jorge Martin Espinosa
1f41c54a82 Merge pull request #8630 from vector-im/dependabot/gradle/org.matrix.rustcomponents-crypto-android-0.3.14
Bump org.matrix.rustcomponents:crypto-android from 0.3.10 to 0.3.14
2023-08-29 08:19:17 +02:00
Jorge Martín
fe51ee3956 Try bumping heap size of gradle and the kotlin daemon as a last resort 2023-08-28 17:17:41 +02:00
Jorge Martín
d65459cc59 Try bumping the whole memory heap size in rust tests 2023-08-28 16:05:40 +02:00
Jorge Martín
dc8230e435 Ignore LocaleFolder lint error since we won't fix it. 2023-08-28 15:35:20 +02:00
Jorge Martín
0838a10b65 Bump memory allocated for tests 2023-08-28 15:34:52 +02:00
Benoit Marty
a3be0286ee Merge pull request #8341 from tomtit/bugfix/issue-7758
Fixes #7758: Fixed JWT token for Jitsi openidtoken-jwt authentication
2023-08-28 13:15:05 +02:00
dependabot[bot]
751bd27c9d Bump org.matrix.rustcomponents:crypto-android from 0.3.10 to 0.3.14
Bumps [org.matrix.rustcomponents:crypto-android](https://github.com/matrix-org/matrix-rust-components-kotlin) from 0.3.10 to 0.3.14.
- [Release notes](https://github.com/matrix-org/matrix-rust-components-kotlin/releases)
- [Commits](https://github.com/matrix-org/matrix-rust-components-kotlin/compare/crypto-v0.3.10...crypto-v0.3.14)

---
updated-dependencies:
- dependency-name: org.matrix.rustcomponents:crypto-android
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-25 23:02:56 +00:00
Benoit Marty
0a6dbeb3fe Remove unsupported language. We may handle that during the next SAS string update. 2023-08-23 16:51:31 +02:00
Benoit Marty
bc23f82ade Merge pull request #8625 from RiotTranslateBot/weblate-element-android-element-app
Translations update from Weblate
2023-08-23 16:08:29 +02:00
Benoit Marty
ca109f70a4 Merge pull request #8626 from vector-im/sync-sas-strings
Sync SAS Strings
2023-08-23 16:07:30 +02:00
Besnik Bleta
b5c224f3e0 Translated using Weblate (Albanian)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/
2023-08-23 09:18:32 +00:00
Besnik Bleta
e2c7833f93 Translated using Weblate (Albanian)
Currently translated at 99.4% (2626 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/
2023-08-23 09:18:30 +00:00
Weblate
6d5f59c67e Merge branch 'origin/develop' into Weblate. 2023-08-23 07:41:31 +00:00
bmarty
e6bd57d88c Sync SAS Strings 2023-08-23 07:21:22 +00:00
Benoit Marty
81f7517560 Be able to trigger manually this workflow. 2023-08-23 09:20:04 +02:00
Weblate
1ceacdd194 Merge branch 'origin/develop' into Weblate. 2023-08-23 07:15:33 +00:00
Benoit Marty
52aa4bb0d8 Translated using Weblate (Turkish)
Currently translated at 58.6% (1546 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/
2023-08-23 07:15:31 +00:00
Florian Renaud
a5d231c259 Merge pull request #8623 from vector-im/travis/fix-sas-import
Fix SAS strings import URL
2023-08-23 08:51:57 +02:00
Linerly
e6a18a2241 Translated using Weblate (Indonesian)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/id/
2023-08-23 06:12:22 +00:00
Jeff Huang
003a134f68 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/
2023-08-23 03:25:15 +00:00
Ihor Hordiichuk
494e824a85 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/
2023-08-22 21:40:19 +00:00
Jozef Gaal
df97229b9c Translated using Weblate (Slovak)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/
2023-08-22 20:18:57 +00:00
Travis Ralston
6e6478a949 add changelog 2023-08-22 12:14:36 -06:00
Travis Ralston
de688aa93b Fix SAS strings import URL
See https://github.com/vector-im/element-android/issues/8525
2023-08-22 12:11:58 -06:00
waclaw66
1eee5c1de7 Translated using Weblate (Czech)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/
2023-08-22 18:09:44 +00:00
Danial Behzadi
ce5d42d484 Translated using Weblate (Persian)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/
2023-08-22 17:58:13 +00:00
Danial Behzadi
6379420401 Translated using Weblate (Persian)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/
2023-08-22 17:58:12 +00:00
Vri
9821487a8e Translated using Weblate (German)
Currently translated at 100.0% (102 of 102 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/de/
2023-08-22 16:08:08 +00:00
Weblate
2b29a57b9b Merge branch 'origin/develop' into Weblate. 2023-08-22 15:14:22 +00:00
Benoit Marty
87e5900dcd Merge pull request #8618 from vector-im/feature/bma/3pidCapability
Hide setting entry point to manage 3Pid if homeserver capability `m.3pid_changes` is set to `false`.
2023-08-22 15:10:12 +02:00
Benoit Marty
dc19380fbf Changelog 2023-08-22 12:41:13 +02:00
Benoit Marty
880ed69f97 OIDC redirect to the web page to delete a session (new session manager) #8616 2023-08-22 12:17:23 +02:00
Benoit Marty
8941e6396c Hide multi signout if we have an external account manager (#8616) 2023-08-22 12:08:33 +02:00
Benoit Marty
425441546e Format 2023-08-22 12:08:06 +02:00
Benoit Marty
12395e9b04 OIDC redirect to the web page to delete a session (legacy session manager) #8616 2023-08-22 11:40:47 +02:00
Benoit Marty
8f6edba403 Fix typo 2023-08-21 18:00:41 +02:00
Benoit Marty
39a783196e Hide setting entry point to manage 3Pid if homeserver capability m.3pid_changes is set to false. 2023-08-21 17:25:48 +02:00
LinAGKar
92399aba07 Translated using Weblate (Swedish)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/
2023-08-17 21:03:17 +00:00
phardyle
83e2419c30 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (2636 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/
2023-08-15 11:56:13 +00:00
phardyle
3216fa6146 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.1% (2613 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/
2023-08-15 11:56:13 +00:00
franconian
eeb67e1934 Translated using Weblate (German)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2023-08-13 23:51:07 +00:00
franconian
23e7bdbae3 Translated using Weblate (German)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2023-08-13 21:14:58 +00:00
Someone
ad7934847c Translated using Weblate (Vietnamese)
Currently translated at 88.2% (2326 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/
2023-08-12 10:45:18 +00:00
Nizami
45be2749f6 Translated using Weblate (Azerbaijani)
Currently translated at 9.2% (245 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/az/
2023-08-10 15:54:18 +00:00
Ihor Hordiichuk
754ea6a98d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (2636 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
2023-08-09 22:49:13 +00:00
Nizami
30906885ec Translated using Weblate (Azerbaijani)
Currently translated at 9.0% (240 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/az/
2023-08-09 22:49:13 +00:00
Ihor Hordiichuk
5580f307be Translated using Weblate (Ukrainian)
Currently translated at 100.0% (2636 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/
2023-08-07 01:11:10 +00:00
DarkCoder15
8885d14ee5 Translated using Weblate (Russian)
Currently translated at 99.9% (2634 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2023-08-04 13:52:59 +00:00
Berk Mirsat KAPOT
380a0b8de3 Translated using Weblate (Turkish)
Currently translated at 58.6% (1546 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/
2023-07-31 06:37:03 +00:00
Edgars Andersons
1bbd4b7e44 Translated using Weblate (Latvian)
Currently translated at 99.8% (2633 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-31 06:37:01 +00:00
Edgars Andersons
27bae30eac Translated using Weblate (Latvian)
Currently translated at 99.8% (2633 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-29 08:36:45 +00:00
Edgars Andersons
40fd9f2f7b Translated using Weblate (Latvian)
Currently translated at 97.0% (2558 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-29 01:16:29 +00:00
Rafael Fontenelle
10cde1f0a6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (100 of 100 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/
2023-07-28 06:43:13 +00:00
Rafael Fontenelle
fd46487270 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (2636 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/
2023-07-28 06:43:11 +00:00
Edgars Andersons
cd7bf12e16 Translated using Weblate (Latvian)
Currently translated at 92.3% (2434 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-28 06:43:09 +00:00
Edgars Andersons
95b63ccefb Translated using Weblate (Latvian)
Currently translated at 88.6% (2336 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-27 12:00:34 +00:00
Weblate
975ef3c06f Merge branch 'origin/develop' into Weblate. 2023-07-25 20:21:06 +00:00
Edgars Andersons
e567b9c9cf Translated using Weblate (Latvian)
Currently translated at 84.0% (2216 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-25 20:21:03 +00:00
Benoit Marty
9aeb3b7074 version++ 2023-07-25 14:58:22 +02:00
Benoit Marty
313d4f82f7 Merge tag 'v1.6.5' into develop
tag
2023-07-25 14:56:55 +02:00
Benoit Marty
fd6a45a3ae Merge branch 'release/1.6.5' into main 2023-07-25 14:56:37 +02:00
Benoit Marty
f8138a7860 Adding fastlane file for version 1.6.5 2023-07-25 14:56:31 +02:00
Benoit Marty
0dea54388c Changelog for version 1.6.5 2023-07-25 14:56:03 +02:00
Benoit Marty
9776839b50 Merge pull request #8589 from RiotTranslateBot/weblate-element-android-element-app
Translations update from Weblate
2023-07-25 09:59:16 +02:00
Edgars Andersons
f48ed3679b Translated using Weblate (Latvian)
Currently translated at 83.7% (2207 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-25 02:31:19 +00:00
Edgars Andersons
d9a27b1514 Translated using Weblate (Latvian)
Currently translated at 83.0% (2189 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-24 08:38:07 +00:00
Edgars Andersons
9d19ca6ec2 Translated using Weblate (Latvian)
Currently translated at 82.7% (2180 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-22 11:22:15 +00:00
Weblate
657822891a Merge branch 'origin/develop' into Weblate. 2023-07-22 02:35:31 +00:00
raspin0
c86b4f9e9f Translated using Weblate (Polish)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/
2023-07-22 02:35:28 +00:00
Edgars Andersons
3fe71357dd Translated using Weblate (Latvian)
Currently translated at 82.3% (2170 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-21 17:52:37 +00:00
Benoit Marty
14d691446a Merge pull request #8583 from RiotTranslateBot/weblate-element-android-element-app
Translations update from Weblate
2023-07-21 17:52:19 +02:00
Edgars Andersons
86a126b257 Translated using Weblate (Latvian)
Currently translated at 80.1% (2113 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-19 13:13:54 +00:00
Srebrni
69b8e125e0 Translated using Weblate (Slovenian)
Currently translated at 2.6% (70 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
2023-07-18 23:14:52 +00:00
Weblate
832b4680d2 Merge branch 'origin/develop' into Weblate. 2023-07-18 20:14:53 +00:00
Srebrni
b3e8a64ad5 Translated using Weblate (Slovenian)
Currently translated at 2.3% (63 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
2023-07-18 20:14:50 +00:00
Poesty Li
37f62671f6 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.1% (2613 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/
2023-07-18 20:14:49 +00:00
Srebrni
0c7fdae63a Translated using Weblate (Slovenian)
Currently translated at 2.3% (63 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
2023-07-18 20:14:35 +00:00
th ad
2ec4d1c98a Translated using Weblate (Slovenian)
Currently translated at 2.3% (63 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
2023-07-18 20:14:35 +00:00
th ad
9ce1034a5c Translated using Weblate (Slovenian)
Currently translated at 2.3% (62 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
2023-07-18 20:13:21 +00:00
Srebrni
2af1516ebd Translated using Weblate (Slovenian)
Currently translated at 2.3% (62 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sl/
2023-07-18 20:13:06 +00:00
Edgars Andersons
2a158996e6 Translated using Weblate (Latvian)
Currently translated at 75.6% (1995 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-18 20:01:58 +00:00
Benoit Marty
180a2eec60 Merge pull request #8578 from vector-im/feature/bma/crashFixes
Crash fixes
2023-07-17 16:12:24 +02:00
Benoit Marty
13596594a4 Merge pull request #8579 from RiotTranslateBot/weblate-element-android-element-app
Translations update from Weblate
2023-07-17 11:05:53 +02:00
Jozef Gaal
12a7506b57 Translated using Weblate (Slovak)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
2023-07-17 05:25:50 +00:00
Edgars Andersons
23f0f6ada3 Translated using Weblate (Latvian)
Currently translated at 75.1% (1982 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-15 09:15:15 +00:00
Edgars Andersons
6ac5254ff3 Translated using Weblate (Latvian)
Currently translated at 73.1% (1929 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-14 06:58:43 +00:00
Edgars Andersons
d98ba3c08e Translated using Weblate (Latvian)
Currently translated at 72.3% (1908 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-14 06:13:43 +00:00
walito-arch
70744b2dad Translated using Weblate (Swahili)
Currently translated at 1.4% (38 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sw/
2023-07-14 00:15:56 +00:00
Nui Harime
e76063126b Translated using Weblate (Russian)
Currently translated at 99.8% (2633 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2023-07-14 00:15:55 +00:00
Edgars Andersons
66f6cfcc6c Translated using Weblate (Latvian)
Currently translated at 71.6% (1888 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-14 00:15:54 +00:00
Benoit Marty
ae52d4cd3c Fix crash in the setting when enabling/disabling integration manager and there is no network. 2023-07-11 18:10:54 +02:00
Benoit Marty
8433e222ad Fix unhandled crashes when network is not reacheable. 2023-07-11 18:10:38 +02:00
Benoit Marty
2a5df54ae4 Fix crash: show an error message with a Retry button when there is no network when displaying the BootstrapBottomSheet. 2023-07-11 18:09:00 +02:00
Edgars Andersons
8eccae44e5 Translated using Weblate (Latvian)
Currently translated at 69.9% (1844 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-11 15:01:50 +00:00
Nui Harime
841028774e Translated using Weblate (Russian)
Currently translated at 99.8% (2633 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2023-07-10 23:07:14 +00:00
Nui Harime
c08b99a4f1 Translated using Weblate (Russian)
Currently translated at 99.7% (2629 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2023-07-10 09:16:09 +00:00
KuriakiMariaHere
6fd589440d Translated using Weblate (Greek)
Currently translated at 17.6% (465 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2023-07-09 22:24:20 +00:00
KuriakiMariaHere
eef01ad8f9 Translated using Weblate (Greek)
Currently translated at 17.1% (452 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2023-07-08 21:49:11 +00:00
Nui Harime
99e171dbee Translated using Weblate (Russian)
Currently translated at 99.6% (2628 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2023-07-08 20:43:45 +00:00
KuriakiMariaHere
2bbd5ee7d9 Translated using Weblate (Greek)
Currently translated at 17.0% (451 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2023-07-08 20:43:44 +00:00
KuriakiMariaHere
c3752f529a Translated using Weblate (Greek)
Currently translated at 16.7% (442 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2023-07-08 00:56:30 +00:00
Nui Harime
c1e77c6dc9 Translated using Weblate (Russian)
Currently translated at 99.6% (2628 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
2023-07-07 12:56:21 +00:00
walito-arch
1ef2de0356 Translated using Weblate (Swahili)
Currently translated at 1.0% (28 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sw/
2023-07-06 11:54:16 +00:00
Theo
87f2a69fb1 Translated using Weblate (Greek)
Currently translated at 16.0% (425 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2023-07-06 11:46:28 +00:00
Dimitris Vagiakakos
fedbfe4931 Translated using Weblate (Greek)
Currently translated at 16.0% (425 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/el/
2023-07-06 11:46:28 +00:00
Edgars Andersons
85d0837f3b Translated using Weblate (Latvian)
Currently translated at 68.6% (1810 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-05 12:21:22 +00:00
Edgars Andersons
1b43087eb3 Translated using Weblate (Latvian)
Currently translated at 68.5% (1807 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-05 09:23:39 +00:00
Weblate
dab799e3cb Merge branch 'origin/develop' into Weblate. 2023-07-04 14:12:38 +00:00
Yoan Pintas
0573915a0a Update MSC3912 implementation: Redaction of related events (#8532) 2023-07-04 13:12:37 +00:00
Edgars Andersons
1ab2bb9bf8 Translated using Weblate (Latvian)
Currently translated at 68.1% (1797 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-07-03 23:17:22 +00:00
Someone
0c9ebfdab6 Translated using Weblate (Vietnamese)
Currently translated at 88.1% (2324 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/
2023-07-02 01:36:44 +00:00
Weblate
9a1f5fd1a0 Merge branch 'origin/develop' into Weblate. 2023-06-30 15:52:04 +00:00
Kat Gerasimova
bbcea97120 Send new issues to new triage board (#8567)
Issues should go to the V2 triage board
2023-06-30 16:18:15 +02:00
Someone
6fcd582f2d Translated using Weblate (Vietnamese)
Currently translated at 88.0% (2320 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/
2023-06-30 11:16:51 +00:00
Someone
e4e17d865b Translated using Weblate (Vietnamese)
Currently translated at 56.0% (56 of 100 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/vi/
2023-06-30 11:16:51 +00:00
Someone
0a4bdceff8 Translated using Weblate (Vietnamese)
Currently translated at 88.0% (2320 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/
2023-06-29 17:16:17 +00:00
Văn Huy Dương
05ce2cab27 Translated using Weblate (Vietnamese)
Currently translated at 88.0% (2320 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/
2023-06-29 10:41:04 +00:00
Edgars Andersons
7080ee1c26 Translated using Weblate (Latvian)
Currently translated at 68.0% (1795 of 2636 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/
2023-06-29 07:27:45 +00:00
Vri
cf6de09483 Translated using Weblate (German)
Currently translated at 100.0% (2640 of 2640 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
2023-06-28 09:00:05 +00:00
Weblate
0c7dcc767d Merge branch 'origin/develop' into Weblate. 2023-06-27 17:03:37 +00:00
Benoit Marty
2b4b5f05eb Merge pull request #8556 from vector-im/feature/bma/noJcenter
Feature/bma/no jcenter
2023-06-27 17:04:59 +02:00
Weblate
12bf1ea2eb Merge branch 'origin/develop' into Weblate. 2023-06-27 14:58:20 +00:00
Valere
7b8cf5d917 version++ 2023-06-27 13:30:17 +02:00
Valere
21200266e2 Merge branch 'release/1.6.3' into develop 2023-06-27 13:22:41 +02:00
Weblate
6e7078637f Merge branch 'origin/develop' into Weblate. 2023-06-26 23:36:45 +00:00
FIONover
61b05edd9e Translated using Weblate (Armenian)
Currently translated at 1.0% (1 of 100 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/hy/
2023-06-26 23:36:41 +00:00
Benoit Marty
7940584674 Changelog. 2023-06-23 23:20:30 +02:00
Benoit Marty
9b63293e45 Rename files to avoid danger issue. 2023-06-23 23:13:52 +02:00
Benoit Marty
2c57453efd Fix detekt issue 2023-06-23 23:00:00 +02:00
Benoit Marty
ea424f29fb Fix ktlint issue 2023-06-23 22:58:28 +02:00
Benoit Marty
dc9e649703 Remove Jcenter repository ref (#2773) 2023-06-23 19:00:06 +02:00
Benoit Marty
e9f9decf00 Import source from https://github.com/dm77/barcodescanner 2023-06-23 18:58:42 +02:00
Benoit Marty
69680a9856 Import source from https://github.com/2dxgujun/Kpan 2023-06-23 18:23:14 +02:00
Benoit Marty
e9b9434671 Remove unused dep. 2023-06-23 18:01:36 +02:00
Benoit Marty
ff09ba1208 Import source from https://github.com/cmelchior/realmfieldnameshelper 2023-06-23 17:58:51 +02:00
Benoit Marty
cd292488b6 Fix warning 2023-06-23 17:51:57 +02:00
Benoit Marty
1dd3c1589e Remove unused dep. 2023-06-23 17:48:06 +02:00
Benoit Marty
3da1497d27 Import source from https://github.com/natario1/Autocomplete 2023-06-23 17:46:12 +02:00
Benoit Marty
f304e40d57 Import source from https://github.com/amulyakhare/TextDrawable 2023-06-23 17:35:14 +02:00
Alexey Nechaev
28da02c583 Fixes #7758: Fixed JWT token for Jitsi openidtoken-jwt authentication
Signed-off-by: Alexey Nechaev <seysane@yahoo.com>
2023-04-22 19:31:24 +03:00
176 changed files with 7309 additions and 838 deletions

View File

@@ -7,7 +7,7 @@ on:
# Enrich gradle.properties for CI/CD
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=1g" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:

View File

@@ -7,7 +7,7 @@ on:
- cron: "0 4 * * *"
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=1g" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:

View File

@@ -6,7 +6,7 @@ on:
- cron: "0 4 * * *"
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=1g" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:

View File

@@ -1,5 +1,6 @@
name: Sync Data From External Sources
on:
workflow_dispatch:
schedule:
# At 00:00 on every Monday UTC
- cron: '0 0 * * 1'
@@ -80,4 +81,4 @@ jobs:
*Note*: Change are coming from [this project](https://github.com/matrix-org/matrix-analytics-events)
branch: sync-analytics-plan
base: develop
base: develop

View File

@@ -9,7 +9,7 @@ on:
# Enrich gradle.properties for CI/CD
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx5g -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx3g" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 4 --no-daemon
jobs:

View File

@@ -15,3 +15,11 @@ jobs:
project: Issue triage
column: Incoming
repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
triage-new-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
with:
project-url: https://github.com/orgs/vector-im/projects/91
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}

View File

@@ -1,3 +1,34 @@
Changes in Element v1.6.6 (2023-10-05)
======================================
Bugfixes 🐛
----------
- Fixed JWT token for Jitsi openidtoken-jwt authentication ([#7758](https://github.com/vector-im/element-android/issues/7758))
- Fix crash when max shortcuts count is exceeded ([#8644](https://github.com/vector-im/element-android/issues/8644))
- Fix Login with QR code not working with rust crypto. ([#8653](https://github.com/vector-im/element-android/issues/8653))
Other changes
-------------
- Use 3PID capability to show / hide email UI in settings ([#8615](https://github.com/vector-im/element-android/issues/8615))
- If an external account manager is configured on the server, use it to delete other sessions and hide the multi session deletion. ([#8616](https://github.com/vector-im/element-android/issues/8616))
- Hide account deactivation UI for account managed externally. ([#8619](https://github.com/vector-im/element-android/issues/8619))
- Fix import of SAS Emoji string translations. ([#8623](https://github.com/vector-im/element-android/issues/8623))
- Open external account manager for delete other sessions using Chrome custom tabs. ([#8645](https://github.com/vector-im/element-android/issues/8645))
Changes in Element v1.6.5 (2023-07-25)
======================================
Bugfixes 🐛
----------
- Fix several crashes observed when the device cannot reach the homeserver ([#8578](https://github.com/vector-im/element-android/issues/8578))
Other changes
-------------
- Update MSC3912 implementation: Redaction of related events ([#8481](https://github.com/vector-im/element-android/issues/8481))
- Include some source code in our project to remove our dependency to artifact hosted by bintray (Jcenter). ([#8556](https://github.com/vector-im/element-android/issues/8556))
Changes in Element v1.6.3 (2023-06-27)
======================================

View File

@@ -112,16 +112,6 @@ allprojects {
groups.google.group.each { includeGroup it }
}
}
//noinspection JcenterRepositoryObsolete
// Do not use `jcenter`, it prevents Dependabot from working properly
maven {
url 'https://jcenter.bintray.com'
content {
groups.jcenter.regex.each { includeGroupByRegex it }
groups.jcenter.group.each { includeGroup it }
}
}
maven {
url 'https://s01.oss.sonatype.org/content/repositories/snapshots'
content {
@@ -129,7 +119,6 @@ allprojects {
groups.mavenSnapshots.group.each { includeGroup it }
}
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
@@ -342,6 +331,10 @@ ext.initScreenshotTests = { project ->
}
}
tasks.withType(Test) {
maxHeapSize = "2g"
}
// Workaround to have KSP generated Kotlin code available in the IDE (for code completion)
// Ref: https://github.com/airbnb/epoxy/releases/tag/5.0.0beta02
subprojects { project ->

View File

@@ -115,6 +115,7 @@ ext.groups = [
'com.linkedin.dexmaker',
'com.mapbox.mapboxsdk',
'com.nulab-inc',
'com.otaliastudios',
'com.otaliastudios.opengl',
'com.parse.bolts',
'com.pinterest',
@@ -234,18 +235,4 @@ ext.groups = [
'xml-apis',
]
],
jcenter : [
regex: [
],
group: [
'com.amulyakhare',
'com.otaliastudios',
'com.yqritc',
// https://github.com/cmelchior/realmfieldnameshelper/issues/42
'dk.ilios',
'im.dlg',
'me.dm7.barcodescanner',
'me.gujun.android',
]
]
]

View File

@@ -0,0 +1,2 @@
Hlavní změny v této verzi: Element Android nyní používá Crypto Rust SDK.
Úplný seznam změn: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Hlavní změny v této verzi: opravné vydání.
Úplný seznam změn: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Die wichtigsten Änderungen in dieser Version: Element Android nutzt nun das Crypto-Rust-SDK.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Die wichtigsten Änderungen in dieser Version: Fehlerbehebungen.
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Main changes in this version: corrective release.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Main changes in this version: mainly bug fixes.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده می‌کند.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
تغغیرات عمده در این نگارش: ارائه تصحیحی.
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1 @@
Խմբային մեսինջեր - գաղտնագրված շփում, խմբային չատեր և վիդեո զանգեր

View File

@@ -0,0 +1 @@
Element - Անվտանգ Մեսինջեր

View File

@@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Element Android sekarang menggunakan SDK Kripto Rust.
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: rilis perbaikan.
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Threads são agora habilitadas por padrão.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Threads são agora habilitadas por padrão.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente correção de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente melhorias no recurso de transmissão de voz.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente correção de bugs, em especial a correção da mensagem não aparecer na linha do tempo.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente correção de bugs, em especial a correção da mensagem não aparecer na linha do tempo.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente correção de bugs.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente correção de bugs.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: permalinks para salas, espaços, usuários e mensagens são agora exibidos como pílulas na linha do tempo. Também corrigimos alguns problemas com figurinhas personalizadas e o marcador de lido ficando travado no passado.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Basicamente correção de bugs.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Element Android está agora usando o Crypto Rust SDK.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Element Android está agora usando o Crypto Rust SDK.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Principais mudanças nesta versão: Element Android está agora usando o Crypto Rust SDK.
Changelog completo: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Element Android teraz používa Crypto Rust SDK.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: opravné vydanie.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Ndryshime në këtë version: Element Android tanimë përdor Crypto Rust SDK.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Ndryshimet kryesore në këtë version: hedhje në qarkullim me ndreqje të ndryshme.
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Основні зміни в цій версії: Element для Android відтепер використовує Crypto Rust SDK.
Список усіх змін: https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
Основні зміни в цій версії: коригувальний випуск.
Список усіх змін: https://github.com/vector-im/element-android/releases

View File

@@ -1,42 +1,42 @@
Element vừa là một ứng dụng nhắn tin bảo mật vừa là một ứng dụng cộng tác nhóm năng suất, lý tưởng cho các cuộc trò chuyện nhóm khi làm việc từ xa. Ứng dụng trò chuyện này sử dụng mã hóa đầu cuối để cung cấp tính năng hội thảo truyền hình, chia sẻ tệp và cuộc gọi thoại mạnh mẽ.
<b> Các tính năng của Element bao gồm: </b>
- Các công cụ giao tiếp trực tuyến tiên tiến
<b>Các tính năng của Element bao gồm:</b>
- Công cụ giao tiếp trực tuyến nâng cao
- Các tin nhắn được mã hóa hoàn toàn để cho phép liên lạc doanh nghiệp an toàn hơn, ngay cả đối với những người làm việc từ xa
- Trò chuyện phi tập trung dựa trên khung mã nguồn mở Matrix
- Chia sẻ tệp một cách an toàn với dữ liệu được mã hóa trong khi quản lý dự án
- Trò chuyện video với gọi thoại qua giao thức Internet (IP - VoIP) và chia sẻ màn hình
- Chia sẻ tệp bảo mật với dữ liệu được mã hóa trong khi quản lý dự án
- Trò chuyện video với gọi thoại qua giao thức Internet (VoIP) và chia sẻ màn hình
- Tích hợp dễ dàng với các công cụ cộng tác trực tuyến yêu thích của bạn, công cụ quản lý dự án, dịch vụ VoIP và các ứng dụng nhắn tin nhóm khác
Element hoàn toàn khác với các ứng dụng nhắn tin và cộng tác khác. Hoạt động trên Matrix, một mạng mở để nhắn tin bảo mật và giao tiếp phi tập trung. Đồng thời, cho phép tự lưu trữ để cung cấp cho người dùng quyền sở hữu và kiểm soát tối đa dữ liệu và tin nhắn của họ.
Element hoàn toàn khác với các ứng dụng nhắn tin và cộng tác khác. Hoạt động trên Matrix, một mạng mở để nhắn tin bảo mật và giao tiếp phi tập trung. Đồng thời, cho phép tự vận hành để cung cấp cho người dùng quyền sở hữu và kiểm soát tối đa dữ liệu và tin nhắn của họ.
<b> Nhắn tin mã hóa và riêng tư </b>
Element bảo vệ bạn khỏi các quảng cáo không mong muốn, khai thác dữ liệu và kiểm soát khu vực. Element cũng bảo mật tất cả dữ liệu của bạn, video 1-1 và giao tiếp thoại thông qua mã hóa đầu cuối và xác minh thiết bị có chữ ký chéo.
<b>Nhắn tin mã hóa và riêng tư</b>
Element bảo vệ bạn khỏi các quảng cáo không mong muốn, khai thác dữ liệu và kiểm soát khu vực. Element cũng bảo mật tất cả dữ liệu của bạn, truyền hình 1-1 và giao tiếp thoại thông qua mã hóa đầu cuối và xác thực chéo thiết bị.
Element cung cấp cho bạn quyền kiểm soát quyền riêng tư của mình đồng thời cho phép bạn giao tiếp an toàn với bất kỳ ai trên mạng Matrix hoặc các công cụ cộng tác kinh doanh khác bằng cách tích hợp với các ứng dụng như Slack.
Element cung cấp cho bạn quyền kiểm soát quyền riêng tư của mình đồng thời cho phép bạn giao tiếp bảo mật với bất kỳ ai trên mạng Matrix hoặc các công cụ cộng tác kinh doanh khác bằng cách tích hợp với các ứng dụng như Slack.
<b> Element có thể được tự lưu trữ </b>
Để cho phép kiểm soát nhiều hơn dữ liệu nhạy cảm và các cuộc trò chuyện của bạn, Element có thể được tự lưu trữ hoặc bạn có thể chọn bất kỳ máy chủ Matrix nào - tiêu chuẩn cho giao tiếp phi tập trung, mã nguồn mở. Element cung cấp cho bạn quyền riêng tư, tuân thủ bảo mật và tính linh hoạt trong tích hợp.
<b>Element có thể được tự vận hành</b>
Để cho phép kiểm soát nhiều hơn dữ liệu nhạy cảm và các cuộc trò chuyện của bạn, Element có thể được tự vận hành hoặc bạn có thể chọn bất kỳ máy chủ Matrix nào - tiêu chuẩn cho giao tiếp phi tập trung, mã nguồn mở. Element cung cấp cho bạn quyền riêng tư, tuân thủ bảo mật và tính linh hoạt trong tích hợp.
<b> Sở hữu dữ liệu của bạn </b>
Bạn quyết định nơi lưu giữ dữ liệu và tin nhắn của mình. Không có rủi ro khai thác dữ liệu hoặc truy cập từ bên thứ ba.
<b>Sở hữu dữ liệu của bạn</b>
Bạn quyết định nơi lưu trữ dữ liệu và tin nhắn của mình. Không có rủi ro khai thác dữ liệu hoặc truy cập từ bên thứ ba.
Element giúp bạn kiểm soát theo những cách khác nhau:
1. Tạo một tài khoản miễn phí trên máy chủ công cộng matrix.org do các nhà phát triển Matrix vận hành hoặc chọn từ hàng nghìn máy chủ công cộng do các tình nguyện viên lưu trữ
1. Tạo một tài khoản miễn phí trên máy chủ công cộng matrix.org do các nhà phát triển Matrix vận hành hoặc chọn từ hàng nghìn máy chủ công cộng do các tình nguyện viên vận hành
2. Tự lưu trữ tài khoản của bạn bằng cách chạy một máy chủ trên cơ sở hạ tầng CNTT của riêng bạn
3. Đăng ký tài khoản trên máy chủ tùy chỉnh bằng cách chỉ cần đăng ký nền tảng Element Matrix Services hosting
<b> Nhắn tin và cộng tác mở </b>
<b>Nhắn tin và cộng tác mở</b>
Bạn có thể trò chuyện với bất kỳ ai trên mạng Matrix, cho dù họ đang sử dụng Element, một ứng dụng Matrix khác hay ngay cả khi họ đang sử dụng một ứng dụng nhắn tin khác.
<b> Siêu bảo mật </b>
Mã hóa đầu-cuối thực (chỉ những người trong cuộc trò chuyện mới có thể giải mã tin nhắn) và xác minh thiết bị xác thực chéo.
<b>Siêu bảo mật</b>
Mã hóa đầu-cuối thực (chỉ những người trong cuộc trò chuyện mới có thể giải mã tin nhắn) và xác thực chéo thiết bị.
<b> Giao tiếp và tích hợp hoàn chỉnh </b>
Nhắn tin, cuộc gọi thoại và video, chia sẻ tệp, chia sẻ màn hình và một loạt các tích hợp, bot và widget. Xây dựng phòng, cộng đồng, giữ liên lạc và hoàn thành công việc.
<b>Giao tiếp và tích hợp hoàn chỉnh</b>
Nhắn tin, gọi thoại và truyền hình, chia sẻ tệp, chia sẻ màn hình và một loạt các tích hợp, bot và widget. Tạo phòng, cộng đồng, giữ liên lạc và hoàn thành công việc.
<b> Tiếp tục nơi bạn đã dừng lại </b>
<b>Tiếp tục nơi bạn đã dừng lạ </b>
Giữ liên lạc mọi lúc mọi nơi với lịch sử tin nhắn được đồng bộ hóa hoàn toàn trên tất cả các thiết bị của bạn và trên web tại https://app.element.io
<b> Mã nguồn mở </b>
Element Android là một dự án mã nguồn mở, được lưu trữ trên GitHub. Vui lòng báo cáo lỗi và / hoặc đóng góp phát triển tại https://github.com/vector-im/element-android
<b>Mã nguồn mở</b>
Element Android là một dự án mã nguồn mở, được lưu trữ trên GitHub. Vui lòng báo cáo lỗi và / hoặc đóng góp, phát triển tại https://github.com/vector-im/element-android

View File

@@ -0,0 +1,2 @@
此版本的主要變更現在起Element Android 使用 Crypto Rust SDK。
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,2 @@
此版本中的主要變動:版本修正。
完整的變更紀錄https://github.com/vector-im/element-android/releases

View File

@@ -0,0 +1,32 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "com.otaliastudios.autocomplete"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation libs.androidx.recyclerview
}
afterEvaluate {
tasks.findAll { it.name.startsWith("lint") }.each {
it.enabled = false
}
}

View File

@@ -0,0 +1,434 @@
package com.otaliastudios.autocomplete;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextWatcher;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.PopupWindow;
import androidx.annotation.NonNull;
/**
* Entry point for adding Autocomplete behavior to a {@link EditText}.
*
* You can construct a {@code Autocomplete} using the builder provided by {@link Autocomplete#on(EditText)}.
* Building is enough, but you can hold a reference to this class to call its public methods.
*
* Requires:
* - {@link EditText}: this is both the anchor for the popup, and the source of text events that we listen to
* - {@link AutocompletePresenter}: this presents items in the popup window. See class for more info.
* - {@link AutocompleteCallback}: if specified, this listens to click events and visibility changes
* - {@link AutocompletePolicy}: if specified, this controls how and when to show the popup based on text events
* If not, this defaults to {@link SimplePolicy}: shows the popup when text.length() bigger than 0.
*/
public final class Autocomplete<T> implements TextWatcher, SpanWatcher {
private final static String TAG = Autocomplete.class.getSimpleName();
private final static boolean DEBUG = false;
private static void log(String log) {
if (DEBUG) Log.e(TAG, log);
}
/**
* Builder for building {@link Autocomplete}.
* The only mandatory item is a presenter, {@link #with(AutocompletePresenter)}.
*
* @param <T> the data model
*/
public final static class Builder<T> {
private EditText source;
private AutocompletePresenter<T> presenter;
private AutocompletePolicy policy;
private AutocompleteCallback<T> callback;
private Drawable backgroundDrawable;
private float elevationDp = 6;
private Builder(EditText source) {
this.source = source;
}
/**
* Registers the {@link AutocompletePresenter} to be used, responsible for showing
* items. See the class for info.
*
* @param presenter desired presenter
* @return this for chaining
*/
public Builder<T> with(AutocompletePresenter<T> presenter) {
this.presenter = presenter;
return this;
}
/**
* Registers the {@link AutocompleteCallback} to be used, responsible for listening to
* clicks provided by the presenter, and visibility changes.
*
* @param callback desired callback
* @return this for chaining
*/
public Builder<T> with(AutocompleteCallback<T> callback) {
this.callback = callback;
return this;
}
/**
* Registers the {@link AutocompletePolicy} to be used, responsible for showing / dismissing
* the popup when certain events happen (e.g. certain characters are typed).
*
* @param policy desired policy
* @return this for chaining
*/
public Builder<T> with(AutocompletePolicy policy) {
this.policy = policy;
return this;
}
/**
* Sets a background drawable for the popup.
*
* @param backgroundDrawable drawable
* @return this for chaining
*/
public Builder<T> with(Drawable backgroundDrawable) {
this.backgroundDrawable = backgroundDrawable;
return this;
}
/**
* Sets elevation for the popup. Defaults to 6 dp.
*
* @param elevationDp popup elevation, in DP
* @return this for chaning.
*/
public Builder<T> with(float elevationDp) {
this.elevationDp = elevationDp;
return this;
}
/**
* Builds an Autocomplete instance. This is enough for autocomplete to be set up,
* but you can hold a reference to the object and call its public methods.
*
* @return an Autocomplete instance, if you need it
*
* @throws RuntimeException if either EditText or the presenter are null
*/
public Autocomplete<T> build() {
if (source == null) throw new RuntimeException("Autocomplete needs a source!");
if (presenter == null) throw new RuntimeException("Autocomplete needs a presenter!");
if (policy == null) policy = new SimplePolicy();
return new Autocomplete<T>(this);
}
private void clear() {
source = null;
presenter = null;
callback = null;
policy = null;
backgroundDrawable = null;
elevationDp = 6;
}
}
/**
* Entry point for building autocomplete on a certain {@link EditText}.
* @param anchor the anchor for the popup, and the source of text events
* @param <T> your data model
* @return a Builder for set up
*/
public static <T> Builder<T> on(EditText anchor) {
return new Builder<T>(anchor);
}
private AutocompletePolicy policy;
private AutocompletePopup popup;
private AutocompletePresenter<T> presenter;
private AutocompleteCallback<T> callback;
private EditText source;
private boolean block;
private boolean disabled;
private boolean openBefore;
private String lastQuery = "null";
private Autocomplete(Builder<T> builder) {
policy = builder.policy;
presenter = builder.presenter;
callback = builder.callback;
source = builder.source;
// Set up popup
popup = new AutocompletePopup(source.getContext());
popup.setAnchorView(source);
popup.setGravity(Gravity.START);
popup.setModal(false);
popup.setBackgroundDrawable(builder.backgroundDrawable);
popup.setElevation(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, builder.elevationDp,
source.getContext().getResources().getDisplayMetrics()));
// popup dimensions
AutocompletePresenter.PopupDimensions dim = this.presenter.getPopupDimensions();
popup.setWidth(dim.width);
popup.setHeight(dim.height);
popup.setMaxWidth(dim.maxWidth);
popup.setMaxHeight(dim.maxHeight);
// Fire visibility events
popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
lastQuery = "null";
if (callback != null) callback.onPopupVisibilityChanged(false);
boolean saved = block;
block = true;
policy.onDismiss(source.getText());
block = saved;
presenter.hideView();
}
});
// Set up source
source.getText().setSpan(this, 0, source.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
source.addTextChangedListener(this);
// Set up presenter
presenter.registerClickProvider(new AutocompletePresenter.ClickProvider<T>() {
@Override
public void click(@NonNull T item) {
AutocompleteCallback<T> callback = Autocomplete.this.callback;
EditText edit = Autocomplete.this.source;
if (callback == null) return;
boolean saved = block;
block = true;
boolean dismiss = callback.onPopupItemClicked(edit.getText(), item);
if (dismiss) dismissPopup();
block = saved;
}
});
builder.clear();
}
/**
* Controls how the popup operates with an input method.
*
* If the popup is showing, calling this method will take effect only
* the next time the popup is shown.
*
* @param mode a {@link PopupWindow} input method mode
*/
public void setInputMethodMode(int mode) {
popup.setInputMethodMode(mode);
}
/**
* Sets the operating mode for the soft input area.
*
* @param mode The desired mode, see {@link WindowManager.LayoutParams#softInputMode}
*/
public void setSoftInputMode(int mode) {
popup.setSoftInputMode(mode);
}
/**
* Shows the popup with the given query.
* There is rarely need to call this externally: it is already triggered by events on the anchor.
* To control when this is called, provide a good implementation of {@link AutocompletePolicy}.
*
* @param query query text.
*/
public void showPopup(@NonNull CharSequence query) {
if (isPopupShowing() && lastQuery.equals(query.toString())) return;
lastQuery = query.toString();
log("showPopup: called with filter "+query);
if (!isPopupShowing()) {
log("showPopup: showing");
presenter.registerDataSetObserver(new Observer()); // Calling new to avoid leaking... maybe...
popup.setView(presenter.getView());
presenter.showView();
popup.show();
if (callback != null) callback.onPopupVisibilityChanged(true);
}
log("showPopup: popup should be showing... "+isPopupShowing());
presenter.onQuery(query);
}
/**
* Dismisses the popup, if showing.
* There is rarely need to call this externally: it is already triggered by events on the anchor.
* To control when this is called, provide a good implementation of {@link AutocompletePolicy}.
*/
public void dismissPopup() {
if (isPopupShowing()) {
popup.dismiss();
}
}
/**
* Returns true if the popup is showing.
* @return whether the popup is currently showing
*/
public boolean isPopupShowing() {
return this.popup.isShowing();
}
/**
* Switch to control the autocomplete behavior. When disabled, no popup is shown.
* This is useful if you want to do runtime edits to the anchor text, without triggering
* the popup.
*
* @param enabled whether to enable autocompletion
*/
public void setEnabled(boolean enabled) {
disabled = !enabled;
}
/**
* Sets the gravity for the popup. Basically only {@link Gravity#START} and {@link Gravity#END}
* do work.
*
* @param gravity gravity for the popup
*/
public void setGravity(int gravity) {
popup.setGravity(gravity);
}
/**
* Controls the vertical offset of the popup from the EditText anchor.
*
* @param offset offset in pixels.
*/
public void setOffsetFromAnchor(int offset) { popup.setVerticalOffset(offset); }
/**
* Controls whether the popup should listen to clicks outside its boundaries.
*
* @param outsideTouchable true to listen to outside clicks
*/
public void setOutsideTouchable(boolean outsideTouchable) { popup.setOutsideTouchable(outsideTouchable); }
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (block || disabled) return;
openBefore = isPopupShowing();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (block || disabled) return;
if (openBefore && !isPopupShowing()) {
return; // Copied from somewhere.
}
if (!(s instanceof Spannable)) {
source.setText(new SpannableString(s));
return;
}
Spannable sp = (Spannable) s;
int cursor = source.getSelectionEnd();
log("onTextChanged: cursor end position is "+cursor);
if (cursor == -1) { // No cursor present.
dismissPopup(); return;
}
if (cursor != source.getSelectionStart()) {
// Not sure about this. We should have no problems dealing with multi selections,
// we just take the end...
// dismissPopup(); return;
}
boolean b = block;
block = true; // policy might add spans or other stuff.
if (isPopupShowing() && policy.shouldDismissPopup(sp, cursor)) {
log("onTextChanged: dismissing");
dismissPopup();
} else if (isPopupShowing() || policy.shouldShowPopup(sp, cursor)) {
// LOG.now("onTextChanged: updating with filter "+policy.getQuery(sp));
showPopup(policy.getQuery(sp));
}
block = b;
}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void onSpanAdded(Spannable text, Object what, int start, int end) {}
@Override
public void onSpanRemoved(Spannable text, Object what, int start, int end) {}
@Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
if (disabled || block) return;
if (what == Selection.SELECTION_END) {
// Selection end changed from ostart to nstart. Trigger a check.
log("onSpanChanged: selection end moved from "+ostart+" to "+nstart);
log("onSpanChanged: block is "+block);
boolean b = block;
block = true;
if (!isPopupShowing() && policy.shouldShowPopup(text, nstart)) {
showPopup(policy.getQuery(text));
}
block = b;
}
}
private class Observer extends DataSetObserver implements Runnable {
private Handler ui = new Handler(Looper.getMainLooper());
@Override
public void onChanged() {
// ??? Not sure this is needed...
ui.post(this);
}
@Override
public void run() {
if (isPopupShowing()) {
// Call show again to revisit width and height.
popup.show();
}
}
}
/**
* A very simple {@link AutocompletePolicy} implementation.
* Popup is shown when text length is bigger than 0, and hidden when text is empty.
* The query string is the whole text.
*/
public static class SimplePolicy implements AutocompletePolicy {
@Override
public boolean shouldShowPopup(@NonNull Spannable text, int cursorPos) {
return text.length() > 0;
}
@Override
public boolean shouldDismissPopup(@NonNull Spannable text, int cursorPos) {
return text.length() == 0;
}
@NonNull
@Override
public CharSequence getQuery(@NonNull Spannable text) {
return text;
}
@Override
public void onDismiss(@NonNull Spannable text) {}
}
}

View File

@@ -0,0 +1,29 @@
package com.otaliastudios.autocomplete;
import android.text.Editable;
import androidx.annotation.NonNull;
/**
* Optional callback to be passed to {@link Autocomplete.Builder}.
*/
public interface AutocompleteCallback<T> {
/**
* Called when an item inside your list is clicked.
* This works if your presenter has dispatched a click event.
* At this point you can edit the text, e.g. {@code editable.append(item.toString())}.
*
* @param editable editable text that you can work on
* @param item item that was clicked
* @return true if the action is valid and the popup can be dismissed
*/
boolean onPopupItemClicked(@NonNull Editable editable, @NonNull T item);
/**
* Called when popup visibility state changes.
*
* @param shown true if the popup was just shown, false if it was just hidden
*/
void onPopupVisibilityChanged(boolean shown);
}

View File

@@ -0,0 +1,64 @@
package com.otaliastudios.autocomplete;
import android.text.Spannable;
import androidx.annotation.NonNull;
/**
* This interface controls when to show or hide the popup window, and, in the first case,
* what text should be passed to the popup {@link AutocompletePresenter}.
*
* @see Autocomplete.SimplePolicy for the simplest possible implementation
*/
public interface AutocompletePolicy {
/**
* Called to understand whether the popup should be shown. Some naive examples:
* - Show when there's text: {@code return text.length() > 0}
* - Show when last char is @: {@code return text.getCharAt(text.length()-1) == '@'}
*
* @param text current text, along with its Spans
* @param cursorPos the position of the cursor
* @return true if popup should be shown
*/
boolean shouldShowPopup(@NonNull Spannable text, int cursorPos);
/**
* Called to understand whether a currently shown popup should be closed, maybe
* because text is invalid. A reasonable implementation is
* {@code return !shouldShowPopup(text, cursorPos)}.
*
* However this is defined so you can add or clear spans.
*
* @param text current text, along with its Spans
* @param cursorPos the position of the cursor
* @return true if popup should be hidden
*/
boolean shouldDismissPopup(@NonNull Spannable text, int cursorPos);
/**
* Called to understand which query should be passed to {@link AutocompletePresenter}
* for a showing popup. If this is called, {@link #shouldShowPopup(Spannable, int)} just returned
* true, or {@link #shouldDismissPopup(Spannable, int)} just returned false.
*
* This is useful to understand which part of the text should be passed to presenters.
* For example, user might have typed '@john' to select a username, but you just want to
* search for 'john'.
*
* For more complex cases, you can add inclusive Spans in {@link #shouldShowPopup(Spannable, int)},
* and get the span position here.
*
* @param text current text, along with its Spans
* @return the query for presenter
*/
@NonNull
CharSequence getQuery(@NonNull Spannable text);
/**
* Called when popup is dismissed. This can be used, for instance, to clear custom Spans
* from the text.
*
* @param text text at the moment of dismissing
*/
void onDismiss(@NonNull Spannable text);
}

View File

@@ -0,0 +1,521 @@
package com.otaliastudios.autocomplete;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.core.view.ViewCompat;
import androidx.core.widget.PopupWindowCompat;
/**
* A simplified version of andriod.widget.ListPopupWindow, which is the class used by
* AutocompleteTextView.
*
* Other than being simplified, this deals with Views rather than ListViews, so the content
* can be whatever. Lots of logic (clicks, selections etc.) has been removed because we manage that
* in {@link AutocompletePresenter}.
*
*/
class AutocompletePopup {
private Context mContext;
private ViewGroup mView;
private int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mMaxHeight = Integer.MAX_VALUE;
private int mMaxWidth = Integer.MAX_VALUE;
private int mUserMaxHeight = Integer.MAX_VALUE;
private int mUserMaxWidth = Integer.MAX_VALUE;
private int mHorizontalOffset = 0;
private int mVerticalOffset = 0;
private boolean mVerticalOffsetSet;
private int mGravity = Gravity.NO_GRAVITY;
private boolean mAlwaysVisible = false;
private boolean mOutsideTouchable = true;
private View mAnchorView;
private final Rect mTempRect = new Rect();
private boolean mModal;
private PopupWindow mPopup;
/**
* Create a new, empty popup window capable of displaying items from a ListAdapter.
* Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
*
* @param context Context used for contained views.
*/
AutocompletePopup(@NonNull Context context) {
super();
mContext = context;
mPopup = new PopupWindow(context);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
/**
* Set whether this window should be modal when shown.
*
* <p>If a popup window is modal, it will receive all touch and key input.
* If the user touches outside the popup window's content area the popup window
* will be dismissed.
* @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
*/
@SuppressWarnings("SameParameterValue")
void setModal(boolean modal) {
mModal = modal;
mPopup.setFocusable(modal);
}
/**
* Returns whether the popup window will be modal when shown.
* @return {@code true} if the popup window will be modal, {@code false} otherwise.
*/
@SuppressWarnings("unused")
boolean isModal() {
return mModal;
}
void setElevation(float elevationPx) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mPopup.setElevation(elevationPx);
}
/**
* Sets whether the drop-down should remain visible under certain conditions.
*
* The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
* of the size or content of the list. {@link #getBackground()} will fill any space
* that is not used by the list.
* @param dropDownAlwaysVisible Whether to keep the drop-down visible.
*
*/
@SuppressWarnings("unused")
void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
mAlwaysVisible = dropDownAlwaysVisible;
}
/**
* @return Whether the drop-down is visible under special conditions.
*/
@SuppressWarnings("unused")
boolean isDropDownAlwaysVisible() {
return mAlwaysVisible;
}
void setOutsideTouchable(boolean outsideTouchable) {
mOutsideTouchable = outsideTouchable;
}
@SuppressWarnings("WeakerAccess")
boolean isOutsideTouchable() {
return mOutsideTouchable && !mAlwaysVisible;
}
/**
* Sets the operating mode for the soft input area.
* @param mode The desired mode, see
* {@link android.view.WindowManager.LayoutParams#softInputMode}
* for the full list
* @see android.view.WindowManager.LayoutParams#softInputMode
* @see #getSoftInputMode()
*/
void setSoftInputMode(int mode) {
mPopup.setSoftInputMode(mode);
}
/**
* Returns the current value in {@link #setSoftInputMode(int)}.
* @see #setSoftInputMode(int)
* @see android.view.WindowManager.LayoutParams#softInputMode
*/
@SuppressWarnings({"WeakerAccess", "unused"})
int getSoftInputMode() {
return mPopup.getSoftInputMode();
}
/**
* @return The background drawable for the popup window.
*/
@SuppressWarnings({"WeakerAccess", "unused"})
@Nullable
Drawable getBackground() {
return mPopup.getBackground();
}
/**
* Sets a drawable to be the background for the popup window.
* @param d A drawable to set as the background.
*/
void setBackgroundDrawable(@Nullable Drawable d) {
mPopup.setBackgroundDrawable(d);
}
/**
* Set an animation style to use when the popup window is shown or dismissed.
* @param animationStyle Animation style to use.
*/
@SuppressWarnings("unused")
void setAnimationStyle(@StyleRes int animationStyle) {
mPopup.setAnimationStyle(animationStyle);
}
/**
* Returns the animation style that will be used when the popup window is
* shown or dismissed.
* @return Animation style that will be used.
*/
@SuppressWarnings("unused")
@StyleRes
int getAnimationStyle() {
return mPopup.getAnimationStyle();
}
/**
* Returns the view that will be used to anchor this popup.
* @return The popup's anchor view
*/
@SuppressWarnings("WeakerAccess")
View getAnchorView() {
return mAnchorView;
}
/**
* Sets the popup's anchor view. This popup will always be positioned relative to
* the anchor view when shown.
* @param anchor The view to use as an anchor.
*/
void setAnchorView(@NonNull View anchor) {
mAnchorView = anchor;
}
/**
* Set the horizontal offset of this popup from its anchor view in pixels.
* @param offset The horizontal offset of the popup from its anchor.
*/
@SuppressWarnings("unused")
void setHorizontalOffset(int offset) {
mHorizontalOffset = offset;
}
/**
* Set the vertical offset of this popup from its anchor view in pixels.
* @param offset The vertical offset of the popup from its anchor.
*/
void setVerticalOffset(int offset) {
mVerticalOffset = offset;
mVerticalOffsetSet = true;
}
/**
* Set the gravity of the dropdown list. This is commonly used to
* set gravity to START or END for alignment with the anchor.
* @param gravity Gravity value to use
*/
void setGravity(int gravity) {
mGravity = gravity;
}
/**
* @return The width of the popup window in pixels.
*/
@SuppressWarnings("unused")
int getWidth() {
return mWidth;
}
/**
* Sets the width of the popup window in pixels. Can also be MATCH_PARENT
* or WRAP_CONTENT.
* @param width Width of the popup window.
*/
void setWidth(int width) {
mWidth = width;
}
/**
* Sets the width of the popup window by the size of its content. The final width may be
* larger to accommodate styled window dressing.
* @param width Desired width of content in pixels.
*/
@SuppressWarnings("unused")
void setContentWidth(int width) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
width += mTempRect.left + mTempRect.right;
}
setWidth(width);
}
void setMaxWidth(int width) {
if (width > 0) {
mUserMaxWidth = width;
}
}
/**
* @return The height of the popup window in pixels.
*/
@SuppressWarnings("unused")
int getHeight() {
return mHeight;
}
/**
* Sets the height of the popup window in pixels. Can also be MATCH_PARENT.
* @param height Height of the popup window.
*/
void setHeight(int height) {
mHeight = height;
}
/**
* Sets the height of the popup window by the size of its content. The final height may be
* larger to accommodate styled window dressing.
* @param height Desired height of content in pixels.
*/
@SuppressWarnings("unused")
void setContentHeight(int height) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
height += mTempRect.top + mTempRect.bottom;
}
setHeight(height);
}
void setMaxHeight(int height) {
if (height > 0) {
mUserMaxHeight = height;
}
}
void setOnDismissListener(PopupWindow.OnDismissListener listener) {
mPopup.setOnDismissListener(listener);
}
/**
* Show the popup list. If the list is already showing, this method
* will recalculate the popup's size and position.
*/
void show() {
if (!ViewCompat.isAttachedToWindow(getAnchorView())) return;
int height = buildDropDown();
final boolean noInputMethod = isInputMethodNotNeeded();
int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
if (mPopup.isShowing()) {
// First pass for this special case, don't know why.
if (mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
int tempWidth = mWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT : 0;
if (noInputMethod) {
mPopup.setWidth(tempWidth);
mPopup.setHeight(0);
} else {
mPopup.setWidth(tempWidth);
mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
}
}
// The call to PopupWindow's update method below can accept -1
// for any value you do not want to update.
// Width.
int widthSpec;
if (mWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
widthSpec = -1;
} else if (mWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
widthSpec = getAnchorView().getWidth();
} else {
widthSpec = mWidth;
}
widthSpec = Math.min(widthSpec, mMaxWidth);
widthSpec = (widthSpec < 0) ? - 1 : widthSpec;
// Height.
int heightSpec;
if (mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
} else if (mHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
heightSpec = height;
} else {
heightSpec = mHeight;
}
heightSpec = Math.min(heightSpec, mMaxHeight);
heightSpec = (heightSpec < 0) ? - 1 : heightSpec;
// Update.
mPopup.setOutsideTouchable(isOutsideTouchable());
if (heightSpec == 0) {
dismiss();
} else {
mPopup.update(getAnchorView(), mHorizontalOffset, mVerticalOffset, widthSpec, heightSpec);
}
} else {
int widthSpec;
if (mWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else if (mWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
widthSpec = getAnchorView().getWidth();
} else {
widthSpec = mWidth;
}
widthSpec = Math.min(widthSpec, mMaxWidth);
int heightSpec;
if (mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else if (mHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
heightSpec = height;
} else {
heightSpec = mHeight;
}
heightSpec = Math.min(heightSpec, mMaxHeight);
// Set width and height.
mPopup.setWidth(widthSpec);
mPopup.setHeight(heightSpec);
mPopup.setClippingEnabled(true);
// use outside touchable to dismiss drop down when touching outside of it, so
// only set this if the dropdown is not always visible
mPopup.setOutsideTouchable(isOutsideTouchable());
PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mHorizontalOffset, mVerticalOffset, mGravity);
}
}
/**
* Dismiss the popup window.
*/
void dismiss() {
mPopup.dismiss();
mPopup.setContentView(null);
mView = null;
}
/**
* Control how the popup operates with an input method: one of
* INPUT_METHOD_FROM_FOCUSABLE, INPUT_METHOD_NEEDED,
* or INPUT_METHOD_NOT_NEEDED.
*
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to the {@link #show()}
* method.</p>
*
* @see #show()
*/
void setInputMethodMode(int mode) {
mPopup.setInputMethodMode(mode);
}
/**
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
*/
boolean isShowing() {
return mPopup.isShowing();
}
/**
* @return {@code true} if this popup is configured to assume the user does not need
* to interact with the IME while it is showing, {@code false} otherwise.
*/
@SuppressWarnings("WeakerAccess")
boolean isInputMethodNotNeeded() {
return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
}
void setView(ViewGroup view) {
mView = view;
mView.setFocusable(true);
mView.setFocusableInTouchMode(true);
ViewGroup dropDownView = mView;
mPopup.setContentView(dropDownView);
ViewGroup.LayoutParams params = mView.getLayoutParams();
if (params != null) {
if (params.height > 0) setHeight(params.height);
if (params.width > 0) setWidth(params.width);
}
}
/**
* <p>Builds the popup window's content and returns the height the popup
* should have. Returns -1 when the content already exists.</p>
*
* @return the content's wrap content height or -1 if content already exists
*/
private int buildDropDown() {
int otherHeights = 0;
// getMaxAvailableHeight() subtracts the padding, so we put it back
// to get the available height for the whole window.
final int paddingVert;
final int paddingHoriz;
final Drawable background = mPopup.getBackground();
if (background != null) {
background.getPadding(mTempRect);
paddingVert = mTempRect.top + mTempRect.bottom;
paddingHoriz = mTempRect.left + mTempRect.right;
// If we don't have an explicit vertical offset, determine one from
// the window background so that content will line up.
if (!mVerticalOffsetSet) {
mVerticalOffset = -mTempRect.top;
}
} else {
mTempRect.setEmpty();
paddingVert = 0;
paddingHoriz = 0;
}
// Redefine dimensions taking into account maxWidth and maxHeight.
final boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
final int maxContentHeight = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
mPopup.getMaxAvailableHeight(getAnchorView(), mVerticalOffset, ignoreBottomDecorations) :
mPopup.getMaxAvailableHeight(getAnchorView(), mVerticalOffset);
final int maxContentWidth = mContext.getResources().getDisplayMetrics().widthPixels - paddingHoriz;
mMaxHeight = Math.min(maxContentHeight + paddingVert, mUserMaxHeight);
mMaxWidth = Math.min(maxContentWidth + paddingHoriz, mUserMaxWidth);
// if (mHeight > 0) mHeight = Math.min(mHeight, maxContentHeight);
// if (mWidth > 0) mWidth = Math.min(mWidth, maxContentWidth);
if (mAlwaysVisible || mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
return mMaxHeight;
}
final int childWidthSpec;
switch (mWidth) {
case ViewGroup.LayoutParams.WRAP_CONTENT:
childWidthSpec = View.MeasureSpec.makeMeasureSpec(maxContentWidth, View.MeasureSpec.AT_MOST); break;
case ViewGroup.LayoutParams.MATCH_PARENT:
childWidthSpec = View.MeasureSpec.makeMeasureSpec(maxContentWidth, View.MeasureSpec.EXACTLY); break;
default:
//noinspection Range
childWidthSpec = View.MeasureSpec.makeMeasureSpec(mWidth, View.MeasureSpec.EXACTLY); break;
}
// Add padding only if the list has items in it, that way we don't show
// the popup if it is not needed. For this reason, we measure as wrap_content.
mView.measure(childWidthSpec, View.MeasureSpec.makeMeasureSpec(maxContentHeight, View.MeasureSpec.AT_MOST));
final int viewHeight = mView.getMeasuredHeight();
if (viewHeight > 0) {
otherHeights += paddingVert + mView.getPaddingTop() + mView.getPaddingBottom();
}
return Math.min(viewHeight + otherHeights, mMaxHeight);
}
}

View File

@@ -0,0 +1,129 @@
package com.otaliastudios.autocomplete;
import android.content.Context;
import android.database.DataSetObserver;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Base class for presenting items inside a popup. This is abstract and must be implemented.
*
* Most important methods are {@link #getView()} and {@link #onQuery(CharSequence)}.
*/
public abstract class AutocompletePresenter<T> {
private Context context;
private boolean isShowing;
@SuppressWarnings("WeakerAccess")
public AutocompletePresenter(@NonNull Context context) {
this.context = context;
}
/**
* At this point the presenter is passed the {@link ClickProvider}.
* The contract is that {@link ClickProvider#click(Object)} must be called when a list item
* is clicked. This ensure that the autocomplete callback will receive the event.
*
* @param provider a click provider for this presenter.
*/
protected void registerClickProvider(ClickProvider<T> provider) {
}
/**
* Useful if you wish to change width/height based on content height.
* The contract is to call {@link DataSetObserver#onChanged()} when your view has
* changes.
*
* This is called after {@link #getView()}.
*
* @param observer the observer.
*/
protected void registerDataSetObserver(@NonNull DataSetObserver observer) {}
/**
* Called each time the popup is shown. You are meant to inflate the view here.
* You can get a LayoutInflater using {@link #getContext()}.
*
* @return a ViewGroup for the popup
*/
@NonNull
protected abstract ViewGroup getView();
/**
* Provide the {@link PopupDimensions} for this popup. Called just once.
* You can use fixed dimensions or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and
* {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
*
* @return a PopupDimensions object
*/
// Called at first to understand which dimensions to use for the popup.
@NonNull
protected PopupDimensions getPopupDimensions() {
return new PopupDimensions();
}
/**
* Perform firther initialization here. Called after {@link #getView()},
* each time the popup is shown.
*/
protected abstract void onViewShown();
/**
* Called to update the view to filter results with the query.
* It is called any time the popup is shown, and any time the text changes and query is updated.
*
* @param query query from the edit text, to filter our results
*/
protected abstract void onQuery(@Nullable CharSequence query);
/**
* Called when the popup is hidden, to release resources.
*/
protected abstract void onViewHidden();
/**
* @return this presenter context
*/
@NonNull
protected final Context getContext() {
return context;
}
/**
* @return whether we are showing currently
*/
@SuppressWarnings("unused")
protected final boolean isShowing() {
return isShowing;
}
final void showView() {
isShowing = true;
onViewShown();
}
final void hideView() {
isShowing = false;
onViewHidden();
}
public interface ClickProvider<T> {
void click(@NonNull T item);
}
/**
* Provides width, height, maxWidth and maxHeight for the popup.
* @see #getPopupDimensions()
*/
@SuppressWarnings("WeakerAccess")
public static class PopupDimensions {
public int width = ViewGroup.LayoutParams.WRAP_CONTENT;
public int height = ViewGroup.LayoutParams.WRAP_CONTENT;
public int maxWidth = Integer.MAX_VALUE;
public int maxHeight = Integer.MAX_VALUE;
}
}

View File

@@ -0,0 +1,184 @@
package com.otaliastudios.autocomplete;
import android.text.Spannable;
import android.text.Spanned;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* A special {@link AutocompletePolicy} for cases when you want to trigger the popup when a
* certain character is shown.
*
* For instance, this might be the case for hashtags ('#') or usernames ('@') or whatever you wish.
* Passing this to {@link Autocomplete.Builder} ensures the following behavior (assuming '@'):
* - text "@john" : presenter will be passed the query "john"
* - text "You should see this @j" : presenter will be passed the query "j"
* - text "You should see this @john @m" : presenter will be passed the query "m"
*/
public class CharPolicy implements AutocompletePolicy {
private final static String TAG = CharPolicy.class.getSimpleName();
private final static boolean DEBUG = false;
private static void log(@NonNull String log) {
if (DEBUG) Log.e(TAG, log);
}
private final char CH;
private final int[] INT = new int[2];
private boolean needSpaceBefore = true;
/**
* Constructs a char policy for the given character.
*
* @param trigger the triggering character.
*/
public CharPolicy(char trigger) {
CH = trigger;
}
/**
* Constructs a char policy for the given character.
* You can choose whether a whitespace is needed before 'trigger'.
*
* @param trigger the triggering character.
* @param needSpaceBefore whether we need a space before trigger
*/
@SuppressWarnings("unused")
public CharPolicy(char trigger, boolean needSpaceBefore) {
CH = trigger;
this.needSpaceBefore = needSpaceBefore;
}
/**
* Can be overriden to understand which characters are valid. The default implementation
* returns true for any character except whitespaces.
*
* @param ch the character
* @return whether it's valid part of a query
*/
@SuppressWarnings("WeakerAccess")
protected boolean isValidChar(char ch) {
return !Character.isWhitespace(ch);
}
@Nullable
private int[] checkText(@NonNull Spannable text, int cursorPos) {
final int spanEnd = cursorPos;
char last = 'x';
cursorPos -= 1; // If the cursor is at the end, we will have cursorPos = length. Go back by 1.
while (cursorPos >= 0 && last != CH) {
char ch = text.charAt(cursorPos);
log("checkText: char is "+ch);
if (isValidChar(ch)) {
// We are going back
log("checkText: char is valid");
cursorPos -= 1;
last = ch;
} else {
// We got a whitespace before getting a CH. This is invalid.
log("checkText: char is not valid, returning NULL");
return null;
}
}
cursorPos += 1; // + 1 because we end BEHIND the valid selection
// Start checking.
if (cursorPos == 0 && last != CH) {
// We got to the start of the string, and no CH was encountered. Nothing to do.
log("checkText: got to start but no CH, returning NULL");
return null;
}
// Additional checks for cursorPos - 1
if (cursorPos > 0 && needSpaceBefore) {
char ch = text.charAt(cursorPos-1);
if (!Character.isWhitespace(ch)) {
log("checkText: char before is not whitespace, returning NULL");
return null;
}
}
// All seems OK.
final int spanStart = cursorPos + 1; // + 1 because we want to exclude CH from the query
INT[0] = spanStart;
INT[1] = spanEnd;
log("checkText: found! cursorPos="+cursorPos);
log("checkText: found! spanStart="+spanStart);
log("checkText: found! spanEnd="+spanEnd);
return INT;
}
@Override
public boolean shouldShowPopup(@NonNull Spannable text, int cursorPos) {
// Returning true if, right before cursorPos, we have a word starting with @.
log("shouldShowPopup: text is "+text);
log("shouldShowPopup: cursorPos is "+cursorPos);
int[] show = checkText(text, cursorPos);
if (show != null) {
text.setSpan(new QuerySpan(), show[0], show[1], Spanned.SPAN_INCLUSIVE_INCLUSIVE);
return true;
}
log("shouldShowPopup: returning false");
return false;
}
@Override
public boolean shouldDismissPopup(@NonNull Spannable text, int cursorPos) {
log("shouldDismissPopup: text is "+text);
log("shouldDismissPopup: cursorPos is "+cursorPos);
boolean dismiss = checkText(text, cursorPos) == null;
log("shouldDismissPopup: returning "+dismiss);
return dismiss;
}
@NonNull
@Override
public CharSequence getQuery(@NonNull Spannable text) {
QuerySpan[] span = text.getSpans(0, text.length(), QuerySpan.class);
if (span == null || span.length == 0) {
// Should never happen.
log("getQuery: there's no span!");
return "";
}
log("getQuery: found spans: "+span.length);
QuerySpan sp = span[0];
log("getQuery: span start is "+text.getSpanStart(sp));
log("getQuery: span end is "+text.getSpanEnd(sp));
CharSequence seq = text.subSequence(text.getSpanStart(sp), text.getSpanEnd(sp));
log("getQuery: returning "+seq);
return seq;
}
@Override
public void onDismiss(@NonNull Spannable text) {
// Remove any span added by shouldShow. Should be useless, but anyway.
QuerySpan[] span = text.getSpans(0, text.length(), QuerySpan.class);
for (QuerySpan s : span) {
text.removeSpan(s);
}
}
private static class QuerySpan {}
/**
* Returns the current query out of the given Spannable.
* @param text the anchor text
* @return an int[] with query start and query end positions
*/
@Nullable
public static int[] getQueryRange(@NonNull Spannable text) {
QuerySpan[] span = text.getSpans(0, text.length(), QuerySpan.class);
if (span == null || span.length == 0) return null;
if (span.length > 1) {
// Won't happen
log("getQueryRange: ERR: MORE THAN ONE QuerySpan.");
}
QuerySpan sp = span[0];
return new int[]{text.getSpanStart(sp), text.getSpanEnd(sp)};
}
}

View File

@@ -0,0 +1,152 @@
package com.otaliastudios.autocomplete;
import android.content.Context;
import android.database.DataSetObserver;
import android.view.ViewGroup;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
* Simple {@link AutocompletePresenter} implementation that hosts a {@link RecyclerView}.
* Supports {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} natively.
* The only contract is to
*
* - provide a {@link RecyclerView.Adapter} in {@link #instantiateAdapter()}
* - call {@link #dispatchClick(Object)} when an object is clicked
* - update your data during {@link #onQuery(CharSequence)}
*
* @param <T> your model object (the object displayed by the list)
*/
public abstract class RecyclerViewPresenter<T> extends AutocompletePresenter<T> {
private RecyclerView recycler;
private ClickProvider<T> clicks;
private Observer observer;
public RecyclerViewPresenter(@NonNull Context context) {
super(context);
}
@Override
protected final void registerClickProvider(@NonNull ClickProvider<T> provider) {
this.clicks = provider;
}
@Override
protected final void registerDataSetObserver(@NonNull DataSetObserver observer) {
this.observer = new Observer(observer);
}
@NonNull
@Override
protected ViewGroup getView() {
recycler = new RecyclerView(getContext());
RecyclerView.Adapter adapter = instantiateAdapter();
recycler.setAdapter(adapter);
recycler.setLayoutManager(instantiateLayoutManager());
if (observer != null) {
adapter.registerAdapterDataObserver(observer);
observer = null;
}
return recycler;
}
@Override
protected void onViewShown() {}
@CallSuper
@Override
protected void onViewHidden() {
recycler = null;
observer = null;
}
@SuppressWarnings("unused")
@Nullable
protected final RecyclerView getRecyclerView() {
return recycler;
}
/**
* Dispatch click event to {@link AutocompleteCallback}.
* Should be called when items are clicked.
*
* @param item the clicked item.
*/
protected final void dispatchClick(@NonNull T item) {
if (clicks != null) clicks.click(item);
}
/**
* Request that the popup should recompute its dimensions based on a recent change in
* the view being displayed.
*
* This is already managed internally for {@link RecyclerView} events.
* Only use it for changes in other views that you have added to the popup,
* and only if one of the dimensions for the popup is WRAP_CONTENT .
*/
@SuppressWarnings("unused")
protected final void dispatchLayoutChange() {
if (observer != null) observer.onChanged();
}
/**
* Provide an adapter for the recycler.
* This should be a fresh instance every time this is called.
*
* @return a new adapter.
*/
@NonNull
protected abstract RecyclerView.Adapter instantiateAdapter();
/**
* Provides a layout manager for the recycler.
* This should be a fresh instance every time this is called.
* Defaults to a vertical LinearLayoutManager, which is guaranteed to work well.
*
* @return a new layout manager.
*/
@SuppressWarnings("WeakerAccess")
@NonNull
protected RecyclerView.LayoutManager instantiateLayoutManager() {
return new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
}
private final static class Observer extends RecyclerView.AdapterDataObserver {
private DataSetObserver root;
Observer(@NonNull DataSetObserver root) {
this.root = root;
}
@Override
public void onChanged() {
root.onChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
root.onChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
root.onChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
root.onChanged();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
root.onChanged();
}
}
}

View File

@@ -0,0 +1,26 @@
apply plugin: 'com.android.library'
android {
namespace "me.dm7.barcodescanner.core"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
}
dependencies {
implementation 'androidx.annotation:annotation-jvm:1.6.0'
}
afterEvaluate {
tasks.findAll { it.name.startsWith("lint") }.each {
it.enabled = false
}
}

View File

@@ -0,0 +1,339 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.ColorInt;
public abstract class BarcodeScannerView extends FrameLayout implements Camera.PreviewCallback {
private CameraWrapper mCameraWrapper;
private CameraPreview mPreview;
private IViewFinder mViewFinderView;
private Rect mFramingRectInPreview;
private CameraHandlerThread mCameraHandlerThread;
private Boolean mFlashState;
private boolean mAutofocusState = true;
private boolean mShouldScaleToFill = true;
private boolean mIsLaserEnabled = true;
@ColorInt private int mLaserColor = getResources().getColor(R.color.viewfinder_laser);
@ColorInt private int mBorderColor = getResources().getColor(R.color.viewfinder_border);
private int mMaskColor = getResources().getColor(R.color.viewfinder_mask);
private int mBorderWidth = getResources().getInteger(R.integer.viewfinder_border_width);
private int mBorderLength = getResources().getInteger(R.integer.viewfinder_border_length);
private boolean mRoundedCorner = false;
private int mCornerRadius = 0;
private boolean mSquaredFinder = false;
private float mBorderAlpha = 1.0f;
private int mViewFinderOffset = 0;
private float mAspectTolerance = 0.1f;
public BarcodeScannerView(Context context) {
super(context);
init();
}
public BarcodeScannerView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
TypedArray a = context.getTheme().obtainStyledAttributes(
attributeSet,
R.styleable.BarcodeScannerView,
0, 0);
try {
setShouldScaleToFill(a.getBoolean(R.styleable.BarcodeScannerView_shouldScaleToFill, true));
mIsLaserEnabled = a.getBoolean(R.styleable.BarcodeScannerView_laserEnabled, mIsLaserEnabled);
mLaserColor = a.getColor(R.styleable.BarcodeScannerView_laserColor, mLaserColor);
mBorderColor = a.getColor(R.styleable.BarcodeScannerView_borderColor, mBorderColor);
mMaskColor = a.getColor(R.styleable.BarcodeScannerView_maskColor, mMaskColor);
mBorderWidth = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_borderWidth, mBorderWidth);
mBorderLength = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_borderLength, mBorderLength);
mRoundedCorner = a.getBoolean(R.styleable.BarcodeScannerView_roundedCorner, mRoundedCorner);
mCornerRadius = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_cornerRadius, mCornerRadius);
mSquaredFinder = a.getBoolean(R.styleable.BarcodeScannerView_squaredFinder, mSquaredFinder);
mBorderAlpha = a.getFloat(R.styleable.BarcodeScannerView_borderAlpha, mBorderAlpha);
mViewFinderOffset = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_finderOffset, mViewFinderOffset);
} finally {
a.recycle();
}
init();
}
private void init() {
mViewFinderView = createViewFinderView(getContext());
}
public final void setupLayout(CameraWrapper cameraWrapper) {
removeAllViews();
mPreview = new CameraPreview(getContext(), cameraWrapper, this);
mPreview.setAspectTolerance(mAspectTolerance);
mPreview.setShouldScaleToFill(mShouldScaleToFill);
if (!mShouldScaleToFill) {
RelativeLayout relativeLayout = new RelativeLayout(getContext());
relativeLayout.setGravity(Gravity.CENTER);
relativeLayout.setBackgroundColor(Color.BLACK);
relativeLayout.addView(mPreview);
addView(relativeLayout);
} else {
addView(mPreview);
}
if (mViewFinderView instanceof View) {
addView((View) mViewFinderView);
} else {
throw new IllegalArgumentException("IViewFinder object returned by " +
"'createViewFinderView()' should be instance of android.view.View");
}
}
/**
* <p>Method that creates view that represents visual appearance of a barcode scanner</p>
* <p>Override it to provide your own view for visual appearance of a barcode scanner</p>
*
* @param context {@link Context}
* @return {@link android.view.View} that implements {@link ViewFinderView}
*/
protected IViewFinder createViewFinderView(Context context) {
ViewFinderView viewFinderView = new ViewFinderView(context);
viewFinderView.setBorderColor(mBorderColor);
viewFinderView.setLaserColor(mLaserColor);
viewFinderView.setLaserEnabled(mIsLaserEnabled);
viewFinderView.setBorderStrokeWidth(mBorderWidth);
viewFinderView.setBorderLineLength(mBorderLength);
viewFinderView.setMaskColor(mMaskColor);
viewFinderView.setBorderCornerRounded(mRoundedCorner);
viewFinderView.setBorderCornerRadius(mCornerRadius);
viewFinderView.setSquareViewFinder(mSquaredFinder);
viewFinderView.setViewFinderOffset(mViewFinderOffset);
return viewFinderView;
}
public void setLaserColor(int laserColor) {
mLaserColor = laserColor;
mViewFinderView.setLaserColor(mLaserColor);
mViewFinderView.setupViewFinder();
}
public void setMaskColor(int maskColor) {
mMaskColor = maskColor;
mViewFinderView.setMaskColor(mMaskColor);
mViewFinderView.setupViewFinder();
}
public void setBorderColor(int borderColor) {
mBorderColor = borderColor;
mViewFinderView.setBorderColor(mBorderColor);
mViewFinderView.setupViewFinder();
}
public void setBorderStrokeWidth(int borderStrokeWidth) {
mBorderWidth = borderStrokeWidth;
mViewFinderView.setBorderStrokeWidth(mBorderWidth);
mViewFinderView.setupViewFinder();
}
public void setBorderLineLength(int borderLineLength) {
mBorderLength = borderLineLength;
mViewFinderView.setBorderLineLength(mBorderLength);
mViewFinderView.setupViewFinder();
}
public void setLaserEnabled(boolean isLaserEnabled) {
mIsLaserEnabled = isLaserEnabled;
mViewFinderView.setLaserEnabled(mIsLaserEnabled);
mViewFinderView.setupViewFinder();
}
public void setIsBorderCornerRounded(boolean isBorderCornerRounded) {
mRoundedCorner = isBorderCornerRounded;
mViewFinderView.setBorderCornerRounded(mRoundedCorner);
mViewFinderView.setupViewFinder();
}
public void setBorderCornerRadius(int borderCornerRadius) {
mCornerRadius = borderCornerRadius;
mViewFinderView.setBorderCornerRadius(mCornerRadius);
mViewFinderView.setupViewFinder();
}
public void setSquareViewFinder(boolean isSquareViewFinder) {
mSquaredFinder = isSquareViewFinder;
mViewFinderView.setSquareViewFinder(mSquaredFinder);
mViewFinderView.setupViewFinder();
}
public void setBorderAlpha(float borderAlpha) {
mBorderAlpha = borderAlpha;
mViewFinderView.setBorderAlpha(mBorderAlpha);
mViewFinderView.setupViewFinder();
}
public void startCamera(int cameraId) {
if(mCameraHandlerThread == null) {
mCameraHandlerThread = new CameraHandlerThread(this);
}
mCameraHandlerThread.startCamera(cameraId);
}
public void setupCameraPreview(CameraWrapper cameraWrapper) {
mCameraWrapper = cameraWrapper;
if(mCameraWrapper != null) {
setupLayout(mCameraWrapper);
mViewFinderView.setupViewFinder();
if(mFlashState != null) {
setFlash(mFlashState);
}
setAutoFocus(mAutofocusState);
}
}
public void startCamera() {
startCamera(CameraUtils.getDefaultCameraId());
}
public void stopCamera() {
if(mCameraWrapper != null) {
mPreview.stopCameraPreview();
mPreview.setCamera(null, null);
mCameraWrapper.mCamera.release();
mCameraWrapper = null;
}
if(mCameraHandlerThread != null) {
mCameraHandlerThread.quit();
mCameraHandlerThread = null;
}
}
public void stopCameraPreview() {
if(mPreview != null) {
mPreview.stopCameraPreview();
}
}
protected void resumeCameraPreview() {
if(mPreview != null) {
mPreview.showCameraPreview();
}
}
public synchronized Rect getFramingRectInPreview(int previewWidth, int previewHeight) {
if (mFramingRectInPreview == null) {
Rect framingRect = mViewFinderView.getFramingRect();
int viewFinderViewWidth = mViewFinderView.getWidth();
int viewFinderViewHeight = mViewFinderView.getHeight();
if (framingRect == null || viewFinderViewWidth == 0 || viewFinderViewHeight == 0) {
return null;
}
Rect rect = new Rect(framingRect);
if(previewWidth < viewFinderViewWidth) {
rect.left = rect.left * previewWidth / viewFinderViewWidth;
rect.right = rect.right * previewWidth / viewFinderViewWidth;
}
if(previewHeight < viewFinderViewHeight) {
rect.top = rect.top * previewHeight / viewFinderViewHeight;
rect.bottom = rect.bottom * previewHeight / viewFinderViewHeight;
}
mFramingRectInPreview = rect;
}
return mFramingRectInPreview;
}
public void setFlash(boolean flag) {
mFlashState = flag;
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
if(flag) {
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
return;
}
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
} else {
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_OFF)) {
return;
}
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
}
mCameraWrapper.mCamera.setParameters(parameters);
}
}
public boolean getFlash() {
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
return true;
} else {
return false;
}
}
return false;
}
public void toggleFlash() {
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
} else {
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
}
mCameraWrapper.mCamera.setParameters(parameters);
}
}
public void setAutoFocus(boolean state) {
mAutofocusState = state;
if(mPreview != null) {
mPreview.setAutoFocus(state);
}
}
public void setShouldScaleToFill(boolean shouldScaleToFill) {
mShouldScaleToFill = shouldScaleToFill;
}
public void setAspectTolerance(float aspectTolerance) {
mAspectTolerance = aspectTolerance;
}
public byte[] getRotatedData(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
int width = size.width;
int height = size.height;
int rotationCount = getRotationCount();
if(rotationCount == 1 || rotationCount == 3) {
for (int i = 0; i < rotationCount; i++) {
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
data = rotatedData;
int tmp = width;
width = height;
height = tmp;
}
}
return data;
}
public int getRotationCount() {
int displayOrientation = mPreview.getDisplayOrientation();
return displayOrientation / 90;
}
}

View File

@@ -0,0 +1,37 @@
package me.dm7.barcodescanner.core;
import android.hardware.Camera;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
// This code is mostly based on the top answer here: http://stackoverflow.com/questions/18149964/best-use-of-handlerthread-over-other-similar-classes
public class CameraHandlerThread extends HandlerThread {
private static final String LOG_TAG = "CameraHandlerThread";
private BarcodeScannerView mScannerView;
public CameraHandlerThread(BarcodeScannerView scannerView) {
super("CameraHandlerThread");
mScannerView = scannerView;
start();
}
public void startCamera(final int cameraId) {
Handler localHandler = new Handler(getLooper());
localHandler.post(new Runnable() {
@Override
public void run() {
final Camera camera = CameraUtils.getCameraInstance(cameraId);
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
mScannerView.setupCameraPreview(CameraWrapper.getWrapper(camera, cameraId));
}
});
}
});
}
}

View File

@@ -0,0 +1,312 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import java.util.List;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private CameraWrapper mCameraWrapper;
private Handler mAutoFocusHandler;
private boolean mPreviewing = true;
private boolean mAutoFocus = true;
private boolean mSurfaceCreated = false;
private boolean mShouldScaleToFill = true;
private Camera.PreviewCallback mPreviewCallback;
private float mAspectTolerance = 0.1f;
public CameraPreview(Context context, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
super(context);
init(cameraWrapper, previewCallback);
}
public CameraPreview(Context context, AttributeSet attrs, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
super(context, attrs);
init(cameraWrapper, previewCallback);
}
public void init(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
setCamera(cameraWrapper, previewCallback);
mAutoFocusHandler = new Handler();
getHolder().addCallback(this);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
mCameraWrapper = cameraWrapper;
mPreviewCallback = previewCallback;
}
public void setShouldScaleToFill(boolean scaleToFill) {
mShouldScaleToFill = scaleToFill;
}
public void setAspectTolerance(float aspectTolerance) {
mAspectTolerance = aspectTolerance;
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mSurfaceCreated = true;
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
if(surfaceHolder.getSurface() == null) {
return;
}
stopCameraPreview();
showCameraPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mSurfaceCreated = false;
stopCameraPreview();
}
public void showCameraPreview() {
if(mCameraWrapper != null) {
try {
getHolder().addCallback(this);
mPreviewing = true;
setupCameraParameters();
mCameraWrapper.mCamera.setPreviewDisplay(getHolder());
mCameraWrapper.mCamera.setDisplayOrientation(getDisplayOrientation());
mCameraWrapper.mCamera.setOneShotPreviewCallback(mPreviewCallback);
mCameraWrapper.mCamera.startPreview();
if(mAutoFocus) {
if (mSurfaceCreated) { // check if surface created before using autofocus
safeAutoFocus();
} else {
scheduleAutoFocus(); // wait 1 sec and then do check again
}
}
} catch (Exception e) {
Log.e(TAG, e.toString(), e);
}
}
}
public void safeAutoFocus() {
try {
mCameraWrapper.mCamera.autoFocus(autoFocusCB);
} catch (RuntimeException re) {
// Horrible hack to deal with autofocus errors on Sony devices
// See https://github.com/dm77/barcodescanner/issues/7 for example
scheduleAutoFocus(); // wait 1 sec and then do check again
}
}
public void stopCameraPreview() {
if(mCameraWrapper != null) {
try {
mPreviewing = false;
getHolder().removeCallback(this);
mCameraWrapper.mCamera.cancelAutoFocus();
mCameraWrapper.mCamera.setOneShotPreviewCallback(null);
mCameraWrapper.mCamera.stopPreview();
} catch(Exception e) {
Log.e(TAG, e.toString(), e);
}
}
}
public void setupCameraParameters() {
Camera.Size optimalSize = getOptimalPreviewSize();
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
mCameraWrapper.mCamera.setParameters(parameters);
adjustViewSize(optimalSize);
}
private void adjustViewSize(Camera.Size cameraSize) {
Point previewSize = convertSizeToLandscapeOrientation(new Point(getWidth(), getHeight()));
float cameraRatio = ((float) cameraSize.width) / cameraSize.height;
float screenRatio = ((float) previewSize.x) / previewSize.y;
if (screenRatio > cameraRatio) {
setViewSize((int) (previewSize.y * cameraRatio), previewSize.y);
} else {
setViewSize(previewSize.x, (int) (previewSize.x / cameraRatio));
}
}
@SuppressWarnings("SuspiciousNameCombination")
private Point convertSizeToLandscapeOrientation(Point size) {
if (getDisplayOrientation() % 180 == 0) {
return size;
} else {
return new Point(size.y, size.x);
}
}
@SuppressWarnings("SuspiciousNameCombination")
private void setViewSize(int width, int height) {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
int tmpWidth;
int tmpHeight;
if (getDisplayOrientation() % 180 == 0) {
tmpWidth = width;
tmpHeight = height;
} else {
tmpWidth = height;
tmpHeight = width;
}
if (mShouldScaleToFill) {
int parentWidth = ((View) getParent()).getWidth();
int parentHeight = ((View) getParent()).getHeight();
float ratioWidth = (float) parentWidth / (float) tmpWidth;
float ratioHeight = (float) parentHeight / (float) tmpHeight;
float compensation;
if (ratioWidth > ratioHeight) {
compensation = ratioWidth;
} else {
compensation = ratioHeight;
}
tmpWidth = Math.round(tmpWidth * compensation);
tmpHeight = Math.round(tmpHeight * compensation);
}
layoutParams.width = tmpWidth;
layoutParams.height = tmpHeight;
setLayoutParams(layoutParams);
}
public int getDisplayOrientation() {
if (mCameraWrapper == null) {
//If we don't have a camera set there is no orientation so return dummy value
return 0;
}
Camera.CameraInfo info = new Camera.CameraInfo();
if(mCameraWrapper.mCameraId == -1) {
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
} else {
Camera.getCameraInfo(mCameraWrapper.mCameraId, info);
}
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int rotation = display.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
private Camera.Size getOptimalPreviewSize() {
if(mCameraWrapper == null) {
return null;
}
List<Camera.Size> sizes = mCameraWrapper.mCamera.getParameters().getSupportedPreviewSizes();
int w = getWidth();
int h = getHeight();
if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
int portraitWidth = h;
h = w;
w = portraitWidth;
}
double targetRatio = (double) w / h;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > mAspectTolerance) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
public void setAutoFocus(boolean state) {
if(mCameraWrapper != null && mPreviewing) {
if(state == mAutoFocus) {
return;
}
mAutoFocus = state;
if(mAutoFocus) {
if (mSurfaceCreated) { // check if surface created before using autofocus
Log.v(TAG, "Starting autofocus");
safeAutoFocus();
} else {
scheduleAutoFocus(); // wait 1 sec and then do check again
}
} else {
Log.v(TAG, "Cancelling autofocus");
mCameraWrapper.mCamera.cancelAutoFocus();
}
}
}
private Runnable doAutoFocus = new Runnable() {
public void run() {
if(mCameraWrapper != null && mPreviewing && mAutoFocus && mSurfaceCreated) {
safeAutoFocus();
}
}
};
// Mimic continuous auto-focusing
Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
scheduleAutoFocus();
}
};
private void scheduleAutoFocus() {
mAutoFocusHandler.postDelayed(doAutoFocus, 1000);
}
}

View File

@@ -0,0 +1,63 @@
package me.dm7.barcodescanner.core;
import android.hardware.Camera;
import java.util.List;
public class CameraUtils {
/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance() {
return getCameraInstance(getDefaultCameraId());
}
/** Favor back-facing camera by default. If none exists, fallback to whatever camera is available **/
public static int getDefaultCameraId() {
int numberOfCameras = Camera.getNumberOfCameras();
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
int defaultCameraId = -1;
for (int i = 0; i < numberOfCameras; i++) {
defaultCameraId = i;
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
return i;
}
}
return defaultCameraId;
}
/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(int cameraId) {
Camera c = null;
try {
if(cameraId == -1) {
c = Camera.open(); // attempt to get a Camera instance
} else {
c = Camera.open(cameraId); // attempt to get a Camera instance
}
}
catch (Exception e) {
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
public static boolean isFlashSupported(Camera camera) {
/* Credits: Top answer at http://stackoverflow.com/a/19599365/868173 */
if (camera != null) {
Camera.Parameters parameters = camera.getParameters();
if (parameters.getFlashMode() == null) {
return false;
}
List<String> supportedFlashModes = parameters.getSupportedFlashModes();
if (supportedFlashModes == null || supportedFlashModes.isEmpty() || supportedFlashModes.size() == 1 && supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF)) {
return false;
}
} else {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,23 @@
package me.dm7.barcodescanner.core;
import android.hardware.Camera;
import androidx.annotation.NonNull;
public class CameraWrapper {
public final Camera mCamera;
public final int mCameraId;
private CameraWrapper(@NonNull Camera camera, int cameraId) {
this.mCamera = camera;
this.mCameraId = cameraId;
}
public static CameraWrapper getWrapper(Camera camera, int cameraId) {
if (camera == null) {
return null;
} else {
return new CameraWrapper(camera, cameraId);
}
}
}

View File

@@ -0,0 +1,41 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.view.Display;
import android.view.WindowManager;
public class DisplayUtils {
public static Point getScreenResolution(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point screenResolution = new Point();
if (android.os.Build.VERSION.SDK_INT >= 13) {
display.getSize(screenResolution);
} else {
screenResolution.set(display.getWidth(), display.getHeight());
}
return screenResolution;
}
public static int getScreenOrientation(Context context)
{
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int orientation = Configuration.ORIENTATION_UNDEFINED;
if(display.getWidth()==display.getHeight()){
orientation = Configuration.ORIENTATION_SQUARE;
} else{
if(display.getWidth() < display.getHeight()){
orientation = Configuration.ORIENTATION_PORTRAIT;
}else {
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
return orientation;
}
}

View File

@@ -0,0 +1,53 @@
package me.dm7.barcodescanner.core;
import android.graphics.Rect;
public interface IViewFinder {
void setLaserColor(int laserColor);
void setMaskColor(int maskColor);
void setBorderColor(int borderColor);
void setBorderStrokeWidth(int borderStrokeWidth);
void setBorderLineLength(int borderLineLength);
void setLaserEnabled(boolean isLaserEnabled);
void setBorderCornerRounded(boolean isBorderCornersRounded);
void setBorderAlpha(float alpha);
void setBorderCornerRadius(int borderCornersRadius);
void setViewFinderOffset(int offset);
void setSquareViewFinder(boolean isSquareViewFinder);
/**
* Method that executes when Camera preview is starting.
* It is recommended to update framing rect here and invalidate view after that. <br/>
* For example see: {@link ViewFinderView#setupViewFinder()}
*/
void setupViewFinder();
/**
* Provides {@link Rect} that identifies area where barcode scanner can detect visual codes
* <p>Note: This rect is a area representation in absolute pixel values. <br/>
* For example: <br/>
* If View's size is 1024x800 so framing rect might be 500x400</p>
*
* @return {@link Rect} that identifies barcode scanner area
*/
Rect getFramingRect();
/**
* Width of a {@link android.view.View} that implements this interface
* <p>Note: this is already implemented in {@link android.view.View},
* so you don't need to override method and provide your implementation</p>
*
* @return width of a view
*/
int getWidth();
/**
* Height of a {@link android.view.View} that implements this interface
* <p>Note: this is already implemented in {@link android.view.View},
* so you don't need to override method and provide your implementation</p>
*
* @return height of a view
*/
int getHeight();
}

View File

@@ -0,0 +1,259 @@
package me.dm7.barcodescanner.core;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
public class ViewFinderView extends View implements IViewFinder {
private static final String TAG = "ViewFinderView";
private Rect mFramingRect;
private static final float PORTRAIT_WIDTH_RATIO = 6f/8;
private static final float PORTRAIT_WIDTH_HEIGHT_RATIO = 0.75f;
private static final float LANDSCAPE_HEIGHT_RATIO = 5f/8;
private static final float LANDSCAPE_WIDTH_HEIGHT_RATIO = 1.4f;
private static final int MIN_DIMENSION_DIFF = 50;
private static final float DEFAULT_SQUARE_DIMENSION_RATIO = 5f / 8;
private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
private int scannerAlpha;
private static final int POINT_SIZE = 10;
private static final long ANIMATION_DELAY = 80l;
private final int mDefaultLaserColor = getResources().getColor(R.color.viewfinder_laser);
private final int mDefaultMaskColor = getResources().getColor(R.color.viewfinder_mask);
private final int mDefaultBorderColor = getResources().getColor(R.color.viewfinder_border);
private final int mDefaultBorderStrokeWidth = getResources().getInteger(R.integer.viewfinder_border_width);
private final int mDefaultBorderLineLength = getResources().getInteger(R.integer.viewfinder_border_length);
protected Paint mLaserPaint;
protected Paint mFinderMaskPaint;
protected Paint mBorderPaint;
protected int mBorderLineLength;
protected boolean mSquareViewFinder;
private boolean mIsLaserEnabled;
private float mBordersAlpha;
private int mViewFinderOffset = 0;
public ViewFinderView(Context context) {
super(context);
init();
}
public ViewFinderView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
init();
}
private void init() {
//set up laser paint
mLaserPaint = new Paint();
mLaserPaint.setColor(mDefaultLaserColor);
mLaserPaint.setStyle(Paint.Style.FILL);
//finder mask paint
mFinderMaskPaint = new Paint();
mFinderMaskPaint.setColor(mDefaultMaskColor);
//border paint
mBorderPaint = new Paint();
mBorderPaint.setColor(mDefaultBorderColor);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setStrokeWidth(mDefaultBorderStrokeWidth);
mBorderPaint.setAntiAlias(true);
mBorderLineLength = mDefaultBorderLineLength;
}
@Override
public void setLaserColor(int laserColor) {
mLaserPaint.setColor(laserColor);
}
@Override
public void setMaskColor(int maskColor) {
mFinderMaskPaint.setColor(maskColor);
}
@Override
public void setBorderColor(int borderColor) {
mBorderPaint.setColor(borderColor);
}
@Override
public void setBorderStrokeWidth(int borderStrokeWidth) {
mBorderPaint.setStrokeWidth(borderStrokeWidth);
}
@Override
public void setBorderLineLength(int borderLineLength) {
mBorderLineLength = borderLineLength;
}
@Override
public void setLaserEnabled(boolean isLaserEnabled) { mIsLaserEnabled = isLaserEnabled; }
@Override
public void setBorderCornerRounded(boolean isBorderCornersRounded) {
if (isBorderCornersRounded) {
mBorderPaint.setStrokeJoin(Paint.Join.ROUND);
} else {
mBorderPaint.setStrokeJoin(Paint.Join.BEVEL);
}
}
@Override
public void setBorderAlpha(float alpha) {
int colorAlpha = (int) (255 * alpha);
mBordersAlpha = alpha;
mBorderPaint.setAlpha(colorAlpha);
}
@Override
public void setBorderCornerRadius(int borderCornersRadius) {
mBorderPaint.setPathEffect(new CornerPathEffect(borderCornersRadius));
}
@Override
public void setViewFinderOffset(int offset) {
mViewFinderOffset = offset;
}
// TODO: Need a better way to configure this. Revisit when working on 2.0
@Override
public void setSquareViewFinder(boolean set) {
mSquareViewFinder = set;
}
public void setupViewFinder() {
updateFramingRect();
invalidate();
}
public Rect getFramingRect() {
return mFramingRect;
}
@Override
public void onDraw(Canvas canvas) {
if(getFramingRect() == null) {
return;
}
drawViewFinderMask(canvas);
drawViewFinderBorder(canvas);
if (mIsLaserEnabled) {
drawLaser(canvas);
}
}
public void drawViewFinderMask(Canvas canvas) {
int width = canvas.getWidth();
int height = canvas.getHeight();
Rect framingRect = getFramingRect();
canvas.drawRect(0, 0, width, framingRect.top, mFinderMaskPaint);
canvas.drawRect(0, framingRect.top, framingRect.left, framingRect.bottom + 1, mFinderMaskPaint);
canvas.drawRect(framingRect.right + 1, framingRect.top, width, framingRect.bottom + 1, mFinderMaskPaint);
canvas.drawRect(0, framingRect.bottom + 1, width, height, mFinderMaskPaint);
}
public void drawViewFinderBorder(Canvas canvas) {
Rect framingRect = getFramingRect();
// Top-left corner
Path path = new Path();
path.moveTo(framingRect.left, framingRect.top + mBorderLineLength);
path.lineTo(framingRect.left, framingRect.top);
path.lineTo(framingRect.left + mBorderLineLength, framingRect.top);
canvas.drawPath(path, mBorderPaint);
// Top-right corner
path.moveTo(framingRect.right, framingRect.top + mBorderLineLength);
path.lineTo(framingRect.right, framingRect.top);
path.lineTo(framingRect.right - mBorderLineLength, framingRect.top);
canvas.drawPath(path, mBorderPaint);
// Bottom-right corner
path.moveTo(framingRect.right, framingRect.bottom - mBorderLineLength);
path.lineTo(framingRect.right, framingRect.bottom);
path.lineTo(framingRect.right - mBorderLineLength, framingRect.bottom);
canvas.drawPath(path, mBorderPaint);
// Bottom-left corner
path.moveTo(framingRect.left, framingRect.bottom - mBorderLineLength);
path.lineTo(framingRect.left, framingRect.bottom);
path.lineTo(framingRect.left + mBorderLineLength, framingRect.bottom);
canvas.drawPath(path, mBorderPaint);
}
public void drawLaser(Canvas canvas) {
Rect framingRect = getFramingRect();
// Draw a red "laser scanner" line through the middle to show decoding is active
mLaserPaint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = framingRect.height() / 2 + framingRect.top;
canvas.drawRect(framingRect.left + 2, middle - 1, framingRect.right - 1, middle + 2, mLaserPaint);
postInvalidateDelayed(ANIMATION_DELAY,
framingRect.left - POINT_SIZE,
framingRect.top - POINT_SIZE,
framingRect.right + POINT_SIZE,
framingRect.bottom + POINT_SIZE);
}
@Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
updateFramingRect();
}
public synchronized void updateFramingRect() {
Point viewResolution = new Point(getWidth(), getHeight());
int width;
int height;
int orientation = DisplayUtils.getScreenOrientation(getContext());
if(mSquareViewFinder) {
if(orientation != Configuration.ORIENTATION_PORTRAIT) {
height = (int) (getHeight() * DEFAULT_SQUARE_DIMENSION_RATIO);
width = height;
} else {
width = (int) (getWidth() * DEFAULT_SQUARE_DIMENSION_RATIO);
height = width;
}
} else {
if(orientation != Configuration.ORIENTATION_PORTRAIT) {
height = (int) (getHeight() * LANDSCAPE_HEIGHT_RATIO);
width = (int) (LANDSCAPE_WIDTH_HEIGHT_RATIO * height);
} else {
width = (int) (getWidth() * PORTRAIT_WIDTH_RATIO);
height = (int) (PORTRAIT_WIDTH_HEIGHT_RATIO * width);
}
}
if(width > getWidth()) {
width = getWidth() - MIN_DIMENSION_DIFF;
}
if(height > getHeight()) {
height = getHeight() - MIN_DIMENSION_DIFF;
}
int leftOffset = (viewResolution.x - width) / 2;
int topOffset = (viewResolution.y - height) / 2;
mFramingRect = new Rect(leftOffset + mViewFinderOffset, topOffset + mViewFinderOffset, leftOffset + width - mViewFinderOffset, topOffset + height - mViewFinderOffset);
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">4</integer>
<integer name="viewfinder_border_length">60</integer>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">5</integer>
<integer name="viewfinder_border_length">80</integer>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">6</integer>
<integer name="viewfinder_border_length">100</integer>
</resources>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BarcodeScannerView">
<attr name="shouldScaleToFill" format="boolean" />
<attr name="laserEnabled" format="boolean" />
<attr name="laserColor" format="color" />
<attr name="borderColor" format="color" />
<attr name="maskColor" format="color" />
<attr name="borderWidth" format="dimension" />
<attr name="borderLength" format="dimension" />
<attr name="roundedCorner" format="boolean" />
<attr name="cornerRadius" format="dimension" />
<attr name="squaredFinder" format="boolean" />
<attr name="borderAlpha" format="float" />
<attr name="finderOffset" format="dimension" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="viewfinder_mask">#60000000</color>
<color name="viewfinder_laser">#ffcc0000</color>
<color name="viewfinder_border">#ffafed44</color>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="viewfinder_border_width">4</integer>
<integer name="viewfinder_border_length">60</integer>
</resources>

View File

@@ -0,0 +1,29 @@
apply plugin: 'com.android.library'
android {
namespace "me.dm7.barcodescanner.zxing"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
}
dependencies {
api project(":library:external:barcodescanner:core")
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
api 'com.google.zxing:core:3.3.3'
}
afterEvaluate {
tasks.findAll { it.name.startsWith("lint") }.each {
it.enabled = false
}
}

View File

@@ -0,0 +1,198 @@
package me.dm7.barcodescanner.zxing;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import me.dm7.barcodescanner.core.BarcodeScannerView;
import me.dm7.barcodescanner.core.DisplayUtils;
public class ZXingScannerView extends BarcodeScannerView {
private static final String TAG = "ZXingScannerView";
public interface ResultHandler {
void handleResult(Result rawResult);
}
private MultiFormatReader mMultiFormatReader;
public static final List<BarcodeFormat> ALL_FORMATS = new ArrayList<>();
private List<BarcodeFormat> mFormats;
private ResultHandler mResultHandler;
static {
ALL_FORMATS.add(BarcodeFormat.AZTEC);
ALL_FORMATS.add(BarcodeFormat.CODABAR);
ALL_FORMATS.add(BarcodeFormat.CODE_39);
ALL_FORMATS.add(BarcodeFormat.CODE_93);
ALL_FORMATS.add(BarcodeFormat.CODE_128);
ALL_FORMATS.add(BarcodeFormat.DATA_MATRIX);
ALL_FORMATS.add(BarcodeFormat.EAN_8);
ALL_FORMATS.add(BarcodeFormat.EAN_13);
ALL_FORMATS.add(BarcodeFormat.ITF);
ALL_FORMATS.add(BarcodeFormat.MAXICODE);
ALL_FORMATS.add(BarcodeFormat.PDF_417);
ALL_FORMATS.add(BarcodeFormat.QR_CODE);
ALL_FORMATS.add(BarcodeFormat.RSS_14);
ALL_FORMATS.add(BarcodeFormat.RSS_EXPANDED);
ALL_FORMATS.add(BarcodeFormat.UPC_A);
ALL_FORMATS.add(BarcodeFormat.UPC_E);
ALL_FORMATS.add(BarcodeFormat.UPC_EAN_EXTENSION);
}
public ZXingScannerView(Context context) {
super(context);
initMultiFormatReader();
}
public ZXingScannerView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
initMultiFormatReader();
}
public void setFormats(List<BarcodeFormat> formats) {
mFormats = formats;
initMultiFormatReader();
}
public void setResultHandler(ResultHandler resultHandler) {
mResultHandler = resultHandler;
}
public Collection<BarcodeFormat> getFormats() {
if(mFormats == null) {
return ALL_FORMATS;
}
return mFormats;
}
private void initMultiFormatReader() {
Map<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.POSSIBLE_FORMATS, getFormats());
mMultiFormatReader = new MultiFormatReader();
mMultiFormatReader.setHints(hints);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if(mResultHandler == null) {
return;
}
try {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
int width = size.width;
int height = size.height;
if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
int rotationCount = getRotationCount();
if (rotationCount == 1 || rotationCount == 3) {
int tmp = width;
width = height;
height = tmp;
}
data = getRotatedData(data, camera);
}
Result rawResult = null;
PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = mMultiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} catch (NullPointerException npe) {
// This is terrible
} catch (ArrayIndexOutOfBoundsException aoe) {
} finally {
mMultiFormatReader.reset();
}
if (rawResult == null) {
LuminanceSource invertedSource = source.invert();
bitmap = new BinaryBitmap(new HybridBinarizer(invertedSource));
try {
rawResult = mMultiFormatReader.decodeWithState(bitmap);
} catch (NotFoundException e) {
// continue
} finally {
mMultiFormatReader.reset();
}
}
}
final Result finalRawResult = rawResult;
if (finalRawResult != null) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// Stopping the preview can take a little long.
// So we want to set result handler to null to discard subsequent calls to
// onPreviewFrame.
ResultHandler tmpResultHandler = mResultHandler;
mResultHandler = null;
stopCameraPreview();
if (tmpResultHandler != null) {
tmpResultHandler.handleResult(finalRawResult);
}
}
});
} else {
camera.setOneShotPreviewCallback(this);
}
} catch(RuntimeException e) {
// TODO: Terrible hack. It is possible that this method is invoked after camera is released.
Log.e(TAG, e.toString(), e);
}
}
public void resumeCameraPreview(ResultHandler resultHandler) {
mResultHandler = resultHandler;
super.resumeCameraPreview();
}
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRectInPreview(width, height);
if (rect == null) {
return null;
}
// Go ahead and assume it's YUV rather than die.
PlanarYUVLuminanceSource source = null;
try {
source = new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height(), false);
} catch(Exception e) {
}
return source;
}
}

View File

@@ -58,9 +58,7 @@ dependencies {
implementation libs.airbnb.mavericks
// Span utils
implementation('me.gujun.android:span:1.7') {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation project(":library:external:span")
implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid

View File

@@ -0,0 +1,23 @@
apply plugin: 'kotlin'
apply plugin: 'java'
sourceCompatibility = versions.sourceCompat
targetCompatibility = versions.sourceCompat
dependencies {
implementation 'com.squareup:javapoet:1.13.0'
}
task javadocJar(type: Jar, dependsOn: 'javadoc') {
from javadoc.destinationDir
classifier = 'javadoc'
}
task sourcesJar(type: Jar, dependsOn: 'classes') {
from sourceSets.main.allSource
classifier = 'sources'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

View File

@@ -0,0 +1,24 @@
package dk.ilios.realmfieldnames
import java.util.TreeMap
/**
* Class responsible for keeping track of the metadata for each Realm model class.
*/
class ClassData(val packageName: String?, val simpleClassName: String, val libraryClass: Boolean = false) {
val fields = TreeMap<String, String?>() // <fieldName, linkedType or null>
fun addField(field: String, linkedType: String?) {
fields.put(field, linkedType)
}
val qualifiedClassName: String
get() {
if (packageName != null && !packageName.isEmpty()) {
return packageName + "." + simpleClassName
} else {
return simpleClassName
}
}
}

View File

@@ -0,0 +1,79 @@
package dk.ilios.realmfieldnames
import java.util.Locale
/**
* Class for encapsulating the rules for converting between the field name in the Realm model class
* and the matching name in the "&lt;class&gt;Fields" class.
*/
class FieldNameFormatter {
@JvmOverloads
fun format(fieldName: String?, locale: Locale = Locale.US): String {
if (fieldName == null || fieldName == "") {
return ""
}
// Normalize word separator chars
val normalizedFieldName: String = fieldName.replace('-', '_')
// Iterate field name using the following rules
// lowerCase m followed by upperCase anything is considered hungarian notation
// lowercase char followed by uppercase char is considered camel case
// Two uppercase chars following each other is considered non-standard camelcase
// _ and - are treated as word separators
val result = StringBuilder(normalizedFieldName.length)
if (normalizedFieldName.codePointCount(0, normalizedFieldName.length) == 1) {
result.append(normalizedFieldName)
} else {
var previousCodepoint: Int?
var currentCodepoint: Int? = null
val length = normalizedFieldName.length
var offset = 0
while (offset < length) {
previousCodepoint = currentCodepoint
currentCodepoint = normalizedFieldName.codePointAt(offset)
if (previousCodepoint != null) {
if (Character.isUpperCase(currentCodepoint) &&
!Character.isUpperCase(previousCodepoint) &&
previousCodepoint === 'm'.code as Int? &&
result.length == 1
) {
// Hungarian notation starting with: mX
result.delete(0, 1)
result.appendCodePoint(currentCodepoint)
} else if (Character.isUpperCase(currentCodepoint) && Character.isUpperCase(previousCodepoint)) {
// InvalidCamelCase: XXYx (should have been xxYx)
if (offset + Character.charCount(currentCodepoint) < normalizedFieldName.length) {
val nextCodePoint = normalizedFieldName.codePointAt(offset + Character.charCount(currentCodepoint))
if (Character.isLowerCase(nextCodePoint)) {
result.append("_")
}
}
result.appendCodePoint(currentCodepoint)
} else if (currentCodepoint === '-'.code as Int? || currentCodepoint === '_'.code as Int?) {
// Word-separator: x-x or x_x
result.append("_")
} else if (Character.isUpperCase(currentCodepoint) && !Character.isUpperCase(previousCodepoint) && Character.isLetterOrDigit(
previousCodepoint
)) {
// camelCase: xX
result.append("_")
result.appendCodePoint(currentCodepoint)
} else {
// Unknown type
result.appendCodePoint(currentCodepoint)
}
} else {
// Only triggered for first code point
result.appendCodePoint(currentCodepoint)
}
offset += Character.charCount(currentCodepoint)
}
}
return result.toString().uppercase(locale)
}
}

View File

@@ -0,0 +1,77 @@
package dk.ilios.realmfieldnames
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
import java.io.IOException
import javax.annotation.processing.Filer
import javax.lang.model.element.Modifier
/**
* Class responsible for creating the final output files.
*/
class FileGenerator(private val filer: Filer) {
private val formatter: FieldNameFormatter
init {
this.formatter = FieldNameFormatter()
}
/**
* Generates all the "&lt;class&gt;Fields" fields with field name references.
* @param fileData Files to create.
* *
* @return `true` if the files where generated, `false` if not.
*/
fun generate(fileData: Set<ClassData>): Boolean {
return fileData
.filter { !it.libraryClass }
.all { generateFile(it, fileData) }
}
private fun generateFile(classData: ClassData, classPool: Set<ClassData>): Boolean {
val fileBuilder = TypeSpec.classBuilder(classData.simpleClassName + "Fields")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addJavadoc("This class enumerate all queryable fields in {@link \$L.\$L}\n",
classData.packageName, classData.simpleClassName)
// Add a static field reference to each queryable field in the Realm model class
classData.fields.forEach { fieldName, value ->
if (value != null) {
// Add linked field names (only up to depth 1)
for (data in classPool) {
if (data.qualifiedClassName == value) {
val linkedTypeSpec = TypeSpec.classBuilder(formatter.format(fieldName))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
val linkedClassFields = data.fields
addField(linkedTypeSpec, "$", fieldName)
for (linkedFieldName in linkedClassFields.keys) {
addField(linkedTypeSpec, linkedFieldName, fieldName + "." + linkedFieldName)
}
fileBuilder.addType(linkedTypeSpec.build())
}
}
} else {
// Add normal field name
addField(fileBuilder, fieldName, fieldName)
}
}
val javaFile = JavaFile.builder(classData.packageName, fileBuilder.build()).build()
try {
javaFile.writeTo(filer)
return true
} catch (e: IOException) {
// e.printStackTrace()
return false
}
}
private fun addField(fileBuilder: TypeSpec.Builder, fieldName: String, fieldNameValue: String) {
val field = FieldSpec.builder(String::class.java, formatter.format(fieldName))
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("\$S", fieldNameValue)
.build()
fileBuilder.addField(field)
}
}

View File

@@ -0,0 +1,197 @@
package dk.ilios.realmfieldnames
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Messager
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.annotation.processing.SupportedAnnotationTypes
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.Modifier
import javax.lang.model.element.PackageElement
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.tools.Diagnostic
/**
* The Realm Field Names Generator is a processor that looks at all available Realm model classes
* and create an companion class with easy, type-safe access to all field names.
*/
@SupportedAnnotationTypes("io.realm.annotations.RealmClass")
class RealmFieldNamesProcessor : AbstractProcessor() {
private val classes = HashSet<ClassData>()
private lateinit var typeUtils: Types
private lateinit var messager: Messager
private lateinit var elementUtils: Elements
private var ignoreAnnotation: TypeMirror? = null
private var realmClassAnnotation: TypeElement? = null
private var realmModelInterface: TypeMirror? = null
private var realmListClass: DeclaredType? = null
private var realmResultsClass: DeclaredType? = null
private var fileGenerator: FileGenerator? = null
private var done = false
@Synchronized
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
typeUtils = processingEnv.typeUtils!!
messager = processingEnv.messager!!
elementUtils = processingEnv.elementUtils!!
// If the Realm class isn't found something is wrong the project setup.
// Most likely Realm isn't on the class path, so just disable the
// annotation processor
val isRealmAvailable = elementUtils.getTypeElement("io.realm.Realm") != null
if (!isRealmAvailable) {
done = true
} else {
ignoreAnnotation = elementUtils.getTypeElement("io.realm.annotations.Ignore")?.asType()
realmClassAnnotation = elementUtils.getTypeElement("io.realm.annotations.RealmClass")
realmModelInterface = elementUtils.getTypeElement("io.realm.RealmModel")?.asType()
realmListClass = typeUtils.getDeclaredType(
elementUtils.getTypeElement("io.realm.RealmList"),
typeUtils.getWildcardType(null, null)
)
realmResultsClass = typeUtils.getDeclaredType(
elementUtils.getTypeElement("io.realm.RealmResults"),
typeUtils.getWildcardType(null, null)
)
fileGenerator = FileGenerator(processingEnv.filer)
}
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
if (done) {
return CONSUME_ANNOTATIONS
}
// Create all proxy classes
roundEnv.getElementsAnnotatedWith(realmClassAnnotation).forEach { classElement ->
if (typeUtils.isAssignable(classElement.asType(), realmModelInterface)) {
val classData = processClass(classElement as TypeElement)
classes.add(classData)
}
}
// If a model class references a library class, the library class will not be part of this
// annotation processor round. For all those references we need to pull field information
// from the classpath instead.
val libraryClasses = HashMap<String, ClassData>()
classes.forEach {
it.fields.forEach { _, value ->
// Analyze the library class file the first time it is encountered.
if (value != null) {
if (classes.all { it.qualifiedClassName != value } && !libraryClasses.containsKey(value)) {
libraryClasses.put(value, processLibraryClass(value))
}
}
}
}
classes.addAll(libraryClasses.values)
done = fileGenerator!!.generate(classes)
return CONSUME_ANNOTATIONS
}
private fun processClass(classElement: TypeElement): ClassData {
val packageName = getPackageName(classElement)
val className = classElement.simpleName.toString()
val data = ClassData(packageName, className)
// Find all appropriate fields
classElement.enclosedElements.forEach {
val elementKind = it.kind
if (elementKind == ElementKind.FIELD) {
val variableElement = it as VariableElement
val modifiers = variableElement.modifiers
if (modifiers.contains(Modifier.STATIC)) {
return@forEach // completely ignore any static fields
}
// Don't add any fields marked with @Ignore
val ignoreField = variableElement.annotationMirrors
.map { it.annotationType.toString() }
.contains("io.realm.annotations.Ignore")
if (!ignoreField) {
data.addField(it.getSimpleName().toString(), getLinkedFieldType(it))
}
}
}
return data
}
private fun processLibraryClass(qualifiedClassName: String): ClassData {
val libraryClass = Class.forName(qualifiedClassName) // Library classes should be on the classpath
val packageName = libraryClass.`package`.name
val className = libraryClass.simpleName
val data = ClassData(packageName, className, libraryClass = true)
libraryClass.declaredFields.forEach { field ->
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
return@forEach // completely ignore any static fields
}
// Add field if it is not being ignored.
if (field.annotations.all { it.toString() != "io.realm.annotations.Ignore" }) {
data.addField(field.name, field.type.name)
}
}
return data
}
/**
* Returns the qualified name of the linked Realm class field or `null` if it is not a linked
* class.
*/
private fun getLinkedFieldType(field: Element): String? {
if (typeUtils.isAssignable(field.asType(), realmModelInterface)) {
// Object link
val typeElement = elementUtils.getTypeElement(field.asType().toString())
return typeElement.qualifiedName.toString()
} else if (typeUtils.isAssignable(field.asType(), realmListClass) || typeUtils.isAssignable(field.asType(), realmResultsClass)) {
// List link or LinkingObjects
val fieldType = field.asType()
val typeArguments = (fieldType as DeclaredType).typeArguments
if (typeArguments.size == 0) {
return null
}
return typeArguments[0].toString()
} else {
return null
}
}
private fun getPackageName(classElement: TypeElement): String? {
val enclosingElement = classElement.enclosingElement
if (enclosingElement.kind != ElementKind.PACKAGE) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Could not determine the package name. Enclosing element was: " + enclosingElement.kind
)
return null
}
val packageElement = enclosingElement as PackageElement
return packageElement.qualifiedName.toString()
}
companion object {
private const val CONSUME_ANNOTATIONS = false
}
}

View File

@@ -0,0 +1 @@
dk.ilios.realmfieldnames.RealmFieldNamesProcessor,aggregating

View File

@@ -0,0 +1 @@
dk.ilios.realmfieldnames.RealmFieldNamesProcessor

20
library/external/span/build.gradle vendored Normal file
View File

@@ -0,0 +1,20 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "me.gujun.android.span"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
dependencies {
implementation 'com.android.support:support-annotations:28.0.0'
}

View File

@@ -0,0 +1,316 @@
package me.gujun.android.span
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.text.Layout
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextUtils
import android.text.style.AbsoluteSizeSpan
import android.text.style.AlignmentSpan
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.text.style.QuoteSpan
import android.text.style.StyleSpan
import android.text.style.SubscriptSpan
import android.text.style.SuperscriptSpan
import android.text.style.TypefaceSpan
import android.text.style.URLSpan
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.Dimension
import me.gujun.android.span.style.CustomTypefaceSpan
import me.gujun.android.span.style.LineSpacingSpan
import me.gujun.android.span.style.SimpleClickableSpan
import me.gujun.android.span.style.TextDecorationLineSpan
import me.gujun.android.span.style.VerticalPaddingSpan
class Span(val parent: Span? = null) : SpannableStringBuilder() {
companion object {
val EMPTY_STYLE = Span()
var globalStyle: Span = EMPTY_STYLE
}
var text: CharSequence = ""
@ColorInt var textColor: Int? = parent?.textColor
@ColorInt var backgroundColor: Int? = parent?.backgroundColor
@Dimension(unit = Dimension.PX) var textSize: Int? = parent?.textSize
var fontFamily: String? = parent?.fontFamily
var typeface: Typeface? = parent?.typeface
var textStyle: String? = parent?.textStyle
var alignment: String? = parent?.alignment
var textDecorationLine: String? = parent?.textDecorationLine
@Dimension(unit = Dimension.PX) var lineSpacing: Int? = null
@Dimension(unit = Dimension.PX) var paddingTop: Int? = null
@Dimension(unit = Dimension.PX) var paddingBottom: Int? = null
@Dimension(unit = Dimension.PX) var verticalPadding: Int? = null
var onClick: (() -> Unit)? = null
var spans: ArrayList<Any> = ArrayList()
var style: Span = EMPTY_STYLE
private fun buildCharacterStyle(builder: ArrayList<Any>) {
if (textColor != null) {
builder.add(ForegroundColorSpan(textColor!!))
}
if (backgroundColor != null) {
builder.add(BackgroundColorSpan(backgroundColor!!))
}
if (textSize != null) {
builder.add(AbsoluteSizeSpan(textSize!!))
}
if (!TextUtils.isEmpty(fontFamily)) {
builder.add(TypefaceSpan(fontFamily))
}
if (typeface != null) {
builder.add(CustomTypefaceSpan(typeface!!))
}
if (!TextUtils.isEmpty(textStyle)) {
builder.add(StyleSpan(when (textStyle) {
"normal" -> Typeface.NORMAL
"bold" -> Typeface.BOLD
"italic" -> Typeface.ITALIC
"bold_italic" -> Typeface.BOLD_ITALIC
else -> throw RuntimeException("Unknown text style")
}))
}
if (!TextUtils.isEmpty(textDecorationLine)) {
builder.add(TextDecorationLineSpan(textDecorationLine!!))
}
if (onClick != null) {
builder.add(object : SimpleClickableSpan() {
override fun onClick(widget: View) {
onClick?.invoke()
}
})
}
}
private fun buildParagraphStyle(builder: ArrayList<Any>) {
if (!TextUtils.isEmpty(alignment)) {
builder.add(AlignmentSpan.Standard(when (alignment) {
"normal" -> Layout.Alignment.ALIGN_NORMAL
"opposite" -> Layout.Alignment.ALIGN_OPPOSITE
"center" -> Layout.Alignment.ALIGN_CENTER
else -> throw RuntimeException("Unknown text alignment")
}))
}
if (lineSpacing != null) {
builder.add(LineSpacingSpan(lineSpacing!!))
}
paddingTop = when {
paddingTop != null -> paddingTop
verticalPadding != null -> verticalPadding
else -> 0
}
paddingBottom = when {
paddingBottom != null -> paddingBottom
verticalPadding != null -> verticalPadding
else -> 0
}
if (paddingTop != 0 || paddingBottom != 0) {
builder.add(VerticalPaddingSpan(paddingTop!!, paddingBottom!!))
}
}
private fun prebuild() {
override(style)
}
fun build(): Span {
prebuild()
val builder = ArrayList<Any>()
if (!TextUtils.isEmpty(text)) {
var p = this.parent
while (p != null) {
if (!TextUtils.isEmpty(p.text)) {
throw RuntimeException("Can't nest \"$text\" in spans")
}
p = p.parent
}
append(text)
buildCharacterStyle(builder)
buildParagraphStyle(builder)
} else {
buildParagraphStyle(builder)
}
builder.addAll(spans)
builder.forEach {
setSpan(it, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return this
}
fun override(style: Span) {
if (textColor == null) {
textColor = style.textColor
}
if (backgroundColor == null) {
backgroundColor = style.backgroundColor
}
if (textSize == null) {
textSize = style.textSize
}
if (fontFamily == null) {
fontFamily = style.fontFamily
}
if (typeface == null) {
typeface = style.typeface
}
if (textStyle == null) {
textStyle = style.textStyle
}
if (alignment == null) {
alignment = style.alignment
}
if (textDecorationLine == null) {
textDecorationLine = style.textDecorationLine
}
if (lineSpacing == null) {
lineSpacing = style.lineSpacing
}
if (paddingTop == null) {
paddingTop = style.paddingTop
}
if (paddingBottom == null) {
paddingBottom = style.paddingBottom
}
if (verticalPadding == null) {
verticalPadding = style.verticalPadding
}
if (onClick == null) {
onClick = style.onClick
}
spans.addAll(style.spans)
}
operator fun CharSequence.unaryPlus(): CharSequence {
return append(Span(parent = this@Span).apply {
text = this@unaryPlus
build()
})
}
operator fun Span.plus(other: CharSequence): CharSequence {
return append(Span(parent = this).apply {
text = other
build()
})
}
}
fun span(init: Span.() -> Unit): Span = Span().apply {
override(Span.globalStyle)
init()
build()
}
fun span(text: CharSequence, init: Span.() -> Unit): Span = Span().apply {
override(Span.globalStyle)
this.text = text
init()
build()
}
fun style(init: Span.() -> Unit): Span = Span().apply {
init()
}
fun Span.span(init: Span.() -> Unit = {}): Span = apply {
append(Span(parent = this).apply {
init()
build()
})
}
fun Span.span(text: CharSequence, init: Span.() -> Unit = {}): Span = apply {
append(Span(parent = this).apply {
this.text = text
init()
build()
})
}
fun Span.link(url: String, text: CharSequence = "",
init: Span.() -> Unit = {}): Span = apply {
append(Span(parent = this).apply {
this.text = text
this.spans.add(URLSpan(url))
init()
build()
})
}
fun Span.quote(@ColorInt color: Int, text: CharSequence = "",
init: Span.() -> Unit = {}): Span = apply {
append(Span(parent = this).apply {
this.text = text
this.spans.add(QuoteSpan(color))
init()
build()
})
}
fun Span.superscript(text: CharSequence = "", init: Span.() -> Unit = {}): Span = apply {
append(Span(parent = this).apply {
this.text = text
this.spans.add(SuperscriptSpan())
init()
build()
})
}
fun Span.subscript(text: CharSequence = "", init: Span.() -> Unit = {}): Span = apply {
append(Span(parent = this).apply {
this.text = text
this.spans.add(SubscriptSpan())
init()
build()
})
}
fun Span.image(drawable: Drawable, alignment: String = "bottom",
init: Span.() -> Unit = {}): Span = apply {
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
append(Span(parent = this).apply {
this.text = " "
this.spans.add(ImageSpan(drawable, when (alignment) {
"bottom" -> ImageSpan.ALIGN_BOTTOM
"baseline" -> ImageSpan.ALIGN_BASELINE
else -> throw RuntimeException("Unknown image alignment")
}))
init()
build()
})
}
fun Span.addSpan(what: Any) = apply {
this.spans.add(what)
}

View File

@@ -0,0 +1,36 @@
package me.gujun.android.span.style
import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import android.text.style.MetricAffectingSpan
class CustomTypefaceSpan(private val tf: Typeface) : MetricAffectingSpan() {
override fun updateMeasureState(paint: TextPaint) {
apply(paint, tf)
}
override fun updateDrawState(ds: TextPaint) {
apply(ds, tf)
}
private fun apply(paint: Paint, tf: Typeface) {
val oldStyle: Int
val old = paint.typeface
oldStyle = old?.style ?: 0
val fake = oldStyle and tf.style.inv()
if (fake and Typeface.BOLD != 0) {
paint.isFakeBoldText = true
}
if (fake and Typeface.ITALIC != 0) {
paint.textSkewX = -0.25f
}
paint.typeface = tf
}
}

View File

@@ -0,0 +1,31 @@
package me.gujun.android.span.style
import android.graphics.Paint.FontMetricsInt
import android.text.Spanned
import android.text.style.LineHeightSpan
class LineSpacingSpan(private val add: Int) : LineHeightSpan {
override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int,
fm: FontMetricsInt) {
text as Spanned
/*val spanStart =*/ text.getSpanStart(this)
val spanEnd = text.getSpanEnd(this)
// Log.d("DEBUG", "Text: start=$start end=$end v=$v") // end may include the \n character
// Log.d("DEBUG", "${text.slice(start until end)}".replace("\n", "#"))
// Log.d("DEBUG", "LineSpacingSpan: spanStart=$spanStart spanEnd=$spanEnd spanstartv=$spanstartv")
// Log.d("DEBUG", "$fm")
// Log.d("DEBUG", "-----------------------")
if (spanstartv == v) {
fm.descent += add
} else if (text[start - 1] == '\n') {
fm.descent += add
}
if (end == spanEnd || end - 1 == spanEnd) {
fm.descent -= add
}
}
}

View File

@@ -0,0 +1,10 @@
package me.gujun.android.span.style
import android.text.TextPaint
import android.text.style.ClickableSpan
abstract class SimpleClickableSpan : ClickableSpan() {
override fun updateDrawState(ds: TextPaint) {
// no-op
}
}

View File

@@ -0,0 +1,29 @@
package me.gujun.android.span.style
import android.text.TextPaint
import android.text.style.CharacterStyle
class TextDecorationLineSpan(private val textDecorationLine: String) : CharacterStyle() {
override fun updateDrawState(tp: TextPaint) {
when (textDecorationLine) {
"none" -> {
tp.isUnderlineText = false
tp.isStrikeThruText = false
}
"underline" -> {
tp.isUnderlineText = true
tp.isStrikeThruText = false
}
"line-through" -> {
tp.isUnderlineText = false
tp.isStrikeThruText = true
}
"underline line-through" -> {
tp.isUnderlineText = true
tp.isStrikeThruText = true
}
else -> throw RuntimeException("Unknown text decoration line")
}
}
}

View File

@@ -0,0 +1,41 @@
package me.gujun.android.span.style
import android.graphics.Paint.FontMetricsInt
import android.text.Spanned
import android.text.style.LineHeightSpan
class VerticalPaddingSpan(private val paddingTop: Int,
private val paddingBottom: Int) : LineHeightSpan {
private var flag: Boolean = true
override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int,
fm: FontMetricsInt) {
text as Spanned
/*val spanStart =*/ text.getSpanStart(this)
val spanEnd = text.getSpanEnd(this)
// Log.d("DEBUG", "Text: start=$start end=$end v=$v") // end may include the \n character
// Log.d("DEBUG", "${text.slice(start until end)}".replace("\n", "#"))
// Log.d("DEBUG", "VerticalPadding: spanStart=$spanStart spanEnd=$spanEnd spanstartv=$spanstartv")
// Log.d("DEBUG", "$fm")
// Log.d("DEBUG", "-----------------------")
if (spanstartv == v) {
fm.top -= paddingTop
fm.ascent -= paddingTop
flag = true
} else if (flag && text[start - 1] != '\n') {
fm.top += paddingTop
fm.ascent += paddingTop
flag = false
} else {
flag = false
}
if (end == spanEnd || end - 1 == spanEnd) {
fm.descent += paddingBottom
fm.bottom += paddingBottom
}
}
}

View File

@@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
apply plugin: 'com.android.library'
android {
namespace "com.amulyakhare.textdrawable"
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
}
afterEvaluate {
tasks.findAll { it.name.startsWith("lint") }.each {
it.enabled = false
}
}

View File

@@ -0,0 +1,316 @@
package com.amulyakhare.textdrawable;
import android.graphics.*;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
/**
* @author amulya
* @datetime 14 Oct 2014, 3:53 PM
*/
public class TextDrawable extends ShapeDrawable {
private final Paint textPaint;
private final Paint borderPaint;
private static final float SHADE_FACTOR = 0.9f;
private final String text;
private final int color;
private final RectShape shape;
private final int height;
private final int width;
private final int fontSize;
private final float radius;
private final int borderThickness;
private TextDrawable(Builder builder) {
super(builder.shape);
// shape properties
shape = builder.shape;
height = builder.height;
width = builder.width;
radius = builder.radius;
// text and color
text = builder.toUpperCase ? builder.text.toUpperCase() : builder.text;
color = builder.color;
// text paint settings
fontSize = builder.fontSize;
textPaint = new Paint();
textPaint.setColor(builder.textColor);
textPaint.setAntiAlias(true);
textPaint.setFakeBoldText(builder.isBold);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTypeface(builder.font);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setStrokeWidth(builder.borderThickness);
// border paint settings
borderThickness = builder.borderThickness;
borderPaint = new Paint();
borderPaint.setColor(getDarkerShade(color));
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(borderThickness);
// drawable paint color
Paint paint = getPaint();
paint.setColor(color);
}
private int getDarkerShade(int color) {
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
(int)(SHADE_FACTOR * Color.green(color)),
(int)(SHADE_FACTOR * Color.blue(color)));
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
Rect r = getBounds();
// draw border
if (borderThickness > 0) {
drawBorder(canvas);
}
int count = canvas.save();
canvas.translate(r.left, r.top);
// draw text
int width = this.width < 0 ? r.width() : this.width;
int height = this.height < 0 ? r.height() : this.height;
int fontSize = this.fontSize < 0 ? (Math.min(width, height) / 2) : this.fontSize;
textPaint.setTextSize(fontSize);
canvas.drawText(text, width / 2, height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2), textPaint);
canvas.restoreToCount(count);
}
private void drawBorder(Canvas canvas) {
RectF rect = new RectF(getBounds());
rect.inset(borderThickness/2, borderThickness/2);
if (shape instanceof OvalShape) {
canvas.drawOval(rect, borderPaint);
}
else if (shape instanceof RoundRectShape) {
canvas.drawRoundRect(rect, radius, radius, borderPaint);
}
else {
canvas.drawRect(rect, borderPaint);
}
}
@Override
public void setAlpha(int alpha) {
textPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
textPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return width;
}
@Override
public int getIntrinsicHeight() {
return height;
}
public static IShapeBuilder builder() {
return new Builder();
}
public static class Builder implements IConfigBuilder, IShapeBuilder, IBuilder {
private String text;
private int color;
private int borderThickness;
private int width;
private int height;
private Typeface font;
private RectShape shape;
public int textColor;
private int fontSize;
private boolean isBold;
private boolean toUpperCase;
public float radius;
private Builder() {
text = "";
color = Color.GRAY;
textColor = Color.WHITE;
borderThickness = 0;
width = -1;
height = -1;
shape = new RectShape();
font = Typeface.create("sans-serif-light", Typeface.NORMAL);
fontSize = -1;
isBold = false;
toUpperCase = false;
}
public IConfigBuilder width(int width) {
this.width = width;
return this;
}
public IConfigBuilder height(int height) {
this.height = height;
return this;
}
public IConfigBuilder textColor(int color) {
this.textColor = color;
return this;
}
public IConfigBuilder withBorder(int thickness) {
this.borderThickness = thickness;
return this;
}
public IConfigBuilder useFont(Typeface font) {
this.font = font;
return this;
}
public IConfigBuilder fontSize(int size) {
this.fontSize = size;
return this;
}
public IConfigBuilder bold() {
this.isBold = true;
return this;
}
public IConfigBuilder toUpperCase() {
this.toUpperCase = true;
return this;
}
@Override
public IConfigBuilder beginConfig() {
return this;
}
@Override
public IShapeBuilder endConfig() {
return this;
}
@Override
public IBuilder rect() {
this.shape = new RectShape();
return this;
}
@Override
public IBuilder round() {
this.shape = new OvalShape();
return this;
}
@Override
public IBuilder roundRect(int radius) {
this.radius = radius;
float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};
this.shape = new RoundRectShape(radii, null, null);
return this;
}
@Override
public TextDrawable buildRect(String text, int color) {
rect();
return build(text, color);
}
@Override
public TextDrawable buildRoundRect(String text, int color, int radius) {
roundRect(radius);
return build(text, color);
}
@Override
public TextDrawable buildRound(String text, int color) {
round();
return build(text, color);
}
@Override
public TextDrawable build(String text, int color) {
this.color = color;
this.text = text;
return new TextDrawable(this);
}
}
public interface IConfigBuilder {
public IConfigBuilder width(int width);
public IConfigBuilder height(int height);
public IConfigBuilder textColor(int color);
public IConfigBuilder withBorder(int thickness);
public IConfigBuilder useFont(Typeface font);
public IConfigBuilder fontSize(int size);
public IConfigBuilder bold();
public IConfigBuilder toUpperCase();
public IShapeBuilder endConfig();
}
public static interface IBuilder {
public TextDrawable build(String text, int color);
}
public static interface IShapeBuilder {
public IConfigBuilder beginConfig();
public IBuilder rect();
public IBuilder round();
public IBuilder roundRect(int radius);
public TextDrawable buildRect(String text, int color);
public TextDrawable buildRoundRect(String text, int color, int radius);
public TextDrawable buildRound(String text, int color);
}
}

View File

@@ -256,4 +256,39 @@
</plurals>
<string name="notice_room_server_acl_set_ip_literals_not_allowed">• IP literallarına uyğunlaşan serverlər qadağan edildi.</string>
<string name="notice_room_server_acl_set_ip_literals_allowed">• IP literallarına uyğunlaşan serverlərə icazə verilir.</string>
<string name="pill_message_in_unknown_room">Otaqdakı mesaj</string>
<string name="set_link_link">Bağlantı</string>
<string name="set_link_create">Bağlantı yarat</string>
<string name="message_reply_to_poll_preview">Anket</string>
<string name="pill_message_unknown_room_or_space">Otaq / Məkan</string>
<string name="set_link_edit">Bağlantını redaktə et</string>
<string name="set_link_text">Mətn</string>
<string name="message_reply_to_sender_ended_poll">Anket başa çatıb.</string>
<string name="notice_display_name_changed_to">%1$s ekran adını %2$s olaraq dəyişdi</string>
<string name="rich_text_editor_inline_code">Daxili kod formatın işlət</string>
<string name="rich_text_editor_code_block">Kod blokun dəyiş</string>
<string name="notice_room_canonical_alias_set_by_you">Bu otaq üçün əsas ünvanı %1$s olaraq təyin etdiniz.</string>
<string name="pill_message_in_room">Mesaj %s</string>
<string name="notice_room_aliases_added_and_removed_by_you">Bu otaq üçün ünvan kimi %2$s-nı sildiniz və %1$s əlavə etdiniz.</string>
<string name="settings_access_token">Giriş Nişanəniz</string>
<string name="notice_room_canonical_alias_unset">%1$s bu otaq üçün əsas ünvanı sildi.</string>
<string name="message_reply_to_ended_poll_preview">Anket bitdi</string>
<string name="notice_room_canonical_alias_set">%1$s bu otaq üçün əsas ünvanı %2$s olaraq təyin etdi.</string>
<string name="pill_message_from_unknown_user">Mesaj</string>
<string name="pill_message_from_user">%s-dan² mesaj</string>
<string name="settings_access_token_summary">Giriş nişanəniz hesabınıza tam giriş imkanı verir.Bunu heç kimlə paylaşmayın.</string>
<plurals name="notice_room_canonical_alias_alternative_added_by_you">
<item quantity="one">Bu otaq üçün alternativ %1$s ünvanın əlavə etdiniz.</item>
<item quantity="other">Bu otaq üçün alternativ %1$s ünvanın əlavə etdiniz.</item>
</plurals>
<string name="notice_room_canonical_alias_alternative_changed">%1$s bu otaq üçün alternativ ünvanları dəyişdi.</string>
<plurals name="notice_room_canonical_alias_alternative_added">
<item quantity="one">%1$s bu otaq üçün %2$s alternativ ünvanın əlavə etdi.</item>
<item quantity="other">%1$s bu otaq üçün %2$s alternativ ünvanın əlavə etdi.</item>
</plurals>
<string name="notice_room_canonical_alias_alternative_changed_by_you">Bu otaq üçün alternativ ünvanları dəyişdiniz.</string>
<plurals name="notice_room_canonical_alias_alternative_removed">
<item quantity="one">%1$s bu otaq üçün %2$s alternativ ünvanın sildi.</item>
<item quantity="other">%1$s bu otaq üçün %2$s alternativ ünvanın sildi.</item>
</plurals>
</resources>

View File

@@ -224,7 +224,7 @@
<string name="notice_room_server_acl_set_title_by_you">Du hast die Server-ACL für diesen Raum gesetzt.</string>
<string name="notice_room_server_acl_set_title">%s hat die Server-Zugriffssteuerungsliste (ACL) für diesen Raum gesetzt.</string>
<string name="title_activity_settings">Einstellungen</string>
<string name="call_notification_answer">Akzeptiere</string>
<string name="call_notification_answer">Akzeptieren</string>
<string name="call_notification_reject">Ablehnen</string>
<string name="call_notification_hangup">Anruf beenden</string>
<string name="ok">Ok</string>
@@ -263,7 +263,7 @@
<string name="matrix_only_filter">Nur Matrix-Kontakte</string>
<string name="no_result_placeholder">Keine Ergebnisse</string>
<string name="rooms_header">Räume</string>
<string name="send_bug_report_include_logs">Sende Protokolle</string>
<string name="send_bug_report_include_logs">Protokolle senden</string>
<string name="send_bug_report_include_crash_logs">Absturzberichte übermitteln</string>
<string name="send_bug_report_include_screenshot">Bildschirmfoto übermitteln</string>
<string name="send_bug_report">Problem melden</string>
@@ -692,7 +692,7 @@
<string name="settings_noisy_notifications_preferences">Laute Benachrichtigungen einstellen</string>
<string name="settings_call_notifications_preferences">Anrufbenachrichtigung einstellen</string>
<string name="settings_silent_notifications_preferences">Stumme Benachrichtigungen einstellen</string>
<string name="settings_system_preferences_summary">Wähle LED-Farbe, Vibration, Ton …</string>
<string name="settings_system_preferences_summary">LED-Farbe, Vibration, Ton auswählen </string>
<string name="notification_silent">Stumm</string>
<string name="passphrase_empty_error_message">Bitte eine Passphrase eingeben</string>
<string name="passphrase_passphrase_too_weak">Passphrase ist zu schwach</string>
@@ -712,7 +712,7 @@
<string name="keys_backup_unlock_button">Historie entschlüsseln</string>
<string name="keys_backup_settings_restore_backup_button">Von Sicherung wiederherstellen</string>
<string name="keys_backup_settings_delete_backup_button">Lösche Sicherung</string>
<string name="keys_backup_settings_deleting_backup">Lösche Sicherung </string>
<string name="keys_backup_settings_deleting_backup">Sicherung löschen …</string>
<string name="keys_backup_settings_delete_confirm_title">Lösche Sicherung</string>
<string name="settings_notification_by_event">Präferenz der Benachrichtigungen nach Ereignis</string>
<string name="settings_troubleshoot_test_fcm_failed_too_many_registration">[%1$s]
@@ -728,7 +728,7 @@
\nSichere deine Schlüssel, um sie nicht zu verlieren.</string>
<string name="keys_backup_setup_step3_generating_key_status">Wiederherstellungsschlüssel aus Passphrase generieren. Dies kann mehrere Sekunden brauchen.</string>
<string name="keys_backup_setup_skip_msg">Du verlierst möglicherweise den Zugang zu deinen Nachrichten, wenn du dich abmeldest oder das Gerät verlierst.</string>
<string name="keys_backup_restore_is_getting_backup_version">Rufe Sicherungsversion ab </string>
<string name="keys_backup_restore_is_getting_backup_version">Sicherungsversion abrufen …</string>
<string name="keys_backup_restore_with_passphrase">Nutze deine Wiederherstellungs-Passphrase, um deinen verschlüsselten Nachrichtenverlauf lesen zu können</string>
<string name="keys_backup_restore_use_recovery_key">nutze deinen Wiederherstellungsschlüssel</string>
<string name="keys_backup_restore_with_passphrase_helper_with_link">Wenn du deine Wiederherstellungspassphrase nicht weist, kannst du %s.</string>
@@ -793,15 +793,15 @@
<string name="keys_backup_banner_in_progress">Sichere deine Schlüssel. Dies könnte einige Minuten dauern </string>
<string name="keys_backup_info_keys_all_backup_up">Alle Schlüssel sind gesichert</string>
<plurals name="keys_backup_info_keys_backing_up">
<item quantity="one">Sichere einen Schlüssel </item>
<item quantity="other">Sichere %d Schlüssel </item>
<item quantity="one">Einen Schlüssel sichern …</item>
<item quantity="other">%d Schlüssel sichern </item>
</plurals>
<string name="keys_backup_info_title_version">Version</string>
<string name="keys_backup_info_title_algorithm">Algorithmus</string>
<string name="keys_backup_info_title_signature">Signatur</string>
<string name="keys_backup_restoring_computing_key_waiting_message">Berechne Wiederherstellungsschlüssel </string>
<string name="keys_backup_restoring_downloading_backup_waiting_message">Lade Schlüssel herunter </string>
<string name="keys_backup_restoring_importing_keys_waiting_message">Importiere Schlüssel </string>
<string name="keys_backup_restoring_computing_key_waiting_message">Wiederherstellungsschlüssel berechnen </string>
<string name="keys_backup_restoring_downloading_backup_waiting_message">Schlüssel herunterladen …</string>
<string name="keys_backup_restoring_importing_keys_waiting_message">Schlüssel importieren …</string>
<string name="action_ignore">Ignorieren</string>
<string name="auth_login_sso">Mit Single-Sign-On anmelden</string>
<string name="settings_send_message_with_enter">Nachricht mit Eingabetaste senden</string>
@@ -903,8 +903,8 @@
<string name="settings_labs_show_hidden_events_in_timeline">Versteckte Ereignisse in der Zeitleiste anzeigen</string>
<string name="bottom_action_people_x">Direktnachrichten</string>
<string name="send_file_step_idle">Warten </string>
<string name="send_file_step_encrypting_thumbnail">Vorschaubild wird verschlüsselt …</string>
<string name="send_file_step_encrypting_file">Verschlüssle Datei </string>
<string name="send_file_step_encrypting_thumbnail">Vorschaubild verschlüsseln …</string>
<string name="send_file_step_encrypting_file">Datei verschlüsseln </string>
<string name="edited_suffix">(bearbeitet)</string>
<string name="message_edits">Nachrichtenbearbeitung</string>
<string name="no_message_edits_found">Keine Änderungen gefunden</string>
@@ -912,7 +912,7 @@
<string name="room_filtering_footer_create_new_direct_message">Sende eine neue Direktnachricht</string>
<string name="room_filtering_footer_open_room_directory">Das Raumverzeichnis anzeigen</string>
<string name="link_copied_to_clipboard">Link in die Zwischenablage kopiert</string>
<string name="creating_direct_room">Erstelle Raum </string>
<string name="creating_direct_room">Raum erstellen</string>
<string name="message_view_edit_history">Bearbeitungsverlauf anzeigen</string>
<string name="import_e2e_keys_from_file">E2E-Schlüssel aus der Datei \"%1$s\" importieren.</string>
<string name="send_suggestion_sent">Vielen Dank, der Vorschlag wurde erfolgreich gesendet</string>
@@ -1003,7 +1003,7 @@
<string name="send_attachment">Anhang senden</string>
<string name="a11y_open_drawer">Navigationsmenü öffnen</string>
<string name="a11y_create_menu_open">Raumerstellungsmenü öffnen</string>
<string name="a11y_create_menu_close">Schließe das Raumerstellungsmenü </string>
<string name="a11y_create_menu_close">Das Raumerstellungsmenü schließen </string>
<string name="a11y_create_direct_message">Erstelle eine neue Direktnachricht</string>
<string name="a11y_create_room">Erstelle einen neuen Raum</string>
<string name="a11y_close_keys_backup_banner">Schließe Key-Backup-Einblendung</string>
@@ -1030,15 +1030,15 @@
<string name="content_reported_title">Inhalt gemeldet</string>
<string name="content_reported_content">Dieser Inhalt wurde gemeldet.
\n
\nWenn du keine weiteren Inhalte dieser Person sehen möchtest, kannst sie ignorieren, um ihre Nachrichten auszublenden.</string>
\nWenn du keine Inhalte mehr von dieser Person sehen möchtest, kannst du sie ignorieren, um ihre Nachrichten auszublenden.</string>
<string name="content_reported_as_spam_title">Als Spam gemeldet</string>
<string name="content_reported_as_spam_content">Dieser Inhalt wurde als Spam gemeldet.
\n
\nWenn du keine weiteren Inhalte dieser Person sehen möchtest, kannst sie ignorieren, um ihre Nachrichten auszublenden.</string>
\nWenn du keine Inhalte mehr von dieser Person sehen möchtest, kannst du sie ignorieren, um ihre Nachrichten auszublenden.</string>
<string name="content_reported_as_inappropriate_title">Als unangebracht gemeldet</string>
<string name="content_reported_as_inappropriate_content">Dieser Inhalt wurde als unangebracht gemeldet.
\n
\nWenn du keine weiteren Inhalte dieser Person sehen möchtest, kannst sie ignorieren, um ihre Nachrichten auszublenden.</string>
\nWenn du keine Inhalte mehr von dieser Person sehen möchtest, kannst du sie ignorieren, um ihre Nachrichten auszublenden.</string>
<string name="message_ignore_user">Nutzer ignorieren</string>
<string name="room_list_quick_actions_notifications_all_noisy">Alle Nachrichten (laut)</string>
<string name="room_list_quick_actions_notifications_all">Alle Nachrichten</string>
@@ -1524,8 +1524,8 @@
<string name="uploads_files_title">DATEIEN</string>
<string name="uploads_files_subtitle">%1$s um %2$s</string>
<string name="uploads_files_no_result">Es gibt in diesem Raum keine Dateien</string>
<string name="room_list_quick_actions_favorite_add">Füge zu Favoriten hinzu</string>
<string name="room_list_quick_actions_favorite_remove">Entferne von Favoriten</string>
<string name="room_list_quick_actions_favorite_add">Zu Favoriten hinzufügen</string>
<string name="room_list_quick_actions_favorite_remove">Aus Favoriten entfernen</string>
<string name="notice_member_no_changes_by_you">Du hast keine Änderungen gemacht</string>
<string name="room_join_rules_public_by_you">Du hast den Raum für alle, die den Link kennen, zugänglich gemacht.</string>
<string name="room_join_rules_invite_by_you">Du hast den Raumbeitritt auf Einladungen beschränkt.</string>
@@ -1622,9 +1622,9 @@
<string name="settings_call_show_confirmation_dialog_title">Versehentliche Anrufe verhindern</string>
<string name="settings_call_show_confirmation_dialog_summary">Bitte um Bestätigung, bevor du einen Anruf tätigst</string>
<string name="bottom_sheet_setup_secure_backup_submit">Einrichten</string>
<string name="no_permissions_to_start_conf_call">Dir fehlt die Berechtigung in diesem Raum eine Konferenz zu starten</string>
<string name="video_meeting">Beginne eine Videokonferenz</string>
<string name="audio_meeting">Beginne eine Audiokonferenz</string>
<string name="no_permissions_to_start_conf_call">Du bist nicht berechtigt, in diesem Raum ein Konferenzgespräch zu starten</string>
<string name="video_meeting">Videokonferenz starten</string>
<string name="audio_meeting">Audiokonferenz starten</string>
<string name="audio_video_meeting_description">Konferenzen nutzen die Jitsi-Sicherheits- und Berechtigungsrichtlinien. Alle im Raum Anwesenden können während der Konferenz beitreten.</string>
<string name="cannot_call_yourself">Du kannst dich nicht selbst anrufen</string>
<string name="cannot_call_yourself_with_invite">Du kannst dich nicht selbst anrufen, warte bis Teilnehmer die Einladung annehmen</string>
@@ -1670,7 +1670,7 @@
<string name="sent_a_poll">Umfrage</string>
<string name="sent_a_reaction">Reagierte mit: %s</string>
<string name="universal_link_malformed">Der Link war fehlerhaft</string>
<string name="no_permissions_to_start_webrtc_call">Du bist nicht berechtigt, einen Anruf in diesem Raum zu beginnen</string>
<string name="no_permissions_to_start_webrtc_call">Du bist nicht berechtigt, in diesem Raum einen Anruf zu beginnen</string>
<string name="sent_verification_conclusion">Ergebnis der Überprüfung</string>
<string name="delete_account_data_warning">Kontodaten vom Typ %1$s löschen\?
\n
@@ -1681,8 +1681,8 @@
<string name="settings_troubleshoot_test_push_loop_waiting_for_push">Die Applikation wartet auf den PUSH</string>
<string name="settings_troubleshoot_test_push_loop_title">Push testen</string>
<string name="search_banned_user_hint">Gebannte Nutzer filtern</string>
<string name="no_permissions_to_start_webrtc_call_in_direct_room">Du bist nicht berechtigt einen Anruf zu beginnen</string>
<string name="no_permissions_to_start_conf_call_in_direct_room">Du hast keine Berechtigung ein Konferenzgespräch zu starten</string>
<string name="no_permissions_to_start_webrtc_call_in_direct_room">Du bist nicht berechtigt, einen Anruf zu beginnen</string>
<string name="no_permissions_to_start_conf_call_in_direct_room">Du bist nicht berechtigt, ein Konferenzgespräch zu starten</string>
<string name="settings_security_pin_code_notifications_summary_on">Details wie Raumnamen und Nachrichteninhalt zeigen.</string>
<string name="settings_security_pin_code_notifications_title">Inhalt in Benachrichtigungen anzeigen</string>
<string name="settings_security_pin_code_use_biometrics_summary_off">PIN-Code ist die einzige Möglichkeit ${app_name} zu entsperren.</string>
@@ -1729,11 +1729,11 @@
<string name="settings_security_pin_code_notifications_summary_off">Nur die Anzahl ungelesener Nachrichten in der Benachrichtigung zeigen.</string>
<string name="attachment_type_dialog_title">Füge Bild hinzu per</string>
<string name="warning_room_not_created_yet">Der Raum ist noch nicht erstellt. Raumerstellung abbrechen\?</string>
<string name="room_list_quick_actions_low_priority_add">Zu niedrige Priorität hinzufügen</string>
<string name="room_list_quick_actions_low_priority_add">Zu niedriger Priorität hinzufügen</string>
<string name="create_room_topic_hint">Thema</string>
<string name="warning_unsaved_change_discard">Änderungen verwerfen</string>
<string name="warning_unsaved_change">Es gibt ungespeicherte Änderungen. Änderungen verwerfen\?</string>
<string name="room_list_quick_actions_low_priority_remove">Von niedrige Priorität entfernen</string>
<string name="room_list_quick_actions_low_priority_remove">Aus niedriger Priorität entfernen</string>
<string name="rotate_and_crop_screen_title">Rotieren und Zuschneiden</string>
<string name="create_room_settings_section">Raumeinstellungen</string>
<string name="create_room_topic_section">Raumthema (optional)</string>
@@ -2026,7 +2026,7 @@
</plurals>
<string name="error_file_too_big_simple">Die Datei ist zu groß.</string>
<string name="send_file_step_compressing_video">Video wird komprimiert (%d%%)</string>
<string name="send_file_step_compressing_image">Komprimiere Bild …</string>
<string name="send_file_step_compressing_image">Bild komprimieren …</string>
<string name="use_as_default_and_do_not_ask_again">Als Standard festsetzen und nicht mehr fragen</string>
<string name="option_always_ask">Jedes Mal fragen</string>
<string name="directory_add_a_new_server_prompt">Gib den Namen des neuen Servers ein, den du erkunden möchtest.</string>
@@ -2128,9 +2128,9 @@
<item quantity="other">%d verpasste Sprachanrufe</item>
</plurals>
<string name="hs_client_url">Heim-Server-API-Adresse</string>
<string name="denied_permission_voice_message">Um Sprachnachrichten zu senden, erlaube bitte Zugriff aufs Mikrofon.</string>
<string name="denied_permission_camera">Um fortzufahren, erlaube bitte in den Systemeinstellungen Zugriff auf die Kamera.</string>
<string name="denied_permission_generic">Für diese Aktion fehlen einige Berechtigungen, bitte erlaube diese in den Systemeinstellungen.</string>
<string name="denied_permission_voice_message">Um Sprachnachrichten zu senden, gewähre bitte den Zugriff aufs Mikrofon.</string>
<string name="denied_permission_camera">Um fortzufahren, gewähre bitte in den Systemeinstellungen den Zugriff auf die Kamera.</string>
<string name="denied_permission_generic">Für diese Aktion fehlen einige Berechtigungen, bitte gewähre diese in den Systemeinstellungen.</string>
<string name="voice_message_slide_to_cancel">Wische zum Abbrechen</string>
<string name="spaces_which_can_access">Spaces mit Zugriff auf</string>
<string name="allow_space_member_to_find_and_access">Space-Mitgliedern Auffinden und Zugriff erlauben.</string>
@@ -2877,7 +2877,7 @@
<string name="notice_voice_broadcast_ended">%1$s beendete eine Sprachübertragung.</string>
<string name="stop_voice_broadcast_content">Möchtest du die Übertragung wirklich beenden\? Dies wird die Übertragung beenden und die vollständige Aufnahme im Raum bereitstellen.</string>
<string name="stop_voice_broadcast_dialog_title">Live-Übertragung beenden\?</string>
<string name="action_stop">Ja, beende</string>
<string name="action_stop">Ja, beenden</string>
<string name="rich_text_editor_link">Link setzen</string>
<string name="set_link_edit">Link bearbeiten</string>
<string name="set_link_create">Link erstellen</string>
@@ -2957,4 +2957,12 @@
<string name="crosssigning_verify_after_update">App aktualisiert</string>
<string name="sign_out_failed_dialog_message">Dein Heim-Server ist nicht erreichbar. Falls du dich dennoch abmeldest, wird dieses Gerät nicht von deiner Geräteliste entfernt, also müsstest du dies mit einer anderen Sitzung selbst machen.</string>
<string name="sign_out_anyway">Dennoch abmelden</string>
<string name="create_room_unknown_users_dialog_submit">Dennoch Unterhaltung beginnen</string>
<string name="create_room_unknown_users_dialog_content">Konnte keine Profile für die folgenden Matrix-IDs finden. Möchtest du dennoch eine Unterhaltung beginnen\?
\n
\n%s</string>
<string name="invite_unknown_users_dialog_content">Konnte keine Profile für die folgenden Matrix-IDs finden. Möchtest du sie dennoch einladen\?
\n
\n%s</string>
<string name="invite_unknown_users_dialog_submit">Dennoch einladen</string>
</resources>

View File

@@ -360,11 +360,11 @@
\nΜε την απενεργοποίηση του λογαρισμού σας <b>δεν θα ξεχαστούν τα μηνύματά που έχετε στείλει</b>. Εάν θα θέλατε να ξεχαστούν, τότε δηλώστε το στο κουτί πιο κάτω.
\n
\nΗ ορατότητα μηνυμάτων δουλεύει σαν το ηλεκτρονικό ταχυδρομείο. Το να ξεχαστούν τα μηνύματα σημαίνει οτι μηνύματα τα οποία έχετε στείλει δεν θα σταλούν σε νέους ή μη-εγγεγραμμένους χρήστες, αλλα εγγεγραμμένοι χρήστες οι οποίοι έχουν ήδη πρόσβαση σε αυτά τα μηνύματα θα μπορούν ακόμα να τα δούν.</string>
<string name="room_tombstone_versioned_description">Αυτό το δωμάτιο έχει αντικατασταθεί και δεν είναι πια ενεργό</string>
<string name="room_tombstone_versioned_description">Αυτό το δωμάτιο έχει αντικατασταθεί και δεν είναι πια ενεργό.</string>
<string name="ssl_remain_offline">Παράβλεψη</string>
<string name="ssl_logout_account">Αποσύνδεση</string>
<string name="ssl_do_not_trust">Δεν εμπιστεύομαι</string>
<string name="room_do_not_have_permission_to_post">Δεν έχετε εξουσιοδότηση να δημοσιεύσετε σε αυτό το δωμάτιο</string>
<string name="room_do_not_have_permission_to_post">Δεν έχετε εξουσιοδότηση να δημοσιεύσετε σε αυτό το δωμάτιο.</string>
<string name="room_many_users_are_typing">%1$s &amp; %2$s &amp; άλλοι γράφουν…</string>
<string name="room_one_user_is_typing">%s γράφει…</string>
<string name="room_two_users_are_typing">%1$s &amp; %2$s γράφουν…</string>
@@ -383,4 +383,132 @@
<string name="location_share_external">Άνοιγμα με</string>
<string name="tooltip_attachment_sticker">Αποστολή αυτοκόλλητου</string>
<string name="notice_room_created">%1$s δημιούργησε το δωμάτιο</string>
</resources>
<string name="notice_direct_room_join_by_you">Εισήλθες</string>
<string name="notice_room_leave_by_you">Αποχώρησες από το δωμάτιο</string>
<string name="notice_direct_room_leave">%1$s αποχώρισε από το δωμάτιο</string>
<string name="notice_room_reject_by_you">Έχετε απορρίψει την πρόσκληση</string>
<string name="notice_display_name_set_by_you">Ορίσατε το όνομά σας ω; %1$s</string>
<string name="notice_room_invite_no_invitee_by_you">Η πρόσκλησή σου</string>
<string name="notice_room_created_by_you">Δημιούργησες αυτό το δωμάτιο</string>
<string name="notice_direct_room_created">%1$s δημιούργησε την συζήτηση</string>
<string name="notice_room_invite_by_you">"Προσκάλεσες τον χρήστη %1$s"</string>
<string name="notice_room_ban_by_you">Έχετε αποκλείσει τον χρήστη %1$s</string>
<string name="notice_avatar_url_changed_by_you">Αλλάξατε το άβατάρ σας</string>
<string name="notice_room_topic_changed_by_you">Άλλαξες το θέμα σε: %1$s</string>
<string name="pill_message_unknown_room_or_space">Δωμάτιο/Χώρος</string>
<string name="soft_logout_signin_submit">Σύνδεση</string>
<plurals name="x_selected">
<item quantity="one">%1$d επιλέχθηκε</item>
<item quantity="other">%1$d επιλέχθηκαν</item>
</plurals>
<plurals name="notice_room_server_acl_changes">
<item quantity="one">%d ACLs του server άλλαξε</item>
<item quantity="other">%d ACLs του server άλλαξαν</item>
</plurals>
<string name="qr_code_login_connecting_to_device">Πραγματοποιείται σύνδεση στην συσκευή</string>
<string name="qr_code_login_status_no_match">Δεν ταιριάζει;</string>
<string name="qr_code_login_try_again">Προσπάθησε ξανά</string>
<string name="notice_room_server_acl_set_allowed">"Οι servers που περιλαμβάνουν %s επιτρέπονται."</string>
<string name="notice_direct_room_created_by_you">Δημιούργησες αυτή την συζήτηση</string>
<string name="notice_direct_room_leave_by_you">Αποχώρισες από το δωμάτιο</string>
<string name="notice_room_unban">Ο χρήστης %1$s αφαίρεσε τον αποκλείσμο του χρήστη %2$s</string>
<string name="notice_room_name_changed_by_you">Άλλαξες το όνομα του δωματίου σε: %1$s</string>
<string name="notice_placed_video_call_by_you">Έχετε ξεκινήσει μία βιντεοκλήση.</string>
<string name="notice_made_future_room_visibility_by_you">Το μελλοντικό ιστορικό του δωματίου θα είναι εμφανή στο χρήστη %1$s</string>
<string name="notice_placed_voice_call_by_you">Έχετε ξεκινησει μία κλήση.</string>
<string name="notice_room_update">Ο χρήστης %s αναβάθμισε αυτό το δωμάτιο.</string>
<string name="notice_room_server_acl_set_banned">Η εύρεση server που περιλαμβάνει %s είναι απεκλεισμένη.</string>
<string name="notice_room_server_acl_updated_banned">"• Οι servers οπού περιλαμβάνουν %s είναι τώρα απεκλεισμένοι."</string>
<string name="qr_code_login_confirm_security_code">Επιβεβαίωση</string>
<string name="qr_code_login_confirm_security_code_description">Παρακαλώ επιβεβαιώστε ότι γνωρίζετε την προέλευση του κώδικα αυτού. Όταν συνδέετε νέες συσκευές, ενδεχομένως να δίνετε σε κάποιον άλλο πλήρης πρόσβαση στον λογαριασμό σας.</string>
<string name="soft_logout_clear_data_title">Εκαθάρριση προσωπικών δεδομένων</string>
<string name="notice_room_remove_by_you">Αφαιρέσατε τον χρήστη %1$s</string>
<string name="notice_display_name_changed_to">Ο χρήστης %1$s άλλαξε το όνομά του σε %2$s</string>
<string name="notice_room_avatar_changed">%1$s άλλαξε το άβαταρ του δωματίου</string>
<string name="notice_room_avatar_changed_by_you">Άλλαξες το άβαταρ του δωματίου</string>
<string name="notice_call_candidates">"%s απέστειλε δεδομένα ώστε να πραγματοποιηθεί η κλήση."</string>
<string name="notice_call_candidates_by_you">Απέστειλες δεδομένα ώστε να ξεκινήσει η κλήση.</string>
<string name="notice_answered_call_by_you">Απάντησες στην κλήση.</string>
<string name="notice_made_future_direct_room_visibility">Ο χρήστης %1$s έκανε τα μελλοντικά μηνύματα διαθέσιμα στον χρήστη %2$s</string>
<string name="notice_room_update_by_you">Έχετε αναβαθμίσει αυτό το δωμάτιο.</string>
<string name="notice_direct_room_update">%s αναβαθμίστηκε εδώ.</string>
<string name="notice_direct_room_update_by_you">Αναβαθμίστηκες εδώ.</string>
<string name="notice_room_server_acl_set_title">%s όρισε τα ACLs αυτού του server σε αυτό το δωμάτιο.</string>
<string name="notice_room_server_acl_set_title_by_you">Έχεις ορίσει το ACL του server για αυτό το δωμάτιο.</string>
<string name="notice_room_server_acl_set_ip_literals_not_allowed">"• Οι servers που περιλαμβάνονται αυτή την IP είναι απεκλεισμένοι."</string>
<string name="notice_room_server_acl_updated_title">%s άλλαξε τα ACLs του server γι αυτό το δωμάτιο.</string>
<string name="notice_room_server_acl_updated_title_by_you">Άλλαξες τα ACLs του server γι αυτό το δωμάτιο.</string>
<string name="soft_logout_signin_password_hint">Κωδικός Πρόσβασης</string>
<string name="soft_logout_signin_notice">Οι διαχειριστές του server σας (%1$s), σας έχουν αποσυνδέσει από τον λογαριασμό σας %2$s (%3$s).</string>
<string name="bug_report_error_too_short">Η πειργραφή είναι πολύ σύντομη</string>
<string name="soft_logout_title">Έχετε αποσυνδεθεί</string>
<string name="settings_advanced_settings">Επιπρόσθετες ρυθμίσεις</string>
<string name="settings_developer_mode">Λειτουργίες Προγραμματιστή</string>
<string name="devices_current_device">Τρέχουσα συνεδρία</string>
<string name="soft_logout_clear_data_dialog_title">Διαγραφή δεδομένων</string>
<string name="devices_other_devices">Άλλες συνεδρίες</string>
<string name="qr_code_login_signing_in">Πραγματοποιείται η σύνδεση</string>
<string name="notice_room_join_by_you">Συμμετέχεις σε αυτό το δωμάτιο</string>
<string name="notice_direct_room_join">%1$s εισήλθε</string>
<string name="notice_room_unban_by_you">Έχετε αφαιρέσει τον αποκλεισμό από τον χρήστη %1$s</string>
<string name="notice_room_withdraw_by_you">Αφαιρέσατε την πρόσκληση του χρήστη %1$s</string>
<string name="notice_display_name_changed_from_by_you">Άλλαξες το όνομά σου από %1$s σε %2$s</string>
<string name="notice_display_name_removed_by_you">Αφαίρεσες το όνομά όπου εμφανιζόταν ο λογαριασμός σου ( προηγουμένως ήταν %1$s)</string>
<string name="notice_ended_call_by_you">.Τερμάτισες την κλήση.</string>
<string name="notice_made_future_direct_room_visibility_by_you">Έκανες τα μελλοντικά μηνύματα διαθέσιμα στον χρήστη %1$s</string>
<string name="rich_text_editor_link">Ορισμός συνδέσμου</string>
<string name="signed_out_submit">Συνδεθείτε ξανά</string>
<string name="soft_logout_signin_title">Σύνδεση</string>
<string name="soft_logout_clear_data_submit">Διαγρφή όλων των δεδομένων</string>
<string name="settings">Ρυθμίσεις</string>
<string name="settings_general_title">Γενικά</string>
<string name="create_spaces_default_public_room_name">Γενικά</string>
<string name="notice_room_avatar_removed">%1$s Έχει αφαιρεθεί το avatar του δωματίου</string>
<string name="notice_room_avatar_removed_by_you">Έχεις αφαιρέσει το avatar του δωματίου</string>
<string name="notice_room_server_acl_updated_no_change">Καμία αλλαγή.</string>
<string name="notice_room_name_removed_by_you">Έχεις αφαίρεσει το όνομα του δωματίου</string>
<string name="notice_direct_room_third_party_invite">Ο χρήστης %1$s προσκάλεσε τον χρήστη %2$s</string>
<string name="notice_direct_room_third_party_invite_by_you">Προσκάλεσες τον χρήστη %1$s</string>
<string name="power_level_admin">Διαχειριστής</string>
<string name="power_level_moderator">Moderator</string>
<string name="notice_power_level_changed_by_you">Άλλαξες το power του level %1$s.</string>
<string name="notice_room_server_acl_updated_was_allowed">Οι Servers που ταιριάζουν με %s έχουν αφαιρεθεί από την επιτρεπόμενη λίστα.</string>
<string name="notice_room_server_acl_updated_ip_literals_not_allowed">Οι Servers που ταιριάζουν με το IP έχουν αποκλειστικοί.</string>
<string name="notice_room_topic_removed_by_you">Έχεις αφαιρέσει το θέμα του δωματίου</string>
<string name="notice_room_third_party_registered_invite_by_you">Αποδέχτηκες την πρόσκληση για το %1$s</string>
<string name="power_level_default">Προεπιλογή</string>
<string name="power_level_custom">Custom (%1$d)</string>
<string name="power_level_custom_no_value">Προσαρμοσμένο</string>
<string name="notice_room_server_acl_allow_is_empty">🎉Όλοι οι Servers έχουν αποκλειστεί από συμμετέχοντες!Το δωμάτιο αυτό δεν μπορεί πια να χρησιμοποιηθεί.</string>
<string name="notice_room_third_party_invite_by_you">Έστειλες μια πρόσκληση στο %1$s για να συνδεθείς στο δωμάτιο</string>
<string name="event_status_sent_message">Το μήνυμα στάλθηκε</string>
<string name="create_room">Δημιουργώ ένα δωμάτιο</string>
<string name="room_error_access_unauthorized">Δεν επιτρέπεται να συνδεθείς σε αυτό το δωμάτιο</string>
<string name="room_displayname_3_members">%1$s,%2$s και %3$s</string>
<string name="change_space">Αλλάζω το Space</string>
<string name="room_displayname_4_members">%1$s, %2$s, %3$s και %4$s</string>
<string name="explore_rooms">Ερευνάω τα δωμάτια</string>
<string name="initial_sync_request_reason_unignored_users">-Κάποιοι χρήστες έχουν αγνοηθεί</string>
<plurals name="room_displayname_four_and_more_members">
<item quantity="one">%1$s, %2$s, %3$s και %4$d άλλος χρήστης</item>
<item quantity="other">%1$s, %2$s, %3$s και %4$d άλλοι χρήστες</item>
</plurals>
<string name="event_status_sending_message">Το μήνυμα στέλνεται…</string>
<string name="pill_message_from_unknown_user">Μύνημα</string>
<string name="set_link_create">Δημιουργώ έναν σύνδεσμο</string>
<string name="set_link_link">Σύνδεσμος</string>
<string name="message_reply_to_sender_sent_file">Στέλνω ένα αρχείο.</string>
<string name="pill_message_from_user">Μύνημα από τον χρήστη %s</string>
<string name="message_reply_to_sender_sent_voice_message">Στέλνω ένα ηχητικό μύνημα.</string>
<string name="message_reply_to_sender_sent_image">Στέλνω μια εικόνα.</string>
<plurals name="notice_room_canonical_alias_alternative_added_by_you">
<item quantity="one">Πρόσθεσες εναλλακτική διεύθυνση %1$ για αυτό το δωμάτιο.</item>
<item quantity="other">Πρόσθεσες εναλλακτικές διευθύνσεις %1$ για αυτό το δωμάτιο.</item>
</plurals>
<string name="message_reply_to_sender_sent_video">Στέλνω ένα βίντεο.</string>
<string name="message_reply_to_sender_sent_audio_file">Στέλνω ένα αρχείο ήχου.</string>
<string name="set_link_edit">Επεξεργάζομαι ένα σύνδεσμο</string>
<string name="set_link_text">Κείμενο</string>
<string name="message_reply_to_sender_sent_sticker">Στέλνω ένα αυτοκόλλητο.</string>
<string name="qr_code_login_scan_qr_code_button">Σκανάρω ένα QR code</string>
</resources>

View File

@@ -2958,4 +2958,12 @@
<string name="crosssigning_verify_after_update">کاره به‌روز شد</string>
<string name="sign_out_anyway">خروج به هر صورت</string>
<string name="sign_out_failed_dialog_message">نمی‌توان به کارساز خانگی رسید. اگر همچنان خارج شوید این افزاره از سیاههٔ افزاره‌هایتان پاک نخواهد شد و باید از کارخواهس دیگر برش دارید.</string>
<string name="invite_unknown_users_dialog_content">ناتوان در یافتن نمایه‌ها برای شناسه‌های ماتریکس سیاهه شده. می‌خواهید به هر حال دعوتشان کنید؟
\n
\n%s</string>
<string name="create_room_unknown_users_dialog_content">ناتوان در یافتن نمایه‌ها برای شناسه‌های ماتریکس سیاهه شده. می‌خواهید به هر حال گپی بیاغازید؟
\n
\n%s</string>
<string name="create_room_unknown_users_dialog_submit">آغاز گپ به هر حال</string>
<string name="invite_unknown_users_dialog_submit">دعوت به هر حال</string>
</resources>

File diff suppressed because it is too large Load Diff

View File

@@ -3077,4 +3077,12 @@
<string name="crosssigning_verify_after_update">Zaktualizowano aplikację</string>
<string name="sign_out_anyway">Wyloguj mimo to</string>
<string name="sign_out_failed_dialog_message">Nie można skontaktować się z serwerem domowym. Jeśli mimo to się wylogujesz, urządzenie nie zostanie usunięte z listy urządzeń. Usuń je za pomocą innego klienta.</string>
<string name="create_room_unknown_users_dialog_content">Nie można znaleźć profili dla poniższych ID Matrix. Czy chcesz rozpocząć czat mimo to\?
\n
\n%s</string>
<string name="invite_unknown_users_dialog_submit">Zaproś mimo to</string>
<string name="create_room_unknown_users_dialog_submit">Rozpocznij czat mimo to</string>
<string name="invite_unknown_users_dialog_content">Nie można znaleźć profili dla poniższych ID Matrix. Czy chcesz zaprosić je mimo to\?
\n
\n%s</string>
</resources>

View File

@@ -2909,4 +2909,53 @@
<string name="sign_out_anyway">Terminar sessão ainda assim</string>
<string name="verification_request_waiting_for_recovery">Verificação a partir de Chave ou Frase Segura…</string>
<string name="verification_profile_other_device_untrust_info">Até que este utilizador confie nesta sessão, as mensagens enviadas para e a partir dela são marcadas com avisos.</string>
<string name="pill_message_in_unknown_room">Mensagem na sala</string>
<string name="rich_text_editor_numbered_list">Alternar lista numerada</string>
<string name="room_polls_active">Votações ativas</string>
<string name="room_poll_details_go_to_timeline">Ver votação na linha do tempo</string>
<string name="unable_to_decrypt_some_events_in_poll">Devido a erros de descriptografia, alguns votos podem não serem contados</string>
<string name="confirm_your_identity_after_update">A mensageria segura fora aprimorada com a atualização mais recente. Verifique novamente seu dispositivo.</string>
<string name="ended_poll_indicator">Votação encerrada.</string>
<string name="room_polls_ended">Votações anteriores</string>
<string name="rich_text_editor_bullet_list">Alternar lista de marcadores</string>
<string name="pill_message_unknown_room_or_space">Sala/Espaço</string>
<string name="direct_room_encryption_enabled_waiting_users">Aguardando os usuários entrarem no ${app_name}</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Assim que os usuários convidados entrarem no ${app_name}, vocês poderão conversar e a sala terá criptografia de ponta a ponta</string>
<string name="room_polls_active_no_item">Não há votação ativa nesta sala</string>
<string name="room_polls_ended_no_item">Não há votações anteriores nesta sala</string>
<string name="message_reply_to_ended_poll_preview">Votação encerrada</string>
<string name="secure_backup_reset_danger_warning">Prossiga apenas se você tem certeza que você perdeu todos os outros dispositivos e sua chave de segurança.</string>
<string name="encrypted_by_deleted">Criptografado por um dispositivo apagado</string>
<string name="rich_text_editor_unindent">Remover recuo</string>
<string name="rich_text_editor_quote">Alternar aspas</string>
<string name="rich_text_editor_inline_code">Aplicar formatação de código em linha</string>
<string name="_resume">Prosseguir</string>
<string name="verification_not_found">O pedido de verificação não foi encontrado. Ele pode ter sido cancelado ou tratado por outra sessão.</string>
<string name="rich_text_editor_indent">Recuar</string>
<string name="rich_text_editor_code_block">Alternar bloco de código</string>
<string name="message_reply_to_sender_ended_poll">terminou uma votação.</string>
<string name="message_reply_to_poll_preview">Votação</string>
<string name="settings_access_token">Token de acesso</string>
<string name="error_voice_message_broadcast_in_progress_message">Você não pode iniciar uma mensagem de voz porque está gravando uma transmissão ao vivo. Termine sua transmissão ao vivo para iniciar uma mensagem de voz</string>
<plurals name="room_polls_ended_no_item_for_loaded_period">
<item quantity="one">Não há votação anterior para antes de ontem.
\nCarregue mais votações para ver as votações dos dias anteriores.</item>
<item quantity="other">Não há votação anterior para os últimos %1$d dias.
\nCarregue mais votações para ver as votações dos dias anteriores.</item>
</plurals>
<plurals name="room_polls_active_no_item_for_loaded_period">
<item quantity="one">Não há votação ativa para antes de ontem.
\nCarregue mais votações para ver as votações dos dias anteriores.</item>
<item quantity="other">Não há votação ativa para os últimos %1$d dias.
\nCarregue mais votações para ver as votações dos dias anteriores.</item>
</plurals>
<string name="room_polls_load_more">Carregar mais votações</string>
<string name="room_polls_loading_error">Erro ao buscar votações.</string>
<string name="settings_access_token_summary">Seu token de acesso fornece acesso completo à sua conta. Não o compartilhe com outras pessoais.</string>
<string name="pill_message_from_user">Mensagem de %s</string>
<string name="secure_backup_reset_all_no_other_devices_long">A redefinição de suas chaves de verificação não pode ser desfeita. Após a redefinição, você não terá acesso às mensagens criptografadas antigas e todos os amigos que fizeram a verificação anteriormente verão avisos de segurança até que você faça a verificação novamente com eles.</string>
<string name="error_voice_message_broadcast_in_progress">Não foi possível iniciar a mensagem de voz</string>
<string name="room_polls_wait_for_display">Exibindo votações</string>
<string name="pill_message_from_unknown_user">Mensagem</string>
<string name="pill_message_in_room">Mensagem em %s</string>
</resources>

View File

@@ -290,7 +290,7 @@
<string name="send_bug_report_progress">Прогресс (%s%%)</string>
<string name="send_bug_report_app_crashed">В прошлый раз приложение некорректно завершило работу. Хотите отправить отчёт о сбое\?</string>
<string name="join_room">Войти в Комнату</string>
<string name="username">Имя пользователя</string>
<string name="username">Псевдоним</string>
<string name="logout">Выйти</string>
<string name="hs_url">URL домашнего сервера</string>
<string name="search">Поиск</string>
@@ -300,7 +300,7 @@
<string name="option_take_photo_video">Камера</string>
<string name="auth_login">Вход</string>
<string name="auth_submit">Подать</string>
<string name="auth_invalid_login_param">Неправильное имя пользователя и/или пароль</string>
<string name="auth_invalid_login_param">Неверный псевдоним и/или пароль</string>
<string name="auth_invalid_email">Это не похоже на действительный адрес электронной почты</string>
<string name="auth_email_already_defined">Этот адрес электронной почты уже используется.</string>
<string name="auth_forgot_password">Забыли пароль?</string>
@@ -797,7 +797,7 @@
<string name="backup">Создание резервной копии</string>
<string name="sign_out_bottom_sheet_will_lose_secure_messages">Сделайте резервную копию ваших ключей или потеряете доступ к вашим зашифрованным сообщениям.</string>
<string name="action_sign_out_confirmation_simple">Уверены, что хотите выйти?</string>
<string name="error_empty_field_enter_user_name">Пожалуйста, введите имя пользователя.</string>
<string name="error_empty_field_enter_user_name">Пожалуйста, введите псевдоним.</string>
<string name="keys_backup_setup_step1_advanced">(Расширенный)</string>
<string name="keys_backup_setup_step1_manual_export">Ручной экспорт ключей</string>
<string name="keys_backup_setup_creating_backup">Создание резервной копии</string>
@@ -1132,11 +1132,11 @@
<string name="login_msisdn_error_not_international">Международные телефонные номера должны начинаться с \'+\'</string>
<string name="login_msisdn_error_other">Номер телефона выглядит недействительным. Пожалуйста, проверьте его</string>
<string name="login_signup_to">Зарегистрироваться в %1$s</string>
<string name="login_signin_username_hint">Имя пользователя или электронная почта</string>
<string name="login_signup_username_hint">Имя пользователя</string>
<string name="login_signin_username_hint">Псевдоним или электронная почта</string>
<string name="login_signup_username_hint">Псевдоним</string>
<string name="login_signup_password_hint">Пароль</string>
<string name="login_signup_submit">Далее</string>
<string name="login_signup_error_user_in_use">Это имя пользователя занято</string>
<string name="login_signup_error_user_in_use">Этот псевдоним уже занят</string>
<string name="login_signup_cancel_confirmation_title">Предупреждение</string>
<string name="login_signup_cancel_confirmation_content">Ваш аккаунт еще не создан. Остановить процесс регистрации\?</string>
<string name="login_a11y_choose_matrix_org">Выбрать matrix.org</string>
@@ -1144,7 +1144,7 @@
<string name="login_a11y_choose_other">Выбрать другой сервер</string>
<string name="login_a11y_captcha_container">Пожалуйста, пройдите проверку капчей</string>
<string name="login_terms_title">Примите условия для продолжения</string>
<string name="login_wait_for_email_title">Пожалуйста, проверьте ваш электронный почтовый ящик</string>
<string name="login_wait_for_email_title">Пожалуйста, проверьте свою электронную почту</string>
<string name="login_wait_for_email_notice">Мы только что отправили письмо на %1$s.
\nПожалуйста, нажмите на содержащуюся в нём ссылку, чтобы продолжить создание аккаунта.</string>
<string name="login_error_outdated_homeserver_title">Домашний сервер устарел</string>
@@ -1191,7 +1191,7 @@
<string name="verification_conclusion_warning">Недоверенный вход</string>
<string name="room_profile_section_more_uploads">Вложения</string>
<string name="cross_signing_verify_by_emoji">Интерактивная проверка со смайлами</string>
<string name="error_empty_field_choose_user_name">Пожалуйста, выберите имя пользователя.</string>
<string name="error_empty_field_choose_user_name">Пожалуйста, выберите псевдоним.</string>
<string name="error_empty_field_choose_password">Пожалуйста, выберите пароль.</string>
<string name="message_action_item_redact">Удалить…</string>
<string name="seen_by">Просмотрено</string>
@@ -1549,7 +1549,7 @@
<string name="settings_messages_in_e2e_group_chat">Зашифрованные сообщения в групповых чатах</string>
<string name="settings_when_rooms_are_upgraded">При обновлении комнат</string>
<string name="command_description_plain">Посылает сообщение в виде простого текста, не интерпретируя его как разметку</string>
<string name="auth_invalid_login_param_space_in_password">Неверное имя пользователя и/или пароль. Введенный пароль начинается или заканчивается пробелами, пожалуйста, проверьте.</string>
<string name="auth_invalid_login_param_space_in_password">Неверный псевдоним и/или пароль. Введённый пароль начинается или заканчивается пробелами, пожалуйста, проверьте его.</string>
<string name="auth_invalid_login_deactivated_account">Эта учётная запись была деактивирована.</string>
<string name="bootstrap_enter_recovery">Введите %s, чтобы продолжить</string>
<string name="use_file">Использовать файл</string>
@@ -2195,7 +2195,7 @@
<string name="settings_group_messages">Групповых сообщениях</string>
<string name="settings_encrypted_direct_messages">Зашифрованных диалогах</string>
<string name="settings_messages_direct_messages">Диалогах</string>
<string name="settings_messages_containing_username">Мое имя пользователя</string>
<string name="settings_messages_containing_username">Мой псевдоним</string>
<string name="settings_messages_containing_display_name">Моё отображаемое имя</string>
<string name="settings_notification_notify_me_for">Уведомлять меня о</string>
<string name="settings_notification_other">Другое</string>
@@ -2537,7 +2537,7 @@
<string name="settings_show_latest_profile">Последняя информация о пользователе</string>
<string name="space_explore_filter_no_result_title">Не найдено</string>
<string name="a11y_presence_busy">Занят</string>
<string name="error_forbidden_digits_only_username">Домашний сервер не принимает имя пользователя, состоящее только из цифр.</string>
<string name="error_forbidden_digits_only_username">Домашний сервер не принимает псевдонимы, состоящие только из цифр.</string>
<string name="ftue_personalize_skip_this_step">Пропустить этот шаг</string>
<string name="ftue_personalize_submit">Сохранить и продолжить</string>
<string name="ftue_personalize_complete_subtitle">Ваши предпочтения были сохранены</string>
@@ -2633,7 +2633,7 @@
<string name="ftue_auth_create_account_password_entry_footer">Должно быть 8 или более символов</string>
<string name="crosssigning_cannot_verify_this_session">Не удалось заверить этот сеанс</string>
<string name="permalink_unsupported_groups">Невозможно открыть эту ссылку: сообщества были заменены пространствами</string>
<string name="ftue_auth_login_username_entry">Имя пользователя / Почта / Телефон</string>
<string name="ftue_auth_login_username_entry">Псевдоним / Почта / Телефон</string>
<string name="ftue_auth_password_reset_email_confirmation_subtitle">Следуйте инструкциям, отправленным на %s</string>
<string name="ftue_auth_forgot_password">Забыли пароль</string>
<string name="ftue_auth_email_verification_footer">Не получили письмо\?</string>
@@ -3020,7 +3020,7 @@
<string name="error_voice_message_broadcast_in_progress">Не удалось записать голосовое сообщение</string>
<string name="review_unverified_sessions_description">Убедиться что Ваш аккаунт в безопасности</string>
<string name="settings_nightly_build_update">Получить последнюю сборку (у вас могут быть проблемы со входом)</string>
<string name="room_profile_section_more_polls">История опроса</string>
<string name="room_profile_section_more_polls">Опросы</string>
<string name="started_a_voice_broadcast">Голосовая трансляция начата</string>
<string name="thread_list_not_available">Ваш домашний сервер не поддерживает список обсуждений.</string>
<string name="action_stop">Остановить</string>
@@ -3032,7 +3032,7 @@
<string name="pill_message_in_room">Сообщение в %s</string>
<string name="pill_message_in_unknown_room">Сообщение в комнате</string>
<string name="pill_message_unknown_room_or_space">Комната/Пространство</string>
<string name="settings_external_account_management_title">Аккаунт</string>
<string name="settings_external_account_management_title">Учётная запись</string>
<string name="avatar_of_user">Аватар профиля пользователя %1$s</string>
<string name="secure_backup_reset_danger_warning">Продолжайте, только если вы уверены, что ваш ключ утерян, а доступ к другим активным устройствам отсустствует.</string>
<string name="verification_profile_other_device_untrust_info">Пока пользователь не верифицировал эту сессию, отправленные и полученные сообщения отмечаются предупреждениями.</string>
@@ -3046,13 +3046,19 @@
<string name="settings_notification_error_on_update">При изменении настроек уведомлений произошла ошибка. Попробуйте ещё раз.</string>
<string name="direct_room_encryption_enabled_waiting_users_tile_description">Когда приглашенные пользователи присоединятся к ${app_name}, вы сможете писать им с использованием сквозного шифрования</string>
<string name="secure_backup_reset_all_no_other_devices_long">Сброс ваших ключей верификации не может быть отменен. После сброса, вы не будете иметь доступа к старым зашифрованным сообщениям, а все ваши контакты, верифицировавшие вас ранее, увидят предупреждение о повторной верификации.</string>
<string name="encrypted_by_deleted">Зашифрованно неактивным устройством</string>
<string name="confirm_your_identity_after_update">Защищенный обмен сообщениями был обновлен. Пожалуйста, повторно верифицируйте ваше устройство.</string>
<string name="encrypted_by_deleted">Зашифровано прерванным сеансом</string>
<string name="confirm_your_identity_after_update">В последнем обновлении улучшили защищённую переписку. Пожалуйста, перезаверьте свой сеанс.</string>
<string name="error_voice_broadcast_unable_to_decrypt">Не удается расшифровать голосовое сообщение.</string>
<string name="room_poll_details_go_to_timeline">Обзор опроса во времени</string>
<string name="pill_message_from_unknown_user">Сообщение</string>
<string name="verification_verify_with_another_device">Подтвердить с помощью активного устройства</string>
<string name="verification_verify_with_another_device">Сверить с другим сеансом</string>
<string name="_resume">Возобновить</string>
<string name="notice_display_name_changed_to">%1$s изменил отображаемое имя на %2$s</string>
<string name="notice_display_name_changed_to">%1$s изменил(а) имя на %2$s</string>
<string name="verification_not_found">Запрос на верификацию не найден. Возможно, он был отменен или обработан другим сеансом.</string>
<string name="rich_text_editor_quote">Цитата</string>
<string name="settings_crypto_version">Версия шифрования</string>
<string name="rich_text_editor_code_block">Блок кода</string>
<string name="rich_text_editor_indent">Подпункт</string>
<string name="rich_text_editor_unindent">Пункт</string>
<string name="settings_acceptable_use_policy">Политика пользования</string>
</resources>

View File

@@ -3018,4 +3018,12 @@
<string name="crosssigning_verify_after_update">Aplikácia bola aktualizovaná</string>
<string name="sign_out_anyway">Aj tak sa odhlásiť</string>
<string name="sign_out_failed_dialog_message">Nie je možné sa spojiť s domovským serverom. Ak sa aj tak odhlásite, toto zariadenie nebude vymazané zo zoznamu zariadení, môžete ho odstrániť pomocou iného klienta.</string>
<string name="create_room_unknown_users_dialog_content">Nie je možné nájsť profily pre nižšie uvedené Matrix ID. Chceli by ste napriek tomu začať konverzáciu\?
\n
\n%s</string>
<string name="create_room_unknown_users_dialog_submit">Spustiť konverzáciu aj tak</string>
<string name="invite_unknown_users_dialog_content">Nie je možné nájsť profily pre nižšie uvedené Matrix ID. Chcete ich aj tak pozvať\?
\n
\n%s</string>
<string name="invite_unknown_users_dialog_submit">Napriek tomu pozvať</string>
</resources>

View File

@@ -6,7 +6,7 @@
<string name="action_send">Pošlji</string>
<string name="notice_room_join_by_you">Pridružil si se v sobi</string>
<string name="notice_room_invite_you">%1$s te je povabil</string>
<string name="notice_answered_call_by_you">Odgovoril si na klic.</string>
<string name="notice_answered_call_by_you">Odgovorili ste na klic.</string>
<string name="notice_answered_call">%s je odgovoril na klic.</string>
<string name="notice_room_name_changed_by_you">Spremenil si ime sobe v: %1$s</string>
<string name="notice_room_name_changed">%1$s je spremenil ime sobe v: %2$s</string>
@@ -40,11 +40,11 @@
<string name="notice_room_avatar_changed_by_you">Spremenil si avatarja sobe</string>
<string name="notice_placed_video_call_by_you">Začel si video klic.</string>
<string name="notice_placed_voice_call">%s je začel video klic.</string>
<string name="notice_placed_voice_call_by_you">Začel si video klic.</string>
<string name="notice_placed_voice_call_by_you">Začeli ste video klic.</string>
<string name="notice_ended_call">%s je končal klic.</string>
<string name="notice_ended_call_by_you">Končal si klic.</string>
<string name="notice_ended_call_by_you">Končali ste klic.</string>
<string name="notice_made_future_room_visibility">%1$s je %2$s omogočil ogled prihodnje zgodovine sobe</string>
<string name="notice_made_future_room_visibility_by_you">Omogočil si ogled prihodnje zgodovine sobe %1$s</string>
<string name="notice_made_future_room_visibility_by_you">Omogočili ste ogled prihodnje zgodovine sobe %1$s</string>
<plurals name="x_selected">
<item quantity="one">%1$d izbran</item>
<item quantity="two">%1$d izbrana</item>
@@ -62,7 +62,17 @@
<string name="notice_display_name_removed_by_you">Odstranil si svoje prikazno ime (bilo je %1$s)</string>
<string name="notice_room_topic_changed_by_you">Temo si spremenil v: %1$s</string>
<string name="notice_placed_video_call">%s je začel video klic.</string>
<string name="notice_made_future_direct_room_visibility">%1$s je omočil %2$s ogled prihodnjih sporočil</string>
<string name="notice_made_future_direct_room_visibility_by_you">%1$s si omogočil ogled prihodnjih sporočil</string>
<string name="notice_made_future_direct_room_visibility">%1$s je omogočil %2$s ogled prihodnjih sporočil</string>
<string name="notice_made_future_direct_room_visibility_by_you">%1$s ste omogočili ogled prihodnjih sporočil</string>
<string name="notice_room_visibility_invited">vsi člani sobe, od trenutka povabila.</string>
<string name="notice_room_invite_no_invitee">Povabilo uporabnika %s</string>
<string name="notice_call_candidates">%s je poslal(a) podatke za začetek klica.</string>
<string name="notice_call_candidates_by_you">Poslali ste podatke za začetek klica.</string>
<string name="notice_direct_room_update_by_you">Tu ste izvedli nadgradnjo.</string>
<string name="notice_room_visibility_joined">vsi člani te sobe od trenutka ko so se pridružili.</string>
<string name="notice_room_visibility_shared">vsi člani sobe.</string>
<string name="notice_room_visibility_world_readable">kdorkoli.</string>
<string name="notice_room_update">%s je nadgradil to sobo.</string>
<string name="notice_room_update_by_you">Vi ste nadgradili to sobo.</string>
<string name="notice_direct_room_update">%s je tu izvedel(a) nadgradnjo.</string>
</resources>

View File

@@ -2945,4 +2945,13 @@
<string name="crosssigning_verify_after_update">Aplikacioni u përditësua</string>
<string name="sign_out_anyway">Dil, sido qoftë</string>
<string name="sign_out_failed_dialog_message">Skapet dot shërbyesi Home. Nëse keni dalë, sido qoftë, kjo pajisje sdo të fshihet te lista e pajisjeve tuaja, mund të doni ta hiqni duke përdorur klient tjetër.</string>
<string name="create_room_unknown_users_dialog_content">Sarrihet të gjenden profile për ID-rat Matrix të radhitura më poshtë. Do të donit të fillohej një fjalosje, sido që të jetë\?
\n
\n%s</string>
<string name="settings_enable_direct_share_title">Aktivizo ndarje të drejtpërdrejtë me të tjerët</string>
<string name="create_room_unknown_users_dialog_submit">Fillo fjalosje, sido që të jetë</string>
<string name="invite_unknown_users_dialog_content">Sarrihet të gjenden profile për ID-rat Matrix të radhitura më poshtë. Do të donit të ftohen, sido qoftë\?
\n
\n%s</string>
<string name="invite_unknown_users_dialog_submit">Ftoji, sido qoftë</string>
</resources>

View File

@@ -2958,4 +2958,12 @@
<string name="crosssigning_verify_after_update">App uppdaterad</string>
<string name="sign_out_failed_dialog_message">Kan inte nå hemservern. Om du ändå loggar ut kommer den här enheten inte att raderas från din enhetslista, du kanske vill ta bort den med en annan klient.</string>
<string name="sign_out_anyway">Logga ut ändå</string>
<string name="create_room_unknown_users_dialog_content">Kunde in hitta profiler för Matrix-ID:n nedan. Vill du starta en chatt ändå\?
\n
\n%s</string>
<string name="create_room_unknown_users_dialog_submit">Starta chatt ändå</string>
<string name="invite_unknown_users_dialog_content">Kunde inte hitta profiler för Matrix-ID:n nedan. Vill du bjuda in dem ändå\?
\n
\n%s</string>
<string name="invite_unknown_users_dialog_submit">Bjud in ändå</string>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="notice_room_remove_by_you">umeondoa %1$s</string>
<string name="notice_room_remove">%1$s kuondolewa %2$s</string>
<string name="notice_room_reject_by_you">Ulikataa mwaliko</string>
@@ -11,4 +11,34 @@
<item quantity="one">%1$d Iliyochaguliwa</item>
<item quantity="other">%1$d Ziliyochaguliwa</item>
</plurals>
</resources>
<string name="notice_room_created">%1$s Andaa chumba</string>
<string name="notice_room_created_by_you">Umeandaa chumba</string>
<string name="notice_direct_room_created">%1$s anzisha mjadala</string>
<string name="notice_direct_room_created_by_you">Ulianzisha mjadala</string>
<string name="notice_room_invite">%1$s Walioalikwa %2$s</string>
<string name="notice_room_join">%1$s jiunge na mjadala</string>
<string name="notice_room_leave">%1$s Acha mjadala</string>
<string name="notice_room_leave_by_you">Umetoka kwenye mjadala</string>
<string name="notice_direct_room_leave">%1$s Acha mjadala</string>
<string name="notice_direct_room_leave_by_you">Umeacha mjadala</string>
<string name="notice_room_unban">%1$s hujazuiliwa %2$s</string>
<string name="notice_room_unban_by_you">Hujazuiliwa %1$s</string>
<string name="notice_room_ban">%1$s huruhusiwi %2$s</string>
<string name="notice_room_ban_by_you">Huruhusiwi %1$s</string>
<string name="notice_room_withdraw_by_you">Sitisha %1$s\'s mwaliko</string>
<string name="notice_room_invite_no_invitee">%s\'s Mwaliko</string>
<string name="notice_room_join_by_you">Umejiunga na mjadala</string>
<string name="notice_room_withdraw">%1$s sitisha %2$s\'s mmwaliko</string>
<string name="notice_room_invite_no_invitee_by_you">Mwaliko wako</string>
<string name="notice_room_invite_by_you">Umealika %1$s</string>
<string name="notice_avatar_url_changed">%1$s wamebadilisha avatar</string>
<string name="notice_avatar_url_changed_by_you">Umebadilisha avatar yako</string>
<string name="notice_display_name_set_by_you">Unaweka jina lako la kuonyesha %1$s</string>
<string name="notice_display_name_changed_from_by_you">ulibadilisha jina lako la kuonyesha kutoka %1$s kwenda %2$s</string>
<string name="notice_display_name_removed_by_you">Umeondoa jina lako la kuonyesha(lilikuwa %1$s)</string>
<string name="notice_room_topic_changed">%1$s alibadilisha mada kuwa: %2$s</string>
<string name="notice_display_name_set">%1$s weka jina lao la kuonyesha %2$s</string>
<string tools:ignore="UnusedResources" name="notice_display_name_changed_from">%1$s badilisha majina yao kuonyesha yalitoka %2$s kwenda %3$s</string>
<string name="notice_display_name_changed_to">%1$s badilisha majina yao kwenda %2$s</string>
<string name="notice_display_name_removed">%1$s wameondoa ma jina yao yaliyonyeshwa (yalikuwa %2$s)</string>
</resources>

View File

@@ -1734,4 +1734,32 @@
<string name="action_view_threads">Konuları Görüntüle</string>
<string name="initial_sync_request_title">Başlangıç eşitleme isteği</string>
<string name="all_chats">Tüm Sohbetler</string>
</resources>
<string name="start_chat">Sohbet Başlat</string>
<string name="notice_voice_broadcast_ended">%1$s bir ses yayınını sonlandırdı.</string>
<string name="notice_voice_broadcast_ended_by_you">Bir sesli yayını sonlandırdınız.</string>
<string name="create_room">Oda yarat</string>
<string name="explore_rooms">Odaları keşfedin</string>
<string name="initial_sync_request_content">${app_name}, aşağıdaki nedenden dolayı güncel olması için önbelleği temizlemelidir:
\n %s
\n
\n Bu eylemin uygulamayı yeniden başlatacağını ve biraz zaman alabileceğini unutmayın.</string>
<string name="initial_sync_request_reason_unignored_users">- Bazı kullanıcılar göz ardı edildi</string>
<string name="a11y_expand_space_children">%s çocuğu genişlet</string>
<string name="time_unit_minute_short">dk.</string>
<string name="action_disable">Devre dışı bırak</string>
<string name="time_unit_hour_short">sa.</string>
<string name="change_space">Alanı Değiştir</string>
<string name="sign_out_anyway">Yine de oturumu kapat</string>
<plurals name="x_selected">
<item quantity="one">%1$d seçildi</item>
<item quantity="other">%1$d seçildi</item>
</plurals>
<string name="notice_display_name_changed_to">%1$s görünen adını %2$s olarak değiştirdi</string>
<string name="sign_out_failed_dialog_message">Ana sunucuya ulaşılamıyor. Yine de çıkış yaparsanız, bu cihaz, cihaz listenizden silinmez, başka bir istemci kullanarak kaldırmak isteyebilirsiniz.</string>
<plurals name="notice_room_server_acl_changes">
<item quantity="one">%d sunucu ACL\'si değişti</item>
<item quantity="other">%d sunucu ACL değişikliği</item>
</plurals>
<string name="time_unit_second_short">s.</string>
<string name="a11y_collapse_space_children">%s çocuğu daralt</string>
</resources>

View File

@@ -57,7 +57,7 @@
<string name="notice_room_name_changed_by_you">Ви змінили назву кімнати на: %1$s</string>
<string name="notice_room_avatar_changed_by_you">Ви змінили аватар кімнати</string>
<string name="notice_room_topic_changed_by_you">Ви змінили тему на: %1$s</string>
<string name="notice_display_name_changed_from_by_you">Ви змінили показуване ім\'я з %1$s на %2$s</string>
<string name="notice_display_name_changed_from_by_you">Ви змінили псевдонім з %1$s на %2$s</string>
<string name="notice_avatar_url_changed_by_you">Ви змінили свій аватар</string>
<plurals name="room_displayname_four_and_more_members">
<item quantity="one">%1$s, %2$s, %3$s та %4$d інший</item>
@@ -85,7 +85,7 @@
<string name="notice_placed_voice_call_by_you">Ви починаєте голосовий виклик.</string>
<string name="notice_placed_video_call_by_you">Ви починаєте відеовиклик.</string>
<string name="notice_room_avatar_changed">%1$s змінює аватар кімнати</string>
<string name="notice_display_name_removed_by_you">Ви прибрали показуване ім\'я (%1$s)</string>
<string name="notice_display_name_removed_by_you">Ви вилучили псевдонім (%1$s)</string>
<string name="notice_room_remove_by_you">Ви вилучили %1$s</string>
<string name="notice_direct_room_third_party_invite">%1$s запрошує %2$s</string>
<string name="notice_room_reject_by_you">Ви відхилили запрошення</string>
@@ -137,7 +137,7 @@
<string name="notice_answered_call_by_you">Ви відповіли на виклик.</string>
<string name="notice_call_candidates_by_you">Ви надіслали дані для налаштування виклику.</string>
<string name="notice_call_candidates">%s надсилає дані для налаштування виклику.</string>
<string name="notice_display_name_set_by_you">Ви встановили собі показуване ім\'я %1$s</string>
<string name="notice_display_name_set_by_you">Ви налаштували псевдонімом %1$s</string>
<string name="notice_room_withdraw_by_you">Ви відкликали запрошення для %1$s</string>
<string name="notice_room_ban_by_you">Ви заблокували %1$s</string>
<string name="notice_room_unban_by_you">Ви заблокували %1$s</string>
@@ -268,7 +268,7 @@
<string name="search_members_hint">Фільтр переліку користувачів</string>
<string name="search_no_results">Тут порожньо</string>
<string name="settings_profile_picture">Аватар</string>
<string name="settings_display_name">Показуване ім\'я</string>
<string name="settings_display_name">Псевдонім</string>
<string name="settings_add_email_address">Додати адресу е-пошти</string>
<string name="settings_add_phone_number">Додати номер телефону</string>
<string name="settings_app_info_link_summary">Екран системної інформації застосунку.</string>
@@ -276,7 +276,7 @@
<string name="settings_notification_ringtone">Тон сповіщення</string>
<string name="settings_enable_all_notif">Увімкнути сповіщення для цього облікового запису</string>
<string name="settings_enable_this_device">Увімкнути сповіщення для цього пристрою</string>
<string name="settings_containing_my_display_name">Повідомлення, що містять моє показуване ім\'я</string>
<string name="settings_containing_my_display_name">Повідомлення з моїм псевдонімом</string>
<string name="settings_containing_my_user_name">Повідомлення, що містять моє ім\'я користувача</string>
<string name="settings_messages_in_one_to_one">В особистих чатах</string>
<string name="settings_messages_in_group_chat">У групових чатах</string>
@@ -495,7 +495,7 @@
<string name="command_description_part_room">Вийти з кімнати</string>
<string name="command_description_topic">Встановити тему кімнати</string>
<string name="command_description_remove_user">Вилучити користувача із вказаним ID</string>
<string name="command_description_nick">Змінити Ваш псевдонім</string>
<string name="command_description_nick">Змінити псевдонім</string>
<string name="command_description_markdown">Увімкнути/Вимкнути розмітку Markdown</string>
<string name="command_description_clear_scalar_token">Для виправлення керування застосунками Matrix</string>
<string name="create">Створити</string>
@@ -816,7 +816,7 @@
<string name="room_widget_permission_theme">Ваша тема</string>
<string name="room_widget_permission_user_id">Ваш ідентифікатор користувача</string>
<string name="room_widget_permission_avatar_url">URL-адреса аватара</string>
<string name="room_widget_permission_display_name">Ваше показуване ім\'я</string>
<string name="room_widget_permission_display_name">Ваш псевдонім</string>
<string name="room_widget_revoke_access">Скасувати доступ для мене</string>
<string name="room_widget_open_in_browser">Відкрити у браузері</string>
<string name="room_widget_reload">Перезавантажити віджет</string>
@@ -1564,7 +1564,7 @@
<string name="settings_encrypted_group_messages">Зашифровані групові повідомлення</string>
<string name="settings_group_messages">Групові повідомлення</string>
<string name="settings_messages_containing_username">Моє користувацьке ім\'я</string>
<string name="settings_messages_containing_display_name">Моє показуване ім\'я</string>
<string name="settings_messages_containing_display_name">Мій псевдонім</string>
<string name="settings_messages_at_room">Повідомлення, які містять @room</string>
<string name="settings_when_rooms_are_upgraded">Коли кімнати оновлено</string>
<string name="settings_messages_in_e2e_group_chat">Зашифровані повідомлення в групових бесідах</string>
@@ -2060,7 +2060,7 @@
<string name="command_description_whois">Показує відомості про користувача</string>
<string name="command_description_avatar_for_room">Змінює ваш аватар лише у поточній кімнаті</string>
<string name="command_description_room_avatar">Змінює аватар поточної кімнати</string>
<string name="command_description_nick_for_room">Змінює ваше показуване ім\'я лише у поточній кімнаті</string>
<string name="command_description_nick_for_room">Змінює ваш псевдонім лише у поточній кімнаті</string>
<string name="command_description_room_name">Установлює назву кімнати</string>
<string name="settings_discovery_no_policy_provided">Сервер ідентифікації не надав жодних правил</string>
<string name="spaces_feeling_experimental_subspace">Бажаєте поекспериментувати\?
@@ -2408,7 +2408,7 @@
<string name="tooltip_attachment_photo">Відкрити камеру</string>
<string name="labs_auto_report_uisi_desc">Ваша система автоматично надсилатиме журнали, коли виникне помилка неможливості розшифрування</string>
<string name="labs_auto_report_uisi">Автозвіт про помилки шифрування.</string>
<string name="room_member_override_nick_color">Замінити колір показуваного імені</string>
<string name="room_member_override_nick_color">Замінити колір псевдоніма</string>
<string name="login_splash_already_have_account">Я вже маю обліковий запис</string>
<string name="ftue_auth_carousel_encrypted_title">Захищене спілкування.</string>
<string name="ftue_auth_carousel_control_title">Ви контролюєте все.</string>
@@ -2517,8 +2517,8 @@
<string name="ftue_profile_picture_subtitle">Час вказати ім’я</string>
<string name="ftue_profile_picture_title">Додати зображення профілю</string>
<string name="ftue_display_name_entry_footer">Ви можете змінити його пізніше</string>
<string name="ftue_display_name_entry_title">Показуване ім\'я</string>
<string name="ftue_display_name_title">Виберіть показуване ім\'я</string>
<string name="ftue_display_name_entry_title">Псевдонім</string>
<string name="ftue_display_name_title">Оберіть псевдонім</string>
<string name="ftue_account_created_subtitle">Ваш обліковий запис %s створений</string>
<string name="ftue_account_created_congratulations_title">Вітаємо!</string>
<string name="ftue_account_created_take_me_home">На головну</string>
@@ -2562,7 +2562,7 @@
\n
\nЗауважте, що ця дія перезапустить застосунок, і це може тривати деякий час.</string>
<string name="initial_sync_request_title">Початковий запит синхронізації</string>
<string name="settings_show_latest_profile_description">Показувати найновіші дані профілю (аватар і показуване ім\'я) для всіх повідомлень.</string>
<string name="settings_show_latest_profile_description">Показувати найновіші дані профілю (аватар і псевдонім) для всіх повідомлень.</string>
<string name="settings_show_latest_profile">Показувати найновіші дані користувача</string>
<string name="a11y_presence_busy">Зайнятий</string>
<string name="keys_backup_settings_signature_from_this_user">Резервна копія має дійсний підпис від цього користувача.</string>

Some files were not shown because too many files have changed in this diff Show More