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
Binary file added app/src/main/assets/img/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/src/main/assets/img/tsapre.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.example.barcodescanner.model.SearchEngine
import com.example.barcodescanner.model.schema.BarcodeSchema
import com.example.barcodescanner.model.schema.OtpAuth
import com.example.barcodescanner.usecase.Logger
import com.example.barcodescanner.usecase.BarcodePkpassSaver
import com.example.barcodescanner.usecase.save
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
Expand Down Expand Up @@ -200,6 +201,8 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene
button_open_otp.setOnClickListener { openOtpInOtherApp() }
button_open_bitcoin_uri.setOnClickListener { openBitcoinUrl() }
button_open_link.setOnClickListener { openLink() }
button_save_as_pkpass.setOnClickListener { savePkpass() }
button_share_as_pkpass.setOnClickListener { sharePkpass() }
button_save_bookmark.setOnClickListener { saveBookmark() }

button_call_phone_1.setOnClickListener { callPhone(barcode.phone) }
Expand Down Expand Up @@ -455,6 +458,41 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene
startActivityIfExists(intent)
}

private fun savePkpass() {
BarcodePkpassSaver.saveBarcodeAsPkpass(this, originalBarcode, null)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
Toast.makeText(this, R.string.activity_save_barcode_as_text_file_name_saved, Toast.LENGTH_LONG).show()
},
{ error ->
showError(error)
}
)
.addTo(disposable)
}

private fun sharePkpass() {
val uri = Array<Uri?>(1, init={i:Int -> null})
BarcodePkpassSaver.saveBarcodeAsPkpass(this, originalBarcode, uri)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri[0], "application/vnd.apple.pkpass")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivityIfExists(intent)
},
{ error ->
showError(error)
}
)
.addTo(disposable)
}

private fun shareBarcodeAsText() {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
Expand Down Expand Up @@ -694,6 +732,8 @@ class BarcodeActivity : BaseActivity(), DeleteConfirmationDialogFragment.Listene
button_open_bitcoin_uri.isVisible = barcode.bitcoinUri.isNullOrEmpty().not()
button_open_link.isVisible = barcode.url.isNullOrEmpty().not()
button_save_bookmark.isVisible = barcode.schema == BarcodeSchema.BOOKMARK
button_save_as_pkpass.isVisible = barcode.schema == BarcodeSchema.BOARDINGPASS
button_share_as_pkpass.isVisible = barcode.schema == BarcodeSchema.BOARDINGPASS
}

private fun showButtonText() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class BoardingPass(
val carrier: String? = null,
val flight: String? = null,
val date: String? = null,
val dateJ: Int = 0,
val cabin: String? = null,
val seat: String? = null,
val seq: String? = null,
Expand All @@ -32,84 +33,99 @@ class BoardingPass(
private val DATE_FORMATTER by unsafeLazy { SimpleDateFormat("d MMMM", Locale.ENGLISH) }

fun parse(text: String): BoardingPass? {
try {

// M1 means single leg barcode
if (text.startsWithIgnoreCase("M1").not()) {
return null
}
// E means electronic ticket
if (text[22] != 'E') {
return null
}
val fieldSize: Int = text.slice(58..59).toInt(16)
// > is the marker for the airline specific optional fields
if (fieldSize != 0 && text[60] != '>') {
return null
}
// ^ is the mandatory security marker
if (text[60+fieldSize] != '^') {
return null
}

val name: String = text.slice(2..21).trim()
val pnr: String = text.slice(23..29).trim()
val from: String = text.slice(30..32)
val to: String = text.slice(33..35)
val carrier: String = text.slice(36..38).trim()
val flight: String = text.slice(39..43).trim()
val dateJ: String = text.slice(44..46)
val cabin: String = text.slice(47..47)
val seat: String = text.slice(48..51).trim()
val seq: String = text.slice(52..56).trim()
// 57 is status - ignore

val today = Calendar.getInstance()
today.set(Calendar.DAY_OF_YEAR, dateJ.toInt())
val date: String = DATE_FORMATTER.format(today.getTime())
var selectee : String = ""
var ticket : String = ""
var ffAirline : String = ""
var ffNo : String = ""
var fasttrack: String = ""
if (text.length < 60) {
return null
}

if (fieldSize != 0) {
val size: Int = text.slice(62..63).toInt(16)
if (size != 0 && size != 24) {
// M1 means single leg barcode
if (text.startsWithIgnoreCase("M1").not()) {
return null
}
// E means electronic ticket
if (text[22] != 'E') {
return null
}
val fieldSize: Int = text.slice(58..59).toInt(16)
// > is the marker for the airline specific optional fields
if (fieldSize != 0 && text[60] != '>') {
return null
}
// don't really care about the first optional field
// it's mostly baggage and checkin information
val size1: Int = text.slice(64+size..65+size).toInt(16)
// European boarding passes are 42 to have appended fasttrack
// US boarding passes are size 41 with no fasttrack
if (size1 != 0 && size1 != 41 && size1 != 42) {
// ^ is the security marker; sometimes missing on paper passes
if (text.length > 60 + fieldSize && text[60+fieldSize] != '^') {
return null
} else {
ticket = text.slice(66+size..78+size).trim()
// TSA field:
// blank for not US flights
// 0 - normal cleared
// 1 - no fly
// 2 - selected for enhanced security
// 3 - precheck
selectee = text.slice(79+size..79+size)
ffAirline = text.slice(84+size..86+size).trim()
ffNo = text.slice(87+size..102+size).trim()
if (size1 == 42) {
// Y - fasttrack eligible
fasttrack = text.slice(107+size..107+size)
}

val name = text.slice(2..21).trim()
val pnr = text.slice(23..29).trim()
val from = text.slice(30..32)
val to = text.slice(33..35)
val carrier = text.slice(36..38).trim()
val flight = text.slice(39..43).trim()
val dateJ = text.slice(44..46).toInt()
val cabin = text.slice(47..47)
val seat = text.slice(48..51).trim()
val seq = text.slice(52..56)
// 57 is status - ignore

val today = Calendar.getInstance()
today.set(Calendar.DAY_OF_YEAR, dateJ)
val date: String = DATE_FORMATTER.format(today.getTime())
var selectee : String? = null
var ticket : String? = null
var ffAirline : String? = null
var ffNo : String? = null
var fasttrack: String? = null

if (fieldSize != 0) {
// don't actually use version but it must parse as an Int
@Suppress("UNUSED_VARIABLE")
val version: Int = text.slice(61..61).toInt()
val size: Int = text.slice(62..63).toInt(16)

if (size != 0 && size < 11) {
return null
}
// don't really care about the first optional field
// it's mostly baggage and checkin information
val size1: Int = text.slice(64+size..65+size).toInt(16)
// European boarding passes are 42 to have appended fasttrack
// US boarding passes are size 41 with no fasttrack
if (size1 != 0 && (size1 < 37 || size1 > 42)) {
return null
} else {
ticket = text.slice(66+size..78+size).trim()
// TSA field:
// blank for not US flights
// 0 - normal cleared
// 1 - no fly
// 2 - selected for enhanced security
// 3 - precheck
selectee = text.slice(79+size..79+size)
ffAirline = text.slice(84+size..86+size).trim()
ffNo = text.slice(87+size..102+size).trim()
if (size1 == 42) {
// Y - fasttrack eligible
fasttrack = text.slice(107+size..107+size)
}
}
}
}

return BoardingPass(name, pnr, from, to, carrier, flight, date,
cabin, seat, seq, ticket, selectee,
ffAirline, ffNo, fasttrack,
text)
return BoardingPass(name, pnr, from, to, carrier, flight, date,
dateJ, cabin, seat, seq, ticket, selectee,
ffAirline, ffNo, fasttrack,
text)
} catch(e: Exception) {
// mostly number format and parse past end of string errors
return null
}
}
}

override val schema = BarcodeSchema.BOARDINGPASS
override fun toFormattedText(): String = listOf(name, pnr, "$from->$to", "$carrier$flight", date, cabin, seat, seq, ticket, selectee, "$ffAirline$ffNo", fasttrack).joinToStringNotNullOrBlankWithLineSeparator()
override fun toBarcodeText(): String = "$blob"
override fun toBarcodeText(): String {
return blob ?: ""
}
}
Loading