Skip to content

Commit cdcfd00

Browse files
committed
Merge branch 'main' into feat/issuer-signed
2 parents 0161cd5 + cd8b291 commit cdcfd00

90 files changed

Lines changed: 4474 additions & 1049 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ compose-material-icons-core = { module = "org.jetbrains.compose.material:materia
269269
easyqrscan = { module = "io.github.kalinjul.easyqrscan:scanner", version.ref = "easyqrscan" }
270270
compose-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "compose-navigation3" }
271271
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
272+
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
272273
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
273274
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" }
274275
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" }

waltid-applications/waltid-wallet-demo-compose/androidApp/src/androidTest/java/id/walt/walletdemo/compose/android/PublicDemoBackendE2ETest.kt

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.launchAndUnlock
1616
import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.latestStatus
1717
import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.sendDeepLink
1818
import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.setTextByTag
19-
import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.waitForResource
20-
import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.waitForResourceEnabled
2119
import id.walt.walletdemo.compose.android.WalletComposeE2EHelper.waitForStatus
2220
import kotlinx.coroutines.runBlocking
2321
import org.junit.Assert.assertTrue
@@ -51,15 +49,16 @@ class PublicDemoBackendE2ETest {
5149

5250
clickByTag(device, "wallet.receiveButton")
5351
assertTrue(
54-
"Transaction-code input did not appear in offer review. Latest status: ${latestStatus(device)}",
55-
waitForResource(device, "wallet.txCodeInput", CREDENTIAL_OPERATION_TIMEOUT) != null,
52+
"Offer preview did not appear. Latest status: ${latestStatus(device)}",
53+
waitForStatus(
54+
device = device,
55+
timeoutMs = CREDENTIAL_OPERATION_TIMEOUT,
56+
matcher = { it.startsWith("Review credential offer") },
57+
failurePrefixes = listOf("Receive failed", "Bootstrap failed"),
58+
),
5659
)
5760

5861
setTextByTag(device, "wallet.txCodeInput", incorrectCodeFor(transactionCode))
59-
assertTrue(
60-
"Accept button did not enable after entering a transaction code",
61-
waitForResourceEnabled(device, "wallet.offerAcceptButton", UI_ELEMENT_TIMEOUT),
62-
)
6362
clickByTag(device, "wallet.offerAcceptButton")
6463
assertTrue(
6564
"Incorrect transaction code was not rejected. Latest status: ${latestStatus(device)}",
@@ -73,10 +72,6 @@ class PublicDemoBackendE2ETest {
7372

7473
// The reviewed offer remains active so the corrected code can be retried directly.
7574
setTextByTag(device, "wallet.txCodeInput", transactionCode)
76-
assertTrue(
77-
"Accept button did not re-enable after correcting the transaction code",
78-
waitForResourceEnabled(device, "wallet.offerAcceptButton", UI_ELEMENT_TIMEOUT),
79-
)
8075
clickByTag(device, "wallet.offerAcceptButton")
8176
assertTrue(
8277
"Receive did not succeed after correcting the transaction code. Latest status: ${latestStatus(device)}",

waltid-applications/waltid-wallet-demo-compose/androidApp/src/androidTest/java/id/walt/walletdemo/compose/android/WalletComposeE2EHelper.kt

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,28 @@ internal object WalletComposeE2EHelper {
130130
}
131131

132132
fun clickByTag(device: UiDevice, tag: String) {
133-
val node = waitForResource(device, tag, CLICK_VISIBLE_TIMEOUT)
134-
?: findVisibleResource(device, tag)
133+
val initialNode = waitForVisibleResource(device, tag, CLICK_VISIBLE_TIMEOUT)
135134
?: findResourceAfterScrolling(device, tag)
136-
if (node == null) {
135+
if (initialNode == null) {
137136
fail("$tag not found.\n${visibleUiSnapshot(device)}")
137+
return
138+
}
139+
var node: UiObject2 = initialNode
140+
val deadline = System.currentTimeMillis() + CLICK_VISIBLE_TIMEOUT
141+
while (!node.isEnabled && System.currentTimeMillis() < deadline) {
142+
Thread.sleep(200)
143+
node = findVisibleResource(device, tag) ?: node
144+
}
145+
if (!node.isEnabled) {
146+
fail("$tag is disabled.\n${visibleUiSnapshot(device)}")
138147
}
139-
assertTrue("$tag is disabled", node!!.isEnabled)
140148
device.waitForIdle()
141149
node.clickableAncestorOrSelf()?.click() ?: node.click()
142150
device.waitForIdle()
143151
}
144152

145153
fun setTextByTag(device: UiDevice, tag: String, value: String) {
146-
val node = waitForResource(device, tag, CLICK_VISIBLE_TIMEOUT)
147-
?: findVisibleResource(device, tag)
154+
val node = waitForVisibleResource(device, tag, CLICK_VISIBLE_TIMEOUT)
148155
?: findResourceAfterScrolling(device, tag)
149156
if (node == null) {
150157
fail("$tag not found.\n${visibleUiSnapshot(device)}")
@@ -178,21 +185,32 @@ internal object WalletComposeE2EHelper {
178185
fail("$message. Claim row $tag not found.\n${visibleUiSnapshot(device)}")
179186
return
180187
}
181-
val visibleTexts = node.flatten()
182-
.mapNotNull { it.text?.trim()?.takeIf(String::isNotEmpty) }
183-
val missingValues = expectedValues.filter { expected -> expected !in visibleTexts }
184-
if (label !in visibleTexts || missingValues.isNotEmpty()) {
185-
fail(
186-
"""
187-
$message.
188-
claim=$tag
189-
expectedLabel=$label
190-
expectedValues=$expectedValues
191-
visibleTexts=$visibleTexts
192-
${visibleUiSnapshot(device)}
193-
""".trimIndent()
194-
)
188+
var visibleTexts = node.visibleTexts()
189+
fun expectedContentIsVisible(): Boolean =
190+
label in visibleTexts && expectedValues.all { expected -> expected in visibleTexts }
191+
192+
if (expectedContentIsVisible()) return
193+
repeat(6) {
194+
device.scrollDown()
195+
findVisibleResource(device, tag)?.let { visibleTexts = it.visibleTexts() }
196+
if (expectedContentIsVisible()) return
197+
}
198+
repeat(12) {
199+
device.scrollUp()
200+
findVisibleResource(device, tag)?.let { visibleTexts = it.visibleTexts() }
201+
if (expectedContentIsVisible()) return
195202
}
203+
204+
fail(
205+
"""
206+
$message.
207+
claim=$tag
208+
expectedLabel=$label
209+
expectedValues=$expectedValues
210+
visibleTexts=$visibleTexts
211+
${visibleUiSnapshot(device)}
212+
""".trimIndent()
213+
)
196214
}
197215

198216
fun waitForResource(device: UiDevice, tag: String, timeoutMs: Long): UiObject2? {
@@ -206,14 +224,13 @@ internal object WalletComposeE2EHelper {
206224
return null
207225
}
208226

209-
fun waitForResourceEnabled(device: UiDevice, tag: String, timeoutMs: Long): Boolean {
227+
private fun waitForVisibleResource(device: UiDevice, tag: String, timeoutMs: Long): UiObject2? {
210228
val deadline = System.currentTimeMillis() + timeoutMs
211229
while (System.currentTimeMillis() < deadline) {
212-
val node = device.findObject(By.res(tag)) ?: findVisibleResource(device, tag)
213-
if (node?.isEnabled == true) return true
230+
findVisibleResource(device, tag)?.let { return it }
214231
Thread.sleep(200)
215232
}
216-
return false
233+
return null
217234
}
218235

219236
private fun findTextAfterScrolling(device: UiDevice, texts: List<String>): UiObject2? {
@@ -233,30 +250,39 @@ internal object WalletComposeE2EHelper {
233250
device.findObjects(By.pkg("id.walt.walletdemo.compose"))
234251
.flatMap { it.flatten() }
235252
.firstOrNull { node ->
236-
runCatching { node.text?.trim() in texts }.getOrDefault(false)
253+
node.isVisibleOn(device) && runCatching { node.text?.trim() in texts }.getOrDefault(false)
237254
}
238255

239256
private fun findVisibleResource(device: UiDevice, tag: String): UiObject2? =
240257
device.findObjects(By.pkg("id.walt.walletdemo.compose"))
241258
.flatMap { it.flatten() }
242259
.firstOrNull { node ->
243-
runCatching { node.resourceName == tag }.getOrDefault(false)
260+
node.isVisibleOn(device) && runCatching { node.resourceName == tag }.getOrDefault(false)
244261
}
245262

246263
private fun findResourceAfterScrolling(device: UiDevice, tag: String): UiObject2? {
264+
findVisibleResource(device, tag)?.let { return it }
247265
repeat(6) {
248266
device.scrollDown()
249-
waitForResource(device, tag, 1_000L)?.let { return it }
250267
findVisibleResource(device, tag)?.let { return it }
251268
}
252269
repeat(12) {
253270
device.scrollUp()
254-
waitForResource(device, tag, 1_000L)?.let { return it }
255271
findVisibleResource(device, tag)?.let { return it }
256272
}
257273
return null
258274
}
259275

276+
private fun UiObject2.isVisibleOn(device: UiDevice): Boolean = runCatching {
277+
val bounds = visibleBounds
278+
bounds.width() > 0 &&
279+
bounds.height() > 0 &&
280+
bounds.right > 0 &&
281+
bounds.bottom > 0 &&
282+
bounds.left < device.displayWidth &&
283+
bounds.top < device.displayHeight
284+
}.getOrDefault(false)
285+
260286
private fun claimTag(path: String): String =
261287
"wallet.claim.${path.map { if (it.isLetterOrDigit()) it else '_' }.joinToString("")}"
262288

@@ -340,6 +366,9 @@ internal object WalletComposeE2EHelper {
340366
private fun UiObject2.flatten(): List<UiObject2> =
341367
listOf(this) + runCatching { children.flatMap { it.flatten() } }.getOrDefault(emptyList())
342368

369+
private fun UiObject2.visibleTexts(): List<String> =
370+
flatten().mapNotNull { it.text?.trim()?.takeIf(String::isNotEmpty) }
371+
343372
private fun UiObject2.snapshotIdentity(): String =
344373
runCatching {
345374
listOf(

waltid-applications/waltid-wallet-demo-compose/androidApp/src/main/java/id/walt/walletdemo/compose/android/MainActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package id.walt.walletdemo.compose.android
22

33
import android.content.Intent
4+
import android.graphics.Color
45
import android.os.Bundle
56
import androidx.activity.ComponentActivity
7+
import androidx.activity.SystemBarStyle
68
import androidx.activity.compose.setContent
79
import androidx.activity.enableEdgeToEdge
810
import id.walt.walletdemo.compose.logic.DemoWalletConfig
@@ -16,7 +18,10 @@ class MainActivity : ComponentActivity() {
1618

1719
override fun onCreate(savedInstanceState: Bundle?) {
1820
super.onCreate(savedInstanceState)
19-
enableEdgeToEdge()
21+
enableEdgeToEdge(
22+
statusBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
23+
navigationBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
24+
)
2025

2126
val config = DemoWalletConfig(
2227
attestationBaseUrl = BuildConfig.ATTESTATION_BASE_URL,

waltid-applications/waltid-wallet-demo-compose/iosApp/iosAppUITests/PublicDemoBackendE2ETests.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ final class PublicDemoBackendE2ETests: XCTestCase {
190190

191191
ui.replaceText(in: txCodeInput, value: incorrectCode(for: transactionCode))
192192
ui.tapButton(identifier: "wallet.offerAcceptButton", fallbackLabel: "Accept")
193+
try await Task.sleep(for: .seconds(1))
194+
if ui.button(identifier: "wallet.offerAcceptButton", fallbackLabel: "Accept").isEnabled {
195+
// Compose iOS can consume the first activation after auto-dismissing a text input.
196+
ui.tapButton(identifier: "wallet.offerAcceptButton", fallbackLabel: "Accept")
197+
}
193198
let rejectedStatus = ui.waitForStatus(prefixes: ["Receive failed"], timeout: credentialOperationTimeout)
194199
guard rejectedStatus?.starts(with: "Receive failed") == true else {
195200
XCTFail("Incorrect transaction code was not rejected, status: \(rejectedStatus ?? "nil")")
@@ -262,7 +267,7 @@ final class PublicDemoBackendE2ETests: XCTestCase {
262267
)
263268
XCTAssertEqual(previewStatus, "Review presentation request", "Presentation preview did not load, status: \(previewStatus ?? "nil")")
264269

265-
XCTAssertTrue(app.staticTexts["PAYMENT AUTHORIZATION"].waitForExistence(timeout: 10))
270+
XCTAssertTrue(app.staticTexts["Payment Authorization"].waitForExistence(timeout: 10))
266271
XCTAssertTrue(app.staticTexts["42.00"].waitForExistence(timeout: 10))
267272
XCTAssertTrue(app.staticTexts["EUR"].waitForExistence(timeout: 10))
268273
XCTAssertTrue(app.staticTexts["ACME Corp"].waitForExistence(timeout: 10))

waltid-applications/waltid-wallet-demo-compose/iosApp/iosAppUITests/WalletE2EHelpers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ final class WalletE2EUI {
237237
let doneButton = app.toolbars.buttons["Done"]
238238
if doneButton.exists && doneButton.isHittable {
239239
doneButton.tap()
240-
} else if let focusedElement {
240+
} else if let focusedElement, hasKeyboardFocus(in: focusedElement) {
241241
focusedElement.typeText(XCUIKeyboardKey.return.rawValue)
242242
} else {
243243
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)).tap()

waltid-applications/waltid-wallet-demo-compose/sharedLogic/src/androidMain/kotlin/id/walt/walletdemo/compose/logic/AndroidDemoWallet.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package id.walt.walletdemo.compose.logic
22

33
import android.content.Context
4+
import android.os.LocaleList
45
import id.walt.wallet2.mobile.MobileWalletConfig
56
import id.walt.wallet2.mobile.MobileWalletFactory
67

@@ -17,6 +18,9 @@ fun createAndroidDemoWallet(
1718
walletId = config.walletId,
1819
attestationConfig = config.toWalletAttestationConfig(),
1920
transactionDataProfiles = transactionDataProfiles.profiles,
21+
preferredLocales = LocaleList.getDefault().let { locales ->
22+
List(locales.size()) { index -> locales[index].toLanguageTag() }
23+
},
2024
)
2125
),
2226
warning = transactionDataProfiles.warning,

waltid-applications/waltid-wallet-demo-compose/sharedLogic/src/commonMain/kotlin/id/walt/walletdemo/compose/logic/CredentialCardDisplayData.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ data class CredentialCardDisplayData(
55
val title: String,
66
val credentialType: String?,
77
val format: String,
8-
val issuer: String?,
8+
val issuer: String,
99
val holderName: String?,
1010
val validity: String?,
1111
val portrait: DisplayValue.Image?,
@@ -27,7 +27,7 @@ fun CredentialDetails.toCardDisplayData(): CredentialCardDisplayData {
2727
title = summary.label,
2828
credentialType = allItems.firstCredentialTypeText(),
2929
format = summary.format,
30-
issuer = summary.issuer,
30+
issuer = summary.issuer?.takeIf { it.isNotBlank() } ?: CredentialDisplayText.Unknown,
3131
holderName = holderName,
3232
validity = expiryDate?.let { CredentialDisplayText.expires(it) }
3333
?: fallbackAddedDate?.let { CredentialDisplayText.added(it) },

waltid-applications/waltid-wallet-demo-compose/sharedLogic/src/commonMain/kotlin/id/walt/walletdemo/compose/logic/CredentialDisplayMapping.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,6 @@ package id.walt.walletdemo.compose.logic
33
fun CredentialSummary.toCredentialDetails(): CredentialDetails =
44
CredentialDisplayNormalizer.toDetails(this)
55

6-
fun WalletDemoPresentationPreview.toVerifierDetails(): VerifierDetails =
7-
VerifierDetails(
8-
name = verifierName,
9-
clientId = clientId,
10-
responseUri = responseUri,
11-
state = state,
12-
nonce = nonce,
13-
transactionData = transactionData,
14-
)
15-
166
fun WalletDemoPresentationCredentialOption.toCredentialDetails(): CredentialDetails {
177
val summary = CredentialSummary(
188
id = selection.id,

waltid-applications/waltid-wallet-demo-compose/sharedLogic/src/commonMain/kotlin/id/walt/walletdemo/compose/logic/CredentialDisplayModels.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ data class CredentialDetails(
88
data class ClaimGroup(
99
val title: String,
1010
val items: List<ClaimItem>,
11+
val initiallyExpanded: Boolean = true,
1112
)
1213

1314
class ClaimItemPath private constructor(

0 commit comments

Comments
 (0)