Compare commits
244 commits
feature_ca
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
6cdd7ce4f7 | ||
|
38ca9d7911 | ||
|
5a19e83a3c | ||
|
4088486344 | ||
|
714ffd0ea1 | ||
|
0e415479f7 | ||
|
3339d318fe | ||
|
2a8b39ce6b | ||
|
105b3195a2 | ||
|
f0485d7865 | ||
|
6c5298b071 | ||
|
5fb07e0508 | ||
|
2831f60c30 | ||
|
b98155e48b | ||
|
a61bae906e | ||
|
96cf096f89 | ||
|
1b6353d470 | ||
|
dd187ab69f | ||
|
a6f7d4699f | ||
|
19372eaa27 | ||
|
c3a5912ff0 | ||
|
85a4501d12 | ||
|
b5052184b2 | ||
|
ba931c787f | ||
|
4e3da926f6 | ||
|
5a6c43fe76 | ||
|
af4c42033c | ||
|
7fc5c58d98 | ||
|
bf6c0226ff | ||
|
0b4aad772b | ||
|
4e62614ef9 | ||
|
1da68a5706 | ||
|
ec80dced3d | ||
|
dcb8fb6eff | ||
|
b47efaf943 | ||
|
a759829497 | ||
|
a7b1acb57b | ||
|
9d6025546f | ||
|
ed999afcd4 | ||
|
5f6b6b2e6d | ||
|
a10ea1330f | ||
|
977c341f0e | ||
|
8a55eafff2 | ||
|
4af1c28b1c | ||
|
0d206f62bc | ||
|
38f0706855 | ||
|
5865182001 | ||
|
b4b3eca448 | ||
|
16b4b05db0 | ||
|
f3738027b2 | ||
|
24f6344663 | ||
|
5b1fb49f60 | ||
|
ac6c67ae0e | ||
|
2b44c6d831 | ||
|
fa45294f21 | ||
|
95232ec455 | ||
|
4374e819d7 | ||
|
ae92eeab70 | ||
|
dfdc8e6c09 | ||
|
7cb399d72c | ||
|
97d3fd8f40 | ||
|
532ede4b17 | ||
|
c3c3ae1951 | ||
|
d624383d91 | ||
|
e853941de9 | ||
|
b3eeda7cff | ||
|
d6d276e349 | ||
|
48d2a945cb | ||
|
8fa742aeab | ||
|
ae92d78443 | ||
|
8e636ee501 | ||
|
1ec8d120a0 | ||
|
98dc2ae8ef | ||
|
1ec753222d | ||
|
488d1be731 | ||
|
c7460138c4 | ||
|
ab41c587ab | ||
|
4efab4213e | ||
|
e187c5999a | ||
|
6ad1049351 | ||
|
b9082a97cd | ||
|
e3a93a8010 | ||
|
c0196680a0 | ||
|
1c8c5a254f | ||
|
ac611db5f8 | ||
|
1a8360417c | ||
|
1a7b8630fb | ||
|
a4e710d804 | ||
|
15d8514b6c | ||
|
7a7abe61c2 | ||
|
9a7a07e7d4 | ||
|
04b745705d | ||
|
16bcb956db | ||
|
6f78caac2a | ||
|
d7c2558980 | ||
|
61038ec0e5 | ||
|
9deb2402e9 | ||
|
3cbb0af0c6 | ||
|
a6baee2ca2 | ||
|
b95ff6d63a | ||
|
f6f6d105d0 | ||
|
dd5eeebc3f | ||
|
acb8f345e2 | ||
|
9bd99a8f73 | ||
|
a553a34cca | ||
|
b71e8ca747 | ||
|
c1850d10c9 | ||
|
536fd4faf1 | ||
|
dab0d7e48f | ||
|
ad2c4ffa25 | ||
|
6f6de55235 | ||
|
9d2300bd0a | ||
|
bfcbe4446e | ||
|
bf38f5f289 | ||
|
1f86ea002f | ||
|
6ceb648bf4 | ||
|
ea1d39b704 | ||
|
7ddf457732 | ||
|
85a97f0dcf | ||
|
99fb620a5b | ||
|
ad752cc251 | ||
|
1d2a413426 | ||
|
fcd9ba26c2 | ||
|
9dbc837430 | ||
|
64ec30d29b | ||
|
9756e696b3 | ||
|
6830623480 | ||
|
a3d8bbc572 | ||
|
eac62d8bd8 | ||
|
9923af6819 | ||
|
7aba6e61de | ||
|
d26478499c | ||
|
e049f2721e | ||
|
492232f22d | ||
|
8596289d9f | ||
|
2d50ff49b8 | ||
|
dede322da3 | ||
|
d39c0d1cbc | ||
|
346d71f434 | ||
|
e51369af86 | ||
|
732b86ebca | ||
|
d0be7e6815 | ||
|
e01ebf5230 | ||
|
4c06188b36 | ||
|
1066dd34d5 | ||
|
328bd4bd4b | ||
|
6f37e4d78f | ||
|
9729d9da63 | ||
|
f6fdf504c9 | ||
|
9eb7362b3a | ||
|
1ffa7fd49d | ||
|
0ae2b1712e | ||
|
63a06fcad0 | ||
|
173596098e | ||
|
6049192d2f | ||
|
4678da2a10 | ||
|
92bcc806e4 | ||
|
d7e25ad1fd | ||
|
7d5babafef | ||
|
5ddc55ac40 | ||
|
d5b326dab9 | ||
|
d89b7f8157 | ||
|
0e76e775fa | ||
|
55b8124853 | ||
|
4a6db0f8b3 | ||
|
e02af2ef81 | ||
|
84ecb71b31 | ||
|
9e73a8d6d9 | ||
|
908181487e | ||
|
ddbaff13dc | ||
|
57631eb18e | ||
|
99aa0df767 | ||
|
8002130a38 | ||
|
08359f6bc7 | ||
|
dcdca77fc6 | ||
|
bbfc09ba1d | ||
|
d5fbdc8b80 | ||
|
a7eb22ce2c | ||
|
3e08791324 | ||
|
c3d27e4eb1 | ||
|
9d180cf455 | ||
|
c5f32b275d | ||
|
704503b9f5 | ||
|
b2727019c5 | ||
|
954f745a0c | ||
|
65ee6ca578 | ||
|
70391dd8fe | ||
|
d5b30fe405 | ||
|
db0da8a178 | ||
|
be904d4848 | ||
|
e686a338c3 | ||
|
6e00aaeb55 | ||
|
c286a6837a | ||
|
417d982115 | ||
|
b92ae21903 | ||
|
553115fce1 | ||
|
a947a296ec | ||
|
6405ff1015 | ||
|
0d766598ab | ||
|
b9ada8fde3 | ||
|
174eaef9dd | ||
|
2eae832bc0 | ||
|
601f16eca1 | ||
|
00e740da91 | ||
|
6ad554616b | ||
|
74fe5540a4 | ||
|
923545fd06 | ||
|
b3c29e2b8a | ||
|
5ca84140bd | ||
|
8b020075e0 | ||
|
b57b51f609 | ||
|
d4dee029de | ||
|
90e1c75580 | ||
|
1207e95f43 | ||
|
ac8a7339cd | ||
|
b9ca1ce816 | ||
|
c831d5cb3b | ||
|
91cc5b7ff9 | ||
|
c6185b29db | ||
|
222c2df562 | ||
|
61c40524cb | ||
|
f4f74566ef | ||
|
01622c9643 | ||
|
9aa2a6d41d | ||
|
59dcba0831 | ||
|
d582b40160 | ||
|
91e350cb2e | ||
|
7e90275433 | ||
|
d806076fe7 | ||
|
2f182665d1 | ||
|
5f6017b169 | ||
|
54a1c96eff | ||
|
e76700a589 | ||
|
a95ba652c6 | ||
|
897e298764 | ||
|
21e167c126 | ||
|
3a9d192ba7 | ||
|
1f4ac8bd7b | ||
|
bc8cc0283b | ||
|
4858b36a4a | ||
|
dc4073d372 | ||
|
76a71dd87c | ||
|
0b7ab6dfe0 | ||
|
245e8b3ae9 |
203 changed files with 3114 additions and 2533 deletions
22
.ci/kontinuum.json
Normal file
22
.ci/kontinuum.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
{
|
||||
"type": "android",
|
||||
"stages": [
|
||||
{
|
||||
"name": "testWithMapsWithAnalyticsForPlayDebugComposer",
|
||||
"needsEmulator": true
|
||||
},
|
||||
{
|
||||
"name": "lint",
|
||||
"needsEmulator": false
|
||||
},
|
||||
{
|
||||
"name": "test",
|
||||
"needsEmulator": false
|
||||
},
|
||||
{
|
||||
"name": "assembleRelease",
|
||||
"needsEmulator": false
|
||||
}
|
||||
]
|
||||
}
|
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: ligi
|
||||
patreon: ligi
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -2,7 +2,6 @@
|
|||
|
||||
Great that you are thinking about submitting a PullRequest!
|
||||
|
||||
|
||||
Please keep them small if not otherwise possible. Contact me *before* you are creating a big PR to avoid unnecessary work and rebasing of big PRs. Also try to add tests - I am not dogmatic about that but prefer PRs backed by tests. This project has everything setup for Espresso UI and Unit-tests. Also the existing unit and UI-tests muss pass before submitting a PullRequest.
|
||||
|
||||
Please base the PullRequests on the branch named dev
|
||||
Please base the PullRequests on the branch named dev if one currently exists - otherwise use master
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,7 +5,6 @@ build
|
|||
bin
|
||||
gen
|
||||
project.properties
|
||||
gradle.properties
|
||||
local.properties
|
||||
*iml
|
||||
|
||||
|
|
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
|
@ -32,8 +32,8 @@ node {
|
|||
} catch(err) {
|
||||
currentBuild.result = FAILURE
|
||||
} finally {
|
||||
step([$class: 'JUnitResultArchiver', testResults: 'android/build/test-results/*/*/*.xml'])
|
||||
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/reports/tests/', reportFiles: "*/*/index.html", reportName: 'UnitTest'])
|
||||
step([$class: 'JUnitResultArchiver', testResults: 'android/build/test-results/*/*.xml'])
|
||||
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/reports/tests/', reportFiles: "*/index.html", reportName: 'UnitTest'])
|
||||
}
|
||||
|
||||
stage 'assemble'
|
||||
|
|
25
README.md
25
README.md
|
@ -1,15 +1,24 @@
|
|||
[![Android app on Google Play](http://ligi.de/img/play_badge.png)](https://play.google.com/store/apps/details?id=org.ligi.passandroid)
|
||||
[![Android app on FDroid](http://ligi.de/img/fdroid_badge.png)](https://f-droid.org/repository/browse/?fdid=org.ligi.passandroid)
|
||||
[![on Google Play](https://ligi.de/img/play_badge.png)](https://play.google.com/store/apps/details?id=org.ligi.passandroid)
|
||||
[![on FDroid](https://ligi.de/img/fdroid_badge.png)](https://f-droid.org/repository/browse/?fdid=org.ligi.passandroid)
|
||||
[![on Amazon](https://ligi.de/img/amazon_badge.png)](https://www.amazon.com/ligi-Passandroid/dp/B01LX9DMSQ)
|
||||
|
||||
PassAndroid
|
||||
===========
|
||||
# PassAndroid
|
||||
|
||||
Android App to view Passes ( e.g. event tickets, coupons, loyalty cards, boarding passes, .. )
|
||||
Android App to view Passes (e.g. event tickets, coupons, loyalty cards, boarding passes, ...)
|
||||
|
||||
<img src="http://ligi.de/img/passandroid_screenshots.png"/>
|
||||
![Screenshots](https://ligi.de/img/passandroid_screenshots.png)
|
||||
|
||||
Displays [esPass (*.esPass)](http://espass.it) and Passbook ( *.pkpass ) files & shows the Barcode ( QR, PDF417, AZTEC, Code 39 and Code 128 format ). It useable offline.
|
||||
When preparing for the Chaos Communication Congress 2012 ( #29c3 ) I stumbled upon a passbook file for the first time. As I really like the idea of paperless tickets as it saves time and trees which both are very valuable to me. The problem was that I found no app with which I could use the downloaded passbook file.
|
||||
Displays [esPass](https://espass.it) (`*.esPass`) & Passbook (`*.pkpass`) files, shows the Barcode (QR, PDF417, AZTEC, Code 39 and Code 128 format) and is also usable offline.
|
||||
|
||||
When preparing for the Chaos Communication Congress 2012 #29c3 I stumbled upon a passbook file for the first time.
|
||||
I really like the idea of paperless tickets as it saves time and trees which are both very valuable to me.
|
||||
The problem was that I was unable to find an app to open and use the downloaded passbook files with, that's why I wrote my own one.
|
||||
|
||||
## Legal
|
||||
|
||||
This project is licensed under the [GNU General Public License v3.0](COPYING).
|
||||
|
||||
We are not affiliated with Apple - Passbook might be trademarked by Apple, but it's introduced like a standard so this should be okay.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
apply plugin: 'android-sdk-manager'
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'spoon'
|
||||
apply plugin: 'com.trevjonez.composer'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
maven { url 'https://jitpack.io' }
|
||||
google()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
versionCode 337
|
||||
versionName "3.3.7"
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 25
|
||||
versionCode 356
|
||||
versionName "3.5.6"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 29
|
||||
applicationId "org.ligi.passandroid"
|
||||
testInstrumentationRunner "org.ligi.passandroid.AppReplacingRunner"
|
||||
archivesBaseName = "PassAndroid-$versionName"
|
||||
|
@ -57,6 +55,22 @@ android {
|
|||
forPlay {
|
||||
dimension "distribution"
|
||||
}
|
||||
forAmazon {
|
||||
dimension "distribution"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
android.variantFilter { variant ->
|
||||
def maps = variant.getFlavors().get(0).name
|
||||
def analytics = variant.getFlavors().get(1).name
|
||||
def distribution = variant.getFlavors().get(2).name
|
||||
|
||||
variant.setIgnore((project.hasProperty("singleFlavor") && (distribution != 'forPlay'))
|
||||
|| ((distribution == 'forAmazon' || distribution == 'forPlay') && analytics == 'noAnalytics')
|
||||
|| ((distribution == 'forAmazon' || distribution == 'forPlay') && maps == 'noMaps')
|
||||
|| (distribution == 'forFDroid' && analytics == 'withAnalytics')
|
||||
|| (distribution == 'forFDroid' && maps == 'withMaps'))
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
|
@ -70,7 +84,6 @@ android {
|
|||
|
||||
exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
|
||||
exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
|
||||
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
|
@ -84,104 +97,99 @@ android {
|
|||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
|
||||
}
|
||||
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "com.google.dagger:dagger:$dagger_version"
|
||||
kapt "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
implementation 'org.permissionsdispatcher:permissionsdispatcher:4.6.0'
|
||||
kapt 'org.permissionsdispatcher:permissionsdispatcher-processor:4.6.0'
|
||||
|
||||
compile 'com.github.hotchemi:permissionsdispatcher:2.3.1'
|
||||
kapt 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
|
||||
implementation "org.koin:koin-android:2.1.2"
|
||||
|
||||
provided 'org.glassfish:javax.annotation:10.0-b28'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
|
||||
|
||||
kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
androidTestCompile "com.android.support:support-annotations:$support_version"
|
||||
androidTestImplementation 'com.github.ligi:trulesk:0.31'
|
||||
androidTestUtil 'com.linkedin.testbutler:test-butler-app:2.1.0'
|
||||
|
||||
androidTestCompile('com.github.ligi:trulesk:0.12') {
|
||||
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
|
||||
exclude group: 'javax.inject'
|
||||
}
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
|
||||
androidTestImplementation 'com.squareup.assertj:assertj-android:1.2.0'
|
||||
androidTestImplementation "org.mockito:mockito-core:$mockito_version"
|
||||
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.25.1'
|
||||
androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
androidTestImplementation 'org.threeten:threetenbp:1.4.1'
|
||||
androidTestImplementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
|
||||
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
|
||||
exclude group: 'com.android.support', module: 'appcompat'
|
||||
exclude group: 'com.android.support', module: 'support-v4'
|
||||
exclude group: 'javax.inject'
|
||||
exclude module: 'recyclerview-v7'
|
||||
}
|
||||
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.2') {
|
||||
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
|
||||
exclude group: 'javax.inject'
|
||||
}
|
||||
implementation 'com.github.ligi:TouchImageView:2.1'
|
||||
implementation 'com.github.ligi:ExtraCompats:1.0'
|
||||
implementation 'net.lingala.zip4j:zip4j:2.3.2'
|
||||
implementation 'com.jakewharton.threetenabp:threetenabp:1.2.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||
|
||||
androidTestCompile "org.mockito:mockito-core:$mockito_version"
|
||||
androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
|
||||
implementation 'com.github.ligi:KAXT:1.0'
|
||||
implementation 'com.github.ligi:KAXTUI:1.0'
|
||||
implementation 'com.github.ligi:loadtoast:1.10.11'
|
||||
implementation 'com.github.ligi:tracedroid:3.0'
|
||||
|
||||
androidTestCompile "com.android.support:appcompat-v7:$support_version"
|
||||
androidTestCompile "com.android.support:design:$support_version"
|
||||
forPlayImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
|
||||
forFDroidImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
|
||||
forAmazonImplementation 'com.github.ligi.snackengage:snackengage-amazonrate:0.24'
|
||||
|
||||
androidTestCompile 'com.google.code.findbugs:jsr305:3.0.1'
|
||||
// https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
|
||||
// Don't update to >=3.13 before minSDK 21 + Java 8
|
||||
//noinspection GradleDependency
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
|
||||
|
||||
compile 'com.github.ligi:TouchImageView:2.1'
|
||||
compile 'com.github.ligi:ExtraCompats:0.4'
|
||||
implementation 'com.larswerkman:HoloColorPicker:1.5'
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
|
||||
compile 'net.lingala.zip4j:zip4j:1.3.2'
|
||||
compile 'com.jakewharton.threetenabp:threetenabp:1.0.5'
|
||||
compile 'org.greenrobot:eventbus:3.0.0'
|
||||
implementation 'com.squareup.okio:okio:2.2.2'
|
||||
|
||||
implementation 'com.squareup.moshi:moshi:1.9.2'
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2")
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-runtime:$kotlin_version"
|
||||
implementation 'com.chibatching.kotpref:kotpref:2.10.0'
|
||||
implementation 'com.chibatching.kotpref:initializer:2.10.0'
|
||||
testImplementation 'androidx.annotation:annotation:1.1.0'
|
||||
testImplementation 'com.squareup.assertj:assertj-android:1.2.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.mockito:mockito-core:$mockito_version"
|
||||
testImplementation 'org.threeten:threetenbp:1.4.1'
|
||||
|
||||
compile "com.android.support:support-annotations:$support_version"
|
||||
compile "com.android.support:recyclerview-v7:$support_version"
|
||||
compile "com.android.support:appcompat-v7:$support_version"
|
||||
compile "com.android.support:cardview-v7:$support_version"
|
||||
compile "com.android.support:design:$support_version"
|
||||
compile "com.android.support:preference-v7:$support_version"
|
||||
// https://github.com/ligi/PassAndroid/issues/181
|
||||
// Don't upgrade before minSDK 19 - or replace zxing
|
||||
//noinspection GradleDependency
|
||||
implementation 'com.google.zxing:core:3.3.0'
|
||||
|
||||
compile 'net.i2p.android.ext:floatingactionbutton:1.10.1'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
||||
|
||||
compile 'com.github.ligi:KAXT:0.12'
|
||||
compile 'com.github.ligi:loadtoast:1.10.11'
|
||||
compile 'com.github.ligi:tracedroid:1.4'
|
||||
compile 'com.github.ligi:snackengage:0.9'
|
||||
|
||||
compile 'com.squareup.okhttp3:okhttp:3.6.0'
|
||||
compile 'com.larswerkman:HoloColorPicker:1.5'
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.1'
|
||||
compile 'com.squareup.moshi:moshi:1.2.0'
|
||||
compile 'com.chibatching:kotpref:1.6.0'
|
||||
|
||||
testCompile "com.android.support:support-annotations:$support_version"
|
||||
testCompile 'com.squareup.assertj:assertj-android:1.1.1'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.mockito:mockito-core:${mockito_version}"
|
||||
testCompile 'org.threeten:threetenbp:1.3.3'
|
||||
|
||||
// cannot use this new version: https://github.com/zxing/zxing/issues/165
|
||||
// WARNING: might work on some devices or the emulator - but fails on others
|
||||
compile 'com.google.zxing:core:3.3.0'
|
||||
|
||||
//compile fileTree(dir: 'libs', include: 'zxing-core-2.3.0-SNAPSHOT.jar')
|
||||
|
||||
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4'
|
||||
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
|
||||
|
||||
withAnalyticsCompile "com.google.android.gms:play-services-analytics:$play_version"
|
||||
withMapsCompile "com.google.android.gms:play-services-maps:$play_version"
|
||||
}
|
||||
|
||||
spoon {
|
||||
debug = true
|
||||
grantAllPermissions = true
|
||||
// requires minSDK 16 according to docs, not sure if it causes an issue
|
||||
withAnalyticsImplementation 'com.google.android.gms:play-services-analytics:17.0.0'
|
||||
withMapsImplementation 'com.google.android.gms:play-services-maps:17.0.0'
|
||||
}
|
||||
|
||||
play {
|
||||
jsonFile = file('/media/ligi/USBCRED/play.json')
|
||||
uploadImages = true
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -20,31 +20,24 @@
|
|||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
public *;
|
||||
}
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# optimize
|
||||
-optimizationpasses 2
|
||||
-optimizations !code/simplification/arithmetic
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-optimizationpasses 2
|
||||
-optimizations !code/simplification/arithmetic
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
|
||||
# AppCompat
|
||||
# Keep line numbers to alleviate debugging stack traces
|
||||
|
||||
-dontwarn android.support.v7.**
|
||||
-keep class android.support.v7.** { *; }
|
||||
-keep interface android.support.v7.** { *; }
|
||||
|
||||
# Keep line numbers to alleviate debugging stack traces
|
||||
|
||||
-renamesourcefileattribute SourceFile
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
### for api client
|
||||
|
||||
|
||||
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
|
||||
|
||||
-keepclassmembers class * {
|
||||
|
@ -54,7 +47,6 @@
|
|||
# Needed by Guava
|
||||
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
|
||||
|
||||
|
||||
-dontwarn sun.misc.Unsafe
|
||||
-dontwarn com.google.common.collect.MinMaxPriorityQueue
|
||||
|
||||
|
@ -72,9 +64,6 @@
|
|||
-keep class **$$ViewBinder { *; }
|
||||
-keepnames class * { @butterknife.Bind *;}
|
||||
|
||||
#### for support 22
|
||||
-dontwarn android.support.**
|
||||
|
||||
#### for guava
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn javax.inject.**
|
||||
|
@ -105,23 +94,20 @@
|
|||
-dontwarn java.nio.file.OpenOption
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
|
||||
-keep public class com.google.android.gms.**
|
||||
-dontwarn com.google.android.gms.**
|
||||
|
||||
|
||||
## New rules for EventBus 3.0.x ##
|
||||
# http://greenrobot.org/eventbus/documentation/proguard/
|
||||
|
||||
-keepattributes *Annotation*
|
||||
-keepclassmembers class ** {
|
||||
-keepclassmembers class * {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
# Only required if you use AsyncExecutor
|
||||
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
|
||||
<init>(java.lang.Throwable);
|
||||
}
|
||||
}
|
||||
|
||||
### for moshi
|
||||
|
||||
|
@ -153,3 +139,17 @@
|
|||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
||||
}
|
||||
|
||||
## okhttp
|
||||
|
||||
# JSR 305 annotations are for embedding nullability information.
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
# A resource is loaded with a relative path so the package of this class must be preserved.
|
||||
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
|
||||
|
||||
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.*
|
||||
|
||||
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
|
||||
-dontwarn okhttp3.internal.platform.ConscryptPlatform
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"message":"",
|
||||
"transfer":"barcode_QR",
|
||||
"alternative": {
|
||||
"order":1,
|
||||
"order":1
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package org.ligi.passandroid;
|
||||
|
||||
import org.ligi.passandroid.model.PassStore;
|
||||
|
||||
public class TestApp extends App {
|
||||
|
||||
@Override
|
||||
public AppComponent createComponent() {
|
||||
return DaggerTestComponent.builder().testModule(new TestModule()).build();
|
||||
}
|
||||
|
||||
public static TestComponent component() {
|
||||
return (TestComponent) App.component();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installLeakCanary() {
|
||||
|
||||
}
|
||||
|
||||
public static PassStore getPassStore() {
|
||||
return component().passStore();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
setComponent(DaggerTestComponent.builder().testModule(new TestModule()).build());
|
||||
}
|
||||
}
|
58
android/src/androidTest/java/org/ligi/passandroid/TestApp.kt
Normal file
58
android/src/androidTest/java/org/ligi/passandroid/TestApp.kt
Normal file
|
@ -0,0 +1,58 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
import org.ligi.passandroid.injections.FixedPassListPassStore
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import org.ligi.passandroid.model.comparator.PassSortOrder
|
||||
import org.ligi.passandroid.model.pass.BarCode
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.model.pass.PassBarCodeFormat
|
||||
import org.ligi.passandroid.model.pass.PassImpl
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class TestApp : App() {
|
||||
|
||||
override fun createKoin(): Module {
|
||||
|
||||
return module {
|
||||
single { passStore as PassStore }
|
||||
single { settings }
|
||||
single { tracker }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val tracker = mock(Tracker::class.java)
|
||||
val passStore = FixedPassListPassStore(emptyList())
|
||||
val settings = mock(Settings::class.java).apply {
|
||||
`when`(getSortOrder()).thenReturn(PassSortOrder.DATE_ASC)
|
||||
`when`(getPassesDir()).thenReturn(File(""))
|
||||
`when`(doTraceDroidEmailSend()).thenReturn(false)
|
||||
}
|
||||
|
||||
fun populatePassStoreWithSinglePass() {
|
||||
|
||||
val passList = ArrayList<Pass>()
|
||||
val pass = PassImpl(UUID.randomUUID().toString())
|
||||
pass.description = "description"
|
||||
pass.barCode = BarCode(PassBarCodeFormat.AZTEC, "messageprobe")
|
||||
passList.add(pass)
|
||||
|
||||
fixedPassListPassStore().setList(passList)
|
||||
|
||||
passStore.classifier.moveToTopic(pass, "test")
|
||||
}
|
||||
|
||||
fun emptyPassStore() {
|
||||
fixedPassListPassStore().setList(emptyList())
|
||||
}
|
||||
|
||||
private fun fixedPassListPassStore() = passStore as FixedPassListPassStore
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(modules = arrayOf(TestModule::class))
|
||||
interface TestComponent : AppComponent {
|
||||
|
||||
fun inject(theFullscreenBarcodeActivity: TheFullscreenBarcodeActivity)
|
||||
|
||||
fun inject(thePassEditActivity: ThePassEditActivity)
|
||||
|
||||
fun inject(thePassViewActivity: ThePassViewActivity)
|
||||
|
||||
fun inject(thePastLocationsStore: ThePastLocationsStore)
|
||||
|
||||
fun inject(theBarCodeEditing: TheBarCodeEditing)
|
||||
|
||||
fun inject(thePassListSwiping: ThePassListSwiping)
|
||||
|
||||
fun inject(theFieldListEditFragment: TheFieldListEditFragment)
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.ligi.passandroid.injections.FixedPassListPassStore
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import org.ligi.passandroid.model.comparator.PassSortOrder
|
||||
import org.ligi.passandroid.model.pass.BarCode
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.model.pass.PassBarCodeFormat
|
||||
import org.ligi.passandroid.model.pass.PassImpl
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class TestModule {
|
||||
|
||||
private val passList: MutableList<Pass>
|
||||
|
||||
constructor() {
|
||||
passList = ArrayList<Pass>()
|
||||
val pass = PassImpl(UUID.randomUUID().toString())
|
||||
pass.description = "description"
|
||||
pass.barCode = BarCode(PassBarCodeFormat.AZTEC, "messageprobe")
|
||||
passList.add(pass)
|
||||
|
||||
}
|
||||
|
||||
constructor(passList: MutableList<Pass>) {
|
||||
this.passList = passList
|
||||
}
|
||||
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providePassStore(): PassStore {
|
||||
val fixedPassListPassStore = FixedPassListPassStore(passList)
|
||||
if (!passList.isEmpty()) {
|
||||
fixedPassListPassStore.currentPass = passList[0]
|
||||
}
|
||||
|
||||
for (pass in passList) {
|
||||
fixedPassListPassStore.classifier.moveToTopic(pass, "test")
|
||||
}
|
||||
return fixedPassListPassStore
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSettings(): Settings {
|
||||
val mock = mock(Settings::class.java)
|
||||
`when`(mock.getSortOrder()).thenReturn(PassSortOrder.DATE_ASC)
|
||||
`when`(mock.getPassesDir()).thenReturn(File(""))
|
||||
`when`(mock.doTraceDroidEmailSend()).thenReturn(false)
|
||||
return mock
|
||||
}
|
||||
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideBus(): EventBus {
|
||||
return mock(EventBus::class.java)
|
||||
}
|
||||
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTracker(): Tracker {
|
||||
return mock(Tracker::class.java)
|
||||
}
|
||||
|
||||
}
|
|
@ -3,14 +3,14 @@ package org.ligi.passandroid
|
|||
import android.app.Activity.RESULT_CANCELED
|
||||
import android.app.Instrumentation
|
||||
import android.provider.CalendarContract
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.intending
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasType
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasType
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.junit.Rule
|
||||
|
@ -23,17 +23,17 @@ import org.threeten.bp.ZonedDateTime
|
|||
|
||||
class TheAddToCalendar {
|
||||
|
||||
val time = ZonedDateTime.now()
|
||||
val time2 = ZonedDateTime.now().plusHours(3)
|
||||
private val time = ZonedDateTime.now()
|
||||
private val time2 = ZonedDateTime.now().plusHours(3)
|
||||
|
||||
@get:Rule
|
||||
var rule = TruleskIntentRule(PassListActivity::class.java, false)
|
||||
|
||||
@Test
|
||||
fun testIfWeOnlyHaveCalendarStartDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
TestApp.getPassStore().currentPass!!.calendarTimespan = PassImpl.TimeSpan(time)
|
||||
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time)
|
||||
rule.launchActivity()
|
||||
|
||||
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
|
||||
|
@ -44,15 +44,15 @@ class TheAddToCalendar {
|
|||
hasType("vnd.android.cursor.item/event"),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
|
||||
hasExtra("title", TestApp.getPassStore().currentPass!!.description)
|
||||
hasExtra("title", TestApp.passStore.currentPass!!.description)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIfWeOnlyHaveCalendarEndDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
TestApp.getPassStore().currentPass!!.calendarTimespan = PassImpl.TimeSpan(to = time)
|
||||
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(to = time)
|
||||
rule.launchActivity()
|
||||
|
||||
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
|
||||
|
@ -63,15 +63,15 @@ class TheAddToCalendar {
|
|||
hasType("vnd.android.cursor.item/event"),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.toEpochSecond() * 1000),
|
||||
hasExtra("title", TestApp.getPassStore().currentPass!!.description)
|
||||
hasExtra("title", TestApp.passStore.currentPass!!.description)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIfWeOnlyHaveCalendarStartAndEndDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
TestApp.getPassStore().currentPass!!.calendarTimespan = PassImpl.TimeSpan(time, time2)
|
||||
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time, time2)
|
||||
rule.launchActivity()
|
||||
|
||||
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
|
||||
|
@ -82,16 +82,16 @@ class TheAddToCalendar {
|
|||
hasType("vnd.android.cursor.item/event"),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time2.toEpochSecond() * 1000),
|
||||
hasExtra("title", TestApp.getPassStore().currentPass!!.description)
|
||||
hasExtra("title", TestApp.passStore.currentPass!!.description)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testIfWeOnlyHaveExpirationDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
(TestApp.getPassStore().currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time))
|
||||
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time))
|
||||
rule.launchActivity()
|
||||
|
||||
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
|
||||
|
@ -105,15 +105,15 @@ class TheAddToCalendar {
|
|||
hasType("vnd.android.cursor.item/event"),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
|
||||
hasExtra("title", TestApp.getPassStore().currentPass!!.description)
|
||||
hasExtra("title", TestApp.passStore.currentPass!!.description)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIfWeOnlyHaveExpirationEndDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
(TestApp.getPassStore().currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(to = time))
|
||||
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(to = time))
|
||||
rule.launchActivity()
|
||||
|
||||
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
|
||||
|
@ -127,15 +127,15 @@ class TheAddToCalendar {
|
|||
hasType("vnd.android.cursor.item/event"),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.toEpochSecond() * 1000),
|
||||
hasExtra("title", TestApp.getPassStore().currentPass!!.description)
|
||||
hasExtra("title", TestApp.passStore.currentPass!!.description)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIfWeOnlyHaveExpirationStartAndEndDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
(TestApp.getPassStore().currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time, time2))
|
||||
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time, time2))
|
||||
rule.launchActivity()
|
||||
|
||||
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
|
||||
|
@ -149,13 +149,13 @@ class TheAddToCalendar {
|
|||
hasType("vnd.android.cursor.item/event"),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
|
||||
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time2.toEpochSecond() * 1000),
|
||||
hasExtra("title", TestApp.getPassStore().currentPass!!.description)
|
||||
hasExtra("title", TestApp.passStore.currentPass!!.description)
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThereIsNoButtonWithNoDate() {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
rule.launchActivity()
|
||||
onView(withId(R.id.timeButton)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.closeSoftKeyboard
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.*
|
||||
import android.support.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.filters.SdkSuppress
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import android.Manifest
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.closeSoftKeyboard
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.*
|
||||
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SdkSuppress
|
||||
import com.linkedin.android.testbutler.TestButler
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -17,7 +20,6 @@ import org.ligi.passandroid.model.pass.PassBarCodeFormat
|
|||
import org.ligi.passandroid.model.pass.PassImpl
|
||||
import org.ligi.passandroid.ui.PassEditActivity
|
||||
import org.ligi.trulesk.TruleskActivityRule
|
||||
import javax.inject.Inject
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TheBarCodeEditing {
|
||||
|
@ -25,19 +27,20 @@ class TheBarCodeEditing {
|
|||
@get:Rule
|
||||
val rule = TruleskActivityRule(PassEditActivity::class.java, false)
|
||||
|
||||
@Inject
|
||||
lateinit var passStore: PassStore
|
||||
val passStore: PassStore = TestApp.passStore
|
||||
|
||||
lateinit var currentPass: PassImpl
|
||||
private lateinit var currentPass: PassImpl
|
||||
|
||||
fun start(setupPass: (pass: PassImpl) -> Unit = {}) {
|
||||
|
||||
TestApp.component().inject(this)
|
||||
private fun start(setupPass: (pass: PassImpl) -> Unit = {}) {
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
|
||||
currentPass = passStore.currentPass as PassImpl
|
||||
|
||||
setupPass(currentPass)
|
||||
|
||||
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
||||
rule.launchActivity(null)
|
||||
closeSoftKeyboard()
|
||||
}
|
||||
|
@ -81,6 +84,7 @@ class TheBarCodeEditing {
|
|||
|
||||
onView(withText(passBarCodeFormat.name)).perform(scrollTo(), click())
|
||||
|
||||
onView(withId(R.id.randomButton)).perform(click())
|
||||
closeSoftKeyboard()
|
||||
|
||||
onView(withText(android.R.string.ok)).perform(click())
|
||||
|
@ -98,7 +102,7 @@ class TheBarCodeEditing {
|
|||
onView(withId(R.id.barcode_img)).perform(click())
|
||||
|
||||
onView(withId(R.id.messageInput)).perform(clearText())
|
||||
onView(withId(R.id.messageInput)).perform(typeText("msg foo txt ;-)"))
|
||||
onView(withId(R.id.messageInput)).perform(replaceText("msg foo txt ;-)"))
|
||||
|
||||
closeSoftKeyboard()
|
||||
|
||||
|
@ -118,7 +122,7 @@ class TheBarCodeEditing {
|
|||
onView(withId(R.id.barcode_img)).perform(click())
|
||||
|
||||
onView(withId(R.id.alternativeMessageInput)).perform(clearText())
|
||||
onView(withId(R.id.alternativeMessageInput)).perform(typeText("alt bar txt ;-)"))
|
||||
onView(withId(R.id.alternativeMessageInput)).perform(replaceText("alt bar txt ;-)"))
|
||||
|
||||
closeSoftKeyboard()
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Fail.fail
|
||||
import org.junit.Test
|
||||
|
@ -48,7 +47,6 @@ class TheBarcodeHelper {
|
|||
} catch (e: Exception) {
|
||||
fail("could not create barcode", e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun testBitmapSizeIsSane(format: PassBarCodeFormat) {
|
||||
|
@ -59,6 +57,5 @@ class TheBarcodeHelper {
|
|||
} catch (e: Exception) {
|
||||
fail("could not create barcode", e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import org.hamcrest.CoreMatchers.containsString
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -19,14 +19,15 @@ class TheCondensedPassViewMode {
|
|||
|
||||
@get:Rule
|
||||
var rule = TruleskActivityRule(PassListActivity::class.java, false) {
|
||||
val currentPass = TestApp.component().passStore().currentPass as PassImpl
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
val currentPass = TestApp.passStore.currentPass as PassImpl
|
||||
currentPass.calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.of(2016, 11, 23, 20, 42, 42, 5, ZoneId.systemDefault()))
|
||||
currentPass.fields = mutableListOf(PassField("textprobe", "bar", "yo", false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDateShowsForCondensedOff() {
|
||||
`when`(TestApp.component().settings().isCondensedModeEnabled()).thenReturn(false)
|
||||
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(false)
|
||||
|
||||
rule.launchActivity()
|
||||
|
||||
|
@ -40,7 +41,7 @@ class TheCondensedPassViewMode {
|
|||
@Test
|
||||
fun testFieldShowsForCondensedOn() {
|
||||
|
||||
`when`(TestApp.component().settings().isCondensedModeEnabled()).thenReturn(true)
|
||||
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(true)
|
||||
|
||||
rule.launchActivity()
|
||||
|
||||
|
|
|
@ -1,30 +1,23 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.junit.Before
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.ligi.passandroid.R.id.emptyView
|
||||
import org.ligi.passandroid.functions.checkThatHelpIsThere
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.ui.PassListActivity
|
||||
import org.ligi.trulesk.TruleskIntentRule
|
||||
import java.util.*
|
||||
|
||||
|
||||
class TheEmptyPassList {
|
||||
|
||||
@get:Rule
|
||||
var rule = TruleskIntentRule(PassListActivity::class.java, false)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
App.setComponent(DaggerTestComponent.builder().testModule(TestModule(ArrayList<Pass>())).build())
|
||||
rule.launchActivity(null)
|
||||
var rule = TruleskIntentRule(PassListActivity::class.java) {
|
||||
TestApp.emptyPassStore()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.replaceText
|
||||
import android.support.test.espresso.action.ViewActions.scrollTo
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.action.ViewActions.scrollTo
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -18,7 +18,7 @@ class TheFieldListEditFragment {
|
|||
|
||||
@get:Rule
|
||||
val rule = TruleskIntentRule(PassEditActivity::class.java) {
|
||||
TestApp.getPassStore().currentPass = PassImpl(UUID.randomUUID().toString()).apply {
|
||||
TestApp.passStore.currentPass = PassImpl(UUID.randomUUID().toString()).apply {
|
||||
fields = arrayListOf(field)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ package org.ligi.passandroid
|
|||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
||||
import android.widget.ImageView
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -19,9 +19,9 @@ import org.ligi.passandroid.ui.FullscreenBarcodeActivity
|
|||
import org.ligi.trulesk.TruleskIntentRule
|
||||
import java.util.*
|
||||
|
||||
class TheFullscreenBarcodeActivity {
|
||||
private const val BARCODE_MESSAGE = "2323"
|
||||
|
||||
private val BARCODE_MESSAGE = "2323"
|
||||
class TheFullscreenBarcodeActivity {
|
||||
|
||||
@get:Rule
|
||||
var rule = TruleskIntentRule(FullscreenBarcodeActivity::class.java, false)
|
||||
|
@ -68,7 +68,7 @@ class TheFullscreenBarcodeActivity {
|
|||
val pass = PassImpl(UUID.randomUUID().toString())
|
||||
pass.barCode = BarCode(format, BARCODE_MESSAGE)
|
||||
|
||||
TestApp.getPassStore().currentPass = pass
|
||||
TestApp.passStore.currentPass = pass
|
||||
|
||||
rule.launchActivity(null)
|
||||
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()))
|
||||
|
@ -78,11 +78,11 @@ class TheFullscreenBarcodeActivity {
|
|||
val bitmap = bitmapDrawable.bitmap
|
||||
|
||||
val bitmapToTest: Bitmap
|
||||
if (format === PassBarCodeFormat.AZTEC) {
|
||||
bitmapToTest = if (format === PassBarCodeFormat.AZTEC) {
|
||||
// not sure why - but for the decoder to pick up AZTEC it must have moar pixelz - smells like a zxing bug
|
||||
bitmapToTest = Bitmap.createScaledBitmap(bitmap, bitmap.width * 2, bitmap.height * 2, false)
|
||||
Bitmap.createScaledBitmap(bitmap, bitmap.width * 2, bitmap.height * 2, false)
|
||||
} else {
|
||||
bitmapToTest = bitmap
|
||||
bitmap
|
||||
}
|
||||
|
||||
assertThat(bitmapToTest.decodeBarCode()).isEqualTo(BARCODE_MESSAGE)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
|
@ -5,14 +5,15 @@ import android.app.Activity.RESULT_CANCELED
|
|||
import android.app.Instrumentation.ActivityResult
|
||||
import android.content.Intent.ACTION_SEND
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.contrib.DrawerActions.open
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.intending
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.*
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.DrawerActions.open
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.*
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.hamcrest.Matchers.not
|
||||
import org.junit.Rule
|
||||
|
@ -39,6 +40,15 @@ class TheNavigationDrawer {
|
|||
onView(withId(R.id.navigationView)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatNavigationDrawerClosesOnBackPress() {
|
||||
testThatNavigationDrawerOpens()
|
||||
|
||||
pressBack()
|
||||
|
||||
onView(withId(R.id.navigationView)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBetatestClick() {
|
||||
testThatNavigationDrawerOpens()
|
||||
|
@ -51,19 +61,6 @@ class TheNavigationDrawer {
|
|||
intended(allOf(hasAction(ACTION_VIEW), hasData("https://play.google.com/apps/testing/org.ligi.passandroid")))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testCommunityClick() {
|
||||
testThatNavigationDrawerOpens()
|
||||
rule.screenShot("open_drawer")
|
||||
|
||||
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
|
||||
|
||||
onView(withText(nav_community_on_google)).perform(click())
|
||||
|
||||
intended(allOf(hasAction(ACTION_VIEW), hasData("https://plus.google.com/communities/116353894782342292067")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGitHubClick() {
|
||||
testThatNavigationDrawerOpens()
|
||||
|
|
|
@ -1,46 +1,45 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Activity
|
||||
import android.app.Instrumentation
|
||||
import android.content.Intent
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.*
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.intending
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.*
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.linkedin.android.testbutler.TestButler
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.ligi.passandroid.R.id.*
|
||||
import org.ligi.passandroid.R.string.*
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.pass.PassType.COUPON
|
||||
import org.ligi.passandroid.model.pass.PassType.EVENT
|
||||
import org.ligi.passandroid.ui.PassEditActivity
|
||||
import org.ligi.trulesk.TruleskIntentRule
|
||||
import javax.inject.Inject
|
||||
|
||||
@TargetApi(14)
|
||||
class ThePassEditActivity {
|
||||
|
||||
@Inject
|
||||
lateinit var passStore: PassStore
|
||||
val passStore = TestApp.passStore
|
||||
|
||||
@get:Rule
|
||||
var rule = TruleskIntentRule(PassEditActivity::class.java) {
|
||||
TestApp.component().inject(this)
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
TestButler.grantPermission(InstrumentationRegistry.getInstrumentation().targetContext, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
TestButler.grantPermission(InstrumentationRegistry.getInstrumentation().targetContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetToEventWorks() {
|
||||
onView(withId(R.id.categoryView)).perform(click())
|
||||
|
||||
onView(withId(categoryView)).perform(click())
|
||||
|
||||
onView(withText(select_category_dialog_title)).perform(click())
|
||||
onView(withText(category_event)).perform(click())
|
||||
onView(withText(R.string.select_category_dialog_title)).perform(click())
|
||||
onView(withText(R.string.category_event)).perform(click())
|
||||
assertThat(passStore.currentPass!!.type).isEqualTo(EVENT)
|
||||
|
||||
rule.screenShot("edit_set_event")
|
||||
|
@ -48,10 +47,10 @@ class ThePassEditActivity {
|
|||
|
||||
@Test
|
||||
fun testSetToCouponWorks() {
|
||||
onView(withId(categoryView)).perform(click())
|
||||
onView(withId(R.id.categoryView)).perform(click())
|
||||
|
||||
onView(withText(select_category_dialog_title)).perform(click())
|
||||
onView(withText(category_coupon)).perform(click())
|
||||
onView(withText(R.string.select_category_dialog_title)).perform(click())
|
||||
onView(withText(R.string.category_coupon)).perform(click())
|
||||
assertThat(passStore.currentPass!!.type).isEqualTo(COUPON)
|
||||
|
||||
rule.screenShot("edit_set_coupon")
|
||||
|
@ -60,7 +59,7 @@ class ThePassEditActivity {
|
|||
@Test
|
||||
fun testSetDescriptionWorks() {
|
||||
|
||||
onView(withId(passTitle)).perform(clearText(), typeText("test description"))
|
||||
onView(withId(R.id.passTitle)).perform(clearText(), replaceText("test description"))
|
||||
assertThat(passStore.currentPass!!.description).isEqualTo("test description")
|
||||
|
||||
rule.screenShot("edit_set_description")
|
||||
|
@ -70,10 +69,10 @@ class ThePassEditActivity {
|
|||
@Test
|
||||
fun testColorWheelIsThere() {
|
||||
|
||||
onView(withId(categoryView)).perform(click())
|
||||
onView(withText(change_color_dialog_title)).perform(click())
|
||||
onView(withId(R.id.categoryView)).perform(click())
|
||||
onView(withText(R.string.change_color_dialog_title)).perform(click())
|
||||
|
||||
onView(withId(colorPicker)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()))
|
||||
|
||||
rule.screenShot("edit_set_color")
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
||||
import android.os.Build
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.ligi.passandroid.R.id.pass_recyclerview
|
||||
import org.ligi.passandroid.functions.checkThatHelpIsThere
|
||||
import org.ligi.passandroid.functions.expand
|
||||
import org.ligi.passandroid.functions.isCollapsed
|
||||
import org.ligi.passandroid.ui.PassListActivity
|
||||
import org.ligi.trulesk.TruleskActivityRule
|
||||
|
||||
|
@ -18,7 +21,7 @@ class ThePassListActivity {
|
|||
|
||||
@get:Rule
|
||||
var rule = TruleskActivityRule(PassListActivity::class.java) {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -35,5 +38,29 @@ class ThePassListActivity {
|
|||
checkThatHelpIsThere()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCloseFabOnBackPressed() {
|
||||
onView(withId(R.id.fam)).perform(expand())
|
||||
|
||||
pressBack()
|
||||
|
||||
onView(withId(R.id.fam))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(isCollapsed()))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testOpenVisibleOn19plus() {
|
||||
onView(withId(R.id.fam)).perform(expand())
|
||||
|
||||
pressBack()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
onView(withId(R.id.fab_action_open_file)).check(matches(isDisplayed()))
|
||||
} else {
|
||||
onView(withId(R.id.fab_action_open_file)).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.action.ViewActions.typeText
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.v7.widget.helper.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -13,22 +13,22 @@ import org.ligi.passandroid.ui.PassListActivity
|
|||
import org.ligi.passandroid.ui.PassListFragment
|
||||
import org.ligi.trulesk.TruleskIntentRule
|
||||
|
||||
const val CUSTOM_PROBE = "FOO_PROBE"
|
||||
|
||||
class ThePassListSwiping {
|
||||
|
||||
@get:Rule
|
||||
val rule = TruleskIntentRule(PassListActivity::class.java) {
|
||||
TestApp.reset()
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
}
|
||||
|
||||
val passStore by lazy { TestApp.getPassStore() }
|
||||
|
||||
@Test
|
||||
fun testWeCanMoveToTrash() {
|
||||
fakeSwipeLeft()
|
||||
|
||||
onView(withText(R.string.topic_trash)).perform(click())
|
||||
|
||||
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
|
||||
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,20 +38,19 @@ class ThePassListSwiping {
|
|||
|
||||
onView(withText(R.string.topic_archive)).perform(click())
|
||||
|
||||
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
|
||||
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWeCanMoveToCustom() {
|
||||
val CUSTOM_PROBE = "FOO_PROBE"
|
||||
|
||||
fakeSwipeLeft()
|
||||
|
||||
onView(withId(R.id.new_topic_edit)).perform(typeText(CUSTOM_PROBE))
|
||||
onView(withId(R.id.new_topic_edit)).perform(replaceText(CUSTOM_PROBE))
|
||||
|
||||
onView(withText(android.R.string.ok)).perform(click())
|
||||
|
||||
assertThat(passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
|
||||
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import org.hamcrest.core.IsNot.not
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.ligi.passandroid.model.pass.BarCode
|
||||
|
@ -21,11 +22,16 @@ import java.util.*
|
|||
@TargetApi(14)
|
||||
class ThePassViewActivity {
|
||||
|
||||
internal fun getActPass() = TestApp.getPassStore().currentPass as PassImpl
|
||||
private fun getActPass() = TestApp.passStore.currentPass as PassImpl
|
||||
|
||||
@get:Rule
|
||||
var rule = TruleskActivityRule(PassViewActivity::class.java, false)
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatDescriptionIsThere() {
|
||||
rule.launchActivity(null)
|
||||
|
@ -35,7 +41,7 @@ class ThePassViewActivity {
|
|||
|
||||
@Test
|
||||
fun testDateIsGoneWhenPassbookHasNoDate() {
|
||||
getActPass().validTimespans = ArrayList<PassImpl.TimeSpan>()
|
||||
getActPass().validTimespans = ArrayList()
|
||||
rule.launchActivity(null)
|
||||
|
||||
onView(withId(R.id.date)).check(matches(not(isDisplayed())))
|
||||
|
@ -92,7 +98,7 @@ class ThePassViewActivity {
|
|||
|
||||
@Test
|
||||
fun testLinkToCalendarIsNotThereWhenPassbookHasNoDate() {
|
||||
getActPass().validTimespans = ArrayList<PassImpl.TimeSpan>()
|
||||
getActPass().validTimespans = ArrayList()
|
||||
rule.launchActivity(null)
|
||||
|
||||
onView(withText(R.string.pass_to_calendar)).check(matches(not(isDisplayed())))
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.assertion.ViewAssertions
|
||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -16,7 +16,10 @@ import org.threeten.bp.ZonedDateTime
|
|||
|
||||
class ThePassViewHolder {
|
||||
|
||||
val currentPass by lazy { TestApp.component().passStore().currentPass as PassImpl }
|
||||
private val currentPass by lazy {
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
TestApp.passStore.currentPass as PassImpl
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
var rule = TruleskActivityRule(PassListActivity::class.java, false)
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.ligi.passandroid
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
|
@ -17,13 +17,14 @@ class ThePastLocationsStore {
|
|||
|
||||
@get:Rule
|
||||
var rule = TruleskActivityRule(PassViewActivity::class.java) {
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
@Mock
|
||||
lateinit var tracker: Tracker
|
||||
|
||||
val prefs: SharedPreferences by lazy { getInstrumentation().context.getSharedPreferences("" + System.currentTimeMillis() / 100000, Context.MODE_PRIVATE) }
|
||||
private val prefs: SharedPreferences by lazy { InstrumentationRegistry.getInstrumentation().context.getSharedPreferences("" + System.currentTimeMillis() / 100000, Context.MODE_PRIVATE) }
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
|
@ -34,7 +35,7 @@ class ThePastLocationsStore {
|
|||
fun testPastLocationsStoreShouldNeverContainMoreThanMaxElements() {
|
||||
val tested = PastLocationsStore(prefs, tracker)
|
||||
|
||||
for (i in 0..PastLocationsStore.MAX_ELEMENTS * 2 - 1) {
|
||||
for (i in 0 until PastLocationsStore.MAX_ELEMENTS * 2) {
|
||||
tested.putLocation("" + i)
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,7 @@ class ThePastLocationsStore {
|
|||
|
||||
@Test
|
||||
fun testPastLocationsStoreShouldStoreOnlyOneOfAKind() {
|
||||
val tested = PastLocationsStore(prefs, tracker!!)
|
||||
val tested = PastLocationsStore(prefs, tracker)
|
||||
|
||||
for (i in 0..2) {
|
||||
tested.putLocation("foo")
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withText
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import com.linkedin.android.testbutler.TestButler
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -18,9 +21,12 @@ import org.ligi.trulesk.TruleskActivityRule
|
|||
class ThePreferenceActivity {
|
||||
|
||||
@get:Rule
|
||||
val rule = TruleskActivityRule(PreferenceActivity::class.java)
|
||||
val rule = TruleskActivityRule(PreferenceActivity::class.java) {
|
||||
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
}
|
||||
|
||||
val androidSettings by lazy { AndroidSettings(rule.activity) }
|
||||
private val androidSettings by lazy { AndroidSettings(rule.activity) }
|
||||
|
||||
@Test
|
||||
fun autoLightToggles() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.ligi.passandroid;
|
||||
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import java.io.InputStream;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -27,7 +27,7 @@ public class TheUnzipPassController {
|
|||
PassStore passStore;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ public class TheUnzipPassController {
|
|||
verify(failCallback).fail(any(String.class));
|
||||
|
||||
} catch (Exception e) {
|
||||
fail("should be able to load file");
|
||||
fail("should be able to load file " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package org.ligi.passandroid.functions
|
||||
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
|
||||
import android.view.View
|
||||
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
|
||||
fun expand(): ViewAction? = ExpandFabAction()
|
||||
|
||||
class ExpandFabAction : ViewAction {
|
||||
|
||||
override fun getConstraints(): Matcher<View> = isAssignableFrom(FloatingActionsMenu::class.java)
|
||||
|
||||
override fun getDescription() = "expands the floating action menu"
|
||||
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
val fam = view as FloatingActionsMenu
|
||||
fam.expand()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CollapsedCheck : TypeSafeMatcher<FloatingActionsMenu>(FloatingActionsMenu::class.java) {
|
||||
|
||||
override fun describeTo(description: Description?) {
|
||||
description?.appendText("is in collapsed state")
|
||||
}
|
||||
|
||||
override fun matchesSafely(fam: FloatingActionsMenu?): Boolean {
|
||||
return !fam?.isExpanded!!
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
package org.ligi.passandroid.functions
|
||||
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.ligi.passandroid.R
|
||||
|
||||
|
||||
fun checkThatHelpIsThere() {
|
||||
onView(withId(R.id.help_text)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package org.ligi.passandroid.functions
|
||||
|
||||
import android.view.View
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
fun isCollapsed(): Matcher<in View>? = CollapsedCheck() as Matcher<in View>
|
|
@ -1,8 +1,9 @@
|
|||
package org.ligi.passandroid.functions
|
||||
|
||||
import android.content.Context
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.assertj.core.api.Fail.fail
|
||||
import org.ligi.passandroid.TestApp
|
||||
import org.ligi.passandroid.model.InputStreamWithSource
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
@ -30,7 +31,7 @@ fun loadPassFromAsset(asset: String, callback: (pass: Pass?) -> Unit) {
|
|||
object : UnzipPassController.SuccessCallback {
|
||||
override fun call(uuid: String) {
|
||||
callback.invoke(AppleStylePassReader.read(File(getTestTargetPath(instrumentation.targetContext), uuid), "en",
|
||||
instrumentation.targetContext))
|
||||
instrumentation.targetContext,TestApp.tracker))
|
||||
}
|
||||
},
|
||||
mock
|
||||
|
|
|
@ -1,28 +1,47 @@
|
|||
package org.ligi.passandroid.injections
|
||||
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import org.ligi.passandroid.model.PassClassifier
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PassStoreUpdateEvent
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class FixedPassListPassStore(private val passes: List<Pass>) : PassStore {
|
||||
class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
|
||||
|
||||
override lateinit var classifier: PassClassifier
|
||||
|
||||
init {
|
||||
classifier = PassClassifier(HashMap(), this)
|
||||
}
|
||||
|
||||
fun setList(newPasses: List<Pass>, newCurrentPass: Pass? = newPasses.firstOrNull()) {
|
||||
currentPass = newCurrentPass
|
||||
passes = newPasses
|
||||
passMap.clear()
|
||||
passMap.putAll(createHashMap())
|
||||
|
||||
classifier = PassClassifier(HashMap(), this)
|
||||
}
|
||||
|
||||
override var currentPass: Pass? = null
|
||||
|
||||
override val passMap: Map<String, Pass> by lazy {
|
||||
override val passMap: HashMap<String, Pass> by lazy {
|
||||
return@lazy createHashMap()
|
||||
}
|
||||
|
||||
private fun createHashMap(): HashMap<String, Pass> {
|
||||
val hashMap = HashMap<String, Pass>()
|
||||
|
||||
passes.forEach { hashMap.put(it.id, it) }
|
||||
return@lazy hashMap
|
||||
passes.forEach { hashMap[it.id] = it }
|
||||
return hashMap
|
||||
}
|
||||
|
||||
override fun getPassbookForId(id: String): Pass? {
|
||||
return passMap[id]
|
||||
}
|
||||
|
||||
override val classifier: PassClassifier by lazy {
|
||||
PassClassifier(HashMap<String, String>(), this)
|
||||
}
|
||||
|
||||
override fun deletePassWithId(id: String): Boolean {
|
||||
return false
|
||||
|
@ -32,6 +51,8 @@ class FixedPassListPassStore(private val passes: List<Pass>) : PassStore {
|
|||
return File("")
|
||||
}
|
||||
|
||||
override val updateChannel: BroadcastChannel<PassStoreUpdateEvent> = ConflatedBroadcastChannel()
|
||||
|
||||
override fun save(pass: Pass) {
|
||||
// no effect in this impl
|
||||
}
|
||||
|
|
5
android/src/forAmazon/res/values/strings.xml
Normal file
5
android/src/forAmazon/res/values/strings.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="market_url" translatable="false">amzn://apps/android?p=%s</string>
|
||||
<string name="nav_market" translatable="false">Play</string>
|
||||
</resources>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="market_url">https://f-droid.org/repository/browse/?fdid=%s</string>
|
||||
<string name="market_url" translatable="false">https://f-droid.org/repository/browse/?fdid=%s</string>
|
||||
</resources>
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
<receiver
|
||||
android:name=".InstallListener"
|
||||
android:permission="android.permission.INSTALL_PACKAGES"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.vending.INSTALL_REFERRER"/>
|
||||
|
@ -36,7 +37,7 @@
|
|||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="@string/authority_fileprovider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
|
@ -45,12 +46,12 @@
|
|||
android:resource="@xml/filepaths"/>
|
||||
</provider>
|
||||
|
||||
<service android:name=".ui.SearchPassesIntentService"/>
|
||||
<service android:name=".scan.SearchPassesIntentService"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.PassListActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppBaseThemeNoActionbar">
|
||||
android:theme="@style/AppThemeNoActionbar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -63,7 +64,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".ui.HelpActivity"
|
||||
android:theme="@style/AppBaseThemeNoActionbar"/>
|
||||
android:theme="@style/AppThemeNoActionbar"/>
|
||||
<activity
|
||||
android:name=".ui.PassEditActivity"/>
|
||||
<activity
|
||||
|
@ -122,39 +123,35 @@
|
|||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/vnd.espass-espass"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http" android:mimeType="application/vnd.espass-espass"/>
|
||||
<data android:scheme="https" android:mimeType="application/vnd.espass-espass"/>
|
||||
<data android:scheme="content" android:mimeType="application/vnd.espass-espass"/>
|
||||
<data android:scheme="file" android:mimeType="application/vnd.espass-espass"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="http" android:mimeType="application/vnd.espass-espass+zip"/>
|
||||
<data android:scheme="https" android:mimeType="application/vnd.espass-espass+zip"/>
|
||||
<data android:scheme="content" android:mimeType="application/vnd.espass-espass+zip"/>
|
||||
<data android:scheme="file" android:mimeType="application/vnd.espass-espass+zip"/>
|
||||
|
||||
<data android:mimeType="application/vnd.espass-espass+zip"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http" android:mimeType="application/vnd.apple.pkpass"/>
|
||||
<data android:scheme="https" android:mimeType="application/vnd.apple.pkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/vnd.apple.pkpass"/>
|
||||
<data android:scheme="file" android:mimeType="application/vnd.apple.pkpass"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="http" android:mimeType="application/pkpass"/>
|
||||
<data android:scheme="https" android:mimeType="application/pkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/pkpass"/>
|
||||
<data android:scheme="file" android:mimeType="application/pkpass"/>
|
||||
|
||||
<data android:mimeType="application/vnd.apple.pkpass"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http" android:mimeType="application/vndapplepkpass"/>
|
||||
<data android:scheme="https" android:mimeType="application/vndapplepkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/vndapplepkpass"/>
|
||||
<data android:scheme="file" android:mimeType="application/vndapplepkpass"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/vndapplepkpass"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
<data android:scheme="http" android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
<data android:scheme="https" android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
<data android:scheme="content" android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
<data android:scheme="file" android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
|
@ -608,17 +605,9 @@
|
|||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/x-pdf"/>
|
||||
<data android:scheme="content" android:mimeType="application/x-pdf"/>
|
||||
<data android:scheme="content" android:mimeType="application/pdf"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/pdf"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
|
@ -1063,22 +1052,6 @@
|
|||
android:pathPattern="/.*\\.pkpass"
|
||||
android:scheme="content"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/vnd.apple.pkpass"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data android:mimeType="application/vnd-com.apple.pkpass"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
@ -1250,8 +1223,7 @@
|
|||
android:host="*"
|
||||
android:pathPattern="/.*\\..*\\..*\\..*\\.pkpass"
|
||||
android:scheme="https"/>
|
||||
<data
|
||||
android:host="*"
|
||||
<data android:host="*"
|
||||
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pkpass"
|
||||
android:scheme="https"/>
|
||||
<data
|
||||
|
@ -1604,10 +1576,31 @@
|
|||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data
|
||||
android:host="services.aircanada.com"
|
||||
android:host="mci.aircanada.com"
|
||||
android:scheme="http"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data
|
||||
android:host="mci.aircanada.com"
|
||||
android:scheme="https"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
|
||||
<data
|
||||
android:host="services.aircanada.com"
|
||||
android:scheme="http"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
@ -1799,7 +1792,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".ui.PassViewActivity"
|
||||
android:theme="@style/AppBaseThemeNoActionbar"
|
||||
android:theme="@style/AppThemeNoActionbar"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".ui.PassListActivity">
|
||||
|
@ -1811,6 +1804,7 @@
|
|||
<activity
|
||||
android:name=".ui.FullscreenBarcodeActivity"
|
||||
android:label="@string/app_name"/>
|
||||
<activity android:name=".scan.PassScanActivity" />
|
||||
|
||||
</application>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005-2014, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -35,6 +35,7 @@ import java.util.List;
|
|||
* <p/>
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
public class CharsetDetector {
|
||||
|
||||
// Question: Should we have getters corresponding to the setters for input text
|
||||
|
@ -182,7 +183,7 @@ public class CharsetDetector {
|
|||
* @stable ICU 3.4
|
||||
*/
|
||||
public CharsetMatch[] detectAll() {
|
||||
ArrayList<CharsetMatch> matches = new ArrayList<CharsetMatch>();
|
||||
ArrayList<CharsetMatch> matches = new ArrayList<>();
|
||||
|
||||
MungeInput(); // Strip html markup, collect byte stats.
|
||||
|
||||
|
@ -344,7 +345,7 @@ public class CharsetDetector {
|
|||
* it by removing what appears to be html markup.
|
||||
*/
|
||||
private void MungeInput() {
|
||||
int srci = 0;
|
||||
int srci;
|
||||
int dsti = 0;
|
||||
byte b;
|
||||
boolean inMarkup = false;
|
||||
|
@ -473,7 +474,7 @@ public class CharsetDetector {
|
|||
private static final List<CSRecognizerInfo> ALL_CS_RECOGNIZERS;
|
||||
|
||||
static {
|
||||
List<CSRecognizerInfo> list = new ArrayList<CSRecognizerInfo>();
|
||||
List<CSRecognizerInfo> list = new ArrayList<>();
|
||||
|
||||
list.add(new CSRecognizerInfo(new CharsetRecog_UTF8(), true));
|
||||
list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_BE(), true));
|
||||
|
@ -522,7 +523,7 @@ public class CharsetDetector {
|
|||
*/
|
||||
@Deprecated
|
||||
public String[] getDetectableCharsets() {
|
||||
List<String> csnames = new ArrayList<String>(ALL_CS_RECOGNIZERS.size());
|
||||
List<String> csnames = new ArrayList<>(ALL_CS_RECOGNIZERS.size());
|
||||
for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {
|
||||
CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i);
|
||||
boolean active = (fEnabledRecognizers == null) ? rcinfo.isDefaultEnabled : fEnabledRecognizers[i];
|
||||
|
@ -530,7 +531,7 @@ public class CharsetDetector {
|
|||
csnames.add(rcinfo.recognizer.getName());
|
||||
}
|
||||
}
|
||||
return csnames.toArray(new String[csnames.size()]);
|
||||
return csnames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005-2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -26,6 +26,7 @@ import java.io.Reader;
|
|||
*
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
public class CharsetMatch implements Comparable<CharsetMatch> {
|
||||
|
||||
|
||||
|
@ -85,13 +86,13 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
|
|||
* @stable ICU 3.4
|
||||
*/
|
||||
public String getString(int maxLength) throws java.io.IOException {
|
||||
String result = null;
|
||||
String result;
|
||||
if (fInputStream != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] buffer = new char[1024];
|
||||
Reader reader = getReader();
|
||||
int max = maxLength < 0? Integer.MAX_VALUE : maxLength;
|
||||
int bytesRead = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) {
|
||||
sb.append(buffer, 0, bytesRead);
|
||||
|
@ -108,6 +109,7 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
|
|||
* be used to open a charset (e.g. IBM424_rtl). The ending '_rtl' or 'ltr'
|
||||
* should be stripped off before creating the string.
|
||||
*/
|
||||
//noinspection IndexOfReplaceableByContains
|
||||
int startSuffix = name.indexOf("_rtl") < 0 ? name.indexOf("_ltr") : name.indexOf("_rtl");
|
||||
if (startSuffix > 0) {
|
||||
name = name.substring(0, startSuffix);
|
||||
|
@ -234,7 +236,7 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
|
|||
// If user gave us a byte array, this is it.
|
||||
private int fRawLength; // Length of data in fRawInput array.
|
||||
|
||||
private InputStream fInputStream = null; // User's input stream, or null if the user
|
||||
private InputStream fInputStream; // User's input stream, or null if the user
|
||||
// gave us a byte array.
|
||||
|
||||
private String fCharsetName; // The name of the charset this CharsetMatch
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
/**
|
||||
|
@ -14,6 +14,7 @@ package com.ibm.icu.text;
|
|||
*
|
||||
* The separate classes are nested within this class.
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
abstract class CharsetRecog_2022 extends CharsetRecognizer {
|
||||
|
||||
|
||||
|
@ -44,7 +45,7 @@ abstract class CharsetRecog_2022 extends CharsetRecognizer {
|
|||
byte [] seq = escapeSequences[escN];
|
||||
|
||||
if ((textLen - i) < seq.length) {
|
||||
continue checkEscapes;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j=1; j<seq.length; j++) {
|
||||
|
@ -92,9 +93,6 @@ abstract class CharsetRecog_2022 extends CharsetRecognizer {
|
|||
return quality;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static class CharsetRecog_2022JP extends CharsetRecog_2022 {
|
||||
private byte [] [] escapeSequences = {
|
||||
{0x1b, 0x24, 0x28, 0x43}, // KS X 1001:1992
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
/**
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2014, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
/**
|
||||
* Charset recognizer for UTF-8
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
class CharsetRecog_UTF8 extends CharsetRecognizer {
|
||||
|
||||
String getName() {
|
||||
|
@ -24,7 +25,7 @@ class CharsetRecog_UTF8 extends CharsetRecognizer {
|
|||
int numInvalid = 0;
|
||||
byte input[] = det.fRawInput;
|
||||
int i;
|
||||
int trailBytes = 0;
|
||||
int trailBytes;
|
||||
int confidence;
|
||||
|
||||
if (det.fRawLength >= 3 &&
|
||||
|
|
|
@ -12,6 +12,7 @@ package com.ibm.icu.text;
|
|||
* This class matches UTF-16 and UTF-32, both big- and little-endian. The
|
||||
* BOM will be used if it is present.
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
abstract class CharsetRecog_Unicode extends CharsetRecognizer {
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
@ -171,7 +172,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
|
|||
{
|
||||
int getChar(byte[] input, int index)
|
||||
{
|
||||
return (input[index + 0] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
|
||||
return (input[index] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
|
||||
(input[index + 2] & 0xFF) << 8 | (input[index + 3] & 0xFF);
|
||||
}
|
||||
|
||||
|
@ -187,7 +188,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
|
|||
int getChar(byte[] input, int index)
|
||||
{
|
||||
return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 |
|
||||
(input[index + 1] & 0xFF) << 8 | (input[index + 0] & 0xFF);
|
||||
(input[index + 1] & 0xFF) << 8 | (input[index] & 0xFF);
|
||||
}
|
||||
|
||||
String getName()
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
/*
|
||||
****************************************************************************
|
||||
* Copyright (C) 2005-2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
****************************************************************************
|
||||
*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
|
@ -21,6 +20,7 @@ import java.util.Arrays;
|
|||
* encodings to be checked. The specific encoding being recognized
|
||||
* is determined by subclass.
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
||||
|
||||
/**
|
||||
|
@ -157,8 +157,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
|||
done = true;
|
||||
return -1;
|
||||
}
|
||||
int byteValue = (int)det.fRawInput[nextIndex++] & 0x00ff;
|
||||
return byteValue;
|
||||
return (int)det.fRawInput[nextIndex++] & 0x00ff;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,9 +320,9 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
|||
boolean nextChar(iteratedChar it, CharsetDetector det) {
|
||||
it.index = it.nextIndex;
|
||||
it.error = false;
|
||||
int firstByte = 0;
|
||||
int secondByte = 0;
|
||||
int thirdByte = 0;
|
||||
int firstByte;
|
||||
int secondByte;
|
||||
int thirdByte;
|
||||
//int fourthByte = 0;
|
||||
|
||||
buildChar: {
|
||||
|
@ -372,7 +371,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
|||
}
|
||||
}
|
||||
|
||||
return (it.done == false);
|
||||
return (!it.done);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -461,10 +460,10 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
|||
boolean nextChar(iteratedChar it, CharsetDetector det) {
|
||||
it.index = it.nextIndex;
|
||||
it.error = false;
|
||||
int firstByte = 0;
|
||||
int secondByte = 0;
|
||||
int thirdByte = 0;
|
||||
int fourthByte = 0;
|
||||
int firstByte;
|
||||
int secondByte;
|
||||
int thirdByte;
|
||||
int fourthByte;
|
||||
|
||||
buildChar: {
|
||||
firstByte = it.charValue = it.nextByte(det);
|
||||
|
@ -483,7 +482,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
|||
secondByte = it.nextByte(det);
|
||||
it.charValue = (it.charValue << 8) | secondByte;
|
||||
|
||||
if (firstByte >= 0x81 && firstByte <= 0xFE) {
|
||||
if (firstByte <= 0xFE) {
|
||||
// Two byte Char
|
||||
if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >=80 && secondByte <=0xFE)) {
|
||||
break buildChar;
|
||||
|
@ -504,11 +503,10 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
|
|||
}
|
||||
|
||||
it.error = true;
|
||||
break buildChar;
|
||||
}
|
||||
}
|
||||
|
||||
return (it.done == false);
|
||||
return !it.done;
|
||||
}
|
||||
|
||||
static int [] commonChars =
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
/*
|
||||
****************************************************************************
|
||||
* Copyright (C) 2005-2013, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
************************************************************************** *
|
||||
*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
|
||||
package com.ibm.icu.text;
|
||||
|
||||
/**
|
||||
* This class recognizes single-byte encodings. Because the encoding scheme is so
|
||||
* simple, language statistics are used to do the matching.
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
abstract class CharsetRecog_sbcs extends CharsetRecognizer {
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
@ -25,7 +24,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
|
|||
private static final int N_GRAM_MASK = 0xFFFFFF;
|
||||
|
||||
protected int byteIndex = 0;
|
||||
private int ngram = 0;
|
||||
private int ngram;
|
||||
|
||||
private int[] ngramList;
|
||||
protected byte[] byteMap;
|
||||
|
@ -161,7 +160,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
|
|||
return (int) (rawPercent * 300.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class NGramParser_IBM420 extends NGramParser
|
||||
{
|
||||
private byte alef = 0x00;
|
||||
|
@ -273,7 +272,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
|
|||
return parser.parse(det, spaceChar);
|
||||
}
|
||||
|
||||
int matchIBM420(CharsetDetector det, int[] ngrams, byte[] byteMap, byte spaceChar){
|
||||
int matchIBM420(CharsetDetector det, int[] ngrams, byte[] byteMap, @SuppressWarnings("SameParameterValue") byte spaceChar){
|
||||
NGramParser_IBM420 parser = new NGramParser_IBM420(ngrams, byteMap);
|
||||
return parser.parse(det, spaceChar);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005-2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
/*
|
||||
*******************************************************************************
|
||||
* Copyright (C) 2005 - 2012, International Business Machines Corporation and *
|
||||
* others. All Rights Reserved. *
|
||||
*******************************************************************************
|
||||
*/
|
||||
package com.ibm.icu.text;
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,7 @@ package com.ibm.icu.text;
|
|||
* The WILL be shared by multiple instances of CharsetDetector.
|
||||
* They encapsulate const charset-specific information.
|
||||
*/
|
||||
@SuppressWarnings("ALL")
|
||||
abstract class CharsetRecognizer {
|
||||
/**
|
||||
* Get the IANA name of this charset.
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package org.ligi.passandroid;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import org.ligi.tracedroid.TraceDroid;
|
||||
import org.ligi.tracedroid.logging.Log;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
private static AppComponent component;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
component = createComponent();
|
||||
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
installLeakCanary();
|
||||
AndroidThreeTen.init(this);
|
||||
initTraceDroid();
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(component.settings().getNightMode());
|
||||
}
|
||||
|
||||
public void installLeakCanary() {
|
||||
LeakCanary.install(this);
|
||||
}
|
||||
|
||||
public AppComponent createComponent() {
|
||||
return DaggerAppComponent.builder().appModule(new AppModule(this)).trackerModule(new TrackerModule(this)).build();
|
||||
}
|
||||
|
||||
private void initTraceDroid() {
|
||||
TraceDroid.init(this);
|
||||
Log.setTAG("PassAndroid");
|
||||
}
|
||||
|
||||
public static AppComponent component() {
|
||||
return component;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setComponent(AppComponent newComponent) {
|
||||
component = newComponent;
|
||||
}
|
||||
}
|
63
android/src/main/java/org/ligi/passandroid/App.kt
Normal file
63
android/src/main/java/org/ligi/passandroid/App.kt
Normal file
|
@ -0,0 +1,63 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.app.Application
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
import org.ligi.passandroid.json_adapter.ColorAdapter
|
||||
import org.ligi.passandroid.json_adapter.ZonedTimeAdapter
|
||||
import org.ligi.passandroid.model.AndroidFileSystemPassStore
|
||||
import org.ligi.passandroid.model.AndroidSettings
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
|
||||
import org.ligi.tracedroid.TraceDroid
|
||||
import org.ligi.tracedroid.logging.Log
|
||||
|
||||
open class App : Application() {
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(ZonedTimeAdapter())
|
||||
.add(ColorAdapter())
|
||||
.build()
|
||||
|
||||
private val settings by lazy { AndroidSettings(this) }
|
||||
|
||||
open fun createKoin(): Module {
|
||||
|
||||
return module {
|
||||
single { AndroidFileSystemPassStore(this@App, get(), moshi) as PassStore }
|
||||
single { settings as Settings }
|
||||
single { createTracker(this@App) }
|
||||
single { PassScanEventChannelProvider() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
startKoin {
|
||||
if (BuildConfig.DEBUG) androidLogger()
|
||||
androidContext(this@App)
|
||||
modules(createKoin())
|
||||
}
|
||||
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
AndroidThreeTen.init(this)
|
||||
initTraceDroid()
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(settings.getNightMode())
|
||||
}
|
||||
|
||||
|
||||
private fun initTraceDroid() {
|
||||
TraceDroid.init(this)
|
||||
Log.setTAG("PassAndroid")
|
||||
}
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import dagger.Component
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import org.ligi.passandroid.ui.*
|
||||
import org.ligi.passandroid.ui.edit.FieldsEditFragment
|
||||
import org.ligi.passandroid.ui.quirk_fix.USAirwaysLoadActivity
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(modules = arrayOf(AppModule::class, TrackerModule::class))
|
||||
interface AppComponent {
|
||||
|
||||
fun inject(passViewActivityBase: PassViewActivityBase)
|
||||
|
||||
fun inject(passListActivity: PassListActivity)
|
||||
|
||||
fun inject(passEditActivity: PassEditActivity)
|
||||
|
||||
fun inject(passAdapter: PassAdapter)
|
||||
|
||||
fun inject(passImportActivity: PassImportActivity)
|
||||
|
||||
fun inject(passMenuOptions: PassMenuOptions)
|
||||
|
||||
fun inject(searchPassesIntentService: SearchPassesIntentService)
|
||||
|
||||
fun inject(usAirwaysLoadActivity: USAirwaysLoadActivity)
|
||||
|
||||
fun inject(passAndroidActivity: PassAndroidActivity)
|
||||
|
||||
fun inject(passListFragment: PassListFragment)
|
||||
|
||||
fun inject(passNavigationView: PassNavigationView)
|
||||
|
||||
fun inject(touchImageActivity: TouchImageActivity)
|
||||
|
||||
fun inject(fieldsEditFragment: FieldsEditFragment)
|
||||
|
||||
fun passStore(): PassStore
|
||||
|
||||
fun tracker(): Tracker
|
||||
|
||||
fun settings(): Settings
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.ligi.passandroid.json_adapter.ColorAdapter
|
||||
import org.ligi.passandroid.json_adapter.ZonedTimeAdapter
|
||||
import org.ligi.passandroid.model.AndroidFileSystemPassStore
|
||||
import org.ligi.passandroid.model.AndroidSettings
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class AppModule(private val app: App) {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
internal fun providePassStore(settings: Settings, moshi: Moshi, bus: EventBus): PassStore {
|
||||
return AndroidFileSystemPassStore(app, settings, moshi, bus)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
internal fun provideMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(ZonedTimeAdapter())
|
||||
.add(ColorAdapter())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
internal fun provideSettings(): Settings {
|
||||
return AndroidSettings(app)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
internal fun provideSharedPreferences(): SharedPreferences {
|
||||
return PreferenceManager.getDefaultSharedPreferences(app)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
internal fun provideBus(): EventBus {
|
||||
return EventBus.getDefault()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.ligi.passandroid;
|
||||
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface Tracker {
|
||||
void trackException(String s, Throwable e, boolean fatal);
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
||||
class PassRefreshEvent(val pass: Pass)
|
|
@ -1,3 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
object PassStoreChangeEvent
|
|
@ -1,5 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
||||
class ScanFinishedEvent(val foundPasses: List<Pass>)
|
|
@ -1,3 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
class ScanProgressEvent(val message: String)
|
|
@ -3,22 +3,22 @@ package org.ligi.passandroid.functions
|
|||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import android.support.annotation.VisibleForTesting
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.model.pass.PassImpl
|
||||
|
||||
val DEFAULT_EVENT_LENGTH_IN_HOURS = 8L
|
||||
const val DEFAULT_EVENT_LENGTH_IN_HOURS = 8L
|
||||
|
||||
fun tryAddDateToCalendar(pass: Pass, contextView: View, timeSpan: PassImpl.TimeSpan) {
|
||||
if (pass.calendarTimespan == null) {
|
||||
AlertDialog.Builder(contextView.context).setMessage(R.string.expiration_date_to_calendar_warning_message)
|
||||
.setTitle(R.string.expiration_date_to_calendar_warning_title)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, which -> reallyAddToCalendar(pass, contextView, timeSpan) }
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> reallyAddToCalendar(pass, contextView, timeSpan) }
|
||||
.show()
|
||||
} else {
|
||||
reallyAddToCalendar(pass, contextView, timeSpan)
|
||||
|
|
|
@ -33,26 +33,26 @@ fun generateBarCodeBitmap(data: String, type: PassBarCodeFormat): Bitmap? {
|
|||
|
||||
// create buffered image to draw to
|
||||
// NTFS Bitmap.Config.ALPHA_8 sounds like an awesome idea - been there - done that ..
|
||||
val barcode_image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||
val barcodeImage = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||
|
||||
// iterate through the matrix and draw the pixels to the image
|
||||
for (y in 0..height - 1) {
|
||||
for (x in 0..width - 1) {
|
||||
barcode_image.setPixel(x, y, if (matrix.get(x, if (is1D) 0 else y)) 0 else 0xFFFFFF)
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
barcodeImage.setPixel(x, y, if (matrix.get(x, if (is1D) 0 else y)) 0 else 0xFFFFFF)
|
||||
}
|
||||
}
|
||||
|
||||
return barcode_image
|
||||
return barcodeImage
|
||||
} catch (e: com.google.zxing.WriterException) {
|
||||
Log.w("could not write image " + e)
|
||||
Log.w("could not write image: $e")
|
||||
// TODO check if we should better return some rescue Image here
|
||||
return null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.w("could not write image " + e)
|
||||
Log.w("could not write image: $e")
|
||||
return null
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
// happens for ITF barcode on certain inputs
|
||||
Log.w("could not write image " + e)
|
||||
Log.w("could not write image: $e")
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.ligi.passandroid.functions
|
||||
|
||||
import android.graphics.Color
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.support.annotation.StringRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.pass.PassType
|
||||
|
||||
|
|
|
@ -4,92 +4,69 @@ import android.content.Context
|
|||
import android.net.Uri
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.model.InputStreamWithSource
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
val IPHONE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
|
||||
const val IPHONE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
|
||||
|
||||
fun fromURI(context: Context, uri: Uri): InputStreamWithSource? {
|
||||
App.component().tracker().trackEvent("protocol", "to_inputstream", uri.scheme, null)
|
||||
when (uri.scheme) {
|
||||
"content" ->
|
||||
|
||||
return fromContent(context, uri)
|
||||
fun fromURI(context: Context, uri: Uri, tracker: Tracker): InputStreamWithSource? {
|
||||
tracker.trackEvent("protocol", "to_inputstream", uri.scheme, null)
|
||||
return when (uri.scheme) {
|
||||
"content" -> fromContent(context, uri)
|
||||
|
||||
"http", "https" ->
|
||||
// TODO check if SPDY should be here
|
||||
return fromOKHttp(uri)
|
||||
return fromOKHttp(uri, tracker)
|
||||
|
||||
"file" -> return getDefaultInputStreamForUri(uri)
|
||||
"file" -> getDefaultInputStreamForUri(uri)
|
||||
else -> {
|
||||
App.component().tracker().trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
|
||||
return getDefaultInputStreamForUri(uri)
|
||||
tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
|
||||
getDefaultInputStreamForUri(uri)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun fromOKHttp(uri: Uri): InputStreamWithSource? {
|
||||
try {
|
||||
val client = OkHttpClient()
|
||||
val url = URL(uri.toString())
|
||||
val requestBuilder = Request.Builder().url(url)
|
||||
private fun fromOKHttp(uri: Uri, tracker: Tracker): InputStreamWithSource? {
|
||||
val client = OkHttpClient()
|
||||
val url = URL(uri.toString())
|
||||
val requestBuilder = Request.Builder().url(url)
|
||||
|
||||
// fake to be an iPhone in some cases when the server decides to send no passbook
|
||||
// to android phones - but only do it then - we are proud to be Android ;-)
|
||||
val iPhoneFakeMap = mapOf(
|
||||
"air_canada" to "//m.aircanada.ca/ebp/",
|
||||
"air_canada2" to "//services.aircanada.com/ebp/",
|
||||
"icelandair" to "//checkin.si.amadeus.net",
|
||||
"mbk" to "//mbk.thy.com/",
|
||||
"heathrow" to "//passbook.heathrow.com/",
|
||||
"eventbrite" to "//www.eventbrite.com/passes/order"
|
||||
)
|
||||
// fake to be an iPhone in some cases when the server decides to send no passbook
|
||||
// to android phones - but only do it then - we are proud to be Android ;-)
|
||||
val iPhoneFakeMap = mapOf(
|
||||
"air_canada" to "//m.aircanada.ca/ebp/",
|
||||
"air_canada2" to "//services.aircanada.com/ebp/",
|
||||
"air_canada3" to "//mci.aircanada.com/mci/bp/",
|
||||
"icelandair" to "//checkin.si.amadeus.net",
|
||||
"mbk" to "//mbk.thy.com/",
|
||||
"heathrow" to "//passbook.heathrow.com/",
|
||||
"eventbrite" to "//www.eventbrite.com/passes/order"
|
||||
)
|
||||
|
||||
for ((key, value) in iPhoneFakeMap) {
|
||||
if (uri.toString().contains(value)) {
|
||||
App.component().tracker().trackEvent("quirk_fix", "ua_fake", key, null)
|
||||
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
|
||||
}
|
||||
for ((key, value) in iPhoneFakeMap) {
|
||||
if (uri.toString().contains(value)) {
|
||||
tracker.trackEvent("quirk_fix", "ua_fake", key, null)
|
||||
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
|
||||
}
|
||||
}
|
||||
|
||||
val request = requestBuilder.build()
|
||||
val request = requestBuilder.build()
|
||||
|
||||
val response = client.newCall(request).execute()
|
||||
val response = client.newCall(request).execute()
|
||||
|
||||
return InputStreamWithSource(uri.toString(), response.body().byteStream())
|
||||
} catch (e: MalformedURLException) {
|
||||
App.component().tracker().trackException("MalformedURLException in ImportAsyncTask", e, false)
|
||||
} catch (e: IOException) {
|
||||
App.component().tracker().trackException("IOException in ImportAsyncTask", e, false)
|
||||
val body = response.body()
|
||||
|
||||
if (body != null) {
|
||||
return InputStreamWithSource(uri.toString(), body.byteStream())
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fromContent(ctx: Context, uri: Uri): InputStreamWithSource? {
|
||||
try {
|
||||
return InputStreamWithSource(uri.toString(), ctx.contentResolver.openInputStream(uri)!!)
|
||||
} catch (e: FileNotFoundException) {
|
||||
App.component().tracker().trackException("FileNotFoundException in passImportActivity/ImportAsyncTask", e, false)
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fromContent(ctx: Context, uri: Uri) = ctx.contentResolver.openInputStream(uri)?.let {
|
||||
InputStreamWithSource(uri.toString(), it)
|
||||
}
|
||||
|
||||
|
||||
private fun getDefaultInputStreamForUri(uri: Uri): InputStreamWithSource? {
|
||||
try {
|
||||
return InputStreamWithSource(uri.toString(), BufferedInputStream(URL(uri.toString()).openStream(), 4096))
|
||||
} catch (e: IOException) {
|
||||
App.component().tracker().trackException("IOException in passImportActivity/ImportAsyncTask", e, false)
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
private fun getDefaultInputStreamForUri(uri: Uri) = InputStreamWithSource(uri.toString(), BufferedInputStream(URL(uri.toString()).openStream(), 4096))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.ligi.passandroid.functions
|
||||
|
||||
import android.app.Activity
|
||||
import android.support.design.widget.Snackbar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.PassClassifier
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
@ -9,7 +9,7 @@ import org.ligi.passandroid.model.pass.Pass
|
|||
fun moveWithUndoSnackbar(passClassifier: PassClassifier, pass: Pass, topic: String, activity: Activity) {
|
||||
val oldTopic = passClassifier.getTopic(pass, "")
|
||||
|
||||
Snackbar.make(activity.window.decorView.findViewById(R.id.fam), "Pass moved to " + topic, Snackbar.LENGTH_LONG)
|
||||
Snackbar.make(activity.window.decorView.findViewById(R.id.fam), "Pass moved to $topic", Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.undo) { passClassifier.moveToTopic(pass, oldTopic) }
|
||||
.show()
|
||||
passClassifier.moveToTopic(pass, topic)
|
||||
|
|
|
@ -15,7 +15,7 @@ import java.io.FileNotFoundException
|
|||
import java.io.FileOutputStream
|
||||
import java.util.*
|
||||
|
||||
val APP = "passandroid"
|
||||
const val APP = "passandroid"
|
||||
|
||||
fun createAndAddEmptyPass(passStore: PassStore, resources: Resources): Pass {
|
||||
val pass = createBasePass()
|
||||
|
|
|
@ -3,11 +3,16 @@ package org.ligi.passandroid.model
|
|||
import android.content.Context
|
||||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.Moshi
|
||||
import okio.Okio
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.ligi.passandroid.App
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.BuildConfig
|
||||
import org.ligi.passandroid.events.PassStoreChangeEvent
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.model.pass.PassImpl
|
||||
import org.ligi.passandroid.reader.AppleStylePassReader
|
||||
|
@ -15,13 +20,24 @@ import org.ligi.passandroid.reader.PassReader
|
|||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class AndroidFileSystemPassStore(private val context: Context, settings: Settings, private val moshi: Moshi, private val bus: EventBus) : PassStore {
|
||||
object PassStoreUpdateEvent
|
||||
|
||||
class AndroidFileSystemPassStore(
|
||||
private val context: Context,
|
||||
settings: Settings,
|
||||
private val moshi: Moshi
|
||||
) : PassStore, KoinComponent {
|
||||
|
||||
override val updateChannel = ConflatedBroadcastChannel<PassStoreUpdateEvent>()
|
||||
|
||||
private val path: File = settings.getPassesDir()
|
||||
|
||||
override val passMap = HashMap<String, Pass>()
|
||||
|
||||
override var currentPass: Pass? = null
|
||||
|
||||
private val tracker: Tracker by inject()
|
||||
|
||||
override val classifier: PassClassifier by lazy {
|
||||
val classificationFile = File(settings.getStateDir(), "classifier_state.json")
|
||||
FileBackedPassClassifier(classificationFile, this, moshi)
|
||||
|
@ -36,11 +52,11 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
pathForID.mkdirs()
|
||||
}
|
||||
|
||||
val buffer = Okio.buffer(Okio.sink(File(pathForID, "main.json")))
|
||||
val buffer = File(pathForID, "main.json").sink().buffer()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
val of = com.squareup.moshi.JsonWriter.of(buffer)
|
||||
of.setIndent(" ")
|
||||
of.indent = " "
|
||||
jsonAdapter.toJson(of, pass as PassImpl)
|
||||
buffer.close()
|
||||
of.close()
|
||||
|
@ -67,9 +83,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
val jsonAdapter = moshi.adapter(PassImpl::class.java)
|
||||
dirty = false
|
||||
try {
|
||||
result = jsonAdapter.fromJson(Okio.buffer(Okio.source(file)))
|
||||
result = jsonAdapter.fromJson(file.source().buffer())
|
||||
} catch (ignored: JsonDataException) {
|
||||
App.component().tracker().trackException("invalid main.json", false)
|
||||
tracker.trackException("invalid main.json", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,14 +95,14 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
}
|
||||
|
||||
if (result == null && File(pathForID, "pass.json").exists()) {
|
||||
result = AppleStylePassReader.read(pathForID, language, context)
|
||||
result = AppleStylePassReader.read(pathForID, language, context, tracker)
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
if (dirty) {
|
||||
save(result)
|
||||
}
|
||||
passMap.put(id, result)
|
||||
passMap[id] = result
|
||||
notifyChange()
|
||||
}
|
||||
|
||||
|
@ -112,7 +128,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
}
|
||||
|
||||
override fun notifyChange() {
|
||||
bus.post(PassStoreChangeEvent)
|
||||
GlobalScope.launch {
|
||||
updateChannel.send(PassStoreUpdateEvent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncPassStoreWithClassifier(defaultTopic: String) {
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.ligi.passandroid.model
|
|||
|
||||
import android.content.Context
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.R.string.preference_key_autolight
|
||||
import org.ligi.passandroid.R.string.preference_key_condensed
|
||||
|
@ -11,12 +11,12 @@ import java.io.File
|
|||
|
||||
class AndroidSettings(val context: Context) : Settings {
|
||||
|
||||
internal val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||
private val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||
|
||||
override fun getSortOrder(): PassSortOrder {
|
||||
val key = context.getString(R.string.preference_key_sort)
|
||||
val stringValue = sharedPreferences.getString(key, "0")
|
||||
val id = Integer.valueOf(stringValue)!!
|
||||
val id = Integer.valueOf(stringValue!!)
|
||||
|
||||
return PassSortOrder.values().first { it.int == id }
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
|
|||
tryToFindDate(pass)
|
||||
}
|
||||
|
||||
fun tryToFindDate(pass: PassImpl) {
|
||||
private fun tryToFindDate(pass: PassImpl) {
|
||||
|
||||
if (pass.calendarTimespan == null) {
|
||||
val foundDate = pass.fields.filter { "date" == it.key }.map {
|
||||
|
@ -44,11 +44,11 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
|
|||
}
|
||||
|
||||
private fun getPassFieldForKey(pass: PassImpl, key: String): PassField? {
|
||||
return pass.fields.firstOrNull() { it.key != null && it.key == key }
|
||||
return pass.fields.firstOrNull { it.key != null && it.key == key }
|
||||
}
|
||||
|
||||
private fun getPassFieldThatMatchesLabel(pass: PassImpl, matcher: String): PassField? {
|
||||
return pass.fields.firstOrNull() {
|
||||
return pass.fields.firstOrNull {
|
||||
val label = it.label
|
||||
label != null && label.matches(matcher.toRegex())
|
||||
}
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package org.ligi.passandroid.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.ibm.icu.text.CharsetDetector;
|
||||
import com.ibm.icu.text.CharsetMatch;
|
||||
|
||||
import org.ligi.passandroid.App;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.HashMap;
|
||||
|
||||
@VisibleForTesting
|
||||
public class AppleStylePassTranslation extends HashMap<String, String> {
|
||||
|
||||
public String translate(String key) {
|
||||
|
@ -63,6 +58,13 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
|
|||
dataInputStream.readFully(fileData);
|
||||
dataInputStream.close();
|
||||
|
||||
if (fileData[0] == (byte) 0xEF && fileData[1] == (byte) 0xBB && fileData[2] == (byte) 0xBF) {
|
||||
final byte[] crop = new byte[fileData.length - 3];
|
||||
System.arraycopy(fileData, 3, crop, 0, crop.length);
|
||||
//noinspection CharsetObjectCanBeUsed
|
||||
return new String(crop, "utf-8");
|
||||
}
|
||||
|
||||
final CharsetMatch match = new CharsetDetector().setText(fileData).detect();
|
||||
|
||||
if (match != null) try {
|
||||
|
@ -71,7 +73,6 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
|
|||
}
|
||||
return new String(fileData);
|
||||
} catch (Throwable e) {
|
||||
App.component().tracker().trackException("problem_reading_translation", e, false);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ open class PassClassifier(val topicByIdMap: MutableMap<String, String>, private
|
|||
}
|
||||
|
||||
fun moveToTopic(pass: Pass, newTopic: String) {
|
||||
topicByIdMap.put(pass.id, newTopic)
|
||||
topicByIdMap[pass.id] = newTopic
|
||||
|
||||
processDataChange()
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ open class PassClassifier(val topicByIdMap: MutableMap<String, String>, private
|
|||
}
|
||||
|
||||
fun getPassListByTopic(topic: String): List<Pass> {
|
||||
return topicByIdMap.filter { it.value.equals(topic) }.map { passStore.getPassbookForId(it.key) }.filterNotNull()
|
||||
return topicByIdMap.filter { it.value == topic }.map { passStore.getPassbookForId(it.key) }.filterNotNull()
|
||||
}
|
||||
|
||||
fun getTopic(pass: Pass, default: String): String {
|
||||
|
@ -38,7 +38,7 @@ open class PassClassifier(val topicByIdMap: MutableMap<String, String>, private
|
|||
}
|
||||
|
||||
if (!default.isEmpty()) {
|
||||
topicByIdMap.put(id, default)
|
||||
topicByIdMap[id] = default
|
||||
processDataChange()
|
||||
}
|
||||
return default
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.ligi.passandroid.model
|
||||
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import java.io.File
|
||||
|
||||
interface PassStore {
|
||||
|
||||
val updateChannel: BroadcastChannel<PassStoreUpdateEvent>
|
||||
|
||||
fun save(pass: Pass)
|
||||
|
||||
fun getPassbookForId(id: String): Pass?
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.ligi.passandroid.model.comparator.PassSortOrder
|
|||
import org.ligi.passandroid.model.pass.Pass
|
||||
import java.util.*
|
||||
|
||||
class PassStoreProjection(private val passStore: PassStore, val topic: String, private val passSortOrder: PassSortOrder? = null) {
|
||||
class PassStoreProjection(private val passStore: PassStore, private val topic: String, private val passSortOrder: PassSortOrder? = null) {
|
||||
|
||||
var passList: List<Pass> = ArrayList()
|
||||
private set
|
||||
|
|
|
@ -1,22 +1,12 @@
|
|||
package org.ligi.passandroid.model
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import org.ligi.passandroid.Tracker
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class PastLocationsStore
|
||||
@Inject
|
||||
constructor(private val sharedPreferences: SharedPreferences, private val tracker: Tracker) {
|
||||
class PastLocationsStore constructor(private val sharedPreferences: SharedPreferences, private val tracker: Tracker) {
|
||||
|
||||
@TargetApi(11)
|
||||
fun putLocation(path: String) {
|
||||
if (Build.VERSION.SDK_INT < 11) {
|
||||
// feature not available for these versions
|
||||
return
|
||||
}
|
||||
val pastLocations = sharedPreferences.getStringSet(KEY_PAST_LOCATIONS, HashSet<String>())
|
||||
|
||||
if (pastLocations!!.size >= MAX_ELEMENTS) {
|
||||
|
@ -43,17 +33,11 @@ constructor(private val sharedPreferences: SharedPreferences, private val tracke
|
|||
|
||||
// feature not available for these versions
|
||||
val locations: Set<String>
|
||||
@TargetApi(11)
|
||||
get() {
|
||||
if (Build.VERSION.SDK_INT < 11) {
|
||||
return HashSet()
|
||||
}
|
||||
return sharedPreferences.getStringSet(KEY_PAST_LOCATIONS, HashSet<String>())
|
||||
}
|
||||
get() = sharedPreferences.getStringSet(KEY_PAST_LOCATIONS, emptySet<String>())?: emptySet()
|
||||
|
||||
companion object {
|
||||
|
||||
val KEY_PAST_LOCATIONS = "past_locations"
|
||||
val MAX_ELEMENTS = 5
|
||||
const val KEY_PAST_LOCATIONS = "past_locations"
|
||||
const val MAX_ELEMENTS = 5
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.chibatching.kotpref.KotprefModel
|
|||
|
||||
object State : KotprefModel() {
|
||||
|
||||
var lastSelectedTab: Int by intPrefVar()
|
||||
var lastSelectedPassUUID: String by stringPrefVar()
|
||||
var lastSelectedTab: Int by intPref()
|
||||
var lastSelectedPassUUID: String by stringPref()
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class DirectionAwarePassByTimeComparator(private val direction: Int) : PassByTim
|
|||
}
|
||||
|
||||
companion object {
|
||||
val DIRECTION_DESC = -1
|
||||
val DIRECTION_ASC = 1
|
||||
const val DIRECTION_DESC = -1
|
||||
const val DIRECTION_ASC = 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import java.util.*
|
|||
open class PassByTimeComparator : Comparator<Pass> {
|
||||
|
||||
override fun compare(lhs: Pass, rhs: Pass): Int {
|
||||
return calculateCompareForNullValues(lhs, rhs, { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
|
||||
return@calculateCompareForNullValues rightDate.compareTo(leftDate)
|
||||
})
|
||||
return calculateCompareForNullValues(lhs, rhs) { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
|
||||
return@calculateCompareForNullValues leftDate.compareTo(rightDate)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun calculateCompareForNullValues(lhs: Pass, rhs: Pass, foo: (leftDate: ZonedDateTime, rightDate: ZonedDateTime) -> Int): Int {
|
||||
|
|
|
@ -11,10 +11,10 @@ class PassByTypeFirstAndTimeSecondComparator : Comparator<Pass> {
|
|||
override fun compare(lhs: Pass, rhs: Pass): Int {
|
||||
val compareResult = lhs.type.compareTo(rhs.type)
|
||||
|
||||
if (compareResult != 0) {
|
||||
return compareResult
|
||||
return if (compareResult != 0) {
|
||||
compareResult
|
||||
} else {
|
||||
return passByTimeComparator.compare(lhs, rhs)
|
||||
passByTimeComparator.compare(lhs, rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,17 +4,15 @@ import org.ligi.passandroid.model.pass.Pass
|
|||
import java.util.*
|
||||
|
||||
enum class PassSortOrder constructor(val int: Int) {
|
||||
DATE_DESC(-1),
|
||||
DATE_ASC(0),
|
||||
DATE_DESC(0),
|
||||
DATE_ASC(-1),
|
||||
TYPE(1),
|
||||
DATE_DIFF(2);
|
||||
|
||||
fun toComparator(): Comparator<Pass> {
|
||||
when (this) {
|
||||
TYPE -> return PassByTypeFirstAndTimeSecondComparator()
|
||||
DATE_DESC -> return DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_DESC)
|
||||
DATE_DIFF -> return PassTemporalDistanceComparator()
|
||||
DATE_ASC -> return DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_ASC)
|
||||
}
|
||||
fun toComparator(): Comparator<Pass> = when (this) {
|
||||
TYPE -> PassByTypeFirstAndTimeSecondComparator()
|
||||
DATE_DESC -> DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_DESC)
|
||||
DATE_DIFF -> PassTemporalDistanceComparator()
|
||||
DATE_ASC -> DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_ASC)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import org.threeten.bp.ZonedDateTime
|
|||
class PassTemporalDistanceComparator : PassByTimeComparator() {
|
||||
|
||||
override fun compare(lhs: Pass, rhs: Pass): Int {
|
||||
return calculateCompareForNullValues(lhs, rhs, { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
|
||||
return calculateCompareForNullValues(lhs, rhs) { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
|
||||
val durationLeft = Duration.between(LocalDateTime.now(), leftDate).abs()
|
||||
val durationRight = Duration.between(LocalDateTime.now(), rightDate).abs()
|
||||
|
||||
return@calculateCompareForNullValues durationLeft.compareTo(durationRight)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,25 +2,30 @@ package org.ligi.passandroid.model.pass
|
|||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import org.ligi.passandroid.App
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.functions.generateBitmapDrawable
|
||||
import org.ligi.tracedroid.logging.Log
|
||||
import java.util.*
|
||||
|
||||
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) : KoinComponent {
|
||||
|
||||
val tracker: Tracker by inject ()
|
||||
var alternativeText: String? = null
|
||||
|
||||
fun getBitmap(resources: Resources): BitmapDrawable? {
|
||||
if (message == null) {
|
||||
// no message -> no barcode
|
||||
App.component().tracker().trackException("No Barcode in pass - strange", false)
|
||||
tracker.trackException("No Barcode in pass - strange", false)
|
||||
return null
|
||||
}
|
||||
|
||||
if (format == null) {
|
||||
Log.w("Barcode format is null - fallback to QR")
|
||||
App.component().tracker().trackException("Barcode format is null - fallback to QR", false)
|
||||
tracker.trackException("Barcode format is null - fallback to QR", false)
|
||||
return generateBitmapDrawable(resources, message, PassBarCodeFormat.QR_CODE)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.ligi.passandroid.model.pass
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.support.annotation.StringDef
|
||||
import androidx.annotation.StringDef
|
||||
import org.ligi.passandroid.model.PassBitmapDefinitions.*
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ enum class PassBarCodeFormat {
|
|||
CODE_39,
|
||||
CODE_128,
|
||||
DATA_MATRIX,
|
||||
EAN_8,
|
||||
EAN_13,
|
||||
ITF,
|
||||
PDF_417,
|
||||
|
@ -23,6 +24,7 @@ enum class PassBarCodeFormat {
|
|||
CODE_39 -> BarcodeFormat.CODE_39
|
||||
CODE_128 -> BarcodeFormat.CODE_128
|
||||
DATA_MATRIX -> BarcodeFormat.DATA_MATRIX
|
||||
EAN_8 -> BarcodeFormat.EAN_8
|
||||
EAN_13 -> BarcodeFormat.EAN_13
|
||||
ITF -> BarcodeFormat.ITF
|
||||
PDF_417 -> BarcodeFormat.PDF_417
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package org.ligi.passandroid.model.pass
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.support.annotation.StringRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PassField(var key: String?, var label: String?, var value: String?, var hide: Boolean) {
|
||||
|
||||
fun toHtmlSnippet(): String {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.ligi.passandroid.model.pass
|
|||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.threeten.bp.ZonedDateTime
|
||||
|
@ -10,6 +11,7 @@ import java.io.FileInputStream
|
|||
import java.io.FileNotFoundException
|
||||
import java.util.*
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PassImpl(override val id: String) : Pass {
|
||||
|
||||
|
||||
|
@ -37,7 +39,10 @@ class PassImpl(override val id: String) : Pass {
|
|||
return field
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class TimeRepeat(val offset: Int, val count: Int)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class TimeSpan(val from: ZonedDateTime? = null, val to: ZonedDateTime? = null, val repeat: TimeRepeat? = null)
|
||||
|
||||
override var validTimespans: List<TimeSpan> = ArrayList()
|
||||
|
@ -59,13 +64,13 @@ class PassImpl(override val id: String) : Pass {
|
|||
override var passIdent: String? = null
|
||||
|
||||
override fun getBitmap(passStore: PassStore, @Pass.PassBitmap passBitmap: String): Bitmap? {
|
||||
try {
|
||||
return try {
|
||||
val file = File(passStore.getPathForID(id), passBitmap + FILETYPE_IMAGES)
|
||||
return BitmapFactory.decodeStream(FileInputStream(file))
|
||||
BitmapFactory.decodeStream(FileInputStream(file))
|
||||
} catch (expectedInSomeCases_willJustReturnNull: FileNotFoundException) {
|
||||
return null
|
||||
null
|
||||
} catch (e: OutOfMemoryError) {
|
||||
return null
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -84,7 +89,7 @@ class PassImpl(override val id: String) : Pass {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val FILETYPE_IMAGES = ".png"
|
||||
const val FILETYPE_IMAGES = ".png"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.ligi.passandroid.model.pass
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class PassLocation {
|
||||
|
||||
var name: String? = null
|
||||
|
|
|
@ -69,7 +69,7 @@ class PassPrintDocumentAdapter(private val context: Context, private val pass: P
|
|||
val centerPaint = Paint()
|
||||
centerPaint.textAlign = Paint.Align.CENTER
|
||||
|
||||
canvas.drawText(pass.description, canvas.width / 2f, centerPaint.textSize, centerPaint)
|
||||
canvas.drawText(pass.description!!, canvas.width / 2f, centerPaint.textSize, centerPaint)
|
||||
var currentBottom = centerPaint.textSize * 3
|
||||
|
||||
val barCode = pass.barCode
|
||||
|
@ -83,14 +83,14 @@ class PassPrintDocumentAdapter(private val context: Context, private val pass: P
|
|||
|
||||
val destRect = Rect(0, 0, (bitmap.width * ratio).toInt(), (bitmap.height * ratio).toInt())
|
||||
|
||||
destRect.offset(((canvas.width - destRect.width()) / 2).toInt(), currentBottom.toInt())
|
||||
destRect.offset((canvas.width - destRect.width()) / 2, currentBottom.toInt())
|
||||
|
||||
currentBottom += destRect.bottom
|
||||
|
||||
canvas.drawBitmap(bitmap, srcRect, destRect, centerPaint)
|
||||
|
||||
if (barCode.alternativeText != null) {
|
||||
canvas.drawText(barCode.alternativeText, destRect.centerX().toFloat(), destRect.bottom.toFloat() + 7 + centerPaint.textSize, centerPaint)
|
||||
canvas.drawText(barCode.alternativeText!!, destRect.centerX().toFloat(), destRect.bottom.toFloat() + 7 + centerPaint.textSize, centerPaint)
|
||||
currentBottom += 7 + centerPaint.textSize * 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import android.graphics.Color
|
|||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.ligi.kaxt.parseColor
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.functions.getHumanCategoryString
|
||||
import org.ligi.passandroid.functions.readJSONSafely
|
||||
import org.ligi.passandroid.model.ApplePassbookQuirkCorrector
|
||||
|
@ -26,116 +26,116 @@ import java.util.*
|
|||
|
||||
object AppleStylePassReader {
|
||||
|
||||
fun read(passFile: File, language: String, context: Context): Pass? {
|
||||
fun read(passFile: File, language: String, context: Context, tracker: Tracker): Pass? {
|
||||
|
||||
val translation = AppleStylePassTranslation()
|
||||
|
||||
val pass = PassImpl(passFile.name)
|
||||
|
||||
var pass_json: JSONObject? = null
|
||||
var passJSON: JSONObject? = null
|
||||
|
||||
val localized_path = findLocalizedPath(passFile, language)
|
||||
val localizedPath = findLocalizedPath(passFile, language, tracker)
|
||||
|
||||
if (localized_path != null) {
|
||||
val file = File(localized_path, "pass.strings")
|
||||
if (localizedPath != null) {
|
||||
val file = File(localizedPath, "pass.strings")
|
||||
translation.loadFromFile(file)
|
||||
}
|
||||
|
||||
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_ICON)
|
||||
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_LOGO)
|
||||
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_STRIP)
|
||||
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_THUMBNAIL)
|
||||
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_FOOTER)
|
||||
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_ICON)
|
||||
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_LOGO)
|
||||
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_STRIP)
|
||||
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_THUMBNAIL)
|
||||
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_FOOTER)
|
||||
|
||||
val file = File(passFile, "pass.json")
|
||||
|
||||
try {
|
||||
val plainJsonString = AppleStylePassTranslation.readFileAsStringGuessEncoding(file)
|
||||
pass_json = readJSONSafely(plainJsonString)
|
||||
passJSON = readJSONSafely(plainJsonString)
|
||||
} catch (e: Exception) {
|
||||
Log.i("PassParse Exception " + e)
|
||||
Log.i("PassParse Exception: $e")
|
||||
}
|
||||
|
||||
if (pass_json == null) {
|
||||
if (passJSON == null) {
|
||||
// I had got a strange passbook with UCS-2 which could not be parsed before
|
||||
// was searching for a auto-detection, but could not find one with support for this encoding
|
||||
// and the right license
|
||||
|
||||
for (charset in Charset.availableCharsets().values) {
|
||||
try {
|
||||
val json_str = file.bufferedReader(charset).readText()
|
||||
pass_json = readJSONSafely(json_str)
|
||||
val json = file.bufferedReader(charset).readText()
|
||||
passJSON = readJSONSafely(json)
|
||||
} catch (ignored: Exception) {
|
||||
// we try with next charset
|
||||
}
|
||||
|
||||
if (pass_json != null) {
|
||||
if (passJSON != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass_json == null) {
|
||||
if (passJSON == null) {
|
||||
Log.w("could not load pass.json from passcode ")
|
||||
App.component().tracker().trackEvent("problem_event", "pass", "without_pass_json", null)
|
||||
tracker.trackEvent("problem_event", "pass", "without_pass_json", null)
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
val barcode_json = pass_json.getBarcodeJson()
|
||||
if (barcode_json != null) {
|
||||
val barcodeFormatString = barcode_json.getString("format")
|
||||
val barcodeJSON = passJSON.getBarcodeJson()
|
||||
if (barcodeJSON != null) {
|
||||
val barcodeFormatString = barcodeJSON.getString("format")
|
||||
|
||||
App.component().tracker().trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
|
||||
tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
|
||||
val barcodeFormat = BarCode.getFormatFromString(barcodeFormatString)
|
||||
val barCode = BarCode(barcodeFormat, barcode_json.getString("message"))
|
||||
val barCode = BarCode(barcodeFormat, barcodeJSON.getString("message"))
|
||||
pass.barCode = barCode
|
||||
|
||||
if (barcode_json.has("altText")) {
|
||||
pass.barCode!!.alternativeText = barcode_json.getString("altText")
|
||||
if (barcodeJSON.has("altText")) {
|
||||
pass.barCode!!.alternativeText = barcodeJSON.getString("altText")
|
||||
}
|
||||
}
|
||||
// TODO should check a bit more with barcode here - this can be dangerous
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
if (pass_json.has("relevantDate")) {
|
||||
if (passJSON.has("relevantDate")) {
|
||||
try {
|
||||
pass.calendarTimespan = PassImpl.TimeSpan(from = ZonedDateTime.parse(pass_json.getString("relevantDate")))
|
||||
pass.calendarTimespan = PassImpl.TimeSpan(from = ZonedDateTime.parse(passJSON.getString("relevantDate")))
|
||||
} catch (e: JSONException) {
|
||||
// be robust when it comes to bad dates - had a RL crash with "2013-12-25T00:00-57:00" here
|
||||
// OK then we just have no date here
|
||||
App.component().tracker().trackException("problem parsing relevant date", e, false)
|
||||
tracker.trackException("problem parsing relevant date", e, false)
|
||||
} catch (e: DateTimeException) {
|
||||
App.component().tracker().trackException("problem parsing relevant date", e, false)
|
||||
tracker.trackException("problem parsing relevant date", e, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (pass_json.has("expirationDate")) {
|
||||
if (passJSON.has("expirationDate")) {
|
||||
try {
|
||||
pass.validTimespans = listOf(PassImpl.TimeSpan(to = ZonedDateTime.parse(pass_json.getString("expirationDate"))))
|
||||
pass.validTimespans = listOf(PassImpl.TimeSpan(to = ZonedDateTime.parse(passJSON.getString("expirationDate"))))
|
||||
} catch (e: JSONException) {
|
||||
// be robust when it comes to bad dates - had a RL crash with "2013-12-25T00:00-57:00" here
|
||||
// OK then we just have no date here
|
||||
App.component().tracker().trackException("problem parsing expiration date", e, false)
|
||||
tracker.trackException("problem parsing expiration date", e, false)
|
||||
} catch (e: DateTimeException) {
|
||||
App.component().tracker().trackException("problem parsing expiration date", e, false)
|
||||
tracker.trackException("problem parsing expiration date", e, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pass.serial = readJsonSafeAsOptional(pass_json, "serialNumber")
|
||||
pass.authToken = readJsonSafeAsOptional(pass_json, "authenticationToken")
|
||||
pass.webServiceURL = readJsonSafeAsOptional(pass_json, "webServiceURL")
|
||||
pass.passIdent = readJsonSafeAsOptional(pass_json, "passTypeIdentifier")
|
||||
pass.serial = readJsonSafeAsOptional(passJSON, "serialNumber")
|
||||
pass.authToken = readJsonSafeAsOptional(passJSON, "authenticationToken")
|
||||
pass.webServiceURL = readJsonSafeAsOptional(passJSON, "webServiceURL")
|
||||
pass.passIdent = readJsonSafeAsOptional(passJSON, "passTypeIdentifier")
|
||||
|
||||
val locations = ArrayList<PassLocation>()
|
||||
try {
|
||||
|
||||
val locations_json = pass_json.getJSONArray("locations")
|
||||
for (i in 0..locations_json.length() - 1) {
|
||||
val obj = locations_json.getJSONObject(i)
|
||||
val locationsJSON = passJSON.getJSONArray("locations")
|
||||
for (i in 0 until locationsJSON.length()) {
|
||||
val obj = locationsJSON.getJSONObject(i)
|
||||
|
||||
val location = PassLocation()
|
||||
location.lat = obj.getDouble("latitude")
|
||||
|
@ -153,13 +153,13 @@ object AppleStylePassReader {
|
|||
|
||||
pass.locations = locations
|
||||
|
||||
readJsonSafe(pass_json, "backgroundColor", object : JsonStringReadCallback {
|
||||
readJsonSafe(passJSON, "backgroundColor", object : JsonStringReadCallback {
|
||||
override fun onString(string: String) {
|
||||
pass.accentColor = string.parseColor(Color.BLACK)
|
||||
}
|
||||
})
|
||||
|
||||
readJsonSafe(pass_json, "description", object : JsonStringReadCallback {
|
||||
readJsonSafe(passJSON, "description", object : JsonStringReadCallback {
|
||||
override fun onString(string: String) {
|
||||
pass.description = translation.translate(string)
|
||||
}
|
||||
|
@ -169,22 +169,22 @@ object AppleStylePassReader {
|
|||
// try to find in a predefined set of tickets
|
||||
|
||||
PassDefinitions.TYPE_TO_NAME.forEach {
|
||||
if (pass_json!!.has(it.value)) {
|
||||
if (passJSON.has(it.value)) {
|
||||
pass.type = it.key
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val type = PassDefinitions.TYPE_TO_NAME[pass.type]
|
||||
val type_json = pass_json.getJSONObject(type)
|
||||
if (type_json != null) {
|
||||
val typeJSON = passJSON.getJSONObject(type)
|
||||
if (typeJSON != null) {
|
||||
val fieldList: ArrayList<PassField> = ArrayList()
|
||||
|
||||
addFields(fieldList, type_json, "primaryFields", translation)
|
||||
addFields(fieldList, type_json, "headerFields", translation)
|
||||
addFields(fieldList, type_json, "secondaryFields", translation)
|
||||
addFields(fieldList, type_json, "auxiliaryFields", translation)
|
||||
addFields(fieldList, type_json, "backFields", translation, hide = true)
|
||||
addFields(fieldList, typeJSON, "primaryFields", translation)
|
||||
addFields(fieldList, typeJSON, "headerFields", translation)
|
||||
addFields(fieldList, typeJSON, "secondaryFields", translation)
|
||||
addFields(fieldList, typeJSON, "auxiliaryFields", translation)
|
||||
addFields(fieldList, typeJSON, "backFields", translation, hide = true)
|
||||
|
||||
fieldList.add(PassField("", context.getString(R.string.type), context.getString(getHumanCategoryString(pass.type)), false))
|
||||
pass.fields = fieldList
|
||||
|
@ -195,13 +195,13 @@ object AppleStylePassReader {
|
|||
|
||||
|
||||
try {
|
||||
pass.creator = pass_json.getString("organizationName")
|
||||
App.component().tracker().trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
|
||||
pass.creator = passJSON.getString("organizationName")
|
||||
tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
|
||||
} catch (ignored: JSONException) {
|
||||
// ok - we have no organisation - big deal ..-)
|
||||
}
|
||||
|
||||
ApplePassbookQuirkCorrector(App.component().tracker()).correctQuirks(pass)
|
||||
ApplePassbookQuirkCorrector(tracker).correctQuirks(pass)
|
||||
|
||||
return pass
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ object AppleStylePassReader {
|
|||
private fun addFields(list: ArrayList<PassField>, type_json: JSONObject, fieldsName: String, translation: AppleStylePassTranslation, hide: Boolean = false) {
|
||||
try {
|
||||
val jsonArray = type_json.getJSONArray(fieldsName)
|
||||
for (i in 0..jsonArray.length() - 1) {
|
||||
for (i in 0 until jsonArray.length()) {
|
||||
try {
|
||||
val jsonObject = jsonArray.getJSONObject(i)
|
||||
val field = PassField(key = getField(jsonObject, "key", translation),
|
||||
|
@ -230,29 +230,28 @@ object AppleStylePassReader {
|
|||
list.add(field)
|
||||
|
||||
} catch (e: JSONException) {
|
||||
Log.w("could not process PassField from JSON for $fieldsName cause:$e")
|
||||
Log.w("could not process PassField from JSON for $fieldsName cause: $e")
|
||||
}
|
||||
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
Log.w("could not process PassFields $fieldsName from JSON$e")
|
||||
Log.w("could not process PassFields $fieldsName from JSON: $e")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun findLocalizedPath(path: File, language: String): String? {
|
||||
|
||||
val localized = File(path, language + ".lproj")
|
||||
private fun findLocalizedPath(path: File, language: String, tracker: Tracker): String? {
|
||||
val localized = File(path, "$language.lproj")
|
||||
|
||||
if (localized.exists() && localized.isDirectory) {
|
||||
App.component().tracker().trackEvent("measure_event", "pass", language + "_native_lproj", null)
|
||||
tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
|
||||
return localized.path
|
||||
}
|
||||
|
||||
val fallback = File(path, "en.lproj")
|
||||
|
||||
if (fallback.exists() && fallback.isDirectory) {
|
||||
App.component().tracker().trackEvent("measure_event", "pass", "en_lproj", null)
|
||||
tracker.trackEvent("measure_event", "pass", "en_lproj", null)
|
||||
return fallback.path
|
||||
}
|
||||
|
||||
|
@ -282,7 +281,6 @@ object AppleStylePassReader {
|
|||
} catch (e: JSONException) {
|
||||
// some passes just do not have the field
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,7 +292,6 @@ object AppleStylePassReader {
|
|||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,6 +326,4 @@ object AppleStylePassReader {
|
|||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,47 +21,47 @@ object PassReader {
|
|||
|
||||
try {
|
||||
val plainJsonString = file.bufferedReader().readText()
|
||||
val pass_json = readJSONSafely(plainJsonString)!!
|
||||
val passJSON = readJSONSafely(plainJsonString)!!
|
||||
|
||||
if (pass_json.has("what")) {
|
||||
val what_json = pass_json.getJSONObject("what")
|
||||
pass.description = what_json.getString("description")
|
||||
if (passJSON.has("what")) {
|
||||
val whatJSON = passJSON.getJSONObject("what")
|
||||
pass.description = whatJSON.getString("description")
|
||||
}
|
||||
|
||||
if (pass_json.has("meta")) {
|
||||
val meta_json = pass_json.getJSONObject("meta")
|
||||
pass.type = PassDefinitions.NAME_TO_TYPE[meta_json.getString("type")] ?: PassType.GENERIC
|
||||
pass.creator = meta_json.getString("organisation")
|
||||
pass.app = meta_json.getString("app")
|
||||
if (passJSON.has("meta")) {
|
||||
val metaJSON = passJSON.getJSONObject("meta")
|
||||
pass.type = PassDefinitions.NAME_TO_TYPE[metaJSON.getString("type")] ?: PassType.GENERIC
|
||||
pass.creator = metaJSON.getString("organisation")
|
||||
pass.app = metaJSON.getString("app")
|
||||
}
|
||||
|
||||
if (pass_json.has("ui")) {
|
||||
val ui_json = pass_json.getJSONObject("ui")
|
||||
pass.accentColor = Color.parseColor(ui_json.getString("bgColor"))
|
||||
if (passJSON.has("ui")) {
|
||||
val uiJSON = passJSON.getJSONObject("ui")
|
||||
pass.accentColor = Color.parseColor(uiJSON.getString("bgColor"))
|
||||
}
|
||||
|
||||
if (pass_json.has("barcode")) {
|
||||
val barcode_json = pass_json.getJSONObject("barcode")
|
||||
val barcodeFormatString = barcode_json.getString("type")
|
||||
if (passJSON.has("barcode")) {
|
||||
val barcodeJSON = passJSON.getJSONObject("barcode")
|
||||
val barcodeFormatString = barcodeJSON.getString("type")
|
||||
|
||||
val barcodeFormat = BarCode.getFormatFromString(barcodeFormatString)
|
||||
val barCode = BarCode(barcodeFormat, barcode_json.getString("message"))
|
||||
val barCode = BarCode(barcodeFormat, barcodeJSON.getString("message"))
|
||||
pass.barCode = barCode
|
||||
|
||||
if (barcode_json.has("altText")) {
|
||||
barCode.alternativeText = barcode_json.getString("altText")
|
||||
if (barcodeJSON.has("altText")) {
|
||||
barCode.alternativeText = barcodeJSON.getString("altText")
|
||||
}
|
||||
}
|
||||
|
||||
if (pass_json.has("when")) {
|
||||
val dateTime = pass_json.getJSONObject("when").getString("dateTime")
|
||||
if (passJSON.has("when")) {
|
||||
val dateTime = passJSON.getJSONObject("when").getString("dateTime")
|
||||
|
||||
pass.calendarTimespan = PassImpl.TimeSpan()
|
||||
pass.calendarTimespan = PassImpl.TimeSpan(from = ZonedDateTime.parse(dateTime))
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.i("PassParse Exception " + e)
|
||||
Log.i("PassParse Exception: $e")
|
||||
}
|
||||
|
||||
return pass
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package org.ligi.passandroid.scan
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View.GONE
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.android.synthetic.main.activity_scan.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.scan.events.DirectoryProcessed
|
||||
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
|
||||
import org.ligi.passandroid.scan.events.ScanFinished
|
||||
import org.ligi.passandroid.ui.PassAndroidActivity
|
||||
|
||||
class PassScanActivity : PassAndroidActivity() {
|
||||
|
||||
private val progressChannelProvider: PassScanEventChannelProvider by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_scan)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
lifecycleScope.launch {
|
||||
for (event in progressChannelProvider.channel.openSubscription()) {
|
||||
when (event) {
|
||||
is DirectoryProcessed -> progress_text.text = event.dir
|
||||
is ScanFinished -> {
|
||||
progress_container.visibility = GONE
|
||||
val message = getString(R.string.scan_finished_dialog_text, event.foundPasses.size)
|
||||
AlertDialog.Builder(this@PassScanActivity)
|
||||
.setTitle(R.string.scan_finished_dialog_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(this, SearchPassesIntentService::class.java)
|
||||
startService(intent)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package org.ligi.passandroid.scan
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.functions.fromURI
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PastLocationsStore
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.scan.events.DirectoryProcessed
|
||||
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
|
||||
import org.ligi.passandroid.scan.events.ScanFinished
|
||||
import org.ligi.passandroid.ui.PassListActivity
|
||||
import org.ligi.passandroid.ui.UnzipPassController
|
||||
import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec
|
||||
import org.ligi.tracedroid.logging.Log
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
private const val NOTIFICATION_CHANNEL_ID = "transactions"
|
||||
|
||||
class SearchPassesIntentService : LifecycleService() {
|
||||
|
||||
private val notifyManager by lazy {
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
}
|
||||
|
||||
private var shouldFinish: Boolean = false
|
||||
private var progressNotificationBuilder: NotificationCompat.Builder? = null
|
||||
private var findNotificationBuilder: NotificationCompat.Builder? = null
|
||||
|
||||
private var foundList = ArrayList<Pass>()
|
||||
|
||||
private var lastProgressUpdate: Long = 0
|
||||
|
||||
private val passStore: PassStore by inject()
|
||||
private val tracker: Tracker by inject()
|
||||
private val progressChannelProvider: PassScanEventChannelProvider by inject()
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
lifecycleScope.launch {
|
||||
foundList.clear()
|
||||
|
||||
if (Build.VERSION.SDK_INT > 25) {
|
||||
|
||||
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, "PassAndroid Pass scan", NotificationManager.IMPORTANCE_DEFAULT)
|
||||
channel.description = "Notifications when PassAndroid is scanning for passes"
|
||||
notifyManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(applicationContext, 1, Intent(baseContext, PassListActivity::class.java), 0)
|
||||
progressNotificationBuilder = NotificationCompat.Builder(this@SearchPassesIntentService, NOTIFICATION_CHANNEL_ID).setContentTitle(getString(R.string.scanning_for_passes))
|
||||
.setSmallIcon(R.drawable.ic_refresh)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setProgress(1, 1, true)
|
||||
|
||||
findNotificationBuilder = NotificationCompat.Builder(this@SearchPassesIntentService, NOTIFICATION_CHANNEL_ID)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
for (path in PastLocationsStore(preferences, tracker).locations) {
|
||||
searchIn(File(path), false)
|
||||
}
|
||||
|
||||
// note to future_me: yea one thinks we only need to search root here, but root was /system for me and so
|
||||
// did not contain "/SDCARD" #dontoptimize
|
||||
// on my phone:
|
||||
|
||||
// | /mnt/sdcard/Download << this looks kind of stupid as we do /mnt/sdcard later and hence will go here twice
|
||||
// but this helps finding passes in Downloads ( where they are very often ) fast - some users with lots of files on the SDCard gave
|
||||
// up the refreshing of passes as it took so long to traverse all files on the SDCard
|
||||
// one could think about not going there anymore but a short look at this showed that it seems cost more time to check than what it gains
|
||||
// in download there are mostly single files in a flat dir - no huge tree behind this imho
|
||||
searchIn(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), true)
|
||||
|
||||
// | /system
|
||||
searchIn(Environment.getRootDirectory(), true)
|
||||
|
||||
// | /mnt/sdcard
|
||||
searchIn(Environment.getExternalStorageDirectory(), true)
|
||||
|
||||
// | /cache
|
||||
searchIn(Environment.getDownloadCacheDirectory(), true)
|
||||
|
||||
// | /data
|
||||
searchIn(Environment.getDataDirectory(), true)
|
||||
notifyManager.cancel(PROGRESS_NOTIFICATION_ID)
|
||||
|
||||
progressChannelProvider.channel.send(ScanFinished(foundList))
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
/**
|
||||
* recursive voyage starting at path to find files named .pkpass
|
||||
*/
|
||||
private suspend fun searchIn(path: File, recursive: Boolean) {
|
||||
|
||||
if (System.currentTimeMillis() - lastProgressUpdate > 1000) {
|
||||
lastProgressUpdate = System.currentTimeMillis()
|
||||
val msg = path.toString()
|
||||
progressChannelProvider.channel.send(DirectoryProcessed(msg))
|
||||
progressNotificationBuilder!!.setContentText(msg)
|
||||
notifyManager.notify(PROGRESS_NOTIFICATION_ID, progressNotificationBuilder!!.build())
|
||||
}
|
||||
|
||||
val files = path.listFiles()
|
||||
|
||||
if (files == null || files.isEmpty()) {
|
||||
// no files here
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
for (file in files) {
|
||||
if (shouldFinish) {
|
||||
return
|
||||
}
|
||||
Log.i("search " + file.absoluteFile)
|
||||
if (recursive && file.isDirectory) {
|
||||
searchIn(file, true)
|
||||
} else if (file.name.toLowerCase().endsWith(".pkpass") || file.name.toLowerCase().endsWith(".espass")) {
|
||||
Log.i("found" + file.absolutePath)
|
||||
|
||||
try {
|
||||
val ins = fromURI(baseContext, Uri.parse("file://" + file.absolutePath), tracker)
|
||||
val onSuccessCallback = SearchSuccessCallback(baseContext,
|
||||
passStore,
|
||||
foundList,
|
||||
findNotificationBuilder!!,
|
||||
file,
|
||||
notifyManager)
|
||||
val spec = InputStreamUnzipControllerSpec(ins!!,
|
||||
baseContext,
|
||||
passStore,
|
||||
onSuccessCallback,
|
||||
object : UnzipPassController.FailCallback {
|
||||
override fun fail(reason: String) {
|
||||
Log.i("fail", reason)
|
||||
}
|
||||
})
|
||||
UnzipPassController.processInputStream(spec)
|
||||
} catch (e: Exception) {
|
||||
tracker.trackException("Error in SearchPassesIntentService", e, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
shouldFinish = true
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROGRESS_NOTIFICATION_ID = 1
|
||||
const val FOUND_NOTIFICATION_ID = 2
|
||||
const val REQUEST_CODE = 1
|
||||
}
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
package org.ligi.passandroid.ui
|
||||
package org.ligi.passandroid.scan
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.PassBitmapDefinitions
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.scan.SearchPassesIntentService
|
||||
import org.ligi.passandroid.ui.PassViewActivity
|
||||
import org.ligi.passandroid.ui.PassViewActivityBase
|
||||
import org.ligi.passandroid.ui.UnzipPassController
|
||||
import org.threeten.bp.ZonedDateTime
|
||||
import java.io.File
|
||||
|
||||
|
@ -19,7 +23,7 @@ internal class SearchSuccessCallback(private val context: Context, private val p
|
|||
|
||||
val pass = passStore.getPassbookForId(uuid)
|
||||
|
||||
val isDuplicate = foundList.any { it.id.equals(uuid) }
|
||||
val isDuplicate = foundList.any { it.id == uuid }
|
||||
if (pass != null && !isDuplicate) {
|
||||
foundList.add(pass)
|
||||
val iconBitmap = pass.getBitmap(passStore, PassBitmapDefinitions.BITMAP_ICON)
|
||||
|
@ -62,7 +66,7 @@ internal class SearchSuccessCallback(private val context: Context, private val p
|
|||
private fun getDateOfPassForComparison(pass: Pass): ZonedDateTime? {
|
||||
if (pass.calendarTimespan != null && pass.calendarTimespan!!.from != null) {
|
||||
return pass.calendarTimespan!!.from
|
||||
} else if (pass.validTimespans != null && pass.validTimespans!!.size > 0 && pass.validTimespans!![0].to != null) {
|
||||
} else if (pass.validTimespans != null && pass.validTimespans!!.isNotEmpty() && pass.validTimespans!![0].to != null) {
|
||||
return pass.validTimespans!![0].to
|
||||
}
|
||||
return null
|
|
@ -0,0 +1,8 @@
|
|||
package org.ligi.passandroid.scan.events
|
||||
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
||||
sealed class PassScanEvent
|
||||
|
||||
data class DirectoryProcessed(val dir: String) : PassScanEvent()
|
||||
data class ScanFinished(val foundPasses: List<Pass>) : PassScanEvent()
|
|
@ -0,0 +1,8 @@
|
|||
package org.ligi.passandroid.scan.events
|
||||
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import org.ligi.passandroid.scan.events.PassScanEvent
|
||||
|
||||
class PassScanEventChannelProvider {
|
||||
val channel = ConflatedBroadcastChannel<PassScanEvent>()
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.ui.quirk_fix.OpenIphoneWebView
|
||||
|
||||
class AlertFragment : Fragment() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
||||
val tracker = (activity!! as PassAndroidActivity).tracker
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setTitle("Workaround failed")
|
||||
builder.setMessage("The URL PassAndroid tried to work around failed :-( some companies just send PassBooks to Apple Devices - this was an attempt to workaround this." + "Unfortunately it failed - perhaps there where changes on the serverside - you can open the site with your browser now - to see it in PassAndroid in future again it would help if you can send me the pass")
|
||||
builder.setPositiveButton("Browser") { dialog, which ->
|
||||
tracker.trackException(activity!!.localClassName + "with invalid activity", false)
|
||||
val intent = Intent(activity, OpenIphoneWebView::class.java)
|
||||
intent.data = activity!!.intent.data
|
||||
startActivity(intent)
|
||||
}
|
||||
builder.setNeutralButton("send") { dialog, which ->
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, "PassAndroid: URLRewrite Problem")
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("ligi@ligi.de"))
|
||||
if(activity!!.intent.data != null)
|
||||
{
|
||||
intent.putExtra(Intent.EXTRA_TEXT, activity!!.intent.data!!.toString())
|
||||
}
|
||||
else{
|
||||
intent.putExtra(Intent.EXTRA_TEXT, "null")
|
||||
}
|
||||
intent.type = "text/plain"
|
||||
}
|
||||
builder.setNegativeButton("cancle") { dialog, which -> activity!!.finish() }
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
}
|
|
@ -4,14 +4,15 @@ import android.app.Activity
|
|||
import android.view.View
|
||||
import android.view.View.*
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.barcode.view.*
|
||||
import org.ligi.kaxt.getSmallestSide
|
||||
import org.ligi.passandroid.model.pass.BarCode
|
||||
|
||||
internal class BarcodeUIController(val rootView: View, private val barCode: BarCode?, activity: Activity, private val passViewHelper: PassViewHelper) {
|
||||
internal class BarcodeUIController(private val rootView: View, private val barCode: BarCode?, activity: Activity, private val passViewHelper: PassViewHelper) {
|
||||
|
||||
fun getBarcodeView() = rootView.barcode_img
|
||||
fun getBarcodeView(): ImageView = rootView.barcode_img
|
||||
|
||||
private var currentBarcodeWidth: Int = 0
|
||||
|
||||
|
|
|
@ -22,10 +22,12 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
progressDialog.show()
|
||||
tracker.trackEvent("quirk_fix", "unpack_attempt", intent.data.host, null)
|
||||
if (intent?.data != null) {
|
||||
progressDialog.show()
|
||||
tracker.trackEvent("quirk_fix", "unpack_attempt", intent?.data?.host, null)
|
||||
|
||||
DownloadExtractAndStartImportTask().execute()
|
||||
DownloadExtractAndStartImportTask().execute()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DownloadExtractAndStartImportTask : AsyncTask<Void, Void, String>() {
|
||||
|
@ -34,20 +36,23 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
|
|||
|
||||
val client = OkHttpClient()
|
||||
try {
|
||||
val requestBuilder = Request.Builder().url(URI(intent.data.toString()).toURL())
|
||||
val requestBuilder = Request.Builder().url(URI(intent?.data.toString()).toURL())
|
||||
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
|
||||
|
||||
val body = client.newCall(requestBuilder.build()).execute().body()
|
||||
val bodyString = body.string()
|
||||
body.close()
|
||||
|
||||
val url = extractURL(bodyString) ?: return null
|
||||
if (body != null) {
|
||||
val bodyString = body.string()
|
||||
body.close()
|
||||
|
||||
if (!url.startsWith("http")) {
|
||||
return intent.data.scheme + "://" + intent.data.host + "/" + url
|
||||
val url = extractURL(bodyString) ?: return null
|
||||
|
||||
if (!url.startsWith("http")) {
|
||||
return intent?.data?.scheme + "://" + intent?.data?.host + "/" + url
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
return url
|
||||
} catch (e: IOException) {
|
||||
tracker.trackException("ExtractURLAsIphoneActivity", e, false)
|
||||
} catch (e: URISyntaxException) {
|
||||
|
@ -76,7 +81,7 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
|
|||
return
|
||||
}
|
||||
|
||||
tracker.trackEvent("quirk_fix", "unpack_success", intent.data.host, null)
|
||||
tracker.trackEvent("quirk_fix", "unpack_success", intent?.data?.host, null)
|
||||
|
||||
val intent = Intent(this@ExtractURLAsIphoneActivity, PassImportActivity::class.java)
|
||||
intent.data = Uri.parse(s)
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.ligi.passandroid.ui
|
|||
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
|
@ -15,12 +16,19 @@ class FullscreenBarcodeActivity : PassViewActivityBase() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.fullscreen_image)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
setShowWhenLocked(true)
|
||||
setTurnScreenOn(true)
|
||||
} else {
|
||||
this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (currentPass == null || currentPass.barCode == null) {
|
||||
if (currentPass.barCode == null) {
|
||||
Log.w("FullscreenBarcodeActivity in bad state")
|
||||
finish() // this should never happen, but better safe than sorry
|
||||
return
|
||||
|
@ -47,7 +55,6 @@ class FullscreenBarcodeActivity : PassViewActivityBase() {
|
|||
* ( reverse orientation / sensor is the problem here ..)
|
||||
*/
|
||||
private fun setBestFittingOrientationForBarCode() {
|
||||
|
||||
if (currentPass.barCode!!.format!!.isQuadratic()) {
|
||||
when (requestedOrientation) {
|
||||
|
||||
|
@ -72,9 +79,4 @@ class FullscreenBarcodeActivity : PassViewActivityBase() {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.text.Editable
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import org.ligi.kaxt.startActivityFromClass
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.functions.fromURI
|
||||
import org.ligi.passandroid.model.InputStreamWithSource
|
||||
import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec
|
||||
|
||||
internal class ImportAndShowAsyncTask(val passImportActivity: PassImportActivity, val intent_uri: Uri) : AsyncTask<Void, Void, InputStreamWithSource?>() {
|
||||
|
||||
private val progressDialog by lazy {
|
||||
ProgressDialog(passImportActivity).apply {
|
||||
setMessage(passImportActivity.getString(R.string.please_wait))
|
||||
setCancelable(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg params: Void) = fromURI(passImportActivity, intent_uri)
|
||||
|
||||
override fun onPreExecute() {
|
||||
progressDialog.show()
|
||||
super.onPreExecute()
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: InputStreamWithSource?) {
|
||||
super.onPostExecute(result)
|
||||
|
||||
if (result == null) {
|
||||
|
||||
if (!passImportActivity.isFinishing && progressDialog.isShowing) {
|
||||
try {
|
||||
progressDialog.dismiss()
|
||||
} catch (ignored: Exception) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
passImportActivity.finish()
|
||||
//TODO show some error here?!
|
||||
return // no result -> no work here
|
||||
}
|
||||
|
||||
if (passImportActivity.isFinishing) { // finish with no UI/Dialogs
|
||||
// let's do it silently TODO check if we need to jump to a service here as the activity is dying
|
||||
val spec = InputStreamUnzipControllerSpec(result, passImportActivity.application, passImportActivity.passStore, null, null)
|
||||
UnzipPassController.processInputStream(spec)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
UnzipPassDialog.show(result, passImportActivity, passImportActivity.passStore) { path ->
|
||||
// TODO this is kind of a hack - there should be a better way
|
||||
val id = path.split("/".toRegex()).dropLastWhile(String::isEmpty).toTypedArray().last()
|
||||
|
||||
val passbookForId = passImportActivity.passStore.getPassbookForId(id)
|
||||
passImportActivity.passStore.currentPass = passbookForId
|
||||
|
||||
passImportActivity.passStore.classifier.moveToTopic(passbookForId!!, passImportActivity.getString(R.string.topic_new))
|
||||
|
||||
passImportActivity.startActivityFromClass(PassViewActivity::class.java)
|
||||
passImportActivity.finish()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
|
@ -17,7 +17,7 @@ internal class MoveToNewTopicUI(private val context: Activity, private val passS
|
|||
.setTitle(context.getString(R.string.move_to_new_topic))
|
||||
.setView(R.layout.dialog_move_to_new_topic)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, which -> passStore.notifyChange() }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> passStore.notifyChange() }
|
||||
.setOnCancelListener { passStore.notifyChange() }
|
||||
.show()
|
||||
|
||||
|
@ -26,16 +26,18 @@ internal class MoveToNewTopicUI(private val context: Activity, private val passS
|
|||
dialog.dismiss()
|
||||
}
|
||||
|
||||
val newTopicEditText = dialog.findViewById(R.id.new_topic_edit) as EditText
|
||||
val suggestionButtonContainer= dialog.findViewById(R.id.topic_suggestions_button_container) as ViewGroup
|
||||
val newTopicEditText = dialog.findViewById<EditText>(R.id.new_topic_edit)
|
||||
val suggestionButtonContainer = dialog.findViewById<ViewGroup>(R.id.topic_suggestions_button_container)
|
||||
|
||||
// we need to do this here so the dialog does not get dismissed
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
if (newTopicEditText.text.toString().isEmpty()) {
|
||||
newTopicEditText.error = context.getString(R.string.cannot_be_empty)
|
||||
newTopicEditText.requestFocus()
|
||||
} else {
|
||||
move(newTopicEditText.text.toString())
|
||||
if (newTopicEditText != null) {
|
||||
// we need to do this here so the dialog does not get dismissed
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
if (newTopicEditText.text.toString().isEmpty()) {
|
||||
newTopicEditText.error = context.getString(R.string.cannot_be_empty)
|
||||
newTopicEditText.requestFocus()
|
||||
} else {
|
||||
move(newTopicEditText.text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,12 +45,12 @@ internal class MoveToNewTopicUI(private val context: Activity, private val passS
|
|||
|
||||
val suggestionTopicStringIds = intArrayOf(R.string.topic_trash, R.string.topic_archive, R.string.topic_new)
|
||||
|
||||
suggestionTopicStringIds.map { context.getString(it) }.forEach {
|
||||
if (it != oldTopic) {
|
||||
suggestionTopicStringIds.map { context.getString(it) }.forEach { topic ->
|
||||
if (topic != oldTopic) {
|
||||
val button = Button(context)
|
||||
button.text = it
|
||||
suggestionButtonContainer.addView(button)
|
||||
button.setOnClickListener { view -> move(it) }
|
||||
button.text = topic
|
||||
suggestionButtonContainer?.addView(button)
|
||||
button.setOnClickListener { move(topic) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,32 +2,36 @@ package org.ligi.passandroid.ui;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
|
||||
import org.ligi.passandroid.R;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class MyShyFABBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
|
||||
|
||||
public MyShyFABBehavior() {
|
||||
}
|
||||
public MyShyFABBehavior() {}
|
||||
|
||||
public MyShyFABBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionsMenu child, View dependency) {
|
||||
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent,
|
||||
@NonNull FloatingActionsMenu child,
|
||||
@NonNull View dependency) {
|
||||
return dependency instanceof Snackbar.SnackbarLayout || dependency instanceof AppBarLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionsMenu child, View dependency) {
|
||||
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
|
||||
@NonNull FloatingActionsMenu child,
|
||||
@NonNull View dependency) {
|
||||
if (dependency instanceof Snackbar.SnackbarLayout) {
|
||||
updateFabTranslationForSnackbar(child, dependency);
|
||||
}
|
||||
|
@ -40,23 +44,24 @@ public class MyShyFABBehavior extends CoordinatorLayout.Behavior<FloatingActions
|
|||
} else {
|
||||
distanceToScroll = (int) (child.getContext().getResources().getDimension(R.dimen.fab_size_normal) + 2 * fabBottomMargin);
|
||||
}
|
||||
final float ratio = ViewCompat.getY(dependency) / getToolbarHeight(dependency.getContext());
|
||||
final float ratio = dependency.getY() / getToolbarHeight(dependency.getContext());
|
||||
|
||||
ViewCompat.setTranslationY(child, -distanceToScroll * ratio);
|
||||
child.setTranslationY(-distanceToScroll * ratio);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDependentViewRemoved(final CoordinatorLayout parent, final FloatingActionsMenu child, final View dependency) {
|
||||
public void onDependentViewRemoved(@NonNull final CoordinatorLayout parent,
|
||||
@NonNull final FloatingActionsMenu child,
|
||||
@NonNull final View dependency) {
|
||||
super.onDependentViewRemoved(parent, child, dependency);
|
||||
onDependentViewChanged(parent,child,dependency);
|
||||
}
|
||||
|
||||
private void updateFabTranslationForSnackbar(FloatingActionsMenu child, View dependency) {
|
||||
final float translationY = ViewCompat.getTranslationY(dependency) - dependency.getHeight();
|
||||
final float translationYClipped = Math.min(0, translationY);
|
||||
ViewCompat.setTranslationY(child, translationYClipped);
|
||||
final float translationY = dependency.getTranslationY() - dependency.getHeight();
|
||||
child.setTranslationY(Math.min(0, translationY));
|
||||
}
|
||||
|
||||
private int getToolbarHeight(Context context) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.app.Activity
|
|||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.support.v7.app.AlertDialog
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.model.pass.PassLocation
|
||||
|
@ -15,25 +15,26 @@ import java.net.URLEncoder
|
|||
fun Activity.showNavigateToLocationsDialog(pass: Pass, finishOnDone: Boolean) {
|
||||
val locations = pass.locations
|
||||
|
||||
if (locations.isEmpty()) {
|
||||
done(this, finishOnDone)
|
||||
} else if (locations.size == 1) {
|
||||
startIntentForLocation(this, locations.first(), pass)
|
||||
done(this, finishOnDone)
|
||||
} else if (locations.size > 1) {
|
||||
val locationDescriptions = arrayOfNulls<String>(locations.size)
|
||||
|
||||
var i = 0
|
||||
for (loc in locations) {
|
||||
locationDescriptions[i++] = loc.getNameWithFallback(pass)
|
||||
when {
|
||||
locations.isEmpty() -> done(this, finishOnDone)
|
||||
locations.size == 1 -> {
|
||||
startIntentForLocation(this, locations.first(), pass)
|
||||
done(this, finishOnDone)
|
||||
}
|
||||
AlertDialog.Builder(this).setTitle(this.getString(R.string.choose_location))
|
||||
.setItems(locationDescriptions) { dialog, which ->
|
||||
startIntentForLocation(this, locations[which], pass)
|
||||
done(this, finishOnDone)
|
||||
}
|
||||
.show()
|
||||
locations.size > 1 -> {
|
||||
val locationDescriptions = arrayOfNulls<String>(locations.size)
|
||||
|
||||
var i = 0
|
||||
for (loc in locations) {
|
||||
locationDescriptions[i++] = loc.getNameWithFallback(pass)
|
||||
}
|
||||
AlertDialog.Builder(this).setTitle(this.getString(R.string.choose_location))
|
||||
.setItems(locationDescriptions) { _, which ->
|
||||
startIntentForLocation(this, locations[which], pass)
|
||||
done(this, finishOnDone)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,13 +61,10 @@ private fun startIntentForLocation(activity: Activity, location: PassLocation, p
|
|||
|
||||
}
|
||||
|
||||
private fun getEncodedDescription(location: PassLocation, pass: Pass): String {
|
||||
try {
|
||||
return URLEncoder.encode(location.getNameWithFallback(pass), "UTF-8")
|
||||
} catch (e1: UnsupportedEncodingException) {
|
||||
// OK - no description
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun getEncodedDescription(location: PassLocation, pass: Pass) = try {
|
||||
URLEncoder.encode(location.getNameWithFallback(pass), "UTF-8")
|
||||
} catch (e1: UnsupportedEncodingException) {
|
||||
// OK - no description
|
||||
""
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +1,39 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.CardView
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.kaxt.startActivityFromClass
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PassStoreProjection
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.ui.pass_view_holder.CondensedPassViewHolder
|
||||
import org.ligi.passandroid.ui.pass_view_holder.PassViewHolder
|
||||
import org.ligi.passandroid.ui.pass_view_holder.VerbosePassViewHolder
|
||||
import javax.inject.Inject
|
||||
|
||||
class PassAdapter(private val passListActivity: AppCompatActivity, private val passStoreProjection: PassStoreProjection) : RecyclerView.Adapter<PassViewHolder>() {
|
||||
class PassAdapter(
|
||||
private val passListActivity: AppCompatActivity,
|
||||
private val passStoreProjection: PassStoreProjection
|
||||
) : RecyclerView.Adapter<PassViewHolder>(), KoinComponent {
|
||||
|
||||
@Inject
|
||||
lateinit var passStore: PassStore
|
||||
|
||||
@Inject
|
||||
lateinit var settings: Settings
|
||||
|
||||
init {
|
||||
App.component().inject(this)
|
||||
}
|
||||
private val passStore: PassStore by inject ()
|
||||
private val settings: Settings by inject ()
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PassViewHolder {
|
||||
val inflater = LayoutInflater.from(viewGroup.context)
|
||||
|
||||
val res = inflater.inflate(R.layout.pass_list_item, viewGroup, false) as CardView
|
||||
if (settings.isCondensedModeEnabled()) {
|
||||
return CondensedPassViewHolder(res)
|
||||
return if (settings.isCondensedModeEnabled()) {
|
||||
CondensedPassViewHolder(res)
|
||||
} else {
|
||||
return VerbosePassViewHolder(res)
|
||||
VerbosePassViewHolder(res)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: PassViewHolder, position: Int) {
|
||||
|
@ -60,15 +54,8 @@ class PassAdapter(private val passListActivity: AppCompatActivity, private val p
|
|||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
private val list: List<Pass>
|
||||
get() = passStoreProjection.passList
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return list.size
|
||||
}
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
private val list = passStoreProjection.passList
|
||||
override fun getItemCount() = list.size
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue