Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed .gitmodules
Empty file.
393 changes: 200 additions & 193 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ kotlin {
api(project(":transit:warsaw"))
api(project(":transit:zolotayakorona"))
api(project(":transit:serialonly"))
api(project(":flipper"))
api(project(":transit:krocap"))
api(project(":transit:snapper"))
api(project(":transit:ndef"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.codebutler.farebot.desktop

import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import com.codebutler.farebot.card.serialize.CardSerializer
import com.codebutler.farebot.flipper.FlipperTransportFactory
import com.codebutler.farebot.flipper.JvmFlipperTransportFactory
import com.codebutler.farebot.persist.CardKeysPersister
import com.codebutler.farebot.persist.CardPersister
import com.codebutler.farebot.persist.db.DbCardKeysPersister
Expand Down Expand Up @@ -87,6 +89,10 @@ abstract class DesktopAppGraph : AppGraph {
json: Json,
): CardImporter = CardImporter(cardSerializer, json)

@Provides
@SingleIn(AppScope::class)
fun provideFlipperTransportFactory(): FlipperTransportFactory = JvmFlipperTransportFactory()

@Provides
fun provideNullableCardScanner(scanner: CardScanner): CardScanner? = scanner
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,49 +71,50 @@ class DesktopCardScanner : CardScanner {

scanJob =
scope.launch {
val backends = discoverBackends()
val backendJobs =
backends.map { backend ->
launch {
println("[DesktopCardScanner] Starting ${backend.name} backend")
try {
backend.scanLoop(
onCardDetected = { tag ->
_scannedTags.tryEmit(tag)
},
onCardRead = { rawCard ->
_scannedCards.tryEmit(rawCard)
},
onError = { error ->
_scanErrors.tryEmit(error)
},
)
} catch (e: Exception) {
if (isActive) {
println("[DesktopCardScanner] ${backend.name} backend failed: ${e.message}")
try {
val backends = discoverBackends()
val backendJobs =
backends.map { backend ->
launch {
println("[DesktopCardScanner] Starting ${backend.name} backend")
try {
backend.scanLoop(
onCardDetected = { tag ->
_scannedTags.tryEmit(tag)
},
onCardRead = { rawCard ->
_scannedCards.tryEmit(rawCard)
},
onError = { error ->
_scanErrors.tryEmit(error)
},
)
} catch (e: Exception) {
if (isActive) {
println("[DesktopCardScanner] ${backend.name} backend failed: ${e.message}")
}
} catch (e: Error) {
// Catch LinkageError / UnsatisfiedLinkError from native libs
println("[DesktopCardScanner] ${backend.name} backend unavailable: ${e.message}")
}
} catch (e: Error) {
// Catch LinkageError / UnsatisfiedLinkError from native libs
println("[DesktopCardScanner] ${backend.name} backend unavailable: ${e.message}")
}
}
}

backendJobs.forEach { it.join() }
backendJobs.forEach { it.join() }

// All backends exited — emit error only if none ran successfully
if (isActive) {
_scanErrors.tryEmit(Exception("All NFC reader backends failed. Is a USB NFC reader connected?"))
// All backends exited — emit error only if none ran successfully
if (isActive) {
_scanErrors.tryEmit(Exception("All NFC reader backends failed. Is a USB NFC reader connected?"))
}
} finally {
_isScanning.value = false
}
_isScanning.value = false
}
}

override fun stopActiveScan() {
scanJob?.cancel()
scanJob = null
_isScanning.value = false
PN533Device.shutdown()
}

private suspend fun discoverBackends(): List<NfcReaderBackend> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import com.codebutler.farebot.shared.nfc.ScannedTag
interface NfcReaderBackend {
val name: String

fun scanLoop(
suspend fun scanLoop(
onCardDetected: (ScannedTag) -> Unit,
onCardRead: (RawCard<*>) -> Unit,
onError: (Throwable) -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import com.codebutler.farebot.card.ultralight.UltralightCardReader
import com.codebutler.farebot.shared.nfc.ISO7816Dispatcher
import com.codebutler.farebot.shared.nfc.ScannedTag
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

/**
* Abstract base for PN53x-family USB reader backends.
Expand All @@ -59,7 +58,7 @@ abstract class PN53xReaderBackend(
tg: Int,
): CardTransceiver = PN533CardTransceiver(pn533, tg)

override fun scanLoop(
override suspend fun scanLoop(
onCardDetected: (ScannedTag) -> Unit,
onCardRead: (RawCard<*>) -> Unit,
onError: (Throwable) -> Unit,
Expand All @@ -72,10 +71,8 @@ abstract class PN53xReaderBackend(
transport.flush()
val pn533 = PN533(transport)
try {
runBlocking {
initDevice(pn533)
pollLoop(pn533, onCardDetected, onCardRead, onError)
}
initDevice(pn533)
pollLoop(pn533, onCardDetected, onCardRead, onError)
} finally {
pn533.close()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import com.codebutler.farebot.card.ultralight.UltralightCardReader
import com.codebutler.farebot.card.vicinity.VicinityCardReader
import com.codebutler.farebot.shared.nfc.ISO7816Dispatcher
import com.codebutler.farebot.shared.nfc.ScannedTag
import kotlinx.coroutines.runBlocking
import javax.smartcardio.CardException
import javax.smartcardio.CommandAPDU
import javax.smartcardio.TerminalFactory
Expand All @@ -51,7 +50,7 @@ import javax.smartcardio.TerminalFactory
class PcscReaderBackend : NfcReaderBackend {
override val name: String = "PC/SC"

override fun scanLoop(
override suspend fun scanLoop(
onCardDetected: (ScannedTag) -> Unit,
onCardRead: (RawCard<*>) -> Unit,
onError: (Throwable) -> Unit,
Expand Down Expand Up @@ -96,7 +95,7 @@ class PcscReaderBackend : NfcReaderBackend {
println("[PC/SC] Tag ID: ${tagId.hex()}")

onCardDetected(ScannedTag(id = tagId, techList = listOf(info.cardType.name)))
val rawCard = runBlocking { readCard(info, channel, tagId) }
val rawCard = readCard(info, channel, tagId)
onCardRead(rawCard)
println("[PC/SC] Card read successfully")
} finally {
Expand Down
59 changes: 9 additions & 50 deletions app/ios/FareBot.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,18 @@
objects = {

/* Begin PBXBuildFile section */
7BFF0BD60CC51FB78D8A764D /* FareBotKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E296318A4ABC8EE549B0C47E /* FareBotKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8E11E423477F24B274729679 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534508E7AAA01FF336ECDC0C /* iOSApp.swift */; };
D52C887B87D2D7CD2DF7A030 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 445C357A8AB1DD9317170556 /* Assets.xcassets */; };
EA3AC0F2B800448FB22567C4 /* FareBotKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E296318A4ABC8EE549B0C47E /* FareBotKit.framework */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
C396E052E1BD6239F169D5D4 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
7BFF0BD60CC51FB78D8A764D /* FareBotKit.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
154ABFFD520502DDADF58B61 /* FareBot.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = FareBot.app; sourceTree = BUILT_PRODUCTS_DIR; };
445C357A8AB1DD9317170556 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
534508E7AAA01FF336ECDC0C /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
A893E13DD60D0B10ECB49A59 /* FareBot.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FareBot.entitlements; sourceTree = "<group>"; };
E296318A4ABC8EE549B0C47E /* FareBotKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FareBotKit.framework; path = "../farebot-app/build/bin/iosSimulatorArm64/debugFramework/FareBotKit.framework"; sourceTree = "<group>"; };
E65B641D90F72BA2E1DEAFF7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
E1F31206D4AE717D1E2DE8D8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EA3AC0F2B800448FB22567C4 /* FareBotKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
2098F79B3F3B6A526575D03F /* Products */ = {
isa = PBXGroup;
Expand All @@ -67,19 +39,10 @@
path = FareBot;
sourceTree = "<group>";
};
9B0F3B4C26A1726B3C4A2BE9 /* Frameworks */ = {
isa = PBXGroup;
children = (
E296318A4ABC8EE549B0C47E /* FareBotKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
E8645766090C58DFD719F43E = {
isa = PBXGroup;
children = (
35C5B4B3C4B8B2643DF5E68A /* FareBot */,
9B0F3B4C26A1726B3C4A2BE9 /* Frameworks */,
2098F79B3F3B6A526575D03F /* Products */,
);
sourceTree = "<group>";
Expand All @@ -94,8 +57,6 @@
B2007E057701C93D2F6474DC /* Build KMP Framework */,
42DDFD780701DBC1BD02AB98 /* Sources */,
5DA19835EA0C3024B2D5A4B9 /* Resources */,
E1F31206D4AE717D1E2DE8D8 /* Frameworks */,
C396E052E1BD6239F169D5D4 /* Embed Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -176,7 +137,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :farebot-app:embedAndSignAppleFrameworkForXcode\n";
shellScript = "cd \"$SRCROOT/../..\"\n./gradlew :app:embedAndSignAppleFrameworkForXcode\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down Expand Up @@ -324,11 +285,10 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = ZJ9GEQ36AH;
FRAMEWORK_SEARCH_PATHS = (
"$(SRCROOT)/../farebot-app/build/XCFrameworks/release",
"$(SRCROOT)/../farebot-app/build/bin/iosSimulatorArm64/debugFramework",
"$(SRCROOT)/../farebot-app/build/bin/iosArm64/debugFramework",
"$(SRCROOT)/../farebot-app/build/bin/iosX64/debugFramework",
"\"../farebot-app/build/bin/iosSimulatorArm64/debugFramework\"",
"$(SRCROOT)/../../app/build/XCFrameworks/release",
"$(SRCROOT)/../../app/build/bin/iosSimulatorArm64/debugFramework",
"$(SRCROOT)/../../app/build/bin/iosArm64/debugFramework",
"$(SRCROOT)/../../app/build/bin/iosX64/debugFramework",
);
INFOPLIST_FILE = FareBot/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -357,11 +317,10 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = ZJ9GEQ36AH;
FRAMEWORK_SEARCH_PATHS = (
"$(SRCROOT)/../farebot-app/build/XCFrameworks/release",
"$(SRCROOT)/../farebot-app/build/bin/iosSimulatorArm64/debugFramework",
"$(SRCROOT)/../farebot-app/build/bin/iosArm64/debugFramework",
"$(SRCROOT)/../farebot-app/build/bin/iosX64/debugFramework",
"\"../farebot-app/build/bin/iosSimulatorArm64/debugFramework\"",
"$(SRCROOT)/../../app/build/XCFrameworks/release",
"$(SRCROOT)/../../app/build/bin/iosSimulatorArm64/debugFramework",
"$(SRCROOT)/../../app/build/bin/iosArm64/debugFramework",
"$(SRCROOT)/../../app/build/bin/iosX64/debugFramework",
);
INFOPLIST_FILE = FareBot/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
3 changes: 0 additions & 3 deletions app/ios/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ targets:
SystemCapabilities:
com.apple.NearFieldCommunicationTagReading:
enabled: 1
dependencies:
- framework: "../../app/build/bin/iosSimulatorArm64/debugFramework/FareBotKit.framework"
embed: true
preBuildScripts:
- name: "Build KMP Framework"
script: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.codebutler.farebot.app.core.nfc.TagReaderFactory
import com.codebutler.farebot.app.core.platform.AndroidAppPreferences
import com.codebutler.farebot.app.feature.home.AndroidCardScanner
import com.codebutler.farebot.card.serialize.CardSerializer
import com.codebutler.farebot.flipper.AndroidFlipperTransportFactory
import com.codebutler.farebot.flipper.FlipperTransportFactory
import com.codebutler.farebot.persist.CardKeysPersister
import com.codebutler.farebot.persist.CardPersister
import com.codebutler.farebot.persist.db.DbCardKeysPersister
Expand Down Expand Up @@ -114,6 +116,11 @@ abstract class AndroidAppGraph : AppGraph {
json: Json,
): CardImporter = CardImporter(cardSerializer, json)

@Provides
@SingleIn(AppScope::class)
fun provideFlipperTransportFactory(context: Context): FlipperTransportFactory =
AndroidFlipperTransportFactory(context)

@Provides
fun provideNullableCardScanner(scanner: CardScanner): CardScanner? = scanner
}
Expand Down
16 changes: 16 additions & 0 deletions app/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,20 @@
<!-- Date labels -->
<string name="date_today">Today</string>
<string name="date_yesterday">Yesterday</string>

<!-- Flipper Zero -->
<string name="flipper_zero">Flipper Zero</string>
<string name="flipper_connecting">Connecting\u2026</string>
<string name="flipper_connecting_message">Connecting to Flipper Zero\u2026</string>
<string name="flipper_disconnect">Disconnect</string>
<string name="flipper_connect_prompt">Connect your Flipper Zero to browse and import NFC card dumps.</string>
<string name="flipper_connect_usb">Connect via USB</string>
<string name="flipper_connect_ble">Connect via Bluetooth</string>
<string name="flipper_no_files">No NFC files found</string>
<string name="flipper_up">Up</string>
<string name="flipper_import_selected">Import Selected (%1$d)</string>
<string name="flipper_import_keys">Import Keys</string>
<string name="flipper_importing">Importing %1$s</string>
<string name="flipper_import_progress">%1$d of %2$d</string>
<string name="flipper_bytes">%1$d bytes</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ interface CardKeysPersister {
fun insert(savedKey: SavedKey): Long

fun delete(savedKey: SavedKey)

fun getGlobalKeys(): List<ByteArray>

fun insertGlobalKeys(
keys: List<ByteArray>,
source: String,
)

fun deleteAllGlobalKeys()
}
Loading