From eff4c024a8a02cf7bf5d4a8da3e39151289b2bc7 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Tue, 16 Jan 2024 18:15:29 -0500 Subject: [PATCH] Add new 'wallet' module. Signed-off-by: David Zeuthen --- README.md | 4 + gradle.properties | 2 + gradle/libs.versions.toml | 11 +- settings.gradle | 3 +- wallet/.gitignore | 1 + wallet/build.gradle | 71 +++ wallet/proguard-rules.pro | 21 + .../wallet/ExampleInstrumentedTest.kt | 24 + wallet/src/main/AndroidManifest.xml | 25 + .../wallet/MainActivity.kt | 477 ++++++++++++++++++ .../wallet/WalletApplication.kt | 284 +++++++++++ .../wallet/ui/theme/Color.kt | 11 + .../wallet/ui/theme/Theme.kt | 70 +++ .../wallet/ui/theme/Type.kt | 34 ++ .../res/drawable/ic_launcher_background.xml | 170 +++++++ .../res/drawable/ic_launcher_foreground.xml | 15 + .../main/res/drawable/img_erika_portrait.jpg | Bin 0 -> 11581 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 938 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2464 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 694 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1482 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1206 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3330 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 1772 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5246 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 2438 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7540 bytes wallet/src/main/res/values/colors.xml | 10 + wallet/src/main/res/values/strings.xml | 3 + wallet/src/main/res/values/themes.xml | 5 + .../wallet/ExampleUnitTest.kt | 17 + 33 files changed, 1266 insertions(+), 2 deletions(-) create mode 100644 wallet/.gitignore create mode 100644 wallet/build.gradle create mode 100644 wallet/proguard-rules.pro create mode 100644 wallet/src/androidTest/java/com/android/identity_credential/wallet/ExampleInstrumentedTest.kt create mode 100644 wallet/src/main/AndroidManifest.xml create mode 100644 wallet/src/main/java/com/android/identity_credential/wallet/MainActivity.kt create mode 100644 wallet/src/main/java/com/android/identity_credential/wallet/WalletApplication.kt create mode 100644 wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Color.kt create mode 100644 wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Theme.kt create mode 100644 wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Type.kt create mode 100644 wallet/src/main/res/drawable/ic_launcher_background.xml create mode 100644 wallet/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 wallet/src/main/res/drawable/img_erika_portrait.jpg create mode 100644 wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 wallet/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 wallet/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 wallet/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 wallet/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 wallet/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 wallet/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 wallet/src/main/res/values/colors.xml create mode 100644 wallet/src/main/res/values/strings.xml create mode 100644 wallet/src/main/res/values/themes.xml create mode 100644 wallet/src/test/java/com/android/identity_credential/wallet/ExampleUnitTest.kt diff --git a/README.md b/README.md index 2e480ebf6..a5c99a0de 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,10 @@ reachable on the left side bar of the Android Studio, or by selecting: _View -> Inside the `Build Variants` panel, at the `appholder` row, the desired flavor can be chosen. Once a flavor is selected, by running the app it will install it on the target device/emulator. +The `wallet` module is a rewrite of the `appholder` reference application +with an eye towards a production-quality and easily rebrandable identity +wallet application. + ## ISO 18013-7 Reader Website The `wwwverifier` module contains the source code for a website acting as an diff --git a/gradle.properties b/gradle.properties index c0c0b2d9d..b7cadebaf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official +# Workaround until we can upgrade to a newer Android Gradle Plugin +android.suppressUnsupportedCompileSdk=34 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c6b26aa32..195c9c6bb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] compile-sdk = "34" min-sdk = "26" - kotlin = "1.8.20" + kotlin = "1.9.0" gradle-plugin = "7.4.0" core-ktx = "1.12.0" activity-compose = "1.8.1" @@ -34,6 +34,9 @@ espresso-core = "3.5.1" junit-jupiter = "5.10.0" truth = "1.1.5" + junit = "4.13.2" + lifecycle-runtime-ktx = "2.6.2" + navigation-compose = "2.7.6" [libraries] androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } @@ -85,6 +88,12 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } kotlinx-coroutine-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines-version" } truth = { module = "com.google.truth:truth", version.ref = "truth" } + junit = { group = "junit", name = "junit", version.ref = "junit" } + lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } + ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } + ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } + ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } + androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose" } [bundles] androidx-core = ["androidx-core-ktx", "androidx-appcompat", "androidx-material", "androidx-contraint-layout", "androidx-fragment-ktx", "androidx-legacy-v4", "androidx-preference-ktx", "androidx-work"] diff --git a/settings.gradle b/settings.gradle index b2713e15a..c4724ca97 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,5 +24,6 @@ dependencyResolutionManagement { } } } -include ':appholder', ':appverifier', ':identity', ':identity-android', ':secure-area-test-app', ':wwwverifier' +include ':appholder', ':appverifier', ':identity', ':identity-android', ':secure-area-test-app', ':wwwverifier', ':wallet' rootProject.name = 'Identity Credential' + diff --git a/wallet/.gitignore b/wallet/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/wallet/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/wallet/build.gradle b/wallet/build.gradle new file mode 100644 index 000000000..3150b0864 --- /dev/null +++ b/wallet/build.gradle @@ -0,0 +1,71 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace 'com.android.identity_credential.wallet' + compileSdk 34 + + defaultConfig { + applicationId "com.android.identity_credential.wallet" + minSdk 27 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.5.1' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + implementation project(':identity') + implementation project(':identity-android') + + implementation libs.cbor + implementation libs.androidx.core.ktx + implementation libs.lifecycle.runtime.ktx + implementation libs.androidx.activity.compose + implementation platform(libs.compose.bom) + implementation libs.compose.ui + implementation libs.ui.graphics + implementation libs.compose.ui.tooling + implementation libs.compose.material + implementation libs.bundles.bouncy.castle + implementation libs.androidx.navigation.compose + testImplementation libs.junit + androidTestImplementation libs.androidx.test.ext.junit + androidTestImplementation libs.androidx.test.espresso + androidTestImplementation platform(libs.compose.bom) + androidTestImplementation libs.ui.test.junit4 + debugImplementation libs.compose.icons + debugImplementation libs.ui.test.manifest +} \ No newline at end of file diff --git a/wallet/proguard-rules.pro b/wallet/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/wallet/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/wallet/src/androidTest/java/com/android/identity_credential/wallet/ExampleInstrumentedTest.kt b/wallet/src/androidTest/java/com/android/identity_credential/wallet/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..47b0b674d --- /dev/null +++ b/wallet/src/androidTest/java/com/android/identity_credential/wallet/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.android.identity_credential.wallet + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.android.identity_credential.wallet", appContext.packageName) + } +} \ No newline at end of file diff --git a/wallet/src/main/AndroidManifest.xml b/wallet/src/main/AndroidManifest.xml new file mode 100644 index 000000000..1876fb266 --- /dev/null +++ b/wallet/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wallet/src/main/java/com/android/identity_credential/wallet/MainActivity.kt b/wallet/src/main/java/com/android/identity_credential/wallet/MainActivity.kt new file mode 100644 index 000000000..8021cc4d6 --- /dev/null +++ b/wallet/src/main/java/com/android/identity_credential/wallet/MainActivity.kt @@ -0,0 +1,477 @@ +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) + +package com.android.identity_credential.wallet + +import android.graphics.BitmapFactory +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.Button +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Divider +import androidx.compose.material3.DrawerState +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberDrawerState +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.android.identity.util.Logger +import com.android.identity_credential.wallet.ui.theme.IdentityCredentialTheme +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class MainActivity : ComponentActivity() { + + companion object { + private const val TAG = "MainActivity" + } + + private lateinit var application: WalletApplication + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + application = getApplication() as WalletApplication + + setContent { + IdentityCredentialTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val navController = rememberNavController() + NavHost(navController = navController, startDestination = "MainScreen") { + composable("MainScreen") { + MainScreen(navController) + } + composable("AboutScreen") { + AboutScreen(navController) + } + composable("AddToWalletScreen") { + AddToWalletScreen(navController) + } + composable("CredentialInfo/{credentialId}") { backStackEntry -> + CredentialInfoScreen(navController, + backStackEntry.arguments?.getString("credentialId")!!) + } + } + } + } + } + } + + + @Composable + fun MainScreen(navigation: NavHostController) { + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + val scope = rememberCoroutineScope() + ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { + ModalDrawerSheet { + Text("Wallet", modifier = Modifier.padding(16.dp)) + Divider() + NavigationDrawerItem( + icon = { Icon(imageVector = Icons.Filled.Add, contentDescription = null) }, + label = { Text(text = "Add to Wallet") }, + selected = false, + onClick = { + scope.launch { + drawerState.close() + navigation.navigate("AddToWalletScreen") + } + } + ) + NavigationDrawerItem( + icon = { Icon(imageVector = Icons.Filled.Info, contentDescription = null) }, + label = { Text(text = "About Wallet") }, + selected = false, + onClick = { + scope.launch { + drawerState.close() + navigation.navigate("AboutScreen") + } + } + ) + } + }, + ) { + MainScreenContent(navigation, scope, drawerState) + } + } + + @Composable + fun MainScreenContent(navigation: NavHostController, + scope: CoroutineScope, + drawerState: DrawerState) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + Scaffold( + topBar = { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text( + "Wallet", + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + onClick = { + scope.launch { + drawerState.apply { + Logger.d(TAG, "isClosed = $isClosed") + if (isClosed) open() else close() + } + } + } + ) { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = "Localized description" + ) + } + }, + scrollBehavior = scrollBehavior, + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxHeight() + .padding(innerPadding), + verticalArrangement = Arrangement.Center, + ) { + if (application.credentialStore.listCredentials().size == 0) { + MainScreenNoCredentialsAvailable(navigation) + } else { + MainScreenCredentialPager(navigation) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Text( + modifier = Modifier.padding(8.dp), + text = "Hold to Reader" + ) + } + } + } + } + } + + @Composable + fun MainScreenNoCredentialsAvailable(navigation: NavHostController) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Text( + modifier = Modifier.padding(8.dp), + text = "No credentials in wallet, start by\n" + + "adding credentials.", + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Button(onClick = { + navigation.navigate("AddToWalletScreen") + }) { + Text("Add to Wallet") + } + } + } + + @Composable + fun MainScreenCredentialPager(navigation: NavHostController) { + + Column() { + + val credentialIds = application.credentialStore.listCredentials() + val pagerState = rememberPagerState(pageCount = { + credentialIds.size + }) + HorizontalPager( + state = pagerState, + modifier = Modifier.height(200.dp) + ) { page -> + + val credentialId = credentialIds[page] + val credential = application.credentialStore.lookupCredential(credentialId)!! + val encodedArtwork = credential.applicationData.getData("artwork") + val options = BitmapFactory.Options() + options.inMutable = true + val credentialBitmap = + BitmapFactory.decodeByteArray(encodedArtwork, 0, encodedArtwork.size, options) + val credentialName = credential.applicationData.getString("displayName") + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Image( + bitmap = credentialBitmap.asImageBitmap(), + contentDescription = "Artwork for $credentialName", + modifier = Modifier.clickable(onClick = { + navigation.navigate("CredentialInfo/$credentialId") + }) + ) + } + } + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .height(30.dp) + .padding(8.dp), + ) { + repeat(pagerState.pageCount) { iteration -> + val color = + if (pagerState.currentPage == iteration) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.secondary + } + Box( + modifier = Modifier + .padding(2.dp) + .clip(CircleShape) + .background(color) + .size(8.dp) + ) + } + } + } + } + + @Composable + fun AboutScreen(navigation: NavHostController) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + Scaffold( + topBar = { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text( + "About Wallet", + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + onClick = { + navigation.popBackStack() + } + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back Arrow" + ) + } + }, + scrollBehavior = scrollBehavior, + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxHeight() + .padding(innerPadding), + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Text( + modifier = Modifier.padding(8.dp), + text = "TODO: About Screen" + ) + } + } + } + } + + @Composable + fun AddToWalletScreen(navigation: NavHostController) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + Scaffold( + topBar = { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text( + "Add to Wallet", + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + onClick = { + navigation.popBackStack() + } + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back Arrow" + ) + } + }, + scrollBehavior = scrollBehavior, + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxHeight() + .padding(innerPadding), + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Button(onClick = { + if (application.addSelfsignedMdl()) { + navigation.popBackStack() + } else { + Toast.makeText(applicationContext, + "Already have two self-signed mDLs, not adding more", + Toast.LENGTH_SHORT).show() + } + }) { + Text("Add self-signed mDL") + } + } + } + } + } + + @Composable + fun CredentialInfoScreen(navigation: NavHostController, + credentialId: String) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + Scaffold( + topBar = { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text( + "Credential Information", + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + onClick = { + navigation.popBackStack() + } + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back Arrow" + ) + } + }, + scrollBehavior = scrollBehavior, + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxHeight() + .padding(innerPadding), + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Text("TODO: show info for $credentialId") + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + Button(onClick = { + application.credentialStore.deleteCredential(credentialId) + navigation.popBackStack() + }) { + Text("Delete") + } + } + + } + } + } + +} diff --git a/wallet/src/main/java/com/android/identity_credential/wallet/WalletApplication.kt b/wallet/src/main/java/com/android/identity_credential/wallet/WalletApplication.kt new file mode 100644 index 000000000..b2fabef78 --- /dev/null +++ b/wallet/src/main/java/com/android/identity_credential/wallet/WalletApplication.kt @@ -0,0 +1,284 @@ +package com.android.identity_credential.wallet + +import android.app.Application +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RadialGradient +import android.graphics.Rect +import android.graphics.Shader +import android.widget.Toast +import com.android.identity.android.securearea.AndroidKeystoreSecureArea +import com.android.identity.android.storage.AndroidStorageEngine +import com.android.identity.credential.CredentialStore +import com.android.identity.credential.NameSpacedData +import com.android.identity.internal.Util +import com.android.identity.mdoc.mso.MobileSecurityObjectGenerator +import com.android.identity.mdoc.mso.StaticAuthDataGenerator +import com.android.identity.mdoc.util.MdocUtil +import com.android.identity.securearea.SecureArea +import com.android.identity.securearea.SecureAreaRepository +import com.android.identity.util.Logger +import com.android.identity.util.Timestamp +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.operator.ContentSigner +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import java.io.ByteArrayOutputStream +import java.io.File +import java.math.BigInteger +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.SecureRandom +import java.security.Security +import java.security.cert.X509Certificate +import java.security.spec.ECGenParameterSpec +import java.util.Date +import java.util.Random +import kotlin.math.ceil + +class WalletApplication : Application() { + companion object { + private const val TAG = "WalletApplication" + + val MDL_DOCTYPE = "org.iso.18013.5.1.mDL" + val MDL_NAMESPACE = "org.iso.18013.5.1" + val AAMVA_NAMESPACE = "org.iso.18013.5.1.aamva" + } + + lateinit var secureAreaRepository: SecureAreaRepository + lateinit var credentialStore: CredentialStore + + private lateinit var androidKeystoreSecureArea: AndroidKeystoreSecureArea + + override fun onCreate() { + super.onCreate() + Logger.d(TAG, "onCreate") + + // This is needed to prefer BouncyCastle bundled with the app instead of the Conscrypt + // based implementation included in the OS itself. + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) + Security.addProvider(BouncyCastleProvider()) + + // Setup singletons + val storageDir = File(applicationContext.noBackupFilesDir, "identity") + val storageEngine = AndroidStorageEngine.Builder(applicationContext, storageDir).build() + secureAreaRepository = SecureAreaRepository() + androidKeystoreSecureArea = AndroidKeystoreSecureArea(applicationContext, storageEngine) + secureAreaRepository.addImplementation(androidKeystoreSecureArea); + credentialStore = CredentialStore(storageEngine, secureAreaRepository) + } + + private fun createArtwork(color1: Int, + color2: Int, + artworkText: String): ByteArray { + val width = 800 + val height = ceil(width.toFloat() * 2.125 / 3.375).toInt() + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val bgPaint = Paint() + bgPaint.setShader( + RadialGradient( + width / 2f, height / 2f, + height / 0.5f, color1, color2, Shader.TileMode.MIRROR + ) + ) + val round = bitmap.width / 25f + canvas.drawRoundRect( + 0f, + 0f, + bitmap.width.toFloat(), + bitmap.height.toFloat(), + round, + round, + bgPaint + ) + + val paint = Paint(Paint.ANTI_ALIAS_FLAG) + paint.setColor(android.graphics.Color.WHITE) + paint.textSize = bitmap.width / 10.0f + paint.setShadowLayer(2.0f, 1.0f, 1.0f, android.graphics.Color.BLACK) + val bounds = Rect() + paint.getTextBounds(artworkText, 0, artworkText.length, bounds) + val textPadding = bitmap.width/25f + val x: Float = textPadding + val y: Float = bitmap.height - bounds.height() - textPadding + paint.textSize/2 + canvas.drawText(artworkText, x, y, paint) + + val baos = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos) + return baos.toByteArray() + } + + // Returns true if the mDL was added, false otherwise + fun addSelfsignedMdl(): Boolean { + if (credentialStore.lookupCredential("mDL_Erika") == null) { + provisionCredential( + "mDL_Erika", + "Erika's Driving License", + android.graphics.Color.rgb(64, 255, 64), + android.graphics.Color.rgb(0, 96, 0), + "E MUS", + "Erika", + "Mustermann", + R.drawable.img_erika_portrait + ) + return true + } + if (credentialStore.lookupCredential("mDL_Max") == null) { + provisionCredential( + "mDL_Max", + "Max's Driving License", + android.graphics.Color.rgb(64, 64, 255), + android.graphics.Color.rgb(0, 0, 96), + "M EXA", + "Max", + "Example-Person", + R.drawable.img_erika_portrait + ) + return true + } + return false + } + + private fun provisionCredential( + credentialId: String, + displayName: String, + color1: Int, + color2: Int, + artworkText: String, + givenName: String, + familyName: String, + portrait_id: Int + ) { + val credential = credentialStore.createCredential(credentialId) + + credential.applicationData.setData("artwork", createArtwork(color1, color2, artworkText)) + credential.applicationData.setString("displayName", displayName) + credential.applicationData.setString("docType", "org.iso.18013.5.1.mDL") + + val baos = ByteArrayOutputStream() + BitmapFactory.decodeResource( + applicationContext.resources, + portrait_id + ).compress(Bitmap.CompressFormat.JPEG, 50, baos) + val portrait: ByteArray = baos.toByteArray() + + val now = Timestamp.now() + val issueDate = now + val expiryDate = Timestamp.ofEpochMilli(issueDate.toEpochMilli() + 5*365*24*3600*1000L) + + val credentialData = NameSpacedData.Builder() + .putEntryString(MDL_NAMESPACE, "given_name", givenName) + .putEntryString(MDL_NAMESPACE, "family_name", familyName) + .putEntryByteString(MDL_NAMESPACE, "portrait", portrait) + .putEntryNumber(MDL_NAMESPACE, "sex", 2) + .putEntry(MDL_NAMESPACE, "issue_date", Util.cborEncodeDateTime(issueDate)) + .putEntry(MDL_NAMESPACE, "expiry_date", Util.cborEncodeDateTime(expiryDate)) + .putEntryString(MDL_NAMESPACE, "document_number", "1234567890") + .putEntryString(MDL_NAMESPACE, "issuing_authority", "State of Utopia") + .putEntryString(AAMVA_NAMESPACE, "DHS_compliance", "F") + .putEntryNumber(AAMVA_NAMESPACE, "EDL_credential", 1) + .putEntryBoolean(MDL_NAMESPACE, "age_over_18", true) + .putEntryBoolean(MDL_NAMESPACE, "age_over_21", true) + .build() + credential.applicationData.setNameSpacedData("credentialData", credentialData) + credential.applicationData.setString("docType", MDL_DOCTYPE) + + // Create AuthKeys and MSOs, make sure they're valid for a long time + val timeSigned = now + val validFrom = now + val validUntil = Timestamp.ofEpochMilli(validFrom.toEpochMilli() + 365*24*3600*1000L) + + // Create three authentication keys and certify them + for (n in 0..2) { + val pendingAuthKey = credential.createPendingAuthenticationKey( + "mdoc", + androidKeystoreSecureArea, + SecureArea.CreateKeySettings("".toByteArray()), + null + ) + + // Generate an MSO and issuer-signed data for this authentication key. + val msoGenerator = MobileSecurityObjectGenerator( + "SHA-256", + MDL_DOCTYPE, + pendingAuthKey.attestation[0].publicKey + ) + msoGenerator.setValidityInfo(timeSigned, validFrom, validUntil, null) + val deterministicRandomProvider = Random(42) + val issuerNameSpaces = MdocUtil.generateIssuerNameSpaces( + credentialData, + deterministicRandomProvider, + 16, + null + ) + for (nameSpaceName in issuerNameSpaces.keys) { + val digests = MdocUtil.calculateDigestsForNameSpace( + nameSpaceName, + issuerNameSpaces, + "SHA-256" + ) + msoGenerator.addDigestIdsForNamespace(nameSpaceName, digests) + } + val issuerKeyPair: KeyPair = generateIssuingAuthorityKeyPair() + val issuerCert = getSelfSignedIssuerAuthorityCertificate(issuerKeyPair) + + val mso = msoGenerator.generate() + val taggedEncodedMso = Util.cborEncode(Util.cborBuildTaggedByteString(mso)) + val issuerCertChain = listOf(issuerCert) + val encodedIssuerAuth = Util.cborEncode( + Util.coseSign1Sign( + issuerKeyPair.private, + "SHA256withECDSA", taggedEncodedMso, + null, + issuerCertChain + ) + ) + + val issuerProvidedAuthenticationData = StaticAuthDataGenerator( + MdocUtil.stripIssuerNameSpaces(issuerNameSpaces, null), + encodedIssuerAuth + ).generate() + + pendingAuthKey.certify(issuerProvidedAuthenticationData, validFrom, validUntil) + } + Logger.d(TAG, "Created credential with name ${credential.name}") + } + + private fun generateIssuingAuthorityKeyPair(): KeyPair { + val kpg = KeyPairGenerator.getInstance("EC") + val ecSpec = ECGenParameterSpec("secp256r1") + kpg.initialize(ecSpec) + return kpg.generateKeyPair() + } + + private fun getSelfSignedIssuerAuthorityCertificate( + issuerAuthorityKeyPair: KeyPair + ): X509Certificate { + val issuer: X500Name = X500Name("CN=State Of Utopia") + val subject: X500Name = X500Name("CN=State Of Utopia Issuing Authority Signing Key") + + // Valid from now to five years from now. + val now = Date() + val kMilliSecsInOneYear = 365L * 24 * 60 * 60 * 1000 + val expirationDate = Date(now.time + 5 * kMilliSecsInOneYear) + val serial = BigInteger("42") + val builder = JcaX509v3CertificateBuilder( + issuer, + serial, + now, + expirationDate, + subject, + issuerAuthorityKeyPair.public + ) + val signer: ContentSigner = JcaContentSignerBuilder("SHA256withECDSA") + .build(issuerAuthorityKeyPair.private) + val certHolder: X509CertificateHolder = builder.build(signer) + return JcaX509CertificateConverter().getCertificate(certHolder) + } +} \ No newline at end of file diff --git a/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Color.kt b/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Color.kt new file mode 100644 index 000000000..b83a4f6c3 --- /dev/null +++ b/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.android.identity_credential.wallet.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Theme.kt b/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Theme.kt new file mode 100644 index 000000000..a61ea6dd6 --- /dev/null +++ b/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package com.android.identity_credential.wallet.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun IdentityCredentialTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Type.kt b/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Type.kt new file mode 100644 index 000000000..00d93dba4 --- /dev/null +++ b/wallet/src/main/java/com/android/identity_credential/wallet/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.android.identity_credential.wallet.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/wallet/src/main/res/drawable/ic_launcher_background.xml b/wallet/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/wallet/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wallet/src/main/res/drawable/ic_launcher_foreground.xml b/wallet/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..0f12c1d4f --- /dev/null +++ b/wallet/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/wallet/src/main/res/drawable/img_erika_portrait.jpg b/wallet/src/main/res/drawable/img_erika_portrait.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31e356ddc4d3d9c5ae56720bef1202af4fe5f841 GIT binary patch literal 11581 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 + + + + \ No newline at end of file diff --git a/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..7353dbd1f --- /dev/null +++ b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/wallet/src/main/res/mipmap-hdpi/ic_launcher.webp b/wallet/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..d75e2c20ac0a8acc422f549d088cb1969d9a559a GIT binary patch literal 938 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 literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-mdpi/ic_launcher.webp b/wallet/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..f5f4dfd11999c9617fc1905d0fdf268a70ba77c9 GIT binary patch literal 694 zcmV;n0!jT+Nk&Gl0ssJ4MM6+kP&iDX0ssInFTe{BFXT9qBuA+`)SEBHU^^aZ&GZz~ z2gi{lIZC15d@%;w@knc?yWB>S>?-~K|M|y+MI6$iu(W2}#;I3tlagr-^guNX z4Nw$~5HX~hxxc>-0Ej>gvLFI6NI*OJ?G#0+LD49R(xpomMd_l{qy|M%uJ0r$NHg#A|CYd*!#@Y5>OU+DhsCn3c?HO1b?l!W^I)=?SI_@B)0OBq>bB_q z!m#*WlXLU&dtZjdrJhtb}G33C|MB{`-Yoa>tAh^+ zo6{;*$hK|Uw4)vG*tS)UZC<;qo3a%y9#{x zBW|pIrChS_AnB5WEVMg4I?$i#fWj`_Qk$gpmZ?_jBz1#FoV2N9lgLL6(e0~eM9EDl%w>nFP^#Y zP6(^b literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..4ed125402fbd49fe8207ae3fa895b6214a441380 GIT binary patch literal 1482 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 UGnG2pDV*erjjbK>gehFvQhN|eSpWb4 literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..504e880cc34349a7b2c2d526e8229bc4f2a01805 GIT binary patch literal 3330 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 MDw{19J@ax104qJE)Bpeg literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..d8f60e8db37a61a69eaf0de8a14c146e14fff1c5 GIT binary patch literal 1772 zcmV* zr(r8TwU{quOZ#U`&hKQcqc}OTZQHiZuY0R<348g|Q{O*F`d6(6aWd7l727t}xPWlk zsaa>Ru@#VP+cxd2@Bd@lNy)Zt+qP}v-gAEXka{{9IJT`Fdw$!tZQB|`>?)Nt0;fvm zl9lrB!_s#8-|zc>&jiS}ZQHb*AGYn-wr$(CZQCxMr*rq!TCtN;v3+j2YCpublPBAD zF)LO%AWD*C+L~+Iwr$(bw*9Vc`?LE$0lbr1{F75&C=36nwtqh0M1hnS+Ic4Ne09J{ zs+oDsFU$JT%bgxLv_qt?G#?FPS!aYJeB$^wIy{RwQut|nVwf%4M<)m@N%*i zrQS*ft)&2kTECE{#9n1ywscZ!DM$zsG894(c{TIe85rjUW& z@&&7J8(gglO?lP#d$y# za;87ivTv(_<(ExBTR25k5HQ2n`9faxEn055R^EJeXp)zV1^D6;o%!6bI2gLf}6C1)Lgsdhx-@53> z(FK5eCn?AKHe`%f1@4FO1c3fN-=onQ6PH#eqW}ncBRZJZ;E|?0i7Wt)6Dj|&$oT7q z6#&aO>54d{s87QSfaetA8Vn3KLj(d~+FvTc)7#fb383s~RyS>DL!%eQ5z8sTUH$bBgICH!vzi1lypYvX zF$B71(hJGhXtn^Bt|$Prx6*h;u#fdoFnF>Cu-yzd>=A;V>E{ij^eM1*R>nZ_CU8dU zK_W&gCoHL>iP0cL@Y~uLXJGB_u4NRyQ4n*?d~Pw*{vXF;6coE++=~I_m&Eu^QtC?4 z>pqIWv5Y$q3r>cf1k)(@3nR%7hvJv{ANnY!5IN){q34Be8C`WqT=hh8=u5{ik->DH zqQ`Zkd7oTcE^Y0<_NiXvF;WvL#V*4LYFLSjZ5X{zRVB#(hVk*4OiKSxu%EkQu<=Gb zAlP<;o@yDJ^-aC$igz~w+VGw$i3R6;2DZJWUPPC$8$?W==cxpak~nIIPk^7KZPII* z<0W2yw06AEZJ0KhESvbaiU@Jec9lKg$JM1TuX9nW+(+GEbie5!)E@ka8 zR~av{95jmE!o5}Jr->rhSNY6E;WYe>dxdUwW()O5<&NC2d}bte!yfIfiI27($Eaeu z{IHbIw8VcNN0d3ab~;9hpd^{k+{E&!?NzeL8$l8g#ke+aa5c|l6iy}lte(pJo#F4p z4?gRl=^ZRc5eP*{py&)jXJ}UA0I-S$gdPBUi_lwOmecR(+(-aMyV0qZCTI@5AY``L z4Zy`zPb0HC1{@h~ZlH2>6Tzcv=MV|nP8iSyH!K=&bc6$?Ep0<$0`(tZLBVTLc;(U} zJm^^9NDjF7pN?yqFjraEzbcS$+juzQFGnGPHys@Rr}d^&1wSz^I|4 z%$R6>(=P>_4OppBkyhXx(!IK1_j}bXZ2kn7rttTwJD9WmGpKFrtvmHgHY+rxO+XbH z?gIXVKzh{Z@vQZm1&9C3&buqG@bVjE^DD0L@{8`xS!*{VhfWd#nPZnLn@Ry$1!n^$ z5CQEa;ENy>K_n@z(xGsqOX)?4tx` OXB)##YzX8Xl>z_{4Q(R; literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..3f36d34cdf7101c5a8d22281a317da095e5ee5fa GIT binary patch literal 5246 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 E0JR8lvH$=8 literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f2dae0dd6cfc860a6d9d7a390daaa7e4ea48c00 GIT binary patch literal 2438 zcmV;133>KXNk&F~2><|BMM6+kP&iC+2><{uzrZgL^@iHEZ5aFix1C<*h?oFu$h6UX zEfUrJIGVPzZHnaP3oM_7na-G*nVA;M?CGkD@V$rsRaefOKRhd@QDxL)_L#!BAlJ&w z%=oXjL!*|)*rA@2fLq(PYGvUL5&RU52%XH`-Ql~tyP)SBXn+=I5;(VQn|j7)8?kNM zw(E3eWjlXBymCgi2$+k^LCz;3CHpjMY+lp=D`6Zv-{|Vp^w~2q+>wk=C4d&!u zmhXqnQKN~%W;Vp;Flr1@$jt1SsS*z@UcR66%#@iqjL1!E(DTgtJ6_v>`rg&{qD`aC zpiS@dhqY-xo!dXmoX-<$W>!DZ+SawjLz)?mP9l{nev%r?M(A%OOLDI zm0+Gs_Ooa_)L;TeN``HzXrQIO*OFtwHzXpr&yxc?Y$Q`ZP@B2{e1|5T!Z*Py5gVm% z+Mtv$kzr5Jkp8@X&po)+&!X`96?__q9;CGDF%>Miz_@9jC^3U-3u)-pl?-b9vw}eFurH zKfLIRGMD=gGJd%Ah)=+61%ekm(dKgBer9`*`vBBqGWYFfwcoHUARs;M&%0s_1_(bE z3P-1(A;>Tc6R{h;9=i-I&ypu|-~|_2hZO+dVtfv`kX#490`S{}Lh#T+v9lB)u-W7eV3RL+Nht7?7QF~E z#Fg>e6;psjXCie3R;12h1-$EyT<&Y`Z3X!E@=x!`j=r!osxn6ki0i#A#m|x_Vpfgt zeSpTlZ6!``>0+|bHK^BvwPLUS+rdDOY#FW;>t`}B6}VMERPXHh$6#Ysy1H}`J?lH9 zU}8U$&>w^0Ms6UnRSvQXFQYX=vugpqj$Y64QfB*o@Xb;G%` zhi4=7f(sx*GvyAXw@FYt$WB(;d2(U7{|ny_lw_%0v9_TKd&!b>{MUoSk~X#daM{Dm zpI`k=n#g^r0i=`@uJ0*zobx$S#~+x5ubgqI5!CjP^8Us92t8jh6K4kM zrOz9I+8w#I?@^qxVfxUV+>Z`2seRP?AYHx1)3GaGCwnghU+ToJfJU*6kXvg>OI{=s8QYBaSw;z&Bk zoCsZNIX^lkqHq*7;V3*|nBhuVzJ_M;EPqAIQ1u))bJ`t2=7#2dn8Wbc{1|}i z=^>K|V77{#_cKA#&lkFW&Ay6+d2g<#PF_b6&5ZVeFA;j|%UGD_eAv|{+}+W~Jiu`I z6~N1@ZmoWp?(fEA_H{HicSP*QsKqV=j9!n>Tg!eE60zgdq19-1cQ5mfKI@E9rZp2S zWV#`*yJsik=&f^n3RkRhf#9A4PftFqnRtRX;y+g}g&w_Rg+~C;G0ScTswp3*Mxq2- z$tmx6Idlgs+A+;>1OW4_8S%2Q_0hBz(n!Wt@$}Qeva@Ey35lyFCCi?77N)*nJ*CV; zDV45I;+})*Wmk+#DF9jmhX5#n?U>?8>Jy$Bl)yA({)7gef4Kz?Ax4D>pb-EJz&qce z23C)p7v)yYF$^Wx=zZa}2=3ad0aoXYnd}Hi04_#kRee^#=>_-5=n_&w%~1lAkd1A% z2|RW=yXc$6F$H#j4aC*`=D;3!KIdK>nM3LW^V^g_%s`}Ves|53Sh;cIMlIwV-(R|Pok863C*H9Tjcdj)(alQ1s^ZDh#e{DivpCoEiMxU=s67?~|E201AoX?M4FWA0wkZP`LW3oVfq2=^z!BXZHT}NT1Ab-r=1K;b8(g<;V8hSLzKRoJDlUn? z3V}JWT?Aaw?Nv75Tix$pJrYno66o_QzAPK?`FGW#ZQ1avIB{fHT#`8oWIzopfDN$Q zun0K&{-O=vp7?jxnxmP-3DXr9^a9?dHQ_8^mi^cFvjeljHJf(y22Pl?xTH63Kl)V@ zkQNGSe7sO7G<|inJ?>4MFm(|ZB>Q?Znb_wuncjU#;6%6)ia6&219Z-baN`vNz>% literal 0 HcmV?d00001 diff --git a/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..8635dc1f696ae8b7897651fb7756bf621d5056ae GIT binary patch literal 7540 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 + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml new file mode 100644 index 000000000..99a792063 --- /dev/null +++ b/wallet/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Wallet + \ No newline at end of file diff --git a/wallet/src/main/res/values/themes.xml b/wallet/src/main/res/values/themes.xml new file mode 100644 index 000000000..c9a1b821b --- /dev/null +++ b/wallet/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +