Compare commits
34 commits
refactorin
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
6cdd7ce4f7 | ||
|
38ca9d7911 | ||
|
5a19e83a3c | ||
|
4088486344 | ||
|
714ffd0ea1 | ||
|
0e415479f7 | ||
|
3339d318fe | ||
|
2a8b39ce6b | ||
|
105b3195a2 | ||
|
f0485d7865 | ||
|
6c5298b071 | ||
|
5fb07e0508 | ||
|
2831f60c30 | ||
|
b98155e48b | ||
|
a61bae906e | ||
|
96cf096f89 | ||
|
1b6353d470 | ||
|
dd187ab69f | ||
|
a6f7d4699f | ||
|
19372eaa27 | ||
|
c3a5912ff0 | ||
|
85a4501d12 | ||
|
b5052184b2 | ||
|
ba931c787f | ||
|
4e3da926f6 | ||
|
5a6c43fe76 | ||
|
af4c42033c | ||
|
7fc5c58d98 | ||
|
bf6c0226ff | ||
|
0b4aad772b | ||
|
4e62614ef9 | ||
|
1da68a5706 | ||
|
ec80dced3d | ||
|
dcb8fb6eff |
71 changed files with 699 additions and 647 deletions
|
@ -18,8 +18,8 @@ android {
|
|||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
versionCode 351
|
||||
versionName "3.5.1"
|
||||
versionCode 356
|
||||
versionName "3.5.6"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 29
|
||||
applicationId "org.ligi.passandroid"
|
||||
|
@ -97,6 +97,10 @@ android {
|
|||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
|
||||
}
|
||||
|
||||
debug {
|
||||
multiDexEnabled true
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -108,10 +112,13 @@ dependencies {
|
|||
implementation 'org.permissionsdispatcher:permissionsdispatcher:4.6.0'
|
||||
kapt 'org.permissionsdispatcher:permissionsdispatcher-processor:4.6.0'
|
||||
|
||||
implementation 'com.github.salomonbrys.kodein:kodein:4.1.0'
|
||||
compileOnly 'org.glassfish:javax.annotation:3.1.1'
|
||||
implementation "org.koin:koin-android:2.1.2"
|
||||
|
||||
androidTestImplementation 'com.github.ligi:trulesk:0.30'
|
||||
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'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
||||
|
@ -123,13 +130,13 @@ dependencies {
|
|||
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.25.1'
|
||||
androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
androidTestImplementation 'org.threeten:threetenbp:1.4.1'
|
||||
androidTestImplementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
|
||||
implementation 'com.github.ligi:TouchImageView:2.1'
|
||||
implementation 'com.github.ligi:ExtraCompats:1.0'
|
||||
implementation 'net.lingala.zip4j:zip4j:2.3.1'
|
||||
implementation 'net.lingala.zip4j:zip4j:2.3.2'
|
||||
implementation 'com.jakewharton.threetenabp:threetenabp:1.2.2'
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package org.ligi.passandroid
|
||||
|
||||
import com.github.salomonbrys.kodein.Kodein
|
||||
import com.github.salomonbrys.kodein.bind
|
||||
import com.github.salomonbrys.kodein.instance
|
||||
import com.github.salomonbrys.kodein.singleton
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
import org.ligi.passandroid.injections.FixedPassListPassStore
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
|
@ -20,26 +17,24 @@ import java.util.*
|
|||
|
||||
class TestApp : App() {
|
||||
|
||||
override fun createKodein() = Kodein.Module {
|
||||
bind<PassStore>() with singleton {
|
||||
FixedPassListPassStore(emptyList())
|
||||
override fun createKoin(): Module {
|
||||
|
||||
return module {
|
||||
single { passStore as PassStore }
|
||||
single { settings }
|
||||
single { tracker }
|
||||
}
|
||||
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) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
fun passStore(): PassStore = kodein.instance()
|
||||
fun settings(): Settings = kodein.instance()
|
||||
val tracker = mock(Tracker::class.java)
|
||||
val passStore = FixedPassListPassStore(emptyList())
|
||||
val settings = mock(Settings::class.java).apply {
|
||||
`when`(getSortOrder()).thenReturn(PassSortOrder.DATE_ASC)
|
||||
`when`(getPassesDir()).thenReturn(File(""))
|
||||
`when`(doTraceDroidEmailSend()).thenReturn(false)
|
||||
}
|
||||
|
||||
fun populatePassStoreWithSinglePass() {
|
||||
|
||||
|
@ -51,13 +46,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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ 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.github.salomonbrys.kodein.instance
|
||||
import com.linkedin.android.testbutler.TestButler
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
|
@ -28,7 +27,7 @@ class TheBarCodeEditing {
|
|||
@get:Rule
|
||||
val rule = TruleskActivityRule(PassEditActivity::class.java, false)
|
||||
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
val passStore: PassStore = TestApp.passStore
|
||||
|
||||
private lateinit var currentPass: PassImpl
|
||||
|
||||
|
@ -85,6 +84,7 @@ class TheBarCodeEditing {
|
|||
|
||||
onView(withText(passBarCodeFormat.name)).perform(scrollTo(), click())
|
||||
|
||||
onView(withId(R.id.randomButton)).perform(click())
|
||||
closeSoftKeyboard()
|
||||
|
||||
onView(withText(android.R.string.ok)).perform(click())
|
||||
|
@ -102,7 +102,7 @@ class TheBarCodeEditing {
|
|||
onView(withId(R.id.barcode_img)).perform(click())
|
||||
|
||||
onView(withId(R.id.messageInput)).perform(clearText())
|
||||
onView(withId(R.id.messageInput)).perform(typeText("msg foo txt ;-)"))
|
||||
onView(withId(R.id.messageInput)).perform(replaceText("msg foo txt ;-)"))
|
||||
|
||||
closeSoftKeyboard()
|
||||
|
||||
|
@ -122,7 +122,7 @@ class TheBarCodeEditing {
|
|||
onView(withId(R.id.barcode_img)).perform(click())
|
||||
|
||||
onView(withId(R.id.alternativeMessageInput)).perform(clearText())
|
||||
onView(withId(R.id.alternativeMessageInput)).perform(typeText("alt bar txt ;-)"))
|
||||
onView(withId(R.id.alternativeMessageInput)).perform(replaceText("alt bar txt ;-)"))
|
||||
|
||||
closeSoftKeyboard()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -25,7 +25,7 @@ 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) {
|
||||
|
@ -59,7 +59,7 @@ class ThePassEditActivity {
|
|||
@Test
|
||||
fun testSetDescriptionWorks() {
|
||||
|
||||
onView(withId(R.id.passTitle)).perform(clearText(), typeText("test description"))
|
||||
onView(withId(R.id.passTitle)).perform(clearText(), replaceText("test description"))
|
||||
assertThat(passStore.currentPass!!.description).isEqualTo("test description")
|
||||
|
||||
rule.screenShot("edit_set_description")
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
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.typeText
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
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
|
||||
|
@ -29,7 +28,7 @@ class ThePassListSwiping {
|
|||
|
||||
onView(withText(R.string.topic_trash)).perform(click())
|
||||
|
||||
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
|
||||
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_trash))
|
||||
}
|
||||
|
||||
|
||||
|
@ -39,7 +38,7 @@ class ThePassListSwiping {
|
|||
|
||||
onView(withText(R.string.topic_archive)).perform(click())
|
||||
|
||||
assertThat(passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
|
||||
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(rule.activity.getString(R.string.topic_archive))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -47,11 +46,11 @@ class ThePassListSwiping {
|
|||
|
||||
fakeSwipeLeft()
|
||||
|
||||
onView(withId(R.id.new_topic_edit)).perform(typeText(CUSTOM_PROBE))
|
||||
onView(withId(R.id.new_topic_edit)).perform(replaceText(CUSTOM_PROBE))
|
||||
|
||||
onView(withText(android.R.string.ok)).perform(click())
|
||||
|
||||
assertThat(passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
|
||||
assertThat(TestApp.passStore.classifier.getTopics()).containsExactly(CUSTOM_PROBE)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -18,7 +18,7 @@ class ThePassViewHolder {
|
|||
|
||||
private val currentPass by lazy {
|
||||
TestApp.populatePassStoreWithSinglePass()
|
||||
App.passStore.currentPass as PassImpl
|
||||
TestApp.passStore.currentPass as PassImpl
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
|
|
|
@ -49,7 +49,7 @@ public class TheUnzipPassController {
|
|||
verify(failCallback).fail(any(String.class));
|
||||
|
||||
} catch (Exception e) {
|
||||
fail("should be able to load file");
|
||||
fail("should be able to load file " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ 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
|
||||
|
@ -30,7 +31,7 @@ fun loadPassFromAsset(asset: String, callback: (pass: Pass?) -> Unit) {
|
|||
object : UnzipPassController.SuccessCallback {
|
||||
override fun call(uuid: String) {
|
||||
callback.invoke(AppleStylePassReader.read(File(getTestTargetPath(instrumentation.targetContext), uuid), "en",
|
||||
instrumentation.targetContext))
|
||||
instrumentation.targetContext,TestApp.tracker))
|
||||
}
|
||||
},
|
||||
mock
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package org.ligi.passandroid.injections
|
||||
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import org.ligi.passandroid.model.PassClassifier
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PassStoreUpdateEvent
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import java.io.File
|
||||
|
||||
|
@ -48,6 +51,8 @@ class FixedPassListPassStore(private var passes: List<Pass>) : PassStore {
|
|||
return File("")
|
||||
}
|
||||
|
||||
override val updateChannel: BroadcastChannel<PassStoreUpdateEvent> = ConflatedBroadcastChannel()
|
||||
|
||||
override fun save(pass: Pass) {
|
||||
// no effect in this impl
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
android:resource="@xml/filepaths"/>
|
||||
</provider>
|
||||
|
||||
<service android:name=".ui.SearchPassesIntentService"/>
|
||||
<service android:name=".scan.SearchPassesIntentService"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.PassListActivity"
|
||||
|
@ -1804,6 +1804,7 @@
|
|||
<activity
|
||||
android:name=".ui.FullscreenBarcodeActivity"
|
||||
android:label="@string/app_name"/>
|
||||
<activity android:name=".scan.PassScanActivity" />
|
||||
|
||||
</application>
|
||||
|
||||
|
|
|
@ -2,58 +2,62 @@ 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.moshi.Moshi
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
import org.ligi.passandroid.json_adapter.ColorAdapter
|
||||
import org.ligi.passandroid.json_adapter.ZonedTimeAdapter
|
||||
import org.ligi.passandroid.model.AndroidFileSystemPassStore
|
||||
import org.ligi.passandroid.model.AndroidSettings
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
|
||||
import org.ligi.tracedroid.TraceDroid
|
||||
import org.ligi.tracedroid.logging.Log
|
||||
|
||||
open class App : Application() {
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(ZonedTimeAdapter())
|
||||
.add(ColorAdapter())
|
||||
.build()
|
||||
|
||||
private val settings by lazy { AndroidSettings(this) }
|
||||
|
||||
open fun createKoin(): Module {
|
||||
|
||||
return module {
|
||||
single { AndroidFileSystemPassStore(this@App, get(), moshi) as PassStore }
|
||||
single { settings as Settings }
|
||||
single { createTracker(this@App) }
|
||||
single { PassScanEventChannelProvider() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
kodein = Kodein {
|
||||
import(createTrackerKodeinModule(this@App))
|
||||
import(createKodein(), allowOverride = true)
|
||||
startKoin {
|
||||
if (BuildConfig.DEBUG) androidLogger()
|
||||
androidContext(this@App)
|
||||
modules(createKoin())
|
||||
}
|
||||
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
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() }
|
||||
}
|
||||
|
||||
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)) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
||||
class PassRefreshEvent(val pass: Pass)
|
|
@ -1,3 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
object PassStoreChangeEvent
|
|
@ -1,5 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
||||
class ScanFinishedEvent(val foundPasses: List<Pass>)
|
|
@ -1,3 +0,0 @@
|
|||
package org.ligi.passandroid.events
|
||||
|
||||
class ScanProgressEvent(val message: String)
|
|
@ -4,31 +4,31 @@ import android.content.Context
|
|||
import android.net.Uri
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.model.InputStreamWithSource
|
||||
import java.io.BufferedInputStream
|
||||
import java.net.URL
|
||||
|
||||
const val IPHONE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
|
||||
|
||||
fun fromURI(context: Context, uri: Uri): InputStreamWithSource? {
|
||||
App.tracker.trackEvent("protocol", "to_inputstream", uri.scheme, null)
|
||||
fun fromURI(context: Context, uri: Uri, tracker: Tracker): InputStreamWithSource? {
|
||||
tracker.trackEvent("protocol", "to_inputstream", uri.scheme, null)
|
||||
return when (uri.scheme) {
|
||||
"content" -> fromContent(context, uri)
|
||||
|
||||
"http", "https" ->
|
||||
// TODO check if SPDY should be here
|
||||
return fromOKHttp(uri)
|
||||
return fromOKHttp(uri, tracker)
|
||||
|
||||
"file" -> getDefaultInputStreamForUri(uri)
|
||||
else -> {
|
||||
App.tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
|
||||
tracker.trackException("unknown scheme in ImportAsyncTask" + uri.scheme, false)
|
||||
getDefaultInputStreamForUri(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fromOKHttp(uri: Uri): InputStreamWithSource? {
|
||||
private fun fromOKHttp(uri: Uri, tracker: Tracker): InputStreamWithSource? {
|
||||
val client = OkHttpClient()
|
||||
val url = URL(uri.toString())
|
||||
val requestBuilder = Request.Builder().url(url)
|
||||
|
@ -47,7 +47,7 @@ private fun fromOKHttp(uri: Uri): InputStreamWithSource? {
|
|||
|
||||
for ((key, value) in iPhoneFakeMap) {
|
||||
if (uri.toString().contains(value)) {
|
||||
App.tracker.trackEvent("quirk_fix", "ua_fake", key, null)
|
||||
tracker.trackEvent("quirk_fix", "ua_fake", key, null)
|
||||
requestBuilder.header("User-Agent", IPHONE_USER_AGENT)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,16 @@ 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.greenrobot.eventbus.EventBus
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.BuildConfig
|
||||
import org.ligi.passandroid.events.PassStoreChangeEvent
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.model.pass.PassImpl
|
||||
import org.ligi.passandroid.reader.AppleStylePassReader
|
||||
|
@ -17,13 +20,24 @@ import org.ligi.passandroid.reader.PassReader
|
|||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class AndroidFileSystemPassStore(private val context: Context, settings: Settings, private val moshi: Moshi, private val bus: EventBus) : PassStore {
|
||||
object PassStoreUpdateEvent
|
||||
|
||||
class AndroidFileSystemPassStore(
|
||||
private val context: Context,
|
||||
settings: Settings,
|
||||
private val moshi: Moshi
|
||||
) : PassStore, KoinComponent {
|
||||
|
||||
override val updateChannel = ConflatedBroadcastChannel<PassStoreUpdateEvent>()
|
||||
|
||||
private val path: File = settings.getPassesDir()
|
||||
|
||||
override val passMap = HashMap<String, Pass>()
|
||||
|
||||
override var currentPass: Pass? = null
|
||||
|
||||
private val tracker: Tracker by inject()
|
||||
|
||||
override val classifier: PassClassifier by lazy {
|
||||
val classificationFile = File(settings.getStateDir(), "classifier_state.json")
|
||||
FileBackedPassClassifier(classificationFile, this, moshi)
|
||||
|
@ -71,7 +85,7 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
try {
|
||||
result = jsonAdapter.fromJson(file.source().buffer())
|
||||
} catch (ignored: JsonDataException) {
|
||||
App.tracker.trackException("invalid main.json", false)
|
||||
tracker.trackException("invalid main.json", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +95,7 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
}
|
||||
|
||||
if (result == null && File(pathForID, "pass.json").exists()) {
|
||||
result = AppleStylePassReader.read(pathForID, language, context)
|
||||
result = AppleStylePassReader.read(pathForID, language, context, tracker)
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
|
@ -114,7 +128,9 @@ class AndroidFileSystemPassStore(private val context: Context, settings: Setting
|
|||
}
|
||||
|
||||
override fun notifyChange() {
|
||||
bus.post(PassStoreChangeEvent)
|
||||
GlobalScope.launch {
|
||||
updateChannel.send(PassStoreUpdateEvent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncPassStoreWithClassifier(defaultTopic: String) {
|
||||
|
|
|
@ -10,7 +10,6 @@ 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> {
|
||||
|
||||
|
@ -74,7 +73,6 @@ public class AppleStylePassTranslation extends HashMap<String, String> {
|
|||
}
|
||||
return new String(fileData);
|
||||
} catch (Throwable e) {
|
||||
App.Companion.getTracker().trackException("problem_reading_translation", e, false);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.ligi.passandroid.model
|
||||
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import java.io.File
|
||||
|
||||
interface PassStore {
|
||||
|
||||
val updateChannel: BroadcastChannel<PassStoreUpdateEvent>
|
||||
|
||||
fun save(pass: Pass)
|
||||
|
||||
fun getPassbookForId(id: String): Pass?
|
||||
|
|
|
@ -2,21 +2,21 @@ package org.ligi.passandroid.model.pass
|
|||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import com.github.salomonbrys.kodein.instance
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.functions.generateBitmapDrawable
|
||||
import org.ligi.tracedroid.logging.Log
|
||||
import java.util.*
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) {
|
||||
class BarCode(val format: PassBarCodeFormat?, val message: String? = UUID.randomUUID().toString().toUpperCase()) : KoinComponent {
|
||||
|
||||
val tracker: Tracker by inject ()
|
||||
var alternativeText: String? = null
|
||||
|
||||
fun getBitmap(resources: Resources): BitmapDrawable? {
|
||||
val tracker: Tracker = App.kodein.instance()
|
||||
if (message == null) {
|
||||
// no message -> no barcode
|
||||
tracker.trackException("No Barcode in pass - strange", false)
|
||||
|
|
|
@ -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): Pass? {
|
||||
fun read(passFile: File, language: String, context: Context, tracker: Tracker): Pass? {
|
||||
|
||||
val translation = AppleStylePassTranslation()
|
||||
|
||||
|
@ -34,7 +34,7 @@ object AppleStylePassReader {
|
|||
|
||||
var passJSON: JSONObject? = null
|
||||
|
||||
val localizedPath = findLocalizedPath(passFile, language)
|
||||
val localizedPath = findLocalizedPath(passFile, language, tracker)
|
||||
|
||||
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 ")
|
||||
App.tracker.trackEvent("problem_event", "pass", "without_pass_json", null)
|
||||
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")
|
||||
|
||||
App.tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
|
||||
tracker.trackEvent("measure_event", "barcode_format", barcodeFormatString, 0L)
|
||||
val barcodeFormat = BarCode.getFormatFromString(barcodeFormatString)
|
||||
val barCode = BarCode(barcodeFormat, 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
|
||||
App.tracker.trackException("problem parsing relevant date", e, false)
|
||||
tracker.trackException("problem parsing relevant date", e, false)
|
||||
} catch (e: DateTimeException) {
|
||||
App.tracker.trackException("problem parsing relevant date", e, false)
|
||||
tracker.trackException("problem parsing relevant date", e, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
App.tracker.trackException("problem parsing expiration date", e, false)
|
||||
tracker.trackException("problem parsing expiration date", e, false)
|
||||
} catch (e: DateTimeException) {
|
||||
App.tracker.trackException("problem parsing expiration date", e, false)
|
||||
tracker.trackException("problem parsing expiration date", e, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -196,12 +196,12 @@ object AppleStylePassReader {
|
|||
|
||||
try {
|
||||
pass.creator = passJSON.getString("organizationName")
|
||||
App.tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
|
||||
tracker.trackEvent("measure_event", "organisation_parse", pass.creator, 1L)
|
||||
} catch (ignored: JSONException) {
|
||||
// ok - we have no organisation - big deal ..-)
|
||||
}
|
||||
|
||||
ApplePassbookQuirkCorrector(App.tracker).correctQuirks(pass)
|
||||
ApplePassbookQuirkCorrector(tracker).correctQuirks(pass)
|
||||
|
||||
return pass
|
||||
}
|
||||
|
@ -240,18 +240,18 @@ object AppleStylePassReader {
|
|||
|
||||
}
|
||||
|
||||
private fun findLocalizedPath(path: File, language: String): String? {
|
||||
private fun findLocalizedPath(path: File, language: String, tracker: Tracker): String? {
|
||||
val localized = File(path, "$language.lproj")
|
||||
|
||||
if (localized.exists() && localized.isDirectory) {
|
||||
App.tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
|
||||
tracker.trackEvent("measure_event", "pass", language + "_native_lproj", null)
|
||||
return localized.path
|
||||
}
|
||||
|
||||
val fallback = File(path, "en.lproj")
|
||||
|
||||
if (fallback.exists() && fallback.isDirectory) {
|
||||
App.tracker.trackEvent("measure_event", "pass", "en_lproj", null)
|
||||
tracker.trackEvent("measure_event", "pass", "en_lproj", null)
|
||||
return fallback.path
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package org.ligi.passandroid.scan
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View.GONE
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.android.synthetic.main.activity_scan.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.scan.events.DirectoryProcessed
|
||||
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
|
||||
import org.ligi.passandroid.scan.events.ScanFinished
|
||||
import org.ligi.passandroid.ui.PassAndroidActivity
|
||||
|
||||
class PassScanActivity : PassAndroidActivity() {
|
||||
|
||||
private val progressChannelProvider: PassScanEventChannelProvider by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_scan)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
lifecycleScope.launch {
|
||||
for (event in progressChannelProvider.channel.openSubscription()) {
|
||||
when (event) {
|
||||
is DirectoryProcessed -> progress_text.text = event.dir
|
||||
is ScanFinished -> {
|
||||
progress_container.visibility = GONE
|
||||
val message = getString(R.string.scan_finished_dialog_text, event.foundPasses.size)
|
||||
AlertDialog.Builder(this@PassScanActivity)
|
||||
.setTitle(R.string.scan_finished_dialog_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(this, SearchPassesIntentService::class.java)
|
||||
startService(intent)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package org.ligi.passandroid.scan
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.functions.fromURI
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PastLocationsStore
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
import org.ligi.passandroid.scan.events.DirectoryProcessed
|
||||
import org.ligi.passandroid.scan.events.PassScanEventChannelProvider
|
||||
import org.ligi.passandroid.scan.events.ScanFinished
|
||||
import org.ligi.passandroid.ui.PassListActivity
|
||||
import org.ligi.passandroid.ui.UnzipPassController
|
||||
import org.ligi.passandroid.ui.UnzipPassController.InputStreamUnzipControllerSpec
|
||||
import org.ligi.tracedroid.logging.Log
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
private const val NOTIFICATION_CHANNEL_ID = "transactions"
|
||||
|
||||
class SearchPassesIntentService : LifecycleService() {
|
||||
|
||||
private val notifyManager by lazy {
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
}
|
||||
|
||||
private var shouldFinish: Boolean = false
|
||||
private var progressNotificationBuilder: NotificationCompat.Builder? = null
|
||||
private var findNotificationBuilder: NotificationCompat.Builder? = null
|
||||
|
||||
private var foundList = ArrayList<Pass>()
|
||||
|
||||
private var lastProgressUpdate: Long = 0
|
||||
|
||||
private val passStore: PassStore by inject()
|
||||
private val tracker: Tracker by inject()
|
||||
private val progressChannelProvider: PassScanEventChannelProvider by inject()
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
lifecycleScope.launch {
|
||||
foundList.clear()
|
||||
|
||||
if (Build.VERSION.SDK_INT > 25) {
|
||||
|
||||
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, "PassAndroid Pass scan", NotificationManager.IMPORTANCE_DEFAULT)
|
||||
channel.description = "Notifications when PassAndroid is scanning for passes"
|
||||
notifyManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(applicationContext, 1, Intent(baseContext, PassListActivity::class.java), 0)
|
||||
progressNotificationBuilder = NotificationCompat.Builder(this@SearchPassesIntentService, NOTIFICATION_CHANNEL_ID).setContentTitle(getString(R.string.scanning_for_passes))
|
||||
.setSmallIcon(R.drawable.ic_refresh)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setProgress(1, 1, true)
|
||||
|
||||
findNotificationBuilder = NotificationCompat.Builder(this@SearchPassesIntentService, NOTIFICATION_CHANNEL_ID)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
for (path in PastLocationsStore(preferences, tracker).locations) {
|
||||
searchIn(File(path), false)
|
||||
}
|
||||
|
||||
// note to future_me: yea one thinks we only need to search root here, but root was /system for me and so
|
||||
// did not contain "/SDCARD" #dontoptimize
|
||||
// on my phone:
|
||||
|
||||
// | /mnt/sdcard/Download << this looks kind of stupid as we do /mnt/sdcard later and hence will go here twice
|
||||
// but this helps finding passes in Downloads ( where they are very often ) fast - some users with lots of files on the SDCard gave
|
||||
// up the refreshing of passes as it took so long to traverse all files on the SDCard
|
||||
// one could think about not going there anymore but a short look at this showed that it seems cost more time to check than what it gains
|
||||
// in download there are mostly single files in a flat dir - no huge tree behind this imho
|
||||
searchIn(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), true)
|
||||
|
||||
// | /system
|
||||
searchIn(Environment.getRootDirectory(), true)
|
||||
|
||||
// | /mnt/sdcard
|
||||
searchIn(Environment.getExternalStorageDirectory(), true)
|
||||
|
||||
// | /cache
|
||||
searchIn(Environment.getDownloadCacheDirectory(), true)
|
||||
|
||||
// | /data
|
||||
searchIn(Environment.getDataDirectory(), true)
|
||||
notifyManager.cancel(PROGRESS_NOTIFICATION_ID)
|
||||
|
||||
progressChannelProvider.channel.send(ScanFinished(foundList))
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
/**
|
||||
* recursive voyage starting at path to find files named .pkpass
|
||||
*/
|
||||
private suspend fun searchIn(path: File, recursive: Boolean) {
|
||||
|
||||
if (System.currentTimeMillis() - lastProgressUpdate > 1000) {
|
||||
lastProgressUpdate = System.currentTimeMillis()
|
||||
val msg = path.toString()
|
||||
progressChannelProvider.channel.send(DirectoryProcessed(msg))
|
||||
progressNotificationBuilder!!.setContentText(msg)
|
||||
notifyManager.notify(PROGRESS_NOTIFICATION_ID, progressNotificationBuilder!!.build())
|
||||
}
|
||||
|
||||
val files = path.listFiles()
|
||||
|
||||
if (files == null || files.isEmpty()) {
|
||||
// no files here
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
for (file in files) {
|
||||
if (shouldFinish) {
|
||||
return
|
||||
}
|
||||
Log.i("search " + file.absoluteFile)
|
||||
if (recursive && file.isDirectory) {
|
||||
searchIn(file, true)
|
||||
} else if (file.name.toLowerCase().endsWith(".pkpass") || file.name.toLowerCase().endsWith(".espass")) {
|
||||
Log.i("found" + file.absolutePath)
|
||||
|
||||
try {
|
||||
val ins = fromURI(baseContext, Uri.parse("file://" + file.absolutePath), tracker)
|
||||
val onSuccessCallback = SearchSuccessCallback(baseContext,
|
||||
passStore,
|
||||
foundList,
|
||||
findNotificationBuilder!!,
|
||||
file,
|
||||
notifyManager)
|
||||
val spec = InputStreamUnzipControllerSpec(ins!!,
|
||||
baseContext,
|
||||
passStore,
|
||||
onSuccessCallback,
|
||||
object : UnzipPassController.FailCallback {
|
||||
override fun fail(reason: String) {
|
||||
Log.i("fail", reason)
|
||||
}
|
||||
})
|
||||
UnzipPassController.processInputStream(spec)
|
||||
} catch (e: Exception) {
|
||||
tracker.trackException("Error in SearchPassesIntentService", e, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
shouldFinish = true
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROGRESS_NOTIFICATION_ID = 1
|
||||
const val FOUND_NOTIFICATION_ID = 2
|
||||
const val REQUEST_CODE = 1
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.ligi.passandroid.ui
|
||||
package org.ligi.passandroid.scan
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
|
@ -10,6 +10,10 @@ 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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.ligi.passandroid.scan.events
|
||||
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
||||
sealed class PassScanEvent
|
||||
|
||||
data class DirectoryProcessed(val dir: String) : PassScanEvent()
|
||||
data class ScanFinished(val foundPasses: List<Pass>) : PassScanEvent()
|
|
@ -0,0 +1,8 @@
|
|||
package org.ligi.passandroid.scan.events
|
||||
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import org.ligi.passandroid.scan.events.PassScanEvent
|
||||
|
||||
class PassScanEventChannelProvider {
|
||||
val channel = ConflatedBroadcastChannel<PassScanEvent>()
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.github.salomonbrys.kodein.instance
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.kaxt.startActivityFromClass
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PassStoreProjection
|
||||
|
@ -17,10 +17,13 @@ import org.ligi.passandroid.ui.pass_view_holder.CondensedPassViewHolder
|
|||
import org.ligi.passandroid.ui.pass_view_holder.PassViewHolder
|
||||
import org.ligi.passandroid.ui.pass_view_holder.VerbosePassViewHolder
|
||||
|
||||
class PassAdapter(private val passListActivity: AppCompatActivity, private val passStoreProjection: PassStoreProjection) : RecyclerView.Adapter<PassViewHolder>() {
|
||||
class PassAdapter(
|
||||
private val passListActivity: AppCompatActivity,
|
||||
private val passStoreProjection: PassStoreProjection
|
||||
) : RecyclerView.Adapter<PassViewHolder>(), KoinComponent {
|
||||
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
val settings: Settings = App.kodein.instance()
|
||||
private val passStore: PassStore by inject ()
|
||||
private val settings: Settings by inject ()
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PassViewHolder {
|
||||
val inflater = LayoutInflater.from(viewGroup.context)
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.salomonbrys.kodein.instance
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.Settings
|
||||
|
||||
open class PassAndroidActivity : AppCompatActivity() {
|
||||
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
val settings: Settings = App.kodein.instance()
|
||||
val bus: EventBus = App.kodein.instance()
|
||||
val tracker: Tracker = App.kodein.instance()
|
||||
val passStore: PassStore by inject()
|
||||
val settings: Settings by inject()
|
||||
val tracker: Tracker by inject()
|
||||
|
||||
private var lastSetNightMode: Int? = null
|
||||
|
||||
|
|
|
@ -3,21 +3,17 @@ 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 com.github.salomonbrys.kodein.instance
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.edit.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.kaxt.doAfterEdit
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.events.PassRefreshEvent
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.pass.BarCode
|
||||
import org.ligi.passandroid.model.pass.Pass
|
||||
|
@ -39,8 +35,7 @@ class PassEditActivity : AppCompatActivity() {
|
|||
private lateinit var currentPass: PassImpl
|
||||
private val imageEditHelper by lazy { ImageEditHelper(this, passStore) }
|
||||
|
||||
internal val passStore: PassStore = App.kodein.instance()
|
||||
internal val bus: EventBus = App.kodein.instance()
|
||||
internal val passStore: PassStore by inject()
|
||||
|
||||
private val passViewHelper: PassViewHelper by lazy { PassViewHelper(this) }
|
||||
|
||||
|
@ -57,8 +52,8 @@ class PassEditActivity : AppCompatActivity() {
|
|||
categoryView.setOnClickListener {
|
||||
AlertDialog.Builder(this).setItems(R.array.category_edit_options) { _, i ->
|
||||
when (i) {
|
||||
0 -> showCategoryPickDialog(this@PassEditActivity, currentPass, bus)
|
||||
1 -> showColorPickDialog(this@PassEditActivity, currentPass, bus)
|
||||
0 -> showCategoryPickDialog(this@PassEditActivity, currentPass, refreshCallback)
|
||||
1 -> showColorPickDialog(this@PassEditActivity, currentPass, refreshCallback)
|
||||
2 -> pickImageWithPermissionCheck(ImageEditHelper.REQ_CODE_PICK_ICON)
|
||||
}
|
||||
}.show()
|
||||
|
@ -85,17 +80,15 @@ class PassEditActivity : AppCompatActivity() {
|
|||
|
||||
add_barcode_button.setOnClickListener {
|
||||
showBarcodeEditDialog(this@PassEditActivity,
|
||||
bus,
|
||||
refreshCallback,
|
||||
this@PassEditActivity.currentPass,
|
||||
BarCode(PassBarCodeFormat.QR_CODE, UUID.randomUUID().toString().toUpperCase()))
|
||||
}
|
||||
}
|
||||
|
||||
val refreshCallback = { refresh(currentPass) }
|
||||
|
||||
|
||||
@Subscribe
|
||||
fun onPassRefresh(event: PassRefreshEvent) {
|
||||
refresh(currentPass)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
@ -118,7 +111,7 @@ class PassEditActivity : AppCompatActivity() {
|
|||
|
||||
add_barcode_button.visibility = if (pass.barCode == null) View.VISIBLE else View.GONE
|
||||
val barcodeUIController = BarcodeUIController(window.decorView, pass.barCode, this, passViewHelper)
|
||||
barcodeUIController.getBarcodeView().setOnClickListener { showBarcodeEditDialog(this@PassEditActivity, bus, currentPass, currentPass.barCode!!) }
|
||||
barcodeUIController.getBarcodeView().setOnClickListener { showBarcodeEditDialog(this@PassEditActivity, refreshCallback, currentPass, currentPass.barCode!!) }
|
||||
}
|
||||
|
||||
@Pass.PassBitmap
|
||||
|
@ -142,13 +135,11 @@ class PassEditActivity : AppCompatActivity() {
|
|||
|
||||
override fun onResumeFragments() {
|
||||
super.onResumeFragments()
|
||||
bus.register(this)
|
||||
refresh(currentPass)
|
||||
}
|
||||
|
||||
|
||||
override fun onPause() {
|
||||
bus.unregister(this)
|
||||
passStore.save(currentPass)
|
||||
passStore.notifyChange()
|
||||
super.onPause()
|
||||
|
|
|
@ -4,15 +4,21 @@ 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 android.widget.Toast
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.Tracker
|
||||
import java.io.File
|
||||
|
||||
internal open class PassExportTaskAndShare(protected val activity: Activity, private val inputPath: File) {
|
||||
internal open class PassExportTaskAndShare(
|
||||
protected val activity: Activity,
|
||||
private val inputPath: File
|
||||
) : KoinComponent {
|
||||
|
||||
val tracker: Tracker by inject()
|
||||
@UiThread
|
||||
fun execute() {
|
||||
val file = File(activity.filesDir, "share/share.espass") // important - the FileProvider must be configured for this path
|
||||
|
@ -31,7 +37,7 @@ internal open class PassExportTaskAndShare(protected val activity: Activity, pri
|
|||
}
|
||||
|
||||
if (passExporter.exception != null) {
|
||||
App.tracker.trackException("passExporterException", passExporter.exception!!, false)
|
||||
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)
|
||||
|
|
|
@ -4,14 +4,16 @@ 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.ligi.passandroid.App
|
||||
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.Tracker
|
||||
import java.io.File
|
||||
|
||||
class PassExporter(private val inputPath: File, val file: File) {
|
||||
class PassExporter(private val inputPath: File, val file: File) : KoinComponent {
|
||||
var exception: Exception? = null
|
||||
|
||||
val tracker: Tracker by inject()
|
||||
|
||||
fun export() {
|
||||
try {
|
||||
file.delete()
|
||||
|
@ -28,7 +30,7 @@ class PassExporter(private val inputPath: File, val file: File) {
|
|||
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
App.tracker.trackException("when exporting pass to zip", exception, false)
|
||||
tracker.trackException("when exporting pass to zip", exception, false)
|
||||
this.exception = exception // we need to take action on the main thread later
|
||||
file.delete() // prevent zombies from taking over
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
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 com.github.salomonbrys.kodein.instance
|
||||
import org.ligi.kaxt.dismissIfShowing
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.android.synthetic.main.activity_import.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.kaxt.startActivityFromClass
|
||||
import org.ligi.kaxtui.alert
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.functions.fromURI
|
||||
|
@ -21,16 +24,8 @@ import permissions.dispatcher.RuntimePermissions
|
|||
@RuntimePermissions
|
||||
class PassImportActivity : AppCompatActivity() {
|
||||
|
||||
val tracker: Tracker = App.kodein.instance()
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
|
||||
|
||||
private val progressDialog by lazy {
|
||||
ProgressDialog(this).apply {
|
||||
setMessage(getString(R.string.please_wait))
|
||||
setCancelable(false)
|
||||
}
|
||||
}
|
||||
val tracker: Tracker by inject()
|
||||
val passStore: PassStore by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -41,7 +36,7 @@ class PassImportActivity : AppCompatActivity() {
|
|||
return
|
||||
}
|
||||
|
||||
progressDialog.show()
|
||||
setContentView(R.layout.activity_import)
|
||||
|
||||
doImportWithPermissionCheck(false)
|
||||
}
|
||||
|
@ -53,12 +48,13 @@ class PassImportActivity : AppCompatActivity() {
|
|||
|
||||
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun doImport(withPermission: Boolean) {
|
||||
Thread {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val fromURI = fromURI(this, intent!!.data!!)
|
||||
val fromURI = fromURI(this@PassImportActivity, intent!!.data!!, tracker)
|
||||
|
||||
runOnUiThread {
|
||||
progressDialog.dismissIfShowing()
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
progress_container.visibility = GONE
|
||||
|
||||
if (fromURI == null) {
|
||||
finish()
|
||||
|
@ -71,7 +67,7 @@ class PassImportActivity : AppCompatActivity() {
|
|||
val spec = UnzipPassController.InputStreamUnzipControllerSpec(fromURI, application, passStore, null, null)
|
||||
UnzipPassController.processInputStream(spec)
|
||||
} else {
|
||||
UnzipPassDialog.show(fromURI, this, passStore) { path ->
|
||||
UnzipPassDialog.show(fromURI, this@PassImportActivity, passStore) { path ->
|
||||
// TODO this is kind of a hack - there should be a better way
|
||||
val id = path.split("/".toRegex()).dropLastWhile(String::isEmpty).toTypedArray().last()
|
||||
|
||||
|
@ -93,12 +89,12 @@ class PassImportActivity : AppCompatActivity() {
|
|||
tracker.trackException("Error in import", e, false)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun showDeniedDialog() {
|
||||
progressDialog.dismissIfShowing()
|
||||
progress_container.visibility = GONE
|
||||
alert(R.string.error_no_permission_msg, R.string.error_no_permission_title, onOK = { finish() })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ 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
|
||||
|
@ -17,21 +15,18 @@ import android.view.View.VISIBLE
|
|||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.pass_list.*
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.ligi.kaxt.setButton
|
||||
import kotlinx.coroutines.launch
|
||||
import org.ligi.kaxt.startActivityFromClass
|
||||
import org.ligi.kaxt.startActivityFromURL
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.events.PassStoreChangeEvent
|
||||
import org.ligi.passandroid.events.ScanFinishedEvent
|
||||
import org.ligi.passandroid.events.ScanProgressEvent
|
||||
import org.ligi.passandroid.functions.createAndAddEmptyPass
|
||||
import org.ligi.passandroid.model.PassStoreProjection
|
||||
import org.ligi.passandroid.model.State
|
||||
import org.ligi.passandroid.scan.PassScanActivity
|
||||
import org.ligi.snackengage.SnackEngage
|
||||
import org.ligi.snackengage.snacks.BaseSnack
|
||||
import org.ligi.snackengage.snacks.DefaultRateSnack
|
||||
|
@ -51,25 +46,7 @@ class PassListActivity : PassAndroidActivity() {
|
|||
private val drawerToggle by lazy { ActionBarDrawerToggle(this, drawer_layout, R.string.drawer_open, R.string.drawer_close) }
|
||||
|
||||
private val adapter by lazy { PassTopicFragmentPagerAdapter(passStore.classifier, supportFragmentManager) }
|
||||
private val pd by lazy { ProgressDialog(this) }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onScanProgress(event: ScanProgressEvent) {
|
||||
if (pd.isShowing) {
|
||||
pd.setMessage(event.message)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onScanFinished(event: ScanFinishedEvent) {
|
||||
|
||||
if (pd.isShowing) {
|
||||
val message = getString(R.string.scan_finished_dialog_text, event.foundPasses.size)
|
||||
pd.dismiss()
|
||||
AlertDialog.Builder(this@PassListActivity).setTitle(R.string.scan_finished_dialog_title).setMessage(message).setPositiveButton(android.R.string.ok, null).show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
|
@ -80,22 +57,7 @@ class PassListActivity : PassAndroidActivity() {
|
|||
|
||||
@TargetApi(16)
|
||||
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
fun scan() {
|
||||
val intent = Intent(this, SearchPassesIntentService::class.java)
|
||||
startService(intent)
|
||||
|
||||
pd.setTitle(R.string.scan_progressdialog_title)
|
||||
pd.setMessage(getString(R.string.scan_progressdialog_message))
|
||||
pd.setCancelable(false)
|
||||
pd.isIndeterminate = true
|
||||
pd.setButton(DialogInterface.BUTTON_NEUTRAL, R.string.scan_dialog_send_background_button) {
|
||||
this@PassListActivity.finish()
|
||||
}
|
||||
pd.setButton(DialogInterface.BUTTON_POSITIVE, android.R.string.cancel) {
|
||||
stopService(intent)
|
||||
}
|
||||
pd.show()
|
||||
}
|
||||
fun scan() = startActivity(Intent(this, PassScanActivity::class.java))
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
@ -172,7 +134,7 @@ class PassListActivity : PassAndroidActivity() {
|
|||
})
|
||||
passStore.syncPassStoreWithClassifier(getString(R.string.topic_new))
|
||||
|
||||
onPassStoreChangeEvent(null)
|
||||
refresh()
|
||||
|
||||
fab_action_create_pass.setOnClickListener {
|
||||
val pass = createAndAddEmptyPass(passStore, resources)
|
||||
|
@ -205,9 +167,29 @@ class PassListActivity : PassAndroidActivity() {
|
|||
}
|
||||
fab_action_open_file.visibility = VISIBLE
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
for (update in passStore.updateChannel.openSubscription()) {
|
||||
navigationView.passStoreUpdate()
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
setupWithViewPagerIfNeeded()
|
||||
|
||||
invalidateOptionsMenu()
|
||||
val empty = passStore.classifier.topicByIdMap.isEmpty()
|
||||
emptyView.visibility = if (empty) VISIBLE else GONE
|
||||
val onlyDefaultTopicExists = passStore.classifier.getTopics().all { it == getString(R.string.topic_new) }
|
||||
tab_layout.visibility = if (onlyDefaultTopicExists) GONE else VISIBLE
|
||||
}
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.menu_help -> {
|
||||
startActivityFromClass(HelpActivity::class.java)
|
||||
|
@ -227,7 +209,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)
|
||||
|
@ -236,10 +218,8 @@ class PassListActivity : PassAndroidActivity() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
bus.register(this)
|
||||
|
||||
adapter.notifyDataSetChanged()
|
||||
onPassStoreChangeEvent(null)
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
|
@ -262,25 +242,6 @@ class PassListActivity : PassAndroidActivity() {
|
|||
drawerToggle.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
bus.unregister(this)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPassStoreChangeEvent(passStoreChangeEvent: PassStoreChangeEvent?) {
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
setupWithViewPagerIfNeeded()
|
||||
|
||||
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)
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.fragment.app.Fragment
|
||||
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 androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.*
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import kotlinx.android.synthetic.main.pass_recycler.view.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.ligi.passandroid.App
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.events.PassStoreChangeEvent
|
||||
import org.ligi.passandroid.events.ScanFinishedEvent
|
||||
import org.ligi.passandroid.functions.moveWithUndoSnackbar
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
import org.ligi.passandroid.model.PassStoreProjection
|
||||
|
@ -28,13 +24,11 @@ import org.ligi.passandroid.model.Settings
|
|||
|
||||
class PassListFragment : Fragment() {
|
||||
|
||||
|
||||
private lateinit var passStoreProjection: PassStoreProjection
|
||||
private lateinit var adapter: PassAdapter
|
||||
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
val settings: Settings = App.kodein.instance()
|
||||
val bus: EventBus = App.kodein.instance()
|
||||
val passStore: PassStore by inject()
|
||||
val settings: Settings by inject()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val inflate = inflater.inflate(R.layout.pass_recycler, container, false)
|
||||
|
@ -59,7 +53,12 @@ class PassListFragment : Fragment() {
|
|||
val itemTouchHelper = ItemTouchHelper(simpleItemTouchCallback)
|
||||
itemTouchHelper.attachToRecyclerView(inflate.pass_recyclerview)
|
||||
|
||||
bus.register(this)
|
||||
lifecycleScope.launch {
|
||||
for (update in passStore.updateChannel.openSubscription()) {
|
||||
passStoreProjection.refresh()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
return inflate
|
||||
}
|
||||
|
||||
|
@ -77,24 +76,6 @@ 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"
|
||||
|
||||
|
|
|
@ -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 com.github.salomonbrys.kodein.instance
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.NavUtils
|
||||
import kotlinx.android.synthetic.main.delete_dialog_layout.view.*
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.kaxt.startActivityFromClass
|
||||
import org.ligi.passandroid.App
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.Tracker
|
||||
import org.ligi.passandroid.maps.PassbookMapsFacade
|
||||
|
@ -19,11 +19,11 @@ import org.ligi.passandroid.model.pass.Pass
|
|||
import org.ligi.passandroid.printing.doPrint
|
||||
import java.io.File
|
||||
|
||||
class PassMenuOptions(val activity: Activity, val pass: Pass) {
|
||||
class PassMenuOptions(val activity: Activity, val pass: Pass) : KoinComponent {
|
||||
|
||||
var passStore: PassStore = App.kodein.instance()
|
||||
var tracker: Tracker = App.kodein.instance()
|
||||
var settings: Settings = App.kodein.instance()
|
||||
val passStore: PassStore by inject()
|
||||
val tracker: Tracker by inject()
|
||||
val settings: Settings by inject()
|
||||
|
||||
fun process(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
|
|
|
@ -4,22 +4,17 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import android.util.AttributeSet
|
||||
import com.github.salomonbrys.kodein.instance
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import kotlinx.android.synthetic.main.navigation_drawer_header.view.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.events.PassStoreChangeEvent
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
|
||||
class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView(context, attrs) {
|
||||
class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView(context, attrs), KoinComponent {
|
||||
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
val bus: EventBus = App.kodein.instance()
|
||||
val passStore: PassStore by inject()
|
||||
|
||||
private fun getIntent(id: Int) = when (id) {
|
||||
R.id.menu_settings -> Intent(context, PreferenceActivity::class.java)
|
||||
|
@ -39,8 +34,6 @@ class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView
|
|||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
|
||||
bus.register(this)
|
||||
|
||||
setNavigationItemSelectedListener { item ->
|
||||
getIntent(item.itemId)?.let {
|
||||
context.startActivity(it)
|
||||
|
@ -48,19 +41,12 @@ class PassNavigationView(context: Context, attrs: AttributeSet) : NavigationView
|
|||
} ?: false
|
||||
}
|
||||
|
||||
onPassStoreChangeEvent(PassStoreChangeEvent)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi") // FIXME: temporary workaround for false-positive
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
bus.unregister(this)
|
||||
passStoreUpdate()
|
||||
}
|
||||
|
||||
private val marketUrl by lazy { context.getString(R.string.market_url, context.packageName) }
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPassStoreChangeEvent(@Suppress("UNUSED_PARAMETER") passStoreChangeEvent: PassStoreChangeEvent) {
|
||||
fun passStoreUpdate() {
|
||||
|
||||
val passCount = passStore.passMap.size
|
||||
getHeaderView(0).pass_count_header.text = context.getString(R.string.passes_nav, passCount)
|
||||
|
|
|
@ -2,14 +2,16 @@ 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
|
||||
|
@ -53,7 +55,10 @@ class PassViewActivity : PassViewActivityBase() {
|
|||
pagerAdapter.refresh()
|
||||
}
|
||||
|
||||
inner class CollectionPagerAdapter(fm: FragmentManager, private var passStoreProjection: PassStoreProjection) : FragmentStatePagerAdapter(fm) {
|
||||
inner class CollectionPagerAdapter(
|
||||
fm: FragmentManager,
|
||||
private var passStoreProjection: PassStoreProjection
|
||||
) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
override fun getCount(): Int = passStoreProjection.passList.count()
|
||||
|
||||
|
@ -61,7 +66,7 @@ class PassViewActivity : PassViewActivityBase() {
|
|||
|
||||
val fragment = PassViewFragment()
|
||||
fragment.arguments = Bundle().apply {
|
||||
putString(PassViewActivityBase.EXTRA_KEY_UUID, getPass(i).id)
|
||||
putString(EXTRA_KEY_UUID, getPass(i).id)
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
|
@ -89,9 +94,9 @@ class PassViewActivity : PassViewActivityBase() {
|
|||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
menu.findItem(R.id.menu_map).isVisible = !currentPass.locations.isEmpty()
|
||||
menu.findItem(R.id.menu_update).isVisible = PassViewActivityBase.mightPassBeAbleToUpdate(currentPass)
|
||||
menu.findItem(R.id.install_shortcut).isVisible = (23..25).contains(Build.VERSION.SDK_INT)
|
||||
menu.findItem(R.id.menu_map).isVisible = currentPass.locations.isNotEmpty()
|
||||
menu.findItem(R.id.menu_update).isVisible = mightPassBeAbleToUpdate(currentPass)
|
||||
menu.findItem(R.id.install_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(this)
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
|
@ -113,8 +118,7 @@ class PassViewActivity : PassViewActivityBase() {
|
|||
NavUtils.navigateUpTo(this, upIntent)
|
||||
}
|
||||
true
|
||||
}
|
||||
else false
|
||||
} else false
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
|
|
@ -3,22 +3,23 @@ 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 com.google.android.material.snackbar.Snackbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.*
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.ligi.passandroid.BuildConfig
|
||||
import org.ligi.passandroid.R
|
||||
import org.ligi.passandroid.model.InputStreamWithSource
|
||||
import org.ligi.passandroid.model.PassBitmapDefinitions.BITMAP_ICON
|
||||
|
@ -28,7 +29,6 @@ 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,34 +150,26 @@ 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)
|
||||
}
|
||||
|
||||
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 name: CharSequence = currentPass.description.let {
|
||||
if (it.isNullOrEmpty()) "pass" else it
|
||||
}
|
||||
|
||||
val targetIntent = Intent(this, PassViewActivity::class.java)
|
||||
.setAction(Intent.ACTION_MAIN)
|
||||
.putExtra(EXTRA_KEY_UUID, currentPass.id)
|
||||
val shortcutInfo = ShortcutInfoCompat.Builder(this, "shortcut$name")
|
||||
.setIntent(targetIntent)
|
||||
.setShortLabel(name)
|
||||
.setIcon(IconCompat.createWithBitmap(shortcutIcon))
|
||||
.build()
|
||||
ShortcutManagerCompat.requestPinShortcut(this, shortcutInfo, null)
|
||||
|
||||
}
|
||||
|
||||
inner class UpdateAsync : Runnable {
|
||||
|
|
|
@ -2,23 +2,22 @@ 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
|
||||
|
@ -29,7 +28,7 @@ import org.ligi.passandroid.ui.pass_view_holder.VerbosePassViewHolder
|
|||
class PassViewFragment : Fragment() {
|
||||
|
||||
private val passViewHelper by lazy { PassViewHelper(requireActivity()) }
|
||||
private var passStore : PassStore = App.kodein.instance()
|
||||
internal val passStore : PassStore by inject()
|
||||
lateinit var pass : Pass
|
||||
|
||||
private fun processImage(view: ImageView, name: String, pass: Pass) {
|
||||
|
|
|
@ -6,14 +6,17 @@ import android.os.Bundle
|
|||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.android.ext.android.inject
|
||||
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)
|
||||
|
@ -26,7 +29,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 = App.settings.getNightMode()
|
||||
@AppCompatDelegate.NightMode val nightMode = settings.getNightMode()
|
||||
|
||||
if (nightMode == MODE_NIGHT_AUTO) {
|
||||
ensureDayNightWithPermissionCheck()
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
package org.ligi.passandroid.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.view.MenuItem
|
||||
import com.github.salomonbrys.kodein.instance
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.ortiz.touch.TouchImageView
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.ligi.passandroid.model.PassStore
|
||||
|
||||
class TouchImageActivity : AppCompatActivity() {
|
||||
|
||||
val passStore: PassStore = App.kodein.instance()
|
||||
val passStore: PassStore by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -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?)
|
||||
: this(App.settings.getPassesDir(), context, passStore, onSuccessCallback, failCallback)
|
||||
constructor(context: Context, passStore: PassStore, onSuccessCallback: SuccessCallback?, failCallback: FailCallback?, settings: Settings)
|
||||
: this(settings.getPassesDir(), context, passStore, onSuccessCallback, failCallback)
|
||||
|
||||
}
|
||||
|
|
|
@ -11,18 +11,25 @@ import net.lingala.zip4j.exception.ZipException
|
|||
import okio.buffer
|
||||
import okio.source
|
||||
import org.json.JSONObject
|
||||
import org.ligi.passandroid.App
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.ligi.passandroid.Tracker
|
||||
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 {
|
||||
object UnzipPassController : KoinComponent {
|
||||
|
||||
val tracker :Tracker by inject()
|
||||
val settings : Settings by inject()
|
||||
|
||||
interface SuccessCallback {
|
||||
fun call(uuid: String)
|
||||
|
@ -39,7 +46,7 @@ object UnzipPassController {
|
|||
processFile(FileUnzipControllerSpec(tempFile.absolutePath, spec))
|
||||
tempFile.delete()
|
||||
} catch (e: Exception) {
|
||||
App.tracker.trackException("problem processing InputStream", e, false)
|
||||
tracker.trackException("problem processing InputStream", e, false)
|
||||
spec.failCallback?.fail("problem with temp file: $e")
|
||||
}
|
||||
|
||||
|
@ -158,6 +165,6 @@ object UnzipPassController {
|
|||
}
|
||||
|
||||
class InputStreamUnzipControllerSpec(internal val inputStreamWithSource: InputStreamWithSource, context: Context, passStore: PassStore,
|
||||
onSuccessCallback: SuccessCallback?, failCallback: FailCallback?) : UnzipControllerSpec(context, passStore, onSuccessCallback, failCallback)
|
||||
onSuccessCallback: SuccessCallback?, failCallback: FailCallback?) : UnzipControllerSpec(context, passStore, onSuccessCallback, failCallback, settings)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
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() {
|
||||
|
||||
private fun getPass(): PassImpl = App.passStore.currentPass as PassImpl
|
||||
val passStore by inject<PassStore>()
|
||||
|
||||
private fun getPass(): PassImpl = passStore.currentPass as PassImpl
|
||||
|
||||
private var isEditingHiddenFields: Boolean = false
|
||||
|
||||
|
|
|
@ -2,15 +2,13 @@ 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, bus: EventBus, pass: Pass, barCode: BarCode) {
|
||||
fun showBarcodeEditDialog(context: AppCompatActivity, refreshCallback: () -> Unit, pass: Pass, barCode: BarCode) {
|
||||
val view = context.inflate(R.layout.barcode_edit)
|
||||
|
||||
val barcodeEditController = BarcodeEditController(view, context, barCode)
|
||||
|
@ -20,7 +18,7 @@ fun showBarcodeEditDialog(context: AppCompatActivity, bus: EventBus, pass: Pass,
|
|||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
pass.barCode = barcodeEditController.getBarCode()
|
||||
bus.post(PassRefreshEvent(pass))
|
||||
refreshCallback.invoke()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
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 org.greenrobot.eventbus.EventBus
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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
|
||||
|
@ -18,7 +16,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, bus: EventBus) {
|
||||
fun showCategoryPickDialog(context: Context, pass: Pass, refreshCallback: () -> Unit) {
|
||||
|
||||
val adapter = object : BaseAdapter() {
|
||||
|
||||
|
@ -47,7 +45,7 @@ fun showCategoryPickDialog(context: Context, pass: Pass, bus: EventBus) {
|
|||
val builder = AlertDialog.Builder(context)
|
||||
builder.setAdapter(adapter) { _, position ->
|
||||
pass.type = passTypes[position]
|
||||
bus.post(PassRefreshEvent(pass))
|
||||
refreshCallback.invoke()
|
||||
}
|
||||
builder.setTitle(R.string.select_category_dialog_title)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
package org.ligi.passandroid.ui.edit.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
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, bus: EventBus) {
|
||||
fun showColorPickDialog(context: Context, pass: Pass, refreshCallback : () -> Unit) {
|
||||
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
|
||||
bus.post(PassRefreshEvent(pass))
|
||||
refreshCallback.invoke()
|
||||
}.setNegativeButton(android.R.string.cancel, null).setTitle(R.string.change_color_dialog_title).show()
|
||||
}
|
||||
|
|
30
android/src/main/res/layout/activity_import.xml
Normal file
30
android/src/main/res/layout/activity_import.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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>
|
38
android/src/main/res/layout/activity_scan.xml
Normal file
38
android/src/main/res/layout/activity_scan.xml
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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>
|
|
@ -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,10 +50,6 @@ 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>
|
||||
|
@ -123,9 +119,6 @@ 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>
|
||||
|
|
|
@ -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,9 +28,6 @@ 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…)
|
||||
|
@ -54,7 +51,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>
|
||||
|
@ -117,9 +114,6 @@ 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>
|
||||
|
|
|
@ -65,9 +65,6 @@
|
|||
<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>
|
||||
|
|
|
@ -37,9 +37,6 @@ 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:
|
||||
|
@ -115,9 +112,6 @@ 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>
|
||||
|
|
|
@ -37,9 +37,6 @@ 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:
|
||||
|
@ -120,9 +117,6 @@ 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>
|
||||
|
|
|
@ -41,9 +41,6 @@ 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>
|
||||
|
@ -110,9 +107,6 @@ 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>
|
||||
|
|
|
@ -41,9 +41,6 @@ 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 .. )
|
||||
|
@ -125,9 +122,6 @@ 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>
|
||||
|
|
|
@ -45,9 +45,6 @@ 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...).
|
||||
|
@ -131,9 +128,6 @@ 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>
|
||||
|
|
|
@ -29,9 +29,6 @@
|
|||
и <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 был просто \"сломан\", написан не по стандарту.
|
||||
|
@ -113,9 +110,6 @@ 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>
|
||||
|
|
|
@ -46,7 +46,7 @@ and <a href=\'https://github.com/irgendwr\'>Jonas Bögle</a>
|
|||
for pull-requests!
|
||||
|
||||
<h1>Feedback</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>
|
||||
Give feedback via email <a href=\'mailto:ligi@ligi.de\'> to the author.</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">please wait - shouldn\'t be long</string>
|
||||
<string name="please_wait">Importing the pass - please wait a bit</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,9 +132,6 @@ 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>
|
||||
|
@ -219,6 +216,7 @@ 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>
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
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 createTrackerKodeinModule(context: Context) = Kodein.Module {
|
||||
bind<Tracker>() with singleton { NotTracker() }
|
||||
}
|
||||
fun createTracker(context: Context) = NotTracker() as Tracker
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
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 createTrackerKodeinModule(context: Context) = Kodein.Module {
|
||||
bind<Tracker>() with singleton { AnalyticsTracker(context) }
|
||||
}
|
||||
fun createTracker(context: Context) = AnalyticsTracker(context) as Tracker
|
||||
|
|
|
@ -11,12 +11,16 @@ 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
|
||||
|
@ -25,7 +29,7 @@ class LocationsMapFragment : SupportMapFragment() {
|
|||
map.setOnMapLoadedCallback {
|
||||
if (clickToFullscreen)
|
||||
map.setOnMapClickListener {
|
||||
App.passStore.currentPass = baseActivity.currentPass
|
||||
passStore.currentPass = baseActivity.currentPass
|
||||
baseActivity.startActivityFromClass(FullscreenMapActivity::class.java)
|
||||
}
|
||||
|
||||
|
|
10
build.gradle
10
build.gradle
|
@ -1,21 +1,23 @@
|
|||
buildscript {
|
||||
ext {
|
||||
kotlin_version = '1.3.61'
|
||||
kotlin_version = '1.3.70'
|
||||
mockito_version = '3.2.4'
|
||||
}
|
||||
|
||||
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.build:gradle:3.5.3'
|
||||
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.github.trevjonez.composer-gradle-plugin:plugin:0.13.1'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
|
||||
classpath 'de.mobilej.unmock:UnMockPlugin:0.7.4'
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xms128m -Xmx1024m -XX:+CMSClassUnloadingEnabled
|
||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
Loading…
Reference in a new issue