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

Commit

Permalink
Merge pull request #391 from tiki/release/ios-support
Browse files Browse the repository at this point in the history
Release/ios support
  • Loading branch information
ricardobrg authored Oct 6, 2023
2 parents 6a67bc1 + c6d4304 commit 4a2de69
Show file tree
Hide file tree
Showing 27 changed files with 320 additions and 170 deletions.
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 ->
this@Email.client(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

0 comments on commit 4a2de69

Please sign in to comment.