Compare commits

..

8 commits

Author SHA1 Message Date
HarryJohnso
956526b5f6
Navbar back to white 2019-10-30 11:40:37 +09:00
HarryJohnso
ddbcb18a0e
removing navigationBarColor from values-night-v27 2019-10-30 11:40:37 +09:00
HarryJohnso
0bef68c9f9
Overriding AppTheme 2019-10-30 11:40:37 +09:00
HarryJohnso
2eac57f68f
Alternative to Simons original proposal 2019-10-30 11:40:37 +09:00
HarryJohnso
539fc8f85e
last refactoring 2019-10-30 11:40:37 +09:00
HarryJohnso
2fce918a7e
refactoring style files 2019-10-30 11:40:37 +09:00
HarryJohnso
0c9521521e
uncommenting style items 2019-10-30 11:40:36 +09:00
HarryJohnso
3b2c37236b
adding style files 2019-10-30 11:40:34 +09:00
80 changed files with 707 additions and 797 deletions

12
.github/FUNDING.yml vendored
View file

@ -1,12 +0,0 @@
# These are supported funding model platforms
github: ligi
patreon: ligi
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View file

@ -4,6 +4,7 @@ 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 {
@ -16,10 +17,11 @@ repositories {
android {
compileSdkVersion 29
buildToolsVersion "28.0.3"
defaultConfig {
versionCode 356
versionName "3.5.6"
versionCode 349
versionName "3.4.9"
minSdkVersion 14
targetSdkVersion 29
applicationId "org.ligi.passandroid"
@ -97,10 +99,6 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
debug {
multiDexEnabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -112,39 +110,35 @@ dependencies {
implementation 'org.permissionsdispatcher:permissionsdispatcher:4.6.0'
kapt 'org.permissionsdispatcher:permissionsdispatcher-processor:4.6.0'
implementation "org.koin:koin-android:2.1.2"
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'
androidTestImplementation 'com.github.ligi:trulesk:0.31'
androidTestUtil 'com.linkedin.testbutler:test-butler-app:2.1.0'
implementation 'com.github.salomonbrys.kodein:kodein:4.1.0'
compileOnly 'org.glassfish:javax.annotation:3.1.1'
androidTestImplementation 'com.github.ligi:trulesk:0.28'
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.linkedin.dexmaker:dexmaker-mockito:2.25.0'
androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2'
androidTestImplementation 'org.threeten:threetenbp:1.4.1'
androidTestImplementation 'com.android.support:multidex:1.0.3'
androidTestImplementation 'com.google.android.material:material:1.0.0'
androidTestImplementation 'org.threeten:threetenbp:1.4.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 'net.lingala.zip4j:zip4j:2.2.3'
implementation 'com.jakewharton.threetenabp:threetenabp:1.2.1'
implementation 'org.greenrobot:eventbus:3.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
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.recyclerview:recyclerview:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'net.i2p.android.ext:floatingactionbutton:1.10.1'
implementation 'com.github.ligi:KAXT:1.0'
@ -152,9 +146,9 @@ dependencies {
implementation 'com.github.ligi:loadtoast:1.10.11'
implementation 'com.github.ligi:tracedroid:3.0'
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'
forPlayImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.22'
forFDroidImplementation 'com.github.ligi.snackengage:snackengage-playrate:0.22'
forAmazonImplementation 'com.github.ligi.snackengage:snackengage-amazonrate:0.22'
// https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce
// Don't update to >=3.13 before minSDK 21 + Java 8
@ -164,25 +158,23 @@ dependencies {
implementation 'com.larswerkman:HoloColorPicker:1.5'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
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")
implementation 'com.squareup.moshi:moshi:1.9.0'
implementation 'com.chibatching.kotpref:kotpref:2.10.0'
implementation 'com.chibatching.kotpref:initializer:2.10.0'
implementation 'com.chibatching.kotpref:kotpref:2.9.2'
implementation 'com.chibatching.kotpref:initializer:2.9.2'
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'
testImplementation 'org.threeten:threetenbp:1.4.0'
// https://github.com/ligi/PassAndroid/issues/181
// Don't upgrade before minSDK 19 - or replace zxing
//noinspection GradleDependency
implementation 'com.google.zxing:core:3.3.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// requires minSDK 16 according to docs, not sure if it causes an issue
withAnalyticsImplementation 'com.google.android.gms:play-services-analytics:17.0.0'

View file

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

View file

@ -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,7 +1,5 @@
package org.ligi.passandroid
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.*
@ -10,7 +8,7 @@ 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 com.github.salomonbrys.kodein.instance
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -27,20 +25,18 @@ class TheBarCodeEditing {
@get:Rule
val rule = TruleskActivityRule(PassEditActivity::class.java, false)
val passStore: PassStore = TestApp.passStore
val passStore: PassStore = App.kodein.instance()
private lateinit var currentPass: PassImpl
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()
}
@ -84,7 +80,6 @@ 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())
@ -102,7 +97,7 @@ class TheBarCodeEditing {
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.messageInput)).perform(clearText())
onView(withId(R.id.messageInput)).perform(replaceText("msg foo txt ;-)"))
onView(withId(R.id.messageInput)).perform(typeText("msg foo txt ;-)"))
closeSoftKeyboard()
@ -122,7 +117,7 @@ class TheBarCodeEditing {
onView(withId(R.id.barcode_img)).perform(click())
onView(withId(R.id.alternativeMessageInput)).perform(clearText())
onView(withId(R.id.alternativeMessageInput)).perform(replaceText("alt bar txt ;-)"))
onView(withId(R.id.alternativeMessageInput)).perform(typeText("alt bar txt ;-)"))
closeSoftKeyboard()

View file

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

@ -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.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 android.widget.ImageView
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
@ -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()))

View file

@ -1,6 +1,5 @@
package org.ligi.passandroid
import android.Manifest
import android.annotation.TargetApi
import android.app.Activity
import android.app.Instrumentation
@ -12,8 +11,6 @@ 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
@ -25,13 +22,11 @@ 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) {
TestApp.populatePassStoreWithSinglePass()
TestButler.grantPermission(InstrumentationRegistry.getInstrumentation().targetContext, Manifest.permission.READ_EXTERNAL_STORAGE)
TestButler.grantPermission(InstrumentationRegistry.getInstrumentation().targetContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
@Test
@ -59,7 +54,7 @@ class ThePassEditActivity {
@Test
fun testSetDescriptionWorks() {
onView(withId(R.id.passTitle)).perform(clearText(), replaceText("test description"))
onView(withId(R.id.passTitle)).perform(clearText(), typeText("test description"))
assertThat(passStore.currentPass!!.description).isEqualTo("test description")
rule.screenShot("edit_set_description")

View file

@ -1,14 +1,15 @@
package org.ligi.passandroid
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.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.recyclerview.widget.ItemTouchHelper
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
@ -28,7 +29,7 @@ class ThePassListSwiping {
onView(withText(R.string.topic_trash)).perform(click())
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
}
@ -38,7 +39,7 @@ class ThePassListSwiping {
onView(withText(R.string.topic_archive)).perform(click())
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
}
@Test
@ -46,11 +47,11 @@ class ThePassListSwiping {
fakeSwipeLeft()
onView(withId(R.id.new_topic_edit)).perform(replaceText(CUSTOM_PROBE))
onView(withId(R.id.new_topic_edit)).perform(typeText(CUSTOM_PROBE))
onView(withText(android.R.string.ok)).perform(click())
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
assertThat(passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
}

View file

@ -22,7 +22,7 @@ import java.util.*
@TargetApi(14)
class ThePassViewActivity {
private fun getActPass() = TestApp.passStore.currentPass as PassImpl
private fun getActPass() = TestApp.passStore().currentPass as PassImpl
@get:Rule
var rule = TruleskActivityRule(PassViewActivity::class.java, false)

View file

@ -18,7 +18,7 @@ class ThePassViewHolder {
private val currentPass by lazy {
TestApp.populatePassStoreWithSinglePass()
TestApp.passStore.currentPass as PassImpl
App.passStore.currentPass as PassImpl
}
@get:Rule

View file

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

View file

@ -49,7 +49,7 @@ public class TheUnzipPassController {
verify(failCallback).fail(any(String.class));
} catch (Exception e) {
fail("should be able to load file " + e);
fail("should be able to load file");
}
}

View file

@ -3,7 +3,6 @@ package org.ligi.passandroid.functions
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import org.assertj.core.api.Fail.fail
import org.ligi.passandroid.TestApp
import org.ligi.passandroid.model.InputStreamWithSource
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.pass.Pass
@ -31,7 +30,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,TestApp.tracker))
instrumentation.targetContext))
}
},
mock

View file

@ -1,10 +1,7 @@
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
@ -51,8 +48,6 @@ 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

@ -46,7 +46,7 @@
android:resource="@xml/filepaths"/>
</provider>
<service android:name=".scan.SearchPassesIntentService"/>
<service android:name=".ui.SearchPassesIntentService"/>
<activity
android:name=".ui.PassListActivity"
@ -1804,7 +1804,6 @@
<activity
android:name=".ui.FullscreenBarcodeActivity"
android:label="@string/app_name"/>
<activity android:name=".scan.PassScanActivity" />
</application>

View file

@ -2,62 +2,64 @@ package org.ligi.passandroid
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
import com.github.salomonbrys.kodein.*
import com.jakewharton.threetenabp.AndroidThreeTen
import com.squareup.leakcanary.LeakCanary
import com.squareup.moshi.Moshi
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.core.module.Module
import org.koin.dsl.module
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.json_adapter.ColorAdapter
import org.ligi.passandroid.json_adapter.ZonedTimeAdapter
import org.ligi.passandroid.model.AndroidFileSystemPassStore
import org.ligi.passandroid.model.AndroidSettings
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
import org.ligi.tracedroid.TraceDroid
import org.ligi.tracedroid.logging.Log
open class App : Application() {
private val moshi = Moshi.Builder()
.add(ZonedTimeAdapter())
.add(ColorAdapter())
.build()
private val settings by lazy { AndroidSettings(this) }
open fun createKoin(): Module {
return module {
single { AndroidFileSystemPassStore(this@App, get(), moshi) as PassStore }
single { settings as Settings }
single { createTracker(this@App) }
single { PassScanEventChannelProvider() }
}
}
override fun onCreate() {
super.onCreate()
startKoin {
if (BuildConfig.DEBUG) androidLogger()
androidContext(this@App)
modules(createKoin())
kodein = Kodein {
import(createTrackerKodeinModule(this@App))
import(createKodein(), allowOverride = true)
}
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

@ -0,0 +1,5 @@
package org.ligi.passandroid.events
import org.ligi.passandroid.model.pass.Pass
class PassRefreshEvent(val pass: Pass)

View file

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

View file

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

View file

@ -0,0 +1,3 @@
package org.ligi.passandroid.events
class ScanProgressEvent(val message: String)

View file

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

View file

@ -3,16 +3,11 @@ package org.ligi.passandroid.model
import android.content.Context
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
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 okio.Okio
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.App
import org.ligi.passandroid.BuildConfig
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.events.PassStoreChangeEvent
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.model.pass.PassImpl
import org.ligi.passandroid.reader.AppleStylePassReader
@ -20,24 +15,13 @@ import org.ligi.passandroid.reader.PassReader
import java.io.File
import java.util.*
object PassStoreUpdateEvent
class AndroidFileSystemPassStore(
private val context: Context,
settings: Settings,
private val moshi: Moshi
) : PassStore, KoinComponent {
override val updateChannel = ConflatedBroadcastChannel<PassStoreUpdateEvent>()
class AndroidFileSystemPassStore(private val context: Context, settings: Settings, private val moshi: Moshi, private val bus: EventBus) : PassStore {
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)
@ -52,7 +36,7 @@ class AndroidFileSystemPassStore(
pathForID.mkdirs()
}
val buffer = File(pathForID, "main.json").sink().buffer()
val buffer = Okio.buffer(Okio.sink(File(pathForID, "main.json")))
if (BuildConfig.DEBUG) {
val of = com.squareup.moshi.JsonWriter.of(buffer)
@ -83,9 +67,9 @@ class AndroidFileSystemPassStore(
val jsonAdapter = moshi.adapter(PassImpl::class.java)
dirty = false
try {
result = jsonAdapter.fromJson(file.source().buffer())
result = jsonAdapter.fromJson(Okio.buffer(Okio.source(file)))
} catch (ignored: JsonDataException) {
tracker.trackException("invalid main.json", false)
App.tracker.trackException("invalid main.json", false)
}
}
@ -95,7 +79,7 @@ class AndroidFileSystemPassStore(
}
if (result == null && File(pathForID, "pass.json").exists()) {
result = AppleStylePassReader.read(pathForID, language, context, tracker)
result = AppleStylePassReader.read(pathForID, language, context)
}
if (result != null) {
@ -128,9 +112,7 @@ class AndroidFileSystemPassStore(
}
override fun notifyChange() {
GlobalScope.launch {
updateChannel.send(PassStoreUpdateEvent)
}
bus.post(PassStoreChangeEvent)
}
override fun syncPassStoreWithClassifier(defaultTopic: String) {

View file

@ -10,6 +10,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import org.ligi.passandroid.App;
public class AppleStylePassTranslation extends HashMap<String, String> {
@ -73,6 +74,7 @@ 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

@ -1,13 +1,10 @@
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

@ -2,21 +2,19 @@ package org.ligi.passandroid.model.pass
import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import com.squareup.moshi.JsonClass
import org.koin.core.KoinComponent
import org.koin.core.inject
import com.github.salomonbrys.kodein.instance
import org.ligi.passandroid.App
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.functions.generateBitmapDrawable
import org.ligi.tracedroid.logging.Log
import java.util.*
@JsonClass(generateAdapter = true)
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) : KoinComponent {
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) {
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

@ -2,9 +2,7 @@ package org.ligi.passandroid.model.pass
import android.content.res.Resources
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,7 +2,6 @@ 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
@ -11,7 +10,6 @@ import java.io.FileInputStream
import java.io.FileNotFoundException
import java.util.*
@JsonClass(generateAdapter = true)
class PassImpl(override val id: String) : Pass {
@ -39,10 +37,7 @@ 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()

View file

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

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,7 +26,7 @@ import java.util.*
object AppleStylePassReader {
fun read(passFile: File, language: String, context: Context, tracker: Tracker): Pass? {
fun read(passFile: File, language: String, context: Context): Pass? {
val translation = AppleStylePassTranslation()
@ -34,7 +34,7 @@ object AppleStylePassReader {
var passJSON: JSONObject? = null
val localizedPath = findLocalizedPath(passFile, language, tracker)
val localizedPath = findLocalizedPath(passFile, language)
if (localizedPath != null) {
val file = File(localizedPath, "pass.strings")
@ -77,7 +77,7 @@ object AppleStylePassReader {
if (passJSON == null) {
Log.w("could not load pass.json from passcode ")
tracker.trackEvent("problem_event", "pass", "without_pass_json", null)
App.tracker.trackEvent("problem_event", "pass", "without_pass_json", null)
return null
}
@ -86,7 +86,7 @@ object AppleStylePassReader {
if (barcodeJSON != null) {
val barcodeFormatString = barcodeJSON.getString("format")
tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
App.tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
val barcodeFormat = BarCode.getFormatFromString(barcodeFormatString)
val barCode = BarCode(barcodeFormat, barcodeJSON.getString("message"))
pass.barCode = barCode
@ -105,9 +105,9 @@ object AppleStylePassReader {
} 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
tracker.trackException("problem parsing relevant date", e, false)
App.tracker.trackException("problem parsing relevant date", e, false)
} catch (e: DateTimeException) {
tracker.trackException("problem parsing relevant date", e, false)
App.tracker.trackException("problem parsing relevant date", e, false)
}
}
@ -118,9 +118,9 @@ object AppleStylePassReader {
} 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
tracker.trackException("problem parsing expiration date", e, false)
App.tracker.trackException("problem parsing expiration date", e, false)
} catch (e: DateTimeException) {
tracker.trackException("problem parsing expiration date", e, false)
App.tracker.trackException("problem parsing expiration date", e, false)
}
}
@ -196,12 +196,12 @@ object AppleStylePassReader {
try {
pass.creator = passJSON.getString("organizationName")
tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
App.tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
} catch (ignored: JSONException) {
// ok - we have no organisation - big deal ..-)
}
ApplePassbookQuirkCorrector(tracker).correctQuirks(pass)
ApplePassbookQuirkCorrector(App.tracker).correctQuirks(pass)
return pass
}
@ -240,18 +240,18 @@ object AppleStylePassReader {
}
private fun findLocalizedPath(path: File, language: String, tracker: Tracker): String? {
private fun findLocalizedPath(path: File, language: String): String? {
val localized = File(path, "$language.lproj")
if (localized.exists() && localized.isDirectory) {
tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
App.tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
return localized.path
}
val fallback = File(path, "en.lproj")
if (fallback.exists() && fallback.isDirectory) {
tracker.trackEvent("measure_event", "pass", "en_lproj", null)
App.tracker.trackEvent("measure_event", "pass", "en_lproj", null)
return fallback.path
}

View file

@ -1,61 +0,0 @@
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

@ -1,179 +0,0 @@
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,8 +0,0 @@
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

@ -1,8 +0,0 @@
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

@ -1,14 +1,14 @@
package org.ligi.passandroid.ui
import android.view.LayoutInflater
import android.view.ViewGroup
import com.google.android.material.snackbar.Snackbar
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 android.view.LayoutInflater
import android.view.ViewGroup
import com.github.salomonbrys.kodein.instance
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,13 +17,10 @@ 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>(), KoinComponent {
class PassAdapter(private val passListActivity: AppCompatActivity, private val passStoreProjection: PassStoreProjection) : RecyclerView.Adapter<PassViewHolder>() {
private val passStore: PassStore by inject ()
private val settings: Settings by inject ()
val passStore: PassStore = App.kodein.instance()
val settings: Settings = App.kodein.instance()
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PassViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)

View file

@ -1,16 +1,19 @@
package org.ligi.passandroid.ui
import androidx.appcompat.app.AppCompatActivity
import org.koin.android.ext.android.inject
import com.github.salomonbrys.kodein.instance
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.App
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
open class PassAndroidActivity : AppCompatActivity() {
val passStore: PassStore by inject()
val settings: Settings by inject()
val tracker: Tracker by inject()
val passStore: PassStore = App.kodein.instance()
val settings: Settings = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
val tracker: Tracker = App.kodein.instance()
private var lastSetNightMode: Int? = null

View file

@ -3,17 +3,21 @@ package org.ligi.passandroid.ui
import android.Manifest
import android.content.Intent
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.ImageView
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.github.salomonbrys.kodein.instance
import kotlinx.android.synthetic.main.edit.*
import org.koin.android.ext.android.inject
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
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
@ -35,7 +39,8 @@ class PassEditActivity : AppCompatActivity() {
private lateinit var currentPass: PassImpl
private val imageEditHelper by lazy { ImageEditHelper(this, passStore) }
internal val passStore: PassStore by inject()
internal val passStore: PassStore = App.kodein.instance()
internal val bus: EventBus = App.kodein.instance()
private val passViewHelper: PassViewHelper by lazy { PassViewHelper(this) }
@ -52,8 +57,8 @@ class PassEditActivity : AppCompatActivity() {
categoryView.setOnClickListener {
AlertDialog.Builder(this).setItems(R.array.category_edit_options) { _, i ->
when (i) {
0 -> showCategoryPickDialog(this@PassEditActivity, currentPass, refreshCallback)
1 -> showColorPickDialog(this@PassEditActivity, currentPass, refreshCallback)
0 -> showCategoryPickDialog(this@PassEditActivity, currentPass, bus)
1 -> showColorPickDialog(this@PassEditActivity, currentPass, bus)
2 -> pickImageWithPermissionCheck(ImageEditHelper.REQ_CODE_PICK_ICON)
}
}.show()
@ -80,15 +85,17 @@ class PassEditActivity : AppCompatActivity() {
add_barcode_button.setOnClickListener {
showBarcodeEditDialog(this@PassEditActivity,
refreshCallback,
bus,
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)
@ -111,7 +118,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, refreshCallback, currentPass, currentPass.barCode!!) }
barcodeUIController.getBarcodeView().setOnClickListener { showBarcodeEditDialog(this@PassEditActivity, bus, currentPass, currentPass.barCode!!) }
}
@Pass.PassBitmap
@ -135,11 +142,13 @@ 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()

View file

@ -4,21 +4,15 @@ import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.os.Handler
import android.widget.Toast
import androidx.annotation.UiThread
import androidx.core.content.FileProvider
import org.koin.core.KoinComponent
import org.koin.core.inject
import android.widget.Toast
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import java.io.File
internal open class PassExportTaskAndShare(
protected val activity: Activity,
private val inputPath: File
) : KoinComponent {
internal open class PassExportTaskAndShare(protected val activity: Activity, private val inputPath: File) {
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
@ -37,7 +31,7 @@ internal open class PassExportTaskAndShare(
}
if (passExporter.exception != null) {
tracker.trackException("passExporterException", passExporter.exception!!, false)
App.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)

View file

@ -4,16 +4,14 @@ import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.model.ZipParameters
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 org.ligi.passandroid.App
import java.io.File
class PassExporter(private val inputPath: File, val file: File) : KoinComponent {
class PassExporter(private val inputPath: File, val file: File) {
var exception: Exception? = null
val tracker: Tracker by inject()
fun export() {
try {
file.delete()
@ -30,7 +28,7 @@ class PassExporter(private val inputPath: File, val file: File) : KoinComponent
} catch (exception: Exception) {
exception.printStackTrace()
tracker.trackException("when exporting pass to zip", exception, false)
App.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,17 +1,14 @@
package org.ligi.passandroid.ui
import android.Manifest
import android.app.ProgressDialog
import android.os.Bundle
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 com.github.salomonbrys.kodein.instance
import org.ligi.kaxt.dismissIfShowing
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
@ -24,8 +21,16 @@ import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
class PassImportActivity : AppCompatActivity() {
val tracker: Tracker by inject()
val passStore: PassStore by inject()
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)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -36,7 +41,7 @@ class PassImportActivity : AppCompatActivity() {
return
}
setContentView(R.layout.activity_import)
progressDialog.show()
doImportWithPermissionCheck(false)
}
@ -48,13 +53,12 @@ class PassImportActivity : AppCompatActivity() {
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun doImport(withPermission: Boolean) {
lifecycleScope.launch(Dispatchers.IO) {
Thread {
try {
val fromURI = fromURI(this@PassImportActivity, intent!!.data!!, tracker)
val fromURI = fromURI(this, intent!!.data!!)
withContext(Dispatchers.Main) {
progress_container.visibility = GONE
runOnUiThread {
progressDialog.dismissIfShowing()
if (fromURI == null) {
finish()
@ -67,7 +71,7 @@ class PassImportActivity : AppCompatActivity() {
val spec = UnzipPassController.InputStreamUnzipControllerSpec(fromURI, application, passStore, null, null)
UnzipPassController.processInputStream(spec)
} else {
UnzipPassDialog.show(fromURI, this@PassImportActivity, passStore) { path ->
UnzipPassDialog.show(fromURI, this, 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()
@ -89,12 +93,12 @@ class PassImportActivity : AppCompatActivity() {
tracker.trackException("Error in import", e, false)
}
}
}
}.start()
}
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showDeniedDialog() {
progress_container.visibility = GONE
progressDialog.dismissIfShowing()
alert(R.string.error_no_permission_msg, R.string.error_no_permission_title, onOK = { finish() })
}
}

View file

@ -3,7 +3,9 @@ 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
@ -15,18 +17,21 @@ 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 kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.kaxt.setButton
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
@ -46,7 +51,25 @@ 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)
@ -57,7 +80,22 @@ class PassListActivity : PassAndroidActivity() {
@TargetApi(16)
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun scan() = startActivity(Intent(this, PassScanActivity::class.java))
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()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
@ -134,7 +172,7 @@ class PassListActivity : PassAndroidActivity() {
})
passStore.syncPassStoreWithClassifier(getString(R.string.topic_new))
refresh()
onPassStoreChangeEvent(null)
fab_action_create_pass.setOnClickListener {
val pass = createAndAddEmptyPass(passStore, resources)
@ -167,29 +205,9 @@ 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)
@ -209,7 +227,7 @@ class PassListActivity : PassAndroidActivity() {
for (pass in passStoreProjection.passList) {
passStore.deletePassWithId(pass.id)
}
}.setNegativeButton(android.R.string.cancel, null).show()
}.setNegativeButton(android.R.string.cancel, null).show()
true
}
else -> drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item)
@ -218,8 +236,10 @@ class PassListActivity : PassAndroidActivity() {
override fun onResume() {
super.onResume()
bus.register(this)
adapter.notifyDataSetChanged()
refresh()
onPassStoreChangeEvent(null)
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
@ -242,6 +262,25 @@ 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()
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
}
private fun setupWithViewPagerIfNeeded() {
if (!areTabLayoutAndViewPagerInSync()) {
tab_layout.setupWithViewPager(view_pager)

View file

@ -1,22 +1,26 @@
package org.ligi.passandroid.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.salomonbrys.kodein.instance
import kotlinx.android.synthetic.main.pass_recycler.view.*
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.passandroid.App
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
@ -24,11 +28,13 @@ import org.ligi.passandroid.model.Settings
class PassListFragment : Fragment() {
private lateinit var passStoreProjection: PassStoreProjection
private lateinit var adapter: PassAdapter
val passStore: PassStore by inject()
val settings: Settings by inject()
val passStore: PassStore = App.kodein.instance()
val settings: Settings = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val inflate = inflater.inflate(R.layout.pass_recycler, container, false)
@ -53,12 +59,7 @@ class PassListFragment : Fragment() {
val itemTouchHelper = ItemTouchHelper(simpleItemTouchCallback)
itemTouchHelper.attachToRecyclerView(inflate.pass_recyclerview)
lifecycleScope.launch {
for (update in passStore.updateChannel.openSubscription()) {
passStoreProjection.refresh()
adapter.notifyDataSetChanged()
}
}
bus.register(this)
return inflate
}
@ -76,6 +77,24 @@ class PassListFragment : Fragment() {
}
}
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 const val BUNDLE_KEY_TOPIC = "topic"

View file

@ -2,14 +2,14 @@ package org.ligi.passandroid.ui
import android.app.Activity
import android.content.Intent
import androidx.core.app.NavUtils
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NavUtils
import com.github.salomonbrys.kodein.instance
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) : KoinComponent {
class PassMenuOptions(val activity: Activity, val pass: Pass) {
val passStore: PassStore by inject()
val tracker: Tracker by inject()
val settings: Settings by inject()
var passStore: PassStore = App.kodein.instance()
var tracker: Tracker = App.kodein.instance()
var settings: Settings = App.kodein.instance()
fun process(item: MenuItem): Boolean {
when (item.itemId) {

View file

@ -4,17 +4,22 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.AttributeSet
import com.google.android.material.navigation.NavigationView
import android.util.AttributeSet
import com.github.salomonbrys.kodein.instance
import kotlinx.android.synthetic.main.navigation_drawer_header.view.*
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.ligi.passandroid.App
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), KoinComponent {
class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView(context, attrs) {
val passStore: PassStore by inject()
val passStore: PassStore = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
private fun getIntent(id: Int) = when (id) {
R.id.menu_settings -> Intent(context, PreferenceActivity::class.java)
@ -34,6 +39,8 @@ class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView
override fun onAttachedToWindow() {
super.onAttachedToWindow()
bus.register(this)
setNavigationItemSelectedListener { item ->
getIntent(item.itemId)?.let {
context.startActivity(it)
@ -41,12 +48,19 @@ class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView
} ?: false
}
passStoreUpdate()
onPassStoreChangeEvent(PassStoreChangeEvent)
}
@SuppressLint("RestrictedApi") // FIXME: temporary workaround for false-positive
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
bus.unregister(this)
}
private val marketUrl by lazy { context.getString(R.string.market_url, context.packageName) }
fun passStoreUpdate() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPassStoreChangeEvent(@Suppress("UNUSED_PARAMETER") passStoreChangeEvent: PassStoreChangeEvent) {
val passCount = passStore.passMap.size
getHeaderView(0).pass_count_header.text = context.getString(R.string.passes_nav, passCount)

View file

@ -2,16 +2,14 @@ package org.ligi.passandroid.ui
import android.os.Build
import android.os.Bundle
import androidx.core.app.*
import androidx.viewpager.widget.ViewPager
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
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 org.ligi.kaxt.disableRotation
import org.ligi.passandroid.R
@ -55,10 +53,7 @@ class PassViewActivity : PassViewActivityBase() {
pagerAdapter.refresh()
}
inner class CollectionPagerAdapter(
fm: FragmentManager,
private var passStoreProjection: PassStoreProjection
) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
inner class CollectionPagerAdapter(fm: FragmentManager, private var passStoreProjection: PassStoreProjection) : FragmentStatePagerAdapter(fm) {
override fun getCount(): Int = passStoreProjection.passList.count()
@ -66,7 +61,7 @@ class PassViewActivity : PassViewActivityBase() {
val fragment = PassViewFragment()
fragment.arguments = Bundle().apply {
putString(EXTRA_KEY_UUID, getPass(i).id)
putString(PassViewActivityBase.EXTRA_KEY_UUID, getPass(i).id)
}
return fragment
}
@ -94,9 +89,9 @@ class PassViewActivity : PassViewActivityBase() {
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
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)
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)
return super.onPrepareOptionsMenu(menu)
}
@ -118,7 +113,8 @@ class PassViewActivity : PassViewActivityBase() {
NavUtils.navigateUpTo(this, upIntent)
}
true
} else false
}
else false
}
else -> super.onOptionsItemSelected(item)

View file

@ -3,23 +3,22 @@ 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.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
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 androidx.appcompat.app.AlertDialog
import android.view.*
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
@ -29,6 +28,7 @@ import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpe
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.RuntimePermissions
import java.io.IOException
import java.util.*
@SuppressLint("Registered")
@RuntimePermissions
@ -150,26 +150,34 @@ open class PassViewActivityBase : PassAndroidActivity() {
@NeedsPermission("com.android.launcher.permission.INSTALL_SHORTCUT")
fun createShortcut() {
val shortcutIntent = Intent()
shortcutIntent.putExtra(EXTRA_KEY_UUID, currentPass.id)
shortcutIntent.component = ComponentName(BuildConfig.APPLICATION_ID,
BuildConfig.APPLICATION_ID + ".ui.PassViewActivity")
val passBitmap = currentPass.getBitmap(passStore, BITMAP_ICON)
val shortcutIcon = if (passBitmap != null) {
Bitmap.createScaledBitmap(passBitmap, 128, 128, true)
} else {
BitmapFactory.decodeResource(resources, R.drawable.ic_launcher)
}
val name: CharSequence = currentPass.description.let {
if (it.isNullOrEmpty()) "pass" else it
if (Build.VERSION.SDK_INT >= 25) {
val shortcutManager = getSystemService<ShortcutManager>(ShortcutManager::class.java)
val shortcut = ShortcutInfo.Builder(this, "id1")
.setShortLabel(currentPass.description ?: "")
.setIcon(Icon.createWithBitmap(shortcutIcon))
.setIntent(shortcutIntent)
.build()
shortcutManager.dynamicShortcuts = Arrays.asList(shortcut)
} else {
val intent = Intent("com.android.launcher.action.INSTALL_SHORTCUT")
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, currentPass.description)
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, shortcutIcon)
sendBroadcast(intent)
}
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 {

View file

@ -2,22 +2,23 @@ package org.ligi.passandroid.ui
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.core.text.util.LinkifyCompat
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 com.github.salomonbrys.kodein.instance
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.App
import org.ligi.passandroid.R
import org.ligi.passandroid.maps.PassbookMapsFacade
import org.ligi.passandroid.model.PassBitmapDefinitions
@ -28,7 +29,7 @@ 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()
private var passStore : PassStore = App.kodein.instance()
lateinit var pass : Pass
private fun processImage(view: ImageView, name: String, pass: Pass) {

View file

@ -6,17 +6,14 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO
import androidx.preference.PreferenceFragmentCompat
import org.koin.android.ext.android.inject
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.model.Settings
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
class PrefsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
val settings : Settings by inject()
override fun onResume() {
super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
@ -29,7 +26,7 @@ class PrefsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPref
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (key == getString(R.string.preference_key_nightmode)) {
@AppCompatDelegate.NightMode val nightMode = settings.getNightMode()
@AppCompatDelegate.NightMode val nightMode = App.settings.getNightMode()
if (nightMode == MODE_NIGHT_AUTO) {
ensureDayNightWithPermissionCheck()

View file

@ -0,0 +1,156 @@
package org.ligi.passandroid.ui
import android.app.IntentService
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Environment
import android.preference.PreferenceManager
import androidx.core.app.NotificationCompat
import com.github.salomonbrys.kodein.instance
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.App
import org.ligi.passandroid.R
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.events.ScanFinishedEvent
import org.ligi.passandroid.events.ScanProgressEvent
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.ui.UnzipPassController.InputStreamUnzipControllerSpec
import org.ligi.tracedroid.logging.Log
import java.io.File
import java.util.*
class SearchPassesIntentService : IntentService("SearchPassesIntentService") {
private var shouldFinish: Boolean = false
private var notifyManager: NotificationManager? = null
private var progressNotificationBuilder: NotificationCompat.Builder? = null
private var findNotificationBuilder: NotificationCompat.Builder? = null
private var foundList: MutableList<Pass>? = null
private var lastProgressUpdate: Long = 0
val passStore: PassStore = App.kodein.instance()
val bus: EventBus = App.kodein.instance()
val tracker: Tracker = App.kodein.instance()
override fun onHandleIntent(intent: Intent?) {
foundList = ArrayList()
notifyManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val pendingIntent = PendingIntent.getActivity(applicationContext, 1, Intent(baseContext, PassListActivity::class.java), 0)
progressNotificationBuilder = NotificationCompat.Builder(this).setContentTitle(getString(R.string.scanning_for_passes))
.setSmallIcon(R.drawable.ic_refresh)
.setOngoing(true)
.setContentIntent(pendingIntent)
.setProgress(1, 1, true)
findNotificationBuilder = NotificationCompat.Builder(this).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)
bus.post(ScanFinishedEvent(foundList!!))
}
/**
* recursive voyage starting at path to find files named .pkpass
*/
private fun searchIn(path: File, recursive: Boolean) {
if (System.currentTimeMillis() - lastProgressUpdate > 1000) {
lastProgressUpdate = System.currentTimeMillis()
val msg = path.toString()
bus.post(ScanProgressEvent(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))
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,4 +1,4 @@
package org.ligi.passandroid.scan
package org.ligi.passandroid.ui
import android.app.NotificationManager
import android.app.PendingIntent
@ -10,10 +10,6 @@ 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

@ -1,15 +1,16 @@
package org.ligi.passandroid.ui
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem
import com.github.salomonbrys.kodein.instance
import com.ortiz.touch.TouchImageView
import org.koin.android.ext.android.inject
import org.ligi.passandroid.App
import org.ligi.passandroid.model.PassStore
class TouchImageActivity : AppCompatActivity() {
val passStore: PassStore by inject()
val passStore: PassStore = App.kodein.instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -1,8 +1,8 @@
package org.ligi.passandroid.ui
import android.content.Context
import org.ligi.passandroid.App
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
import org.ligi.passandroid.ui.UnzipPassController.FailCallback
import org.ligi.passandroid.ui.UnzipPassController.SuccessCallback
import java.io.File
@ -14,7 +14,7 @@ open class UnzipControllerSpec(var targetPath: File,
val failCallback: FailCallback?) {
var overwrite = false
constructor(context: Context, passStore: PassStore, onSuccessCallback: SuccessCallback?, failCallback: FailCallback?, settings: Settings)
: this(settings.getPassesDir(), context, passStore, onSuccessCallback, failCallback)
constructor(context: Context, passStore: PassStore, onSuccessCallback: SuccessCallback?, failCallback: FailCallback?)
: this(App.settings.getPassesDir(), context, passStore, onSuccessCallback, failCallback)
}

View file

@ -8,28 +8,20 @@ import android.os.Build
import android.os.ParcelFileDescriptor
import net.lingala.zip4j.ZipFile
import net.lingala.zip4j.exception.ZipException
import okio.buffer
import okio.source
import okio.Okio
import org.json.JSONObject
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.ligi.passandroid.Tracker
import org.ligi.passandroid.App
import org.ligi.passandroid.functions.createPassForImageImport
import org.ligi.passandroid.functions.createPassForPDFImport
import org.ligi.passandroid.functions.readJSONSafely
import org.ligi.passandroid.model.AndroidSettings
import org.ligi.passandroid.model.InputStreamWithSource
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.model.Settings
import org.ligi.tracedroid.logging.Log
import java.io.File
import java.io.FileOutputStream
import java.util.*
object UnzipPassController : KoinComponent {
val tracker :Tracker by inject()
val settings : Settings by inject()
object UnzipPassController {
interface SuccessCallback {
fun call(uuid: String)
@ -46,7 +38,7 @@ object UnzipPassController : KoinComponent {
processFile(FileUnzipControllerSpec(tempFile.absolutePath, spec))
tempFile.delete()
} catch (e: Exception) {
tracker.trackException("problem processing InputStream", e, false)
App.tracker.trackException("problem processing InputStream", e, false)
spec.failCallback?.fail("problem with temp file: $e")
}
@ -115,7 +107,7 @@ object UnzipPassController : KoinComponent {
if (Build.VERSION.SDK_INT >= 21) {
try {
val file = File(spec.zipFileString)
val readUtf8 = file.source().buffer().readUtf8(4)
val readUtf8 = Okio.buffer(Okio.source(file)).readUtf8(4)
if (readUtf8 == "%PDF") {
val open = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
val pdfRenderer = PdfRenderer(open)
@ -165,6 +157,6 @@ object UnzipPassController : KoinComponent {
}
class InputStreamUnzipControllerSpec(internal val inputStreamWithSource: InputStreamWithSource, context: Context, passStore: PassStore,
onSuccessCallback: SuccessCallback?, failCallback: FailCallback?) : UnzipControllerSpec(context, passStore, onSuccessCallback, failCallback, settings)
onSuccessCallback: SuccessCallback?, failCallback: FailCallback?) : UnzipControllerSpec(context, passStore, onSuccessCallback, failCallback)
}

View file

@ -1,25 +1,22 @@
package org.ligi.passandroid.ui.edit
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.edit_field.view.*
import kotlinx.android.synthetic.main.edit_fields.view.*
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.model.PassStore
import org.ligi.passandroid.model.pass.PassField
import org.ligi.passandroid.model.pass.PassImpl
class FieldsEditFragment : Fragment() {
val passStore by inject<PassStore>()
private fun getPass(): PassImpl = passStore.currentPass as PassImpl
private fun getPass(): PassImpl = App.passStore.currentPass as PassImpl
private var isEditingHiddenFields: Boolean = false

View file

@ -2,13 +2,15 @@ package org.ligi.passandroid.ui.edit.dialogs
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import org.greenrobot.eventbus.EventBus
import org.ligi.kaxt.inflate
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassRefreshEvent
import org.ligi.passandroid.model.pass.BarCode
import org.ligi.passandroid.model.pass.Pass
import org.ligi.passandroid.ui.edit.BarcodeEditController
fun showBarcodeEditDialog(context: AppCompatActivity, refreshCallback: () -> Unit, pass: Pass, barCode: BarCode) {
fun showBarcodeEditDialog(context: AppCompatActivity, bus: EventBus, pass: Pass, barCode: BarCode) {
val view = context.inflate(R.layout.barcode_edit)
val barcodeEditController = BarcodeEditController(view, context, barCode)
@ -18,7 +20,7 @@ fun showBarcodeEditDialog(context: AppCompatActivity, refreshCallback: () -> Uni
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->
pass.barCode = barcodeEditController.getBarCode()
refreshCallback.invoke()
bus.post(PassRefreshEvent(pass))
}
.show()
}

View file

@ -1,13 +1,15 @@
package org.ligi.passandroid.ui.edit.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassRefreshEvent
import org.ligi.passandroid.functions.getCategoryDefaultBG
import org.ligi.passandroid.functions.getHumanCategoryString
import org.ligi.passandroid.model.pass.Pass
@ -16,7 +18,7 @@ import org.ligi.passandroid.ui.views.BaseCategoryIndicatorView
private val passTypes = arrayOf(PassType.BOARDING, PassType.EVENT, PassType.GENERIC, PassType.LOYALTY, PassType.VOUCHER, PassType.COUPON)
fun showCategoryPickDialog(context: Context, pass: Pass, refreshCallback: () -> Unit) {
fun showCategoryPickDialog(context: Context, pass: Pass, bus: EventBus) {
val adapter = object : BaseAdapter() {
@ -45,7 +47,7 @@ fun showCategoryPickDialog(context: Context, pass: Pass, refreshCallback: () ->
val builder = AlertDialog.Builder(context)
builder.setAdapter(adapter) { _, position ->
pass.type = passTypes[position]
refreshCallback.invoke()
bus.post(PassRefreshEvent(pass))
}
builder.setTitle(R.string.select_category_dialog_title)
builder.setNegativeButton(android.R.string.cancel, null)

View file

@ -1,19 +1,21 @@
package org.ligi.passandroid.ui.edit.dialogs
import android.content.Context
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import android.view.LayoutInflater
import kotlinx.android.synthetic.main.edit_color.view.*
import org.greenrobot.eventbus.EventBus
import org.ligi.passandroid.R
import org.ligi.passandroid.events.PassRefreshEvent
import org.ligi.passandroid.model.pass.Pass
fun showColorPickDialog(context: Context, pass: Pass, refreshCallback : () -> Unit) {
fun showColorPickDialog(context: Context, pass: Pass, bus: EventBus) {
val inflate = LayoutInflater.from(context).inflate(R.layout.edit_color, null)
inflate.colorPicker.color = pass.accentColor
inflate.colorPicker.oldCenterColor = pass.accentColor
AlertDialog.Builder(context).setView(inflate).setPositiveButton(android.R.string.ok) { _, _ ->
pass.accentColor = inflate.colorPicker.color
refreshCallback.invoke()
bus.post(PassRefreshEvent(pass))
}.setNegativeButton(android.R.string.cancel, null).setTitle(R.string.change_color_dialog_title).show()
}

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/progress_container"
android:orientation="vertical"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/progress"
android:layout_centerHorizontal="true"
android:text="@string/please_wait"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/progress_container"
android:orientation="vertical"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/progress"
android:layout_gravity="center"
android:text="@string/scan_pass_message"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/progress"
android:layout_gravity="center"
android:id="@+id/progress_text"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>

View file

@ -17,7 +17,7 @@ Im Grunde ist es ein Ersatz für Karten aus Plastik oder Papier - kann so gutes
Beispiel-Karten
Brauchst du ein paar Karten um die Anwendung auszuprobieren?
Brauchst du ein paar Karten um die Anwendung auszuprobieren?
<a href=\'http://espass.it/examples\'>Hier findest du einige!</a>
<h1>Wie funktioniert das Ganze?</h1>
@ -50,6 +50,10 @@ für pull requests!
<h1>Mehr</h1>
PassAndroid ist am besten zu Öffnen mit <a href=\'https://play.google.com/store/apps/details?id=org.ligi.fast&referrer=utm_source%3Dticketview%26utm_medium%3Dapp\'>FAST App Search Tool</a>, welches kostenlos und ohne Berechtigung zu erhalten ist.
<h1>Feedback</h1>
Es wäre toll zu erfahren, wie ihr die Anwendung benutzt, was ihr toll oder was ihr nicht so toll findet.
Kommt am besten in <a href=\'https://plus.google.com/communities/116353894782342292067\'>die Google+ Community</a> - oder schreibt mir <a href=\'mailto:ligi@ligi.de\'>eine E-Mail.</a>
<h1>Haftungsausschluss</h1>
<p>This app is not affiliated with Apple - Passbook might be trademarked by Apple, but it\'s introduced like a standard so this should be okay.</p>
<p>
@ -119,6 +123,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="found__pass">Und %d mehr</string>
<string name="scan_finished_dialog_title">Suche abgeschlossen</string>
<string name="scan_finished_dialog_text">%d Pässe gefunden</string>
<string name="scan_progressdialog_title">Suche…</string>
<string name="scan_progressdialog_message">Bitte warten</string>
<string name="scan_dialog_send_background_button">Hintergrund</string>
<string name="emtytrash_label">Leere Papierkorb</string>
<string name="barcode_edit_hint_alternative_message">Alternative Nachricht</string>
<string name="barcode_edit_random">Zufällig</string>

View file

@ -8,9 +8,9 @@
<string name="barcode">código de barras</string>
<string name="help_label">Ayuda</string>
<string name="help_content"><![CDATA[
<h1>¿Qué es esto?</h1>
<h1>¿Qué es esto?</h1>
Esta aplicación es un visor de passbook - un passbook puedes ser p. ej. una <b>tarjeta de embarque</b> para tu próximo vuelo, un cupón para una deliciosa bebida en tu próxima visita, una entrada de cine, una tarjeta de socio, hay un motón de posibilidades. Básicamente es el reemplazo de un trozo de papel o plástico que puedes llevar normalmente contigo - salvemos unos cuantos árboles <b>¡reduce desperdicios!</b>
Esta aplicación es un visor de passbook - un passbook puedes ser p. ej. una <b>tarjeta de embarque</b> para tu próximo vuelo, un cupón para una deliciosa bebida en tu próxima visita, una entrada de cine, una tarjeta de socio, hay un motón de posibilidades. Básicamente es el reemplazo de un trozo de papel o plástico que puedes llevar normalmente contigo - salvemos unos cuantos árboles <b>¡reduce desperdicios!</b>
<h1>Ejemplos de pases</h1>
@ -28,6 +28,9 @@ y <a href=\'https://github.com/simontb\'>Simon Tenbeitel</a>
y <a href=\'https://github.com/irgendwr\'>Jonas Bögle</a>
por sus sugerencias!
<H1>Sugerencias</H1>
Puedes contribuir en <a href=\'https://plus.google.com/communities/116353894782342292067\'>Google+ Community</a> o enviar un email al <a href=\'mailto:ligi@ligi.de\'>autor.</a> Si te gusta la aplicación puedes valorarla en <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a>, también me gustaría leer algunas de vuestras historias con el uso de esta aplicación.
<h1>Historia</h1>
Me topé con los archivos Passbook cuando recibí por primera vez mi entrada para el 29C3 - me gustó la idea - pero no existía una aplicación para Android que me sirviese. Comprobé que la aplicación era bastante sencilla (únicamente un archivo zip con json e imágenes) - La primera versión fue realizada rápidamente y era funcional para mi (me dio acceso al 29C3) - Durante el siguiente año no trabajé en seguir desarrollando la aplicación ya que no tenia mayor uso práctico, pero los usuarios continuaron descargandola (ahora cerca de 100k) por lo que arreglé el procesado de algunos pases que me reportaron (que mostraban de manera incorrecta los json…)
@ -51,7 +54,7 @@ Esta aplicación se abre mejor con <a href=\'https://play.google.com/store/apps/
<h1>Aviso legal</h1>
<p>Esta aplicación no esta afiliada con Apple - Passbook puede ser una marca de Apple, pero fue introducido como un estandar, por lo que esto estaría bien.</p>
<p>
ESTE SOFTWARE SE OFRECE \"TAL CUAL\" Y CUALQUIER GARANTÍA EXPRESA O IMPLÍCITA, INCLUYENDO, PERO NO LIMITADO A, LAS GARANTÍAS DE COMERCIALIZACIÓN Y APTITUD PARA UN PROPÓSITO PARTICULAR. EN NINGÚN CASO EL PROPIETARIO DEL COPYRIGHT O LOS COLABORADORES SERÁN RESPONSABLES POR NINGÚN DAÑO DIRECTO, INDIRECTO, INCIDENTAL, ESPECIAL, EJEMPLAR O CONSECUENTE (INCLUYENDO, PERO NO LIMITADO A LA OBTENCIÓN DE BIENES O SERVICIOS SUSTITUTOS, LA PÉRDIDA DE USO, DE DATOS O DE BENEFICIOS; O INTERRUPCIÓN DE NEGOCIO) CAUSADO SIN EMBARGO Y EN CUALQUIER TEORÍA DE RESPONSABILIDAD, YA SEA POR CONTRATO, RESPONSABILIDAD ESTRICTA O AGRAVIO (INCLUYENDO NEGLIGENCIA) QUE RESULTEN DEL USO DE ESTE SOFTWARE, AUNQUE SE HAYA ADVERTIDO DE LA POSIBILIDAD DE TALES DAÑOS.
ESTE SOFTWARE SE OFRECE \"TAL CUAL\" Y CUALQUIER GARANTÍA EXPRESA O IMPLÍCITA, INCLUYENDO, PERO NO LIMITADO A, LAS GARANTÍAS DE COMERCIALIZACIÓN Y APTITUD PARA UN PROPÓSITO PARTICULAR. EN NINGÚN CASO EL PROPIETARIO DEL COPYRIGHT O LOS COLABORADORES SERÁN RESPONSABLES POR NINGÚN DAÑO DIRECTO, INDIRECTO, INCIDENTAL, ESPECIAL, EJEMPLAR O CONSECUENTE (INCLUYENDO, PERO NO LIMITADO A LA OBTENCIÓN DE BIENES O SERVICIOS SUSTITUTOS, LA PÉRDIDA DE USO, DE DATOS O DE BENEFICIOS; O INTERRUPCIÓN DE NEGOCIO) CAUSADO SIN EMBARGO Y EN CUALQUIER TEORÍA DE RESPONSABILIDAD, YA SEA POR CONTRATO, RESPONSABILIDAD ESTRICTA O AGRAVIO (INCLUYENDO NEGLIGENCIA) QUE RESULTEN DEL USO DE ESTE SOFTWARE, AUNQUE SE HAYA ADVERTIDO DE LA POSIBILIDAD DE TALES DAÑOS.
</p>
]]></string>
@ -114,6 +117,9 @@ ESTE SOFTWARE SE OFRECE \"TAL CUAL\" Y CUALQUIER GARANTÍA EXPRESA O IMPLÍCITA,
<string name="found__pass">Y %d más</string>
<string name="scan_finished_dialog_title">Búsqueda terminada</string>
<string name="scan_finished_dialog_text">encontrados %d Passbooks</string>
<string name="scan_progressdialog_title">Buscando…</string>
<string name="scan_progressdialog_message">Por favor, espere</string>
<string name="scan_dialog_send_background_button">fondo</string>
<string name="emtytrash_label">Vaciar papelera</string>
<string name="barcode_edit_hint_alternative_message">mensaje alternativo</string>
<string name="barcode_edit_random">aleatorio</string>

View file

@ -65,6 +65,9 @@
<string name="found__pass">Eta %d gehiago</string>
<string name="scan_finished_dialog_title">Eskaneatzen amaitu da.</string>
<string name="scan_finished_dialog_text">%d pase aurkitu dira</string>
<string name="scan_progressdialog_title">Bilatzen…</string>
<string name="scan_progressdialog_message">Mesedez itxaron.</string>
<string name="scan_dialog_send_background_button">atzealdea</string>
<string name="emtytrash_label">hustu zakarrontzia</string>
<string name="barcode_edit_hint_alternative_message">Mezu alternatiboa</string>
<string name="barcode_edit_random">ausazkoa</string>

View file

@ -37,6 +37,9 @@ et <a href=\"https://github.com/cketti\">Cketti</a>,
et <a href=\"https://github.com/irgendwr\">Jonas Bögle</a>
pour les pull-requests !
<h1>Feedback / Réactions</h1>
Partagez vos réactions dans la <a href=\'https://plus.google.com/communities/116353894782342292067\'>Communauté Google+</a> ou bien vous pouvez envoyer <a href=\'mailto:ligi@ligi.de\'>un mail à l\'auteur.</a> Si vous aimez cette application, notez-là sur <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a> J\'apprécie également connaître vos différentes utilisations de cette application.
<h1>Historique</h1>
Je suis tombé sur les fichiers Passbook quand j\'ai reçu mon billet pour le 29c3 - J\'ai aimé l\'idée - mais il n\'y avait pas d\'application Android qui fonctionnait pour moi. J\'ai vu que la mise en œuvre est assez facile (juste un conteneur zip avec json et images) - La première version a été construire très rapidement et fonctionnait bien pour moi (elle a même eu son entrée au 29C3) - L\'année suivante, je n\'ai pas travaillé sur cette app beaucoup, puisque je n\'avais pas vraiment de cas d\'utilisation. Pourtant beaucoup d\'utilisateurs téléchargeaient l\'application (presque 100\'000 désormais) et je continuais de traiter certains passes qui m\'étaient envoyés (certains contenaient des json vraiment corrompus ..)
Un an plus tard - juste avant le 30C3 - j\'ai à nouveau effectué un peu de travail sur cette application - pour 2 raisons:
@ -112,6 +115,9 @@ THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLU
<string name="found__pass">Et %d plus</string>
<string name="scan_finished_dialog_title">Recherche terminée</string>
<string name="scan_finished_dialog_text">%d passes trouvés</string>
<string name="scan_progressdialog_title">Recherche…</string>
<string name="scan_progressdialog_message">Veuillez patienter.</string>
<string name="scan_dialog_send_background_button">arrière-plan</string>
<string name="emtytrash_label">corbeille vide</string>
<string name="barcode_edit_hint_alternative_message">message alternatif</string>
<string name="barcode_edit_random">aléatoire</string>

View file

@ -37,6 +37,9 @@ Moitas grazas a <a href=\'http://ltorrecilla.com/\'>Luis Torrecilla</a> polo Des
& <a href=\'https://github.com/irgendwr\'>Jonas Bögle</a>
polos pull-requests!
<h1>Reporte</h1>
Reporte en <a href=\'https://plus.google.com/communities/116353894782342292067\'>Google+ Community</a> ou pode deixar <a href=\'mailto:ligi@ligi.de\'>aquí un correo.</a> Si lle gusta puntúe en <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a> Gustaríame saber tamén qué usos lle da a este app.
<h1>Historial</h1>
Batín nos ficheiros passbook cando me apuntei ao 29c3 - Gostei da idea - pero non había un aplicativo Android dispoñible. Vin que era doado de implementar ( só un contedor zip con json e imaxes ) - A primeira versión foi escrita moi rápido e para min xa funcionaba ( entrei ao 29C3 ) - No seguinte ano non traballei moito no app xa que non tiña moito uso que darlle pero había moita xente descargándoo ( case 100k agora ) e arranxeino para varios passes que me enviaron ( algúns passes tiñan json defectuoso … )
Un ano despois - xusto antes de 30C3 - voltei traballar un pouco no app - por dúas razóns:
@ -117,6 +120,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="found__pass">e %d máis</string>
<string name="scan_finished_dialog_title">Busca finalizada.</string>
<string name="scan_finished_dialog_text">atopou %d passes</string>
<string name="scan_progressdialog_title">Buscando…</string>
<string name="scan_progressdialog_message">Por favor agarde.</string>
<string name="scan_dialog_send_background_button">fondo</string>
<string name="emtytrash_label">lixo baldeiro</string>
<string name="barcode_edit_hint_alternative_message">Mensaxe alternativa</string>
<string name="barcode_edit_random">ao chou</string>

View file

@ -41,6 +41,9 @@ Un grande grazie a <a href=\"http://ltorrecilla.com/\">Luis Torrecilla</a> per i
& <a href=\"https://github.com/irgendwr\">Jonas Bögle</a>
per le pull-request!
<h1>Feedback</h1>
Dicci cosa ne pensi sulla <a href=\'https://plus.google.com/communities/116353894782342292067\'>Community di Google+</a> oppure <a href=\'mailto:ligi@ligi.de\'>scrivi un\'email all\'autore.</a> Se ti piace l\'app, votala sul <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Play Store</a>. Inoltre mi piace tanto sapere come viene utilizzata quest\'app!
<h1>Legal</h1>
<p>Quest\'app non è affiliata con Apple - Passbook può essere un marchio registrato da Apple, ma è stato introdotto come uno standard. Quindi dovrebbe essere OK.</p>
<p>
@ -107,6 +110,9 @@ IL SOFTWARE VIENE FORNITO \"COSÌ COM\'È\", SENZA GARANZIE, ESPRESSE O IMPLICIT
<string name="found__pass">E altri %d</string>
<string name="scan_finished_dialog_title">Scansione finita.</string>
<string name="scan_finished_dialog_text">trovato %d Pass</string>
<string name="scan_progressdialog_title">Sto cercando…</string>
<string name="scan_progressdialog_message">Aspetta.</string>
<string name="scan_dialog_send_background_button">background</string>
<string name="emtytrash_label">svuota cestino</string>
<string name="barcode_edit_hint_alternative_message">Messaggio alternativo</string>
<string name="barcode_edit_random">casuale</string>

View file

@ -41,6 +41,9 @@ and <a href=\'https://github.com/cketti\'>cketti</a>,
and <a href=\'https://github.com/irgendwr\'>Jonas Bögle</a>
for pull-requests!
<h1>フィードバック</h1>
Give feedback in the <a href=\'https://plus.google.com/communities/116353894782342292067\'>Google+ Community</a> or you can drop the <a href=\'mailto:ligi@ligi.de\'>author a mail.</a> If you like the app rate it on <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a> I also would love to hear stories about where you used this app.
<h1>履歴</h1>
I stumbled upon Passbook files when i received my ticket for the 29c3 - I liked the Idea - but there was no Android App which was working for me. I saw that the implementation is quite easy ( just a zip container with json and images ) - The first version was build very quick & was working for me ( got entry to 29C3 ) - In the following year I have not worked on this app much as I had no real use-cases but there where a lot of users downloading the app ( nearly 100k now ) and I fixed processing of some passes that where send to me ( some passes contain really broken json .. )
@ -122,6 +125,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="found__pass">および %d 以上</string>
<string name="scan_finished_dialog_title">スキャンが終了しました。</string>
<string name="scan_finished_dialog_text">%d チケットが見つかりました</string>
<string name="scan_progressdialog_title">検索中…</string>
<string name="scan_progressdialog_message">しばらくお待ちください。</string>
<string name="scan_dialog_send_background_button">バックグラウンド</string>
<string name="emtytrash_label">ゴミ箱を空にする</string>
<string name="barcode_edit_hint_alternative_message">代りのメッセージ</string>
<string name="barcode_edit_random">ランダム</string>

View file

@ -45,6 +45,9 @@ Veel dank aan <a href=\'http://ltorrecilla.com/\'>Luis Torrecilla</a> voor het d
& <a href=\'https://github.com/irgendwr\'>Jonas Bögle</a>
voor verbeteringen in het algemeen!
<h1>Feedback</h1>
Geef feedback in de <a href=\'https://plus.google.com/communities/116353894782342292067\'>Google+ Community</a> of je kan een <a href=\'mailto:ligi@ligi.de\'>email</a> naar de maker sturen. Als je de app leuk vindt, kan je dit hier laten weten: <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a> Ik ben ook altijd benieuwd om te weten op welke manier je de app gebruikt.
<h1>Geschiedenis</h1>
Ik kwam de eerste keer in aanraking met Passbook bestanden toen ik m\'n ticket voor de 29c3 ontving. Ik vond het een leuk idee, maar er was geen Android app die voor me werkte. Ik zag dat de implementatie eenvoudig was (gewoon een zip bestand met wat json en wat afbeeldingen). De eerste versie was heel snel ontwikkeld en werkte (ik geraakte binnen op 29c3). Het jaar nadien heb ik niet veel gewerkt aan de app vermits ik niet veel concrete wensen had, maar er waren veel mensen die de app gebruikten en regelmatig deed ik verbeteringen als mensen me een pass doorstuurden die niet bleek te werken (sommige passes bevatten ongeldige json inhoud...).
@ -128,6 +131,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="found__pass">En %d meer</string>
<string name="scan_finished_dialog_title">Zoekactie afgewerkt.</string>
<string name="scan_finished_dialog_text">%d passen gevonden</string>
<string name="scan_progressdialog_title">Zoeken…</string>
<string name="scan_progressdialog_message">Even geduld.</string>
<string name="scan_dialog_send_background_button">achtergrond</string>
<string name="emtytrash_label">Prullenbak leegmaken</string>
<string name="barcode_edit_hint_alternative_message">alternatieve boodschap</string>
<string name="barcode_edit_random">willekeurig</string>

View file

@ -29,6 +29,9 @@
и <a href=\"https://github.com/irgendwr\">Jonas Bögle</a>
за pull-requests!
<h1>Обратная связь</h1>
Оставляйте отзывы в <a href=\'https://plus.google.com/communities/116353894782342292067\'>сообществе Google+</a> или вы можете <a href=\'mailto:ligi@ligi.de\'>написать автору.</a> Если приложение вам понравилось, оставьте отзыв в <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a>. Интересно было бы прочитать вашу историю, как вы использовали это приложение.
<h1>История создания</h1>
Я наткнулся на файлы Passbook когда получил билет на конференцию \"29C3\" (ежегодные компьютерные конференции, эта проводилась в 2013г.), мне понравилась идея но не было приложения под Android которое меня бы устроило. Я увидел что технически реализация довольно проста: обычный zip архив со скриптом json и картинками. Первая версия приложения была создана очень быстро и сработала, меня пустили на конференцию. В течении следующего года я почти не занимался этим приложением, поскольку лично у меня оно не находило постоянного применения. Однако много людей скачали приложение и используют его (около 100 тысяч человек), люди писали отзывы, присылали файлы passbook которые не открывались в приложении, пришлось поработать и добиться нормальной работы с файлами passbook, в которых json был просто \"сломан\", написан не по стандарту.
@ -110,6 +113,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="found__pass">Еще %d</string>
<string name="scan_finished_dialog_title">Поиск завершен.</string>
<string name="scan_finished_dialog_text">найдено %d карточек</string>
<string name="scan_progressdialog_title">Поиск…</string>
<string name="scan_progressdialog_message">Пожалуйста подождите.</string>
<string name="scan_dialog_send_background_button">в фоне</string>
<string name="emtytrash_label">очистить корзину</string>
<string name="barcode_edit_hint_alternative_message">альтернативное Сообщение</string>
<string name="barcode_edit_random">случайный</string>

View file

@ -46,7 +46,7 @@ and <a href=\'https://github.com/irgendwr\'>Jonas Bögle</a>
for pull-requests!
<h1>Feedback</h1>
Give feedback via email <a href=\'mailto:ligi@ligi.de\'> to the author.</a>
Give feedback in the <a href=\'https://plus.google.com/communities/116353894782342292067\'>Google+ Community</a> or you can drop the <a href=\'mailto:ligi@ligi.de\'>author a mail.</a>
If you like the app rate it on <a href=\'https://play.google.com/store/apps/details?id=org.ligi.ticketviewer\'>Google Play</a>.
I also would love to hear stories about where you used this app.
@ -96,7 +96,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="pass_to_calendar">"to Calendar"</string>
<string name="invalid_passbook_title">Invalid Passbook</string>
<string name="preparing_pass">Preparing Pass</string>
<string name="please_wait">Importing the pass - please wait a bit</string>
<string name="please_wait">please wait - shouldn\'t be long</string>
<string name="passbook_is_shared_subject">a Passbook is shared with you</string>
<string name="passbook_share_chooser_title">How to send Pass?</string>
<string name="dialog_delete_confirm_text">Do you really want to delete this passbook?</string>
@ -132,6 +132,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="found__pass">And %d more</string>
<string name="scan_finished_dialog_title">Scan finished.</string>
<string name="scan_finished_dialog_text">found %d passes</string>
<string name="scan_progressdialog_title">Searching…</string>
<string name="scan_progressdialog_message">Please wait.</string>
<string name="scan_dialog_send_background_button">background</string>
<string name="emtytrash_label">empty trash</string>
<string name="barcode_edit_hint_alternative_message">alternative Message</string>
<string name="barcode_edit_random">random</string>
@ -216,7 +219,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
<string name="error_no_permission_msg">Need permission to import pass</string>
<string name="error_no_permission_title">Permission Error</string>
<string name="scan_pass_message">Scanning for passes. Please wait.</string>
</resources>

View file

@ -1,18 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/preference_key_autolight"
android:summary="@string/preference_autolight_summary"
android:title="@string/preference_autolight_title"
app:iconSpaceReserved="false" />
android:defaultValue="true"
android:title="@string/preference_autolight_title"/>
<CheckBoxPreference
android:key="@string/preference_key_condensed"
android:summary="@string/preference_condensed_summary"
android:title="@string/preference_condensed_title"
app:iconSpaceReserved="false" />
android:title="@string/preference_condensed_title"/>
<ListPreference
android:defaultValue="0"
@ -20,8 +17,7 @@
android:entryValues="@array/sort_order_keys"
android:key="@string/preference_key_sort"
android:summary="@string/preference_sort_summary"
android:title="@string/preference_sort_title"
app:iconSpaceReserved="false" />
android:title="@string/preference_sort_title"/>
<ListPreference
android:defaultValue="auto"
@ -29,7 +25,6 @@
android:entryValues="@array/nightmode_keys"
android:key="@string/preference_key_nightmode"
android:summary="@string/preference_daynight_summary"
android:title="@string/preference_daynight_title"
app:iconSpaceReserved="false" />
android:title="@string/preference_daynight_title"/>
</PreferenceScreen>

View file

@ -1,5 +1,10 @@
package org.ligi.passandroid
import android.content.Context
import com.github.salomonbrys.kodein.Kodein
import com.github.salomonbrys.kodein.bind
import com.github.salomonbrys.kodein.singleton
fun createTracker(context: Context) = NotTracker() as Tracker
fun createTrackerKodeinModule(context: Context) = Kodein.Module {
bind<Tracker>() with singleton { NotTracker() }
}

View file

@ -1,5 +1,10 @@
package org.ligi.passandroid
import android.content.Context
import com.github.salomonbrys.kodein.Kodein
import com.github.salomonbrys.kodein.bind
import com.github.salomonbrys.kodein.singleton
fun createTracker(context: Context) = AnalyticsTracker(context) as Tracker
fun createTrackerKodeinModule(context: Context) = Kodein.Module {
bind<Tracker>() with singleton { AnalyticsTracker(context) }
}

View file

@ -11,16 +11,12 @@ import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.gms.maps.model.MarkerOptions
import org.koin.android.ext.android.inject
import org.ligi.kaxt.startActivityFromClass
import org.ligi.passandroid.model.PassStore
import org.ligi.passandroid.ui.PassViewActivityBase
class LocationsMapFragment : SupportMapFragment() {
var clickToFullscreen = false
val passStore : PassStore by inject()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = super.onCreateView(inflater, container, savedInstanceState)
val baseActivity = activity as PassViewActivityBase
@ -29,7 +25,7 @@ class LocationsMapFragment : SupportMapFragment() {
map.setOnMapLoadedCallback {
if (clickToFullscreen)
map.setOnMapClickListener {
passStore.currentPass = baseActivity.currentPass
App.passStore.currentPass = baseActivity.currentPass
baseActivity.startActivityFromClass(FullscreenMapActivity::class.java)
}

View file

@ -19,9 +19,9 @@ sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
implementation 'com.google.appengine:appengine-endpoints:1.9.78'
implementation 'com.google.appengine:appengine-endpoints-deps:1.9.78'
implementation 'com.google.appengine:appengine-endpoints:1.9.59'
implementation 'com.google.appengine:appengine-endpoints-deps:1.9.59'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'com.googlecode.objectify:objectify:6.0.6'
implementation 'com.googlecode.objectify:objectify:4.1.3'
implementation 'com.ganyo:gcm-server:1.1.0'
}

View file

@ -1,29 +1,25 @@
buildscript {
ext {
kotlin_version = '1.3.70'
mockito_version = '3.2.4'
kotlin_version = '1.3.50'
mockito_version = '3.1.0'
}
repositories {
jcenter()
google()
maven { url 'https://storage.googleapis.com/r8-releases/raw' }
maven { url 'https://www.jitpack.io' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools:r8:2.0.39' // Must be before the Gradle Plugin for Android.
classpath 'com.android.tools.build:gradle:3.6.1'
classpath 'com.android.tools.build:gradle:3.5.1'
classpath 'com.github.trevjonez.composer-gradle-plugin:plugin:0.13.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.28.0'
classpath 'de.mobilej.unmock:UnMockPlugin:0.7.5'
classpath 'com.github.triplet.gradle:play-publisher:1.2.2'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
classpath 'de.mobilej.unmock:UnMockPlugin:0.7.3'
classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
}
}
repositories {
google()
}
apply from: 'https://raw.githubusercontent.com/ligi/gradle-common/master/versions_plugin_stable_only.gradle'
}

View file

@ -1,3 +1,2 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xms128m -Xmx1024m -XX:+CMSClassUnloadingEnabled
android.useAndroidX=true

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

29
gradlew vendored
View file

@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
i=$((i+1))
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@ -175,9 +175,14 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"