Compare commits

..

399 commits

Author SHA1 Message Date
ligi
6cdd7ce4f7
Bump verson to 3.5.6 2020-03-05 03:27:48 +01:00
ligi
38ca9d7911
Prevent creationg shortcut with empty label - closes #250 2020-03-05 03:10:32 +01:00
ligi
5a19e83a3c
Bump version to 3.5.5 2020-03-05 01:53:34 +01:00
ligi
4088486344
Address problem on CI "OutOfMemoryError: GC overhead limit exceeded" 2020-03-05 01:53:34 +01:00
ligi
714ffd0ea1
Use new R8 version to fix #247 (#249)
Co-authored-by: ligi <ligi@ligi.de>
2020-03-05 01:30:30 +01:00
ligi
0e415479f7
Bump gradle to 6.2.2 2020-03-05 00:42:45 +01:00
ligi
3339d318fe
Use Koin 2.1.2 2020-03-05 00:42:45 +01:00
ligi
2a8b39ce6b
Use Kotlin 1.3.70 2020-03-05 00:42:45 +01:00
ligi
105b3195a2
Bump version to 3.5.4 2020-03-01 05:05:37 +01:00
ligi
f0485d7865
Replace the rest of the bus with channels and remove EventBus 2020-03-01 05:05:37 +01:00
ligi
6c5298b071
Convert scanning events from bus to channel 2020-03-01 05:05:37 +01:00
ligi
5fb07e0508
Migrate from kodein to koin 2020-03-01 05:05:37 +01:00
ligi
2831f60c30
Introduce PassScanActivity
Hope to fix #244 - nicer anyway - less dialogs is better
2020-03-01 03:44:58 +01:00
ligi
b98155e48b
Use gradle 6.2.1 2020-02-29 21:28:54 +01:00
ligi
a61bae906e
Enable multidex for androidTest 2020-02-29 20:47:13 +01:00
ligi
96cf096f89
Get rid of ProgressDialog when importing 2020-02-29 18:57:41 +01:00
ligi
1b6353d470
Use coroutines 2020-02-29 18:26:02 +01:00
ligi
dd187ab69f
Use replaceText instead of typeText for faster UI tests 2020-02-29 18:11:00 +01:00
ligi
a6f7d4699f
Get rid of 2 smelly !! 2020-02-29 18:11:00 +01:00
ligi
19372eaa27
Use androidx PreferenceManager 2020-02-29 18:11:00 +01:00
ligi
c3a5912ff0
Fix notifications for SDK > 25 2020-02-29 18:11:00 +01:00
ligi
85a4501d12
Use eventbus 3.2.0 2020-02-29 17:38:35 +01:00
ligi
b5052184b2
Stop using deprecated method 2020-02-29 03:31:00 +01:00
ligi
ba931c787f
Use unmock 0.7.5 2020-02-29 03:05:33 +01:00
ligi
4e3da926f6
Use versions plugin 0.28 2020-02-29 03:05:22 +01:00
ligi
5a6c43fe76
Use build plugin 3.6.1 2020-02-29 03:05:08 +01:00
ligi
af4c42033c
Use trulesk 0.31 2020-02-29 03:04:43 +01:00
ligi
7fc5c58d98
Use zip4j 2.3.2 2020-02-29 02:53:38 +01:00
ligi
bf6c0226ff
Bump version to 3.5.3 2020-02-16 20:02:09 +01:00
ligi
0b4aad772b
Improve kotlin style 2020-02-16 18:11:27 +01:00
ligi
4e62614ef9
Improve shortcut handling by using ShortcutManagerCompat
supports shortcuts on newer versions
2020-02-16 17:51:57 +01:00
ligi
1da68a5706
Improve variable naming 2020-02-16 17:51:57 +01:00
ligi
ec80dced3d
Bump version to 3.5.2 2020-02-16 07:49:35 +01:00
Falko Richter
dcb8fb6eff
remove g+ 2020-02-16 07:32:06 +01:00
ligi
b47efaf943
Use com.google.android.material 1.1.0 2020-02-16 06:40:00 +01:00
ligi
a759829497
Bump backend versions 2020-02-16 06:40:00 +01:00
ligi
a7b1acb57b
Remove indent from preferences (via iconSpaceReserved false) 2020-02-16 06:40:00 +01:00
ligi
9d6025546f
Use play-publisher 1.2.2 2020-02-16 06:40:00 +01:00
ligi
ed999afcd4
Use mockito 3.2.4 2020-02-16 06:40:00 +01:00
ligi
5f6b6b2e6d
Use dexmaker-mockito 2.25.1 2020-02-16 06:39:59 +01:00
ligi
a10ea1330f
Use zip4j 2.3.1 2020-02-16 06:39:59 +01:00
ligi
977c341f0e
Use RecyclerView 1.1.0 2020-02-16 06:39:59 +01:00
ligi
8a55eafff2
Use Moshi 1.9.2 2020-02-16 06:39:59 +01:00
ligi
4af1c28b1c
Use snackengage 0.24 2020-02-16 06:39:59 +01:00
ligi
0d206f62bc
Migrate to leakcanary 2.2 2020-02-16 06:39:59 +01:00
ligi
38f0706855
Limit versions tool to production 2020-02-16 06:39:59 +01:00
ligi
5865182001
Use kotpref 2.10.0 2020-02-16 06:39:59 +01:00
ligi
b4b3eca448
Update threeten(a)bp 1.4.1(1.2.2) 2020-02-16 06:39:58 +01:00
ligi
16b4b05db0
Use gradle 6.1.1 2020-02-16 06:39:58 +01:00
ligi
f3738027b2
Improve UI test setup
care for permissions and update trulesk/test-bulter
2020-02-16 06:39:58 +01:00
ligi
24f6344663
Use android build plugin 3.5.3 2020-02-16 06:39:58 +01:00
ligi
5b1fb49f60
Use unmock plugin 0.7.4 2020-02-16 06:39:58 +01:00
ligi
ac6c67ae0e
Use kotlin 1.3.61 2020-02-16 06:39:58 +01:00
ligi
2b44c6d831
Use zip4j 2.2.4 2020-02-16 06:39:55 +01:00
ligi
fa45294f21
Create FUNDING.yml 2019-11-14 12:32:30 +09:00
ligi
95232ec455
Bump version to 3.5.1 2019-10-30 14:12:38 +09:00
ligi
4374e819d7
Use moshi code generation 2019-10-30 13:10:25 +09:00
ligi
ae92eeab70
Remove buildToolsVersion - not needed anymore 2019-10-30 12:20:51 +09:00
ligi
dfdc8e6c09
Bump version to 3.5.0 2019-10-30 11:50:28 +09:00
Harry Johnson
7cb399d72c White navbar in day theme (#229) 2019-10-30 11:47:18 +09:00
ligi
97d3fd8f40
Bump sdk to 29 2019-10-30 11:19:28 +09:00
LemonRain
532ede4b17 fixed bugs in the URLRewriteActivity and USAirwaysLoadActivity (#230) 2019-10-30 10:49:21 +09:00
Liuhao
c3c3ae1951
Adjusted part of the code 2019-10-30 10:37:11 +09:00
Liuhao
d624383d91
fixed some bug in the application and makes it looks DRY 2019-10-30 10:37:11 +09:00
LemonRain
e853941de9
Update android/src/main/java/org/ligi/passandroid/ui/quirk_fix/URLRewriteActivity.java
Co-Authored-By: Simon Tenbeitel <simon.tenbeitel@gmail.com>
2019-10-30 10:37:11 +09:00
Liuhao
b3eeda7cff
fixed bugs in the URLRewriteActivity and USAirwaysLoadActivity 2019-10-30 10:37:08 +09:00
Jonas Bögle
d6d276e349 capitalize menu items (#235) 2019-10-30 02:50:08 +09:00
ligi
48d2a945cb
Use moshi 1.9.0 2019-10-30 02:27:58 +09:00
ligi
8fa742aeab
Use unmock 0.7.3 2019-10-30 02:27:58 +09:00
ligi
ae92d78443
Use gms 17.0.0 2019-10-30 02:27:58 +09:00
ligi
8e636ee501
Use permissionsdispatcher 4.6.0 2019-10-30 02:27:58 +09:00
ligi
1ec8d120a0
Use snackengage 0.22 2019-10-30 02:27:58 +09:00
ligi
98dc2ae8ef
Use androidx.annotation 1.1.0 2019-10-30 02:27:58 +09:00
ligi
1ec753222d
Use androidx.preference 1.1.0 2019-10-30 02:27:58 +09:00
ligi
488d1be731
Use tracedroid 3.0 2019-10-30 02:27:58 +09:00
ligi
c7460138c4
Use extracompats 1.0 2019-10-30 02:27:58 +09:00
ligi
ab41c587ab
Use KAXT 1.0 2019-10-30 02:27:58 +09:00
ligi
4efab4213e
Replace spoon with composer 2019-10-30 02:27:58 +09:00
ligi
e187c5999a
Use gradle 5.6.3 / build-plugin 3.5.1 / appengine 2.2.0 2019-10-30 02:27:58 +09:00
ligi
6ad1049351
Cleanup 2019-10-30 01:40:40 +09:00
ligi
b9082a97cd
Migrate to zip4j 2.2.3 2019-10-29 23:59:09 +09:00
ligi
e3a93a8010
Use appcompat 1.1.0 2019-10-29 23:59:09 +09:00
ligi
c0196680a0
Use androidx.annotation 1.1.0 2019-10-28 23:30:00 +09:00
ligi
1c8c5a254f
Use Kotpref 2.9.2 2019-10-28 23:30:00 +09:00
ligi
ac611db5f8
Use espresso 3.2.0 2019-10-28 23:01:31 +09:00
ligi
1a8360417c
Use junit 1.1.1 2019-10-28 23:01:15 +09:00
ligi
1a7b8630fb
Cleanup 2019-10-28 22:51:10 +09:00
ligi
a4e710d804
Use androidx.test 1.2.0 2019-10-28 22:49:37 +09:00
ligi
15d8514b6c
Use mockito 3.1.0 2019-10-28 22:46:42 +09:00
ligi
7a7abe61c2
Change java compatibility from 1.7 to 1.8
needed for some new libs
2019-10-28 22:46:42 +09:00
ligi
9a7a07e7d4
Use threetenbp 1.4.0 2019-10-28 20:45:33 +09:00
ligi
04b745705d
Use dexmaker-mockito 2.25.0 2019-10-28 19:48:39 +09:00
ligi
16bcb956db
Use permissionsdispatcher 4.5.0 2019-10-28 16:15:41 +09:00
ligi
6f78caac2a
Use threetenabp 1.2.1 2019-10-28 15:44:10 +09:00
ligi
d7c2558980
Use Kotlin 1.3.50 2019-10-28 15:42:09 +09:00
ligi
61038ec0e5
Use versions plugin 0.27.0 2019-10-28 15:40:46 +09:00
ligi
9deb2402e9
Workaround for @jitpack issue https://github.com/jitpack/jitpack.io/issues/3973 2019-10-27 00:23:03 +09:00
ligi
3cbb0af0c6
Use new kontinuum config format 2019-10-27 00:23:03 +09:00
Jonas Bögle
a6baee2ca2 Refactor: SDK 28, androidx, ... (#221) 2019-05-06 09:01:33 +02:00
Jonas Bögle
b95ff6d63a fix #217 (#222) 2019-03-10 17:58:23 +01:00
Stefan Rutzmoser
f6f6d105d0 Improve German translation and add strings for menu_print and menu_edit (#214)
* Add translation entries for menu_print and menu_edit

* Improve German translation
2018-09-22 20:18:34 +02:00
Tobias Preuss
dd5eeebc3f Use Gradle wrapper 4.10. (#215)
+ Updates jar missing in b71e8ca747.
+ Use the following command:
  gradle wrapper --gradle-version 4.10 --distribution-type all
+ See https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:adding_wrapper.
2018-09-22 19:51:11 +02:00
ligi
acb8f345e2
Bump version to 3.4.9 2018-09-13 19:25:47 +02:00
Jonas Bögle
9bd99a8f73 Credit and minor improvements (#213) 2018-09-13 16:18:22 +02:00
ligi
a553a34cca
Use KotPref 2.6.0 2018-09-11 02:58:58 +02:00
ligi
b71e8ca747
Use gradle 4.10 2018-09-11 02:55:45 +02:00
ligi
c1850d10c9
Bump version to 2.4.8 2018-09-10 21:44:32 +02:00
Jonas Bögle
536fd4faf1 added swipe functionality (#212) 2018-09-10 21:38:06 +02:00
ligi
dab0d7e48f
Use Kotlin 1.2.61 2018-08-24 19:30:03 +02:00
ligi
ad2c4ffa25
Use threetenbp 1.3.7 2018-08-24 19:27:08 +02:00
ligi
6f6de55235
Decapitalize @cketti on his wish 2018-08-24 19:18:17 +02:00
Simon Tenbeitel
9d2300bd0a Add support for barcode type EAN8. Closes #205 (#209) 2018-08-17 15:24:34 +02:00
ligi
bfcbe4446e
Bump version to 3.4.7 2018-08-02 11:20:42 +02:00
Simon Tenbeitel
bf38f5f289 Only show navigation tabs for at least 2 passes. Closes #189 (#196)
* Only show navigation tabs for at least 2 passes

Closes #189

* Only show navigation tabs when only default topic exists
2018-08-02 11:17:31 +02:00
Xose M
1f86ea002f first galician (gl) translation (#182) 2018-08-02 01:38:18 +02:00
ligi
6ceb648bf4
Use unmock 0.6.5 2018-08-01 22:29:04 +02:00
ligi
ea1d39b704
Use KAXT 0.20 2018-08-01 22:20:46 +02:00
ligi
7ddf457732
Use kotlin 1.2.60 2018-08-01 22:14:00 +02:00
ligi
85a97f0dcf
Use okhttp 3.11.0 2018-07-29 16:19:42 +02:00
ligi
99fb620a5b
Use versions plugin 0.20.0 2018-07-29 16:00:46 +02:00
ligi
ad752cc251
Use gralde 4.9 2018-07-29 15:39:47 +02:00
ligi
1d2a413426
Use kotlin 1.2.51 2018-07-29 15:39:47 +02:00
ligi
fcd9ba26c2
Use gradle 4.8 2018-06-04 22:05:37 +02:00
Simon Tenbeitel
9dbc837430 Multi-line support for emptyView. Fixes #197 2018-05-30 15:53:31 +02:00
ligi
64ec30d29b
Use gradle 4.7 2018-05-30 14:54:16 +02:00
ligi
9756e696b3
Use threetenabp 1.1.0 2018-05-21 10:05:59 +02:00
ligi
6830623480
Use Kotlin 1.2.41 2018-05-21 09:48:38 +02:00
ligi
a3d8bbc572
Bump version to 3.4.6 2018-04-07 00:14:53 +02:00
Simon Tenbeitel
eac62d8bd8
Close navigation drawer on back press 2018-04-06 23:50:24 +02:00
ligi
9923af6819
Mark constant and remove companion object 2018-04-06 17:43:58 +02:00
ligi
7aba6e61de
Use kotpref 2.5.0 2018-04-05 14:33:41 +02:00
ligi
d26478499c
Use gradle 4.6 2018-04-05 13:25:38 +02:00
ligi
e049f2721e
Revert "Use leak-canary 1.5.4"
This reverts commit 2d50ff49b8.

Unfortunately the new version is minSDK 14 and we are still 9
2018-04-05 12:45:27 +02:00
ligi
492232f22d
Use Kotlin 1.2.31 2018-04-05 12:37:20 +02:00
ligi
8596289d9f
Mark as const 2018-02-10 17:58:07 +01:00
ligi
2d50ff49b8
Use leak-canary 1.5.4 2018-01-21 20:35:52 +01:00
ligi
dede322da3
Bump version to 3.4.5 2018-01-21 17:00:10 +01:00
ligi
d39c0d1cbc
Fix a crash with new zxing on old phones - closes #181 2018-01-21 16:40:32 +01:00
ligi
346d71f434
Bump version to 3.4.4 2018-01-07 22:43:05 +01:00
ligi
e51369af86
Add intent-filter for application/pkpass (without vendor prefix)
Have seen this in the wild :-(
2018-01-07 19:56:55 +01:00
ligi
732b86ebca
Use kotpref 2.2.0 2018-01-03 14:10:22 +01:00
ligi
d0be7e6815
Use okhttp 3.9.1 2018-01-03 14:10:21 +01:00
ligi
e01ebf5230
Use Kotlin 1.2.0 2018-01-03 14:10:20 +01:00
ligi
4c06188b36
Use snackengage 0.15 2017-11-12 18:18:43 -05:00
ligi
1066dd34d5
Use zxing 3.3.1 2017-11-12 18:16:12 -05:00
ligi
328bd4bd4b
Use kaxt 0.19 2017-11-12 18:03:04 -05:00
ligi
6f37e4d78f
Use versions plugin 0.17.0 2017-10-27 15:48:59 +02:00
ligi
9729d9da63
Use unmock 0.6.4 2017-10-27 15:31:44 +02:00
ligi
f6fdf504c9
Use gradle 4.2.1 2017-10-07 21:47:31 +02:00
ligi
9eb7362b3a
Use kotlin 1.1.51 2017-10-01 02:41:36 +02:00
ligi
1ffa7fd49d
Bump version to 3.4.3 2017-09-18 18:55:39 +02:00
ligi
0ae2b1712e
Improve code 2017-09-18 14:38:43 +02:00
ligi
63a06fcad0
Really adapt to new AirCanadas server-side change (this time verified with a pass from a user) 2017-09-18 14:38:43 +02:00
Simon Tenbeitel
173596098e Find passes with uppercase file extension #169 2017-09-13 18:30:34 +02:00
ligi
6049192d2f
Bump version to 3.4.2 2017-09-11 22:06:45 +02:00
ligi
4678da2a10
Use okhttp 3.9.0 2017-09-06 17:58:32 +02:00
ligi
92bcc806e4
Improve code 2017-09-06 17:58:32 +02:00
ligi
d7e25ad1fd
Move matcher to own file and add test for visibility of open_file in FAM
closes #164
2017-09-06 17:34:29 +02:00
ligi
7d5babafef
Move the matcher 2017-09-06 16:41:15 +02:00
ligi
5ddc55ac40
Use kaxt 0.18 2017-09-06 16:34:43 +02:00
ligi
d5b326dab9
Request permission when importing pass 2017-09-06 16:34:43 +02:00
ligi
d89b7f8157
Cleanup 2017-09-06 16:34:43 +02:00
ligi
0e76e775fa
Cleanup 2017-09-06 16:34:43 +02:00
ligi
55b8124853
This else branch is not needed anymore 2017-09-06 16:34:28 +02:00
ligi
4a6db0f8b3
Set to visible if SDK>19 2017-09-06 12:27:44 +02:00
Simon Tenbeitel
e02af2ef81 Add Simon Tenbeitel to credits 2017-09-06 12:25:33 +02:00
Simon Tenbeitel
84ecb71b31 Fix confusing sort by time implementation #147 2017-09-06 12:25:33 +02:00
ligi
9e73a8d6d9
Bump version to 3.4.1 2017-09-02 19:36:49 +02:00
ligi
908181487e
Cleanup code 2017-09-02 19:12:15 +02:00
ligi
ddbaff13dc
Deal with null and better naming 2017-09-02 19:12:15 +02:00
ligi
57631eb18e
Don't show shortcut option for Oreo and above 2017-09-02 19:12:15 +02:00
ligi
99aa0df767
Use the correct icon for launcher shortcut - closes #162 2017-09-02 19:12:15 +02:00
ligi
8002130a38
Respect new aircanada url - closes #160 2017-09-02 19:12:15 +02:00
ligi
08359f6bc7
Use build-tools 25.0.3 2017-09-02 19:12:15 +02:00
ligi
dcdca77fc6
Use espresso-contrib 3.0.1 2017-09-02 19:12:14 +02:00
ligi
bbfc09ba1d
Use trulesk 0.21 2017-09-02 19:12:14 +02:00
ligi
d5fbdc8b80
Use kotlin 1.1.4-3 2017-09-02 10:40:25 +02:00
ligi
a7eb22ce2c
Use gradle 4.1 2017-09-02 04:12:31 +02:00
ligi
3e08791324
Use versions plugin 0.15 2017-09-02 04:12:31 +02:00
ligi
c3d27e4eb1
Use kodein 4.1.0 2017-08-27 21:12:51 +02:00
ligi
9d180cf455
Use snackengage 0.14 2017-08-27 21:12:51 +02:00
ligi
c5f32b275d
Use mockito-core 2.9.0 2017-08-27 21:12:51 +02:00
ligi
704503b9f5
Use okhttp 3.8.1 2017-08-27 20:37:09 +02:00
ligi
b2727019c5
Use kotpref 2.1.2 2017-08-27 19:19:01 +02:00
ligi
954f745a0c
Use espresso-contrib 3.0.0 and trulesk 0.20 2017-08-27 19:04:07 +02:00
ligi
65ee6ca578
Use threetenbp 1.3.6 2017-08-27 18:41:41 +02:00
ligi
70391dd8fe
Use kotlin 1.1.4-2 2017-08-20 13:06:08 +02:00
ligi
d5b30fe405
Use google maven repo 2017-08-20 13:05:37 +02:00
ligi
db0da8a178
Use mockito 2.8.47 2017-06-20 16:12:04 +02:00
ligi
be904d4848
Use build-plugin 2.3.3 2017-06-20 16:12:04 +02:00
ligi
e686a338c3
Use kotlin 1.1.2-5 2017-06-20 16:12:04 +02:00
ligi
6e00aaeb55
Use kodein 4.0.0 2017-06-20 16:12:03 +02:00
ligi
c286a6837a
Use threetenbp 1.3.4 2017-05-23 08:38:47 +02:00
ligi
417d982115
Cleanup 2017-05-23 08:38:47 +02:00
ligi
b92ae21903
Use snackengega 0.13 2017-05-12 10:01:36 +02:00
ligi
553115fce1
Use ben-manes versions plugin 0.14.0 2017-05-12 08:46:32 +02:00
ligi
a947a296ec
Use unmock 0.6.0 2017-05-12 08:24:58 +02:00
ligi
6405ff1015
Use mockito 2.8.9 2017-05-11 11:13:53 +02:00
ligi
0d766598ab
Use okhttp 3.7.0 2017-05-11 10:57:15 +02:00
ligi
b9ada8fde3
Use kotlin 1.1.2-3 2017-05-11 10:44:53 +02:00
ligi
174eaef9dd
Simplify 2017-04-26 17:00:12 +02:00
ligi
2eae832bc0
Bump version to 3.4.0 2017-04-13 19:13:56 +02:00
ligi
601f16eca1
Add Amazon store badge 2017-04-13 19:10:50 +02:00
ligi
00e740da91
Use SnackEngage 0.12 2017-04-13 04:38:15 +02:00
ligi
6ad554616b
Add dutch translations - closes #150 2017-04-13 04:38:09 +02:00
ligi
74fe5540a4
Use gradle 3.5 2017-04-13 03:37:26 +02:00
ligi
923545fd06
Replace Dagger with Kodein 2017-04-12 00:43:29 +02:00
ligi
b3c29e2b8a
Improve the excludes for AndroidTest 2017-04-12 00:43:28 +02:00
ligi
5ca84140bd
Convert App class to Kotlin 2017-04-12 00:43:28 +02:00
ligi
8b020075e0
Use KAXT 0.17 2017-04-12 00:43:28 +02:00
ligi
b57b51f609
Use trulesk 0.19 2017-04-12 00:43:28 +02:00
ligi
d4dee029de
Ignore one device in espresso-tests for now
Context: http://stackoverflow.com/questions/43308368/app-had-used-a-different-appcomponent-during-pre-verification
2017-04-12 00:43:28 +02:00
ligi
90e1c75580
Use mockito 2.7.22 2017-04-12 00:43:27 +02:00
ligi
1207e95f43
Implement singleFlavor flag for CI 2017-04-12 00:43:27 +02:00
ligi
ac8a7339cd
Remove unused parameters 2017-04-12 00:43:27 +02:00
ligi
b9ca1ce816
Use build-plugin 2.3.1 2017-04-12 00:43:26 +02:00
ligi
c831d5cb3b
Use JSR305 3.0.2 2017-04-12 00:43:26 +02:00
ligi
91cc5b7ff9
Use Mockito 2.7.21 2017-04-12 00:43:26 +02:00
ligi
c6185b29db
Use support 25.3.1 2017-04-12 00:43:26 +02:00
ligi
222c2df562
Use kotpref 2.1.1 2017-04-12 00:43:25 +02:00
ligi
61c40524cb
Cleanup 2017-04-12 00:43:25 +02:00
ligi
f4f74566ef
Filter build variants 2017-04-12 00:43:25 +02:00
ligi
01622c9643
Improve code style 2017-04-12 00:43:25 +02:00
ligi
9aa2a6d41d
Use Mockito 2.7.19 2017-04-12 00:43:24 +02:00
ligi
59dcba0831
Use KAXT 0.16 2017-04-12 00:43:24 +02:00
ligi
d582b40160
Use SnackEngage 0.10 with play/amazon split 2017-04-12 00:43:24 +02:00
ligi
91e350cb2e
Cleanup 2017-04-12 00:43:24 +02:00
ligi
7e90275433
Use moshi 1.4.0 2017-04-12 00:43:24 +02:00
ligi
d806076fe7
Use dagger 2.10 2017-04-12 00:43:24 +02:00
ligi
2f182665d1
Use Kotpref 2.1.0 2017-04-12 00:43:23 +02:00
ligi
5f6017b169
Use Kotlin 1.1.1, support 25.3.0 and trulesk 0.18 2017-04-12 00:43:23 +02:00
ligi
54a1c96eff
Use mockito 2.7.18 2017-04-12 00:43:23 +02:00
ligi
e76700a589
Use permissions dispatcher 2.3.2 2017-04-12 00:43:23 +02:00
ligi
a95ba652c6
Use gradle 3.4.1 2017-04-12 00:43:23 +02:00
ligi
897e298764
Add kontinuum config 2017-04-12 00:43:23 +02:00
ligi
21e167c126
Use Kotlin 1.1 and latest KAXT / trulesk 2017-03-04 19:25:57 +01:00
ligi
3a9d192ba7
Use latest build-plugin and gradle - remove sdkmanager
sdkmanager is now incompatible and was not supported anymore anyway
2017-03-04 19:25:56 +01:00
Simon Tenbeitel
1f4ac8bd7b Close FAB menu on back pressed 2017-03-01 13:45:49 +01:00
ligi
bc8cc0283b
Do not deoend on dev as it does not exist in some phases.
especially caused by needing some space on jenkins - and deleting dev it frees up some

ref: https://github.com/ligi/PassAndroid/pull/143
2017-03-01 12:42:40 +01:00
ligi
4858b36a4a
Bump version to 3.3.9 2017-02-27 21:24:10 +01:00
ligi
dc4073d372
Migrate to kotpref2 2017-02-27 21:18:36 +01:00
ligi
76a71dd87c
Support UTF-8 BOM - closes #141 2017-02-26 21:01:56 +01:00
ligi
0b7ab6dfe0
Use support 25.2.0 2017-02-23 02:13:32 +01:00
ligi
245e8b3ae9
Bump version to 3.3.8 2017-02-19 18:36:20 +01:00
ligi
b150886b9b
Add first location name to calendar when available - closes #140 2017-02-19 17:57:33 +01:00
ligi
f431b97deb
Use mockito 2 & allow final mocking 2017-02-19 17:13:18 +01:00
ligi
1e674dce3a
Shorten code 2017-02-07 13:09:26 +01:00
ligi
b8a4974744
Remove build-badge - snap-ci is terminating service 2017-02-06 21:21:24 +01:00
ligi
16e0c26f45
Bump version to 3.3.7 2017-02-06 10:17:51 +01:00
ligi
be3d15f2b8
Support DATA_MATRIX format - closes #139 2017-02-06 00:21:39 +01:00
ligi
2680f5f05d
Sort elements 2017-02-05 23:55:56 +01:00
ligi
388bc137e1
Use threetenabp 1.0.5 2017-02-05 22:31:39 +01:00
ligi
9524c63ce2
Use permission-dispatcher 2.3.1 2017-02-03 01:07:27 +01:00
ligi
84017d57c3
Use support 25.1.1 2017-02-03 01:07:14 +01:00
ligi
429531c624
Use okhttp 3.6.0 2017-02-02 23:26:48 +01:00
ligi
7398f69149
Add new languages from transifex 2017-01-29 22:55:18 +01:00
ligi
361fda7b75
Bump version to 3.3.6 2017-01-24 21:59:40 +01:00
ligi
e94ef98332
Support importing barcode(s!) field - closes #138 2017-01-24 19:45:43 +01:00
ligi
77a96bf8dc
Update spanish translations 2017-01-24 19:45:17 +01:00
ligi
7ae9e9ac5a
Bump version to 3.3.5 2016-12-24 21:47:44 +01:00
ligi
c67a2a8269
Use Kotpref 1.6.0 2016-12-24 21:47:44 +01:00
ligi
fb399f6dae
Improve handling of currentPass 2016-12-24 21:47:37 +01:00
ligi
b99fe8a359
Improve spanish translations ( transifex pull ) 2016-12-24 17:43:37 +01:00
ligi
480868c699
Shorten code 2016-12-24 12:52:09 +01:00
ligi
413df5f2ef
Shorten code 2016-12-24 12:46:39 +01:00
ligi
eea18f66cd
Support Interleaved 2 of 5 (ITF, Interleaved Two of Five) Barcode - closes #131 2016-12-24 12:44:11 +01:00
ligi
387e5f76bd
Simplify code 2016-12-24 12:44:11 +01:00
ligi
79f6a5ae81
More descriptive code 2016-12-24 01:05:07 +01:00
ligi
553a2de631
Shorten code 2016-12-24 00:42:00 +01:00
ligi
f097738630
Use KAXT 0.12 2016-12-23 14:00:49 +01:00
ligi
02fe60bdfd
Use okhttp 3.5.0 2016-12-21 22:31:27 +01:00
ligi
c48b84cfd3
Use threetenbp 1.3.3 2016-12-21 22:31:02 +01:00
ligi
8f00b82268
Use trulesk 0.12 2016-12-21 13:55:51 +01:00
ligi
8b796d9efe
Use Kotlin 1.0.5-3 2016-12-21 00:29:19 +01:00
ligi
d4455fff68
Use KotPref 1.5.0 2016-12-16 13:44:26 +01:00
ligi
04a25a1169
Use LinkifyCompat 2016-12-15 16:53:28 +01:00
ligi
b9b9920a91
Use play-services 10.0.1 2016-12-14 18:31:01 +01:00
ligi
48a66190c4
Use support 25.1.0 2016-12-14 18:27:19 +01:00
ligi
085fc750fb
Use build-toos 25.0.2 2016-12-14 18:26:57 +01:00
ligi
288c85ae68
Use permission-dispatcher 2.3.0 2016-12-14 18:26:35 +01:00
ligi
7e2526a20a
Bump version to 3.3.4 2016-12-14 17:21:54 +01:00
ligi
46b91f040c
Fix a visibility problem in CondensedPassViewHolder and add tests to cover this 2016-12-14 16:42:12 +01:00
ligi
c8fff3735a
Give the icon a wee bit of padding and set gravity to end instead of right 2016-12-10 13:34:40 +01:00
ligi
f364aad0ba
Place the icons on the correct side 2016-12-10 13:34:06 +01:00
ligi
847b19feac
Bump version to3.3.3 2016-12-10 13:00:21 +01:00
ligi
ce4030ac5d
Fix time and nav icon-swap - closes #126 2016-12-10 12:57:40 +01:00
ligi
9da9f7b179
Use permissionsdispatcher 2.2.1 2016-12-08 14:41:53 +01:00
ligi
e7b959acc7
Shorten code 2016-12-08 13:55:47 +01:00
ligi
2fb1eeee28
Use build-plugin 2.2.3 2016-12-08 13:55:35 +01:00
ligi
9d3ed8b5df
Update gradle to 3.2 2016-12-03 19:16:56 +01:00
ligi
1361bb6fc4
Bump version to 3.3.2 2016-12-02 22:48:40 +01:00
ligi
3c873c2bd1
Add play publisher plugin 2016-12-02 22:26:10 +01:00
ligi
e476be2011
Shorten code 2016-12-02 22:26:10 +01:00
ligi
de281b6f15
Update ( shorten ) russian string 2016-12-02 22:21:56 +01:00
ligi
2a37166d4c
Fix a NPE when only a valid-timespan to is given - closes #122
Also add tests to cover this path
2016-12-02 22:19:54 +01:00
ligi
deaff19be7
Use lateinit instead of nullable value 2016-12-02 22:19:54 +01:00
ligi
89e1512163
Shorten code 2016-12-02 22:19:54 +01:00
ligi
512049ea54
Update russian translations 2016-12-02 22:19:54 +01:00
ligi
7b38bdb40d
Migrate more code to Kotlin 2016-12-02 22:19:54 +01:00
ligi
b72d8d42c8
Correct the name 2016-11-30 03:36:32 +01:00
ligi
07d505dc89
Use isNotEmpty 2016-11-30 03:31:55 +01:00
ligi
b30af764fb
AddToCalendar as TopLevel Function 2016-11-30 03:31:38 +01:00
ligi
bf00ea895a
Extract string 2016-11-30 03:21:57 +01:00
ligi
b92041b13a
Remove survival-manual promo 2016-11-30 03:16:51 +01:00
ligi
5953756578
We can use ActivityTestRule here 2016-11-30 03:05:54 +01:00
ligi
6a2fccbb16
Bump version to 3.3.1 2016-11-28 17:46:34 +01:00
ligi
4dfaac8af7
Cleanup 2016-11-28 17:27:30 +01:00
ligi
520b66aab7
Use trulesk 0.9 which now activates testbuttler
This removes a ugly emulator flakyness I had currently
"launcher3 has crashed" ..-(
2016-11-28 16:44:46 +01:00
ligi
c4d23e366d
Use a better symbol for translate 2016-11-27 18:03:00 +01:00
ligi
9c109c9efc
Also Linkify front-fields 2016-11-27 17:17:48 +01:00
ligi
b496d0e477
Give MORE/LESS a ripple 2016-11-27 17:13:15 +01:00
ligi
582a1519bf
Replace clickable_bg and Improve TimeAndNavBar 2016-11-27 16:51:54 +01:00
ligi
9348ad8f15
Convert more to kotlin 2016-11-27 11:58:34 +01:00
ligi
45a436a3b2
Make code shorter with new KAXT extension 2016-11-27 11:54:48 +01:00
ligi
c313553b13
Add ability to cancel a scan - closes #115 2016-11-27 11:43:48 +01:00
ligi
a4794ee587
Convert more code to kotlin & cleanup 2016-11-27 10:49:34 +01:00
ligi
c604fca0ac
Improve wording 2016-11-26 21:12:51 +01:00
ligi
b87094a544
Fix the link for samples in help 2016-11-26 21:12:50 +01:00
ligi
8ac7969093
Convert more code to kotlin 2016-11-26 21:12:50 +01:00
ligi
39c10520d3
Convert the rest of unit-tests to Kotlin and cleanup 2016-11-26 12:49:03 +01:00
ligi
e869a61a33
Improve tests 2016-11-26 12:49:03 +01:00
ligi
28a806fd52
Cleanup - we do not use lombok anymore 2016-11-25 06:05:06 +01:00
ligi
2ad3cb5402
Convert test to kotlin 2016-11-25 04:48:25 +01:00
ligi
67727e9159
Use the screenShot method from the rule 2016-11-25 04:47:58 +01:00
ligi
3b83081419
Use material rythm 2016-11-25 02:46:04 +01:00
ligi
fda0afd2e3
Correct the colors for the zoom in night mode 2016-11-25 02:40:39 +01:00
ligi
15a9743769
Bump version to 3.3.0 2016-11-25 00:04:26 +01:00
ligi
6e9df603e8
Add intent-filters for PDF - closes #114 2016-11-25 00:01:37 +01:00
ligi
2bb0342ed2
Cleanup 2016-11-25 00:01:37 +01:00
ligi
9f0b9a37c5
Use trulesk / update KAXT to 0.4 / Cleanup 2016-11-25 00:01:37 +01:00
ligi
7cc2d83825
Nice up code 2016-11-24 12:46:45 +01:00
ligi
1f19a2c6c7
Use build-tools 25.0.1 2016-11-24 12:46:35 +01:00
ligi
9310188f6b
Update translations 2016-11-20 22:18:39 +01:00
ligi
5fcdd516c8
Update german translations 2016-11-20 16:38:29 +01:00
ligi
f4feb0c3ca
Update russian translations 2016-11-20 16:36:57 +01:00
ligi
511e749ca2
Deduplicate returns 2016-11-19 19:41:02 +01:00
ligi
6389046047
Bump version to 3.2.9 2016-11-17 11:58:24 +01:00
ligi
4f655a4d05
Add russian translations 2016-11-17 11:39:54 +01:00
ligi
df5e987683
Initial Shortcut feature - closes #112 2016-11-17 11:39:54 +01:00
ligi
001326c40d
Replace AXT with KAXT or native kotlin code & cleanup 2016-11-17 11:38:43 +01:00
ligi
8bca0eb83a
Bump version to 3.2.8 2016-11-15 18:48:46 +01:00
ligi
e411c45c86
Use kotlin 1.0.5-2 2016-11-15 18:31:16 +01:00
ligi
630897d68f
Use support 25.0.1 2016-11-15 18:29:57 +01:00
ligi
eabd7d4d7d
Use okhttp 3.4.2 2016-11-15 18:29:57 +01:00
ligi
8d89438905
Support TimeSpan when adding to calendar
closes #72
2016-11-15 18:29:56 +01:00
ligi
a7a42420f8
Simplify / leverage kotlin more 2016-11-15 18:29:56 +01:00
ligi
c19357000e
Use isEmpty and isNotEmpty as suggested by lint 2016-11-15 18:29:56 +01:00
ligi
7299e74d58
Simplify 2016-11-15 18:29:56 +01:00
ligi
76d7e86c7c
Use kotlin 1.0.5 2016-11-15 18:29:56 +01:00
ligi
7638a95791
Fix indent and layout 2016-11-15 18:29:56 +01:00
ligi
1cf27c4737
Only show snack for SurvivalManual when english locale and utm track 2016-11-15 18:29:56 +01:00
ligi
4444d21d0f
Cleanup 2016-11-15 18:29:55 +01:00
ligi
deb6fe8e95
Improve the order 2016-11-15 18:29:55 +01:00
ligi
1f8cac4d98
Assert -> AssertThat 2016-11-15 18:29:55 +01:00
ligi
bfb9c03dfe
Add new approach to the bar with time and nav below the pass ( no more wrapped drawables ) 2016-11-15 18:29:55 +01:00
ligi
6652b5166c
Cleanup 2016-10-29 20:44:29 +02:00
ligi
b814c21ec1
Bump version to 3.2.7 2016-10-29 20:16:54 +02:00
ligi
77960c92e6
Do not publish lint html anymore 2016-10-29 20:13:06 +02:00
ligi
2ec15648ce
Update languages 2016-10-29 20:11:56 +02:00
ligi
227cba6667
Add root build.gradle 2016-10-29 20:10:57 +02:00
ligi
7273da05cb
Add tests for navitgation-drawer 2016-10-29 19:50:05 +02:00
ligi
6a7c45e8ae
Add new action in NavDrawer to send people to transifex and improve navigation-code 2016-10-29 18:42:19 +02:00
ligi
78a2d63be3
Improve NavigationDrawer
- extract strings
- correct color
- replace g+ symbol
2016-10-29 18:11:38 +02:00
ligi
572da6eafe
Add a back-field advice 2016-10-29 17:50:04 +02:00
ligi
5c2a1e743e
No more use of butterknife 2016-10-29 17:18:35 +02:00
ligi
6f969f4217
Convert ViewHolder Code to kotlin 2016-10-29 17:18:35 +02:00
ligi
74d4fbe409
DeDuplicate 2016-10-29 17:18:35 +02:00
ligi
098e6bcc9f
Add missing test 2016-10-29 17:18:35 +02:00
ligi
949e312a0f
Do the same but more the kotlin way 2016-10-29 17:18:35 +02:00
ligi
3a11b6ae4e
Cleanup 2016-10-29 17:18:35 +02:00
ligi
cc02abf014
Replace mkdir with mkdirs so it will not fail importing when parent path is not yet there 2016-10-29 17:18:34 +02:00
ligi
7c461160d3
Extract strings and distinguish between image and PDF import 2016-10-29 17:18:34 +02:00
ligi
b1e3bbd241
Shorten code 2016-10-29 17:18:34 +02:00
ligi
42242cdaf5
Convert class to kotlin 2016-10-29 17:18:34 +02:00
ligi
10d13bbd32
Use HtmlCompat 2016-10-29 17:18:34 +02:00
ligi
05b44cadd3
Convert PassViewActivity to Kotlin 2016-10-29 17:18:34 +02:00
ligi
cc8b3fb61f
Add ability to click on large images to zoom in
This was needed after the PDF import for usability
2016-10-29 17:08:51 +02:00
ligi
b307fe61df
Use onResumeFragments instead of onResume 2016-10-29 17:08:51 +02:00
ligi
4e3ccc6c16
Adapt to renaming of version from google-play 2016-10-29 17:08:51 +02:00
ligi
09a34451ff
Cleanup 2016-10-29 17:08:51 +02:00
ligi
3c377a4683
Convert pdf into image when importing - closes #81 2016-10-29 17:08:50 +02:00
ligi
a129422401
Replace deprecated methods 2016-10-29 17:08:50 +02:00
ligi
15786c8a78
Convert event to kotlin 2016-10-29 17:08:50 +02:00
ligi
89297a2736
Improve test code 2016-10-29 17:08:50 +02:00
ligi
13586cc08a
Convert more code to Kotlin 2016-10-24 17:52:53 +02:00
ligi
296d9211b2
Improve the layout for the fullscreen-barcode and make sure alternative-text is displayed in night 2016-10-24 15:57:00 +02:00
ligi
eae71f6d47
Convert FullscreenBarCodeActivity to kotlin 2016-10-24 15:50:57 +02:00
ligi
43bbfcc4b0
Improve editing code 2016-10-24 15:28:19 +02:00
ligi
beb20e782e
Use SnackEngage 0.9 2016-10-24 15:07:21 +02:00
ligi
a479539911
Cleanup 2016-10-23 17:43:41 +02:00
ligi
e4e49f89c4
Improve help code 2016-10-23 17:43:41 +02:00
ligi
f76430f474
Cleanup 2016-10-23 17:43:40 +02:00
ligi
64add36331
Add missing annotations 2016-10-23 17:43:40 +02:00
ligi
ab0d316fd3
Improve settings code and cover with tests 2016-10-23 17:43:40 +02:00
ligi
883bd14309
Extract strings 2016-10-23 16:18:53 +02:00
ligi
7deec6c3b2
Add missing @TargetAPI and NonNull annotations 2016-10-23 16:06:06 +02:00
ligi
0e8550989c
Import MODE_NIGHT_AUTO from the right package 2016-10-23 15:37:00 +02:00
ligi
945a85c76c
Use play-services 9.8.00 2016-10-23 15:05:20 +02:00
ligi
d15c64d19e
Use SDK 25 and build-plugin 2.2.2 2016-10-21 03:20:52 +02:00
329 changed files with 8632 additions and 7174 deletions

22
.ci/kontinuum.json Normal file
View 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
View 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']

View file

@ -2,7 +2,6 @@
Great that you are thinking about submitting a PullRequest! 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 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

3
.gitignore vendored
View file

@ -5,7 +5,6 @@ build
bin bin
gen gen
project.properties project.properties
gradle.properties
local.properties local.properties
*iml *iml
/build.gradle

12
Jenkinsfile vendored
View file

@ -1,3 +1,4 @@
node { node {
def flavorCombination='WithMapsWithAnalyticsForPlay' def flavorCombination='WithMapsWithAnalyticsForPlay'
@ -11,8 +12,8 @@ node {
} catch(err) { } catch(err) {
currentBuild.result = FAILURE currentBuild.result = FAILURE
} finally { } finally {
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "android/build/spoon-output/${flavorCombination}DebugAndroidTest", reportFiles: 'index.html', reportName: 'Spoon']) publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: "android/build/spoon", reportFiles: '*/debug/index.html', reportName: 'Spoon'])
step([$class: 'JUnitResultArchiver', testResults: 'android/build/spoon-output/*/junit-reports/*.xml']) step([$class: 'JUnitResultArchiver', testResults: 'android/build/spoon/*/debug/junit-reports/*.xml'])
} }
} }
@ -22,9 +23,8 @@ node {
} catch(err) { } catch(err) {
currentBuild.result = FAILURE currentBuild.result = FAILURE
} finally { } finally {
publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/outputs/', reportFiles: "lint-results-*Release.html", reportName: 'Lint'])
androidLint canComputeNew: false, defaultEncoding: '', healthy: '', pattern: '', unHealthy: '' androidLint canComputeNew: false, defaultEncoding: '', healthy: '', pattern: '', unHealthy: ''
} }
stage 'test' stage 'test'
try { try {
@ -32,8 +32,8 @@ node {
} catch(err) { } catch(err) {
currentBuild.result = FAILURE currentBuild.result = FAILURE
} finally { } finally {
step([$class: 'JUnitResultArchiver', testResults: 'android/build/test-results/*/*/*.xml']) 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']) publishHTML(target:[allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'android/build/reports/tests/', reportFiles: "*/index.html", reportName: 'UnitTest'])
} }
stage 'assemble' stage 'assemble'

View file

@ -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) [![on Google Play](https://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 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. 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. 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.
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 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 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
@ -17,7 +26,3 @@ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Status
------
[![Build Status](https://snap-ci.com/ligi/PassAndroid/branch/master/build_image)](https://snap-ci.com/ligi/PassAndroid/branch/master)

View file

@ -1,56 +1,29 @@
buildscript {
ext {
support_version = '24.2.1'
dagger_version = '2.7'
kotlin_version = '1.0.4'
butterknife_version = '8.4.0'
play_version = '9.6.1'
}
repositories {
jcenter()
maven { url 'https://jitpack.io' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:2.2.1'
classpath 'de.felixschulze.gradle:gradle-spoon-plugin:2.7.3'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
classpath 'de.mobilej.unmock:UnMockPlugin:0.5.0'
// http://stackoverflow.com/a/33889117/322642
classpath 'com.github.JakeWharton:sdk-manager-plugin:220bf7a88a7072df3ed16dc8466fb144f2817070'
}
}
apply plugin: 'android-sdk-manager'
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'de.felixschulze.gradle.spoon' apply plugin: 'com.trevjonez.composer'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'de.mobilej.unmock' apply plugin: 'de.mobilej.unmock'
apply plugin: 'com.github.ben-manes.versions' apply plugin: 'com.github.triplet.play'
repositories { repositories {
jcenter() jcenter()
mavenLocal() mavenLocal()
maven { url 'https://jitpack.io' } google()
maven { url 'https://www.jitpack.io' }
} }
android { android {
compileSdkVersion 24 compileSdkVersion 29
buildToolsVersion "24.0.2"
defaultConfig { defaultConfig {
versionCode 326 versionCode 356
versionName "3.2.6" versionName "3.5.6"
minSdkVersion 9 minSdkVersion 14
targetSdkVersion 24 targetSdkVersion 29
applicationId "org.ligi.passandroid" applicationId "org.ligi.passandroid"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "org.ligi.passandroid.AppReplacingRunner"
archivesBaseName = "PassAndroid-$versionName" archivesBaseName = "PassAndroid-$versionName"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@ -82,6 +55,22 @@ android {
forPlay { forPlay {
dimension "distribution" 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 { packagingOptions {
@ -95,15 +84,11 @@ android {
exclude 'META-INF/maven/com.google.guava/guava/pom.properties' exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
exclude 'META-INF/maven/com.google.guava/guava/pom.xml' exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
} }
lintOptions { lintOptions {
warning 'MissingTranslation' warning 'MissingTranslation'
warning 'InvalidPackage' warning 'InvalidPackage'
// TODO: remove when this is solved: http://stackoverflow.com/questions/39714487/kapt2-and-butterknife-produce-lint-error-expected-resource-of-type-id
warning 'ResourceType'
} }
buildTypes { buildTypes {
@ -112,98 +97,99 @@ android {
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
} }
debug {
multiDexEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
} }
} }
dependencies { dependencies {
kapt "com.google.dagger:dagger-compiler:$dagger_version" implementation 'org.permissionsdispatcher:permissionsdispatcher:4.6.0'
kapt "com.jakewharton:butterknife-compiler:$butterknife_version" kapt 'org.permissionsdispatcher:permissionsdispatcher-processor:4.6.0'
provided 'org.glassfish:javax.annotation:10.0-b28' implementation "org.koin:koin-android:2.1.2"
kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
androidTestCompile "com.android.support:support-annotations:$support_version" implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') { androidTestImplementation 'com.github.ligi:trulesk:0.31'
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception androidTestUtil 'com.linkedin.testbutler:test-butler-app:2.1.0'
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') { androidTestImplementation 'androidx.test:core:1.2.0'
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception androidTestImplementation 'androidx.test.ext:junit:1.1.1'
exclude group: 'javax.inject' 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.squareup.spoon:spoon-client:1.7.0'
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
androidTestCompile 'org.mockito:mockito-core:1.9.5' implementation 'com.github.ligi:TouchImageView:2.1'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' 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.jraska:falcon-spoon-compat:1.0.3' 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'
compile 'net.lingala.zip4j:zip4j:1.3.2' implementation 'com.github.ligi:KAXT:1.0'
compile "com.jakewharton:butterknife:$butterknife_version" implementation 'com.github.ligi:KAXTUI:1.0'
compile 'com.jakewharton.threetenabp:threetenabp:1.0.4' implementation 'com.github.ligi:loadtoast:1.10.11'
compile 'org.greenrobot:eventbus:3.0.0' implementation 'com.github.ligi:tracedroid:3.0'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" forPlayImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
compile "org.jetbrains.kotlin:kotlin-runtime:$kotlin_version" forFDroidImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
forAmazonImplementation 'com.github.ligi.snackengage:snackengage-amazonrate:0.24'
compile "com.android.support:support-annotations:$support_version" // https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
compile "com.android.support:recyclerview-v7:$support_version" // Don't update to >=3.13 before minSDK 21 + Java 8
compile "com.android.support:appcompat-v7:$support_version" //noinspection GradleDependency
compile "com.android.support:cardview-v7:$support_version" implementation 'com.squareup.okhttp3:okhttp:3.12.1'
compile "com.android.support:design:$support_version"
compile "com.android.support:preference-v7:$support_version"
androidTestCompile "com.android.support:appcompat-v7:$support_version" implementation 'com.larswerkman:HoloColorPicker:1.5'
androidTestCompile "com.android.support:design:$support_version" implementation 'com.google.code.findbugs:jsr305:3.0.2'
compile 'net.i2p.android.ext:floatingactionbutton:1.10.1' 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.ligi:AXT:0.37' implementation 'com.chibatching.kotpref:kotpref:2.10.0'
compile 'org.ligi:tracedroid:1.4' implementation 'com.chibatching.kotpref:initializer:2.10.0'
compile 'com.github.ligi:snackengage:0.8' testImplementation 'androidx.annotation:annotation:1.1.0'
compile 'com.squareup.okhttp3:okhttp:3.4.1' testImplementation 'com.squareup.assertj:assertj-android:1.2.0'
compile 'com.larswerkman:HoloColorPicker:1.5' testImplementation 'junit:junit:4.12'
compile 'com.google.code.findbugs:jsr305:3.0.1' testImplementation "org.mockito:mockito-core:$mockito_version"
compile 'com.squareup.moshi:moshi:1.2.0' testImplementation 'org.threeten:threetenbp:1.4.1'
compile 'com.chibatching:kotpref:1.4.0'
compile 'net.steamcrafted:load-toast:1.0.10' // 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 "com.google.dagger:dagger:$dagger_version" debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
androidTestCompile 'com.google.code.findbugs:jsr305:3.0.1' // requires minSDK 16 according to docs, not sure if it causes an issue
withAnalyticsImplementation 'com.google.android.gms:play-services-analytics:17.0.0'
testCompile "com.android.support:support-annotations:$support_version" withMapsImplementation 'com.google.android.gms:play-services-maps:17.0.0'
testCompile 'com.squareup.assertj:assertj-android:1.1.1'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'org.threeten:threetenbp:1.3.2'
// 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"
compile 'com.github.hotchemi:permissionsdispatcher:2.2.0'
kapt 'com.github.hotchemi:permissionsdispatcher-processor:2.2.0'
} }
spoon { play {
debug = true jsonFile = file('/media/ligi/USBCRED/play.json')
} uploadImages = true
}

View file

@ -1 +0,0 @@
lombok.addGeneratedAnnotation = false

View file

@ -20,31 +20,24 @@
# If your project uses WebView with JS, uncomment the following # If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface # and specify the fully qualified class name to the JavaScript interface
# class: # class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview { #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *; # public *;
} #}
# optimize # optimize
-optimizationpasses 2 -optimizationpasses 2
-optimizations !code/simplification/arithmetic -optimizations !code/simplification/arithmetic
-dontusemixedcaseclassnames -dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses -dontskipnonpubliclibraryclasses
# AppCompat # Keep line numbers to alleviate debugging stack traces
-dontwarn android.support.v7.** -renamesourcefileattribute SourceFile
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
# Keep line numbers to alleviate debugging stack traces
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
### for api client ### for api client
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault -keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
-keepclassmembers class * { -keepclassmembers class * {
@ -54,7 +47,6 @@
# Needed by Guava # Needed by Guava
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI # See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
-dontwarn sun.misc.Unsafe -dontwarn sun.misc.Unsafe
-dontwarn com.google.common.collect.MinMaxPriorityQueue -dontwarn com.google.common.collect.MinMaxPriorityQueue
@ -72,9 +64,6 @@
-keep class **$$ViewBinder { *; } -keep class **$$ViewBinder { *; }
-keepnames class * { @butterknife.Bind *;} -keepnames class * { @butterknife.Bind *;}
#### for support 22
-dontwarn android.support.**
#### for guava #### for guava
-dontwarn javax.annotation.** -dontwarn javax.annotation.**
-dontwarn javax.inject.** -dontwarn javax.inject.**
@ -105,23 +94,20 @@
-dontwarn java.nio.file.OpenOption -dontwarn java.nio.file.OpenOption
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -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 ## ## New rules for EventBus 3.0.x ##
# http://greenrobot.org/eventbus/documentation/proguard/ # http://greenrobot.org/eventbus/documentation/proguard/
-keepattributes *Annotation* -keepattributes *Annotation*
-keepclassmembers class ** { -keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>; @org.greenrobot.eventbus.Subscribe <methods>;
} }
-keep enum org.greenrobot.eventbus.ThreadMode { *; } -keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor # Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable); <init>(java.lang.Throwable);
} }
### for moshi ### for moshi
@ -153,3 +139,17 @@
-assumenosideeffects class kotlin.jvm.internal.Intrinsics { -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String); 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

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest
xmlns:tools="http://schemas.android.com/tools"
package="org.ligi.passandroid">
<uses-sdk tools:overrideLibrary="com.jraska.falcon.spooncompat , com.jraska.falcon"/>
</manifest>

View file

@ -15,7 +15,7 @@
"message":"", "message":"",
"transfer":"barcode_QR", "transfer":"barcode_QR",
"alternative": { "alternative": {
"order":1, "order":1
} }
}, },

View file

@ -0,0 +1,9 @@
package org.ligi.passandroid
import org.ligi.trulesk.AppReplacingRunnerBase
class AppReplacingRunner : AppReplacingRunnerBase() {
override fun testAppClass() = TestApp::class.java
}

View file

@ -1,37 +0,0 @@
package org.ligi.passandroid;
import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.view.WindowManager;
import org.ligi.passandroid.reporting.SpooningFailureHandler;
import static android.support.test.espresso.Espresso.setFailureHandler;
public abstract class BaseIntegration<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
public BaseIntegration(Class<T> activityClass) {
super(activityClass);
}
@Override
public void setUp() throws Exception {
super.setUp();
setFailureHandler(new SpooningFailureHandler(getInstrumentation()));
}
@Override
public T getActivity() {
final T activity = super.getActivity();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
});
return activity;
}
}

View file

@ -1,18 +0,0 @@
package org.ligi.passandroid
import android.app.Activity
import android.support.test.InstrumentationRegistry
import android.support.test.espresso.Espresso.setFailureHandler
import android.view.WindowManager
import org.ligi.passandroid.reporting.SpooningFailureHandler
abstract class BaseUnitTest {
fun setUp(activity: Activity) {
setFailureHandler(SpooningFailureHandler(InstrumentationRegistry.getInstrumentation()))
activity.runOnUiThread { activity.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) }
}
}

View 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
}
}

View file

@ -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)
}

View file

@ -1,83 +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.State
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.sortOrder).thenReturn(PassSortOrder.DATE_ASC)
`when`(mock.passesDir).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)
}
@Singleton
@Provides
fun provideState(): State {
return State()
}
}

View file

@ -0,0 +1,164 @@
package org.ligi.passandroid
import android.app.Activity.RESULT_CANCELED
import android.app.Instrumentation
import android.provider.CalendarContract
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
import org.junit.Test
import org.ligi.passandroid.functions.DEFAULT_EVENT_LENGTH_IN_HOURS
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskIntentRule
import org.threeten.bp.ZonedDateTime
class TheAddToCalendar {
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.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
intended(allOf(
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.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveCalendarEndDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(to = time)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
intended(allOf(
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.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveCalendarStartAndEndDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time, time2)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
onView(withId(R.id.timeButton)).perform(click())
intended(allOf(
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.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveExpirationDate() {
TestApp.populatePassStoreWithSinglePass()
(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))
onView(withId(R.id.timeButton)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_title)).check(matches(isDisplayed()))
onView(withText(android.R.string.ok)).perform(click())
intended(allOf(
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.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveExpirationEndDate() {
TestApp.populatePassStoreWithSinglePass()
(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))
onView(withId(R.id.timeButton)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_title)).check(matches(isDisplayed()))
onView(withText(android.R.string.ok)).perform(click())
intended(allOf(
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.passStore.currentPass!!.description)
))
}
@Test
fun testIfWeOnlyHaveExpirationStartAndEndDate() {
TestApp.populatePassStoreWithSinglePass()
(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))
onView(withId(R.id.timeButton)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_title)).check(matches(isDisplayed()))
onView(withText(android.R.string.ok)).perform(click())
intended(allOf(
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.passStore.currentPass!!.description)
))
}
@Test
fun testThereIsNoButtonWithNoDate() {
TestApp.populatePassStoreWithSinglePass()
rule.launchActivity()
onView(withId(R.id.timeButton)).check(matches(not(isDisplayed())))
}
}

View file

@ -1,71 +0,0 @@
package org.ligi.passandroid;
import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import java.io.File;
import java.io.InputStream;
import org.junit.Before;
import org.ligi.passandroid.model.InputStreamWithSource;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.passandroid.reader.AppleStylePassReader;
import org.ligi.passandroid.ui.UnzipPassController;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Fail.fail;
import static org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class TheAppleStyleBarcodeReaderBase {
@Mock
UnzipPassController.FailCallback failCallback;
@Mock
PassStore passStore;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
private File getTestTargetPath(final Context context) {
return new File(context.getCacheDir() , "test_passes");
}
void loadPassFromAsset(final String asset, final OnPassLoadCallback callback) {
try {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final InputStream inputStream = instrumentation.getContext().getResources().getAssets().open(asset);
final InputStreamWithSource inputStreamWithSource = new InputStreamWithSource("none", inputStream);
final InputStreamUnzipControllerSpec spec = new InputStreamUnzipControllerSpec(inputStreamWithSource, instrumentation.getTargetContext(),passStore,
new UnzipPassController.SuccessCallback() {
@Override
public void call(String uuid) {
callback.onPassLoad(AppleStylePassReader.INSTANCE.read(new File(getTestTargetPath(instrumentation.getTargetContext()), uuid), "en",
instrumentation.getTargetContext()));
}
}
, failCallback
);
spec.setOverwrite(true);
spec.setTargetPath(getTestTargetPath(spec.getContext()));
UnzipPassController.INSTANCE.processInputStream(spec);
verify(failCallback, never()).fail(any(String.class));
} catch (Exception e) {
fail("should be able to load file ", e);
}
}
interface OnPassLoadCallback {
void onPassLoad(Pass pass);
}
}

View file

@ -1,15 +1,16 @@
package org.ligi.passandroid package org.ligi.passandroid
import android.support.test.espresso.Espresso.closeSoftKeyboard import android.Manifest
import android.support.test.espresso.Espresso.onView import androidx.test.core.app.ApplicationProvider
import android.support.test.espresso.action.ViewActions.* import androidx.test.espresso.Espresso.closeSoftKeyboard
import android.support.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.Espresso.onView
import android.support.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.action.ViewActions.*
import android.support.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import android.support.test.filters.SdkSuppress import androidx.test.espresso.assertion.ViewAssertions.matches
import android.support.test.rule.ActivityTestRule import androidx.test.espresso.matcher.ViewMatchers.*
import android.support.test.runner.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.squareup.spoon.Spoon import androidx.test.filters.SdkSuppress
import com.linkedin.android.testbutler.TestButler
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -18,33 +19,29 @@ import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.PassBarCodeFormat import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassImpl import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassEditActivity import org.ligi.passandroid.ui.PassEditActivity
import javax.inject.Inject import org.ligi.trulesk.TruleskActivityRule
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class TheBarCodeEditing : BaseUnitTest() { class TheBarCodeEditing {
@get:Rule @get:Rule
val rule: ActivityTestRule<PassEditActivity> = ActivityTestRule(PassEditActivity::class.java, true, false) val rule = TruleskActivityRule(PassEditActivity::class.java, false)
@Inject val passStore: PassStore = TestApp.passStore
lateinit var passStore: PassStore
lateinit var currentPass: PassImpl private lateinit var currentPass: PassImpl
private val passEditActivity by lazy { rule.launchActivity(null) } private fun start(setupPass: (pass: PassImpl) -> Unit = {}) {
TestApp.populatePassStoreWithSinglePass()
fun start(setupPass: (pass: PassImpl) -> Unit) {
val build = DaggerTestComponent.create()
build.inject(this)
App.setComponent(build)
currentPass = passStore.currentPass as PassImpl currentPass = passStore.currentPass as PassImpl
setupPass(currentPass) setupPass(currentPass)
setUp(passEditActivity) TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
rule.launchActivity(null)
closeSoftKeyboard() closeSoftKeyboard()
} }
@ -55,7 +52,7 @@ class TheBarCodeEditing : BaseUnitTest() {
it.barCode = null it.barCode = null
} }
Spoon.screenshot(passEditActivity!!, "no_barcode") rule.screenShot("no_barcode")
onView(withId(R.id.add_barcode_button)).perform(scrollTo()) onView(withId(R.id.add_barcode_button)).perform(scrollTo())
onView(withId(R.id.add_barcode_button)).check(matches(isDisplayed())) onView(withId(R.id.add_barcode_button)).check(matches(isDisplayed()))
@ -81,30 +78,31 @@ class TheBarCodeEditing : BaseUnitTest() {
@SdkSuppress(minSdkVersion = 14) @SdkSuppress(minSdkVersion = 14)
@Test @Test
fun testCanSetToAllBarcodeTypes() { fun testCanSetToAllBarcodeTypes() {
start {} start()
for (passBarCodeFormat in PassBarCodeFormat.values()) { for (passBarCodeFormat in PassBarCodeFormat.values()) {
onView(withId(R.id.barcode_img)).perform(scrollTo(), click()) onView(withId(R.id.barcode_img)).perform(scrollTo(), click())
onView(withText(passBarCodeFormat.name)).perform(scrollTo(), click()) onView(withText(passBarCodeFormat.name)).perform(scrollTo(), click())
onView(withId(R.id.randomButton)).perform(click())
closeSoftKeyboard() closeSoftKeyboard()
onView(withText(android.R.string.ok)).perform(click()) onView(withText(android.R.string.ok)).perform(click())
assertThat(currentPass.barCode!!.format).isEqualTo(passBarCodeFormat) assertThat(currentPass.barCode!!.format).isEqualTo(passBarCodeFormat)
Spoon.screenshot(passEditActivity!!, "edit_set_" + passBarCodeFormat.name) rule.screenShot("edit_set_" + passBarCodeFormat.name)
} }
} }
@Test @Test
fun testCanSetMessage() { fun testCanSetMessage() {
start {} start()
onView(withId(R.id.barcode_img)).perform(click()) onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.messageInput)).perform(clearText()) 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() closeSoftKeyboard()
@ -113,18 +111,18 @@ class TheBarCodeEditing : BaseUnitTest() {
onView(withText(R.string.edit_barcode_dialog_title)).check(doesNotExist()) onView(withText(R.string.edit_barcode_dialog_title)).check(doesNotExist())
assertThat(passStore.currentPass!!.barCode!!.message).isEqualTo("msg foo txt ;-)") assertThat(passStore.currentPass!!.barCode!!.message).isEqualTo("msg foo txt ;-)")
Spoon.screenshot(passEditActivity!!, "edit_set_msg") rule.screenShot("edit_set_msg")
} }
@Test @Test
fun testCanSetAltMessage() { fun testCanSetAltMessage() {
start {} start()
onView(withId(R.id.barcode_img)).perform(click()) onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.alternativeMessageInput)).perform(clearText()) 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() closeSoftKeyboard()
@ -133,12 +131,12 @@ class TheBarCodeEditing : BaseUnitTest() {
onView(withText(R.string.edit_barcode_dialog_title)).check(doesNotExist()) onView(withText(R.string.edit_barcode_dialog_title)).check(doesNotExist())
assertThat(passStore.currentPass!!.barCode!!.alternativeText).isEqualTo("alt bar txt ;-)") assertThat(passStore.currentPass!!.barCode!!.alternativeText).isEqualTo("alt bar txt ;-)")
Spoon.screenshot(passEditActivity!!, "edit_set_altmsg") rule.screenShot("edit_set_altmsg")
} }
@Test @Test
fun testThatRandomChangesMessage() { fun testThatRandomChangesMessage() {
start {} start()
onView(withId(R.id.barcode_img)).perform(click()) onView(withId(R.id.barcode_img)).perform(click())
@ -152,5 +150,4 @@ class TheBarCodeEditing : BaseUnitTest() {
assertThat(oldMessage).isNotEqualTo(passStore.currentPass!!.barCode!!.message) assertThat(oldMessage).isNotEqualTo(passStore.currentPass!!.barCode!!.message)
} }
} }

View file

@ -1,69 +0,0 @@
package org.ligi.passandroid;
import android.graphics.Bitmap;
import android.support.test.runner.AndroidJUnit4;
import com.google.zxing.common.BitMatrix;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ligi.passandroid.helper.BarcodeHelper;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
@RunWith(AndroidJUnit4.class)
public class TheBarcodeHelper {
@Test
public void testQRBitMatrixHasCorrectSize() throws Exception {
testBitMatrixSizeIsSane(PassBarCodeFormat.QR_CODE);
}
@Test
public void testQRBitmapHasCorrectSize() throws Exception {
testBitmapSizeIsSane(PassBarCodeFormat.QR_CODE);
}
@Test
public void testPDF417BitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.PDF_417);
}
@Test
public void testPDF417BitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.PDF_417);
}
@Test
public void testAZTECBitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.AZTEC);
}
@Test
public void testAZTECBitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.AZTEC);
}
public void testBitMatrixSizeIsSane(final PassBarCodeFormat format) {
try {
BitMatrix tested = BarcodeHelper.getBitMatrix("foo-data", format);
assertThat(tested.getWidth()).isGreaterThan(3);
} catch (Exception e) {
fail("could not create barcode", e);
}
}
public void testBitmapSizeIsSane(final PassBarCodeFormat format) {
try {
Bitmap tested2 = BarcodeHelper.generateBarCodeBitmap("foo-data", format);
assert tested2 != null;
assertThat(tested2.getWidth()).isGreaterThan(3);
} catch (Exception e) {
fail("could not create barcode" ,e);
}
}
}

View file

@ -0,0 +1,61 @@
package org.ligi.passandroid
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Fail.fail
import org.junit.Test
import org.ligi.passandroid.functions.generateBarCodeBitmap
import org.ligi.passandroid.functions.getBitMatrix
import org.ligi.passandroid.model.pass.PassBarCodeFormat
class TheBarcodeHelper {
@Test
fun testQRBitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.QR_CODE)
}
@Test
fun testQRBitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.QR_CODE)
}
@Test
fun testPDF417BitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.PDF_417)
}
@Test
fun testPDF417BitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.PDF_417)
}
@Test
fun testAZTECBitmapHasCorrectSize() {
testBitmapSizeIsSane(PassBarCodeFormat.AZTEC)
}
@Test
fun testAZTECBitMatrixHasCorrectSize() {
testBitMatrixSizeIsSane(PassBarCodeFormat.AZTEC)
}
fun testBitMatrixSizeIsSane(format: PassBarCodeFormat) {
try {
val tested = getBitMatrix("foo-data", format)
assertThat(tested.width).isGreaterThan(3)
} catch (e: Exception) {
fail("could not create barcode", e)
}
}
fun testBitmapSizeIsSane(format: PassBarCodeFormat) {
try {
val tested2 = generateBarCodeBitmap("foo-data", format)!!
assertThat(tested2.width).isGreaterThan(3)
} catch (e: Exception) {
fail("could not create barcode", e)
}
}
}

View file

@ -1,25 +0,0 @@
package org.ligi.passandroid;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.ligi.passandroid.helper.CategoryHelper;
import org.ligi.passandroid.model.PassDefinitions;
import org.ligi.passandroid.model.pass.PassType;
import static org.assertj.core.api.Assertions.assertThat;
public class TheCategoryHelper {
@Test
public void testAllCategoriesAreTranslated() {
final Set<Integer> probe = new HashSet<>();
for (PassType type : PassDefinitions.INSTANCE.getTYPES().keySet()) {
probe.add(CategoryHelper.INSTANCE.getHumanCategoryString(type));
}
assertThat(probe.size()).isEqualTo(PassDefinitions.INSTANCE.getTYPES().keySet().size());
}
}

View file

@ -0,0 +1,20 @@
package org.ligi.passandroid
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.ligi.passandroid.functions.getHumanCategoryString
import org.ligi.passandroid.model.PassDefinitions
class TheCategoryHelper {
@Test
fun testAllCategoriesAreTranslated() {
val allTranslationSet = PassDefinitions.TYPE_TO_NAME.keys
.map(::getHumanCategoryString)
.toSet()
assertThat(allTranslationSet.size).isEqualTo(PassDefinitions.TYPE_TO_NAME.keys.size)
}
}

View file

@ -0,0 +1,54 @@
package org.ligi.passandroid
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
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskActivityRule
import org.mockito.Mockito.`when`
import org.threeten.bp.ZoneId
import org.threeten.bp.ZonedDateTime
class TheCondensedPassViewMode {
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java, false) {
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.settings.isCondensedModeEnabled()).thenReturn(false)
rule.launchActivity()
onView(withId(R.id.date)).check(matches(withText(containsString("23"))))
onView(withId(R.id.timeButton)).check(matches(withText(R.string.pass_to_calendar)))
rule.screenShot("condensed_off")
}
@Test
fun testFieldShowsForCondensedOn() {
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(true)
rule.launchActivity()
onView(withId(R.id.date)).check(matches(withText(containsString("bar"))))
onView(withId(R.id.timeButton)).check(matches(withText(containsString("23"))))
rule.screenShot("condensed_on")
}
}

View file

@ -1,42 +0,0 @@
package org.ligi.passandroid;
import android.support.test.filters.MediumTest;
import com.squareup.spoon.Spoon;
import java.util.ArrayList;
import org.ligi.passandroid.model.pass.Pass;
import org.ligi.passandroid.ui.PassListActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
public class TheEmptyPassList extends BaseIntegration<PassListActivity> {
public TheEmptyPassList() {
super(PassListActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
App.setComponent(DaggerTestComponent.builder().testModule(new TestModule(new ArrayList<Pass>())).build());
getActivity();
}
@MediumTest
public void testEmptyViewIsThereWhenThereAreNoPasses() {
Spoon.screenshot(getActivity(), "empty_view");
onView(withId(R.id.emptyView)).check(matches(isDisplayed()));
}
@MediumTest
public void testHelpGoesToHelp() {
onView(withId(R.id.menu_help)).perform(click());
checkThatHelpIsThere();
}
}

View file

@ -0,0 +1,36 @@
package org.ligi.passandroid
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.ui.PassListActivity
import org.ligi.trulesk.TruleskIntentRule
class TheEmptyPassList {
@get:Rule
var rule = TruleskIntentRule(PassListActivity::class.java) {
TestApp.emptyPassStore()
}
@Test
fun testEmptyViewIsThereWhenThereAreNoPasses() {
rule.screenShot("empty_view")
onView(withId(emptyView)).check(matches(isDisplayed()))
}
@Test
fun testHelpGoesToHelp() {
onView(withId(R.id.menu_help)).perform(click())
checkThatHelpIsThere()
}
}

View file

@ -1,54 +1,34 @@
package org.ligi.passandroid package org.ligi.passandroid
import android.app.Activity import androidx.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.replaceText
import android.support.test.espresso.action.ViewActions.replaceText import androidx.test.espresso.action.ViewActions.scrollTo
import android.support.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.*
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import com.squareup.spoon.Spoon
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.PassField import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.PassEditActivity import org.ligi.passandroid.ui.PassEditActivity
import javax.inject.Inject import org.ligi.trulesk.TruleskIntentRule
import java.util.*
@RunWith(AndroidJUnit4::class) class TheFieldListEditFragment {
class TheFieldListEditFragment : BaseUnitTest(){
@get:Rule @get:Rule
val rule: ActivityTestRule<PassEditActivity> = ActivityTestRule(PassEditActivity::class.java, true, false) val rule = TruleskIntentRule(PassEditActivity::class.java) {
TestApp.passStore.currentPass = PassImpl(UUID.randomUUID().toString()).apply {
@Inject fields = arrayListOf(field)
lateinit var passStore: PassStore }
}
private val field: PassField = PassField(null, "labelfieldcontent", "valuefieldcontent", false) private val field: PassField = PassField(null, "labelfieldcontent", "valuefieldcontent", false)
fun start(): Activity {
val build = DaggerTestComponent.create()
build.inject(this)
App.setComponent(build)
val currentPass = passStore.currentPass as PassImpl
currentPass.fields = arrayListOf(field)
val activity = rule.launchActivity(null)
setUp(activity)
return activity
}
@Test @Test
fun testFieldDetailsArePreFilled() { fun testFieldDetailsArePreFilled() {
Spoon.screenshot(start(), "one_field")
rule.screenShot("one_field")
onView(withId(R.id.label_field_edit)).perform(scrollTo()) onView(withId(R.id.label_field_edit)).perform(scrollTo())
onView(withId(R.id.label_field_edit)).check(matches(isDisplayed())) onView(withId(R.id.label_field_edit)).check(matches(isDisplayed()))
@ -63,8 +43,6 @@ class TheFieldListEditFragment : BaseUnitTest(){
@Test @Test
fun testThatChangingLabelWorks() { fun testThatChangingLabelWorks() {
start()
onView(withId(R.id.label_field_edit)).perform(scrollTo()) onView(withId(R.id.label_field_edit)).perform(scrollTo())
onView(withId(R.id.label_field_edit)).perform(replaceText("newlabel")) onView(withId(R.id.label_field_edit)).perform(replaceText("newlabel"))
assertThat(field.label).isEqualTo("newlabel") assertThat(field.label).isEqualTo("newlabel")
@ -73,8 +51,6 @@ class TheFieldListEditFragment : BaseUnitTest(){
@Test @Test
fun testThatChangingValueWorks() { fun testThatChangingValueWorks() {
start()
onView(withId(R.id.value_field_edit)).perform(scrollTo()) onView(withId(R.id.value_field_edit)).perform(scrollTo())
onView(withId(R.id.value_field_edit)).perform(replaceText("newvalue")) onView(withId(R.id.value_field_edit)).perform(replaceText("newvalue"))
assertThat(field.value).isEqualTo("newvalue") assertThat(field.value).isEqualTo("newvalue")

View file

@ -1,111 +0,0 @@
package org.ligi.passandroid;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.support.test.filters.MediumTest;
import android.widget.ImageView;
import butterknife.ButterKnife;
import com.squareup.spoon.Spoon;
import java.util.UUID;
import javax.inject.Inject;
import org.ligi.passandroid.helper.BarcodeDecoder;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.BarCode;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.ui.FullscreenBarcodeActivity;
import org.ligi.tracedroid.TraceDroid;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.assertj.core.api.Assertions.assertThat;
public class TheFullscreenBarcodeActivity extends BaseIntegration<FullscreenBarcodeActivity> {
@Inject
PassStore passStore;
public static final String BARCODE_MESSAGE = "2323";
public TheFullscreenBarcodeActivity() {
super(FullscreenBarcodeActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
TestComponent component = DaggerTestComponent.create();
component.inject(this);
App.setComponent(component);
TraceDroid.deleteStacktraceFiles();
}
@MediumTest
public void testPDF417BarcodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.PDF_417);
Spoon.screenshot(getActivity(), "pdf417_barcode");
}
@MediumTest
public void testAztecBarcodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.AZTEC);
Spoon.screenshot(getActivity(), "aztec_barcode");
}
@MediumTest
public void testQRCodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.QR_CODE);
Spoon.screenshot(getActivity(), "qr_barcode");
}
@MediumTest
public void testCode128CodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.CODE_128);
Spoon.screenshot(getActivity(), "code128_barcode");
}
@MediumTest
public void testCode39CodeIsShown() {
testWithBarcodeFormat(PassBarCodeFormat.CODE_39);
Spoon.screenshot(getActivity(), "code39_barcode");
}
private void testWithBarcodeFormat(final PassBarCodeFormat format) {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setBarCode(new BarCode(format, BARCODE_MESSAGE));
passStore.setCurrentPass(pass);
getActivity();
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()));
final ImageView viewById = ButterKnife.findById(getActivity(), R.id.fullscreen_barcode);
BitmapDrawable bitmapDrawable = (BitmapDrawable) viewById.getDrawable();
final Bitmap bitmap = bitmapDrawable.getBitmap();
final Bitmap 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.getWidth() * 2, bitmap.getHeight() * 2, false);
} else {
bitmapToTest = bitmap;
}
assertThat(BarcodeDecoder.INSTANCE.decodeBitmap(bitmapToTest)).isEqualTo(BARCODE_MESSAGE);
}
}

View file

@ -0,0 +1,91 @@
package org.ligi.passandroid
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
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
import org.ligi.passandroid.functions.decodeBarCode
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassBarCodeFormat.*
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.ui.FullscreenBarcodeActivity
import org.ligi.trulesk.TruleskIntentRule
import java.util.*
private const val BARCODE_MESSAGE = "2323"
class TheFullscreenBarcodeActivity {
@get:Rule
var rule = TruleskIntentRule(FullscreenBarcodeActivity::class.java, false)
@Test
fun testPDF417BarcodeIsShown() {
testWithBarcodeFormat(PDF_417)
rule.screenShot("pdf417_barcode")
}
@Test
fun testAztecBarcodeIsShown() {
testWithBarcodeFormat(AZTEC)
rule.screenShot("aztec_barcode")
}
@Test
fun testQRCodeIsShown() {
testWithBarcodeFormat(QR_CODE)
rule.screenShot("qr_barcode")
}
@Test
fun testCode128CodeIsShown() {
testWithBarcodeFormat(CODE_128)
rule.screenShot("code128_barcode")
}
@Test
fun testCode39CodeIsShown() {
testWithBarcodeFormat(CODE_39)
rule.screenShot("code39_barcode")
}
private fun testWithBarcodeFormat(format: PassBarCodeFormat) {
val pass = PassImpl(UUID.randomUUID().toString())
pass.barCode = BarCode(format, BARCODE_MESSAGE)
TestApp.passStore.currentPass = pass
rule.launchActivity(null)
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()))
val viewById = rule.activity.findViewById(R.id.fullscreen_barcode) as ImageView
val bitmapDrawable = viewById.drawable as BitmapDrawable
val bitmap = bitmapDrawable.bitmap
val bitmapToTest: Bitmap
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
Bitmap.createScaledBitmap(bitmap, bitmap.width * 2, bitmap.height * 2, false)
} else {
bitmap
}
assertThat(bitmapToTest.decodeBarCode()).isEqualTo(BARCODE_MESSAGE)
}
}

View file

@ -1,32 +1,32 @@
package org.ligi.passandroid package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.*
import android.support.test.rule.ActivityTestRule import org.assertj.core.api.Assertions.assertThat
import junit.framework.Assert
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.ligi.passandroid.helper.ScreenshotTaker import org.ligi.passandroid.functions.checkThatHelpIsThere
import org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere
import org.ligi.passandroid.ui.HelpActivity import org.ligi.passandroid.ui.HelpActivity
import org.ligi.trulesk.TruleskActivityRule
class TheHelpActivity { class TheHelpActivity {
@get:Rule @get:Rule
val rule: ActivityTestRule<HelpActivity> = ActivityTestRule(HelpActivity::class.java) val rule = TruleskActivityRule(HelpActivity::class.java)
@Test @Test
fun testHelpIsThere() { fun testHelpIsThere() {
checkThatHelpIsThere() checkThatHelpIsThere()
ScreenshotTaker.takeScreenshot(rule.activity, "help") rule.screenShot("help")
} }
@Test @Test
fun test_that_help_finishes_on_home() { fun test_that_help_finishes_on_home() {
onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click()) onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click())
Assert.assertTrue(rule.activity.isFinishing)
assertThat(rule.activity.isFinishing).isTrue()
} }
@Test @Test

View file

@ -0,0 +1,109 @@
package org.ligi.passandroid
import android.annotation.TargetApi
import android.app.Activity.RESULT_CANCELED
import android.app.Instrumentation.ActivityResult
import android.content.Intent.ACTION_SEND
import android.content.Intent.ACTION_VIEW
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
import org.junit.Test
import org.ligi.passandroid.R.string.*
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.passandroid.ui.PreferenceActivity
import org.ligi.trulesk.TruleskIntentRule
@TargetApi(14)
class TheNavigationDrawer {
@get:Rule
var rule = TruleskIntentRule(PassListActivity::class.java)
@Test
fun testNavigationDrawerIsUsuallyNotShown() {
onView(withId(R.id.navigationView)).check(matches(not(isDisplayed())))
}
@Test
fun testThatNavigationDrawerOpens() {
onView(withId(R.id.drawer_layout)).perform(open())
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()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_betatest_opt_in_out)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://play.google.com/apps/testing/org.ligi.passandroid")))
}
@Test
fun testGitHubClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_github)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://github.com/ligi/PassAndroid")))
}
@Test
fun testImproveTranslationsClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_improve_translation)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://transifex.com/projects/p/passandroid")))
}
@Test
fun testShareClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_SEND)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_share)).perform(click())
intended(allOf(hasAction(ACTION_SEND), hasType("text/plain")))
}
@Test
fun testSettings() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
onView(withText(nav_settings)).perform(click())
intended(hasComponent(PreferenceActivity::class.java.name))
}
}

View file

@ -1,85 +0,0 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import com.squareup.spoon.Spoon;
import javax.inject.Inject;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.PassType;
import org.ligi.passandroid.ui.PassEditActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.assertj.core.api.Assertions.assertThat;
@TargetApi(14)
public class ThePassEditActivity extends BaseIntegration<PassEditActivity> {
@Inject
PassStore passStore;
public ThePassEditActivity() {
super(PassEditActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
final TestComponent build = DaggerTestComponent.create();
build.inject(this);
App.setComponent(build);
getActivity();
}
@MediumTest
public void testSetToEventWorks() {
onView(withId(R.id.categoryView)).perform(click());
onView(withText(R.string.select_category_dialog_title)).perform(click());
onView(withText(R.string.category_event)).perform(click());
assertThat(passStore.getCurrentPass().getType()).isEqualTo(PassType.EVENT);
Spoon.screenshot(getActivity(), "edit_set_event");
}
@MediumTest
public void testSetToCouponWorks() {
onView(withId(R.id.categoryView)).perform(click());
onView(withText(R.string.select_category_dialog_title)).perform(click());
onView(withText(R.string.category_coupon)).perform(click());
assertThat(passStore.getCurrentPass().getType()).isEqualTo(PassType.COUPON);
Spoon.screenshot(getActivity(), "edit_set_coupon");
}
@MediumTest
public void testSetDescriptionWorks() {
onView(withId(R.id.title)).perform(clearText(),typeText("test description"));
assertThat(passStore.getCurrentPass().getDescription()).isEqualTo("test description");
Spoon.screenshot(getActivity(), "edit_set_description");
}
@MediumTest
public void testColorWheelIsThere() {
onView(withId(R.id.categoryView)).perform(click());
onView(withText(R.string.change_color_dialog_title)).perform(click());
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "edit_set_color");
}
}

View file

@ -0,0 +1,109 @@
package org.ligi.passandroid
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
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.model.pass.PassType.COUPON
import org.ligi.passandroid.model.pass.PassType.EVENT
import org.ligi.passandroid.ui.PassEditActivity
import org.ligi.trulesk.TruleskIntentRule
@TargetApi(14)
class ThePassEditActivity {
val passStore = TestApp.passStore
@get:Rule
var rule = TruleskIntentRule(PassEditActivity::class.java) {
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(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")
}
@Test
fun testSetToCouponWorks() {
onView(withId(R.id.categoryView)).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")
}
@Test
fun testSetDescriptionWorks() {
onView(withId(R.id.passTitle)).perform(clearText(), replaceText("test description"))
assertThat(passStore.currentPass!!.description).isEqualTo("test description")
rule.screenShot("edit_set_description")
}
@Test
fun testColorWheelIsThere() {
onView(withId(R.id.categoryView)).perform(click())
onView(withText(R.string.change_color_dialog_title)).perform(click())
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()))
rule.screenShot("edit_set_color")
}
@Test
fun testAddAbortFooterImagePick() {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null))
onView(withId(R.id.add_footer)).perform(scrollTo(), click())
intended(hasAction(Intent.ACTION_CHOOSER))
}
@Test
fun testAddAbortStripImagePick() {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null))
onView(withId(R.id.add_strip)).perform(scrollTo(), click())
intended(hasAction(Intent.ACTION_CHOOSER))
}
@Test
fun testAddAbortLogoImagePick() {
intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_CANCELED, null))
onView(withId(R.id.add_logo)).perform(scrollTo(), click())
intended(hasAction(Intent.ACTION_CHOOSER))
}
}

View file

@ -1,62 +0,0 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import com.squareup.spoon.Spoon;
import org.ligi.passandroid.ui.PassListActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.DrawerActions.open;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.not;
import static org.ligi.passandroid.steps.HelpSteps.checkThatHelpIsThere;
@TargetApi(14)
public class ThePassListActivity extends BaseIntegration<PassListActivity> {
public ThePassListActivity() {
super(PassListActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
App.setComponent(DaggerTestComponent.builder().build());
getActivity();
}
@MediumTest
public void testListIsThere() {
onView(withId(R.id.pass_recyclerview)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "list");
}
@MediumTest
public void testHelpMenuBringsUsToHelp() {
onView(withId(R.id.menu_help)).perform(click());
checkThatHelpIsThere();
}
@MediumTest
public void testNavigationDrawerIsUsuallyNotShown() {
onView(withId(R.id.navigationView)).check(matches(not(isDisplayed())));
}
@MediumTest
public void testThatNavigationDrawerOpens() {
onView(withId(R.id.drawer_layout)).perform(open());
onView(withId(R.id.navigationView)).check(matches(isDisplayed()));
Spoon.screenshot(getActivity(), "open_drawer");
}
}

View file

@ -0,0 +1,66 @@
package org.ligi.passandroid
import android.annotation.TargetApi
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
@TargetApi(14)
class ThePassListActivity {
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
}
@Test
fun testListIsThere() {
onView(withId(pass_recyclerview)).check(matches(isDisplayed()))
rule.screenShot("list")
}
@Test
fun testHelpMenuBringsUsToHelp() {
onView(withId(R.id.menu_help)).perform(click())
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)))
}
}
}

View file

@ -1,34 +1,25 @@
package org.ligi.passandroid package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView import androidx.recyclerview.widget.ItemTouchHelper
import android.support.test.espresso.action.ViewActions.click import androidx.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.typeText import androidx.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.action.ViewActions.replaceText
import android.support.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.assertion.ViewAssertions.matches
import android.support.test.rule.ActivityTestRule import androidx.test.espresso.matcher.ViewMatchers.*
import android.support.v7.widget.helper.ItemTouchHelper
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.ligi.passandroid.helper.ScreenshotTaker
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.ui.PassListActivity import org.ligi.passandroid.ui.PassListActivity
import org.ligi.passandroid.ui.PassListFragment import org.ligi.passandroid.ui.PassListFragment
import javax.inject.Inject import org.ligi.trulesk.TruleskIntentRule
class ThePassListSwiping : BaseUnitTest() { const val CUSTOM_PROBE = "FOO_PROBE"
class ThePassListSwiping {
@get:Rule @get:Rule
val rule: ActivityTestRule<PassListActivity> = ActivityTestRule(PassListActivity::class.java, true, false) val rule = TruleskIntentRule(PassListActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
@Inject
lateinit var passStore: PassStore
private val activity by lazy {
val build = DaggerTestComponent.create()
build.inject(this)
App.setComponent(build)
rule.launchActivity(null)
} }
@Test @Test
@ -37,7 +28,7 @@ class ThePassListSwiping : BaseUnitTest() {
onView(withText(R.string.topic_trash)).perform(click()) onView(withText(R.string.topic_trash)).perform(click())
assertThat(passStore.classifier.getTopics()).containsExactly(activity.getString(R.string.topic_trash)) assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
} }
@ -47,20 +38,19 @@ class ThePassListSwiping : BaseUnitTest() {
onView(withText(R.string.topic_archive)).perform(click()) onView(withText(R.string.topic_archive)).perform(click())
assertThat(passStore.classifier.getTopics()).containsExactly(activity.getString(R.string.topic_archive)) assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
} }
@Test @Test
fun testWeCanMoveToCustom() { fun testWeCanMoveToCustom() {
val CUSTOM_PROBE = "FOO_PROBE"
fakeSwipeLeft() 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()) onView(withText(android.R.string.ok)).perform(click())
assertThat(passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE) assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
} }
@ -75,7 +65,7 @@ class ThePassListSwiping : BaseUnitTest() {
fakeSwipeRight() fakeSwipeRight()
ScreenshotTaker.takeScreenshot(activity, "move_to_new_topic_dialog") rule.screenShot("move_to_new_topic_dialog")
onView(withText(R.string.move_to_new_topic)).check(matches(isDisplayed())) onView(withText(R.string.move_to_new_topic)).check(matches(isDisplayed()))
} }
@ -86,18 +76,13 @@ class ThePassListSwiping : BaseUnitTest() {
http://stackoverflow.com/questions/35397439/swipe-tests-flaky-on-recyclerview http://stackoverflow.com/questions/35397439/swipe-tests-flaky-on-recyclerview
*/ */
private fun fakeSwipe(dir: Int) { private fun fakeSwipe(dir: Int) {
activity.runOnUiThread { rule.activity.runOnUiThread {
val fragment = activity.supportFragmentManager.fragments.firstOrNull { it is PassListFragment } as PassListFragment val fragment = rule.activity.supportFragmentManager.fragments.firstOrNull { it is PassListFragment } as PassListFragment
fragment.onSwiped(0, dir) fragment.onSwiped(0, dir)
} }
} }
private fun fakeSwipeRight() { private fun fakeSwipeRight() = fakeSwipe(ItemTouchHelper.RIGHT)
fakeSwipe(ItemTouchHelper.RIGHT) private fun fakeSwipeLeft() = fakeSwipe(ItemTouchHelper.LEFT)
}
private fun fakeSwipeLeft() {
fakeSwipe(ItemTouchHelper.LEFT)
}
} }

View file

@ -1,133 +0,0 @@
package org.ligi.passandroid;
import android.annotation.TargetApi;
import android.support.test.filters.MediumTest;
import java.util.ArrayList;
import javax.inject.Inject;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.pass.BarCode;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.ui.PassViewActivity;
import org.threeten.bp.ZonedDateTime;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNot.not;
@TargetApi(14)
public class ThePassViewActivity extends BaseIntegration<PassViewActivity> {
@Inject
PassStore passStore;
PassImpl act_pass;
public ThePassViewActivity() {
super(PassViewActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
final TestComponent newComponent = DaggerTestComponent.create();
newComponent.inject(this);
App.setComponent(newComponent);
act_pass = (PassImpl) passStore.getCurrentPass();
}
@MediumTest
public void testThatDescriptionIsThere() {
getActivity();
onView(withText(act_pass.getDescription())).check(matches(isDisplayed()));
}
@MediumTest
public void testDateIsGoneWhenPassbookHasNoDate() {
getActivity();
onView(withId(R.id.date)).check(matches(not(isDisplayed())));
}
@MediumTest
public void testDateIsThereWhenPassbookHasDate() {
act_pass.setCalendarTimespan(new PassImpl.TimeSpan(ZonedDateTime.now(), null, null));
getActivity();
onView(withId(R.id.date)).check(matches(isDisplayed()));
}
@MediumTest
public void testLinkToCalendarIsThereWhenPassbookHasDate() {
act_pass.setCalendarTimespan(new PassImpl.TimeSpan(ZonedDateTime.now(), null, null));
getActivity();
onView(withId(R.id.addCalendar)).check(matches(isDisplayed()));
}
@MediumTest
public void testClickOnCalendarWithExpirationDateGivesWarning() {
final ArrayList<PassImpl.TimeSpan> validTimespans = new ArrayList<>();
validTimespans.add(new PassImpl.TimeSpan(null, ZonedDateTime.now().minusHours(12), null));
act_pass.setValidTimespans(validTimespans);
act_pass.setCalendarTimespan(null);
getActivity();
onView(withId(R.id.addCalendar)).perform(click());
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(matches(isDisplayed()));
}
@MediumTest
public void testThatTheDialogCanBeDismissed() {
testClickOnCalendarWithExpirationDateGivesWarning();
onView(withText(android.R.string.cancel)).perform(click());
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(doesNotExist());
}
@MediumTest
public void testLinkToCalendarIsNotThereWhenPassbookHasNoDate() {
getActivity();
onView(withId(R.id.addCalendar)).check(matches(not(isDisplayed())));
}
@MediumTest
public void testClickOnBarcodeOpensFullscreenImage() {
getActivity();
onView(withId(R.id.barcode_img)).perform(click());
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()));
}
@MediumTest
public void testZoomControlsAreThereWithBarcode() {
act_pass.setBarCode(new BarCode(PassBarCodeFormat.AZTEC, "foo"));
getActivity();
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()));
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()));
}
@MediumTest
public void testZoomControlsAreGoneWithoutBarcode() {
act_pass.setBarCode(null);
getActivity();
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())));
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())));
}
}

View file

@ -0,0 +1,134 @@
package org.ligi.passandroid
import android.annotation.TargetApi
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
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassLocation
import org.ligi.passandroid.ui.PassViewActivity
import org.ligi.trulesk.TruleskActivityRule
import org.threeten.bp.ZonedDateTime
import java.util.*
@TargetApi(14)
class ThePassViewActivity {
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)
onView(withText(getActPass().description)).check(matches(isDisplayed()))
}
@Test
fun testDateIsGoneWhenPassbookHasNoDate() {
getActPass().validTimespans = ArrayList()
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(not(isDisplayed())))
}
@Test
fun testEverythingWorksWhenWeHaveSomeLocation() {
val timeSpen = ArrayList<PassLocation>()
timeSpen.add(PassLocation())
getActPass().locations = timeSpen
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(not(isDisplayed())))
}
@Test
fun testDateIsThereWhenPassbookHasDate() {
getActPass().calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.now(), null, null)
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(isDisplayed()))
}
@Test
fun testLinkToCalendarIsThereWhenPassbookHasDate() {
getActPass().calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.now(), null, null)
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).check(matches(isDisplayed()))
}
@Test
fun testClickOnCalendarWithExpirationDateGivesWarning() {
val validTimespans = ArrayList<PassImpl.TimeSpan>()
validTimespans.add(PassImpl.TimeSpan(null, ZonedDateTime.now().minusHours(12), null))
getActPass().validTimespans = validTimespans
getActPass().calendarTimespan = null
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(matches(isDisplayed()))
}
@Test
fun testThatTheDialogCanBeDismissed() {
testClickOnCalendarWithExpirationDateGivesWarning()
onView(withText(android.R.string.cancel)).perform(click())
onView(withText(R.string.expiration_date_to_calendar_warning_message)).check(doesNotExist())
}
@Test
fun testLinkToCalendarIsNotThereWhenPassbookHasNoDate() {
getActPass().validTimespans = ArrayList()
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).check(matches(not(isDisplayed())))
}
@Test
fun testClickOnBarcodeOpensFullscreenImage() {
getActPass().barCode = BarCode(PassBarCodeFormat.QR_CODE, "foo")
rule.launchActivity(null)
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()))
}
@Test
fun testZoomControlsAreThereWithBarcode() {
getActPass().barCode = BarCode(PassBarCodeFormat.AZTEC, "foo")
rule.launchActivity(null)
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()))
onView(withId(R.id.zoomIn)).check(matches(isDisplayed()))
}
@Test
fun testZoomControlsAreGoneWithoutBarcode() {
getActPass().barCode = null
rule.launchActivity(null)
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())))
onView(withId(R.id.zoomIn)).check(matches(not(isDisplayed())))
}
}

View file

@ -0,0 +1,76 @@
package org.ligi.passandroid
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
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassLocation
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.trulesk.TruleskActivityRule
import org.threeten.bp.ZoneId
import org.threeten.bp.ZonedDateTime
class ThePassViewHolder {
private val currentPass by lazy {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass as PassImpl
}
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java, false)
@Test
fun locationButtonShouldBeVisibleIfWeHaveALocation() {
currentPass.locations = listOf(PassLocation())
rule.launchActivity()
onView(withId(R.id.locationButton)).check(ViewAssertions.matches(isDisplayed()))
rule.screenShot("with_location")
}
@Test
fun locationButtonShouldNotShowIfWeHaveNoLocation() {
currentPass.locations = listOf()
rule.launchActivity()
onView(withId(R.id.locationButton)).check(ViewAssertions.matches(not(isDisplayed())))
rule.screenShot("no_location")
}
@Test
fun dateButtonShouldBeVisibleIfWeHaveADate() {
currentPass.calendarTimespan = PassImpl.TimeSpan(ZonedDateTime.of(2016, 11, 23, 20, 42, 42, 5, ZoneId.systemDefault()))
rule.launchActivity()
onView(withId(R.id.timeButton)).check(ViewAssertions.matches(isDisplayed()))
rule.screenShot("with_date")
}
@Test
fun dateButtonShouldNotBeVisibleIfWeHaveNoDate() {
currentPass.calendarTimespan = null
rule.launchActivity()
onView(withId(R.id.timeButton)).check(ViewAssertions.matches(not(isDisplayed())))
rule.screenShot("no_date")
}
}

View file

@ -1,60 +0,0 @@
package org.ligi.passandroid;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.test.filters.SmallTest;
import javax.inject.Inject;
import org.ligi.passandroid.model.PastLocationsStore;
import static org.assertj.core.api.Assertions.assertThat;
public class ThePastLocationsStore extends BaseIntegration<Activity> {
@Inject
Tracker tracker;
private SharedPreferences sharedPreferences;
public ThePastLocationsStore() {
super(Activity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
App.setComponent(DaggerTestComponent.builder().testModule(new TestModule()).build());
sharedPreferences = getInstrumentation().getContext().getSharedPreferences("" + (System.currentTimeMillis() / 100000), Context.MODE_PRIVATE);
((TestComponent) App.component()).inject(this);
}
@Override
protected void tearDown() throws Exception {
sharedPreferences.edit().clear().apply();
super.tearDown();
}
@SmallTest
public void testPastLocationsStoreShouldNeverContainMoreThanMaxElements() {
PastLocationsStore tested = new PastLocationsStore(sharedPreferences, tracker);
for (int i = 0; i < PastLocationsStore.MAX_ELEMENTS * 2; i++) {
tested.putLocation("" + i);
}
assertThat(tested.getLocations().size()).isEqualTo(PastLocationsStore.MAX_ELEMENTS);
}
@SmallTest
public void testPastLocationsStoreShouldStoreOnlyOneOfAKind() {
PastLocationsStore tested = new PastLocationsStore(sharedPreferences, tracker);
for (int i = 0; i < 3; i++) {
tested.putLocation("foo");
}
assertThat(tested.getLocations()).containsOnly("foo");
}
}

View file

@ -0,0 +1,58 @@
package org.ligi.passandroid
import android.content.Context
import android.content.SharedPreferences
import androidx.test.platform.app.InstrumentationRegistry
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.PastLocationsStore
import org.ligi.passandroid.ui.PassViewActivity
import org.ligi.trulesk.TruleskActivityRule
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class ThePastLocationsStore {
@get:Rule
var rule = TruleskActivityRule(PassViewActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
MockitoAnnotations.initMocks(this)
}
@Mock
lateinit var tracker: Tracker
private val prefs: SharedPreferences by lazy { InstrumentationRegistry.getInstrumentation().context.getSharedPreferences("" + System.currentTimeMillis() / 100000, Context.MODE_PRIVATE) }
@After
fun tearDown() {
prefs.edit().clear().apply()
}
@Test
fun testPastLocationsStoreShouldNeverContainMoreThanMaxElements() {
val tested = PastLocationsStore(prefs, tracker)
for (i in 0 until PastLocationsStore.MAX_ELEMENTS * 2) {
tested.putLocation("" + i)
}
assertThat(tested.locations.size).isEqualTo(PastLocationsStore.MAX_ELEMENTS)
}
@Test
fun testPastLocationsStoreShouldStoreOnlyOneOfAKind() {
val tested = PastLocationsStore(prefs, tracker)
for (i in 0..2) {
tested.putLocation("foo")
}
assertThat(tested.locations).containsOnly("foo")
}
}

View file

@ -0,0 +1,102 @@
package org.ligi.passandroid
import android.Manifest
import android.os.Build
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
import org.ligi.passandroid.model.AndroidSettings
import org.ligi.passandroid.model.comparator.PassByTimeComparator
import org.ligi.passandroid.model.comparator.PassByTypeFirstAndTimeSecondComparator
import org.ligi.passandroid.model.comparator.PassTemporalDistanceComparator
import org.ligi.passandroid.ui.PreferenceActivity
import org.ligi.trulesk.TruleskActivityRule
class ThePreferenceActivity {
@get:Rule
val rule = TruleskActivityRule(PreferenceActivity::class.java) {
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION)
}
private val androidSettings by lazy { AndroidSettings(rule.activity) }
@Test
fun autoLightToggles() {
rule.screenShot("preferences")
val automaticLightEnabled = androidSettings.isAutomaticLightEnabled()
onView(withText(R.string.preference_autolight_title)).perform(click())
assertThat(automaticLightEnabled).isEqualTo(!androidSettings.isAutomaticLightEnabled())
onView(withText(R.string.preference_autolight_title)).perform(click())
assertThat(automaticLightEnabled).isEqualTo(androidSettings.isAutomaticLightEnabled())
}
@Test
fun condensedToggles() {
val condensedModeEnabled = androidSettings.isCondensedModeEnabled()
onView(withText(R.string.preference_condensed_title)).perform(click())
assertThat(condensedModeEnabled).isEqualTo(!androidSettings.isCondensedModeEnabled())
onView(withText(R.string.preference_condensed_title)).perform(click())
assertThat(condensedModeEnabled).isEqualTo(androidSettings.isCondensedModeEnabled())
}
@Test
fun weCanSetAllSortOrders() {
val resources = rule.activity.resources
val sortOrders = resources.getStringArray(R.array.sort_orders)
sortOrders.forEach { sortOrder ->
onView(withText(R.string.preference_sort_title)).perform(click())
onView(withText(sortOrder)).perform(click())
assertThat(androidSettings.getSortOrder().toComparator()).isInstanceOf(when (sortOrder) {
resources.getString(R.string.sort_order_date_asc) -> PassByTimeComparator::class.java
resources.getString(R.string.sort_order_date_desc) -> PassByTimeComparator::class.java
resources.getString(R.string.sort_order_date_type) -> PassByTypeFirstAndTimeSecondComparator::class.java
resources.getString(R.string.sort_order_date_temporaldistance) -> PassTemporalDistanceComparator::class.java
else -> throw RuntimeException("unexpected sort order")
})
}
}
@Test
fun weCanSetAllNightModes() {
val resources = rule.activity.resources
val sortOrders = resources.getStringArray(R.array.night_modes)
sortOrders.filterNot { Build.VERSION.SDK_INT >= 21 && it == resources.getString(R.string.night_mode_auto) }.forEach { sortOrder ->
onView(withText(R.string.preference_daynight_title)).perform(click())
onView(withText(sortOrder)).perform(click())
assertThat(androidSettings.getNightMode()).isEqualTo(when (sortOrder) {
resources.getString(R.string.night_mode_day) -> AppCompatDelegate.MODE_NIGHT_NO
resources.getString(R.string.night_mode_night) -> AppCompatDelegate.MODE_NIGHT_YES
resources.getString(R.string.night_mode_auto) -> AppCompatDelegate.MODE_NIGHT_AUTO
else -> throw RuntimeException("unexpected night-mode")
})
}
}
}

View file

@ -1,17 +1,15 @@
package org.ligi.passandroid package org.ligi.passandroid
import android.support.test.runner.AndroidJUnit4
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.ligi.passandroid.functions.loadPassFromAsset
@RunWith(AndroidJUnit4::class) class TheQuirkCorrector {
class TheQuirkCorrector : TheAppleStyleBarcodeReaderBase() {
@Test @Test
fun testWestbahnDescriptionIsFixed() { fun testWestbahnDescriptionIsFixed() {
loadPassFromAsset("passes/workarounds/westbahn/special.pkpass") { loadPassFromAsset("passes/workarounds/westbahn/special.pkpass") {
assertThat(it.description).isEqualTo("Wien Westbahnhof->Amstetten") assertThat(it!!.description).isEqualTo("Wien Westbahnhof->Amstetten")
} }
} }

View file

@ -1,6 +1,6 @@
package org.ligi.passandroid; package org.ligi.passandroid;
import android.support.test.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry;
import java.io.InputStream; import java.io.InputStream;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -27,7 +27,7 @@ public class TheUnzipPassController {
PassStore passStore; PassStore passStore;
@Before @Before
public void setUp() throws Exception { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
} }
@ -49,7 +49,7 @@ public class TheUnzipPassController {
verify(failCallback).fail(any(String.class)); verify(failCallback).fail(any(String.class));
} catch (Exception e) { } catch (Exception e) {
fail("should be able to load file"); fail("should be able to load file " + e);
} }
} }

View file

@ -0,0 +1,22 @@
package org.ligi.passandroid.functions
import android.graphics.Bitmap
import com.google.zxing.BinaryBitmap
import com.google.zxing.MultiFormatReader
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.common.HybridBinarizer
fun Bitmap.decodeBarCode(): String {
val intArray = IntArray(this.width * this.height)
this.getPixels(intArray, 0, this.width, 0, 0, this.width, this.height)
val source = RGBLuminanceSource(this.width, this.height, intArray)
val bitmap = BinaryBitmap(HybridBinarizer(source))
val reader = MultiFormatReader()// use this otherwise ChecksumException
val result = reader.decode(bitmap)
return result.text
}

View file

@ -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!!
}
}

View file

@ -0,0 +1,11 @@
package org.ligi.passandroid.functions
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()))
}

View file

@ -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>

View file

@ -0,0 +1,50 @@
package org.ligi.passandroid.functions
import android.content.Context
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
import org.ligi.passandroid.reader.AppleStylePassReader
import org.ligi.passandroid.ui.UnzipPassController
import org.ligi.passandroid.ui.UnzipPassController.FailCallback
import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec
import org.mockito.Mockito.*
import java.io.File
private fun getTestTargetPath(context: Context) = File(context.cacheDir, "test_passes")
fun loadPassFromAsset(asset: String, callback: (pass: Pass?) -> Unit) {
try {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val inputStream = instrumentation.context.resources.assets.open(asset)
val inputStreamWithSource = InputStreamWithSource("none", inputStream)
val mock = mock(FailCallback::class.java)
val spec = InputStreamUnzipControllerSpec(inputStreamWithSource, instrumentation.targetContext, mock(
PassStore::class.java),
object : UnzipPassController.SuccessCallback {
override fun call(uuid: String) {
callback.invoke(AppleStylePassReader.read(File(getTestTargetPath(instrumentation.targetContext), uuid), "en",
instrumentation.targetContext,TestApp.tracker))
}
},
mock
)
spec.overwrite = true
spec.targetPath = getTestTargetPath(spec.context)
UnzipPassController.processInputStream(spec)
verify(mock, never()).fail(anyString())
} catch (e: Exception) {
fail("should be able to load file ", e)
}
}

View file

@ -1,24 +0,0 @@
package org.ligi.passandroid.helper
import android.graphics.Bitmap
import com.google.zxing.BinaryBitmap
import com.google.zxing.MultiFormatReader
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.common.HybridBinarizer
object BarcodeDecoder {
fun decodeBitmap(bMap: Bitmap): String {
val intArray = IntArray(bMap.width * bMap.height)
bMap.getPixels(intArray, 0, bMap.width, 0, 0, bMap.width, bMap.height)
val source = RGBLuminanceSource(bMap.width, bMap.height, intArray)
val bitmap = BinaryBitmap(HybridBinarizer(source))
val reader = MultiFormatReader()// use this otherwise ChecksumException
val result = reader.decode(bitmap)
return result.text
}
}

View file

@ -1,14 +0,0 @@
package org.ligi.passandroid.helper
import android.app.Activity
import com.jraska.falcon.FalconSpoon
/*
Facade for screenshot's
*/
object ScreenshotTaker {
fun takeScreenshot(activity: Activity, message: String) {
FalconSpoon.screenshot(activity, message)
}
}

View file

@ -1,28 +1,47 @@
package org.ligi.passandroid.injections 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.PassClassifier
import org.ligi.passandroid.model.PassStore import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.PassStoreUpdateEvent
import org.ligi.passandroid.model.pass.Pass import org.ligi.passandroid.model.pass.Pass
import java.io.File 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 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>() val hashMap = HashMap<String, Pass>()
passes.forEach { hashMap.put(it.id, it) } passes.forEach { hashMap[it.id] = it }
return@lazy hashMap return hashMap
} }
override fun getPassbookForId(id: String): Pass? { override fun getPassbookForId(id: String): Pass? {
return passMap[id] return passMap[id]
} }
override val classifier: PassClassifier by lazy {
PassClassifier(HashMap<String, String>(), this)
}
override fun deletePassWithId(id: String): Boolean { override fun deletePassWithId(id: String): Boolean {
return false return false
@ -32,6 +51,8 @@ class FixedPassListPassStore(private val passes: List<Pass>) : PassStore {
return File("") return File("")
} }
override val updateChannel: BroadcastChannel<PassStoreUpdateEvent> = ConflatedBroadcastChannel()
override fun save(pass: Pass) { override fun save(pass: Pass) {
// no effect in this impl // no effect in this impl
} }

View file

@ -1,43 +0,0 @@
package org.ligi.passandroid.reporting
import android.app.Activity
import android.app.Instrumentation
import android.support.test.espresso.FailureHandler
import android.support.test.espresso.base.DefaultFailureHandler
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import android.support.test.runner.lifecycle.Stage
import android.view.View
import org.hamcrest.Matcher
import org.ligi.passandroid.helper.ScreenshotTaker
class SpooningFailureHandler(private val instrumentation: Instrumentation) : FailureHandler {
private val delegate by lazy { DefaultFailureHandler(instrumentation.targetContext) }
override fun handle(error: Throwable, viewMatcher: Matcher<View>) {
try {
ScreenshotTaker.takeScreenshot(currentActivity, "error_falcon")
} catch (throwable: Throwable) {
throwable.printStackTrace()
}
delegate.handle(error, viewMatcher)
}
private val currentActivity: Activity
@Throws(Throwable::class)
get() {
instrumentation.waitForIdleSync()
val activity = arrayOfNulls<Activity>(1)
instrumentation.runOnMainSync {
val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
activity[0] = activities.iterator().next()
}
return activity[0]!!
}
}

View file

@ -1,17 +0,0 @@
package org.ligi.passandroid.steps;
import org.ligi.passandroid.R;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class HelpSteps {
public static void checkThatHelpIsThere() {
onView(withId(R.id.help_text)).check(matches(isDisplayed()));
}
}

View 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>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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> </resources>

View file

@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission-sdk-23 android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- <!--
@ -28,6 +29,7 @@
<receiver <receiver
android:name=".InstallListener" android:name=".InstallListener"
android:permission="android.permission.INSTALL_PACKAGES"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER"/> <action android:name="com.android.vending.INSTALL_REFERRER"/>
@ -35,21 +37,21 @@
</receiver> </receiver>
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="@string/authority_fileprovider" android:authorities="@string/authority_fileprovider"
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:exported="false"> android:exported="false">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" /> android:resource="@xml/filepaths"/>
</provider> </provider>
<service android:name=".ui.SearchPassesIntentService"/> <service android:name=".scan.SearchPassesIntentService"/>
<activity <activity
android:name=".ui.PassListActivity" android:name=".ui.PassListActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppBaseThemeNoActionbar"> android:theme="@style/AppThemeNoActionbar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@ -62,7 +64,7 @@
<activity <activity
android:name=".ui.HelpActivity" android:name=".ui.HelpActivity"
android:theme="@style/AppBaseThemeNoActionbar"/> android:theme="@style/AppThemeNoActionbar"/>
<activity <activity
android:name=".ui.PassEditActivity"/> android:name=".ui.PassEditActivity"/>
<activity <activity
@ -121,39 +123,35 @@
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd.espass-espass"/> <data android:scheme="http" android:mimeType="application/vnd.espass-espass"/>
</intent-filter> <data android:scheme="https" android:mimeType="application/vnd.espass-espass"/>
<intent-filter> <data android:scheme="content" android:mimeType="application/vnd.espass-espass"/>
<action android:name="android.intent.action.VIEW"/> <data android:scheme="file" android:mimeType="application/vnd.espass-espass"/>
<category android:name="android.intent.category.DEFAULT"/> <data android:scheme="http" android:mimeType="application/vnd.espass-espass+zip"/>
<category android:name="android.intent.category.BROWSABLE"/> <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"/> <data android:scheme="http" android:mimeType="application/vnd.apple.pkpass"/>
</intent-filter> <data android:scheme="https" android:mimeType="application/vnd.apple.pkpass"/>
<intent-filter> <data android:scheme="content" android:mimeType="application/vnd.apple.pkpass"/>
<action android:name="android.intent.action.VIEW"/> <data android:scheme="file" android:mimeType="application/vnd.apple.pkpass"/>
<category android:name="android.intent.category.DEFAULT"/> <data android:scheme="http" android:mimeType="application/pkpass"/>
<category android:name="android.intent.category.BROWSABLE"/> <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"/> <data android:scheme="http" android:mimeType="application/vndapplepkpass"/>
</intent-filter> <data android:scheme="https" android:mimeType="application/vndapplepkpass"/>
<intent-filter> <data android:scheme="content" android:mimeType="application/vndapplepkpass"/>
<action android:name="android.intent.action.VIEW"/> <data android:scheme="file" android:mimeType="application/vndapplepkpass"/>
<category android:name="android.intent.category.DEFAULT"/> <data android:scheme="http" android:mimeType="application/vnd-com.apple.pkpass"/>
<category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="https" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="content" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:mimeType="application/vndapplepkpass"/> <data android:scheme="file" android:mimeType="application/vnd-com.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>
<intent-filter> <intent-filter>
@ -555,6 +553,460 @@
android:scheme="content"/> android:scheme="content"/>
</intent-filter> </intent-filter>
<!-- pdf -->
<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="*"
android:pathPattern="/.*\\.pdf"
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="*"
android:pathPattern="/.*\\.pdf"
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="*"
android:pathPattern="/.*\\.pdf"
android:scheme="file"/>
</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="*"
android:pathPattern="/.*\\.pdf"
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: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:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
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="*"
android:pathPattern="/.*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
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="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="http"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
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="*"
android:pathPattern="/.*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="https"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
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="*"
android:pathPattern="/.*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
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:host="*"
android:pathPattern="/.*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
</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="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="file"/>
</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="*"
android:mimeType="*/*"
android:pathPattern="/.*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.pdf"
android:scheme="content"/>
</intent-filter>
<!-- pkpass --> <!-- pkpass -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@ -600,22 +1052,6 @@
android:pathPattern="/.*\\.pkpass" android:pathPattern="/.*\\.pkpass"
android:scheme="content"/> android:scheme="content"/>
</intent-filter> </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> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@ -787,8 +1223,7 @@
android:host="*" android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\.pkpass" android:pathPattern="/.*\\..*\\..*\\..*\\.pkpass"
android:scheme="https"/> android:scheme="https"/>
<data <data android:host="*"
android:host="*"
android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pkpass" android:pathPattern="/.*\\..*\\..*\\..*\\..*\\.pkpass"
android:scheme="https"/> android:scheme="https"/>
<data <data
@ -1113,7 +1548,7 @@
<data <data
android:scheme="http" android:scheme="http"
android:host="pass-cloud.appspot.com" android:host="pass-cloud.appspot.com"
/> />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
@ -1141,10 +1576,31 @@
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data <data
android:host="services.aircanada.com" android:host="mci.aircanada.com"
android:scheme="http"/> android:scheme="http"/>
</intent-filter> </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> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@ -1242,6 +1698,7 @@
</activity> </activity>
<activity android:name=".ui.quirk_fix.OpenIphoneWebView"/> <activity android:name=".ui.quirk_fix.OpenIphoneWebView"/>
<activity android:name=".ui.TouchImageActivity"/>
<activity android:name=".ui.quirk_fix.USAirwaysLoadActivity"> <activity android:name=".ui.quirk_fix.USAirwaysLoadActivity">
<intent-filter> <intent-filter>
@ -1335,8 +1792,9 @@
<activity <activity
android:name=".ui.PassViewActivity" android:name=".ui.PassViewActivity"
android:theme="@style/AppBaseThemeNoActionbar" android:theme="@style/AppThemeNoActionbar"
android:label="@string/app_name" android:label="@string/app_name"
android:exported="true"
android:parentActivityName=".ui.PassListActivity"> android:parentActivityName=".ui.PassListActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@ -1346,6 +1804,7 @@
<activity <activity
android:name=".ui.FullscreenBarcodeActivity" android:name=".ui.FullscreenBarcodeActivity"
android:label="@string/app_name"/> android:label="@string/app_name"/>
<activity android:name=".scan.PassScanActivity" />
</application> </application>

View file

@ -1,9 +1,9 @@
/** /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2005-2014, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
import java.io.IOException; import java.io.IOException;
@ -35,6 +35,7 @@ import java.util.List;
* <p/> * <p/>
* @stable ICU 3.4 * @stable ICU 3.4
*/ */
@SuppressWarnings("ALL")
public class CharsetDetector { public class CharsetDetector {
// Question: Should we have getters corresponding to the setters for input text // Question: Should we have getters corresponding to the setters for input text
@ -182,7 +183,7 @@ public class CharsetDetector {
* @stable ICU 3.4 * @stable ICU 3.4
*/ */
public CharsetMatch[] detectAll() { public CharsetMatch[] detectAll() {
ArrayList<CharsetMatch> matches = new ArrayList<CharsetMatch>(); ArrayList<CharsetMatch> matches = new ArrayList<>();
MungeInput(); // Strip html markup, collect byte stats. MungeInput(); // Strip html markup, collect byte stats.
@ -344,7 +345,7 @@ public class CharsetDetector {
* it by removing what appears to be html markup. * it by removing what appears to be html markup.
*/ */
private void MungeInput() { private void MungeInput() {
int srci = 0; int srci;
int dsti = 0; int dsti = 0;
byte b; byte b;
boolean inMarkup = false; boolean inMarkup = false;
@ -473,7 +474,7 @@ public class CharsetDetector {
private static final List<CSRecognizerInfo> ALL_CS_RECOGNIZERS; private static final List<CSRecognizerInfo> ALL_CS_RECOGNIZERS;
static { 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_UTF8(), true));
list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_BE(), true)); list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_BE(), true));
@ -522,7 +523,7 @@ public class CharsetDetector {
*/ */
@Deprecated @Deprecated
public String[] getDetectableCharsets() { 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++) { for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {
CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i); CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i);
boolean active = (fEnabledRecognizers == null) ? rcinfo.isDefaultEnabled : fEnabledRecognizers[i]; boolean active = (fEnabledRecognizers == null) ? rcinfo.isDefaultEnabled : fEnabledRecognizers[i];
@ -530,7 +531,7 @@ public class CharsetDetector {
csnames.add(rcinfo.recognizer.getName()); csnames.add(rcinfo.recognizer.getName());
} }
} }
return csnames.toArray(new String[csnames.size()]); return csnames.toArray(new String[0]);
} }
/** /**

View file

@ -1,9 +1,9 @@
/** /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -26,6 +26,7 @@ import java.io.Reader;
* *
* @stable ICU 3.4 * @stable ICU 3.4
*/ */
@SuppressWarnings("ALL")
public class CharsetMatch implements Comparable<CharsetMatch> { public class CharsetMatch implements Comparable<CharsetMatch> {
@ -85,13 +86,13 @@ public class CharsetMatch implements Comparable<CharsetMatch> {
* @stable ICU 3.4 * @stable ICU 3.4
*/ */
public String getString(int maxLength) throws java.io.IOException { public String getString(int maxLength) throws java.io.IOException {
String result = null; String result;
if (fInputStream != null) { if (fInputStream != null) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024]; char[] buffer = new char[1024];
Reader reader = getReader(); Reader reader = getReader();
int max = maxLength < 0? Integer.MAX_VALUE : maxLength; int max = maxLength < 0? Integer.MAX_VALUE : maxLength;
int bytesRead = 0; int bytesRead;
while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) { while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) {
sb.append(buffer, 0, bytesRead); 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' * be used to open a charset (e.g. IBM424_rtl). The ending '_rtl' or 'ltr'
* should be stripped off before creating the string. * should be stripped off before creating the string.
*/ */
//noinspection IndexOfReplaceableByContains
int startSuffix = name.indexOf("_rtl") < 0 ? name.indexOf("_ltr") : name.indexOf("_rtl"); int startSuffix = name.indexOf("_rtl") < 0 ? name.indexOf("_ltr") : name.indexOf("_rtl");
if (startSuffix > 0) { if (startSuffix > 0) {
name = name.substring(0, startSuffix); 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. // If user gave us a byte array, this is it.
private int fRawLength; // Length of data in fRawInput array. 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. // gave us a byte array.
private String fCharsetName; // The name of the charset this CharsetMatch private String fCharsetName; // The name of the charset this CharsetMatch

View file

@ -1,9 +1,9 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2005 - 2012, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
/** /**
@ -14,6 +14,7 @@ package com.ibm.icu.text;
* *
* The separate classes are nested within this class. * The separate classes are nested within this class.
*/ */
@SuppressWarnings("ALL")
abstract class CharsetRecog_2022 extends CharsetRecognizer { abstract class CharsetRecog_2022 extends CharsetRecognizer {
@ -44,7 +45,7 @@ abstract class CharsetRecog_2022 extends CharsetRecognizer {
byte [] seq = escapeSequences[escN]; byte [] seq = escapeSequences[escN];
if ((textLen - i) < seq.length) { if ((textLen - i) < seq.length) {
continue checkEscapes; continue;
} }
for (j=1; j<seq.length; j++) { for (j=1; j<seq.length; j++) {
@ -92,9 +93,6 @@ abstract class CharsetRecog_2022 extends CharsetRecognizer {
return quality; return quality;
} }
static class CharsetRecog_2022JP extends CharsetRecog_2022 { static class CharsetRecog_2022JP extends CharsetRecog_2022 {
private byte [] [] escapeSequences = { private byte [] [] escapeSequences = {
{0x1b, 0x24, 0x28, 0x43}, // KS X 1001:1992 {0x1b, 0x24, 0x28, 0x43}, // KS X 1001:1992

View file

@ -1,14 +1,15 @@
/** /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2005 - 2014, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
/** /**
* Charset recognizer for UTF-8 * Charset recognizer for UTF-8
*/ */
@SuppressWarnings("ALL")
class CharsetRecog_UTF8 extends CharsetRecognizer { class CharsetRecog_UTF8 extends CharsetRecognizer {
String getName() { String getName() {
@ -24,7 +25,7 @@ class CharsetRecog_UTF8 extends CharsetRecognizer {
int numInvalid = 0; int numInvalid = 0;
byte input[] = det.fRawInput; byte input[] = det.fRawInput;
int i; int i;
int trailBytes = 0; int trailBytes;
int confidence; int confidence;
if (det.fRawLength >= 3 && if (det.fRawLength >= 3 &&

View file

@ -12,6 +12,7 @@ package com.ibm.icu.text;
* This class matches UTF-16 and UTF-32, both big- and little-endian. The * This class matches UTF-16 and UTF-32, both big- and little-endian. The
* BOM will be used if it is present. * BOM will be used if it is present.
*/ */
@SuppressWarnings("ALL")
abstract class CharsetRecog_Unicode extends CharsetRecognizer { abstract class CharsetRecog_Unicode extends CharsetRecognizer {
/* (non-Javadoc) /* (non-Javadoc)
@ -171,7 +172,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
{ {
int getChar(byte[] input, int index) 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); (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) int getChar(byte[] input, int index)
{ {
return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 | 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() String getName()

View file

@ -1,9 +1,8 @@
/* /*
**************************************************************************** *******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
**************************************************************************** *******************************************************************************
*
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
@ -21,6 +20,7 @@ import java.util.Arrays;
* encodings to be checked. The specific encoding being recognized * encodings to be checked. The specific encoding being recognized
* is determined by subclass. * is determined by subclass.
*/ */
@SuppressWarnings("ALL")
abstract class CharsetRecog_mbcs extends CharsetRecognizer { abstract class CharsetRecog_mbcs extends CharsetRecognizer {
/** /**
@ -157,8 +157,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
done = true; done = true;
return -1; return -1;
} }
int byteValue = (int)det.fRawInput[nextIndex++] & 0x00ff; return (int)det.fRawInput[nextIndex++] & 0x00ff;
return byteValue;
} }
} }
@ -321,9 +320,9 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
boolean nextChar(iteratedChar it, CharsetDetector det) { boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex; it.index = it.nextIndex;
it.error = false; it.error = false;
int firstByte = 0; int firstByte;
int secondByte = 0; int secondByte;
int thirdByte = 0; int thirdByte;
//int fourthByte = 0; //int fourthByte = 0;
buildChar: { 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) { boolean nextChar(iteratedChar it, CharsetDetector det) {
it.index = it.nextIndex; it.index = it.nextIndex;
it.error = false; it.error = false;
int firstByte = 0; int firstByte;
int secondByte = 0; int secondByte;
int thirdByte = 0; int thirdByte;
int fourthByte = 0; int fourthByte;
buildChar: { buildChar: {
firstByte = it.charValue = it.nextByte(det); firstByte = it.charValue = it.nextByte(det);
@ -483,7 +482,7 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
secondByte = it.nextByte(det); secondByte = it.nextByte(det);
it.charValue = (it.charValue << 8) | secondByte; it.charValue = (it.charValue << 8) | secondByte;
if (firstByte >= 0x81 && firstByte <= 0xFE) { if (firstByte <= 0xFE) {
// Two byte Char // Two byte Char
if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >=80 && secondByte <=0xFE)) { if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >=80 && secondByte <=0xFE)) {
break buildChar; break buildChar;
@ -504,11 +503,10 @@ abstract class CharsetRecog_mbcs extends CharsetRecognizer {
} }
it.error = true; it.error = true;
break buildChar;
} }
} }
return (it.done == false); return !it.done;
} }
static int [] commonChars = static int [] commonChars =

View file

@ -1,17 +1,16 @@
/* /*
**************************************************************************** *******************************************************************************
* Copyright (C) 2005-2013, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
************************************************************************** * *******************************************************************************
*
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
/** /**
* This class recognizes single-byte encodings. Because the encoding scheme is so * This class recognizes single-byte encodings. Because the encoding scheme is so
* simple, language statistics are used to do the matching. * simple, language statistics are used to do the matching.
*/ */
@SuppressWarnings("ALL")
abstract class CharsetRecog_sbcs extends CharsetRecognizer { abstract class CharsetRecog_sbcs extends CharsetRecognizer {
/* (non-Javadoc) /* (non-Javadoc)
@ -25,7 +24,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
private static final int N_GRAM_MASK = 0xFFFFFF; private static final int N_GRAM_MASK = 0xFFFFFF;
protected int byteIndex = 0; protected int byteIndex = 0;
private int ngram = 0; private int ngram;
private int[] ngramList; private int[] ngramList;
protected byte[] byteMap; protected byte[] byteMap;
@ -161,7 +160,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
return (int) (rawPercent * 300.0); return (int) (rawPercent * 300.0);
} }
} }
static class NGramParser_IBM420 extends NGramParser static class NGramParser_IBM420 extends NGramParser
{ {
private byte alef = 0x00; private byte alef = 0x00;
@ -273,7 +272,7 @@ abstract class CharsetRecog_sbcs extends CharsetRecognizer {
return parser.parse(det, spaceChar); 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); NGramParser_IBM420 parser = new NGramParser_IBM420(ngrams, byteMap);
return parser.parse(det, spaceChar); return parser.parse(det, spaceChar);
} }

View file

@ -1,9 +1,9 @@
/** /*
******************************************************************************* *******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and * * Copyright (C) 2005 - 2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
*/ */
package com.ibm.icu.text; package com.ibm.icu.text;
/** /**
@ -20,6 +20,7 @@ package com.ibm.icu.text;
* The WILL be shared by multiple instances of CharsetDetector. * The WILL be shared by multiple instances of CharsetDetector.
* They encapsulate const charset-specific information. * They encapsulate const charset-specific information.
*/ */
@SuppressWarnings("ALL")
abstract class CharsetRecognizer { abstract class CharsetRecognizer {
/** /**
* Get the IANA name of this charset. * Get the IANA name of this charset.

View file

@ -1,45 +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 = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.trackerModule(new TrackerModule(this))
.build();
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
LeakCanary.install(this);
AndroidThreeTen.init(this);
initTraceDroid();
AppCompatDelegate.setDefaultNightMode(component.settings().getNightMode());
}
private void initTraceDroid() {
TraceDroid.init(this);
Log.setTAG("PassAndroid");
}
public static AppComponent component() {
return component;
}
@VisibleForTesting
public static void setComponent(AppComponent newComponent) {
component = newComponent;
}
}

View 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")
}
}

View file

@ -1,53 +0,0 @@
package org.ligi.passandroid;
import dagger.Component;
import javax.inject.Singleton;
import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.Settings;
import org.ligi.passandroid.ui.PassAdapter;
import org.ligi.passandroid.ui.PassAndroidActivity;
import org.ligi.passandroid.ui.PassEditActivity;
import org.ligi.passandroid.ui.PassImportActivity;
import org.ligi.passandroid.ui.PassListActivity;
import org.ligi.passandroid.ui.PassListFragment;
import org.ligi.passandroid.ui.PassMenuOptions;
import org.ligi.passandroid.ui.PassNavigationView;
import org.ligi.passandroid.ui.PassViewActivityBase;
import org.ligi.passandroid.ui.SearchPassesIntentService;
import org.ligi.passandroid.ui.edit.PassandroidFragment;
import org.ligi.passandroid.ui.quirk_fix.USAirwaysLoadActivity;
@Singleton
@Component(modules = {AppModule.class , TrackerModule.class})
public interface AppComponent {
void inject(PassViewActivityBase passViewActivityBase);
void inject(PassListActivity passListActivity);
void inject(PassEditActivity passEditActivity);
void inject(PassandroidFragment passandroidFragment);
void inject(PassAdapter passAdapter);
void inject(PassImportActivity passImportActivity);
void inject(PassMenuOptions passMenuOptions);
void inject(SearchPassesIntentService searchPassesIntentService);
void inject(USAirwaysLoadActivity usAirwaysLoadActivity);
void inject(PassAndroidActivity passAndroidActivity);
void inject(PassListFragment passListFragment);
void inject(PassNavigationView passNavigationView);
PassStore passStore();
Tracker tracker();
Settings settings();
}

View file

@ -1,65 +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 javax.inject.Singleton;
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 org.ligi.passandroid.model.State;
@Module
public class AppModule {
private final App app;
public AppModule(App app) {
this.app = app;
}
@Singleton
@Provides
PassStore providePassStore(Settings settings, Moshi moshi,EventBus bus) {
return new AndroidFileSystemPassStore(app, settings, moshi,bus);
}
@Singleton
@Provides
Moshi provideMoshi() {
return new Moshi.Builder()
.add(new ZonedTimeAdapter())
.add(new ColorAdapter())
.build();
}
@Singleton
@Provides
Settings provideSettings() {
return new AndroidSettings(app);
}
@Singleton
@Provides
SharedPreferences provideSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(app);
}
@Singleton
@Provides
EventBus provideBus() {
return EventBus.getDefault();
}
@Singleton
@Provides
State provideState() {
return new State();
}
}

View file

@ -1,7 +1,7 @@
package org.ligi.passandroid; package org.ligi.passandroid;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
public interface Tracker { public interface Tracker {
void trackException(String s, Throwable e, boolean fatal); void trackException(String s, Throwable e, boolean fatal);

View file

@ -1,50 +0,0 @@
package org.ligi.passandroid.actions;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.provider.CalendarContract;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.pass.Pass;
import org.threeten.bp.ZonedDateTime;
public class AddToCalendar {
public static void tryAddDateToCalendar(final Pass pass, final Activity activity, final ZonedDateTime date) {
if (pass.getCalendarTimespan() == null) {
new AlertDialog.Builder(activity).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, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
reallyAddToCalendar(pass, activity, date);
}
})
.show();
} else {
reallyAddToCalendar(pass, activity, date);
}
}
private static void reallyAddToCalendar(Pass pass, Activity activity, ZonedDateTime date) {
try {
final Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setType("vnd.android.cursor.item/event");
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, date.toEpochSecond() * 1000);
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, date.plusHours(1).toEpochSecond() * 1000);
intent.putExtra("title", pass.getDescription());
activity.startActivity(intent);
} catch (ActivityNotFoundException exception) {
// TODO maybe action to install calendar app
Snackbar.make(activity.getWindow().getDecorView(), R.string.no_calendar_app_found, Snackbar.LENGTH_LONG).show();
}
}
}

View file

@ -1,11 +0,0 @@
package org.ligi.passandroid.events;
import org.ligi.passandroid.model.pass.Pass;
public class PassRefreshEvent {
public final Pass pass;
public PassRefreshEvent(Pass pass) {
this.pass = pass;
}
}

View file

@ -1,3 +0,0 @@
package org.ligi.passandroid.events
object PassStoreChangeEvent

View file

@ -1,5 +0,0 @@
package org.ligi.passandroid.events
import org.ligi.passandroid.model.pass.Pass
class ScanFinishedEvent(val foundPasses: List<Pass>)

View file

@ -1,10 +0,0 @@
package org.ligi.passandroid.events;
public class ScanProgressEvent {
public final String message;
public ScanProgressEvent(String message) {
this.message = message;
}
}

View file

@ -0,0 +1,60 @@
package org.ligi.passandroid.functions
import android.content.ActivityNotFoundException
import android.content.Intent
import android.provider.CalendarContract
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
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) { _, _ -> reallyAddToCalendar(pass, contextView, timeSpan) }
.show()
} else {
reallyAddToCalendar(pass, contextView, timeSpan)
}
}
private fun reallyAddToCalendar(pass: Pass, contextView: View, timeSpan: PassImpl.TimeSpan) = try {
val intent = createIntent(pass, timeSpan)
contextView.context.startActivity(intent)
} catch (exception: ActivityNotFoundException) {
// TODO maybe action to install calendar app
Snackbar.make(contextView, R.string.no_calendar_app_found, Snackbar.LENGTH_LONG).show()
}
@VisibleForTesting
fun createIntent(pass: Pass, timeSpan: PassImpl.TimeSpan) = Intent(Intent.ACTION_EDIT).apply {
if (timeSpan.from == null && timeSpan.to == null) {
throw IllegalArgumentException("span must have either a to or a from")
}
type = "vnd.android.cursor.item/event"
val from = timeSpan.from ?: timeSpan.to!!.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS)
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, from.toEpochSecond() * 1000)
val to = timeSpan.to ?: timeSpan.from!!.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, to.toEpochSecond() * 1000)
putExtra("title", pass.description)
pass.locations.firstOrNull()?.name?.let {
putExtra("eventLocation", it)
}
}

View file

@ -0,0 +1,63 @@
package org.ligi.passandroid.functions
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import com.google.zxing.MultiFormatWriter
import org.ligi.passandroid.model.pass.PassBarCodeFormat
import org.ligi.tracedroid.logging.Log
fun generateBitmapDrawable(resources: Resources, data: String, type: PassBarCodeFormat): BitmapDrawable? {
val bitmap = generateBarCodeBitmap(data, type) ?: return null
return BitmapDrawable(resources, bitmap).apply {
isFilterBitmap = false
setAntiAlias(false)
}
}
fun generateBarCodeBitmap(data: String, type: PassBarCodeFormat): Bitmap? {
if (data.isEmpty()) {
return null
}
try {
val matrix = getBitMatrix(data, type)
val is1D = matrix.height == 1
// generate an image from the byte matrix
val width = matrix.width
val height = if (is1D) width / 5 else matrix.height
// create buffered image to draw to
// NTFS Bitmap.Config.ALPHA_8 sounds like an awesome idea - been there - done that ..
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 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 barcodeImage
} catch (e: com.google.zxing.WriterException) {
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")
return null
} catch (e: ArrayIndexOutOfBoundsException) {
// happens for ITF barcode on certain inputs
Log.w("could not write image: $e")
return null
}
}
fun getBitMatrix(data: String, type: PassBarCodeFormat)
= MultiFormatWriter().encode(data, type.zxingBarCodeFormat(), 0, 0)!!

View file

@ -0,0 +1,46 @@
package org.ligi.passandroid.functions
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.PassType
@StringRes
fun getHumanCategoryString(fromPass: PassType) = when (fromPass) {
PassType.BOARDING -> R.string.boarding_pass
PassType.EVENT -> R.string.category_event
PassType.COUPON -> R.string.category_coupon
PassType.LOYALTY -> R.string.category_storecard
PassType.GENERIC -> R.string.category_generic
PassType.VOUCHER -> R.string.categories_voucher
else -> R.string.category_none
}
@ColorInt
fun getCategoryDefaultBG(category: PassType) = when (category) {
PassType.BOARDING -> 0xFF3d73e9
PassType.EVENT -> 0xFF9f3dd0
PassType.COUPON -> 0xFF9ccb05
PassType.LOYALTY -> 0xFFf29b21
PassType.VOUCHER -> 0xFF2A2727
PassType.GENERIC -> 0xFFea3c48
else -> Color.WHITE.toLong()
}.toInt()
@DrawableRes
fun getCategoryTopImageRes(type: PassType) = when (type) {
PassType.BOARDING -> R.drawable.cat_bp
PassType.EVENT -> R.drawable.cat_et
PassType.COUPON -> R.drawable.cat_cp
PassType.LOYALTY -> R.drawable.cat_sc
PassType.VOUCHER -> R.drawable.cat_ps
PassType.GENERIC -> R.drawable.cat_none
else -> R.drawable.cat_none
}

View file

@ -0,0 +1,72 @@
package org.ligi.passandroid.functions
import android.content.Context
import android.net.Uri
import okhttp3.OkHttpClient
import okhttp3.Request
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.model.InputStreamWithSource
import java.io.BufferedInputStream
import java.net.URL
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, 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, tracker)
"file" -> getDefaultInputStreamForUri(uri)
else -> {
tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
getDefaultInputStreamForUri(uri)
}
}
}
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/",
"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)) {
tracker.trackEvent("quirk_fix", "ua_fake", key, null)
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
}
}
val request = requestBuilder.build()
val response = client.newCall(request).execute()
val body = response.body()
if (body != null) {
return InputStreamWithSource(uri.toString(), body.byteStream())
}
return null
}
private fun fromContent(ctx: Context, uri: Uri) = ctx.contentResolver.openInputStream(uri)?.let {
InputStreamWithSource(uri.toString(), it)
}
private fun getDefaultInputStreamForUri(uri: Uri) = InputStreamWithSource(uri.toString(), BufferedInputStream(URL(uri.toString()).openStream(), 4096))

View file

@ -0,0 +1,16 @@
package org.ligi.passandroid.functions
import android.app.Activity
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
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)
.setAction(R.string.undo) { passClassifier.moveToTopic(pass, oldTopic) }
.show()
passClassifier.moveToTopic(pass, topic)
}

View file

@ -0,0 +1,68 @@
package org.ligi.passandroid.functions
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
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.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.model.pass.PassType
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.*
const val APP = "passandroid"
fun createAndAddEmptyPass(passStore: PassStore, resources: Resources): Pass {
val pass = createBasePass()
pass.description = "custom Pass"
passStore.currentPass = pass
passStore.save(pass)
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher)
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, FileOutputStream(File(passStore.getPathForID(pass.id), PassBitmapDefinitions.BITMAP_ICON + ".png")))
} catch (ignored: FileNotFoundException) {
}
return pass
}
fun createPassForImageImport(resources: Resources): Pass {
return createBasePass().apply {
description = resources.getString(R.string.image_import)
fields = mutableListOf(
PassField.create(R.string.field_source, R.string.field_source_image, resources),
PassField.create(R.string.field_advice_label, R.string.field_advice_text, resources),
PassField.create(R.string.field_note, R.string.field_note_image, resources, true)
)
}
}
fun createPassForPDFImport(resources: Resources): Pass {
return createBasePass().apply {
description = resources.getString(R.string.pdf_import)
fields = mutableListOf(
PassField.create(R.string.field_source, R.string.field_source_pdf, resources),
PassField.create(R.string.field_advice_label, R.string.field_advice_text, resources),
PassField.create(R.string.field_note, R.string.field_note_pdf, resources, true)
)
}
}
private fun createBasePass(): PassImpl {
val pass = PassImpl(UUID.randomUUID().toString())
pass.accentColor = 0xFF0000FF.toInt()
pass.app = APP
pass.type = PassType.EVENT
return pass
}

View file

@ -0,0 +1,82 @@
package org.ligi.passandroid.functions
import org.json.JSONException
import org.json.JSONObject
/**
* I got a really broken passes with invalid json from users.
* As it is not possible to change the problem in the generator side
* It has to be worked around here
*/
private val replacementMap = mapOf(
// first we try without fixing -> always positive and try to have minimal impact
"" to "",
// but here the horror starts ..
// Fix for Virgin Australia
// "value": "NTL",}
// a comma should never be before a closing curly brace like this ,}
// note the \t are greetings to Empire Theatres Tickets - without it their passes do not work
",[\n\r\t ]*\\}" to "}",
/*
Entrada cine Entradas.com
{
"key": "passSourceUpdate",
"label": "Actualiza tu entrada",
"value": "http://www.entradas.com/entradas/passbook.do?cutout
},
],
*/
",[\n\r\t ]*\\]" to "]",
/*
forgotten value aka ( also Entradas.com):
"locations": [
{
"latitude": ,
"longitude": ,
"relevantText": "Bienvenido a yelmo cines espacio coruña"
}
*/
":[ ]*,[\n\r\t ]*\"" to ":\"\",",
/*
from RENFE OPERADORA Billete de tren
],
"transitType": "PKTransitTypeTrain"
},
,
"relevantDate": "2013-08-10T19:15+02:00"
*/
",[\n\r\t ]*," to ","
)
@Throws(JSONException::class)
fun readJSONSafely(str: String?): JSONObject? {
if (str == null) {
return null
}
var allReplaced: String = str
// first try with single fixes
for ((key, value) in replacementMap) {
try {
allReplaced = allReplaced.replace(key.toRegex(), value)
return JSONObject(str.replace(key.toRegex(), value))
} catch (e: JSONException) {
// expected because of problems in JSON we are trying to fix here
}
}
// if that did not work do combination of all - if this is not working a JSONException is thrown
return JSONObject(allReplaced)
}

View file

@ -1,70 +0,0 @@
package org.ligi.passandroid.helper;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.Writer;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import org.ligi.passandroid.model.pass.PassBarCodeFormat;
import org.ligi.tracedroid.logging.Log;
public class BarcodeHelper {
@Nullable
public static BitmapDrawable generateBitmapDrawable(@NonNull Resources resources, @NonNull String data, @NonNull PassBarCodeFormat type) {
final Bitmap bitmap = generateBarCodeBitmap(data, type);
if (bitmap == null) {
return null;
}
final BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, bitmap);
bitmapDrawable.setFilterBitmap(false);
bitmapDrawable.setAntiAlias(false);
return bitmapDrawable;
}
@Nullable
public static Bitmap generateBarCodeBitmap(@NonNull String data, @NonNull PassBarCodeFormat type) {
if (data.isEmpty()) {
return null;
}
try {
final BitMatrix matrix = getBitMatrix(data, type);
final boolean is1D = matrix.getHeight() == 1;
// generate an image from the byte matrix
final int width = matrix.getWidth();
final int height = is1D ? (width / 5) : matrix.getHeight();
// create buffered image to draw to
// NTFS Bitmap.Config.ALPHA_8 sounds like an awesome idea - been there - done that ..
final Bitmap barcode_image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
// iterate through the matrix and draw the pixels to the image
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
barcode_image.setPixel(x, y, matrix.get(x, is1D ? 0 : y) ? 0 : 0xFFFFFF);
}
}
return barcode_image;
} catch (com.google.zxing.WriterException | IllegalArgumentException e) {
Log.w("could not write image " + e);
// TODO check if we should better return some rescue Image here
return null;
}
}
public static BitMatrix getBitMatrix(String data, PassBarCodeFormat type) throws WriterException {
final Writer writer = new MultiFormatWriter();
return writer.encode(data, type.zxingBarCodeFormat(), 0, 0);
}
}

View file

@ -1,56 +0,0 @@
package org.ligi.passandroid.helper
import android.graphics.Color
import android.support.annotation.ColorInt
import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.PassType
object CategoryHelper {
@StringRes
fun getHumanCategoryString(fromPass: PassType): Int {
when (fromPass) {
PassType.BOARDING -> return R.string.boarding_pass
PassType.EVENT -> return R.string.category_event
PassType.COUPON -> return R.string.category_coupon
PassType.LOYALTY -> return R.string.category_storecard
PassType.GENERIC -> return R.string.category_generic
PassType.VOUCHER -> return R.string.categories_voucher
else -> return R.string.category_none
}
}
@ColorInt
fun getCategoryDefaultBG(category: PassType): Int {
when (category) {
PassType.BOARDING -> return 0xFF3d73e9.toInt()
PassType.EVENT -> return 0xFF9f3dd0.toInt()
PassType.COUPON -> return 0xFF9ccb05.toInt()
PassType.LOYALTY -> return 0xFFf29b21.toInt()
PassType.VOUCHER -> return 0xFF2A2727.toInt()
PassType.GENERIC -> return 0xFFea3c48.toInt()
else -> return Color.WHITE
}
}
@DrawableRes
fun getCategoryTopImageRes(type: PassType): Int {
when (type) {
PassType.BOARDING -> return R.drawable.cat_bp
PassType.EVENT -> return R.drawable.cat_et
PassType.COUPON -> return R.drawable.cat_cp
PassType.LOYALTY -> return R.drawable.cat_sc
PassType.VOUCHER -> return R.drawable.cat_ps
PassType.GENERIC -> return R.drawable.cat_none
else -> return R.drawable.cat_none
}
}
}

View file

@ -1,24 +0,0 @@
package org.ligi.passandroid.helper;
import android.app.Activity;
import android.support.design.widget.Snackbar;
import android.view.View;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.PassClassifier;
import org.ligi.passandroid.model.pass.Pass;
public class MoveHelper {
public static void moveWithUndoSnackbar(final PassClassifier passClassifier, final Pass pass, String topic, final Activity activity) {
final String oldTopic = passClassifier.getTopic(pass, "");
Snackbar.make(activity.getWindow().getDecorView().findViewById(R.id.fam), "Pass moved to " + topic, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
passClassifier.moveToTopic(pass, oldTopic);
}
})
.show();
passClassifier.moveToTopic(pass, topic);
}
}

View file

@ -1,58 +0,0 @@
package org.ligi.passandroid.helper;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
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.model.pass.PassField;
import org.ligi.passandroid.model.pass.PassImpl;
import org.ligi.passandroid.model.pass.PassType;
public class PassTemplates {
public static final String APP = "passandroid";
public static Pass createAndAddEmptyPass(final PassStore passStore, final Resources resources) {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setAccentColor(0xFF0000ff);
pass.setDescription("custom Pass");
pass.setApp(APP);
pass.setType(PassType.EVENT);
passStore.setCurrentPass(pass);
passStore.save(pass);
final Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher);
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 90, new FileOutputStream(new File(passStore.getPathForID(pass.getId()), PassBitmapDefinitions.BITMAP_ICON+".png")));
} catch (FileNotFoundException ignored) {
}
return pass;
}
public static Pass createPassForImageImport() {
final PassImpl pass = new PassImpl(UUID.randomUUID().toString());
pass.setAccentColor(0xFF0000ff);
pass.setDescription("image import");
List<PassField> list = new ArrayList<>();
list.add(new PassField(null, "source", "image", false));
list.add(new PassField(null, "note", "This is imported from image - not a real pass", false));
pass.setFields(list);
pass.setApp(APP);
pass.setType(PassType.EVENT);
return pass;
}
}

View file

@ -1,87 +0,0 @@
package org.ligi.passandroid.helper;
import android.support.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* I got a really broken passes with invalid json from users.
* As it is not possible to change the problem in the generator side
* It has to be worked around here
*/
public class SafeJSONReader {
private static Map<String, String> replacementMap = new LinkedHashMap<String, String>() {{
// first we try without fixing -> always positive and try to have minimal impact
put("", "");
// but here the horror starts ..
// Fix for Virgin Australia
// "value": "NTL",}
// a comma should never be before a closing curly brace like this ,}
// note the \t are greetings to Empire Theatres Tickets - without it their passes do not work
put(",[\n\r\t ]*\\}", "}");
/*
Entrada cine Entradas.com
{
"key": "passSourceUpdate",
"label": "Actualiza tu entrada",
"value": "http://www.entradas.com/entradas/passbook.do?cutout
},
],
*/
put(",[\n\r\t ]*\\]", "]");
/*
forgotten value aka ( also Entradas.com):
"locations": [
{
"latitude": ,
"longitude": ,
"relevantText": "Bienvenido a yelmo cines espacio coruña"
}
*/
put(":[ ]*,[\n\r\t ]*\"", ":\"\",");
/*
from RENFE OPERADORA Billete de tren
],
"transitType": "PKTransitTypeTrain"
},
,
"relevantDate": "2013-08-10T19:15+02:00"
*/
put(",[\n\r\t ]*,", ",");
}};
public static JSONObject readJSONSafely(@Nullable String str) throws JSONException {
if (str == null) {
return null;
}
String allReplaced = str;
// first try with single fixes
for (Map.Entry<String, String> replacementPair : replacementMap.entrySet()) {
try {
allReplaced = allReplaced.replaceAll(replacementPair.getKey(), replacementPair.getValue());
return new JSONObject(str.replaceAll(replacementPair.getKey(), replacementPair.getValue()));
} catch (JSONException e) {
// expected because of problems in JSON we are trying to fix here
}
}
// if that did not work do combination of all - if this is not working a JSONException is thrown
return new JSONObject(allReplaced);
}
}

View file

@ -1,9 +0,0 @@
package org.ligi.passandroid.helper;
import android.support.annotation.Nullable;
public class Strings {
public static String nullToEmpty(@Nullable String in) {
return in != null ? in : "";
}
}

View file

@ -1,19 +0,0 @@
package org.ligi.passandroid.json_adapter;
import android.graphics.Color;
import com.squareup.moshi.FromJson;
import com.squareup.moshi.ToJson;
import org.ligi.passandroid.model.pass.PassImpl;
public class ColorAdapter {
@ToJson
String toJson(@PassImpl.HexColor int rgb) {
return String.format("#%06x", rgb);
}
@FromJson
@PassImpl.HexColor
int fromJson(String rgb) {
return Color.parseColor(rgb);
}
}

View file

@ -0,0 +1,16 @@
package org.ligi.passandroid.json_adapter
import android.graphics.Color
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import org.ligi.passandroid.model.pass.PassImpl
class ColorAdapter {
@ToJson
internal fun toJson(@PassImpl.HexColor rgb: Int) = String.format("#%06x", rgb)
@FromJson
@PassImpl.HexColor
internal fun fromJson(rgb: String) = Color.parseColor(rgb)
}

View file

@ -1,18 +0,0 @@
package org.ligi.passandroid.json_adapter;
import com.squareup.moshi.FromJson;
import com.squareup.moshi.ToJson;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.format.DateTimeFormatter;
public class ZonedTimeAdapter {
@ToJson
String toJson(ZonedDateTime zonedDateTime) {
return zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME );
}
@FromJson
ZonedDateTime fromJson(String zonedDateTime) {
return ZonedDateTime.parse(zonedDateTime);
}
}

View file

@ -0,0 +1,16 @@
package org.ligi.passandroid.json_adapter
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.format.DateTimeFormatter
class ZonedTimeAdapter {
@ToJson
internal fun toJson(zonedDateTime: ZonedDateTime) = zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
@FromJson
internal fun fromJson(zonedDateTime: String) = ZonedDateTime.parse(zonedDateTime)
}

View file

@ -3,12 +3,16 @@ package org.ligi.passandroid.model
import android.content.Context import android.content.Context
import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import okio.Okio import kotlinx.coroutines.GlobalScope
import org.greenrobot.eventbus.EventBus import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import org.ligi.axt.AXT import kotlinx.coroutines.launch
import org.ligi.passandroid.App 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.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.Pass
import org.ligi.passandroid.model.pass.PassImpl import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.reader.AppleStylePassReader import org.ligi.passandroid.reader.AppleStylePassReader
@ -16,15 +20,26 @@ import org.ligi.passandroid.reader.PassReader
import java.io.File import java.io.File
import java.util.* import java.util.*
class AndroidFileSystemPassStore(private val context: Context, settings: Settings, private val moshi: Moshi, private val bus: EventBus) : PassStore { object PassStoreUpdateEvent
private val path: File = settings.passesDir
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 val passMap = HashMap<String, Pass>()
override var currentPass: Pass? = null override var currentPass: Pass? = null
private val tracker: Tracker by inject()
override val classifier: PassClassifier by lazy { override val classifier: PassClassifier by lazy {
val classificationFile = File(settings.stateDir, "classifier_state.json") val classificationFile = File(settings.getStateDir(), "classifier_state.json")
FileBackedPassClassifier(classificationFile, this, moshi) FileBackedPassClassifier(classificationFile, this, moshi)
} }
@ -37,11 +52,11 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
pathForID.mkdirs() pathForID.mkdirs()
} }
val buffer = Okio.buffer(Okio.sink(File(pathForID, "main.json"))) val buffer = File(pathForID, "main.json").sink().buffer()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
val of = com.squareup.moshi.JsonWriter.of(buffer) val of = com.squareup.moshi.JsonWriter.of(buffer)
of.setIndent(" ") of.indent = " "
jsonAdapter.toJson(of, pass as PassImpl) jsonAdapter.toJson(of, pass as PassImpl)
buffer.close() buffer.close()
of.close() of.close()
@ -68,9 +83,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
val jsonAdapter = moshi.adapter(PassImpl::class.java) val jsonAdapter = moshi.adapter(PassImpl::class.java)
dirty = false dirty = false
try { try {
result = jsonAdapter.fromJson(Okio.buffer(Okio.source(file))) result = jsonAdapter.fromJson(file.source().buffer())
} catch (ignored: JsonDataException) { } catch (ignored: JsonDataException) {
App.component().tracker().trackException("invalid main.json", false) tracker.trackException("invalid main.json", false)
} }
} }
@ -80,14 +95,14 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
} }
if (result == null && File(pathForID, "pass.json").exists()) { 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 (result != null) {
if (dirty) { if (dirty) {
save(result) save(result)
} }
passMap.put(id, result) passMap[id] = result
notifyChange() notifyChange()
} }
@ -99,7 +114,7 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
} }
override fun deletePassWithId(id: String): Boolean { override fun deletePassWithId(id: String): Boolean {
val result = AXT.at(getPathForID(id)).deleteRecursive() val result = getPathForID(id).deleteRecursively()
if (result) { if (result) {
passMap.remove(id) passMap.remove(id)
classifier.removePass(id) classifier.removePass(id)
@ -113,7 +128,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
} }
override fun notifyChange() { override fun notifyChange() {
bus.post(PassStoreChangeEvent) GlobalScope.launch {
updateChannel.send(PassStoreUpdateEvent)
}
} }
override fun syncPassStoreWithClassifier(defaultTopic: String) { override fun syncPassStoreWithClassifier(defaultTopic: String) {

View file

@ -1,78 +0,0 @@
package org.ligi.passandroid.model;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate;
import java.io.File;
import org.ligi.passandroid.R;
import org.ligi.passandroid.model.comparator.PassSortOrder;
import static org.ligi.passandroid.R.string.preference_key_autolight;
import static org.ligi.passandroid.R.string.preference_key_condensed;
public class AndroidSettings implements Settings {
public final Context context;
final SharedPreferences sharedPreferences;
public AndroidSettings(Context context) {
this.context = context;
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
}
@Override
public PassSortOrder getSortOrder() {
final String key = context.getString(R.string.preference_key_sort);
final String stringValue = sharedPreferences.getString(key, "0");
final int id = Integer.valueOf(stringValue);
for (PassSortOrder order : PassSortOrder.values()) {
if (order.getInt() == id) {
return order;
}
}
return PassSortOrder.DATE_ASC;
}
@Override
public boolean doTraceDroidEmailSend() {
// will be overridden in test-module
return true;
}
@Override
public File getPassesDir() {
return new File(context.getFilesDir().getAbsolutePath(), "passes");
}
@Override
public File getStateDir() {
return new File(context.getFilesDir(), "state");
}
@Override
public boolean isCondensedModeEnabled() {
return sharedPreferences.getBoolean(context.getString(preference_key_condensed), false);
}
@Override
public boolean isAutomaticLightEnabled() {
return sharedPreferences.getBoolean(context.getString(preference_key_autolight), true);
}
@Override
public int getNightMode() {
final String key = sharedPreferences.getString(context.getString(R.string.preference_key_nightmode), "auto");
switch (key) {
case "day":
return AppCompatDelegate.MODE_NIGHT_NO;
case "night":
return AppCompatDelegate.MODE_NIGHT_YES;
case "auto":
return AppCompatDelegate.MODE_NIGHT_AUTO;
}
return AppCompatDelegate.MODE_NIGHT_AUTO;
}
}

View file

@ -0,0 +1,44 @@
package org.ligi.passandroid.model
import android.content.Context
import android.preference.PreferenceManager
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
import org.ligi.passandroid.model.comparator.PassSortOrder
import java.io.File
class AndroidSettings(val context: Context) : Settings {
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!!)
return PassSortOrder.values().first { it.int == id }
}
override fun doTraceDroidEmailSend() = true
override fun getPassesDir() = File(context.filesDir.absolutePath, "passes")
override fun getStateDir() = File(context.filesDir, "state")
override fun isCondensedModeEnabled() = sharedPreferences.getBoolean(context.getString(preference_key_condensed), false)
override fun isAutomaticLightEnabled() = sharedPreferences.getBoolean(context.getString(preference_key_autolight), true)
override fun getNightMode(): Int {
val key = sharedPreferences.getString(context.getString(R.string.preference_key_nightmode), "auto")
return when (key) {
"day" -> AppCompatDelegate.MODE_NIGHT_NO
"night" -> AppCompatDelegate.MODE_NIGHT_YES
"auto" -> AppCompatDelegate.MODE_NIGHT_AUTO
else -> AppCompatDelegate.MODE_NIGHT_AUTO
}
}
}

View file

@ -1,7 +1,6 @@
package org.ligi.passandroid.model package org.ligi.passandroid.model
import org.ligi.passandroid.Tracker import org.ligi.passandroid.Tracker
import org.ligi.passandroid.helper.Strings
import org.ligi.passandroid.model.pass.PassField import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl import org.ligi.passandroid.model.pass.PassImpl
import org.threeten.bp.DateTimeException import org.threeten.bp.DateTimeException
@ -25,10 +24,10 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
tryToFindDate(pass) tryToFindDate(pass)
} }
fun tryToFindDate(pass: PassImpl) { private fun tryToFindDate(pass: PassImpl) {
if (pass.calendarTimespan == null) { if (pass.calendarTimespan == null) {
val foundDate = pass.fields.filter { it.key.equals("date") }.map { val foundDate = pass.fields.filter { "date" == it.key }.map {
try { try {
ZonedDateTime.parse(it.value) ZonedDateTime.parse(it.value)
} catch (e: DateTimeException) { } catch (e: DateTimeException) {
@ -45,11 +44,11 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
} }
private fun getPassFieldForKey(pass: PassImpl, key: String): PassField? { 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? { private fun getPassFieldThatMatchesLabel(pass: PassImpl, matcher: String): PassField? {
return pass.fields.firstOrNull() { return pass.fields.firstOrNull {
val label = it.label val label = it.label
label != null && label.matches(matcher.toRegex()) label != null && label.matches(matcher.toRegex())
} }
@ -152,7 +151,7 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
} }
private fun careForWestbahn(pass: PassImpl) { private fun careForWestbahn(pass: PassImpl) {
if (pass.calendarTimespan != null || Strings.nullToEmpty(pass.creator) != "WESTbahn") { if (pass.calendarTimespan != null || (pass.creator ?: "") != "WESTbahn") {
return return
} }

View file

@ -1,21 +1,16 @@
package org.ligi.passandroid.model; package org.ligi.passandroid.model;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.ibm.icu.text.CharsetDetector; import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch; import com.ibm.icu.text.CharsetMatch;
import org.ligi.passandroid.App;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.HashMap; import java.util.HashMap;
@VisibleForTesting
public class AppleStylePassTranslation extends HashMap<String, String> { public class AppleStylePassTranslation extends HashMap<String, String> {
public String translate(String key) { public String translate(String key) {
@ -63,6 +58,13 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
dataInputStream.readFully(fileData); dataInputStream.readFully(fileData);
dataInputStream.close(); 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(); final CharsetMatch match = new CharsetDetector().setText(fileData).detect();
if (match != null) try { if (match != null) try {
@ -71,7 +73,6 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
} }
return new String(fileData); return new String(fileData);
} catch (Throwable e) { } catch (Throwable e) {
App.component().tracker().trackException("problem_reading_translation", e, false);
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }

View file

@ -1,6 +1,7 @@
package org.ligi.passandroid.model; package org.ligi.passandroid.model;
public class PassBitmapDefinitions { public class PassBitmapDefinitions {
public final static String BITMAP_ICON = "icon"; public final static String BITMAP_ICON = "icon";
public final static String BITMAP_THUMBNAIL = "thumbnail"; public final static String BITMAP_THUMBNAIL = "thumbnail";
public final static String BITMAP_STRIP = "strip"; public final static String BITMAP_STRIP = "strip";

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