Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Release/ios support #391

Merged
merged 23 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
031c2cb
fix: temp remove the last scan date
ricardobrg Oct 4, 2023
eddfa9e
fix: remove debug logs
mike-audi Oct 4, 2023
130346f
fix
Schuler-Gabriel Oct 4, 2023
3c7a50f
Document Email and Retailer WIP
JesseMonteiro Oct 4, 2023
4272e1d
all public methods and files documented
JesseMonteiro Oct 4, 2023
9d5df35
fix: scan is scrape the right amount of receipts without return dupli…
Schuler-Gabriel Oct 4, 2023
0ebc707
fix: support separate android and ios keys
mike-audi Oct 4, 2023
ed55750
Merge pull request #388 from tiki/fix/ios/update-documentation
ricardobrg Oct 4, 2023
f7856cb
fix: merge fix/ios-and-android-keys
ricardobrg Oct 4, 2023
c6dedad
fix: merge conflicts
ricardobrg Oct 4, 2023
6c2a556
fix: resolve pr
Schuler-Gabriel Oct 4, 2023
f41f869
Merge pull request #381 from tiki/bug/onError-callbacks-fired-multipl…
ricardobrg Oct 4, 2023
03a935d
fix initialize licenseKey and productKey
JesseMonteiro Oct 4, 2023
068e6cb
Merge branch 'release/ios-support' of https://github.com/tiki/capture…
JesseMonteiro Oct 4, 2023
431c3af
feat: update readme for ios support
ricardobrg Oct 4, 2023
70d7ae6
version bump
actions-user Oct 4, 2023
964fa51
fix: android ReqInitialize
ricardobrg Oct 4, 2023
e4e3a66
fix: add retailers to example app
ricardobrg Oct 4, 2023
fbf3474
fix scan dayCutOff calculation
JesseMonteiro Oct 4, 2023
60ff93e
Merge branch 'release/ios-support' of https://github.com/tiki/capture…
JesseMonteiro Oct 4, 2023
0041139
fix: webview closes
ricardobrg Oct 5, 2023
f2a52a5
fix logout and scan onComplete
JesseMonteiro Oct 5, 2023
c6d4304
Merge branch 'release/ios-support' of https://github.com/tiki/capture…
JesseMonteiro Oct 5, 2023
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
21 changes: 6 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

The TIKI Capture Receipt Plugin for Capacitor enables the extraction of receipt data from photos, email inboxes, and retailer accounts using [Microblink's](https://microblink.com) OCR technology.
The TIKI Capture Receipt Plugin for Capacitor enables the extraction of receipt data from email inboxes, and retailer accounts using [Microblink's](https://microblink.com) technology.

This plugin wraps (and simplifies) Microblink's native [iOS](https://github.com/BlinkReceipt/blinkreceipt-ios) and [Android](https://github.com/BlinkReceipt/blinkreceipt-android) SDKs for use with Capacitor applications.

Expand All @@ -14,9 +14,9 @@ This plugin wraps (and simplifies) Microblink's native [iOS](https://github.com/

1. Install the dependency from NPM

`npm install @mytiki/tiki-capture-receipt-capacitor`
`npm install @mytiki/capture-receipt-capacitor`

2. Add Microblink iOS dependencies in `ios/App/Podfile`
2. iOS only - Add Microblink iOS dependencies in `ios/App/Podfile`

```
source 'https://github.com/BlinkReceipt/PodSpecRepo.git' # <- add this
Expand Down Expand Up @@ -48,6 +48,8 @@ end

That's it. And yes, it's really that easy.

_NOTE: if Cocoapods can't find BlinkReceipt/BlinkEReceipt in iOS, run `pod install --repo-udpate` and `npx cap sync` again._

## Initialization

**IMPORTANT: Requires a valid Microblink BlinkReceipt License Key and Product Intelligence Key. [Reach out to get one →](https://mytiki.com)**
Expand All @@ -56,23 +58,12 @@ That's it. And yes, it's really that easy.
```ts
import { instance } from '@mytiki/tiki-capture-receipt-capacitor'

instance.initialize('<LICENSE KEY>', '<PRODUCT KEY>')
instance.initialize('<PRODUCT KEY>', '<IOS LICENSE KEY>', '<ANDROID LICENSE KEY>',
.then((rsp) => console.log(`initialized`))
```

_NOTE: Only iOS and Android are supported._

### Google OAuth

To use Google OAuth for Gmail login provide a valid [Google OAuth Client ID](https://developers.google.com/identity/protocols/oauth2) in initialization:

```ts
import { instance } from '@mytiki/tiki-capture-receipt-capacitor'

instance.initialize('<LICENSE KEY>', '<PRODUCT KEY>', '<GOOGLE_CLIENT_ID>')
.then((rsp) => console.log(`initialized`))
```

# Contributing

- Use [GitHub Issues](https://github.com/tiki/tiki-capture-receipt-capacitor/issues) to report any bugs you find or to request enhancements.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ class CaptureReceipt {
} else {
when (account.accountCommon.type) {
AccountTypeEnum.EMAIL -> {
email.remove(context, account, onComplete, onError)
email.logout(context, account, onComplete, onError)
}

AccountTypeEnum.RETAILER -> {
retailer.remove(context, account, onComplete, onError)
retailer.logout(context, account, onComplete, onError)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
package com.mytiki.sdk.capture.receipt.capacitor.email

import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.fragment.app.FragmentManager
import com.microblink.core.InitializeCallback
import com.microblink.core.ScanResults
Expand All @@ -27,9 +25,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.tasks.await
import java.time.Duration
import java.util.Calendar
import kotlin.math.abs
import kotlin.math.floor

typealias OnReceiptCallback = ((receipt: ScanResults?) -> Unit)
Expand Down Expand Up @@ -137,32 +133,30 @@ class Email {
onError: (msg: String) -> Unit,
onComplete: () -> Unit
) {
val onRead = { lastScrape: Long ->
MainScope().async {
var dayCutOff = 15
val now = Calendar.getInstance().timeInMillis
val lastScrape = context.getImapScanTime().await()
val diffInMillis = now - lastScrape
val diffInDays = floor((diffInMillis/86400000).toDouble()).toInt()
if(diffInDays <= 15) {
val diffInDays = floor((diffInMillis / 86400000).toDouble()).toInt()
if (diffInDays <= 15) {
dayCutOff = diffInDays
}

this.client(context, onError) { client ->
[email protected](context, onError) { client ->
client.dayCutoff(dayCutOff)
client.messages(object : MessagesCallback {
override fun onComplete(
credential: PasswordCredentials,
result: List<ScanResults>
) {
if (result.isEmpty()) {
onError("No results for ${credential.username()} - ${credential.provider()}")
}
result.forEach { receipt ->
onReceipt(receipt)
}
context.setImapScanTime(now)
onComplete()
client.close()
}

override fun onException(throwable: Throwable) {
onError(throwable.message ?: throwable.toString())
onComplete()
Expand All @@ -171,7 +165,6 @@ class Email {
})
}
}
context.getImapScanTime(onRead, onError)
}

/**
Expand All @@ -197,7 +190,6 @@ class Email {
} else {
for (credential in credentials) {
val account = Account.fromEmailAccount(credential)

account.isVerified = client.verify(credential).await()
onAccount(account)
returnedAccounts++
Expand All @@ -224,7 +216,7 @@ class Email {
* @param onRemove Callback called when the account is successfully removed.
* @param onError Callback called when an error occurs during account removal.
*/
fun remove(
fun logout(
context: Context,
account: Account,
onRemove: () -> Unit,
Expand All @@ -238,8 +230,9 @@ class Email {
).value
}
client.logout(passwordCredentials).addOnSuccessListener {
onRemove()
client.clearLastCheckedTime(Provider.valueOf(account.accountCommon.id))
context.deleteImapScanTime()
onRemove()
}.addOnFailureListener {
onError(
it.message
Expand All @@ -261,15 +254,16 @@ class Email {
fun flush(context: Context, onComplete: () -> Unit, onError: (msg: String) -> Unit) {
this.client(context, onError) { client ->
client.logout().addOnSuccessListener {
onComplete()
client.clearLastCheckedTime()
context.deleteImapScanTime()
onComplete()
}.addOnFailureListener {
onError(it.message ?: it.toString())
}
}
}

private fun client(
fun client(
context: Context, onError: (String) -> Unit,
onClientReady: (ImapClient) -> Unit
) {
Expand All @@ -280,7 +274,6 @@ class Email {
override fun onComplete() {
clientInitialization.complete(Unit)
}

override fun onException(ex: Throwable) {
onError(ex.message ?: "Error in IMAP client initialization: $ex")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package com.mytiki.sdk.capture.receipt.capacitor.email

import android.content.Context
import androidx.datastore.preferences.core.*
import com.microblink.core.Timberland
import com.mytiki.sdk.capture.receipt.capacitor.plugin.dataStore
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

val key = stringPreferencesKey("tiki-captures-receipt.imap-latest-date")
Expand All @@ -27,17 +31,15 @@ fun Context.setImapScanTime(value: Long) {
* @param onComplete Callback function to handle the retrieved [Long] value.
* @param onError Callback function to handle errors.
*/
fun Context.getImapScanTime(onComplete: (Long) -> Unit, onError: (String) -> Unit) {
MainScope().async {
try {
val date = dataStore.data.map { pref ->
pref[longPreferencesKey(key.name)] ?: 0L
}
date.distinctUntilChanged().collect { value ->
onComplete(value)
}
fun Context.getImapScanTime(): Deferred<Long> {
return MainScope().async {
try{
//dataStore.data.first()[longPreferencesKey(key.name)] ?: 0L
15

}catch(ex: Exception){
onError(ex.message ?: "Error in getting Imap sca time.")
Timberland.d(ex.message ?: "Error in getting Imap scan time.")
15
}
}
}
Expand All @@ -47,7 +49,7 @@ fun Context.getImapScanTime(onComplete: (Long) -> Unit, onError: (String) -> Uni
*/
fun Context.deleteImapScanTime(){
MainScope().async {
dataStore.edit { pref -> pref.clear() }
dataStore.edit { pref -> pref[longPreferencesKey(key.name)] = 0L }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ class CaptureReceiptPlugin : Plugin() {
*/
@PluginMethod
fun initialize(call: PluginCall) {
val request = ReqInitialize(call)
captureReceipt.initialize(context, request.licenseKey, request.productKey, {
call.resolve()
}, { error -> call.reject(error) })
try{
val request = ReqInitialize(call)
captureReceipt.initialize(context, request.licenseKey, request.productKey, {
call.resolve()
}, { error -> call.reject(error) })
}catch (error: Error) {
call.reject(error.message);
}
}

/**
Expand Down Expand Up @@ -167,12 +171,10 @@ class CaptureReceiptPlugin : Plugin() {
* @param scan The scanned results.
*/
private fun onReceipt(requestId: String, scan: ScanResults? = null) {
val data = if (scan != null) {
RspReceipt(requestId, scan).toJS()
} else {
JSObject()
if (scan != null) {
val data = RspReceipt(requestId, scan).toJS()
notifyListeners("onCapturePluginResult", data)
}
notifyListeners("onCapturePluginResult", data)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import com.getcapacitor.PluginCall
* @param call A [PluginCall] containing the initialization data.
*/
class ReqInitialize(call: PluginCall) : Req(call) {
val licenseKey: String
val productKey: String
val licenseKey: String

/**
* Initializes the [ReqInitialize] object by extracting the licenseKey and productKey
Expand All @@ -29,17 +29,9 @@ class ReqInitialize(call: PluginCall) : Req(call) {
* @param data A [JSObject] containing the initialization data.
*/
init {
val licenseKey = call.getString("licenseKey")
val productKey = call.getString("productKey")
if (licenseKey == null) {
call.reject("Provide a License Key for initialization.")
throw Error("Provide a License Key for initialization.")
}
if (productKey == null) {
call.reject("Provide a Product Intelligence Key for initialization.")
throw Error("Provide a Product Intelligence Key for initialization.")
}
this.productKey = productKey
this.licenseKey = licenseKey
productKey = call.getString("productKey")
?: throw Error("Provide a Product Intelligence Key for initialization.")
licenseKey = call.getString("android") ?:
throw Error("Provide an Android License Key for initialization.")
}
}
Loading
Loading