Skip to content

Commit 1beef41

Browse files
vanshgjumaallan
andauthored
Prepare for 10.0.0-beta05 release (#174)
* Remove Polling (#169) * Add Async Enhanced KYC endpoint (#168) * Add Async Enhanced KYC endpoint * Fix URL * Setting up loading button (#170) * setting up loading button * fixed pr comments * loading button tests * Include ID Info in Document Verification network request (#173) * Include ID Info in network request * Update CHANGELOG * Fix test * Update CHANGELOG --------- Co-authored-by: Juma Allan <allanjuma@gmail.com>
1 parent 0f5eaf4 commit 1beef41

File tree

34 files changed

+1966
-444
lines changed

34 files changed

+1966
-444
lines changed

.github/workflows/release_sdk.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,6 @@ jobs:
131131
132132
git config --local user.email "${{ github.actor }}@users.noreply.github.com"
133133
git config --local user.name "${{ github.actor }}"
134-
git add VERSION
134+
git add ./lib/VERSION
135135
git commit -m "Prepare for next development iteration"
136136
git push

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
# Changelog
22

3-
## 10.0.0-beta05 (unreleased)
3+
## 10.0.0-beta05
44

55
### Added
6+
- Add helper functions which return a Flow of the latest JobStatus for a Job until it is complete
7+
- Add a `JobStatusResponse` interface
8+
- Enhanced KYC Async API endpoint
69

710
### Fixed
11+
- Fixed a bug where `id_info` was not included in Document Verification network requests
812

913
### Changed
1014
- Kotlin 1.9
15+
- Updated API key exception error message to be actionable
16+
- `SmileID.useSandbox` getter is now publicly accessible
17+
- Bump Sentry to 6.25.2
1118

1219
### Removed
20+
- Removed polling from SmartSelfie Authentication, Document Verification, and Biometric KYC. The
21+
returned `SmileIDResult`s will now contain only the immediate result of job status without waiting
22+
for job completion
1323

1424
## 10.0.0-beta04
1525

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Smile ID Android SDK
22

3+
<p align="center">
4+
<img width="200" height="200" style="border-radius:15%" src="sample/listing/ic_launcher-playstore.png" />
5+
</p>
6+
37
[![Build](https://github.com/smileidentity/android/actions/workflows/build.yaml/badge.svg)](https://github.com/smileidentity/android/actions/workflows/build.yaml)
48
[![Maven Central](https://img.shields.io/maven-central/v/com.smileidentity/android-sdk)](https://mvnrepository.com/artifact/com.smileidentity/android-sdk)
59
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/com.smileidentity/android-sdk?server=https%3A%2F%2Foss.sonatype.org)](https://oss.sonatype.org/content/repositories/snapshots/com/smileidentity/android-sdk/)
@@ -12,18 +16,17 @@ If you haven’t already,
1216
[sign up](https://www.usesmileid.com/schedule-a-demo/) for a free Smile ID account, which comes
1317
with Sandbox access.
1418

15-
Please see [CHANGELOG.md](CHANGELOG.md) or
16-
[Releases](https://github.com/smileidentity/android/releases) for the most recent version and
19+
Please see [CHANGELOG.md](CHANGELOG.md) or
20+
[Releases](https://github.com/smileidentity/android/releases) for the most recent version and
1721
release notes
1822

19-
2023
## Getting Started
2124

2225
Full documentation is available at https://docs.usesmileid.com/integration-options/mobile
2326

2427
Javadocs are available at https://javadoc.io/doc/com.smileidentity/android-sdk/latest/index.html
2528

26-
The [sample app](sample/src/main/java/com/smileidentity/sample/compose/MainScreen.kt) included in
29+
The [sample app](sample/src/main/java/com/smileidentity/sample/compose/MainScreen.kt) included in
2730
this repo is a good reference implementation
2831

2932
#### 0. Requirements
@@ -59,9 +62,10 @@ All UI functionality is exposed via either Jetpack Compose or Fragments
5962

6063
#### Jetpack Compose
6164

62-
All Composables are available under the `SmileID` object.
65+
All Composables are available under the `SmileID` object.
6366

6467
e.g.
68+
6569
```kotlin
6670
SmileID.SmartSelfieEnrollment()
6771
SmileID.SmartSelfieAuthentication()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.smileidentity.compose.components
2+
3+
import androidx.compose.ui.test.assertIsDisplayed
4+
import androidx.compose.ui.test.assertIsNotDisplayed
5+
import androidx.compose.ui.test.junit4.createComposeRule
6+
import androidx.compose.ui.test.onNodeWithTag
7+
import androidx.compose.ui.test.onNodeWithText
8+
import androidx.compose.ui.test.performClick
9+
import org.junit.Assert
10+
import org.junit.Rule
11+
import org.junit.Test
12+
13+
class LoadingButtonTest {
14+
@get:Rule
15+
val composeTestRule = createComposeRule()
16+
17+
@Test
18+
fun testContinueButtonIsClickable() {
19+
// given
20+
var callbackInvoked = false
21+
val onConfirmButtonClicked = { callbackInvoked = true }
22+
val continueButtonText = "Continue"
23+
24+
// when
25+
composeTestRule.setContent {
26+
LoadingButton(
27+
continueButtonText,
28+
onClick = onConfirmButtonClicked,
29+
)
30+
}
31+
composeTestRule.onNodeWithText(continueButtonText).performClick()
32+
33+
// then
34+
Assert.assertTrue(callbackInvoked)
35+
}
36+
37+
@Test
38+
fun testContinueButtonShowsLoadingStateWhenClicked() {
39+
// given
40+
var callbackInvoked = false
41+
val onConfirmButtonClicked = { callbackInvoked = true }
42+
val continueButtonText = "Continue"
43+
44+
// when
45+
composeTestRule.setContent {
46+
LoadingButton(
47+
continueButtonText,
48+
onClick = onConfirmButtonClicked,
49+
)
50+
}
51+
composeTestRule.onNodeWithText(continueButtonText).performClick()
52+
53+
// then
54+
Assert.assertTrue(callbackInvoked)
55+
composeTestRule.onNodeWithText(continueButtonText).assertIsNotDisplayed()
56+
composeTestRule.onNodeWithTag("circular_loading_indicator").assertIsDisplayed()
57+
}
58+
}

lib/src/main/java/com/smileidentity/SmileID.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ import java.util.concurrent.TimeUnit
2727
@Suppress("unused", "RemoveRedundantQualifierName")
2828
object SmileID {
2929
@JvmStatic
30-
lateinit var api: SmileIDService private set
30+
lateinit var api: SmileIDService internal set
3131
val moshi: Moshi = initMoshi() // Initialized immediately so it can be used to parse Config
3232

3333
lateinit var config: Config
3434
private lateinit var retrofit: Retrofit
3535

3636
// Can't use lateinit on primitives, this default will be overwritten as soon as init is called
37-
internal var useSandbox: Boolean = true
37+
var useSandbox: Boolean = true
38+
private set
3839

3940
internal var apiKey: String? = null
4041

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.smileidentity.compose.components
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.size
6+
import androidx.compose.material3.Button
7+
import androidx.compose.material3.CircularProgressIndicator
8+
import androidx.compose.material3.Text
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.Alignment
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.platform.testTag
13+
import androidx.compose.ui.res.colorResource
14+
import androidx.compose.ui.unit.dp
15+
import com.smileidentity.R
16+
import com.smileidentity.compose.preview.SmilePreviews
17+
18+
@Composable
19+
fun LoadingButton(
20+
buttonText: String,
21+
modifier: Modifier = Modifier,
22+
loading: Boolean = false,
23+
onClick: () -> Unit,
24+
) {
25+
Button(
26+
onClick = onClick,
27+
modifier = modifier.fillMaxWidth(),
28+
enabled = !loading,
29+
) {
30+
Box {
31+
if (loading) {
32+
CircularProgressIndicator(
33+
color = colorResource(id = R.color.si_color_accent),
34+
strokeWidth = 2.dp,
35+
modifier = Modifier
36+
.size(15.dp)
37+
.align(Alignment.Center)
38+
.testTag("circular_loading_indicator"),
39+
)
40+
} else {
41+
Text(text = buttonText)
42+
}
43+
}
44+
}
45+
}
46+
47+
@SmilePreviews
48+
@Composable
49+
fun LoadingButtonPreview() {
50+
LoadingButton(
51+
buttonText = "Continue",
52+
onClick = {},
53+
)
54+
}

lib/src/main/java/com/smileidentity/models/Authentication.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import kotlinx.parcelize.Parcelize
3333
@Parcelize
3434
@JsonClass(generateAdapter = true)
3535
data class AuthenticationRequest(
36-
@Json(name = "job_type") val jobType: JobType,
36+
@Json(name = "job_type") val jobType: JobType? = null,
3737
@Json(name = "enrollment") val enrollment: Boolean = jobType == SmartSelfieEnrollment,
3838
@Json(name = "country") val country: String? = null,
3939
@Json(name = "id_type") val idType: String? = null,
@@ -50,6 +50,9 @@ data class AuthenticationRequest(
5050
* [consentInfo] is only populated when a country and ID type are provided in the
5151
* [AuthenticationRequest]. To get information about *all* countries and ID types instead, use
5252
* [com.smileidentity.networking.SmileIDService.getProductsConfig]
53+
*
54+
* [timestamp] is *not* a [java.util.Date] because technically, any arbitrary value could have been
55+
* passed to it. This applies to all other timestamp fields in the SDK.
5356
*/
5457
@Parcelize
5558
@JsonClass(generateAdapter = true)
@@ -58,6 +61,7 @@ data class AuthenticationResponse(
5861
@Json(name = "signature") val signature: String,
5962
@Json(name = "timestamp") val timestamp: String,
6063
@Json(name = "partner_params") val partnerParams: PartnerParams,
64+
@Json(name = "callback_url") val callbackUrl: String? = null,
6165
@Json(name = "consent_info") val consentInfo: ConsentInfo? = null,
6266
) : Parcelable
6367

lib/src/main/java/com/smileidentity/models/EnhancedKyc.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ data class EnhancedKycRequest(
2020
@Json(name = "dob") val dob: String? = null,
2121
@Json(name = "phone_number") val phoneNumber: String? = null,
2222
@Json(name = "bank_code") val bankCode: String? = null,
23+
@Json(name = "callback_url") val callbackUrl: String? = null,
2324
@Json(name = "partner_params") val partnerParams: PartnerParams,
2425
@Json(name = "partner_id") val partnerId: String = SmileID.config.partnerId,
2526
@Json(name = "source_sdk") val sourceSdk: String = "android",
@@ -44,3 +45,7 @@ data class EnhancedKycResponse(
4445
@Json(name = "DOB") val dob: String?,
4546
@Json(name = "Photo") val base64Photo: String?,
4647
) : Parcelable
48+
49+
@Parcelize
50+
@JsonClass(generateAdapter = true)
51+
data class EnhancedKycAsyncResponse(@Json(name = "success") val success: Boolean) : Parcelable

lib/src/main/java/com/smileidentity/models/JobStatus.kt

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,50 @@ data class JobStatusRequest(
2222
@Json(name = "signature") val signature: String = calculateSignature(timestamp),
2323
) : Parcelable
2424

25+
interface JobStatusResponse {
26+
val timestamp: String
27+
val jobComplete: Boolean
28+
val jobSuccess: Boolean
29+
val code: Int
30+
val result: JobResult?
31+
val imageLinks: ImageLinks?
32+
}
33+
2534
@Parcelize
2635
@JsonClass(generateAdapter = true)
27-
data class JobStatusResponse(
28-
@Json(name = "timestamp") val timestamp: String,
29-
@Json(name = "job_complete") val jobComplete: Boolean,
30-
@Json(name = "job_success") val jobSuccess: Boolean,
31-
@Json(name = "code") val code: Int,
32-
@Json(name = "result") val result: JobResult?,
36+
data class SmartSelfieJobStatusResponse(
37+
@Json(name = "timestamp") override val timestamp: String,
38+
@Json(name = "job_complete") override val jobComplete: Boolean,
39+
@Json(name = "job_success") override val jobSuccess: Boolean,
40+
@Json(name = "code") override val code: Int,
41+
@Json(name = "result") override val result: JobResult?,
3342
@Json(name = "history") val history: List<JobResult.Entry>?,
34-
@Json(name = "image_links") val imageLinks: ImageLinks?,
35-
) : Parcelable
43+
@Json(name = "image_links") override val imageLinks: ImageLinks?,
44+
) : JobStatusResponse, Parcelable
3645

3746
@Parcelize
3847
@JsonClass(generateAdapter = true)
3948
data class DocVJobStatusResponse(
40-
@Json(name = "timestamp") val timestamp: String,
41-
@Json(name = "job_complete") val jobComplete: Boolean,
42-
@Json(name = "job_success") val jobSuccess: Boolean,
43-
@Json(name = "code") val code: Int,
44-
@Json(name = "result") val result: JobResult?,
49+
@Json(name = "timestamp") override val timestamp: String,
50+
@Json(name = "job_complete") override val jobComplete: Boolean,
51+
@Json(name = "job_success") override val jobSuccess: Boolean,
52+
@Json(name = "code") override val code: Int,
53+
@Json(name = "result") override val result: JobResult?,
4554
@Json(name = "history") val history: List<JobResult.DocVEntry>?,
46-
@Json(name = "image_links") val imageLinks: ImageLinks?,
47-
) : Parcelable
55+
@Json(name = "image_links") override val imageLinks: ImageLinks?,
56+
) : JobStatusResponse, Parcelable
4857

4958
@Parcelize
5059
@JsonClass(generateAdapter = true)
5160
data class BiometricKycJobStatusResponse(
52-
@Json(name = "timestamp") val timestamp: String,
53-
@Json(name = "job_complete") val jobComplete: Boolean,
54-
@Json(name = "job_success") val jobSuccess: Boolean,
55-
@Json(name = "code") val code: Int,
56-
@Json(name = "result") val result: JobResult?,
61+
@Json(name = "timestamp") override val timestamp: String,
62+
@Json(name = "job_complete") override val jobComplete: Boolean,
63+
@Json(name = "job_success") override val jobSuccess: Boolean,
64+
@Json(name = "code") override val code: Int,
65+
@Json(name = "result") override val result: JobResult?,
5766
@Json(name = "history") val history: List<JobResult.BiometricKycEntry>?,
58-
@Json(name = "image_links") val imageLinks: ImageLinks?,
59-
) : Parcelable
67+
@Json(name = "image_links") override val imageLinks: ImageLinks?,
68+
) : JobStatusResponse, Parcelable
6069

6170
/**
6271
* The job result might sometimes be a freeform text field instead of an object (i.e. when the

lib/src/main/java/com/smileidentity/models/Models.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
package com.smileidentity.models
44

55
import android.os.Parcelable
6+
import com.smileidentity.util.randomJobId
7+
import com.smileidentity.util.randomUserId
68
import com.squareup.moshi.Json
79
import com.squareup.moshi.JsonClass
810
import kotlinx.parcelize.Parcelize
9-
import java.util.UUID
1011

1112
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
1213
@Parcelize
@@ -30,8 +31,8 @@ class SmileIDException(val details: Details) : Exception(details.message), Parce
3031
@Parcelize
3132
data class PartnerParams(
3233
val jobType: JobType? = null,
33-
val jobId: String = UUID.randomUUID().toString(),
34-
val userId: String = UUID.randomUUID().toString(),
34+
val jobId: String = randomJobId(),
35+
val userId: String = randomUserId(),
3536
val extras: Map<String, String> = mapOf(),
3637
) : Parcelable
3738

lib/src/main/java/com/smileidentity/networking/NetworkingUtil.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import java.util.zip.ZipEntry
1313
import java.util.zip.ZipOutputStream
1414

1515
fun calculateSignature(timestamp: String): String {
16-
val apiKey = SmileID.apiKey ?: throw IllegalStateException("API key not set")
16+
val apiKey = SmileID.apiKey ?: throw IllegalStateException(
17+
"""API key not set. If using the authToken from smile_config.json, ensure you have set the
18+
|signature/timestamp properties on the request from the values returned by
19+
|SmileID.authenticate.signature/timestamp
20+
""".trimMargin().replace("\n", ""),
21+
)
1722
val hashContent = timestamp + SmileID.config.partnerId + "sid_request"
1823
return hashContent.encode().hmacSha256(apiKey.encode()).base64()
1924
}

0 commit comments

Comments
 (0)