Compare commits

...

127 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
188 changed files with 2494 additions and 2003 deletions

View file

@ -1,4 +1,22 @@
{
"type":"android",
"stages":["spoon","lint","test","assemble"]
"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']

1
.gitignore vendored
View file

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

View file

@ -1,16 +1,24 @@
[![on Google Play](http://ligi.de/img/play_badge.png)](https://play.google.com/store/apps/details?id=org.ligi.passandroid)
[![on FDroid](http://ligi.de/img/fdroid_badge.png)](https://f-droid.org/repository/browse/?fdid=org.ligi.passandroid)
[![on Amazon](http://ligi.de/img/amazon_badge.png)](https://www.amazon.com/ligi-Passandroid/dp/B01LX9DMSQ)
[![on Google Play](https://ligi.de/img/play_badge.png)](https://play.google.com/store/apps/details?id=org.ligi.passandroid)
[![on FDroid](https://ligi.de/img/fdroid_badge.png)](https://f-droid.org/repository/browse/?fdid=org.ligi.passandroid)
[![on Amazon](https://ligi.de/img/amazon_badge.png)](https://www.amazon.com/ligi-Passandroid/dp/B01LX9DMSQ)
PassAndroid
===========
# PassAndroid
Android App to view Passes ( e.g. event tickets, coupons, loyalty cards, boarding passes, .. )
Android App to view Passes (e.g. event tickets, coupons, loyalty cards, boarding passes, ...)
<img src="http://ligi.de/img/passandroid_screenshots.png"/>
![Screenshots](https://ligi.de/img/passandroid_screenshots.png)
Displays [esPass (*.esPass)](http://espass.it) and Passbook ( *.pkpass ) files & shows the Barcode ( QR, PDF417, AZTEC, Code 39 and Code 128 format ). It useable offline.
When preparing for the Chaos Communication Congress 2012 ( #29c3 ) I stumbled upon a passbook file for the first time. As I really like the idea of paperless tickets as it saves time and trees which both are very valuable to me. The problem was that I found no app with which I could use the downloaded passbook file.
Displays [esPass](https://espass.it) (`*.esPass`) & Passbook (`*.pkpass`) files, shows the Barcode (QR, PDF417, AZTEC, Code 39 and Code 128 format) and is also usable offline.
When preparing for the Chaos Communication Congress 2012 #29c3 I stumbled upon a passbook file for the first time.
I really like the idea of paperless tickets as it saves time and trees which are both very valuable to me.
The problem was that I was unable to find an app to open and use the downloaded passbook files with, that's why I wrote my own one.
## Legal
This project is licensed under the [GNU General Public License v3.0](COPYING).
We are not affiliated with Apple - Passbook might be trademarked by Apple, but it's introduced like a standard so this should be okay.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT

View file

@ -1,29 +1,27 @@
apply plugin: 'com.android.application'
apply plugin: 'spoon'
apply plugin: 'com.trevjonez.composer'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'de.mobilej.unmock'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'com.github.triplet.play'
repositories {
jcenter()
mavenLocal()
maven { url 'https://jitpack.io' }
maven { url 'https://maven.google.com' }
google()
maven { url 'https://www.jitpack.io' }
}
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
compileSdkVersion 29
defaultConfig {
versionCode 346
versionName "3.4.6"
minSdkVersion 9
targetSdkVersion 25
versionCode 356
versionName "3.5.6"
minSdkVersion 14
targetSdkVersion 29
applicationId "org.ligi.passandroid"
testInstrumentationRunner "org.ligi.passandroid.AppReplacingRunner"
archivesBaseName = "PassAndroid-$versionName"
@ -62,6 +60,7 @@ android {
}
}
android.variantFilter { variant ->
def maps = variant.getFlavors().get(0).name
def analytics = variant.getFlavors().get(1).name
@ -85,7 +84,6 @@ android {
exclude 'META-INF/maven/com.google.guava/guava/pom.properties'
exclude 'META-INF/maven/com.google.guava/guava/pom.xml'
}
lintOptions {
@ -99,101 +97,99 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug {
multiDexEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
configurations {
// http://stackoverflow.com/questions/30578243/why-would-adding-espresso-contrib-cause-an-inflateexception
androidTestCompile.exclude group: 'com.android.support', module: 'appcompat-v7'
androidTestCompile.exclude group: 'com.android.support', module: 'design'
androidTestCompile.exclude group: 'com.android.support', module: 'support-v4'
androidTestCompile.exclude group: 'com.android.support', module: 'support-annotations'
androidTestCompile.exclude group: 'com.android.support', module: 'preference-v7'
androidTestCompile.exclude module: 'recyclerview-v7'
androidTestCompile.exclude module: 'kotlin-stdlib'
}
dependencies {
compile 'com.github.hotchemi:permissionsdispatcher:2.3.2'
kapt 'com.github.hotchemi:permissionsdispatcher-processor:2.3.2'
implementation 'org.permissionsdispatcher:permissionsdispatcher:4.6.0'
kapt 'org.permissionsdispatcher:permissionsdispatcher-processor:4.6.0'
compile 'com.github.salomonbrys.kodein:kodein:4.1.0'
implementation "org.koin:koin-android:2.1.2"
provided 'org.glassfish:javax.annotation:10.0-b28'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
androidTestCompile 'com.github.ligi:trulesk:0.21'
androidTestImplementation 'com.github.ligi:trulesk:0.31'
androidTestUtil 'com.linkedin.testbutler:test-butler-app:2.1.0'
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.1'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation 'com.squareup.assertj:assertj-android:1.2.0'
androidTestImplementation "org.mockito:mockito-core:$mockito_version"
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.25.1'
androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2'
androidTestImplementation 'org.threeten:threetenbp:1.4.1'
androidTestImplementation 'com.android.support:multidex:1.0.3'
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
androidTestCompile "org.mockito:mockito-core:$mockito_version"
androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
implementation 'com.github.ligi:TouchImageView:2.1'
implementation 'com.github.ligi:ExtraCompats:1.0'
implementation 'net.lingala.zip4j:zip4j:2.3.2'
implementation 'com.jakewharton.threetenabp:threetenabp:1.2.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
androidTestCompile 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
androidTestCompile "com.android.support:appcompat-v7:$support_version"
androidTestCompile "com.android.support:design:$support_version"
implementation 'com.github.ligi:KAXT:1.0'
implementation 'com.github.ligi:KAXTUI:1.0'
implementation 'com.github.ligi:loadtoast:1.10.11'
implementation 'com.github.ligi:tracedroid:3.0'
compile 'com.github.ligi:TouchImageView:2.1'
compile 'com.github.ligi:ExtraCompats:0.4'
forPlayImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
forFDroidImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.24'
forAmazonImplementation 'com.github.ligi.snackengage:snackengage-amazonrate:0.24'
compile 'net.lingala.zip4j:zip4j:1.3.2'
compile 'com.jakewharton.threetenabp:threetenabp:1.0.5'
compile 'org.greenrobot:eventbus:3.0.0'
// https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
// Don't update to >=3.13 before minSDK 21 + Java 8
//noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.larswerkman:HoloColorPicker:1.5'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
compile "com.android.support:support-annotations:$support_version"
compile "com.android.support:recyclerview-v7:$support_version"
compile "com.android.support:appcompat-v7:$support_version"
compile "com.android.support:cardview-v7:$support_version"
compile "com.android.support:design:$support_version"
compile "com.android.support:preference-v7:$support_version"
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 'net.i2p.android.ext:floatingactionbutton:1.10.1'
compile 'com.github.ligi:KAXT:0.19'
compile 'com.github.ligi:KAXTUI:0.4'
compile 'com.github.ligi:loadtoast:1.10.11'
compile 'com.github.ligi:tracedroid:1.4'
forPlayCompile 'com.github.ligi.snackengage:snackengage-playrate:0.15'
forFDroidCompile 'com.github.ligi.snackengage:snackengage-playrate:0.15'
forAmazonCompile 'com.github.ligi.snackengage:snackengage-amazonrate:0.15'
compile 'com.squareup.okhttp3:okhttp:3.9.1'
compile 'com.larswerkman:HoloColorPicker:1.5'
compile 'com.google.code.findbugs:jsr305:3.0.2'
compile 'com.squareup.moshi:moshi:1.4.0'
compile "com.chibatching.kotpref:kotpref:2.5.0"
compile "com.chibatching.kotpref:initializer:2.5.0"
testCompile "com.android.support:support-annotations:$support_version"
testCompile 'com.squareup.assertj:assertj-android:1.1.1'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:${mockito_version}"
testCompile 'org.threeten:threetenbp:1.3.6'
implementation 'com.chibatching.kotpref:kotpref:2.10.0'
implementation 'com.chibatching.kotpref:initializer:2.10.0'
testImplementation 'androidx.annotation:annotation:1.1.0'
testImplementation 'com.squareup.assertj:assertj-android:1.2.0'
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:$mockito_version"
testImplementation 'org.threeten:threetenbp:1.4.1'
// https://github.com/ligi/PassAndroid/issues/181
// Don't update to 3.3.1 before minSDK 19 - or replace zxing
compile 'com.google.zxing:core:3.3.0'
// Don't upgrade before minSDK 19 - or replace zxing
//noinspection GradleDependency
implementation 'com.google.zxing:core:3.3.0'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
withAnalyticsCompile "com.google.android.gms:play-services-analytics:$play_version"
withMapsCompile "com.google.android.gms:play-services-maps:$play_version"
}
spoon {
debug = true
grantAllPermissions = true
// requires minSDK 16 according to docs, not sure if it causes an issue
withAnalyticsImplementation 'com.google.android.gms:play-services-analytics:17.0.0'
withMapsImplementation 'com.google.android.gms:play-services-maps:17.0.0'
}
play {
jsonFile = file('/media/ligi/USBCRED/play.json')
uploadImages = true
}
}

View file

@ -20,31 +20,24 @@
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# optimize
-optimizationpasses 2
-optimizations !code/simplification/arithmetic
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-optimizationpasses 2
-optimizations !code/simplification/arithmetic
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
# AppCompat
# Keep line numbers to alleviate debugging stack traces
-dontwarn android.support.v7.**
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
# Keep line numbers to alleviate debugging stack traces
-renamesourcefileattribute SourceFile
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
### for api client
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
-keepclassmembers class * {
@ -54,7 +47,6 @@
# Needed by Guava
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
-dontwarn sun.misc.Unsafe
-dontwarn com.google.common.collect.MinMaxPriorityQueue
@ -72,9 +64,6 @@
-keep class **$$ViewBinder { *; }
-keepnames class * { @butterknife.Bind *;}
#### for support 22
-dontwarn android.support.**
#### for guava
-dontwarn javax.annotation.**
-dontwarn javax.inject.**
@ -105,23 +94,20 @@
-dontwarn java.nio.file.OpenOption
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-keep public class com.google.android.gms.**
-dontwarn com.google.android.gms.**
## New rules for EventBus 3.0.x ##
# http://greenrobot.org/eventbus/documentation/proguard/
-keepattributes *Annotation*
-keepclassmembers class ** {
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
}
### for moshi
@ -153,3 +139,17 @@
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
## okhttp
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform

View file

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

View file

@ -1,10 +1,7 @@
package org.ligi.passandroid
import com.github.salomonbrys.kodein.Kodein
import com.github.salomonbrys.kodein.bind
import com.github.salomonbrys.kodein.instance
import com.github.salomonbrys.kodein.singleton
import org.greenrobot.eventbus.EventBus
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
@ -20,28 +17,24 @@ import java.util.*
class TestApp : App() {
override fun createKodein() = Kodein.Module {
bind<PassStore>() with singleton {
FixedPassListPassStore(emptyList())
}
bind<Settings>() with singleton {
mock(Settings::class.java).apply {
`when`(getSortOrder()).thenReturn(PassSortOrder.DATE_ASC)
`when`(getPassesDir()).thenReturn(File(""))
`when`(doTraceDroidEmailSend()).thenReturn(false)
}
}
bind<Tracker>(overrides = true) with singleton { mock(Tracker::class.java) }
bind<EventBus>() with singleton { mock(EventBus::class.java) }
}
override fun createKoin(): Module {
override fun installLeakCanary() = Unit
return module {
single { passStore as PassStore }
single { settings }
single { tracker }
}
}
companion object {
fun passStore(): PassStore = kodein.instance()
fun settings(): Settings = kodein.instance()
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() {
@ -53,13 +46,13 @@ class TestApp : App() {
fixedPassListPassStore().setList(passList)
passStore().classifier.moveToTopic(pass, "test")
passStore.classifier.moveToTopic(pass, "test")
}
fun emptyPassStore() {
fixedPassListPassStore().setList(emptyList())
}
fun fixedPassListPassStore() = passStore() as FixedPassListPassStore
private fun fixedPassListPassStore() = passStore as FixedPassListPassStore
}
}

View file

@ -3,14 +3,14 @@ package org.ligi.passandroid
import android.app.Activity.RESULT_CANCELED
import android.app.Instrumentation
import android.provider.CalendarContract
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.intending
import android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra
import android.support.test.espresso.intent.matcher.IntentMatchers.hasType
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
import androidx.test.espresso.intent.matcher.IntentMatchers.hasType
import androidx.test.espresso.matcher.ViewMatchers.*
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.allOf
import org.junit.Rule
@ -23,8 +23,8 @@ import org.threeten.bp.ZonedDateTime
class TheAddToCalendar {
val time = ZonedDateTime.now()
val time2 = ZonedDateTime.now().plusHours(3)
private val time = ZonedDateTime.now()
private val time2 = ZonedDateTime.now().plusHours(3)
@get:Rule
var rule = TruleskIntentRule(PassListActivity::class.java, false)
@ -33,7 +33,7 @@ class TheAddToCalendar {
fun testIfWeOnlyHaveCalendarStartDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore().currentPass!!.calendarTimespan = PassImpl.TimeSpan(time)
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
@ -44,7 +44,7 @@ class TheAddToCalendar {
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore().currentPass!!.description)
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@ -52,7 +52,7 @@ class TheAddToCalendar {
fun testIfWeOnlyHaveCalendarEndDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore().currentPass!!.calendarTimespan = PassImpl.TimeSpan(to = time)
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(to = time)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
@ -63,7 +63,7 @@ class TheAddToCalendar {
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore().currentPass!!.description)
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@ -71,7 +71,7 @@ class TheAddToCalendar {
fun testIfWeOnlyHaveCalendarStartAndEndDate() {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore().currentPass!!.calendarTimespan = PassImpl.TimeSpan(time, time2)
TestApp.passStore.currentPass!!.calendarTimespan = PassImpl.TimeSpan(time, time2)
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
@ -82,7 +82,7 @@ class TheAddToCalendar {
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time2.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore().currentPass!!.description)
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@ -91,7 +91,7 @@ class TheAddToCalendar {
fun testIfWeOnlyHaveExpirationDate() {
TestApp.populatePassStoreWithSinglePass()
(TestApp.passStore().currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time))
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time))
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
@ -105,7 +105,7 @@ class TheAddToCalendar {
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.plusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore().currentPass!!.description)
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@ -113,7 +113,7 @@ class TheAddToCalendar {
fun testIfWeOnlyHaveExpirationEndDate() {
TestApp.populatePassStoreWithSinglePass()
(TestApp.passStore().currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(to = time))
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(to = time))
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
@ -127,7 +127,7 @@ class TheAddToCalendar {
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.minusHours(DEFAULT_EVENT_LENGTH_IN_HOURS).toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore().currentPass!!.description)
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}
@ -135,7 +135,7 @@ class TheAddToCalendar {
fun testIfWeOnlyHaveExpirationStartAndEndDate() {
TestApp.populatePassStoreWithSinglePass()
(TestApp.passStore().currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time, time2))
(TestApp.passStore.currentPass as PassImpl).validTimespans = listOf(PassImpl.TimeSpan(time, time2))
rule.launchActivity()
intending(hasType("vnd.android.cursor.item/event")).respondWith(Instrumentation.ActivityResult(RESULT_CANCELED, null))
@ -149,7 +149,7 @@ class TheAddToCalendar {
hasType("vnd.android.cursor.item/event"),
hasExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, time.toEpochSecond() * 1000),
hasExtra(CalendarContract.EXTRA_EVENT_END_TIME, time2.toEpochSecond() * 1000),
hasExtra("title", TestApp.passStore().currentPass!!.description)
hasExtra("title", TestApp.passStore.currentPass!!.description)
))
}

View file

@ -1,14 +1,16 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.closeSoftKeyboard
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.*
import android.support.test.espresso.assertion.ViewAssertions.doesNotExist
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.filters.SdkSuppress
import android.support.test.runner.AndroidJUnit4
import com.github.salomonbrys.kodein.instance
import android.Manifest
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.closeSoftKeyboard
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import com.linkedin.android.testbutler.TestButler
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -25,16 +27,20 @@ class TheBarCodeEditing {
@get:Rule
val rule = TruleskActivityRule(PassEditActivity::class.java, false)
val passStore: PassStore = App.kodein.instance()
val passStore: PassStore = TestApp.passStore
lateinit var currentPass: PassImpl
private lateinit var currentPass: PassImpl
fun start(setupPass: (pass: PassImpl) -> Unit = {}) {
private fun start(setupPass: (pass: PassImpl) -> Unit = {}) {
TestApp.populatePassStoreWithSinglePass()
currentPass = passStore.currentPass as PassImpl
setupPass(currentPass)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
rule.launchActivity(null)
closeSoftKeyboard()
}
@ -78,6 +84,7 @@ class TheBarCodeEditing {
onView(withText(passBarCodeFormat.name)).perform(scrollTo(), click())
onView(withId(R.id.randomButton)).perform(click())
closeSoftKeyboard()
onView(withText(android.R.string.ok)).perform(click())
@ -95,7 +102,7 @@ class TheBarCodeEditing {
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.messageInput)).perform(clearText())
onView(withId(R.id.messageInput)).perform(typeText("msg foo txt ;-)"))
onView(withId(R.id.messageInput)).perform(replaceText("msg foo txt ;-)"))
closeSoftKeyboard()
@ -115,7 +122,7 @@ class TheBarCodeEditing {
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.alternativeMessageInput)).perform(clearText())
onView(withId(R.id.alternativeMessageInput)).perform(typeText("alt bar txt ;-)"))
onView(withId(R.id.alternativeMessageInput)).perform(replaceText("alt bar txt ;-)"))
closeSoftKeyboard()

View file

@ -1,6 +1,5 @@
package org.ligi.passandroid
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Fail.fail
import org.junit.Test
@ -48,7 +47,6 @@ class TheBarcodeHelper {
} catch (e: Exception) {
fail("could not create barcode", e)
}
}
fun testBitmapSizeIsSane(format: PassBarCodeFormat) {
@ -59,6 +57,5 @@ class TheBarcodeHelper {
} catch (e: Exception) {
fail("could not create barcode", e)
}
}
}

View file

@ -1,9 +1,9 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.CoreMatchers.containsString
import org.junit.Rule
import org.junit.Test
@ -20,14 +20,14 @@ class TheCondensedPassViewMode {
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java, false) {
TestApp.populatePassStoreWithSinglePass()
val currentPass = TestApp.passStore().currentPass as PassImpl
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)
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(false)
rule.launchActivity()
@ -41,7 +41,7 @@ class TheCondensedPassViewMode {
@Test
fun testFieldShowsForCondensedOn() {
`when`(TestApp.settings().isCondensedModeEnabled()).thenReturn(true)
`when`(TestApp.settings.isCondensedModeEnabled()).thenReturn(true)
rule.launchActivity()

View file

@ -1,10 +1,10 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.withId
import 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

View file

@ -1,10 +1,10 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.replaceText
import android.support.test.espresso.action.ViewActions.scrollTo
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -18,7 +18,7 @@ class TheFieldListEditFragment {
@get:Rule
val rule = TruleskIntentRule(PassEditActivity::class.java) {
TestApp.passStore().currentPass = PassImpl(UUID.randomUUID().toString()).apply {
TestApp.passStore.currentPass = PassImpl(UUID.randomUUID().toString()).apply {
fields = arrayListOf(field)
}
}

View file

@ -2,11 +2,11 @@ package org.ligi.passandroid
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.widget.ImageView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -19,9 +19,9 @@ import org.ligi.passandroid.ui.FullscreenBarcodeActivity
import org.ligi.trulesk.TruleskIntentRule
import java.util.*
class TheFullscreenBarcodeActivity {
private const val BARCODE_MESSAGE = "2323"
private val BARCODE_MESSAGE = "2323"
class TheFullscreenBarcodeActivity {
@get:Rule
var rule = TruleskIntentRule(FullscreenBarcodeActivity::class.java, false)
@ -68,7 +68,7 @@ class TheFullscreenBarcodeActivity {
val pass = PassImpl(UUID.randomUUID().toString())
pass.barCode = BarCode(format, BARCODE_MESSAGE)
TestApp.passStore().currentPass = pass
TestApp.passStore.currentPass = pass
rule.launchActivity(null)
onView(withId(R.id.fullscreen_barcode)).check(matches(isDisplayed()))
@ -78,11 +78,11 @@ class TheFullscreenBarcodeActivity {
val bitmap = bitmapDrawable.bitmap
val bitmapToTest: Bitmap
if (format === PassBarCodeFormat.AZTEC) {
bitmapToTest = if (format === PassBarCodeFormat.AZTEC) {
// not sure why - but for the decoder to pick up AZTEC it must have moar pixelz - smells like a zxing bug
bitmapToTest = Bitmap.createScaledBitmap(bitmap, bitmap.width * 2, bitmap.height * 2, false)
Bitmap.createScaledBitmap(bitmap, bitmap.width * 2, bitmap.height * 2, false)
} else {
bitmapToTest = bitmap
bitmap
}
assertThat(bitmapToTest.decodeBarCode()).isEqualTo(BARCODE_MESSAGE)

View file

@ -1,9 +1,9 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test

View file

@ -5,15 +5,15 @@ import android.app.Activity.RESULT_CANCELED
import android.app.Instrumentation.ActivityResult
import android.content.Intent.ACTION_SEND
import android.content.Intent.ACTION_VIEW
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.pressBack
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.contrib.DrawerActions.open
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.intending
import android.support.test.espresso.intent.matcher.IntentMatchers.*
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions.open
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.*
import androidx.test.espresso.matcher.ViewMatchers.*
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not
import org.junit.Rule
@ -61,19 +61,6 @@ class TheNavigationDrawer {
intended(allOf(hasAction(ACTION_VIEW), hasData("https://play.google.com/apps/testing/org.ligi.passandroid")))
}
@Test
fun testCommunityClick() {
testThatNavigationDrawerOpens()
rule.screenShot("open_drawer")
intending(hasAction(ACTION_VIEW)).respondWith(ActivityResult(RESULT_CANCELED, null))
onView(withText(nav_community_on_google)).perform(click())
intended(allOf(hasAction(ACTION_VIEW), hasData("https://plus.google.com/communities/116353894782342292067")))
}
@Test
fun testGitHubClick() {
testThatNavigationDrawerOpens()

View file

@ -1,21 +1,22 @@
package org.ligi.passandroid
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.*
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.intending
import android.support.test.espresso.intent.matcher.IntentMatchers.hasAction
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.platform.app.InstrumentationRegistry
import com.linkedin.android.testbutler.TestButler
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.R.id.*
import org.ligi.passandroid.R.string.*
import org.ligi.passandroid.model.pass.PassType.COUPON
import org.ligi.passandroid.model.pass.PassType.EVENT
import org.ligi.passandroid.ui.PassEditActivity
@ -24,18 +25,21 @@ import org.ligi.trulesk.TruleskIntentRule
@TargetApi(14)
class ThePassEditActivity {
val passStore = TestApp.passStore()
val passStore = TestApp.passStore
@get:Rule
var rule = TruleskIntentRule(PassEditActivity::class.java)
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(withId(categoryView)).perform(click())
onView(withText(select_category_dialog_title)).perform(click())
onView(withText(category_event)).perform(click())
onView(withText(R.string.select_category_dialog_title)).perform(click())
onView(withText(R.string.category_event)).perform(click())
assertThat(passStore.currentPass!!.type).isEqualTo(EVENT)
rule.screenShot("edit_set_event")
@ -43,10 +47,10 @@ class ThePassEditActivity {
@Test
fun testSetToCouponWorks() {
onView(withId(categoryView)).perform(click())
onView(withId(R.id.categoryView)).perform(click())
onView(withText(select_category_dialog_title)).perform(click())
onView(withText(category_coupon)).perform(click())
onView(withText(R.string.select_category_dialog_title)).perform(click())
onView(withText(R.string.category_coupon)).perform(click())
assertThat(passStore.currentPass!!.type).isEqualTo(COUPON)
rule.screenShot("edit_set_coupon")
@ -55,7 +59,7 @@ class ThePassEditActivity {
@Test
fun testSetDescriptionWorks() {
onView(withId(passTitle)).perform(clearText(), typeText("test description"))
onView(withId(R.id.passTitle)).perform(clearText(), replaceText("test description"))
assertThat(passStore.currentPass!!.description).isEqualTo("test description")
rule.screenShot("edit_set_description")
@ -65,10 +69,10 @@ class ThePassEditActivity {
@Test
fun testColorWheelIsThere() {
onView(withId(categoryView)).perform(click())
onView(withText(change_color_dialog_title)).perform(click())
onView(withId(R.id.categoryView)).perform(click())
onView(withText(R.string.change_color_dialog_title)).perform(click())
onView(withId(colorPicker)).check(matches(isDisplayed()))
onView(withId(R.id.colorPicker)).check(matches(isDisplayed()))
rule.screenShot("edit_set_color")
}

View file

@ -2,11 +2,11 @@ package org.ligi.passandroid
import android.annotation.TargetApi
import android.os.Build
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.pressBack
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.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

View file

@ -1,19 +1,20 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.action.ViewActions.typeText
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.v7.widget.helper.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.App.Companion.passStore
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.passandroid.ui.PassListFragment
import org.ligi.trulesk.TruleskIntentRule
const val CUSTOM_PROBE = "FOO_PROBE"
class ThePassListSwiping {
@get:Rule
@ -27,7 +28,7 @@ class ThePassListSwiping {
onView(withText(R.string.topic_trash)).perform(click())
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
}
@ -37,20 +38,19 @@ class ThePassListSwiping {
onView(withText(R.string.topic_archive)).perform(click())
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
}
@Test
fun testWeCanMoveToCustom() {
val CUSTOM_PROBE = "FOO_PROBE"
fakeSwipeLeft()
onView(withId(R.id.new_topic_edit)).perform(typeText(CUSTOM_PROBE))
onView(withId(R.id.new_topic_edit)).perform(replaceText(CUSTOM_PROBE))
onView(withText(android.R.string.ok)).perform(click())
assertThat(passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
}

View file

@ -1,12 +1,13 @@
package org.ligi.passandroid
import android.annotation.TargetApi
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.assertion.ViewAssertions.doesNotExist
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.hamcrest.core.IsNot.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.ligi.passandroid.model.pass.BarCode
@ -21,11 +22,16 @@ import java.util.*
@TargetApi(14)
class ThePassViewActivity {
internal fun getActPass() = TestApp.passStore().currentPass as PassImpl
private fun getActPass() = TestApp.passStore.currentPass as PassImpl
@get:Rule
var rule = TruleskActivityRule(PassViewActivity::class.java, false)
@Before
fun before() {
TestApp.populatePassStoreWithSinglePass()
}
@Test
fun testThatDescriptionIsThere() {
rule.launchActivity(null)
@ -35,7 +41,7 @@ class ThePassViewActivity {
@Test
fun testDateIsGoneWhenPassbookHasNoDate() {
getActPass().validTimespans = ArrayList<PassImpl.TimeSpan>()
getActPass().validTimespans = ArrayList()
rule.launchActivity(null)
onView(withId(R.id.date)).check(matches(not(isDisplayed())))
@ -92,7 +98,7 @@ class ThePassViewActivity {
@Test
fun testLinkToCalendarIsNotThereWhenPassbookHasNoDate() {
getActPass().validTimespans = ArrayList<PassImpl.TimeSpan>()
getActPass().validTimespans = ArrayList()
rule.launchActivity(null)
onView(withText(R.string.pass_to_calendar)).check(matches(not(isDisplayed())))

View file

@ -1,9 +1,9 @@
package org.ligi.passandroid
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.assertion.ViewAssertions
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.not
import org.junit.Rule
import org.junit.Test
@ -16,7 +16,10 @@ import org.threeten.bp.ZonedDateTime
class ThePassViewHolder {
val currentPass by lazy { App.passStore.currentPass as PassImpl }
private val currentPass by lazy {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass as PassImpl
}
@get:Rule
var rule = TruleskActivityRule(PassListActivity::class.java, false)

View file

@ -2,7 +2,7 @@ package org.ligi.passandroid
import android.content.Context
import android.content.SharedPreferences
import android.support.test.InstrumentationRegistry.getInstrumentation
import androidx.test.platform.app.InstrumentationRegistry
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Rule
@ -17,13 +17,14 @@ class ThePastLocationsStore {
@get:Rule
var rule = TruleskActivityRule(PassViewActivity::class.java) {
TestApp.populatePassStoreWithSinglePass()
MockitoAnnotations.initMocks(this)
}
@Mock
lateinit var tracker: Tracker
val prefs: SharedPreferences by lazy { getInstrumentation().context.getSharedPreferences("" + System.currentTimeMillis() / 100000, Context.MODE_PRIVATE) }
private val prefs: SharedPreferences by lazy { InstrumentationRegistry.getInstrumentation().context.getSharedPreferences("" + System.currentTimeMillis() / 100000, Context.MODE_PRIVATE) }
@After
fun tearDown() {
@ -34,7 +35,7 @@ class ThePastLocationsStore {
fun testPastLocationsStoreShouldNeverContainMoreThanMaxElements() {
val tested = PastLocationsStore(prefs, tracker)
for (i in 0..PastLocationsStore.MAX_ELEMENTS * 2 - 1) {
for (i in 0 until PastLocationsStore.MAX_ELEMENTS * 2) {
tested.putLocation("" + i)
}
@ -44,7 +45,7 @@ class ThePastLocationsStore {
@Test
fun testPastLocationsStoreShouldStoreOnlyOneOfAKind() {
val tested = PastLocationsStore(prefs, tracker!!)
val tested = PastLocationsStore(prefs, tracker)
for (i in 0..2) {
tested.putLocation("foo")

View file

@ -1,10 +1,13 @@
package org.ligi.passandroid
import android.Manifest
import android.os.Build
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.v7.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.linkedin.android.testbutler.TestButler
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -18,9 +21,12 @@ import org.ligi.trulesk.TruleskActivityRule
class ThePreferenceActivity {
@get:Rule
val rule = TruleskActivityRule(PreferenceActivity::class.java)
val rule = TruleskActivityRule(PreferenceActivity::class.java) {
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(ApplicationProvider.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION)
}
val androidSettings by lazy { AndroidSettings(rule.activity) }
private val androidSettings by lazy { AndroidSettings(rule.activity) }
@Test
fun autoLightToggles() {

View file

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

View file

@ -1,8 +1,8 @@
package org.ligi.passandroid.functions
import android.support.test.espresso.UiController
import android.support.test.espresso.ViewAction
import android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom
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

View file

@ -1,12 +1,11 @@
package org.ligi.passandroid.functions
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.ligi.passandroid.R
fun checkThatHelpIsThere() {
onView(withId(R.id.help_text)).check(matches(isDisplayed()))
}

View file

@ -1,8 +1,9 @@
package org.ligi.passandroid.functions
import android.content.Context
import android.support.test.InstrumentationRegistry
import androidx.test.platform.app.InstrumentationRegistry
import org.assertj.core.api.Fail.fail
import org.ligi.passandroid.TestApp
import org.ligi.passandroid.model.InputStreamWithSource
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
@ -30,7 +31,7 @@ fun loadPassFromAsset(asset: String, callback: (pass: Pass?) -> Unit) {
object : UnzipPassController.SuccessCallback {
override fun call(uuid: String) {
callback.invoke(AppleStylePassReader.read(File(getTestTargetPath(instrumentation.targetContext), uuid), "en",
instrumentation.targetContext))
instrumentation.targetContext,TestApp.tracker))
}
},
mock

View file

@ -1,7 +1,10 @@
package org.ligi.passandroid.injections
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import org.ligi.passandroid.model.PassClassifier
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.PassStoreUpdateEvent
import org.ligi.passandroid.model.pass.Pass
import java.io.File
@ -10,7 +13,7 @@ class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
override lateinit var classifier: PassClassifier
init {
classifier = PassClassifier(HashMap<String, String>(), this)
classifier = PassClassifier(HashMap(), this)
}
fun setList(newPasses: List<Pass>, newCurrentPass: Pass? = newPasses.firstOrNull()) {
@ -19,7 +22,7 @@ class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
passMap.clear()
passMap.putAll(createHashMap())
classifier = PassClassifier(HashMap<String, String>(), this)
classifier = PassClassifier(HashMap(), this)
}
override var currentPass: Pass? = null
@ -31,7 +34,7 @@ class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
private fun createHashMap(): HashMap<String, Pass> {
val hashMap = HashMap<String, Pass>()
passes.forEach { hashMap.put(it.id, it) }
passes.forEach { hashMap[it.id] = it }
return hashMap
}
@ -48,6 +51,8 @@ class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
return File("")
}
override val updateChannel: BroadcastChannel<PassStoreUpdateEvent> = ConflatedBroadcastChannel()
override fun save(pass: Pass) {
// no effect in this impl
}

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="market_url">https://f-droid.org/repository/browse/?fdid=%s</string>
<string name="market_url" translatable="false">https://f-droid.org/repository/browse/?fdid=%s</string>
</resources>

View file

@ -29,6 +29,7 @@
<receiver
android:name=".InstallListener"
android:permission="android.permission.INSTALL_PACKAGES"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER"/>
@ -36,7 +37,7 @@
</receiver>
<provider
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="@string/authority_fileprovider"
android:grantUriPermissions="true"
android:exported="false">
@ -45,12 +46,12 @@
android:resource="@xml/filepaths"/>
</provider>
<service android:name=".ui.SearchPassesIntentService"/>
<service android:name=".scan.SearchPassesIntentService"/>
<activity
android:name=".ui.PassListActivity"
android:label="@string/app_name"
android:theme="@style/AppBaseThemeNoActionbar">
android:theme="@style/AppThemeNoActionbar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@ -63,7 +64,7 @@
<activity
android:name=".ui.HelpActivity"
android:theme="@style/AppBaseThemeNoActionbar"/>
android:theme="@style/AppThemeNoActionbar"/>
<activity
android:name=".ui.PassEditActivity"/>
<activity
@ -122,47 +123,35 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd.espass-espass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" android:mimeType="application/vnd.espass-espass"/>
<data android:scheme="https" android:mimeType="application/vnd.espass-espass"/>
<data android:scheme="content" android:mimeType="application/vnd.espass-espass"/>
<data android:scheme="file" android:mimeType="application/vnd.espass-espass"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:scheme="https" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:scheme="content" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:scheme="file" android:mimeType="application/vnd.espass-espass+zip"/>
<data android:mimeType="application/vnd.espass-espass+zip"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="https" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="content" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="file" android:mimeType="application/vnd.apple.pkpass"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:mimeType="application/pkpass"/>
<data android:scheme="https" android:mimeType="application/pkpass"/>
<data android:scheme="content" android:mimeType="application/pkpass"/>
<data android:scheme="file" android:mimeType="application/pkpass"/>
<data android:mimeType="application/vnd.apple.pkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="https" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="content" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="file" android:mimeType="application/vndapplepkpass"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/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/vndapplepkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="http" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="https" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="content" android:mimeType="application/vnd-com.apple.pkpass"/>
<data android:scheme="file" android:mimeType="application/vnd-com.apple.pkpass"/>
</intent-filter>
<intent-filter>
@ -616,17 +605,9 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/x-pdf"/>
<data android:scheme="content" android:mimeType="application/x-pdf"/>
<data android:scheme="content" android:mimeType="application/pdf"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/pdf"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -1071,22 +1052,6 @@
android:pathPattern="/.*\\.pkpass"
android:scheme="content"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd.apple.pkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/vnd-com.apple.pkpass"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -1827,7 +1792,7 @@
<activity
android:name=".ui.PassViewActivity"
android:theme="@style/AppBaseThemeNoActionbar"
android:theme="@style/AppThemeNoActionbar"
android:label="@string/app_name"
android:exported="true"
android:parentActivityName=".ui.PassListActivity">
@ -1839,6 +1804,7 @@
<activity
android:name=".ui.FullscreenBarcodeActivity"
android:label="@string/app_name"/>
<activity android:name=".scan.PassScanActivity" />
</application>

View file

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

View file

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

View file

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

View file

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

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
* BOM will be used if it is present.
*/
@SuppressWarnings("ALL")
abstract class CharsetRecog_Unicode extends CharsetRecognizer {
/* (non-Javadoc)
@ -171,7 +172,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
{
int getChar(byte[] input, int index)
{
return (input[index + 0] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
return (input[index] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |
(input[index + 2] & 0xFF) << 8 | (input[index + 3] & 0xFF);
}
@ -187,7 +188,7 @@ abstract class CharsetRecog_Unicode extends CharsetRecognizer {
int getChar(byte[] input, int index)
{
return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 |
(input[index + 1] & 0xFF) << 8 | (input[index + 0] & 0xFF);
(input[index + 1] & 0xFF) << 8 | (input[index] & 0xFF);
}
String getName()

View file

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

View file

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

View file

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

View file

@ -1,65 +1,63 @@
package org.ligi.passandroid
import android.app.Application
import android.support.v7.app.AppCompatDelegate
import com.github.salomonbrys.kodein.*
import androidx.appcompat.app.AppCompatDelegate
import com.jakewharton.threetenabp.AndroidThreeTen
import com.squareup.leakcanary.LeakCanary
import com.squareup.moshi.Moshi
import org.greenrobot.eventbus.EventBus
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()
kodein = Kodein {
import(createTrackerKodeinModule(this@App))
import(createKodein(), allowOverride = true)
startKoin {
if (BuildConfig.DEBUG) androidLogger()
androidContext(this@App)
modules(createKoin())
}
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
installLeakCanary()
AndroidThreeTen.init(this)
initTraceDroid()
val settings: Settings = kodein.instance()
AppCompatDelegate.setDefaultNightMode(settings.getNightMode())
}
open fun createKodein() = Kodein.Module {
val build = Moshi.Builder()
.add(ZonedTimeAdapter())
.add(ColorAdapter())
.build()
bind<PassStore>() with singleton { AndroidFileSystemPassStore(this@App, instance(), build, instance()) }
bind<Settings>() with singleton { AndroidSettings(this@App) }
bind<EventBus>() with singleton { EventBus.getDefault() }
}
open fun installLeakCanary() {
LeakCanary.install(this)
}
private fun initTraceDroid() {
TraceDroid.init(this)
Log.setTAG("PassAndroid")
}
companion object {
lateinit var kodein: Kodein
val tracker by lazy { kodein.Instance(TT(Tracker::class.java)) }
val passStore by lazy { kodein.Instance(TT(PassStore::class.java)) }
val settings by lazy { kodein.Instance(TT(Settings::class.java)) }
}
}

View file

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

View file

@ -1,5 +0,0 @@
package org.ligi.passandroid.events
import org.ligi.passandroid.model.pass.Pass
class PassRefreshEvent(val 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,3 +0,0 @@
package org.ligi.passandroid.events
class ScanProgressEvent(val message: String)

View file

@ -3,15 +3,15 @@ package org.ligi.passandroid.functions
import android.content.ActivityNotFoundException
import android.content.Intent
import android.provider.CalendarContract
import android.support.annotation.VisibleForTesting
import android.support.design.widget.Snackbar
import android.support.v7.app.AlertDialog
import androidx.annotation.VisibleForTesting
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AlertDialog
import android.view.View
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassImpl
val DEFAULT_EVENT_LENGTH_IN_HOURS = 8L
const val DEFAULT_EVENT_LENGTH_IN_HOURS = 8L
fun tryAddDateToCalendar(pass: Pass, contextView: View, timeSpan: PassImpl.TimeSpan) {
if (pass.calendarTimespan == null) {

View file

@ -33,26 +33,26 @@ fun generateBarCodeBitmap(data: String, type: PassBarCodeFormat): Bitmap? {
// create buffered image to draw to
// NTFS Bitmap.Config.ALPHA_8 sounds like an awesome idea - been there - done that ..
val barcode_image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
val barcodeImage = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
// iterate through the matrix and draw the pixels to the image
for (y in 0..height - 1) {
for (x in 0..width - 1) {
barcode_image.setPixel(x, y, if (matrix.get(x, if (is1D) 0 else y)) 0 else 0xFFFFFF)
for (y in 0 until height) {
for (x in 0 until width) {
barcodeImage.setPixel(x, y, if (matrix.get(x, if (is1D) 0 else y)) 0 else 0xFFFFFF)
}
}
return barcode_image
return barcodeImage
} catch (e: com.google.zxing.WriterException) {
Log.w("could not write image " + e)
Log.w("could not write image: $e")
// TODO check if we should better return some rescue Image here
return null
} catch (e: IllegalArgumentException) {
Log.w("could not write image " + e)
Log.w("could not write image: $e")
return null
} catch (e: ArrayIndexOutOfBoundsException) {
// happens for ITF barcode on certain inputs
Log.w("could not write image " + e)
Log.w("could not write image: $e")
return null
}

View file

@ -1,9 +1,9 @@
package org.ligi.passandroid.functions
import android.graphics.Color
import android.support.annotation.ColorInt
import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.PassType

View file

@ -4,31 +4,31 @@ import android.content.Context
import android.net.Uri
import okhttp3.OkHttpClient
import okhttp3.Request
import org.ligi.passandroid.App
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.model.InputStreamWithSource
import java.io.BufferedInputStream
import java.net.URL
val IPHONE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
const val IPHONE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
fun fromURI(context: Context, uri: Uri): InputStreamWithSource? {
App.tracker.trackEvent("protocol", "to_inputstream", uri.scheme, null)
fun fromURI(context: Context, uri: Uri, tracker: Tracker): InputStreamWithSource? {
tracker.trackEvent("protocol", "to_inputstream", uri.scheme, null)
return when (uri.scheme) {
"content" -> fromContent(context, uri)
"http", "https" ->
// TODO check if SPDY should be here
return fromOKHttp(uri)
return fromOKHttp(uri, tracker)
"file" -> getDefaultInputStreamForUri(uri)
else -> {
App.tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
getDefaultInputStreamForUri(uri)
}
}
}
private fun fromOKHttp(uri: Uri): InputStreamWithSource? {
private fun fromOKHttp(uri: Uri, tracker: Tracker): InputStreamWithSource? {
val client = OkHttpClient()
val url = URL(uri.toString())
val requestBuilder = Request.Builder().url(url)
@ -47,7 +47,7 @@ private fun fromOKHttp(uri: Uri): InputStreamWithSource? {
for ((key, value) in iPhoneFakeMap) {
if (uri.toString().contains(value)) {
App.tracker.trackEvent("quirk_fix", "ua_fake", key, null)
tracker.trackEvent("quirk_fix", "ua_fake", key, null)
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
}
}
@ -65,6 +65,8 @@ private fun fromOKHttp(uri: Uri): InputStreamWithSource? {
return null
}
private fun fromContent(ctx: Context, uri: Uri) = InputStreamWithSource(uri.toString(), ctx.contentResolver.openInputStream(uri))
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

@ -1,7 +1,7 @@
package org.ligi.passandroid.functions
import android.app.Activity
import android.support.design.widget.Snackbar
import com.google.android.material.snackbar.Snackbar
import org.ligi.passandroid.R
import org.ligi.passandroid.model.PassClassifier
import org.ligi.passandroid.model.pass.Pass
@ -9,7 +9,7 @@ import org.ligi.passandroid.model.pass.Pass
fun moveWithUndoSnackbar(passClassifier: PassClassifier, pass: Pass, topic: String, activity: Activity) {
val oldTopic = passClassifier.getTopic(pass, "")
Snackbar.make(activity.window.decorView.findViewById(R.id.fam), "Pass moved to " + topic, Snackbar.LENGTH_LONG)
Snackbar.make(activity.window.decorView.findViewById(R.id.fam), "Pass moved to $topic", Snackbar.LENGTH_LONG)
.setAction(R.string.undo) { passClassifier.moveToTopic(pass, oldTopic) }
.show()
passClassifier.moveToTopic(pass, topic)

View file

@ -15,7 +15,7 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.*
val APP = "passandroid"
const val APP = "passandroid"
fun createAndAddEmptyPass(passStore: PassStore, resources: Resources): Pass {
val pass = createBasePass()

View file

@ -3,11 +3,16 @@ package org.ligi.passandroid.model
import android.content.Context
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import okio.Okio
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.App
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.launch
import okio.buffer
import okio.sink
import okio.source
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.passandroid.BuildConfig
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.reader.AppleStylePassReader
@ -15,13 +20,24 @@ import org.ligi.passandroid.reader.PassReader
import java.io.File
import java.util.*
class AndroidFileSystemPassStore(private val context: Context, settings: Settings, private val moshi: Moshi, private val bus: EventBus) : PassStore {
object PassStoreUpdateEvent
class AndroidFileSystemPassStore(
private val context: Context,
settings: Settings,
private val moshi: Moshi
) : PassStore, KoinComponent {
override val updateChannel = ConflatedBroadcastChannel<PassStoreUpdateEvent>()
private val path: File = settings.getPassesDir()
override val passMap = HashMap<String, Pass>()
override var currentPass: Pass? = null
private val tracker: Tracker by inject()
override val classifier: PassClassifier by lazy {
val classificationFile = File(settings.getStateDir(), "classifier_state.json")
FileBackedPassClassifier(classificationFile, this, moshi)
@ -36,11 +52,11 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
pathForID.mkdirs()
}
val buffer = Okio.buffer(Okio.sink(File(pathForID, "main.json")))
val buffer = File(pathForID, "main.json").sink().buffer()
if (BuildConfig.DEBUG) {
val of = com.squareup.moshi.JsonWriter.of(buffer)
of.setIndent(" ")
of.indent = " "
jsonAdapter.toJson(of, pass as PassImpl)
buffer.close()
of.close()
@ -67,9 +83,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
val jsonAdapter = moshi.adapter(PassImpl::class.java)
dirty = false
try {
result = jsonAdapter.fromJson(Okio.buffer(Okio.source(file)))
result = jsonAdapter.fromJson(file.source().buffer())
} catch (ignored: JsonDataException) {
App.tracker.trackException("invalid main.json", false)
tracker.trackException("invalid main.json", false)
}
}
@ -79,14 +95,14 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
}
if (result == null && File(pathForID, "pass.json").exists()) {
result = AppleStylePassReader.read(pathForID, language, context)
result = AppleStylePassReader.read(pathForID, language, context, tracker)
}
if (result != null) {
if (dirty) {
save(result)
}
passMap.put(id, result)
passMap[id] = result
notifyChange()
}
@ -112,7 +128,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
}
override fun notifyChange() {
bus.post(PassStoreChangeEvent)
GlobalScope.launch {
updateChannel.send(PassStoreUpdateEvent)
}
}
override fun syncPassStoreWithClassifier(defaultTopic: String) {

View file

@ -2,7 +2,7 @@ package org.ligi.passandroid.model
import android.content.Context
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate
import org.ligi.passandroid.R
import org.ligi.passandroid.R.string.preference_key_autolight
import org.ligi.passandroid.R.string.preference_key_condensed
@ -11,12 +11,12 @@ import java.io.File
class AndroidSettings(val context: Context) : Settings {
internal val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
private val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
override fun getSortOrder(): PassSortOrder {
val key = context.getString(R.string.preference_key_sort)
val stringValue = sharedPreferences.getString(key, "0")
val id = Integer.valueOf(stringValue)!!
val id = Integer.valueOf(stringValue!!)
return PassSortOrder.values().first { it.int == id }
}

View file

@ -24,7 +24,7 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
tryToFindDate(pass)
}
fun tryToFindDate(pass: PassImpl) {
private fun tryToFindDate(pass: PassImpl) {
if (pass.calendarTimespan == null) {
val foundDate = pass.fields.filter { "date" == it.key }.map {
@ -44,11 +44,11 @@ class ApplePassbookQuirkCorrector(val tracker: Tracker) {
}
private fun getPassFieldForKey(pass: PassImpl, key: String): PassField? {
return pass.fields.firstOrNull() { it.key != null && it.key == key }
return pass.fields.firstOrNull { it.key != null && it.key == key }
}
private fun getPassFieldThatMatchesLabel(pass: PassImpl, matcher: String): PassField? {
return pass.fields.firstOrNull() {
return pass.fields.firstOrNull {
val label = it.label
label != null && label.matches(matcher.toRegex())
}

View file

@ -1,8 +1,8 @@
package org.ligi.passandroid.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;
import java.io.DataInputStream;
@ -10,9 +10,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import org.ligi.passandroid.App;
@VisibleForTesting
public class AppleStylePassTranslation extends HashMap<String, String> {
public String translate(String key) {
@ -63,6 +61,7 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
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");
}
@ -74,7 +73,6 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
}
return new String(fileData);
} catch (Throwable e) {
App.Companion.getTracker().trackException("problem_reading_translation", e, false);
e.printStackTrace();
return null;
}

View file

@ -14,7 +14,7 @@ open class PassClassifier(val topicByIdMap: MutableMap<String, String>, private
}
fun moveToTopic(pass: Pass, newTopic: String) {
topicByIdMap.put(pass.id, newTopic)
topicByIdMap[pass.id] = newTopic
processDataChange()
}
@ -24,7 +24,7 @@ open class PassClassifier(val topicByIdMap: MutableMap<String, String>, private
}
fun getPassListByTopic(topic: String): List<Pass> {
return topicByIdMap.filter { it.value.equals(topic) }.map { passStore.getPassbookForId(it.key) }.filterNotNull()
return topicByIdMap.filter { it.value == topic }.map { passStore.getPassbookForId(it.key) }.filterNotNull()
}
fun getTopic(pass: Pass, default: String): String {
@ -38,7 +38,7 @@ open class PassClassifier(val topicByIdMap: MutableMap<String, String>, private
}
if (!default.isEmpty()) {
topicByIdMap.put(id, default)
topicByIdMap[id] = default
processDataChange()
}
return default

View file

@ -1,10 +1,13 @@
package org.ligi.passandroid.model
import kotlinx.coroutines.channels.BroadcastChannel
import org.ligi.passandroid.model.pass.Pass
import java.io.File
interface PassStore {
val updateChannel: BroadcastChannel<PassStoreUpdateEvent>
fun save(pass: Pass)
fun getPassbookForId(id: String): Pass?

View file

@ -4,7 +4,7 @@ import org.ligi.passandroid.model.comparator.PassSortOrder
import org.ligi.passandroid.model.pass.Pass
import java.util.*
class PassStoreProjection(private val passStore: PassStore, val topic: String, private val passSortOrder: PassSortOrder? = null) {
class PassStoreProjection(private val passStore: PassStore, private val topic: String, private val passSortOrder: PassSortOrder? = null) {
var passList: List<Pass> = ArrayList()
private set

View file

@ -1,18 +1,12 @@
package org.ligi.passandroid.model
import android.annotation.TargetApi
import android.content.SharedPreferences
import android.os.Build
import org.ligi.passandroid.Tracker
import java.util.*
class PastLocationsStore constructor(private val sharedPreferences: SharedPreferences, private val tracker: Tracker) {
fun putLocation(path: String) {
if (Build.VERSION.SDK_INT < 11) {
// feature not available for these versions
return
}
val pastLocations = sharedPreferences.getStringSet(KEY_PAST_LOCATIONS, HashSet<String>())
if (pastLocations!!.size >= MAX_ELEMENTS) {
@ -39,17 +33,11 @@ class PastLocationsStore constructor(private val sharedPreferences: SharedPrefer
// feature not available for these versions
val locations: Set<String>
@TargetApi(11)
get() {
if (Build.VERSION.SDK_INT < 11) {
return HashSet()
}
return sharedPreferences.getStringSet(KEY_PAST_LOCATIONS, HashSet<String>())
}
get() = sharedPreferences.getStringSet(KEY_PAST_LOCATIONS, emptySet<String>())?: emptySet()
companion object {
val KEY_PAST_LOCATIONS = "past_locations"
val MAX_ELEMENTS = 5
const val KEY_PAST_LOCATIONS = "past_locations"
const val MAX_ELEMENTS = 5
}
}

View file

@ -9,7 +9,7 @@ class DirectionAwarePassByTimeComparator(private val direction: Int) : PassByTim
}
companion object {
val DIRECTION_DESC = -1
val DIRECTION_ASC = 1
const val DIRECTION_DESC = -1
const val DIRECTION_ASC = 1
}
}

View file

@ -7,9 +7,9 @@ import java.util.*
open class PassByTimeComparator : Comparator<Pass> {
override fun compare(lhs: Pass, rhs: Pass): Int {
return calculateCompareForNullValues(lhs, rhs, { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
return calculateCompareForNullValues(lhs, rhs) { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
return@calculateCompareForNullValues leftDate.compareTo(rightDate)
})
}
}
protected fun calculateCompareForNullValues(lhs: Pass, rhs: Pass, foo: (leftDate: ZonedDateTime, rightDate: ZonedDateTime) -> Int): Int {

View file

@ -11,10 +11,10 @@ class PassByTypeFirstAndTimeSecondComparator : Comparator<Pass> {
override fun compare(lhs: Pass, rhs: Pass): Int {
val compareResult = lhs.type.compareTo(rhs.type)
if (compareResult != 0) {
return compareResult
return if (compareResult != 0) {
compareResult
} else {
return passByTimeComparator.compare(lhs, rhs)
passByTimeComparator.compare(lhs, rhs)
}
}
}

View file

@ -9,12 +9,10 @@ enum class PassSortOrder constructor(val int: Int) {
TYPE(1),
DATE_DIFF(2);
fun toComparator(): Comparator<Pass> {
when (this) {
TYPE -> return PassByTypeFirstAndTimeSecondComparator()
DATE_DESC -> return DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_DESC)
DATE_DIFF -> return PassTemporalDistanceComparator()
DATE_ASC -> return DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_ASC)
}
fun toComparator(): Comparator<Pass> = when (this) {
TYPE -> PassByTypeFirstAndTimeSecondComparator()
DATE_DESC -> DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_DESC)
DATE_DIFF -> PassTemporalDistanceComparator()
DATE_ASC -> DirectionAwarePassByTimeComparator(DirectionAwarePassByTimeComparator.DIRECTION_ASC)
}
}

View file

@ -8,11 +8,11 @@ import org.threeten.bp.ZonedDateTime
class PassTemporalDistanceComparator : PassByTimeComparator() {
override fun compare(lhs: Pass, rhs: Pass): Int {
return calculateCompareForNullValues(lhs, rhs, { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
return calculateCompareForNullValues(lhs, rhs) { leftDate: ZonedDateTime, rightDate: ZonedDateTime ->
val durationLeft = Duration.between(LocalDateTime.now(), leftDate).abs()
val durationRight = Duration.between(LocalDateTime.now(), rightDate).abs()
return@calculateCompareForNullValues durationLeft.compareTo(durationRight)
})
}
}
}

View file

@ -2,19 +2,21 @@ package org.ligi.passandroid.model.pass
import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import com.github.salomonbrys.kodein.instance
import org.ligi.passandroid.App
import com.squareup.moshi.JsonClass
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.functions.generateBitmapDrawable
import org.ligi.tracedroid.logging.Log
import java.util.*
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) {
@JsonClass(generateAdapter = true)
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) : KoinComponent {
val tracker: Tracker by inject ()
var alternativeText: String? = null
fun getBitmap(resources: Resources): BitmapDrawable? {
val tracker: Tracker = App.kodein.instance()
if (message == null) {
// no message -> no barcode
tracker.trackException("No Barcode in pass - strange", false)

View file

@ -1,7 +1,7 @@
package org.ligi.passandroid.model.pass
import android.graphics.Bitmap
import android.support.annotation.StringDef
import androidx.annotation.StringDef
import org.ligi.passandroid.model.PassBitmapDefinitions.*
import org.ligi.passandroid.model.PassStore

View file

@ -8,6 +8,7 @@ enum class PassBarCodeFormat {
CODE_39,
CODE_128,
DATA_MATRIX,
EAN_8,
EAN_13,
ITF,
PDF_417,
@ -23,6 +24,7 @@ enum class PassBarCodeFormat {
CODE_39 -> BarcodeFormat.CODE_39
CODE_128 -> BarcodeFormat.CODE_128
DATA_MATRIX -> BarcodeFormat.DATA_MATRIX
EAN_8 -> BarcodeFormat.EAN_8
EAN_13 -> BarcodeFormat.EAN_13
ITF -> BarcodeFormat.ITF
PDF_417 -> BarcodeFormat.PDF_417

View file

@ -1,8 +1,10 @@
package org.ligi.passandroid.model.pass
import android.content.res.Resources
import android.support.annotation.StringRes
import androidx.annotation.StringRes
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class PassField(var key: String?, var label: String?, var value: String?, var hide: Boolean) {
fun toHtmlSnippet(): String {

View file

@ -2,6 +2,7 @@ package org.ligi.passandroid.model.pass
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.squareup.moshi.JsonClass
import com.squareup.moshi.JsonQualifier
import org.ligi.passandroid.model.PassStore
import org.threeten.bp.ZonedDateTime
@ -10,6 +11,7 @@ import java.io.FileInputStream
import java.io.FileNotFoundException
import java.util.*
@JsonClass(generateAdapter = true)
class PassImpl(override val id: String) : Pass {
@ -37,7 +39,10 @@ class PassImpl(override val id: String) : Pass {
return field
}
@JsonClass(generateAdapter = true)
class TimeRepeat(val offset: Int, val count: Int)
@JsonClass(generateAdapter = true)
class TimeSpan(val from: ZonedDateTime? = null, val to: ZonedDateTime? = null, val repeat: TimeRepeat? = null)
override var validTimespans: List<TimeSpan> = ArrayList()
@ -59,13 +64,13 @@ class PassImpl(override val id: String) : Pass {
override var passIdent: String? = null
override fun getBitmap(passStore: PassStore, @Pass.PassBitmap passBitmap: String): Bitmap? {
try {
return try {
val file = File(passStore.getPathForID(id), passBitmap + FILETYPE_IMAGES)
return BitmapFactory.decodeStream(FileInputStream(file))
BitmapFactory.decodeStream(FileInputStream(file))
} catch (expectedInSomeCases_willJustReturnNull: FileNotFoundException) {
return null
null
} catch (e: OutOfMemoryError) {
return null
null
}
}
@ -84,7 +89,7 @@ class PassImpl(override val id: String) : Pass {
}
companion object {
val FILETYPE_IMAGES = ".png"
const val FILETYPE_IMAGES = ".png"
}
}

View file

@ -1,5 +1,8 @@
package org.ligi.passandroid.model.pass
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
class PassLocation {
var name: String? = null

View file

@ -69,7 +69,7 @@ class PassPrintDocumentAdapter(private val context: Context, private val pass: P
val centerPaint = Paint()
centerPaint.textAlign = Paint.Align.CENTER
canvas.drawText(pass.description, canvas.width / 2f, centerPaint.textSize, centerPaint)
canvas.drawText(pass.description!!, canvas.width / 2f, centerPaint.textSize, centerPaint)
var currentBottom = centerPaint.textSize * 3
val barCode = pass.barCode
@ -83,14 +83,14 @@ class PassPrintDocumentAdapter(private val context: Context, private val pass: P
val destRect = Rect(0, 0, (bitmap.width * ratio).toInt(), (bitmap.height * ratio).toInt())
destRect.offset(((canvas.width - destRect.width()) / 2).toInt(), currentBottom.toInt())
destRect.offset((canvas.width - destRect.width()) / 2, currentBottom.toInt())
currentBottom += destRect.bottom
canvas.drawBitmap(bitmap, srcRect, destRect, centerPaint)
if (barCode.alternativeText != null) {
canvas.drawText(barCode.alternativeText, destRect.centerX().toFloat(), destRect.bottom.toFloat() + 7 + centerPaint.textSize, centerPaint)
canvas.drawText(barCode.alternativeText!!, destRect.centerX().toFloat(), destRect.bottom.toFloat() + 7 + centerPaint.textSize, centerPaint)
currentBottom += 7 + centerPaint.textSize * 2
}
}

View file

@ -7,8 +7,8 @@ import android.graphics.Color
import org.json.JSONException
import org.json.JSONObject
import org.ligi.kaxt.parseColor
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.functions.getHumanCategoryString
import org.ligi.passandroid.functions.readJSONSafely
import org.ligi.passandroid.model.ApplePassbookQuirkCorrector
@ -26,116 +26,116 @@ import java.util.*
object AppleStylePassReader {
fun read(passFile: File, language: String, context: Context): Pass? {
fun read(passFile: File, language: String, context: Context, tracker: Tracker): Pass? {
val translation = AppleStylePassTranslation()
val pass = PassImpl(passFile.name)
var pass_json: JSONObject? = null
var passJSON: JSONObject? = null
val localized_path = findLocalizedPath(passFile, language)
val localizedPath = findLocalizedPath(passFile, language, tracker)
if (localized_path != null) {
val file = File(localized_path, "pass.strings")
if (localizedPath != null) {
val file = File(localizedPath, "pass.strings")
translation.loadFromFile(file)
}
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_ICON)
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_LOGO)
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_STRIP)
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_THUMBNAIL)
copyBitmapFile(passFile, localized_path, PassBitmapDefinitions.BITMAP_FOOTER)
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_ICON)
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_LOGO)
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_STRIP)
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_THUMBNAIL)
copyBitmapFile(passFile, localizedPath, PassBitmapDefinitions.BITMAP_FOOTER)
val file = File(passFile, "pass.json")
try {
val plainJsonString = AppleStylePassTranslation.readFileAsStringGuessEncoding(file)
pass_json = readJSONSafely(plainJsonString)
passJSON = readJSONSafely(plainJsonString)
} catch (e: Exception) {
Log.i("PassParse Exception " + e)
Log.i("PassParse Exception: $e")
}
if (pass_json == null) {
if (passJSON == null) {
// I had got a strange passbook with UCS-2 which could not be parsed before
// was searching for a auto-detection, but could not find one with support for this encoding
// and the right license
for (charset in Charset.availableCharsets().values) {
try {
val json_str = file.bufferedReader(charset).readText()
pass_json = readJSONSafely(json_str)
val json = file.bufferedReader(charset).readText()
passJSON = readJSONSafely(json)
} catch (ignored: Exception) {
// we try with next charset
}
if (pass_json != null) {
if (passJSON != null) {
break
}
}
}
if (pass_json == null) {
if (passJSON == null) {
Log.w("could not load pass.json from passcode ")
App.tracker.trackEvent("problem_event", "pass", "without_pass_json", null)
tracker.trackEvent("problem_event", "pass", "without_pass_json", null)
return null
}
try {
val barcode_json = pass_json.getBarcodeJson()
if (barcode_json != null) {
val barcodeFormatString = barcode_json.getString("format")
val barcodeJSON = passJSON.getBarcodeJson()
if (barcodeJSON != null) {
val barcodeFormatString = barcodeJSON.getString("format")
App.tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
val barcodeFormat = BarCode.getFormatFromString(barcodeFormatString)
val barCode = BarCode(barcodeFormat, barcode_json.getString("message"))
val barCode = BarCode(barcodeFormat, barcodeJSON.getString("message"))
pass.barCode = barCode
if (barcode_json.has("altText")) {
pass.barCode!!.alternativeText = barcode_json.getString("altText")
if (barcodeJSON.has("altText")) {
pass.barCode!!.alternativeText = barcodeJSON.getString("altText")
}
}
// TODO should check a bit more with barcode here - this can be dangerous
} catch (ignored: Exception) {
}
if (pass_json.has("relevantDate")) {
if (passJSON.has("relevantDate")) {
try {
pass.calendarTimespan = PassImpl.TimeSpan(from = ZonedDateTime.parse(pass_json.getString("relevantDate")))
pass.calendarTimespan = PassImpl.TimeSpan(from = ZonedDateTime.parse(passJSON.getString("relevantDate")))
} catch (e: JSONException) {
// be robust when it comes to bad dates - had a RL crash with "2013-12-25T00:00-57:00" here
// OK then we just have no date here
App.tracker.trackException("problem parsing relevant date", e, false)
tracker.trackException("problem parsing relevant date", e, false)
} catch (e: DateTimeException) {
App.tracker.trackException("problem parsing relevant date", e, false)
tracker.trackException("problem parsing relevant date", e, false)
}
}
if (pass_json.has("expirationDate")) {
if (passJSON.has("expirationDate")) {
try {
pass.validTimespans = listOf(PassImpl.TimeSpan(to = ZonedDateTime.parse(pass_json.getString("expirationDate"))))
pass.validTimespans = listOf(PassImpl.TimeSpan(to = ZonedDateTime.parse(passJSON.getString("expirationDate"))))
} catch (e: JSONException) {
// be robust when it comes to bad dates - had a RL crash with "2013-12-25T00:00-57:00" here
// OK then we just have no date here
App.tracker.trackException("problem parsing expiration date", e, false)
tracker.trackException("problem parsing expiration date", e, false)
} catch (e: DateTimeException) {
App.tracker.trackException("problem parsing expiration date", e, false)
tracker.trackException("problem parsing expiration date", e, false)
}
}
pass.serial = readJsonSafeAsOptional(pass_json, "serialNumber")
pass.authToken = readJsonSafeAsOptional(pass_json, "authenticationToken")
pass.webServiceURL = readJsonSafeAsOptional(pass_json, "webServiceURL")
pass.passIdent = readJsonSafeAsOptional(pass_json, "passTypeIdentifier")
pass.serial = readJsonSafeAsOptional(passJSON, "serialNumber")
pass.authToken = readJsonSafeAsOptional(passJSON, "authenticationToken")
pass.webServiceURL = readJsonSafeAsOptional(passJSON, "webServiceURL")
pass.passIdent = readJsonSafeAsOptional(passJSON, "passTypeIdentifier")
val locations = ArrayList<PassLocation>()
try {
val locations_json = pass_json.getJSONArray("locations")
for (i in 0..locations_json.length() - 1) {
val obj = locations_json.getJSONObject(i)
val locationsJSON = passJSON.getJSONArray("locations")
for (i in 0 until locationsJSON.length()) {
val obj = locationsJSON.getJSONObject(i)
val location = PassLocation()
location.lat = obj.getDouble("latitude")
@ -153,13 +153,13 @@ object AppleStylePassReader {
pass.locations = locations
readJsonSafe(pass_json, "backgroundColor", object : JsonStringReadCallback {
readJsonSafe(passJSON, "backgroundColor", object : JsonStringReadCallback {
override fun onString(string: String) {
pass.accentColor = string.parseColor(Color.BLACK)
}
})
readJsonSafe(pass_json, "description", object : JsonStringReadCallback {
readJsonSafe(passJSON, "description", object : JsonStringReadCallback {
override fun onString(string: String) {
pass.description = translation.translate(string)
}
@ -169,22 +169,22 @@ object AppleStylePassReader {
// try to find in a predefined set of tickets
PassDefinitions.TYPE_TO_NAME.forEach {
if (pass_json!!.has(it.value)) {
if (passJSON.has(it.value)) {
pass.type = it.key
}
}
try {
val type = PassDefinitions.TYPE_TO_NAME[pass.type]
val type_json = pass_json.getJSONObject(type)
if (type_json != null) {
val typeJSON = passJSON.getJSONObject(type)
if (typeJSON != null) {
val fieldList: ArrayList<PassField> = ArrayList()
addFields(fieldList, type_json, "primaryFields", translation)
addFields(fieldList, type_json, "headerFields", translation)
addFields(fieldList, type_json, "secondaryFields", translation)
addFields(fieldList, type_json, "auxiliaryFields", translation)
addFields(fieldList, type_json, "backFields", translation, hide = true)
addFields(fieldList, typeJSON, "primaryFields", translation)
addFields(fieldList, typeJSON, "headerFields", translation)
addFields(fieldList, typeJSON, "secondaryFields", translation)
addFields(fieldList, typeJSON, "auxiliaryFields", translation)
addFields(fieldList, typeJSON, "backFields", translation, hide = true)
fieldList.add(PassField("", context.getString(R.string.type), context.getString(getHumanCategoryString(pass.type)), false))
pass.fields = fieldList
@ -195,13 +195,13 @@ object AppleStylePassReader {
try {
pass.creator = pass_json.getString("organizationName")
App.tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
pass.creator = passJSON.getString("organizationName")
tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
} catch (ignored: JSONException) {
// ok - we have no organisation - big deal ..-)
}
ApplePassbookQuirkCorrector(App.tracker).correctQuirks(pass)
ApplePassbookQuirkCorrector(tracker).correctQuirks(pass)
return pass
}
@ -220,7 +220,7 @@ object AppleStylePassReader {
private fun addFields(list: ArrayList<PassField>, type_json: JSONObject, fieldsName: String, translation: AppleStylePassTranslation, hide: Boolean = false) {
try {
val jsonArray = type_json.getJSONArray(fieldsName)
for (i in 0..jsonArray.length() - 1) {
for (i in 0 until jsonArray.length()) {
try {
val jsonObject = jsonArray.getJSONObject(i)
val field = PassField(key = getField(jsonObject, "key", translation),
@ -230,29 +230,28 @@ object AppleStylePassReader {
list.add(field)
} catch (e: JSONException) {
Log.w("could not process PassField from JSON for $fieldsName cause:$e")
Log.w("could not process PassField from JSON for $fieldsName cause: $e")
}
}
} catch (e: JSONException) {
Log.w("could not process PassFields $fieldsName from JSON$e")
Log.w("could not process PassFields $fieldsName from JSON: $e")
}
}
private fun findLocalizedPath(path: File, language: String): String? {
val localized = File(path, language + ".lproj")
private fun findLocalizedPath(path: File, language: String, tracker: Tracker): String? {
val localized = File(path, "$language.lproj")
if (localized.exists() && localized.isDirectory) {
App.tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
return localized.path
}
val fallback = File(path, "en.lproj")
if (fallback.exists() && fallback.isDirectory) {
App.tracker.trackEvent("measure_event", "pass", "en_lproj", null)
tracker.trackEvent("measure_event", "pass", "en_lproj", null)
return fallback.path
}
@ -282,7 +281,6 @@ object AppleStylePassReader {
} catch (e: JSONException) {
// some passes just do not have the field
}
}
}
@ -294,7 +292,6 @@ object AppleStylePassReader {
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@ -329,6 +326,4 @@ object AppleStylePassReader {
}
return null
}
}

View file

@ -21,47 +21,47 @@ object PassReader {
try {
val plainJsonString = file.bufferedReader().readText()
val pass_json = readJSONSafely(plainJsonString)!!
val passJSON = readJSONSafely(plainJsonString)!!
if (pass_json.has("what")) {
val what_json = pass_json.getJSONObject("what")
pass.description = what_json.getString("description")
if (passJSON.has("what")) {
val whatJSON = passJSON.getJSONObject("what")
pass.description = whatJSON.getString("description")
}
if (pass_json.has("meta")) {
val meta_json = pass_json.getJSONObject("meta")
pass.type = PassDefinitions.NAME_TO_TYPE[meta_json.getString("type")] ?: PassType.GENERIC
pass.creator = meta_json.getString("organisation")
pass.app = meta_json.getString("app")
if (passJSON.has("meta")) {
val metaJSON = passJSON.getJSONObject("meta")
pass.type = PassDefinitions.NAME_TO_TYPE[metaJSON.getString("type")] ?: PassType.GENERIC
pass.creator = metaJSON.getString("organisation")
pass.app = metaJSON.getString("app")
}
if (pass_json.has("ui")) {
val ui_json = pass_json.getJSONObject("ui")
pass.accentColor = Color.parseColor(ui_json.getString("bgColor"))
if (passJSON.has("ui")) {
val uiJSON = passJSON.getJSONObject("ui")
pass.accentColor = Color.parseColor(uiJSON.getString("bgColor"))
}
if (pass_json.has("barcode")) {
val barcode_json = pass_json.getJSONObject("barcode")
val barcodeFormatString = barcode_json.getString("type")
if (passJSON.has("barcode")) {
val barcodeJSON = passJSON.getJSONObject("barcode")
val barcodeFormatString = barcodeJSON.getString("type")
val barcodeFormat = BarCode.getFormatFromString(barcodeFormatString)
val barCode = BarCode(barcodeFormat, barcode_json.getString("message"))
val barCode = BarCode(barcodeFormat, barcodeJSON.getString("message"))
pass.barCode = barCode
if (barcode_json.has("altText")) {
barCode.alternativeText = barcode_json.getString("altText")
if (barcodeJSON.has("altText")) {
barCode.alternativeText = barcodeJSON.getString("altText")
}
}
if (pass_json.has("when")) {
val dateTime = pass_json.getJSONObject("when").getString("dateTime")
if (passJSON.has("when")) {
val dateTime = passJSON.getJSONObject("when").getString("dateTime")
pass.calendarTimespan = PassImpl.TimeSpan()
pass.calendarTimespan = PassImpl.TimeSpan(from = ZonedDateTime.parse(dateTime))
}
} catch (e: Exception) {
Log.i("PassParse Exception " + e)
Log.i("PassParse Exception: $e")
}
return pass

View file

@ -0,0 +1,61 @@
package org.ligi.passandroid.scan
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.view.View.GONE
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_scan.*
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.ligi.passandroid.R
import org.ligi.passandroid.scan.events.DirectoryProcessed
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
import org.ligi.passandroid.scan.events.ScanFinished
import org.ligi.passandroid.ui.PassAndroidActivity
class PassScanActivity : PassAndroidActivity() {
private val progressChannelProvider: PassScanEventChannelProvider by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scan)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
lifecycleScope.launch {
for (event in progressChannelProvider.channel.openSubscription()) {
when (event) {
is DirectoryProcessed -> progress_text.text = event.dir
is ScanFinished -> {
progress_container.visibility = GONE
val message = getString(R.string.scan_finished_dialog_text, event.foundPasses.size)
AlertDialog.Builder(this@PassScanActivity)
.setTitle(R.string.scan_finished_dialog_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok) { _, _ ->
finish()
}
.show()
}
}
}
}
val intent = Intent(this, SearchPassesIntentService::class.java)
startService(intent)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
android.R.id.home -> {
finish()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View file

@ -0,0 +1,179 @@
package org.ligi.passandroid.scan
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.functions.fromURI
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.PastLocationsStore
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.scan.events.DirectoryProcessed
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
import org.ligi.passandroid.scan.events.ScanFinished
import org.ligi.passandroid.ui.PassListActivity
import org.ligi.passandroid.ui.UnzipPassController
import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec
import org.ligi.tracedroid.logging.Log
import java.io.File
import java.util.*
private const val NOTIFICATION_CHANNEL_ID = "transactions"
class SearchPassesIntentService : LifecycleService() {
private val notifyManager by lazy {
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
private var shouldFinish: Boolean = false
private var progressNotificationBuilder: NotificationCompat.Builder? = null
private var findNotificationBuilder: NotificationCompat.Builder? = null
private var foundList = ArrayList<Pass>()
private var lastProgressUpdate: Long = 0
private val passStore: PassStore by inject()
private val tracker: Tracker by inject()
private val progressChannelProvider: PassScanEventChannelProvider by inject()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
lifecycleScope.launch {
foundList.clear()
if (Build.VERSION.SDK_INT > 25) {
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, "PassAndroid Pass scan", NotificationManager.IMPORTANCE_DEFAULT)
channel.description = "Notifications when PassAndroid is scanning for passes"
notifyManager.createNotificationChannel(channel)
}
val pendingIntent = PendingIntent.getActivity(applicationContext, 1, Intent(baseContext, PassListActivity::class.java), 0)
progressNotificationBuilder = NotificationCompat.Builder(this@SearchPassesIntentService, NOTIFICATION_CHANNEL_ID).setContentTitle(getString(R.string.scanning_for_passes))
.setSmallIcon(R.drawable.ic_refresh)
.setOngoing(true)
.setContentIntent(pendingIntent)
.setProgress(1, 1, true)
findNotificationBuilder = NotificationCompat.Builder(this@SearchPassesIntentService, NOTIFICATION_CHANNEL_ID)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
val preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
for (path in PastLocationsStore(preferences, tracker).locations) {
searchIn(File(path), false)
}
// note to future_me: yea one thinks we only need to search root here, but root was /system for me and so
// did not contain "/SDCARD" #dontoptimize
// on my phone:
// | /mnt/sdcard/Download << this looks kind of stupid as we do /mnt/sdcard later and hence will go here twice
// but this helps finding passes in Downloads ( where they are very often ) fast - some users with lots of files on the SDCard gave
// up the refreshing of passes as it took so long to traverse all files on the SDCard
// one could think about not going there anymore but a short look at this showed that it seems cost more time to check than what it gains
// in download there are mostly single files in a flat dir - no huge tree behind this imho
searchIn(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), true)
// | /system
searchIn(Environment.getRootDirectory(), true)
// | /mnt/sdcard
searchIn(Environment.getExternalStorageDirectory(), true)
// | /cache
searchIn(Environment.getDownloadCacheDirectory(), true)
// | /data
searchIn(Environment.getDataDirectory(), true)
notifyManager.cancel(PROGRESS_NOTIFICATION_ID)
progressChannelProvider.channel.send(ScanFinished(foundList))
}
return START_STICKY
}
/**
* recursive voyage starting at path to find files named .pkpass
*/
private suspend fun searchIn(path: File, recursive: Boolean) {
if (System.currentTimeMillis() - lastProgressUpdate > 1000) {
lastProgressUpdate = System.currentTimeMillis()
val msg = path.toString()
progressChannelProvider.channel.send(DirectoryProcessed(msg))
progressNotificationBuilder!!.setContentText(msg)
notifyManager.notify(PROGRESS_NOTIFICATION_ID, progressNotificationBuilder!!.build())
}
val files = path.listFiles()
if (files == null || files.isEmpty()) {
// no files here
return
}
for (file in files) {
if (shouldFinish) {
return
}
Log.i("search " + file.absoluteFile)
if (recursive && file.isDirectory) {
searchIn(file, true)
} else if (file.name.toLowerCase().endsWith(".pkpass") || file.name.toLowerCase().endsWith(".espass")) {
Log.i("found" + file.absolutePath)
try {
val ins = fromURI(baseContext, Uri.parse("file://" + file.absolutePath), tracker)
val onSuccessCallback = SearchSuccessCallback(baseContext,
passStore,
foundList,
findNotificationBuilder!!,
file,
notifyManager)
val spec = InputStreamUnzipControllerSpec(ins!!,
baseContext,
passStore,
onSuccessCallback,
object : UnzipPassController.FailCallback {
override fun fail(reason: String) {
Log.i("fail", reason)
}
})
UnzipPassController.processInputStream(spec)
} catch (e: Exception) {
tracker.trackException("Error in SearchPassesIntentService", e, false)
}
}
}
}
override fun onDestroy() {
shouldFinish = true
super.onDestroy()
}
companion object {
const val PROGRESS_NOTIFICATION_ID = 1
const val FOUND_NOTIFICATION_ID = 2
const val REQUEST_CODE = 1
}
}

View file

@ -1,15 +1,19 @@
package org.ligi.passandroid.ui
package org.ligi.passandroid.scan
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.support.v4.app.NotificationCompat
import androidx.core.app.NotificationCompat
import org.ligi.passandroid.R
import org.ligi.passandroid.model.PassBitmapDefinitions
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.scan.SearchPassesIntentService
import org.ligi.passandroid.ui.PassViewActivity
import org.ligi.passandroid.ui.PassViewActivityBase
import org.ligi.passandroid.ui.UnzipPassController
import org.threeten.bp.ZonedDateTime
import java.io.File

View file

@ -0,0 +1,8 @@
package org.ligi.passandroid.scan.events
import org.ligi.passandroid.model.pass.Pass
sealed class PassScanEvent
data class DirectoryProcessed(val dir: String) : PassScanEvent()
data class ScanFinished(val foundPasses: List<Pass>) : PassScanEvent()

View file

@ -0,0 +1,8 @@
package org.ligi.passandroid.scan.events
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import org.ligi.passandroid.scan.events.PassScanEvent
class PassScanEventChannelProvider {
val channel = ConflatedBroadcastChannel<PassScanEvent>()
}

View file

@ -0,0 +1,47 @@
package org.ligi.passandroid.ui
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.ui.quirk_fix.OpenIphoneWebView
class AlertFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val tracker = (activity!! as PassAndroidActivity).tracker
val builder = AlertDialog.Builder(activity)
builder.setTitle("Workaround failed")
builder.setMessage("The URL PassAndroid tried to work around failed :-( some companies just send PassBooks to Apple Devices - this was an attempt to workaround this." + "Unfortunately it failed - perhaps there where changes on the serverside - you can open the site with your browser now - to see it in PassAndroid in future again it would help if you can send me the pass")
builder.setPositiveButton("Browser") { dialog, which ->
tracker.trackException(activity!!.localClassName + "with invalid activity", false)
val intent = Intent(activity, OpenIphoneWebView::class.java)
intent.data = activity!!.intent.data
startActivity(intent)
}
builder.setNeutralButton("send") { dialog, which ->
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_SUBJECT, "PassAndroid: URLRewrite Problem")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("ligi@ligi.de"))
if(activity!!.intent.data != null)
{
intent.putExtra(Intent.EXTRA_TEXT, activity!!.intent.data!!.toString())
}
else{
intent.putExtra(Intent.EXTRA_TEXT, "null")
}
intent.type = "text/plain"
}
builder.setNegativeButton("cancle") { dialog, which -> activity!!.finish() }
val dialog = builder.create()
dialog.show()
}
}

View file

@ -4,14 +4,15 @@ import android.app.Activity
import android.view.View
import android.view.View.*
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.barcode.view.*
import org.ligi.kaxt.getSmallestSide
import org.ligi.passandroid.model.pass.BarCode
internal class BarcodeUIController(val rootView: View, private val barCode: BarCode?, activity: Activity, private val passViewHelper: PassViewHelper) {
internal class BarcodeUIController(private val rootView: View, private val barCode: BarCode?, activity: Activity, private val passViewHelper: PassViewHelper) {
fun getBarcodeView() = rootView.barcode_img
fun getBarcodeView(): ImageView = rootView.barcode_img
private var currentBarcodeWidth: Int = 0

View file

@ -22,10 +22,12 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
progressDialog.show()
tracker.trackEvent("quirk_fix", "unpack_attempt", intent.data.host, null)
if (intent?.data != null) {
progressDialog.show()
tracker.trackEvent("quirk_fix", "unpack_attempt", intent?.data?.host, null)
DownloadExtractAndStartImportTask().execute()
DownloadExtractAndStartImportTask().execute()
}
}
private inner class DownloadExtractAndStartImportTask : AsyncTask<Void, Void, String>() {
@ -34,7 +36,7 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
val client = OkHttpClient()
try {
val requestBuilder = Request.Builder().url(URI(intent.data.toString()).toURL())
val requestBuilder = Request.Builder().url(URI(intent?.data.toString()).toURL())
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
val body = client.newCall(requestBuilder.build()).execute().body()
@ -46,7 +48,7 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
val url = extractURL(bodyString) ?: return null
if (!url.startsWith("http")) {
return intent.data.scheme + "://" + intent.data.host + "/" + url
return intent?.data?.scheme + "://" + intent?.data?.host + "/" + url
}
return url
@ -79,7 +81,7 @@ class ExtractURLAsIphoneActivity : PassAndroidActivity() {
return
}
tracker.trackEvent("quirk_fix", "unpack_success", intent.data.host, null)
tracker.trackEvent("quirk_fix", "unpack_success", intent?.data?.host, null)
val intent = Intent(this@ExtractURLAsIphoneActivity, PassImportActivity::class.java)
intent.data = Uri.parse(s)

View file

@ -2,6 +2,7 @@ package org.ligi.passandroid.ui
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowManager
@ -15,12 +16,19 @@ class FullscreenBarcodeActivity : PassViewActivityBase() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fullscreen_image)
if (Build.VERSION.SDK_INT >= 27) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
}
}
override fun onResume() {
super.onResume()
if (currentPass == null || currentPass.barCode == null) {
if (currentPass.barCode == null) {
Log.w("FullscreenBarcodeActivity in bad state")
finish() // this should never happen, but better safe than sorry
return
@ -47,7 +55,6 @@ class FullscreenBarcodeActivity : PassViewActivityBase() {
* ( reverse orientation / sensor is the problem here ..)
*/
private fun setBestFittingOrientationForBarCode() {
if (currentPass.barCode!!.format!!.isQuadratic()) {
when (requestedOrientation) {
@ -72,9 +79,4 @@ class FullscreenBarcodeActivity : PassViewActivityBase() {
}
}
override fun onAttachedToWindow() {
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
}
}

View file

@ -1,7 +1,7 @@
package org.ligi.passandroid.ui
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
import android.text.Editable
import android.text.Html
import android.text.method.LinkMovementMethod

View file

@ -1,7 +1,7 @@
package org.ligi.passandroid.ui
import android.app.Activity
import android.support.v7.app.AlertDialog
import androidx.appcompat.app.AlertDialog
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
@ -26,16 +26,18 @@ internal class MoveToNewTopicUI(private val context: Activity, private val passS
dialog.dismiss()
}
val newTopicEditText = dialog.findViewById(R.id.new_topic_edit) as EditText
val suggestionButtonContainer= dialog.findViewById(R.id.topic_suggestions_button_container) as ViewGroup
val newTopicEditText = dialog.findViewById<EditText>(R.id.new_topic_edit)
val suggestionButtonContainer = dialog.findViewById<ViewGroup>(R.id.topic_suggestions_button_container)
// we need to do this here so the dialog does not get dismissed
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (newTopicEditText.text.toString().isEmpty()) {
newTopicEditText.error = context.getString(R.string.cannot_be_empty)
newTopicEditText.requestFocus()
} else {
move(newTopicEditText.text.toString())
if (newTopicEditText != null) {
// we need to do this here so the dialog does not get dismissed
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
if (newTopicEditText.text.toString().isEmpty()) {
newTopicEditText.error = context.getString(R.string.cannot_be_empty)
newTopicEditText.requestFocus()
} else {
move(newTopicEditText.text.toString())
}
}
}
@ -43,12 +45,12 @@ internal class MoveToNewTopicUI(private val context: Activity, private val passS
val suggestionTopicStringIds = intArrayOf(R.string.topic_trash, R.string.topic_archive, R.string.topic_new)
suggestionTopicStringIds.map { context.getString(it) }.forEach {
if (it != oldTopic) {
suggestionTopicStringIds.map { context.getString(it) }.forEach { topic ->
if (topic != oldTopic) {
val button = Button(context)
button.text = it
suggestionButtonContainer.addView(button)
button.setOnClickListener { _ -> move(it) }
button.text = topic
suggestionButtonContainer?.addView(button)
button.setOnClickListener { move(topic) }
}
}
}

View file

@ -2,32 +2,36 @@ package org.ligi.passandroid.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import com.google.android.material.appbar.AppBarLayout;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.snackbar.Snackbar;
import android.util.AttributeSet;
import android.view.View;
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
import org.ligi.passandroid.R;
@SuppressWarnings("WeakerAccess")
public class MyShyFABBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
public MyShyFABBehavior() {
}
public MyShyFABBehavior() {}
public MyShyFABBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionsMenu child, View dependency) {
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent,
@NonNull FloatingActionsMenu child,
@NonNull View dependency) {
return dependency instanceof Snackbar.SnackbarLayout || dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionsMenu child, View dependency) {
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent,
@NonNull FloatingActionsMenu child,
@NonNull View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar(child, dependency);
}
@ -40,23 +44,24 @@ public class MyShyFABBehavior extends CoordinatorLayout.Behavior<FloatingActions
} else {
distanceToScroll = (int) (child.getContext().getResources().getDimension(R.dimen.fab_size_normal) + 2 * fabBottomMargin);
}
final float ratio = ViewCompat.getY(dependency) / getToolbarHeight(dependency.getContext());
final float ratio = dependency.getY() / getToolbarHeight(dependency.getContext());
ViewCompat.setTranslationY(child, -distanceToScroll * ratio);
child.setTranslationY(-distanceToScroll * ratio);
}
return false;
}
@Override
public void onDependentViewRemoved(final CoordinatorLayout parent, final FloatingActionsMenu child, final View dependency) {
public void onDependentViewRemoved(@NonNull final CoordinatorLayout parent,
@NonNull final FloatingActionsMenu child,
@NonNull final View dependency) {
super.onDependentViewRemoved(parent, child, dependency);
onDependentViewChanged(parent,child,dependency);
}
private void updateFabTranslationForSnackbar(FloatingActionsMenu child, View dependency) {
final float translationY = ViewCompat.getTranslationY(dependency) - dependency.getHeight();
final float translationYClipped = Math.min(0, translationY);
ViewCompat.setTranslationY(child, translationYClipped);
final float translationY = dependency.getTranslationY() - dependency.getHeight();
child.setTranslationY(Math.min(0, translationY));
}
private int getToolbarHeight(Context context) {

View file

@ -4,7 +4,7 @@ import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.support.v7.app.AlertDialog
import androidx.appcompat.app.AlertDialog
import org.ligi.passandroid.R
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassLocation
@ -15,25 +15,26 @@ import java.net.URLEncoder
fun Activity.showNavigateToLocationsDialog(pass: Pass, finishOnDone: Boolean) {
val locations = pass.locations
if (locations.isEmpty()) {
done(this, finishOnDone)
} else if (locations.size == 1) {
startIntentForLocation(this, locations.first(), pass)
done(this, finishOnDone)
} else if (locations.size > 1) {
val locationDescriptions = arrayOfNulls<String>(locations.size)
var i = 0
for (loc in locations) {
locationDescriptions[i++] = loc.getNameWithFallback(pass)
when {
locations.isEmpty() -> done(this, finishOnDone)
locations.size == 1 -> {
startIntentForLocation(this, locations.first(), pass)
done(this, finishOnDone)
}
AlertDialog.Builder(this).setTitle(this.getString(R.string.choose_location))
.setItems(locationDescriptions) { _, which ->
startIntentForLocation(this, locations[which], pass)
done(this, finishOnDone)
}
.show()
locations.size > 1 -> {
val locationDescriptions = arrayOfNulls<String>(locations.size)
var i = 0
for (loc in locations) {
locationDescriptions[i++] = loc.getNameWithFallback(pass)
}
AlertDialog.Builder(this).setTitle(this.getString(R.string.choose_location))
.setItems(locationDescriptions) { _, which ->
startIntentForLocation(this, locations[which], pass)
done(this, finishOnDone)
}
.show()
}
}
}

View file

@ -1,14 +1,14 @@
package org.ligi.passandroid.ui
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.CardView
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import com.github.salomonbrys.kodein.instance
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.kaxt.startActivityFromClass
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.PassStoreProjection
@ -17,21 +17,23 @@ import org.ligi.passandroid.ui.pass_view_holder.CondensedPassViewHolder
import org.ligi.passandroid.ui.pass_view_holder.PassViewHolder
import org.ligi.passandroid.ui.pass_view_holder.VerbosePassViewHolder
class PassAdapter(private val passListActivity: AppCompatActivity, private val passStoreProjection: PassStoreProjection) : RecyclerView.Adapter<PassViewHolder>() {
class PassAdapter(
private val passListActivity: AppCompatActivity,
private val passStoreProjection: PassStoreProjection
) : RecyclerView.Adapter<PassViewHolder>(), KoinComponent {
val passStore: PassStore = App.kodein.instance()
val settings: Settings = App.kodein.instance()
private val passStore: PassStore by inject ()
private val settings: Settings by inject ()
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PassViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val res = inflater.inflate(R.layout.pass_list_item, viewGroup, false) as CardView
if (settings.isCondensedModeEnabled()) {
return CondensedPassViewHolder(res)
return if (settings.isCondensedModeEnabled()) {
CondensedPassViewHolder(res)
} else {
return VerbosePassViewHolder(res)
VerbosePassViewHolder(res)
}
}
override fun onBindViewHolder(viewHolder: PassViewHolder, position: Int) {

View file

@ -1,20 +1,16 @@
package org.ligi.passandroid.ui
import android.support.v7.app.AppCompatActivity
import com.github.salomonbrys.kodein.instance
import org.greenrobot.eventbus.EventBus
import org.ligi.kaxt.recreateWhenPossible
import org.ligi.passandroid.App
import androidx.appcompat.app.AppCompatActivity
import org.koin.android.ext.android.inject
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
open class PassAndroidActivity : AppCompatActivity() {
val passStore: PassStore = App.kodein.instance()
val settings: Settings = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
val tracker: Tracker = App.kodein.instance()
val passStore: PassStore by inject()
val settings: Settings by inject()
val tracker: Tracker by inject()
private var lastSetNightMode: Int? = null
@ -22,7 +18,7 @@ open class PassAndroidActivity : AppCompatActivity() {
super.onResume()
if (lastSetNightMode != null && lastSetNightMode != settings.getNightMode()) {
recreateWhenPossible()
recreate()
}
lastSetNightMode = settings.getNightMode()
}

View file

@ -3,20 +3,17 @@ package org.ligi.passandroid.ui
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.support.annotation.IdRes
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.ImageView
import com.github.salomonbrys.kodein.instance
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.edit.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.koin.android.ext.android.inject
import org.ligi.kaxt.doAfterEdit
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassRefreshEvent
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.Pass
@ -38,8 +35,7 @@ class PassEditActivity : AppCompatActivity() {
private lateinit var currentPass: PassImpl
private val imageEditHelper by lazy { ImageEditHelper(this, passStore) }
internal val passStore: PassStore = App.kodein.instance()
internal val bus: EventBus = App.kodein.instance()
internal val passStore: PassStore by inject()
private val passViewHelper: PassViewHelper by lazy { PassViewHelper(this) }
@ -56,9 +52,9 @@ class PassEditActivity : AppCompatActivity() {
categoryView.setOnClickListener {
AlertDialog.Builder(this).setItems(R.array.category_edit_options) { _, i ->
when (i) {
0 -> showCategoryPickDialog(this@PassEditActivity, currentPass, bus)
1 -> showColorPickDialog(this@PassEditActivity, currentPass, bus)
2 -> PassEditActivityPermissionsDispatcher.pickImageWithCheck(this@PassEditActivity, ImageEditHelper.REQ_CODE_PICK_ICON)
0 -> showCategoryPickDialog(this@PassEditActivity, currentPass, refreshCallback)
1 -> showColorPickDialog(this@PassEditActivity, currentPass, refreshCallback)
2 -> pickImageWithPermissionCheck(ImageEditHelper.REQ_CODE_PICK_ICON)
}
}.show()
}
@ -84,17 +80,15 @@ class PassEditActivity : AppCompatActivity() {
add_barcode_button.setOnClickListener {
showBarcodeEditDialog(this@PassEditActivity,
bus,
refreshCallback,
this@PassEditActivity.currentPass,
BarCode(PassBarCodeFormat.QR_CODE, UUID.randomUUID().toString().toUpperCase()))
}
}
val refreshCallback = { refresh(currentPass) }
@Subscribe
fun onPassRefresh(event: PassRefreshEvent) {
refresh(currentPass)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@ -103,7 +97,7 @@ class PassEditActivity : AppCompatActivity() {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
PassEditActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
private fun refresh(pass: Pass) {
@ -117,7 +111,7 @@ class PassEditActivity : AppCompatActivity() {
add_barcode_button.visibility = if (pass.barCode == null) View.VISIBLE else View.GONE
val barcodeUIController = BarcodeUIController(window.decorView, pass.barCode, this, passViewHelper)
barcodeUIController.getBarcodeView().setOnClickListener { showBarcodeEditDialog(this@PassEditActivity, bus, currentPass, currentPass.barCode!!) }
barcodeUIController.getBarcodeView().setOnClickListener { showBarcodeEditDialog(this@PassEditActivity, refreshCallback, currentPass, currentPass.barCode!!) }
}
@Pass.PassBitmap
@ -126,14 +120,14 @@ class PassEditActivity : AppCompatActivity() {
val bitmap = currentPass.getBitmap(passStore, imageString)
val addButton = findViewById(add_logo)!!
val addButton = findViewById<Button>(add_logo)!!
addButton.visibility = if (bitmap == null) View.VISIBLE else View.GONE
val listener = View.OnClickListener {
PassEditActivityPermissionsDispatcher.pickImageWithCheck(this@PassEditActivity, requestCode)
pickImageWithPermissionCheck(requestCode)
}
val logoImage = findViewById(logo_img) as ImageView
val logoImage = findViewById<ImageView>(logo_img)
passViewHelper.setBitmapSafe(logoImage, bitmap)
logoImage.setOnClickListener(listener)
addButton.setOnClickListener(listener)
@ -141,13 +135,11 @@ class PassEditActivity : AppCompatActivity() {
override fun onResumeFragments() {
super.onResumeFragments()
bus.register(this)
refresh(currentPass)
}
override fun onPause() {
bus.unregister(this)
passStore.save(currentPass)
passStore.notifyChange()
super.onPause()
@ -159,7 +151,5 @@ class PassEditActivity : AppCompatActivity() {
true
}
else -> super.onOptionsItemSelected(item)
}
}

View file

@ -4,35 +4,41 @@ import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.os.Handler
import android.support.annotation.UiThread
import android.support.v4.content.FileProvider
import android.widget.Toast
import org.ligi.passandroid.App
import androidx.annotation.UiThread
import androidx.core.content.FileProvider
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import java.io.File
internal open class PassExportTaskAndShare(protected val activity: Activity, val inputPath: File) {
internal open class PassExportTaskAndShare(
protected val activity: Activity,
private val inputPath: File
) : KoinComponent {
val tracker: Tracker by inject()
@UiThread
fun execute() {
val file = File(activity.filesDir, "share/share.espass") // important - the FileProvider must be configured for this path
val passExporter = PassExporter(inputPath, file)
val progress_dialog = ProgressDialog(activity)
progress_dialog.setTitle(R.string.preparing_pass)
progress_dialog.setMessage(activity.getString(R.string.please_wait))
progress_dialog.show()
val progressDialog = ProgressDialog(activity)
progressDialog.setTitle(R.string.preparing_pass)
progressDialog.setMessage(activity.getString(R.string.please_wait))
progressDialog.show()
val handler = Handler()
Thread(Runnable {
passExporter.export()
handler.post {
if (!activity.isFinishing && progress_dialog.isShowing) {
progress_dialog.dismiss()
if (!activity.isFinishing && progressDialog.isShowing) {
progressDialog.dismiss()
}
if (passExporter.exception != null) {
App.tracker.trackException("passExporterException", passExporter.exception, false)
Toast.makeText(activity, "could not export pass " + passExporter.exception, Toast.LENGTH_LONG).show()
tracker.trackException("passExporterException", passExporter.exception!!, false)
Toast.makeText(activity, "could not export pass: " + passExporter.exception, Toast.LENGTH_LONG).show()
} else {
val uriForFile = FileProvider.getUriForFile(activity, activity.getString(R.string.authority_fileprovider), file)
val it = Intent(Intent.ACTION_SEND)

View file

@ -1,33 +1,36 @@
package org.ligi.passandroid.ui
import net.lingala.zip4j.core.ZipFile
import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.model.ZipParameters
import net.lingala.zip4j.util.Zip4jConstants
import org.ligi.passandroid.App
import net.lingala.zip4j.model.enums.CompressionLevel
import net.lingala.zip4j.model.enums.CompressionMethod
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.passandroid.Tracker
import java.io.File
class PassExporter(private val inputPath: File, val file: File) {
class PassExporter(private val inputPath: File, val file: File) : KoinComponent {
var exception: Exception? = null
val tracker: Tracker by inject()
fun export() {
try {
file.delete()
file.parentFile.mkdirs()
val zipFile = ZipFile(file)
zipFile.createZipFileFromFolder(inputPath, object : ZipParameters() {
zipFile.createSplitZipFileFromFolder(inputPath, object : ZipParameters() {
init {
isIncludeRootFolder = false
compressionMethod = Zip4jConstants.COMP_DEFLATE
compressionLevel = Zip4jConstants.DEFLATE_LEVEL_NORMAL
compressionMethod = CompressionMethod.DEFLATE
compressionLevel = CompressionLevel.NORMAL
}
}, false, 0)
} catch (exception: Exception) {
exception.printStackTrace()
App.tracker.trackException("when exporting pass to zip", exception, false)
tracker.trackException("when exporting pass to zip", exception, false)
this.exception = exception // we need to take action on the main thread later
file.delete() // prevent zombies from taking over
}

View file

@ -1,15 +1,17 @@
package org.ligi.passandroid.ui
import android.Manifest
import android.app.ProgressDialog
import android.content.DialogInterface
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.github.salomonbrys.kodein.instance
import org.ligi.kaxt.dismissIfShowing
import android.view.View.GONE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_import.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import org.ligi.kaxt.startActivityFromClass
import org.ligi.kaxtui.alert
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.functions.fromURI
@ -22,16 +24,8 @@ import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
class PassImportActivity : AppCompatActivity() {
val tracker: Tracker = App.kodein.instance()
val passStore: PassStore = App.kodein.instance()
private val progressDialog by lazy {
ProgressDialog(this).apply {
setMessage(getString(R.string.please_wait))
setCancelable(false)
}
}
val tracker: Tracker by inject()
val passStore: PassStore by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -42,25 +36,25 @@ class PassImportActivity : AppCompatActivity() {
return
}
progressDialog.show()
doImport(false)
setContentView(R.layout.activity_import)
doImportWithPermissionCheck(false)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
PassImportActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun doImport(withPermission: Boolean) {
Thread({
lifecycleScope.launch(Dispatchers.IO) {
try {
val fromURI = fromURI(this, intent.data)
val fromURI = fromURI(this@PassImportActivity, intent!!.data!!, tracker)
runOnUiThread({
progressDialog.dismissIfShowing()
withContext(Dispatchers.Main) {
progress_container.visibility = GONE
if (fromURI == null) {
finish()
@ -73,7 +67,7 @@ class PassImportActivity : AppCompatActivity() {
val spec = UnzipPassController.InputStreamUnzipControllerSpec(fromURI, application, passStore, null, null)
UnzipPassController.processInputStream(spec)
} else {
UnzipPassDialog.show(fromURI, this, passStore) { path ->
UnzipPassDialog.show(fromURI, this@PassImportActivity, passStore) { path ->
// TODO this is kind of a hack - there should be a better way
val id = path.split("/".toRegex()).dropLastWhile(String::isEmpty).toTypedArray().last()
@ -87,25 +81,20 @@ class PassImportActivity : AppCompatActivity() {
}
}
}
})
}
} catch (e: Exception) {
if (e.message?.contains("Permission") == true && !withPermission) {
PassImportActivityPermissionsDispatcher.doImportWithCheck(this@PassImportActivity, true)
doImportWithPermissionCheck(true)
} else {
tracker.trackException("Error in import", e, false)
}
}
}).start()
}
}
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showDeniedDialog() {
progressDialog.dismissIfShowing()
alert(R.string.error_no_permission_msg, R.string.error_no_permission_title, onOKListener = DialogInterface.OnClickListener { _, _ -> finish() })
progress_container.visibility = GONE
alert(R.string.error_no_permission_msg, R.string.error_no_permission_title, onOK = { finish() })
}
}

View file

@ -3,35 +3,30 @@ package org.ligi.passandroid.ui
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.ProgressDialog
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.view.GravityCompat
import android.support.v4.view.ViewPager
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.app.AlertDialog
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.core.view.GravityCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewpager.widget.ViewPager
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.pass_list.*
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.kaxt.setButton
import kotlinx.coroutines.launch
import org.ligi.kaxt.startActivityFromClass
import org.ligi.kaxt.startActivityFromURL
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.events.ScanFinishedEvent
import org.ligi.passandroid.events.ScanProgressEvent
import org.ligi.passandroid.functions.createAndAddEmptyPass
import org.ligi.passandroid.model.PassStoreProjection
import org.ligi.passandroid.model.State
import org.ligi.passandroid.scan.PassScanActivity
import org.ligi.snackengage.SnackEngage
import org.ligi.snackengage.snacks.BaseSnack
import org.ligi.snackengage.snacks.DefaultRateSnack
@ -51,25 +46,7 @@ class PassListActivity : PassAndroidActivity() {
private val drawerToggle by lazy { ActionBarDrawerToggle(this, drawer_layout, R.string.drawer_open, R.string.drawer_close) }
private val adapter by lazy { PassTopicFragmentPagerAdapter(passStore.classifier, supportFragmentManager) }
private val pd by lazy { ProgressDialog(this) }
@Subscribe(threadMode = ThreadMode.MAIN)
fun onScanProgress(event: ScanProgressEvent) {
if (pd.isShowing) {
pd.setMessage(event.message)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onScanFinished(event: ScanFinishedEvent) {
if (pd.isShowing) {
val message = getString(R.string.scan_finished_dialog_text, event.foundPasses.size)
pd.dismiss()
AlertDialog.Builder(this@PassListActivity).setTitle(R.string.scan_finished_dialog_title).setMessage(message).setPositiveButton(android.R.string.ok, null).show()
}
}
@TargetApi(16)
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
@ -80,26 +57,11 @@ class PassListActivity : PassAndroidActivity() {
@TargetApi(16)
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun scan() {
val intent = Intent(this, SearchPassesIntentService::class.java)
startService(intent)
pd.setTitle(R.string.scan_progressdialog_title)
pd.setMessage(getString(R.string.scan_progressdialog_message))
pd.setCancelable(false)
pd.isIndeterminate = true
pd.setButton(DialogInterface.BUTTON_NEUTRAL, R.string.scan_dialog_send_background_button) {
this@PassListActivity.finish()
}
pd.setButton(DialogInterface.BUTTON_POSITIVE, android.R.string.cancel) {
stopService(intent)
}
pd.show()
}
fun scan() = startActivity(Intent(this, PassScanActivity::class.java))
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
PassListActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
@TargetApi(VERSION_STARTING_TO_SUPPORT_STORAGE_FRAMEWORK)
@ -116,6 +78,7 @@ class PassListActivity : PassAndroidActivity() {
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == OPEN_FILE_READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
val targetIntent = Intent(this, PassImportActivity::class.java)
@ -162,7 +125,7 @@ class PassListActivity : PassAndroidActivity() {
override fun onPageSelected(position: Int) {
State.lastSelectedTab = position
supportInvalidateOptionsMenu()
invalidateOptionsMenu()
}
override fun onPageScrollStateChanged(state: Int) {
@ -171,7 +134,7 @@ class PassListActivity : PassAndroidActivity() {
})
passStore.syncPassStoreWithClassifier(getString(R.string.topic_new))
onPassStoreChangeEvent(null)
refresh()
fab_action_create_pass.setOnClickListener {
val pass = createAndAddEmptyPass(passStore, resources)
@ -189,7 +152,7 @@ class PassListActivity : PassAndroidActivity() {
}
fab_action_scan.setOnClickListener {
PassListActivityPermissionsDispatcher.scanWithCheck(this)
scanWithPermissionCheck()
fam.collapse()
}
@ -204,9 +167,29 @@ class PassListActivity : PassAndroidActivity() {
}
fab_action_open_file.visibility = VISIBLE
}
lifecycleScope.launch {
for (update in passStore.updateChannel.openSubscription()) {
navigationView.passStoreUpdate()
refresh()
}
}
}
fun refresh() {
adapter.notifyDataSetChanged()
setupWithViewPagerIfNeeded()
invalidateOptionsMenu()
val empty = passStore.classifier.topicByIdMap.isEmpty()
emptyView.visibility = if (empty) VISIBLE else GONE
val onlyDefaultTopicExists = passStore.classifier.getTopics().all { it == getString(R.string.topic_new) }
tab_layout.visibility = if (onlyDefaultTopicExists) GONE else VISIBLE
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.menu_help -> {
startActivityFromClass(HelpActivity::class.java)
@ -214,28 +197,29 @@ class PassListActivity : PassAndroidActivity() {
}
R.id.menu_emptytrash -> {
AlertDialog.Builder(this).setMessage(getString(R.string.empty_trash_dialog_message)).setIcon(R.drawable.ic_alert_warning).setTitle(getString(R.string.empty_trash_dialog_title)).setPositiveButton(R.string.emtytrash_label) { dialog, which ->
val passStoreProjection = PassStoreProjection(passStore,
getString(R.string.topic_trash),
null)
AlertDialog.Builder(this)
.setMessage(getString(R.string.empty_trash_dialog_message))
.setIcon(R.drawable.ic_alert_warning)
.setTitle(getString(R.string.empty_trash_dialog_title))
.setPositiveButton(R.string.emtytrash_label) { _, _ ->
val passStoreProjection = PassStoreProjection(passStore,
getString(R.string.topic_trash),
null)
for (pass in passStoreProjection.passList) {
passStore.deletePassWithId(pass.id)
}
}.setNegativeButton(android.R.string.cancel, null).show()
for (pass in passStoreProjection.passList) {
passStore.deletePassWithId(pass.id)
}
}.setNegativeButton(android.R.string.cancel, null).show()
true
}
else -> drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item)
}
override fun onResume() {
super.onResume()
bus.register(this)
adapter.notifyDataSetChanged()
onPassStoreChangeEvent(null)
refresh()
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
@ -258,24 +242,6 @@ class PassListActivity : PassAndroidActivity() {
drawerToggle.onConfigurationChanged(newConfig)
}
override fun onPause() {
bus.unregister(this)
super.onPause()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPassStoreChangeEvent(passStoreChangeEvent: PassStoreChangeEvent?) {
adapter.notifyDataSetChanged()
setupWithViewPagerIfNeeded()
supportInvalidateOptionsMenu()
val empty = passStore.classifier.topicByIdMap.isEmpty()
emptyView.visibility = if (empty) View.VISIBLE else View.GONE
tab_layout.visibility = if (empty) View.GONE else View.VISIBLE
}
private fun setupWithViewPagerIfNeeded() {
if (!areTabLayoutAndViewPagerInSync()) {
tab_layout.setupWithViewPager(view_pager)
@ -283,7 +249,6 @@ class PassListActivity : PassAndroidActivity() {
}
private fun areTabLayoutAndViewPagerInSync(): Boolean {
if (adapter.count != tab_layout.tabCount) {
return false
}
@ -295,7 +260,6 @@ class PassListActivity : PassAndroidActivity() {
}
}
return true
}
override fun onBackPressed() {
@ -305,5 +269,4 @@ class PassListActivity : PassAndroidActivity() {
else -> super.onBackPressed()
}
}
}

View file

@ -1,25 +1,22 @@
package org.ligi.passandroid.ui
import android.os.Bundle
import android.support.annotation.VisibleForTesting
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.support.v7.widget.helper.ItemTouchHelper.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.salomonbrys.kodein.instance
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import kotlinx.android.synthetic.main.pass_recycler.view.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.passandroid.App
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.events.ScanFinishedEvent
import org.ligi.passandroid.functions.moveWithUndoSnackbar
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.PassStoreProjection
@ -27,18 +24,16 @@ import org.ligi.passandroid.model.Settings
class PassListFragment : Fragment() {
private lateinit var passStoreProjection: PassStoreProjection
private lateinit var adapter: PassAdapter
val passStore: PassStore = App.kodein.instance()
val settings: Settings = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
val passStore: PassStore by inject()
val settings: Settings by inject()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val inflate = inflater.inflate(R.layout.pass_recycler, container, false)
passStoreProjection = PassStoreProjection(passStore, arguments.getString(BUNDLE_KEY_TOPIC)!!, settings.getSortOrder())
passStoreProjection = PassStoreProjection(passStore, arguments?.getString(BUNDLE_KEY_TOPIC)!!, settings.getSortOrder())
adapter = PassAdapter(activity as AppCompatActivity, passStoreProjection)
inflate.pass_recyclerview.adapter = adapter
@ -47,10 +42,10 @@ class PassListFragment : Fragment() {
val simpleItemTouchCallback = object : SimpleCallback(0, LEFT or RIGHT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder)
override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder)
= false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
override fun onSwiped(viewHolder: ViewHolder, swipeDir: Int) {
this@PassListFragment.onSwiped(viewHolder.adapterPosition, swipeDir)
}
}
@ -58,7 +53,12 @@ class PassListFragment : Fragment() {
val itemTouchHelper = ItemTouchHelper(simpleItemTouchCallback)
itemTouchHelper.attachToRecyclerView(inflate.pass_recyclerview)
bus.register(this)
lifecycleScope.launch {
for (update in passStore.updateChannel.openSubscription()) {
passStoreProjection.refresh()
adapter.notifyDataSetChanged()
}
}
return inflate
}
@ -67,38 +67,22 @@ class PassListFragment : Fragment() {
val pass = passStoreProjection.passList[pos]
val nextTopic = passStore.classifier.getTopicWithOffset(pass, if (swipeDir == LEFT) -1 else 1)
if (nextTopic != null) {
moveWithUndoSnackbar(passStore.classifier, pass, nextTopic, activity)
} else {
MoveToNewTopicUI(activity, passStore, pass).show()
activity?.let {
if (nextTopic != null) {
moveWithUndoSnackbar(passStore.classifier, pass, nextTopic, it)
} else {
MoveToNewTopicUI(it, passStore, pass).show()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
bus.unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPassStoreChangeEvent(passStoreChangeEvent: PassStoreChangeEvent) {
passStoreProjection.refresh()
adapter.notifyDataSetChanged()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onScanFinishedEvent(scanFinishedEvent: ScanFinishedEvent) {
passStoreProjection.refresh()
adapter.notifyDataSetChanged()
}
companion object {
private val BUNDLE_KEY_TOPIC = "topic"
private const val BUNDLE_KEY_TOPIC = "topic"
fun newInstance(topic: String) = PassListFragment().apply {
arguments = Bundle()
arguments.putString(BUNDLE_KEY_TOPIC, topic)
val bundle = Bundle()
bundle.putString(BUNDLE_KEY_TOPIC, topic)
arguments = bundle
}
}

View file

@ -2,14 +2,14 @@ package org.ligi.passandroid.ui
import android.app.Activity
import android.content.Intent
import android.support.v4.app.NavUtils
import android.support.v7.app.AlertDialog
import android.view.LayoutInflater
import android.view.MenuItem
import com.github.salomonbrys.kodein.instance
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NavUtils
import kotlinx.android.synthetic.main.delete_dialog_layout.view.*
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.kaxt.startActivityFromClass
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.maps.PassbookMapsFacade
@ -19,11 +19,11 @@ import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.printing.doPrint
import java.io.File
class PassMenuOptions(val activity: Activity, val pass: Pass) {
class PassMenuOptions(val activity: Activity, val pass: Pass) : KoinComponent {
var passStore: PassStore = App.kodein.instance()
var tracker: Tracker = App.kodein.instance()
var settings: Settings = App.kodein.instance()
val passStore: PassStore by inject()
val tracker: Tracker by inject()
val settings: Settings by inject()
fun process(item: MenuItem): Boolean {
when (item.itemId) {

View file

@ -1,28 +1,23 @@
package org.ligi.passandroid.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.support.design.widget.NavigationView
import android.util.AttributeSet
import com.github.salomonbrys.kodein.instance
import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.navigation_drawer_header.view.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.passandroid.App
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.model.PassStore
class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView(context, attrs) {
class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView(context, attrs), KoinComponent {
val passStore: PassStore = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
val passStore: PassStore by inject()
fun getIntent(id: Int) = when (id) {
private fun getIntent(id: Int) = when (id) {
R.id.menu_settings -> Intent(context, PreferenceActivity::class.java)
R.id.menu_plus -> intentFromUrl("https://plus.google.com/communities/116353894782342292067")
R.id.menu_github -> intentFromUrl("https://github.com/ligi/PassAndroid")
R.id.menu_beta -> intentFromUrl("https://play.google.com/apps/testing/org.ligi.passandroid")
R.id.menu_language -> intentFromUrl("https://transifex.com/projects/p/passandroid")
@ -33,13 +28,12 @@ class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView
else -> null
}
fun intentFromUrl(url: String) = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(url) }
private fun intentFromUrl(url: String) = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(url) }
@SuppressLint("RestrictedApi") // FIXME: temporary workaround for false-positive
override fun onAttachedToWindow() {
super.onAttachedToWindow()
bus.register(this)
setNavigationItemSelectedListener { item ->
getIntent(item.itemId)?.let {
context.startActivity(it)
@ -47,18 +41,12 @@ class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView
} ?: false
}
onPassStoreChangeEvent(PassStoreChangeEvent)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
bus.unregister(this)
passStoreUpdate()
}
private val marketUrl by lazy { context.getString(R.string.market_url, context.packageName) }
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPassStoreChangeEvent(@Suppress("UNUSED_PARAMETER") passStoreChangeEvent: PassStoreChangeEvent) {
fun passStoreUpdate() {
val passCount = passStore.passMap.size
getHeaderView(0).pass_count_header.text = context.getString(R.string.passes_nav, passCount)

View file

@ -1,13 +1,13 @@
package org.ligi.passandroid.ui
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter
import android.support.v4.view.PagerAdapter
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.PagerAdapter
import org.ligi.passandroid.model.PassClassifier
class PassTopicFragmentPagerAdapter(private val passClassifier: PassClassifier, fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
private lateinit var topic_array: Array<String>
private lateinit var topics: Array<String>
init {
notifyDataSetChanged()
@ -15,15 +15,16 @@ class PassTopicFragmentPagerAdapter(private val passClassifier: PassClassifier,
override fun notifyDataSetChanged() {
val topics = passClassifier.getTopics()
topic_array = topics.toTypedArray()
this.topics = topics.toTypedArray()
super.notifyDataSetChanged()
}
override fun getItem(position: Int) = PassListFragment.newInstance(topic_array[position])
override fun getItem(position: Int) = PassListFragment.newInstance(topics[position])
override fun getItemPosition(`object`: Any?) = PagerAdapter.POSITION_NONE // TODO - return POSITION_UNCHANGED in some cases
// TODO - return POSITION_UNCHANGED in some cases
override fun getItemPosition(`object`: Any) = PagerAdapter.POSITION_NONE
override fun getCount() = topic_array.size
override fun getCount() = topics.size
override fun getPageTitle(position: Int) = topic_array[position]
override fun getPageTitle(position: Int) = topics[position]
}

View file

@ -1,129 +1,92 @@
package org.ligi.passandroid.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.support.v4.app.NavUtils
import android.support.v4.app.TaskStackBuilder
import android.support.v4.text.util.LinkifyCompat
import android.text.util.Linkify
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_pass_view.*
import androidx.core.app.NavUtils
import androidx.core.app.TaskStackBuilder
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import kotlinx.android.synthetic.main.activity_pass_view_base.*
import kotlinx.android.synthetic.main.barcode.*
import kotlinx.android.synthetic.main.edit.*
import kotlinx.android.synthetic.main.pass_view_extra_data.*
import org.ligi.compat.HtmlCompat
import org.ligi.kaxt.disableRotation
import org.ligi.kaxt.startActivityFromClass
import org.ligi.passandroid.R
import org.ligi.passandroid.maps.PassbookMapsFacade
import org.ligi.passandroid.model.PassBitmapDefinitions
import org.ligi.passandroid.model.PassStoreProjection
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.ui.pass_view_holder.VerbosePassViewHolder
class PassViewActivity : PassViewActivityBase() {
private lateinit var pagerAdapter: CollectionPagerAdapter
private lateinit var viewPager: ViewPager
val passViewHelper by lazy { PassViewHelper(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
internal fun processImage(view: ImageView, name: String, pass: Pass) {
val bitmap = pass.getBitmap(passStore, name)
if (bitmap != null && bitmap.width > 300) {
view.setOnClickListener {
val intent = Intent(view.context, TouchImageActivity::class.java)
intent.putExtra("IMAGE", name)
startActivity(intent)
}
if (Build.VERSION.SDK_INT >= 27) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
}
passViewHelper.setBitmapSafe(view, bitmap)
disableRotation()
setContentView(R.layout.activity_pass_view)
pagerAdapter = CollectionPagerAdapter(supportFragmentManager, PassStoreProjection(passStore,
passStore.classifier.getTopic(currentPass, ""),
settings.getSortOrder()))
viewPager = pager
viewPager.adapter = pagerAdapter
viewPager.currentItem = pagerAdapter.getPos(currentPass)
viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(pos: Int) {
currentPass = pagerAdapter.getPass(pos)
passStore.currentPass = currentPass
}
})
}
override fun refresh() {
super.refresh()
BarcodeUIController(findViewById(android.R.id.content), currentPass.barCode, this, passViewHelper)
processImage(logo_img_view, PassBitmapDefinitions.BITMAP_LOGO, currentPass)
processImage(footer_img_view, PassBitmapDefinitions.BITMAP_FOOTER, currentPass)
processImage(thumbnail_img_view, PassBitmapDefinitions.BITMAP_THUMBNAIL, currentPass)
processImage(strip_img_view, PassBitmapDefinitions.BITMAP_STRIP, currentPass)
if (map_container != null) {
if (!(currentPass.locations.isNotEmpty() && PassbookMapsFacade.init(this))) {
map_container.visibility = View.GONE
}
}
val back_str = StringBuilder()
front_field_container.removeAllViews()
for (field in currentPass.fields) {
if (field.hide) {
back_str.append(field.toHtmlSnippet())
} else {
val v = layoutInflater.inflate(R.layout.main_field_item, front_field_container, false)
val key = v.findViewById(R.id.key) as TextView
key.text = field.label
val value = v.findViewById(R.id.value) as TextView
value.text = field.value
front_field_container.addView(v)
LinkifyCompat.addLinks(key, Linkify.ALL)
LinkifyCompat.addLinks(value, Linkify.ALL)
}
}
if (back_str.isNotEmpty()) {
back_fields.text = HtmlCompat.fromHtml(back_str.toString())
moreTextView.visibility = View.VISIBLE
} else {
moreTextView.visibility = View.GONE
}
LinkifyCompat.addLinks(back_fields, Linkify.ALL)
val passViewHolder = VerbosePassViewHolder(pass_card)
passViewHolder.apply(currentPass, passStore, this)
pagerAdapter.refresh()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
inner class CollectionPagerAdapter(
fm: FragmentManager,
private var passStoreProjection: PassStoreProjection
) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
disableRotation()
override fun getCount(): Int = passStoreProjection.passList.count()
setContentView(R.layout.activity_pass_view)
override fun getItem(i: Int): Fragment {
val passExtrasView = layoutInflater.inflate(R.layout.pass_view_extra_data, passExtrasContainer, false)
passExtrasContainer.addView(passExtrasView)
val fragment = PassViewFragment()
fragment.arguments = Bundle().apply {
putString(EXTRA_KEY_UUID, getPass(i).id)
}
return fragment
}
fun getPass(i: Int): Pass {
return passStoreProjection.passList[i]
}
fun getPos(pass: Pass): Int {
return passStoreProjection.passList.indexOf(pass)
}
fun refresh() {
passStoreProjection.refresh()
}
}
override fun onResumeFragments() {
super.onResumeFragments()
moreTextView.setOnClickListener {
if (back_fields.visibility == View.VISIBLE) {
back_fields.visibility = View.GONE
moreTextView.setText(R.string.more)
} else {
back_fields.visibility = View.VISIBLE
moreTextView.setText(R.string.less)
}
}
barcode_img.setOnClickListener {
startActivityFromClass(FullscreenBarcodeActivity::class.java)
}
setSupportActionBar(toolbar)
configureActionBar()
@ -131,9 +94,9 @@ class PassViewActivity : PassViewActivityBase() {
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
menu.findItem(R.id.menu_map).isVisible = !currentPass.locations.isEmpty()
menu.findItem(R.id.menu_update).isVisible = PassViewActivityBase.mightPassBeAbleToUpdate(currentPass)
menu.findItem(R.id.install_shortcut).isVisible = (23..25).contains(Build.VERSION.SDK_INT)
menu.findItem(R.id.menu_map).isVisible = currentPass.locations.isNotEmpty()
menu.findItem(R.id.menu_update).isVisible = mightPassBeAbleToUpdate(currentPass)
menu.findItem(R.id.install_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(this)
return super.onPrepareOptionsMenu(menu)
}
@ -147,18 +110,17 @@ class PassViewActivity : PassViewActivityBase() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
android.R.id.home -> {
val upIntent = NavUtils.getParentActivityIntent(this)
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities()
finish()
} else {
NavUtils.navigateUpTo(this, upIntent)
}
true
if (upIntent != null) {
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities()
finish()
} else {
NavUtils.navigateUpTo(this, upIntent)
}
true
} else false
}
else -> super.onOptionsItemSelected(item)
}
override fun onAttachedToWindow() = window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
}
}

View file

@ -1,23 +1,25 @@
package org.ligi.passandroid.ui
import android.annotation.SuppressLint
import android.app.Dialog
import android.app.ProgressDialog
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AlertDialog
import android.view.Menu
import android.view.MenuItem
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.google.android.material.snackbar.Snackbar
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.ligi.passandroid.BuildConfig
import org.ligi.passandroid.R
import org.ligi.passandroid.model.InputStreamWithSource
import org.ligi.passandroid.model.PassBitmapDefinitions.BITMAP_ICON
@ -28,6 +30,7 @@ import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.RuntimePermissions
import java.io.IOException
@SuppressLint("Registered")
@RuntimePermissions
open class PassViewActivityBase : PassAndroidActivity() {
@ -45,14 +48,13 @@ open class PassViewActivityBase : PassAndroidActivity() {
try {
val config = ViewConfiguration.get(this)
val menuKeyField = ViewConfiguration::class.java.getDeclaredField("sHasPermanentMenuKey")
if (menuKeyField != null) {
menuKeyField.isAccessible = true
menuKeyField.setBoolean(config, false)
}
menuKeyField.isAccessible = true
menuKeyField.setBoolean(config, false)
} catch (ex: Exception) {
// Ignore - but at least we tried ;-)
}
updateCurrentPass()
}
override fun onPause() {
@ -63,26 +65,6 @@ open class PassViewActivityBase : PassAndroidActivity() {
override fun onResume() {
super.onResume()
val uuid = intent.getStringExtra(EXTRA_KEY_UUID)
if (uuid != null) {
val passbookForId = passStore.getPassbookForId(uuid)
passStore.currentPass = passbookForId
}
if (passStore.currentPass == null) {
val passbookForId = passStore.getPassbookForId(State.lastSelectedPassUUID)
passStore.currentPass = passbookForId
}
if (passStore.currentPass == null) {
tracker.trackException("pass not present in " + this, false)
finish()
return
}
currentPass = passStore.currentPass!!
configureActionBar()
if (settings.isAutomaticLightEnabled()) {
@ -90,6 +72,26 @@ open class PassViewActivityBase : PassAndroidActivity() {
}
}
private fun updateCurrentPass() {
val uuid = intent.getStringExtra(EXTRA_KEY_UUID)
if (uuid != null) {
passStore.currentPass = passStore.getPassbookForId(uuid)
}
if (passStore.currentPass == null) {
passStore.currentPass = passStore.getPassbookForId(State.lastSelectedPassUUID)
}
if (passStore.currentPass == null) {
tracker.trackException("pass not present in $this", false)
finish()
return
}
currentPass = passStore.currentPass!!
}
protected fun configureActionBar() {
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -127,7 +129,7 @@ open class PassViewActivityBase : PassAndroidActivity() {
}
R.id.install_shortcut -> {
PassViewActivityBasePermissionsDispatcher.createShortcutWithCheck(this)
createShortcutWithPermissionCheck()
true
}
@ -143,28 +145,31 @@ open class PassViewActivityBase : PassAndroidActivity() {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
PassViewActivityBasePermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission("com.android.launcher.permission.INSTALL_SHORTCUT")
fun createShortcut() {
val intent = Intent("com.android.launcher.action.INSTALL_SHORTCUT")
val shortcutIntent = Intent()
shortcutIntent.putExtra(EXTRA_KEY_UUID, currentPass.id)
val component = ComponentName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".ui.PassViewActivity")
shortcutIntent.component = component
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, currentPass.description)
val passBitmap = currentPass.getBitmap(passStore, BITMAP_ICON)
val bitmapToUse = if (passBitmap != null) {
val shortcutIcon = if (passBitmap != null) {
Bitmap.createScaledBitmap(passBitmap, 128, 128, true)
} else {
BitmapFactory.decodeResource(resources, R.drawable.ic_launcher)
}
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmapToUse)
sendBroadcast(intent)
val name: CharSequence = currentPass.description.let {
if (it.isNullOrEmpty()) "pass" else it
}
val targetIntent = Intent(this, PassViewActivity::class.java)
.setAction(Intent.ACTION_MAIN)
.putExtra(EXTRA_KEY_UUID, currentPass.id)
val shortcutInfo = ShortcutInfoCompat.Builder(this, "shortcut$name")
.setIntent(targetIntent)
.setShortLabel(name)
.setIcon(IconCompat.createWithBitmap(shortcutIcon))
.build()
ShortcutManagerCompat.requestPinShortcut(this, shortcutInfo, null)
}
inner class UpdateAsync : Runnable {
@ -183,7 +188,7 @@ open class PassViewActivityBase : PassAndroidActivity() {
val url = pass.webServiceURL + "/v1/passes/" + pass.passIdent + "/" + pass.serial
val requestBuilder = Request.Builder().url(url)
requestBuilder.addHeader("Authorization", "ApplePass " + pass.authToken!!)
requestBuilder.addHeader("Authorization", "ApplePass " + pass.authToken)
val request = requestBuilder.build()
@ -250,7 +255,7 @@ open class PassViewActivityBase : PassAndroidActivity() {
params.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
win.attributes = params
fullBrightnessSet = true
supportInvalidateOptionsMenu()
invalidateOptionsMenu()
}
companion object {
@ -258,7 +263,7 @@ open class PassViewActivityBase : PassAndroidActivity() {
const val EXTRA_KEY_UUID = "uuid"
fun mightPassBeAbleToUpdate(pass: Pass?): Boolean {
return pass != null && pass.webServiceURL != null && pass.passIdent != null && pass.serial != null
return pass?.webServiceURL != null && pass.passIdent != null && pass.serial != null
}
}
}
}

View file

@ -0,0 +1,132 @@
package org.ligi.passandroid.ui
import android.content.Intent
import android.os.Bundle
import android.text.util.Linkify
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.text.util.LinkifyCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.android.synthetic.main.activity_pass_view.*
import kotlinx.android.synthetic.main.barcode.*
import kotlinx.android.synthetic.main.pass_list_item.*
import kotlinx.android.synthetic.main.pass_view_extra_data.*
import org.koin.android.ext.android.inject
import org.ligi.compat.HtmlCompat
import org.ligi.kaxt.startActivityFromClass
import org.ligi.passandroid.R
import org.ligi.passandroid.maps.PassbookMapsFacade
import org.ligi.passandroid.model.PassBitmapDefinitions
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.ui.pass_view_holder.VerbosePassViewHolder
class PassViewFragment : Fragment() {
private val passViewHelper by lazy { PassViewHelper(requireActivity()) }
internal val passStore : PassStore by inject()
lateinit var pass : Pass
private fun processImage(view: ImageView, name: String, pass: Pass) {
val bitmap = pass.getBitmap(passStore, name)
if (bitmap != null && bitmap.width > 300) {
view.setOnClickListener {
val intent = Intent(view.context, TouchImageActivity::class.java)
intent.putExtra("IMAGE", name)
startActivity(intent)
}
}
passViewHelper.setBitmapSafe(view, bitmap)
}
override fun onResume() {
super.onResume()
moreTextView.setOnClickListener {
if (back_fields.visibility == View.VISIBLE) {
back_fields.visibility = View.GONE
moreTextView.setText(R.string.more)
} else {
back_fields.visibility = View.VISIBLE
moreTextView.setText(R.string.less)
}
}
barcode_img.setOnClickListener {
activity?.startActivityFromClass(FullscreenBarcodeActivity::class.java)
}
BarcodeUIController(view!!, pass.barCode, activity!!, passViewHelper)
processImage(logo_img_view, PassBitmapDefinitions.BITMAP_LOGO, pass)
processImage(footer_img_view, PassBitmapDefinitions.BITMAP_FOOTER, pass)
processImage(thumbnail_img_view, PassBitmapDefinitions.BITMAP_THUMBNAIL, pass)
processImage(strip_img_view, PassBitmapDefinitions.BITMAP_STRIP, pass)
if (map_container != null) {
if (!(pass.locations.isNotEmpty() && PassbookMapsFacade.init(activity as FragmentActivity))) {
@Suppress("PLUGIN_WARNING")
map_container.visibility = View.GONE
}
}
val backStrBuilder = StringBuilder()
front_field_container.removeAllViews()
for (field in pass.fields) {
if (field.hide) {
backStrBuilder.append(field.toHtmlSnippet())
} else {
val v = activity!!.layoutInflater.inflate(R.layout.main_field_item, front_field_container, false)
val key = v?.findViewById<TextView>(R.id.key)
key?.text = field.label
val value = v?.findViewById<TextView>(R.id.value)
value?.text = field.value
front_field_container.addView(v)
key?.let { LinkifyCompat.addLinks(it, Linkify.ALL) }
value?.let { LinkifyCompat.addLinks(it, Linkify.ALL) }
}
}
if (backStrBuilder.isNotEmpty()) {
back_fields.text = HtmlCompat.fromHtml(backStrBuilder.toString())
moreTextView.visibility = View.VISIBLE
} else {
moreTextView.visibility = View.GONE
}
LinkifyCompat.addLinks(back_fields, Linkify.ALL)
val passViewHolder = VerbosePassViewHolder(pass_card)
passViewHolder.apply(pass, passStore, activity!!)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
val rootView = inflater.inflate(R.layout.activity_pass_view_page, container, false)
arguments?.takeIf { it.containsKey(PassViewActivityBase.EXTRA_KEY_UUID) }?.apply {
val uuid = getString(PassViewActivityBase.EXTRA_KEY_UUID)
pass = if (uuid != null) {
passStore.getPassbookForId(uuid) ?: passStore.currentPass!!
} else {
passStore.currentPass!!
}
}
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val passExtrasView = activity!!.layoutInflater.inflate(R.layout.pass_view_extra_data, passExtrasContainer, false)
passExtrasContainer.addView(passExtrasView)
}
}

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