mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
161 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4fa634a283 | ||
|
7001f21330 | ||
|
d379cef0ba | ||
|
59ddf1a107 | ||
|
a015eda72c | ||
|
87df8ab6f6 | ||
|
1bd2da5c99 | ||
|
2709cb2973 | ||
|
0d70f6eb54 | ||
|
42eec4b557 | ||
|
6ee438d7d5 | ||
|
3b9daec869 | ||
|
1b3be240b3 | ||
|
8c1cc44255 | ||
|
3f2f3860e1 | ||
|
470557c59e | ||
|
ff548d2f98 | ||
|
d31c741f9d | ||
|
ec9a066900 | ||
|
52a06931f4 | ||
|
a889d8d678 | ||
|
1f41c54a82 | ||
|
fe51ee3956 | ||
|
d65459cc59 | ||
|
dc8230e435 | ||
|
0838a10b65 | ||
|
a3be0286ee | ||
|
751bd27c9d | ||
|
0a6dbeb3fe | ||
|
bc23f82ade | ||
|
ca109f70a4 | ||
|
b5c224f3e0 | ||
|
e2c7833f93 | ||
|
6d5f59c67e | ||
|
e6bd57d88c | ||
|
81f7517560 | ||
|
1ceacdd194 | ||
|
52aa4bb0d8 | ||
|
a5d231c259 | ||
|
e6a18a2241 | ||
|
003a134f68 | ||
|
494e824a85 | ||
|
df97229b9c | ||
|
6e6478a949 | ||
|
de688aa93b | ||
|
1eee5c1de7 | ||
|
ce5d42d484 | ||
|
6379420401 | ||
|
9821487a8e | ||
|
2b29a57b9b | ||
|
87e5900dcd | ||
|
dc19380fbf | ||
|
880ed69f97 | ||
|
8941e6396c | ||
|
425441546e | ||
|
12395e9b04 | ||
|
8f6edba403 | ||
|
39a783196e | ||
|
92399aba07 | ||
|
83e2419c30 | ||
|
3216fa6146 | ||
|
eeb67e1934 | ||
|
23e7bdbae3 | ||
|
ad7934847c | ||
|
45be2749f6 | ||
|
754ea6a98d | ||
|
30906885ec | ||
|
5580f307be | ||
|
8885d14ee5 | ||
|
380a0b8de3 | ||
|
1bbd4b7e44 | ||
|
27bae30eac | ||
|
40fd9f2f7b | ||
|
10cde1f0a6 | ||
|
fd46487270 | ||
|
cd7bf12e16 | ||
|
95b63ccefb | ||
|
975ef3c06f | ||
|
e567b9c9cf | ||
|
9aeb3b7074 | ||
|
313d4f82f7 | ||
|
fd6a45a3ae | ||
|
f8138a7860 | ||
|
0dea54388c | ||
|
9776839b50 | ||
|
f48ed3679b | ||
|
d9a27b1514 | ||
|
9d19ca6ec2 | ||
|
657822891a | ||
|
c86b4f9e9f | ||
|
3fe71357dd | ||
|
14d691446a | ||
|
86a126b257 | ||
|
69b8e125e0 | ||
|
832b4680d2 | ||
|
b3e8a64ad5 | ||
|
37f62671f6 | ||
|
0c7fdae63a | ||
|
2ec4d1c98a | ||
|
9ce1034a5c | ||
|
2af1516ebd | ||
|
2a158996e6 | ||
|
180a2eec60 | ||
|
13596594a4 | ||
|
12a7506b57 | ||
|
23f0f6ada3 | ||
|
6ac5254ff3 | ||
|
d98ba3c08e | ||
|
70744b2dad | ||
|
e76063126b | ||
|
66f6cfcc6c | ||
|
ae52d4cd3c | ||
|
8433e222ad | ||
|
2a5df54ae4 | ||
|
8eccae44e5 | ||
|
841028774e | ||
|
c08b99a4f1 | ||
|
6fd589440d | ||
|
eef01ad8f9 | ||
|
99e171dbee | ||
|
2bbd5ee7d9 | ||
|
c3752f529a | ||
|
c1e77c6dc9 | ||
|
1ef2de0356 | ||
|
87f2a69fb1 | ||
|
fedbfe4931 | ||
|
85d0837f3b | ||
|
1b43087eb3 | ||
|
dab799e3cb | ||
|
0573915a0a | ||
|
1ab2bb9bf8 | ||
|
0c9ebfdab6 | ||
|
9a1f5fd1a0 | ||
|
bbcea97120 | ||
|
6fcd582f2d | ||
|
e4e17d865b | ||
|
0a4bdceff8 | ||
|
05ce2cab27 | ||
|
7080ee1c26 | ||
|
cf6de09483 | ||
|
0c7dcc767d | ||
|
2b4b5f05eb | ||
|
12bf1ea2eb | ||
|
7b8cf5d917 | ||
|
21200266e2 | ||
|
6e7078637f | ||
|
61b05edd9e | ||
|
7940584674 | ||
|
9b63293e45 | ||
|
2c57453efd | ||
|
ea424f29fb | ||
|
dc9e649703 | ||
|
e9f9decf00 | ||
|
69680a9856 | ||
|
e9b9434671 | ||
|
ff09ba1208 | ||
|
cd292488b6 | ||
|
1dd3c1589e | ||
|
3da1497d27 | ||
|
f304e40d57 | ||
|
28da02c583 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -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:
|
||||
|
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -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:
|
||||
|
2
.github/workflows/nightly_er.yml
vendored
2
.github/workflows/nightly_er.yml
vendored
@@ -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:
|
||||
|
@@ -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
|
||||
|
2
.github/workflows/tests-rust.yml
vendored
2
.github/workflows/tests-rust.yml
vendored
@@ -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:
|
||||
|
8
.github/workflows/triage-incoming.yml
vendored
8
.github/workflows/triage-incoming.yml
vendored
@@ -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 }}
|
||||
|
31
CHANGES.md
31
CHANGES.md
@@ -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)
|
||||
======================================
|
||||
|
||||
|
15
build.gradle
15
build.gradle
@@ -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 ->
|
||||
|
@@ -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',
|
||||
]
|
||||
]
|
||||
]
|
||||
|
2
fastlane/metadata/android/cs-CZ/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40106030.txt
Normal 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
|
2
fastlane/metadata/android/cs-CZ/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40106050.txt
Normal 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
|
2
fastlane/metadata/android/de-DE/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40106030.txt
Normal 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
|
2
fastlane/metadata/android/de-DE/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Die wichtigsten Änderungen in dieser Version: Fehlerbehebungen.
|
||||
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: corrective release.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40106060.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106060.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: mainly bug fixes.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40106030.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده میکند.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
تغغیرات عمده در این نگارش: ارائه تصحیحی.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
1
fastlane/metadata/android/hy/short_description.txt
Normal file
1
fastlane/metadata/android/hy/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Խմբային մեսինջեր - գաղտնագրված շփում, խմբային չատեր և վիդեո զանգեր
|
1
fastlane/metadata/android/hy/title.txt
Normal file
1
fastlane/metadata/android/hy/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Element - Անվտանգ Մեսինջեր
|
2
fastlane/metadata/android/id/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40106030.txt
Normal 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
|
2
fastlane/metadata/android/id/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Perubahan utama dalam versi ini: rilis perbaikan.
|
||||
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/pt-BR/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105160.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105180.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105200.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105200.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105220.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105220.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105240.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105240.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105250.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105250.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105260.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105260.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105280.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105280.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105300.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105300.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40105320.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40105320.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40106000.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40106010.txt
Normal 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
|
2
fastlane/metadata/android/pt-BR/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40106020.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40106030.txt
Normal 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
|
2
fastlane/metadata/android/sk/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Hlavné zmeny v tejto verzii: opravné vydanie.
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sq/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40106030.txt
Normal 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
|
2
fastlane/metadata/android/sq/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40106050.txt
Normal 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
|
2
fastlane/metadata/android/uk/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40106030.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Основні зміни в цій версії: Element для Android відтепер використовує Crypto Rust SDK.
|
||||
Список усіх змін: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/uk/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Основні зміни в цій версії: коригувальний випуск.
|
||||
Список усіх змін: https://github.com/vector-im/element-android/releases
|
@@ -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 có 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
|
||||
|
2
fastlane/metadata/android/zh-TW/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40106030.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
此版本的主要變更:現在起,Element Android 使用 Crypto Rust SDK。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/zh-TW/changelogs/40106050.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40106050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
此版本中的主要變動:版本修正。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
32
library/external/autocomplete/build.gradle
vendored
Normal file
32
library/external/autocomplete/build.gradle
vendored
Normal 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
|
||||
}
|
||||
}
|
434
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/Autocomplete.java
vendored
Normal file
434
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/Autocomplete.java
vendored
Normal 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) {}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
}
|
521
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/AutocompletePopup.java
vendored
Normal file
521
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/AutocompletePopup.java
vendored
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
184
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/CharPolicy.java
vendored
Normal file
184
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/CharPolicy.java
vendored
Normal 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)};
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
26
library/external/barcodescanner/core/build.gradle
vendored
Normal file
26
library/external/barcodescanner/core/build.gradle
vendored
Normal 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
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
312
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java
vendored
Normal file
312
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
63
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java
vendored
Normal file
63
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
23
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java
vendored
Normal file
23
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
41
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java
vendored
Normal file
41
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
53
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java
vendored
Normal file
53
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java
vendored
Normal 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();
|
||||
}
|
259
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java
vendored
Normal file
259
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
|
5
library/external/barcodescanner/core/src/main/res/values-hdpi/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values-hdpi/integers.xml
vendored
Normal 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>
|
5
library/external/barcodescanner/core/src/main/res/values-xhdpi/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values-xhdpi/integers.xml
vendored
Normal 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>
|
5
library/external/barcodescanner/core/src/main/res/values-xxhdpi/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values-xxhdpi/integers.xml
vendored
Normal 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>
|
17
library/external/barcodescanner/core/src/main/res/values/attrs.xml
vendored
Normal file
17
library/external/barcodescanner/core/src/main/res/values/attrs.xml
vendored
Normal 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>
|
6
library/external/barcodescanner/core/src/main/res/values/colors.xml
vendored
Normal file
6
library/external/barcodescanner/core/src/main/res/values/colors.xml
vendored
Normal 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>
|
5
library/external/barcodescanner/core/src/main/res/values/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values/integers.xml
vendored
Normal 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>
|
29
library/external/barcodescanner/zxing/build.gradle
vendored
Normal file
29
library/external/barcodescanner/zxing/build.gradle
vendored
Normal 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
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
4
library/external/jsonviewer/build.gradle
vendored
4
library/external/jsonviewer/build.gradle
vendored
@@ -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
|
||||
|
23
library/external/realmfieldnameshelper/build.gradle
vendored
Normal file
23
library/external/realmfieldnameshelper/build.gradle
vendored
Normal 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'
|
||||
}
|
||||
|
24
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/ClassData.kt
vendored
Normal file
24
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/ClassData.kt
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 "<class>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)
|
||||
}
|
||||
}
|
77
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/FileGenerator.kt
vendored
Normal file
77
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/FileGenerator.kt
vendored
Normal 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 "<class>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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
dk.ilios.realmfieldnames.RealmFieldNamesProcessor,aggregating
|
@@ -0,0 +1 @@
|
||||
dk.ilios.realmfieldnames.RealmFieldNamesProcessor
|
20
library/external/span/build.gradle
vendored
Normal file
20
library/external/span/build.gradle
vendored
Normal 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'
|
||||
}
|
316
library/external/span/src/main/kotlin/me/gujun/android/span/Span.kt
vendored
Normal file
316
library/external/span/src/main/kotlin/me/gujun/android/span/Span.kt
vendored
Normal 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)
|
||||
}
|
36
library/external/span/src/main/kotlin/me/gujun/android/span/style/CustomTypefaceSpan.kt
vendored
Normal file
36
library/external/span/src/main/kotlin/me/gujun/android/span/style/CustomTypefaceSpan.kt
vendored
Normal 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
|
||||
}
|
||||
}
|
31
library/external/span/src/main/kotlin/me/gujun/android/span/style/LineSpacingSpan.kt
vendored
Normal file
31
library/external/span/src/main/kotlin/me/gujun/android/span/style/LineSpacingSpan.kt
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
10
library/external/span/src/main/kotlin/me/gujun/android/span/style/SimpleClickableSpan.kt
vendored
Normal file
10
library/external/span/src/main/kotlin/me/gujun/android/span/style/SimpleClickableSpan.kt
vendored
Normal 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
|
||||
}
|
||||
}
|
29
library/external/span/src/main/kotlin/me/gujun/android/span/style/TextDecorationLineSpan.kt
vendored
Normal file
29
library/external/span/src/main/kotlin/me/gujun/android/span/style/TextDecorationLineSpan.kt
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
41
library/external/span/src/main/kotlin/me/gujun/android/span/style/VerticalPaddingSpan.kt
vendored
Normal file
41
library/external/span/src/main/kotlin/me/gujun/android/span/style/VerticalPaddingSpan.kt
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
25
library/external/textdrawable/build.gradle
vendored
Normal file
25
library/external/textdrawable/build.gradle
vendored
Normal 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
|
||||
}
|
||||
}
|
316
library/external/textdrawable/src/main/java/com/amulyakhare/textdrawable/TextDrawable.java
vendored
Normal file
316
library/external/textdrawable/src/main/java/com/amulyakhare/textdrawable/TextDrawable.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
@@ -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>
|
@@ -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>
|
@@ -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 & %2$s & άλλοι γράφουν…</string>
|
||||
<string name="room_one_user_is_typing">%s γράφει…</string>
|
||||
<string name="room_two_users_are_typing">%1$s & %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>
|
@@ -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
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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">S’kapet dot shërbyesi Home. Nëse keni dalë, sido qoftë, kjo pajisje s’do 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">S’arrihet 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">S’arrihet 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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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
Reference in New Issue
Block a user