mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
329 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
f5f4d4a326 | ||
|
637b1483cc | ||
|
042f144afe | ||
|
8cd51ea803 | ||
|
6e7078637f | ||
|
61b05edd9e | ||
|
218799e025 | ||
|
0f581dc391 | ||
|
75fd62aef9 | ||
|
3c1c645806 | ||
|
7205cd73d4 | ||
|
ac1ee734f4 | ||
|
51c4dfc675 | ||
|
5f20ceeb1c | ||
|
3e73137c18 | ||
|
dfadc8eca1 | ||
|
7940584674 | ||
|
9b63293e45 | ||
|
2c57453efd | ||
|
ea424f29fb | ||
|
dc9e649703 | ||
|
e9f9decf00 | ||
|
69680a9856 | ||
|
e9b9434671 | ||
|
ff09ba1208 | ||
|
cd292488b6 | ||
|
1dd3c1589e | ||
|
3da1497d27 | ||
|
f304e40d57 | ||
|
93d3c73306 | ||
|
6ef9557698 | ||
|
08ae1105ff | ||
|
16f5d48120 | ||
|
a065cd338c | ||
|
cfae6e9e51 | ||
|
e988308dc6 | ||
|
ff0873f5e8 | ||
|
3170d07f9b | ||
|
90e752472f | ||
|
eeec549bd0 | ||
|
107f51da0d | ||
|
9d239bf94d | ||
|
3c645ef1bb | ||
|
f5ac1f120a | ||
|
e8e8e7c5bc | ||
|
7d4af8d059 | ||
|
c1887aaa9f | ||
|
38ba1cbbe9 | ||
|
cd5737276c | ||
|
4af2f700f7 | ||
|
710d21f6a4 | ||
|
7497cf6729 | ||
|
572bdb6dfe | ||
|
5e60c97566 | ||
|
ce80d7ff2f | ||
|
b0558a300e | ||
|
257072330f | ||
|
1d651db82b | ||
|
6fe0002bd3 | ||
|
f5764372c2 | ||
|
bbc1ed9e62 | ||
|
d30c6018e4 | ||
|
cb64175c2b | ||
|
3b887fdf4e | ||
|
36b97b1647 | ||
|
cfa0f95799 | ||
|
f3db4a857a | ||
|
ba3e7f63ff | ||
|
8909c6027d | ||
|
48df1136ca | ||
|
871f054486 | ||
|
7d084f18a0 | ||
|
69ecdba175 | ||
|
38330a7b67 | ||
|
0e7ca50588 | ||
|
2a7b92d48f | ||
|
0ca5caee85 | ||
|
4903c24661 | ||
|
5e00d474d9 | ||
|
33ef138d4b | ||
|
b840ff1d25 | ||
|
e6bf8a981d | ||
|
836fc71ebc | ||
|
4d291a4f43 | ||
|
0f18cdb7d2 | ||
|
a311b21d7c | ||
|
6b2331dcbf | ||
|
2d21d3f6ce | ||
|
45d75ecc6d | ||
|
db993fc3d6 | ||
|
ec9c3fa6f7 | ||
|
6e300872ac | ||
|
8cd716b87e | ||
|
961ea9a8e0 | ||
|
ebb05484a5 | ||
|
fabde93481 | ||
|
b5f1941ae4 | ||
|
bb5a65cb60 | ||
|
93a93c5295 | ||
|
acdaec42c3 | ||
|
e35ec86930 | ||
|
65175106da | ||
|
551d559be1 | ||
|
a8d8176d97 | ||
|
f7dd492adc | ||
|
8c63e872b9 | ||
|
e4bff75557 | ||
|
cc08bfd500 | ||
|
06d30c3df2 | ||
|
a70fba9f6a | ||
|
07e06957ef | ||
|
824f3802b2 | ||
|
97eb2330c6 | ||
|
47da9c4534 | ||
|
6c353d96a5 | ||
|
840ca30d8a | ||
|
9007fafdf2 | ||
|
4965be9961 | ||
|
29d1d5e4b7 | ||
|
bb703f3935 | ||
|
5a74572209 | ||
|
3c08fb637a | ||
|
9f797e49e9 | ||
|
8157ec7746 | ||
|
632f316f8e | ||
|
1e4f47bc52 | ||
|
56ea316a22 | ||
|
91f507e6c4 | ||
|
7f8a19f194 | ||
|
ada8539898 | ||
|
688ae7d259 | ||
|
be8f226948 | ||
|
99b6c887d2 | ||
|
1eda087233 | ||
|
2d68c06698 | ||
|
0965050ec9 | ||
|
e976ddde34 | ||
|
61d4e468cb | ||
|
2f1a7b76ad | ||
|
f741c4e7d9 | ||
|
cb0fb63fe6 | ||
|
b96140f668 | ||
|
4371ba48cb | ||
|
ae4c3b078b | ||
|
957e89bbfd | ||
|
f92f87ce08 | ||
|
3292a07d3a | ||
|
7e6376bf90 | ||
|
8c1938987b | ||
|
dc969f502a | ||
|
2d335c1307 | ||
|
b59068b463 | ||
|
268cbb83cd | ||
|
7e1659b1f9 | ||
|
83795344ed | ||
|
d1fa9050ca | ||
|
9409f47bcb | ||
|
c53ec382e5 | ||
|
f5eb78612f | ||
|
ba15aa11f2 | ||
|
d7b621f9d8 | ||
|
09580af844 | ||
|
d1f82ab2b9 | ||
|
9c9fecc97d | ||
|
0736c9ede5 | ||
|
ac705151e9 | ||
|
b37eca4520 | ||
|
24b18847b5 | ||
|
ac916078bc | ||
|
41b803980c | ||
|
0316582537 | ||
|
e7584d37bb | ||
|
36a2e6e11c | ||
|
64c47a6e6c | ||
|
c039d62e5e | ||
|
8ecd03d584 | ||
|
72956bda64 | ||
|
1fd2f78f4d | ||
|
ac235fa1db | ||
|
13bdd10a69 | ||
|
882fb58a7d | ||
|
9481221140 | ||
|
37429c277b | ||
|
a2ef95584f | ||
|
3bf5c0cc1b | ||
|
8f69e411d7 | ||
|
e961a8ccb5 | ||
|
b9cc1b6dab | ||
|
93e2d7d176 | ||
|
f261a903f0 | ||
|
82748591f9 | ||
|
997c9dd917 | ||
|
cff9fbd008 | ||
|
43692c1da8 | ||
|
287bff473d | ||
|
d26d7f124b | ||
|
390377480c | ||
|
7cff46d271 | ||
|
1058f4b91d | ||
|
8eee238220 | ||
|
b1b784de9f | ||
|
8447e06b21 | ||
|
580f979c05 | ||
|
cffd6e80d0 | ||
|
d6302d3b9b | ||
|
d78d07b42a | ||
|
7e748f28e8 | ||
|
900828f57d | ||
|
d5794441d6 | ||
|
09c70c99fa | ||
|
0391f77623 | ||
|
3848982c07 | ||
|
d409a33f92 | ||
|
671f846f53 | ||
|
17a3ea3148 | ||
|
baccb84efb | ||
|
36db0ac8d6 | ||
|
1996df2f93 | ||
|
8fc5d73f75 | ||
|
541690605f | ||
|
19ba773df9 | ||
|
72b2b591e3 | ||
|
0edea016b1 | ||
|
bf244f6805 | ||
|
d5d5c9009b | ||
|
e4e4c2940b | ||
|
6da05a3804 | ||
|
9f3255dd78 | ||
|
bb59a758cf | ||
|
591b08f1ff | ||
|
cee6ec5939 | ||
|
df1641995e | ||
|
27f9be5eda | ||
|
19dc812719 | ||
|
bb09bee641 | ||
|
2ea6cdba6f | ||
|
b98d6ca55b | ||
|
ccf77eb05a | ||
|
94956a2ec6 | ||
|
517af968e6 | ||
|
705b17e7f1 | ||
|
7ccbaf9ec8 | ||
|
0e9b62a43a | ||
|
3e8eeac533 | ||
|
d5c98414a4 | ||
|
16f8f01cdb | ||
|
6412c2a7ce | ||
|
d79df5a0cb | ||
|
872fef6d22 | ||
|
fe37d63149 | ||
|
c98598eed1 | ||
|
a3bbe0bd8c | ||
|
a7130f63ea | ||
|
1659675d3b | ||
|
6752c69c22 | ||
|
3157a35b74 | ||
|
24614bbbae | ||
|
29d8845792 | ||
|
2d1dcd34c0 | ||
|
aecdd475d8 | ||
|
66aa4226b5 | ||
|
4ca67022e6 | ||
|
6e27ffc3cf | ||
|
d6028b75c9 | ||
|
dadad3501b |
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
@@ -13,7 +13,7 @@ updates:
|
||||
reviewers:
|
||||
- "vector-im/element-android-reviewers"
|
||||
ignore:
|
||||
- dependency-name: "*github-script*"
|
||||
- dependency-name: "*"
|
||||
# Updates for Gradle dependencies used in the app
|
||||
- package-ecosystem: gradle
|
||||
directory: "/"
|
||||
@@ -22,5 +22,6 @@ updates:
|
||||
open-pull-requests-limit: 200
|
||||
reviewers:
|
||||
- "vector-im/element-android-reviewers"
|
||||
ignore:
|
||||
- dependency-name: com.google.zxing:core
|
||||
allow:
|
||||
- dependency-name: "io.element.android:wysiwyg"
|
||||
- dependency-name: "org.matrix.rustcomponents:crypto-android"
|
||||
|
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 }}
|
||||
|
73
CHANGES.md
73
CHANGES.md
@@ -1,3 +1,76 @@
|
||||
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)
|
||||
======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- **Element Android is now using the Crypto Rust SDK**. Migration of user's data should be done at first launch after application upgrade. ([#8390](https://github.com/vector-im/element-android/issues/8390))
|
||||
- [Rich text editor] Add mentions and slash commands ([#8440](https://github.com/vector-im/element-android/issues/8440))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Update rich text editor library to support pasting of images. ([#8270](https://github.com/vector-im/element-android/issues/8270))
|
||||
- Fix | Got asked twice about verification #8353 (and other verification banners problems) ([#8353](https://github.com/vector-im/element-android/issues/8353))
|
||||
- Prompt the user when the invited MatrixId is not recognized ([#8468](https://github.com/vector-im/element-android/issues/8468))
|
||||
- The correct title and options are now displayed When a poll that was edited is ended. ([#8471](https://github.com/vector-im/element-android/issues/8471))
|
||||
- In some conditions the room shield is not refreshed correctly ([#8507](https://github.com/vector-im/element-android/issues/8507))
|
||||
- Fix crypto config fallback key sharing strategy ([#8541](https://github.com/vector-im/element-android/issues/8541))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- MSC3987 implementation: the 'dont_notify' action for a push_rule is now deprecated and replaced by an empty action list. ([#8503](https://github.com/vector-im/element-android/issues/8503))
|
||||
- Update crypto rust sdk version to 0.3.10 ([#8554](https://github.com/vector-im/element-android/issues/8554))
|
||||
|
||||
|
||||
Changes in Element v1.6.2 (2023-06-02)
|
||||
======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- **Element Android is now using the Crypto Rust SDK**. Migration of user's data should be done at first launch after application upgrade. ([#8390](https://github.com/vector-im/element-android/issues/8390))
|
||||
- Marks WebP files as Animated and allows them to play ([#8120](https://github.com/vector-im/element-android/issues/8120))
|
||||
- Updates to protocol used for Sign in with QR code ([#8299](https://github.com/vector-im/element-android/issues/8299))
|
||||
- Updated rust crypto SDK to version 0.3.9 ([#8488](https://github.com/vector-im/element-android/issues/8488))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Fix: Allow users to sign out even if the sign out request fails. ([#4855](https://github.com/vector-im/element-android/issues/4855))
|
||||
- fix: Make some crypto calls suspendable to avoid reported ANR ([#8482](https://github.com/vector-im/element-android/issues/8482))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Refactoring: Extract a new interface for common access to crypto store between kotlin and rust crypto ([#8470](https://github.com/vector-im/element-android/issues/8470))
|
||||
|
||||
|
||||
Changes in Element v1.6.1 (2023-05-25)
|
||||
======================================
|
||||
|
||||
Corrective release for 1.6.0
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Allow stateloss on verification dialogfragment ([#8439](https://github.com/vector-im/element-android/issues/8439))
|
||||
- Fix: Update verification popup text when a re-verification is needed after rust migration (read only sessions) ([#8445](https://github.com/vector-im/element-android/issues/8445))
|
||||
- Fix several performance issues causing app non responsive issues. ([#8454](https://github.com/vector-im/element-android/issues/8454))
|
||||
- Fix: The device list screen from the member profile page was always showing the current user devices (rust crypto). ([#8457](https://github.com/vector-im/element-android/issues/8457))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Remove UI option to manually verify a specific device of another user (deprecated behaviour) ([#8458](https://github.com/vector-im/element-android/issues/8458))
|
||||
|
||||
|
||||
Changes in Element v1.6.0 (2023-05-17)
|
||||
======================================
|
||||
|
||||
|
13
build.gradle
13
build.gradle
@@ -28,7 +28,7 @@ buildscript {
|
||||
classpath 'com.google.gms:google-services:4.3.15'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:4.0.0.2929'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
|
||||
classpath "com.likethesalad.android:stem-plugin:2.4.0"
|
||||
classpath "com.likethesalad.android:stem-plugin:2.4.1"
|
||||
classpath 'org.owasp:dependency-check-gradle:8.2.1'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10"
|
||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||
@@ -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 {
|
||||
|
@@ -47,7 +47,7 @@ ext.libs = [
|
||||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||
],
|
||||
androidx : [
|
||||
'activity' : "androidx.activity:activity-ktx:1.7.1",
|
||||
'activity' : "androidx.activity:activity-ktx:1.7.2",
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.6.1",
|
||||
'biometric' : "androidx.biometric:biometric:1.1.0",
|
||||
'core' : "androidx.core:core-ktx:1.10.1",
|
||||
@@ -101,7 +101,7 @@ ext.libs = [
|
||||
],
|
||||
element : [
|
||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||
'wysiwyg' : "io.element.android:wysiwyg:1.2.2"
|
||||
'wysiwyg' : "io.element.android:wysiwyg:2.2.2"
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||
@@ -172,6 +172,7 @@ ext.libs = [
|
||||
'kluent' : "org.amshove.kluent:kluent-android:1.73",
|
||||
'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1",
|
||||
'junit' : "junit:junit:4.13.2",
|
||||
'robolectric' : "org.robolectric:robolectric:4.9",
|
||||
]
|
||||
]
|
||||
|
||||
|
@@ -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',
|
||||
@@ -189,6 +190,7 @@ ext.groups = [
|
||||
'org.codehaus.groovy',
|
||||
'org.codehaus.mojo',
|
||||
'org.codehaus.woodstox',
|
||||
'org.conscrypt',
|
||||
'org.eclipse.ee4j',
|
||||
'org.ec4j.core',
|
||||
'org.freemarker',
|
||||
@@ -221,6 +223,7 @@ ext.groups = [
|
||||
'org.ow2.asm',
|
||||
'org.ow2.asm',
|
||||
'org.reactivestreams',
|
||||
'org.robolectric',
|
||||
'org.slf4j',
|
||||
'org.sonatype.oss',
|
||||
'org.testng',
|
||||
@@ -232,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/40106000.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40106000.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/40106010.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40106010.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/40106020.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40106020.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/de-DE/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40106000.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/40106010.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40106010.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/40106020.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40106020.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/en-US/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: Element Android is now using the Crypto Rust SDK.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: Element Android is now using the Crypto Rust SDK.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/en-US/changelogs/40106030.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106030.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: Element Android is now using the Crypto Rust SDK.
|
||||
Full changelog: 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/et/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Põhilised muutused selles versioonis: Element Android krüptoteekideks on nüüd Crypto Rust SDK.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Põhilised muutused selles versioonis: Element Android krüptoteekideks on nüüd Crypto Rust SDK.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Põhilised muutused selles versioonis: Element Android krüptoteekideks on nüüd Crypto Rust SDK.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده میکند.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده میکند.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: المنت اندروید اکنون از SDK راست Crypto استفاده میکند.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : Element Android utilise désormais le SDK cryptographique en Rust.
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : Element Android utilise désormais le SDK cryptographique en Rust.
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : Element Android utilise désormais le SDK cryptographique en Rust.
|
||||
Intégralité des changements : 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/40106000.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40106000.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/40106010.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40106010.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/40106020.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40106020.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/it-IT/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Modifiche principali in questa versione: Element Android ora utilizza l'SDK Rust Crypto.
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/it-IT/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Modifiche principali in questa versione: Element Android ora utilizza l'SDK Rust Crypto.
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/it-IT/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Modifiche principali in questa versione: Element Android ora utilizza l'SDK Rust Crypto.
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases
|
@@ -1 +1 @@
|
||||
Sikker desentralisert chat & VoIP. Beskytt dataene dine fra tredjeparter.
|
||||
Gruppe-meldingsapp - krypterte meldinger, gruppechat og videosamtaler
|
||||
|
@@ -1 +1 @@
|
||||
Element (tidligere Riot.im)
|
||||
Element - Sikker Meldingsapp
|
||||
|
2
fastlane/metadata/android/pl-PL/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/pl-PL/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Główne zmiany w tej wersji: Element Android teraz korzysta z Crypto Rust SDK.
|
||||
Pełna lista zmian: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/pl-PL/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/pl-PL/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Główne zmiany w tej wersji: Element Android teraz korzysta z Crypto Rust SDK.
|
||||
Pełna lista zmian: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/pl-PL/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/pl-PL/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Główne zmiany w tej wersji: Element Android teraz korzysta z Crypto Rust SDK.
|
||||
Pełna lista zmian: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/ru-RU/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Главные изменения этой версии: Element для Android теперь использует Crypto Rust SDK.
|
||||
Полный список изменений: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sk/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40106000.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/40106010.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40106010.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/40106020.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40106020.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/sq/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Ndryshimet kryesore në këtë version: Element Android tani përdor Crypto Rust SDK.
|
||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sq/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Ndryshimet kryesore në këtë version: Element Android tanimë përdor SDK Rust Fshehtëzimesh.
|
||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sq/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Ndryshimet kryesore në këtë version: Element Android tanimë përdor SDK Rust për Kripto.
|
||||
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sv-SE/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Huvudsakliga ändringar i den här versionen: Element Android använder nu Rust-krypto-SDK:t
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sv-SE/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Huvudsakliga ändringar i den här versionen: Element Android använder nu Rust-krypto-SDK:t
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sv-SE/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Huvudsakliga ändringar i den här versionen: Element Android använder nu Rust-krypto-SDK:t
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/uk/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40106000.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/40106010.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40106010.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/40106020.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Основні зміни в цій версії: Element Android тепер використовує Crypto Rust SDK.
|
||||
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
@@ -1,2 +1,2 @@
|
||||
Những thay đổi chính trong phiên bản này: Thêm hỗ trợ Android Auto. Sửa rất nhiều lỗi!
|
||||
Log thay đổi đầy đủ: https://github.com/vector-im/element-android/releases/tag/v1.3.2
|
||||
Thay đổi chính trong phiên bản này: Hỗ trợ Android Auto. Sửa rất nhiều lỗi!
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.2
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Những thay đổi chính trong phiên bản này: Hiển thị (các) chính sách máy chủ xác thực trong phần cài đặt. Tạm thời bỏ hỗ trợ Android Auto.
|
||||
Log thay đổi đầy đủ: https://github.com/vector-im/element-android/releases/tag/v1.3.3
|
||||
Thay đổi chính trong phiên bản này: Hiển thị (các) chính sách máy chủ định danh trong phần cài đặt. Tạm thời bỏ hỗ trợ Android Auto.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.3
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Những thay đổi chính trong phiên bản này: Thêm hỗ trợ hiển thị, cho phòng Tin nhắn Trực tiếp (lưu ý: hiển thị bị vô hiệu hóa trên matrix.org. Hỗ trợ Android Auto trở lại.
|
||||
Nhật ký thay đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
||||
Thay đổi chính trong phiên bản này: Hỗ trợ trạng thái, cho các phòng nhắn tin trực tiếp (ghi chú: trạng thái bị vô hiệu trên matrix.org). Hỗ trợ Android Auto trở lại.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.4
|
||||
|
2
fastlane/metadata/android/vi/changelogs/40103050.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40103050.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Hỗ trợ trạng thái, cho các phòng nhắn tin trực tiếp (ghi chú: trạng thái bị vô hiệu trên matrix.org). Hỗ trợ Android Auto trở lại.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.5
|
2
fastlane/metadata/android/vi/changelogs/40103060.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40103060.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Hỗ trợ trạng thái, cho các phòng nhắn tin trực tiếp (ghi chú: trạng thái bị vô hiệu trên matrix.org). Hỗ trợ Android Auto trở lại.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases/tag/v1.3.6
|
2
fastlane/metadata/android/vi/changelogs/40105080.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40105080.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Sửa lỗi và cải thiện.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/vi/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40105100.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Triển khai chế độ toàn màn hình mới cho trình soạn thảo văn bản phong phú và sửa lỗi.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/vi/changelogs/40105110.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40105110.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Triển khai chế độ toàn màn hình mới cho trình soạn thảo văn bản phong phú và sửa lỗi.
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chức năng chủ đề được bật theo mặc định.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Hầu hết là sửa lỗi.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Hầu hết là cải thiện chức năng phát thanh.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Hầu hết là sửa lỗi, cụ thể là sửa lỗi khiến cho tin nhắn không xuất hiện trên dòng thời gian.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Hầu hết là sửa lỗi, cụ thể là sửa lỗi khiến cho tin nhắn không xuất hiện trên dòng thời gian.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chủ yếu là sửa lỗi.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chủ yếu là sửa lỗi.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Liên kết cố định tới các phòng, spaces, người dùng và tin nhắn giờ được hiển thị hình viên thuốc. Chúng tôi cũng đã sửa một số vấn đề với những nhãn dãn (sticker) tùy chỉnh và thanh đánh dấu đã đọc bị kẹt ở quá khứ.
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
@@ -1,2 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Chủ yếu là sửa lỗi
|
||||
Toàn bộ nhật ký thay đổi: https://github.com/vector-im/element-android/releases
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
||||
|
2
fastlane/metadata/android/vi/changelogs/40106000.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40106000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Element Android giờ dùng công cụ phát triển phần mềm Rust cho mã hóa
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/vi/changelogs/40106010.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40106010.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Element Android giờ dùng công cụ phát triển phần mềm Rust cho mã hóa
|
||||
Toàn bộ nhật ký sửa đổi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/vi/changelogs/40106020.txt
Normal file
2
fastlane/metadata/android/vi/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Thay đổi chính trong phiên bản này: Element Android giờ dùng công cụ phát triển phần mềm Rust cho mã hóa
|
||||
Toàn bộ nhật ký sửa đổi: 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/40106000.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40106000.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/40106010.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40106010.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/40106020.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40106020.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
此版本的主要變更:現在起,Element Android 使用 Crypto Rust SDK。
|
||||
完整異動: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>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user