diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a6c9973..8ef1207 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -14,11 +14,11 @@ jobs: steps: - name: Check out repository code from ${{ github.repository }}/${{ github.ref }} uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '11' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' - name: Set up Node JS uses: actions/setup-node@v4 with: @@ -29,7 +29,7 @@ jobs: dotnet-version: 6.0.x - name: Update permissions working-directory: . - run: chmod +x ./client/android/gradlew ./server/gradlew ./.github/scripts/*.sh + run: chmod +x ./client/android/gradlew ./client/android-light/gradlew ./server/gradlew ./.github/scripts/*.sh - name: Check version client Android working-directory: ./client/android/ run: ../../.github/scripts/check_version.sh @@ -45,11 +45,14 @@ jobs: dotnet test --no-build --verbosity normal - name: Build and Test client Android working-directory: ./client/android/ - run: ./gradlew build test --info --stacktrace + run: ./gradlew clean build test --info --stacktrace + - name: Build and Test client Android Light + working-directory: ./client/android-light/ + run: ./gradlew clean build test --info --stacktrace - name: Build and Test server working-directory: ./server/ run: | cd ./dashboard-app npm install cd .. - ./gradlew build test --info --stacktrace + ./gradlew clean build test --info --stacktrace diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc3361..c481930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Added a new Android client application to demonstrate the use of the **Keyple Distributed JSON API** and without the + need of the `keyple-card-calypso-java-lib` on terminal side. The corresponding code is located in the folder named + `android-light`. +- Added new remote services in the server app to enhance keyple-less clients capabilities. ### Changed - Rename repository from `keyple-java-demo-remote` to `keyple-demo-ticketing-reloading-remote` - Rename Android artifact from `keyple-demo-remote-client-android` to `keyple-demo-ticketing-reloading-client-android-app` diff --git a/client/android-light/.gitignore b/client/android-light/.gitignore new file mode 100644 index 0000000..69d9e61 --- /dev/null +++ b/client/android-light/.gitignore @@ -0,0 +1,92 @@ +# Built application files +*.apk +#*.aar For ".aar" files in "libs" directory +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +/.idea +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +/LICENSE_HEADER +/signing.properties diff --git a/client/android-light/README.md b/client/android-light/README.md new file mode 100644 index 0000000..a28fdb1 --- /dev/null +++ b/client/android-light/README.md @@ -0,0 +1,56 @@ +# Keyple Reload Demo - Android Light Client's repository + +This is the repository for the Android Client of the Keyple Reload Demo application. + +This demo is an open source project provided by the [Calypso Networks Association](https://calypsonet.org) implementing +the [Eclipse Keyple SDK](https://keyple.org) in a typical use case that can serve as a basis for building a ticketing +ecosystem based on contactless cards and/or NFC smartphones. + +The source code and APK are available +at [calypsonet/keyple-demo-ticketing-reloading-remote/releases](https://github.com/calypsonet/keyple-demo-ticketing-reloading-remote/releases) + +This version differs from the other Android demo, where the card selection is handled directly by the client terminal. +In this application, the card selection process is delegated to the server, demonstrating the "keyple-less" capabilities +of the server protocol. As a result, it is no longer necessary to have the Calypso card extension on the client +terminal, making it more lightweight and further simplifying client-side operations. This modification allows the server +to manage the card selection logic, reducing the dependency on local configuration and improving central control over +card interactions. + +The code can be easily adapted to other cards, terminals and business logic. + +It shows how to load contracts into a Calypso card, the whole ticketing process being managed remotely. +Following the contract loading the card can pay presented to a validator running the +[Keyple Demo Validation](https://github.com/calypsonet/keyple-demo-ticketing-validation-app) application and then checked with the [Keyple Demo Control](https://github.com/calypsonet/keyple-demo-ticketing-control-app) application. + +Read the main [README](https://github.com/calypsonet/keyple-demo-ticketing-reloading-remote#readme) to understand the purpose of this application. + +## Screens + +- Main screen (`MainActivity`): Setup Screen. +- Home (`HomeActivity`): Display a menu allowing to choose the Calypso card type to read and load. + - 'Contactless support': works with the native Android NFC reader and is available for any android smartphone. + - 'SIM Card': works with the native Android OMAPI reader and is available for any android smartphone. (Work in + progress) + - 'Embedded Secure Element': works with the Wizway plugin to access to eSE. (Work in progress) +- Settings (`SettingsMenuActivity`): + - Server (ServerSettingsActivity): Settings for server connexion. + - Configuration (ConfigurationSettingsActivity): Activate/Deactivate each plugin availability in the android + smartphone. + - Personalization (PersonalizationActivity): Reset a card (clean contracts). +- Card Reader (`CardReaderActivity`): Launches the flavour associated Keyple plugin. It will try to read Card using + selected card reader. + - Initialize Keyple plugin regarding selected Calypso card type. + - Connect to remote server. + - With remote server connected to a Calypso SAM, proceed to a secured reading of card content. +- Card Summary (`CardSummaryActivity`): displays the card content. + - Card content can be season pass and/or multi-trip ticket. +- Select Tickets (`SelectTicketsActivity`): The remote server will return a list of available products (Season Pass and + Multi-trip ticket) to buy for this card. This list presented in this view. +- Checkout (`CheckoutActivity`): Simulates a payment done with a credit card. +- Payment Validated (`PayementValidatedActivity`): Simulates the payment validation. +- Charge (`ChargeActivity`): Presents the process of loading the product selected. + - Block loading if card has been swapped. + - Initialize Keyple plugin regarding selected Calypso card type. + - Connect to remote server. + - With remote server connected to a SAM, proceed to a card writing. +- Charge Result (`ChargeResultActivity`): Displays if loading was successful or failed. \ No newline at end of file diff --git a/client/android-light/app/build.gradle.kts b/client/android-light/app/build.gradle.kts new file mode 100644 index 0000000..8518f89 --- /dev/null +++ b/client/android-light/app/build.gradle.kts @@ -0,0 +1,206 @@ +import java.util.Properties +/////////////////////////////////////////////////////////////////////////////// +// GRADLE CONFIGURATION +/////////////////////////////////////////////////////////////////////////////// +plugins { + id("com.android.application") + id("kotlin-android") + id("kotlin-parcelize") + kotlin("kapt") + id("com.diffplug.spotless") +} + +/////////////////////////////////////////////////////////////////////////////// +// APP CONFIGURATION +/////////////////////////////////////////////////////////////////////////////// +val kotlinVersion: String by project +val archivesBaseName: String by project +val signingPropertiesFile = File("signing.properties") +android { + /** + * The app's namespace. Used primarily to access app resources. + */ + namespace = "org.calypsonet.keyple.demo.reload.remote" + + /** + * compileSdk specifies the Android API level Gradle should use to + * compile your app. This means your app can use the API features included in + * this API level and lower. + */ + compileSdk = 34 + + /** + * The defaultConfig block encapsulates default settings and entries for all + * build variants and can override some attributes in main/AndroidManifest.xml + * dynamically from the build system. You can configure product flavors to override + * these values for different versions of your app. + */ + signingConfigs { + create("default") { + // If the application has to be signed, the elements necessary for this operation + // must be defined in a 'signing.properties' file placed at the root of the project. + if (signingPropertiesFile.exists()) { + val properties = Properties().apply { + load(signingPropertiesFile.reader()) + } + storeFile = File(properties.getProperty("storeFilePath")) + storePassword = properties.getProperty("storePassword") + keyPassword = properties.getProperty("keyPassword") + keyAlias = properties.getProperty("keyAlias") + } + } + } + + defaultConfig { + applicationId = "org.calypsonet.keyple.demo.reload.remote" + minSdk = 26 + //noinspection ExpiredTargetSdkVersion + targetSdk = 31 + versionName = project.version.toString() + versionCode = versionName!!.replace(".", "").toIntOrNull() ?: 1 + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false // Disables code shrinking for the release build type. + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + if (signingPropertiesFile.exists()) { + signingConfig = signingConfigs.getByName("default") + } + } + } + + val javaSourceLevel: String by project + val javaTargetLevel: String by project + compileOptions { + sourceCompatibility = JavaVersion.toVersion(javaSourceLevel) + targetCompatibility = JavaVersion.toVersion(javaTargetLevel) + } + + kotlinOptions { + jvmTarget = javaTargetLevel + } + + packagingOptions { + exclude("META-INF/NOTICE.md") + exclude("META-INF/plugin_release.kotlin_module") + } + + lintOptions { + isAbortOnError = false + } + + // generate output aar with a qualified name: with version number + applicationVariants.all { + outputs.forEach { output -> + if (output is com.android.build.gradle.internal.api.BaseVariantOutputImpl) { + output.outputFileName = "${archivesBaseName}-${project.version}-${buildType.name}.${output.outputFile.extension}".replace("-SNAPSHOT", "") + } + } + } + + /** + * Build Features Configuration + */ + buildFeatures { + viewBinding = true + } + + sourceSets { + getByName("main").java.srcDirs("src/main/kotlin") + } +} + +dependencies { + // Demo common + implementation("org.calypsonet.keyple:keyple-demo-ticketing-common-lib:2.0.2-SNAPSHOT") { isChanging = true } + + // Begin Keyple configuration (generated by 'https://keyple.org/components/overview/configuration-wizard/') + implementation("org.eclipse.keypop:keypop-reader-java-api:2.0.1") + implementation("org.eclipse.keyple:keyple-common-java-api:2.0.1") + implementation("org.eclipse.keyple:keyple-util-java-lib:2.4.0") + implementation("org.eclipse.keyple:keyple-service-java-lib:3.3.3") + implementation("org.eclipse.keyple:keyple-distributed-network-java-lib:2.5.1") + implementation("org.eclipse.keyple:keyple-distributed-local-java-lib:2.5.1") + implementation("org.eclipse.keyple:keyple-plugin-android-nfc-java-lib:2.2.0") + implementation("org.eclipse.keyple:keyple-plugin-android-omapi-java-lib:2.1.0") + // End Keyple configuration + + // Network + implementation("org.java-websocket:Java-WebSocket:1.5.5") + + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.4.0") + implementation("com.squareup.retrofit2:converter-scalars:2.4.0") + implementation("com.squareup.retrofit2:adapter-rxjava2:2.4.0") + implementation("com.squareup.okhttp3:logging-interceptor:3.9.1") + + // WorkManager + implementation("androidx.work:work-runtime-ktx:2.9.0") + implementation("org.greenrobot:eventbus:3.2.0") // to easily handle server status change + + // Android components + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.10.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.activity:activity-ktx:1.8.1") + implementation("androidx.fragment:fragment-ktx:1.6.2") + + // Log + implementation("org.slf4j:slf4j-api:1.7.32") + implementation("com.jakewharton.timber:timber:5.0.1") + implementation("com.arcao:slf4j-timber:3.1@aar") //SLF4J binding for Timber + + // Kotlin + implementation("androidx.core:core-ktx:1.12.0") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + + implementation("androidx.multidex:multidex:2.0.1") + + // RxJava + implementation("io.reactivex.rxjava2:rxjava:2.1.13") + implementation("io.reactivex.rxjava2:rxandroid:2.0.2") + + // Google GSON + implementation("com.google.code.gson:gson:2.10.1") + + // Devnied - Byte Utils + implementation("com.github.devnied:bit-lib4j:1.4.5") { + exclude(group = "org.slf4j") + } + + // Dagger dependencies + kapt("com.google.dagger:dagger-compiler:2.19") + annotationProcessor("com.google.dagger:dagger-compiler:2.19") + kapt("com.google.dagger:dagger-android-processor:2.19") + annotationProcessor("com.google.dagger:dagger-android-processor:2.19") + implementation("com.google.dagger:dagger:2.19") + implementation("com.google.dagger:dagger-android:2.19") + implementation("com.google.dagger:dagger-android-support:2.19") + compileOnly("org.glassfish:javax.annotation:10.0-b28") + + // Common lang + implementation("org.apache.commons:commons-lang3:3.11") + + // Lottie + implementation("com.airbnb.android:lottie:3.4.4") + + testImplementation("junit:junit:4.13.2") + testImplementation("org.robolectric:robolectric:4.3.1") + androidTestImplementation("com.android.support.test:runner:1.0.2") + androidTestImplementation("androidx.test.ext:junit:1.1.2") + androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0") + + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.0") +} + +apply(plugin = "org.eclipse.keyple") // To do last \ No newline at end of file diff --git a/client/android-light/app/gradle.properties b/client/android-light/app/gradle.properties new file mode 100644 index 0000000..ee5c58d --- /dev/null +++ b/client/android-light/app/gradle.properties @@ -0,0 +1 @@ +# leave this file empty (needed by the keyple-gradle:setVersion task) \ No newline at end of file diff --git a/client/android-light/app/proguard-rules.pro b/client/android-light/app/proguard-rules.pro new file mode 100644 index 0000000..62dff9c --- /dev/null +++ b/client/android-light/app/proguard-rules.pro @@ -0,0 +1,42 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.old. +# +# 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 + +# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and +# EnclosingMethod is required to use InnerClasses. +-keepattributes Signature, InnerClasses, EnclosingMethod + +# Retain service method parameters when optimizing. +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} + +# Ignore annotation used for build tooling. +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement + +# Ignore JSR 305 annotations for embedding nullability information. +-dontwarn javax.annotation.** + +# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. +-dontwarn kotlin.Unit + +# Top-level functions that can only be used by Kotlin. +-dontwarn retrofit2.-KotlinExtensions \ No newline at end of file diff --git a/client/android-light/app/src/androidTest/AndroidManifest.xml b/client/android-light/app/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000..644b5f4 --- /dev/null +++ b/client/android-light/app/src/androidTest/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/client/android-light/app/src/main/AndroidManifest.xml b/client/android-light/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f3d6828 --- /dev/null +++ b/client/android-light/app/src/main/AndroidManifest.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/android-light/app/src/main/assets/anim_card_scan.json b/client/android-light/app/src/main/assets/anim_card_scan.json new file mode 100644 index 0000000..c9aa686 --- /dev/null +++ b/client/android-light/app/src/main/assets/anim_card_scan.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":24,"ip":0,"op":48,"w":110,"h":100,"nm":"cardscan","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"cardtext1 Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55,50,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.919,-0.246],[0,0],[0.246,-0.919],[0.918,0.246],[0,0],[-0.246,0.918]],"o":[[0,0],[0.919,0.246],[-0.246,0.919],[0,0],[-0.919,-0.246],[0.246,-0.919]],"v":[[-4.527,-2.996],[5.418,-0.331],[6.636,1.778],[4.528,2.996],[-5.419,0.331],[-6.636,-1.778]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[83.546,41.126],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cardtext2 Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55,50,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.919,-0.246],[0,0],[0.246,-0.918],[0.919,0.246],[0,0],[-0.246,0.918]],"o":[[0,0],[0.919,0.246],[-0.246,0.919],[0,0],[-0.918,-0.246],[0.246,-0.919]],"v":[[-0.546,-1.929],[1.437,-1.398],[2.655,0.711],[0.546,1.929],[-1.438,1.398],[-2.654,-0.711]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[86.101,47.516],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"stroke2 Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-74.779,"ix":10},"p":{"a":0,"k":[68.25,30.5,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-2.219],[0,0],[-2.215,0],[0,0],[0,2.219],[0,0],[0,0],[2.214,0]],"o":[[-2.215,0],[0,0],[0,2.219],[0,0],[2.214,0],[0,0],[0,0],[0,-2.219],[0,0]],"v":[[-21.365,-37.01],[-25.383,-32.982],[-25.383,32.982],[-21.365,37.01],[21.367,37.01],[25.383,32.982],[25.383,-22.103],[25.383,-32.982],[21.367,-37.01]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0.313],[0,0],[-0.313,0],[0,0],[0,-0.313],[0,0],[0,0],[0.312,0],[0,0]],"o":[[0,0],[0,-0.313],[0,0],[0.312,0],[0,0],[0,0],[0,0.313],[0,0],[-0.313,0]],"v":[[-21.941,32.982],[-21.941,-32.982],[-21.365,-33.557],[21.367,-33.557],[21.941,-32.982],[21.941,-22.103],[21.941,32.982],[21.367,33.557],[-21.365,33.557]],"c":true},"ix":2},"nm":"Tracé 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Fusionner les tracés 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[35.23,46.207],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"wifi1 Silhouettes","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":33,"s":[0]},{"t":34,"s":[100]}],"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[15.25,41.75,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-8.999,8.188],[-0.639,-0.706],[0.705,-0.64],[-6.915,-7.573],[0.701,-0.644],[0.642,0.703]],"o":[[0.704,-0.641],[0.639,0.706],[-7.587,6.903],[0.642,0.703],[-0.701,0.643],[-8.204,-8.983]],"v":[[3.286,-15.591],[5.719,-15.473],[5.6,-13.035],[4.387,13.15],[4.279,15.589],[1.846,15.481]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[26.374,46.264],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"wifi2 Silhouettes","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[0]},{"t":28,"s":[100]}],"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[15.25,41.75,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.001,0],[0.33,-0.302],[-4.98,-5.476],[-0.703,0.643],[0.641,0.706],[-4.057,3.708],[0.639,0.704],[0.466,0]],"o":[[-0.414,0],[-5.462,4.991],[0.64,0.703],[0.703,-0.642],[-3.697,-4.066],[0.702,-0.641],[-0.34,-0.372],[-0.001,0]],"v":[[2.55,-10.072],[1.391,-9.622],[0.517,9.318],[2.949,9.43],[3.061,6.991],[3.711,-7.072],[3.824,-9.51],[2.553,-10.072]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[35.627,46.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"wifi3 Silhouettes","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":18,"s":[0]},{"t":19,"s":[100]}],"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[15.25,41.75,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.331,-0.302],[-3.252,-3.576],[-0.702,0.641],[0.641,0.704],[-2.161,1.976],[0.641,0.705],[0.468,0]],"o":[[-3.566,3.259],[0.64,0.704],[0.703,-0.642],[-1.97,-2.168],[0.703,-0.642],[-0.339,-0.373],[-0.414,0]],"v":[[0.374,-6.337],[-0.196,6.033],[2.236,6.146],[2.348,3.708],[2.695,-3.786],[2.807,-6.223],[1.533,-6.786]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[44.74,46.552],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"button Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55,50,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.775],[-2.768,0],[0,2.775],[2.768,0]],"o":[[0,2.775],[2.768,0],[0,-2.775],[-2.768,0]],"v":[[-5.012,0],[0,5.025],[5.012,0],[0,-5.025]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0.869],[-0.866,0],[0,-0.868],[0.867,0]],"o":[[0,-0.868],[0.867,0],[0,0.869],[-0.866,0]],"v":[[-1.569,0],[0,-1.572],[1.569,0],[0,1.573]],"c":true},"ix":2},"nm":"Tracé 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Fusionner les tracés 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[35.668,89.595],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"stroke1 Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55,50,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-6.122],[0,0],[-6.107,0],[0,0],[0,6.122],[0,0],[0,0],[6.107,0]],"o":[[-6.107,0],[0,0],[0,6.122],[0,0],[6.107,0],[0,0],[0,0],[0,-6.122],[0,0]],"v":[[-22.516,-49.465],[-33.611,-38.342],[-33.611,38.34],[-22.516,49.465],[22.515,49.465],[33.611,38.34],[33.611,20.352],[33.611,-38.342],[22.515,-49.465]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,4.216],[0,0],[-4.205,0],[0,0],[0,-4.215],[0,0],[0,0],[4.205,0],[0,0]],"o":[[0,0],[0,-4.215],[0,0],[4.205,0],[0,0],[0,0],[0,4.216],[0,0],[-4.205,0]],"v":[[-30.169,38.34],[-30.169,-38.342],[-22.516,-46.013],[22.514,-46.013],[30.168,-38.342],[30.168,20.352],[30.168,38.34],[22.514,46.012],[-22.516,46.012]],"c":true},"ix":2},"nm":"Tracé 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Fusionner les tracés 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.631,50.032],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"stroke2 Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[55,50,0],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-2.219],[0,0],[-2.215,0],[0,0],[0,2.219],[0,0],[0,0],[2.214,0]],"o":[[-2.215,0],[0,0],[0,2.219],[0,0],[2.214,0],[0,0],[0,0],[0,-2.219],[0,0]],"v":[[-21.365,-37.01],[-25.383,-32.982],[-25.383,32.982],[-21.365,37.01],[21.367,37.01],[25.383,32.982],[25.383,-22.103],[25.383,-32.982],[21.367,-37.01]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0.313],[0,0],[-0.313,0],[0,0],[0,-0.313],[0,0],[0,0],[0.312,0],[0,0]],"o":[[0,0],[0,-0.313],[0,0],[0.312,0],[0,0],[0,0],[0,0.313],[0,0],[-0.313,0]],"v":[[-21.941,32.982],[-21.941,-32.982],[-21.365,-33.557],[21.367,-33.557],[21.941,-32.982],[21.941,-22.103],[21.941,32.982],[21.367,33.557],[-21.365,33.557]],"c":true},"ix":2},"nm":"Tracé 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Fusionner les tracés 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.097999999102,0.528999956916,0.776000019148,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[35.23,46.207],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Groupe 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Card","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2]},{"t":18,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[62,49,0],"to":[-1.667,0.167,0],"ti":[1.667,-0.167,0]},{"t":18,"s":[52,50,0]}],"ix":2},"a":{"a":0,"k":[55,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[116,10.75],[58.629,11.875],[61.378,87.796],[116,86.768]],"c":true}]},{"t":17,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[116,10.75],[69.347,10.75],[69.347,86.768],[116,86.768]],"c":true}]}],"ix":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":17,"s":[100]}],"ix":3},"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":17,"s":[0]}],"ix":4},"nm":"Masque 1"}],"w":110,"h":100,"ip":0,"op":1680,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/error_anim.json b/client/android-light/app/src/main/assets/error_anim.json new file mode 100644 index 0000000..62df180 --- /dev/null +++ b/client/android-light/app/src/main/assets/error_anim.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":24,"ip":0,"op":30,"w":100,"h":100,"nm":"KEYPLE-error-anim","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-171,"ix":10},"p":{"a":0,"k":[51,51,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[77.926,77.926],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.717647058824,0,0.227450980392,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.537,-0.537],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0],"e":[100]},{"t":20}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque 2 Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.754,50.925,0],"ix":2},"a":{"a":0,"k":[13.19,13.027,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.811,3.227],[23.061,23.602]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.717647058824,0,0.227450995352,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.717999985639,0,0.226999993418,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[1.5,0.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.344],"y":[1.002]},"o":{"x":[0.537],"y":[0.01]},"n":["0p344_1p002_0p537_0p01"],"t":6,"s":[0],"e":[100]},{"t":18}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.811,3.227],[23.061,23.602]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.344],"y":[1.002]},"o":{"x":[0.537],"y":[0.01]},"n":["0p344_1p002_0p537_0p01"],"t":9,"s":[0],"e":[100]},{"t":21}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.717647058824,0,0.227450995352,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.717999985639,0,0.226999993418,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[14.778,13.099],"ix":2},"a":{"a":0,"k":[12.528,12.599],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 2","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/error_orange_anim.json b/client/android-light/app/src/main/assets/error_orange_anim.json new file mode 100644 index 0000000..e7b097f --- /dev/null +++ b/client/android-light/app/src/main/assets/error_orange_anim.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":24,"ip":0,"op":30,"w":100,"h":100,"nm":"KEYPLE-error_orange","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-171,"ix":10},"p":{"a":0,"k":[51,51,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[77.926,77.926],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.886274569642,0.56862745098,0.258823529412,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.537,-0.537],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0],"e":[100]},{"t":20}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cross","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.754,50.925,0],"ix":2},"a":{"a":0,"k":[13.19,13.027,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.811,3.227],[23.061,23.602]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.886274569642,0.56862745098,0.258823529412,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.5,0.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.344],"y":[1.002]},"o":{"x":[0.537],"y":[0.01]},"n":["0p344_1p002_0p537_0p01"],"t":6,"s":[0],"e":[100]},{"t":18}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.811,3.227],[23.061,23.602]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.344],"y":[1.002]},"o":{"x":[0.537],"y":[0.01]},"n":["0p344_1p002_0p537_0p01"],"t":9,"s":[0],"e":[100]},{"t":21}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.886274569642,0.56862745098,0.258823529412,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[14.778,13.099],"ix":2},"a":{"a":0,"k":[12.528,12.599],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 2","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/error_white.json b/client/android-light/app/src/main/assets/error_white.json new file mode 100644 index 0000000..7fa616a --- /dev/null +++ b/client/android-light/app/src/main/assets/error_white.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":24,"ip":0,"op":30,"w":100,"h":100,"nm":"KEYPLE-error_white","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-171,"ix":10},"p":{"a":0,"k":[51,51,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[77.926,77.926],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.537,-0.537],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0],"e":[100]},{"t":20}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"cross","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.754,50.925,0],"ix":2},"a":{"a":0,"k":[13.19,13.027,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.811,3.227],[23.061,23.602]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[1.5,0.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.344],"y":[1.002]},"o":{"x":[0.537],"y":[0.01]},"n":["0p344_1p002_0p537_0p01"],"t":6,"s":[0],"e":[100]},{"t":18}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":2,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.811,3.227],[23.061,23.602]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.344],"y":[1.002]},"o":{"x":[0.537],"y":[0.01]},"n":["0p344_1p002_0p537_0p01"],"t":9,"s":[0],"e":[100]},{"t":21}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[14.778,13.099],"ix":2},"a":{"a":0,"k":[12.528,12.599],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 2","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/loading_anim.json b/client/android-light/app/src/main/assets/loading_anim.json new file mode 100644 index 0000000..6338e61 --- /dev/null +++ b/client/android-light/app/src/main/assets/loading_anim.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":25,"ip":0,"op":59,"w":123,"h":127,"nm":"picto_loading","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60.5,61,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[104.289,104.289],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.113725497676,0.529411764706,0.733333333333,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.338,1.133],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[1]},{"t":21.5,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21.5,"s":[0]},{"t":50,"s":[99]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.113725497676,0.529411764706,0.733333333333,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":16725,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60.5,61,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[104.289,104.289],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.894117706897,0.478431402468,0.152941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.338,1.133],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[0]},{"t":50,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[0]},{"t":54,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.894117706897,0.478431402468,0.152941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":22,"op":16747,"st":22,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60.5,61,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[104.289,104.289],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.717647058824,0,0.21568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.338,1.133],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"t":58,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[0]},{"t":56,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.717647058824,0,0.21568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":22,"op":16747,"st":22,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/splashscreen_anim.json b/client/android-light/app/src/main/assets/splashscreen_anim.json new file mode 100644 index 0000000..419c801 --- /dev/null +++ b/client/android-light/app/src/main/assets/splashscreen_anim.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":24,"ip":0,"op":35,"w":360,"h":640,"nm":"KEYPLE-splashscreen 2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Calque de forme 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[423,92.5,0],"e":[362,70.5,0],"to":[-10.1666669845581,-3.66666674613953,0],"ti":[10.1666669845581,3.66666674613953,0]},{"t":6}],"ix":2},"a":{"a":0,"k":[-95,-196,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.725490196078,0.003921568627,0.227450995352,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-25.993,165.672],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Calque de forme 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":2,"s":[494,645.5,0],"e":[385,645.5,0],"to":[-18.1666660308838,0,0],"ti":[18.1666660308838,0,0]},{"t":11}],"ix":2},"a":{"a":0,"k":[-111,-238,0],"ix":1},"s":{"a":0,"k":[113,113,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.352941176471,0.772549079446,0.945098099054,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-25.993,165.672],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Calque de forme 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":9,"s":[517,165.5,0],"e":[326,165.5,0],"to":[-31.8333339691162,0,0],"ti":[31.8333339691162,0,0]},{"t":19}],"ix":2},"a":{"a":0,"k":[-111,-238,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.352941176471,0.772549079446,0.945098099054,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-25.993,165.672],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Calque de forme 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":11,"s":[-51,479.5,0],"e":[40,479.5,0],"to":[15.1666669845581,0,0],"ti":[-15.1666669845581,0,0]},{"t":17}],"ix":2},"a":{"a":0,"k":[-111,-228,0],"ix":1},"s":{"a":0,"k":[90,90,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.886274569642,0.56862745098,0.258823529412,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-50.437,112.859],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Calque de forme 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":43,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":10,"s":[-257,640.5,0],"e":[27,640.5,0],"to":[47.3333320617676,0,0],"ti":[-47.3333320617676,0,0]},{"t":16}],"ix":2},"a":{"a":0,"k":[-111,-228,0],"ix":1},"s":{"a":0,"k":[150,150,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.576470588235,0.866666726505,0.882353001015,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-49.211,105.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Calque de forme 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":5,"s":[440,205.5,0],"e":[365.25,204.5,0],"to":[-12.4583330154419,-0.16666667163372,0],"ti":[12.4583330154419,0.16666667163372,0]},{"t":9}],"ix":2},"a":{"a":0,"k":[-111,-228,0],"ix":1},"s":{"a":0,"k":[66.218,66.218,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.886274569642,0.56862745098,0.258823529412,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-50.437,112.859],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Calque de forme 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[-103,26.5,0],"e":[125,20.5,0],"to":[38,-1,0],"ti":[-38,1,0]},{"t":6}],"ix":2},"a":{"a":0,"k":[-111,-270,0],"ix":1},"s":{"a":0,"k":[114,114,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.486274509804,0.745098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-55.652,23.613],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Calque de forme 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":133,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":14,"s":[216.311,665.189,0],"e":[273.25,603.25,0],"to":[9.48991680145264,-10.3232498168945,0],"ti":[-9.48991680145264,10.3232498168945,0]},{"t":21}],"ix":2},"a":{"a":0,"k":[-95,-196,0],"ix":1},"s":{"a":0,"k":[50.532,50.532,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.725490196078,0.003921568627,0.227450995352,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-25.993,165.672],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Calque de forme 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":9,"s":[-7,-35.5,0],"e":[93,74.5,0],"to":[16.3333339691162,18.3333339691162,0],"ti":[-16.6666660308838,-18.3333339691162,0]},{"t":16}],"ix":2},"a":{"a":0,"k":[-111,-228,0],"ix":1},"s":{"a":0,"k":[60,60,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-127.688,-320.062],[-54.313,-242.5],[18.875,-319.906]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.576470588235,0.866666726505,0.878431432387,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fond 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-42.075,130.566],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[126.483,126.483],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1680,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/tick_anim.json b/client/android-light/app/src/main/assets/tick_anim.json new file mode 100644 index 0000000..dcb6f74 --- /dev/null +++ b/client/android-light/app/src/main/assets/tick_anim.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":24,"ip":0,"op":30,"w":100,"h":100,"nm":"KEYPLE-tick-anim","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-171,"ix":10},"p":{"a":0,"k":[51,51,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[77.926,77.926],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.113725497676,0.670588235294,0.364705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.537,-0.537],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0],"e":[100]},{"t":20}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"tick","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.998,50.321,0],"ix":2},"a":{"a":0,"k":[17.829,15.422,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[32.706,2.851],[12.581,27.351],[3.081,17.351]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.113725497676,0.670588235294,0.364705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[100],"e":[0]},{"t":18}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/assets/tick_white.json b/client/android-light/app/src/main/assets/tick_white.json new file mode 100644 index 0000000..3b857fa --- /dev/null +++ b/client/android-light/app/src/main/assets/tick_white.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":24,"ip":0,"op":30,"w":100,"h":100,"nm":"KEYPLE-tick_white","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-171,"ix":10},"p":{"a":0,"k":[51,51,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[92,92,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[77.926,77.926],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Tracé d'ellipse 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-0.537,-0.537],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":4,"s":[0],"e":[100]},{"t":20}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"tick","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.998,50.321,0],"ix":2},"a":{"a":0,"k":[17.829,15.422,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[32.706,2.851],[12.581,27.351],[3.081,17.351]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Forme 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[100],"e":[0]},{"t":18}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Raccorder les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":30,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/client/android-light/app/src/main/ic_launcher-playstore.png b/client/android-light/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..4896ad1 Binary files /dev/null and b/client/android-light/app/src/main/ic_launcher-playstore.png differ diff --git a/client/android-light/app/src/main/ic_launcher-web.png b/client/android-light/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..798edcf Binary files /dev/null and b/client/android-light/app/src/main/ic_launcher-web.png differ diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/Application.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/Application.kt new file mode 100644 index 0000000..b0bf99f --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/Application.kt @@ -0,0 +1,37 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote + +import android.content.Context +import androidx.multidex.MultiDex +import dagger.android.DaggerApplication +import org.calypsonet.keyple.demo.reload.remote.di.AppComponent +import org.calypsonet.keyple.demo.reload.remote.di.DaggerAppComponent +import timber.log.Timber +import timber.log.Timber.DebugTree + +class Application : DaggerApplication() { + + override fun attachBaseContext(context: Context?) { + super.attachBaseContext(context) + MultiDex.install(this) + } + + override fun onCreate() { + super.onCreate() + Timber.plant(DebugTree()) + } + + override fun applicationInjector(): AppComponent? { + return DaggerAppComponent.builder().application(this).build() + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/Extensions.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/Extensions.kt new file mode 100644 index 0000000..84d6c9e --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/Extensions.kt @@ -0,0 +1,36 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.DrawableRes +import androidx.annotation.LayoutRes +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView + +const val KEYPLE_PREFS = "Keyple-prefs" + +fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View { + return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) +} + +fun RecyclerView.setDivider(@DrawableRes drawableRes: Int) { + val divider = DividerItemDecoration(this.context, DividerItemDecoration.VERTICAL) + val drawable = ContextCompat.getDrawable(this.context, drawableRes) + drawable?.let { + divider.setDrawable(it) + addItemDecoration(divider) + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/ReaderRepository.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/ReaderRepository.kt new file mode 100644 index 0000000..3b329d1 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/ReaderRepository.kt @@ -0,0 +1,61 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data + +import kotlin.jvm.Throws +import org.eclipse.keyple.core.common.KeyplePluginExtensionFactory +import org.eclipse.keyple.core.service.Plugin +import org.eclipse.keyple.core.service.SmartCardServiceProvider +import org.eclipse.keypop.reader.CardReader +import org.eclipse.keypop.reader.ObservableCardReader +import org.eclipse.keypop.reader.ReaderCommunicationException +import timber.log.Timber + +/** + * Manager provided to encapsulate slight differences between readers provide methods to improve + * code readability. + */ +object ReaderRepository { + + /** Register any keyple plugin */ + fun registerPlugin(factory: KeyplePluginExtensionFactory): Plugin? { + return try { + SmartCardServiceProvider.getService().registerPlugin(factory) + } catch (e: Exception) { + null + } + } + + /** Un register any keyple plugin */ + fun unregisterPlugin(pluginName: String) { + try { + SmartCardServiceProvider.getService().unregisterPlugin(pluginName) + } catch (e: Exception) { + Timber.e(e) + } + } + + /** Retrieve a registered reader */ + @Throws(ReaderCommunicationException::class) + fun getReader(readerName: String): CardReader { + var reader: CardReader? = null + SmartCardServiceProvider.getService().plugins.forEach { reader = it.getReader(readerName) } + return reader ?: throw ReaderCommunicationException("$readerName not found") + } + + /** Retrieve a registered observable reader. */ + @Throws(Exception::class) + fun getObservableReader(readerName: String): ObservableCardReader { + val reader = getReader(readerName) + return if (reader is ObservableCardReader) reader else throw Exception("$readerName not found") + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/SharedPrefDataRepository.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/SharedPrefDataRepository.kt new file mode 100644 index 0000000..13e96f6 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/SharedPrefDataRepository.kt @@ -0,0 +1,149 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data + +import android.annotation.SuppressLint +import android.content.SharedPreferences +import java.util.* +import javax.inject.Inject +import org.calypsonet.keyple.demo.reload.remote.di.scopes.AppScoped + +@AppScoped +class SharedPrefDataRepository @Inject constructor(private var prefs: SharedPreferences) { + + @SuppressLint("ApplySharedPref") + fun saveServerIP(serverIp: String?) { + val editor = prefs.edit() + editor.putString(SERVER_IP_KEY, serverIp) + editor.commit() // We need to use commit instead of apply because the app is restart just after + // the change of this pref + } + + fun loadServerIP(): String? { + return prefs.getString(SERVER_IP_KEY, DEFAULT_SERVER_IP_KEY) + } + + @SuppressLint("ApplySharedPref") + fun saveServerPort(serverPort: Int?) { + val editor = prefs.edit() + editor.putInt(SERVER_PORT_KEY, serverPort!!) + editor.commit() // We need to use commit instead of apply because the app is restart just after + // the change of this pref + } + + fun loadServerPort(): Int { + return prefs.getInt(SERVER_PORT_KEY, DEFAULT_PORT) + } + + @SuppressLint("ApplySharedPref") + fun saveServerProtocol(serverProtocol: String?) { + val editor = prefs.edit() + editor.putString(SERVER_PROTOCOL_KEY, serverProtocol) + editor.commit() // We need to use commit instead of apply because the app is restart just after + // the change of this pref + } + + fun loadServerProtocol(): String? { + return prefs.getString(SERVER_PROTOCOL_KEY, DEFAULT_PROTOCOL) + } + + fun saveDeviceType(deviceType: String?) { + val editor = prefs.edit() + editor.putString(DEVICE_TYPE, deviceType) + editor.apply() + } + + fun loadDeviceType(): String? { + return prefs.getString(DEVICE_TYPE, "") + } + + fun saveContactlessConfigurationVisibility(visibility: Visibility) { + val editor = prefs.edit() + editor.putString(SETTING_CONTACTLESS_VISIBILITY, visibility.text) + editor.apply() + } + + fun loadContactlessConfigurationVisibility(): Visibility { + return Visibility.valueOf( + prefs + .getString(SETTING_CONTACTLESS_VISIBILITY, Visibility.ENABLE.text)!! + .uppercase(Locale.ROOT)) + } + + fun saveSimConfigurationVisibility(visibility: Visibility) { + val editor = prefs.edit() + editor.putString(SETTING_SIM_VISIBILITY, visibility.text) + editor.apply() + } + + fun loadSimConfigurationVisibility(): Visibility { + return Visibility.valueOf( + prefs.getString(SETTING_SIM_VISIBILITY, Visibility.DISABLE.text)!!.uppercase(Locale.ROOT)) + } + + fun saveWearableConfigurationVisibility(visibility: Visibility) { + val editor = prefs.edit() + editor.putString(SETTING_WEARABLE_VISIBILITY, visibility.text) + editor.apply() + } + + fun loadWearableConfigurationVisibility(): Visibility { + return Visibility.valueOf( + prefs + .getString(SETTING_WEARABLE_VISIBILITY, Visibility.DISABLE.text)!! + .uppercase(Locale.ROOT)) + } + + fun saveEmbeddedConfigurationVisibility(visibility: Visibility) { + val editor = prefs.edit() + editor.putString(SETTING_EMBEDDED_VISIBILITY, visibility.text) + editor.apply() + } + + fun loadEmbeddedConfigurationVisibility(): Visibility { + return Visibility.valueOf( + prefs + .getString(SETTING_EMBEDDED_VISIBILITY, Visibility.DISABLE.text)!! + .uppercase(Locale.ROOT)) + } + + fun saveLastStatus(up: Boolean) { + val editor = prefs.edit() + editor.putBoolean(SETTING_SERVER_LAST_STATUS_UP, up) + editor.apply() + } + + fun loadLastStatus(): Boolean { + return prefs.getBoolean(SETTING_SERVER_LAST_STATUS_UP, false) + } + + companion object { + private const val SERVER_IP_KEY = "server_ip_key" + private const val SERVER_PORT_KEY = "server_port_key" + private const val SERVER_PROTOCOL_KEY = "server_protocol_key" + private const val DEVICE_TYPE = "device_type" + private const val DEFAULT_SERVER_IP_KEY = "192.168.0.1" + private const val DEFAULT_PORT = 8080 + private const val DEFAULT_PROTOCOL = "http://" + private const val SETTING_CONTACTLESS_VISIBILITY = "setting_contactless_visibility" + private const val SETTING_SIM_VISIBILITY = "setting_sim_visibility" + private const val SETTING_WEARABLE_VISIBILITY = "setting_wearable_visibility" + private const val SETTING_EMBEDDED_VISIBILITY = "setting_embedded_visibility" + private const val SETTING_SERVER_LAST_STATUS_UP = "setting_server_last_status_up" + + enum class Visibility constructor(val text: String) { + ENABLE("enable"), + DISABLE("disable"), + HIDE("hide") + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/CardReaderResponse.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/CardReaderResponse.kt new file mode 100644 index 0000000..cd17c65 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/CardReaderResponse.kt @@ -0,0 +1,26 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class CardReaderResponse( + val status: Status, + val cardType: String, + val ticketsNumber: Int, + val titlesList: List, + val lastValidationsList: ArrayList, + val seasonPassExpiryDate: String, + val errorMessage: String? = null +) : Parcelable diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/CardTitle.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/CardTitle.kt new file mode 100644 index 0000000..6d0ca4c --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/CardTitle.kt @@ -0,0 +1,18 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class CardTitle(val name: String, val description: String, val valid: Boolean) : Parcelable diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/DeviceEnum.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/DeviceEnum.kt new file mode 100644 index 0000000..c72a5e6 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/DeviceEnum.kt @@ -0,0 +1,34 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +import java.util.* +import org.calypsonet.keyple.demo.reload.remote.R + +enum class DeviceEnum(val textId: Int) { + CONTACTLESS_CARD(R.string.contactless_card), + SIM(R.string.sim_card), + WEARABLE(R.string.wearable), + EMBEDDED(R.string.embedded_secure_elem); + + companion object { + @JvmStatic + fun getDeviceEnum(name: String): DeviceEnum { + return try { + valueOf(name.uppercase(Locale.ROOT)) + } catch (e: Exception) { + // If the given state does not exist, return the default value. + CONTACTLESS_CARD + } + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/SamStatus.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/SamStatus.kt new file mode 100644 index 0000000..d97db21 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/SamStatus.kt @@ -0,0 +1,17 @@ +/* ************************************************************************************** + * Copyright (c) 2022 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize data class SamStatus(val isSamReady: Boolean) : Parcelable diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/ServerStatusEvent.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/ServerStatusEvent.kt new file mode 100644 index 0000000..0ee8ee8 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/ServerStatusEvent.kt @@ -0,0 +1,14 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +class ServerStatusEvent(val isUp: Boolean) diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/Status.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/Status.kt new file mode 100644 index 0000000..23c6152 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/Status.kt @@ -0,0 +1,38 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +import java.util.* + +enum class Status(private val status: String) { + LOADING("loading"), + ERROR("error"), + TICKETS_FOUND("tickets_found"), + INVALID_CARD("invalid_card"), + EMPTY_CARD("empty_card"), + SUCCESS("success"); + + override fun toString(): String { + return status + } + + companion object { + fun getStatus(name: String?): Status { + return try { + valueOf(name!!.uppercase(Locale.ROOT)) + } catch (e: Exception) { + // If the given state does not exist, return the default value. + ERROR + } + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/Validation.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/Validation.kt new file mode 100644 index 0000000..a777d36 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/model/Validation.kt @@ -0,0 +1,19 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.model + +import android.os.Parcelable +import java.util.Date +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Validation(val name: String, val location: String, val date: Date) : Parcelable diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/network/KeypleSyncEndPointClient.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/network/KeypleSyncEndPointClient.kt new file mode 100644 index 0000000..bf929bd --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/network/KeypleSyncEndPointClient.kt @@ -0,0 +1,28 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.network + +import io.reactivex.Single +import org.eclipse.keyple.distributed.MessageDto +import org.eclipse.keyple.distributed.spi.SyncEndpointClientSpi + +/** We have to wrap the retrofit client */ +class KeypleSyncEndPointClient(private val restClient: RestClient) : SyncEndpointClientSpi { + + override fun sendRequest(msg: MessageDto?): MutableList { + return restClient.sendRequest(msg).blockingGet() + } + + fun ping(): Single { + return restClient.ping() + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/network/RestClient.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/network/RestClient.kt new file mode 100644 index 0000000..517d4e9 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/data/network/RestClient.kt @@ -0,0 +1,32 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.data.network + +import io.reactivex.Single +import org.eclipse.keyple.distributed.MessageDto +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.POST + +/** + * Cannot extends directly SyncEndpointClient because retrofit allows API interfaces to extend + * interfaces. + */ +interface RestClient { + + @GET("/card/sam-status") fun ping(): Single + + @Headers("Accept: application/json", "Content-Type: application/json; charset=UTF-8") + @POST("/card/remote-plugin") + fun sendRequest(@Body msg: MessageDto?): Single> +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/AppComponent.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/AppComponent.kt new file mode 100644 index 0000000..6067b3b --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/AppComponent.kt @@ -0,0 +1,38 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di + +import dagger.BindsInstance +import dagger.Component +import dagger.android.AndroidInjector +import dagger.android.support.AndroidSupportInjectionModule +import org.calypsonet.keyple.demo.reload.remote.Application +import org.calypsonet.keyple.demo.reload.remote.di.scopes.AppScoped + +@AppScoped +@Component( + modules = + [ + AppModule::class, + UIModule::class, + AndroidSupportInjectionModule::class, + DataModule::class, + RestModule::class, + ReaderModule::class]) +interface AppComponent : AndroidInjector { + @Component.Builder + interface Builder { + @BindsInstance fun application(application: Application): Builder + + fun build(): AppComponent + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/AppModule.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/AppModule.kt new file mode 100644 index 0000000..2252402 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/AppModule.kt @@ -0,0 +1,24 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di + +import android.content.Context +import dagger.Binds +import dagger.Module +import org.calypsonet.keyple.demo.reload.remote.Application + +@Suppress("unused") +@Module +abstract class AppModule { + // expose Application as an injectable context + @Binds abstract fun bindContext(application: Application?): Context? +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/DataModule.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/DataModule.kt new file mode 100644 index 0000000..cb151f3 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/DataModule.kt @@ -0,0 +1,30 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di + +import android.content.Context +import android.content.SharedPreferences +import dagger.Module +import dagger.Provides +import org.calypsonet.keyple.demo.reload.remote.Application +import org.calypsonet.keyple.demo.reload.remote.di.scopes.AppScoped + +@Suppress("unused") +@Module +class DataModule { + + @Provides + @AppScoped + fun getSharedPreferences(app: Application): SharedPreferences { + return app.getSharedPreferences("Keyple-prefs", Context.MODE_PRIVATE) + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/ReaderModule.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/ReaderModule.kt new file mode 100644 index 0000000..0af3354 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/ReaderModule.kt @@ -0,0 +1,47 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di + +import dagger.Module +import dagger.Provides +import org.calypsonet.keyple.demo.reload.remote.data.ReaderRepository +import org.calypsonet.keyple.demo.reload.remote.data.network.KeypleSyncEndPointClient +import org.calypsonet.keyple.demo.reload.remote.di.scopes.AppScoped +import org.eclipse.keyple.core.service.SmartCardServiceProvider +import org.eclipse.keyple.distributed.LocalServiceClient +import org.eclipse.keyple.distributed.LocalServiceClientFactoryBuilder + +@Suppress("unused") +@Module +class ReaderModule { + + @Provides + @AppScoped + fun provideLocalServiceClient( + keypleSyncEndPointClient: KeypleSyncEndPointClient + ): LocalServiceClient { + val smartCardService = SmartCardServiceProvider.getService() + val localService = + smartCardService.getDistributedLocalService("localService") + ?: smartCardService.registerDistributedLocalService( + LocalServiceClientFactoryBuilder.builder("localService") + .withSyncNode(keypleSyncEndPointClient) + .build()) + return localService.getExtension(LocalServiceClient::class.java) + } + + @Provides + @AppScoped + fun provideReaderRepository(): ReaderRepository { + return ReaderRepository + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/RestModule.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/RestModule.kt new file mode 100644 index 0000000..26b6c01 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/RestModule.kt @@ -0,0 +1,48 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di + +import dagger.Module +import dagger.Provides +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.calypsonet.keyple.demo.reload.remote.data.SharedPrefDataRepository +import org.calypsonet.keyple.demo.reload.remote.data.network.KeypleSyncEndPointClient +import org.calypsonet.keyple.demo.reload.remote.data.network.RestClient +import org.calypsonet.keyple.demo.reload.remote.di.scopes.AppScoped +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import timber.log.Timber + +@Suppress("unused") +@Module +class RestModule { + + @Provides + @AppScoped + fun provideKeypleSyncEndpointClient( + prefData: SharedPrefDataRepository + ): KeypleSyncEndPointClient { + val serverUrl = + prefData.loadServerProtocol() + prefData.loadServerIP() + ":" + prefData.loadServerPort() + Timber.i("Loaded Rest client with URL: $serverUrl") + return KeypleSyncEndPointClient( + Retrofit.Builder() + .baseUrl(serverUrl) + .client(OkHttpClient.Builder().addNetworkInterceptor(HttpLoggingInterceptor()).build()) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() + .create(RestClient::class.java)) + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/UIModule.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/UIModule.kt new file mode 100644 index 0000000..7b9e56a --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/UIModule.kt @@ -0,0 +1,76 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di + +import dagger.Module +import dagger.android.ContributesAndroidInjector +import org.calypsonet.keyple.demo.reload.remote.di.scopes.ActivityScoped +import org.calypsonet.keyple.demo.reload.remote.ui.CardReaderActivity +import org.calypsonet.keyple.demo.reload.remote.ui.CheckoutActivity +import org.calypsonet.keyple.demo.reload.remote.ui.ConfigurationSettingsActivity +import org.calypsonet.keyple.demo.reload.remote.ui.HomeActivity +import org.calypsonet.keyple.demo.reload.remote.ui.MainActivity +import org.calypsonet.keyple.demo.reload.remote.ui.PaymentValidatedActivity +import org.calypsonet.keyple.demo.reload.remote.ui.PersonalizationActivity +import org.calypsonet.keyple.demo.reload.remote.ui.ReloadActivity +import org.calypsonet.keyple.demo.reload.remote.ui.ReloadResultActivity +import org.calypsonet.keyple.demo.reload.remote.ui.SelectTicketsActivity +import org.calypsonet.keyple.demo.reload.remote.ui.ServerSettingsActivity +import org.calypsonet.keyple.demo.reload.remote.ui.SettingsMenuActivity +import org.calypsonet.keyple.demo.reload.remote.ui.cardsummary.CardSummaryActivity + +@Suppress("unused") +@Module +abstract class UIModule { + + @ActivityScoped @ContributesAndroidInjector abstract fun mainActivity(): MainActivity? + + @ActivityScoped @ContributesAndroidInjector abstract fun homeActivity(): HomeActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun configurationSettingsActivity(): ConfigurationSettingsActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun serverSettingsActivity(): ServerSettingsActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun settingsMenuActivity(): SettingsMenuActivity? + + @ActivityScoped @ContributesAndroidInjector abstract fun cardReaderActivity(): CardReaderActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun cardSummaryActivity(): CardSummaryActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun selectTicketsActivity(): SelectTicketsActivity? + + @ActivityScoped @ContributesAndroidInjector abstract fun checkoutActivity(): CheckoutActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun paymentValidatedActivity(): PaymentValidatedActivity? + + @ActivityScoped @ContributesAndroidInjector abstract fun chargeCardActivity(): ReloadActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun chargeResultActivity(): ReloadResultActivity? + + @ActivityScoped + @ContributesAndroidInjector + abstract fun personalizationActivity(): PersonalizationActivity? +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/ActivityScoped.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/ActivityScoped.kt new file mode 100644 index 0000000..1265b6b --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/ActivityScoped.kt @@ -0,0 +1,24 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di.scopes + +import java.lang.annotation.Documented +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import javax.inject.Scope + +/** + * In Dagger, an unscoped component cannot depend on a scoped component. As [AppComponent] is a + * scoped component @AppScoped, we create a custom scope to be used by all fragment components. + * Additionally, a component with a specific scope cannot have a sub component with the same scope. + */ +@Documented @Scope @Retention(RetentionPolicy.RUNTIME) annotation class ActivityScoped diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/AppScoped.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/AppScoped.kt new file mode 100644 index 0000000..e87b926 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/AppScoped.kt @@ -0,0 +1,19 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di.scopes + +import java.lang.annotation.Documented +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import javax.inject.Scope + +@Documented @Scope @Retention(RetentionPolicy.RUNTIME) annotation class AppScoped diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/FragmentScoped.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/FragmentScoped.kt new file mode 100644 index 0000000..fa3b447 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/di/scopes/FragmentScoped.kt @@ -0,0 +1,26 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.di.scopes + +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import javax.inject.Scope + +@Scope +@Retention(RetentionPolicy.RUNTIME) +@Target( + AnnotationTarget.ANNOTATION_CLASS, + AnnotationTarget.CLASS, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER) +annotation class FragmentScoped diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/AbstractCardActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/AbstractCardActivity.kt new file mode 100644 index 0000000..aabc3d3 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/AbstractCardActivity.kt @@ -0,0 +1,174 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.os.Bundle +import javax.inject.Inject +import kotlin.jvm.Throws +import org.calypsonet.keyple.demo.reload.remote.data.ReaderRepository +import org.calypsonet.keyple.demo.reload.remote.data.model.CardReaderResponse +import org.calypsonet.keyple.demo.reload.remote.data.model.DeviceEnum +import org.calypsonet.keyple.demo.reload.remote.data.model.Status +import org.eclipse.keyple.core.service.KeyplePluginException +import org.eclipse.keyple.core.service.Plugin +import org.eclipse.keyple.distributed.LocalServiceClient +import org.eclipse.keyple.plugin.android.nfc.AndroidNfcPlugin +import org.eclipse.keyple.plugin.android.nfc.AndroidNfcPluginFactoryProvider +import org.eclipse.keyple.plugin.android.nfc.AndroidNfcReader +import org.eclipse.keyple.plugin.android.nfc.AndroidNfcSupportedProtocols +import org.eclipse.keyple.plugin.android.omapi.AndroidOmapiPlugin +import org.eclipse.keyple.plugin.android.omapi.AndroidOmapiPluginFactoryProvider +import org.eclipse.keyple.plugin.android.omapi.AndroidOmapiReader +import org.eclipse.keypop.reader.ConfigurableCardReader +import org.eclipse.keypop.reader.ObservableCardReader +import org.eclipse.keypop.reader.spi.CardReaderObservationExceptionHandlerSpi +import org.eclipse.keypop.reader.spi.CardReaderObserverSpi +import timber.log.Timber + +abstract class AbstractCardActivity : + AbstractDemoActivity(), CardReaderObserverSpi, CardReaderObservationExceptionHandlerSpi { + + @Inject lateinit var localServiceClient: LocalServiceClient + @Inject lateinit var readerRepository: ReaderRepository + lateinit var selectedDeviceReaderName: String + lateinit var device: DeviceEnum + lateinit var pluginType: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + device = DeviceEnum.getDeviceEnum(prefData.loadDeviceType()!!) + selectedDeviceReaderName = + when (device) { + DeviceEnum.CONTACTLESS_CARD -> { + pluginType = "Android NFC" + AndroidNfcReader.READER_NAME + } + DeviceEnum.SIM -> { + pluginType = "Android OMAPI" + AndroidOmapiReader.READER_NAME_SIM_1 + } + DeviceEnum.WEARABLE -> { + pluginType = "Android WEARABLE" + "WEARABLE" + } + DeviceEnum.EMBEDDED -> { + pluginType = "Android EMBEDDED" + "EMBEDDED" + } + } + } + + override fun onResume() { + super.onResume() + initReaders() + } + + /** Android Nfc Reader is strongly dependent and Android Activity component. */ + @Throws(KeyplePluginException::class) + fun initAndActivateAndroidKeypleNfcReader() { + val plugin: Plugin? = + readerRepository.registerPlugin( + AndroidNfcPluginFactoryProvider(this@AbstractCardActivity).getFactory()) + + if (plugin == null) { + return + } + + val observableCardReader: ObservableCardReader = + readerRepository.getObservableReader(selectedDeviceReaderName) + observableCardReader.setReaderObservationExceptionHandler(this@AbstractCardActivity) + observableCardReader.addObserver(this@AbstractCardActivity) + observableCardReader.setReaderObservationExceptionHandler(this@AbstractCardActivity) + + (observableCardReader as ConfigurableCardReader).activateProtocol( + AndroidNfcSupportedProtocols.ISO_14443_4.name, "ISO_14443_4") + + observableCardReader.startCardDetection(ObservableCardReader.DetectionMode.REPEATING) + } + + @Throws(KeyplePluginException::class) + fun deactivateAndClearAndroidKeypleNfcReader() { + (readerRepository.getReader(selectedDeviceReaderName) as ObservableCardReader) + .stopCardDetection() + (readerRepository.getReader(selectedDeviceReaderName) as ConfigurableCardReader) + .deactivateProtocol(AndroidNfcSupportedProtocols.ISO_14443_4.name) + readerRepository.unregisterPlugin(AndroidNfcPlugin.PLUGIN_NAME) + } + + /** + * Initialisation of AndroidOmapiPlugin is async and take time and cannot be observed. So we'll + * trigger process only when the plugin is registered + */ + @Throws(KeyplePluginException::class) + fun initOmapiReader(callback: () -> Unit) { + AndroidOmapiPluginFactoryProvider(this@AbstractCardActivity) { + readerRepository.registerPlugin(it) + callback() + } + } + + @Throws(KeyplePluginException::class) + fun deactivateAndClearOmapiReader() { + readerRepository.unregisterPlugin(AndroidOmapiPlugin.PLUGIN_NAME) + } + + fun launchInvalidCardResponse(message: String) { + runOnUiThread { + changeDisplay( + CardReaderResponse(Status.INVALID_CARD, "", 0, arrayListOf(), arrayListOf(), "", message), + finishActivity = + device != + DeviceEnum.CONTACTLESS_CARD // /Only with NFC we can come back to 'wait for device + // screen' + ) + } + } + + fun launchServerErrorResponse() { + runOnUiThread { + changeDisplay( + CardReaderResponse(Status.ERROR, "", 0, arrayListOf(), arrayListOf(), ""), + finishActivity = + device != + DeviceEnum.CONTACTLESS_CARD // /Only with NFC we can come back to 'wait for device + // screen' + ) + } + } + + fun launchExceptionResponse(e: Exception, finishActivity: Boolean? = false) { + runOnUiThread { + changeDisplay( + CardReaderResponse(Status.ERROR, "", 0, arrayListOf(), arrayListOf(), "", e.message), + finishActivity = finishActivity) + } + } + + protected abstract fun changeDisplay( + cardReaderResponse: CardReaderResponse, + applicationSerialNumber: String? = null, + finishActivity: Boolean? = false + ) + + override fun onReaderObservationError(contextInfo: String?, readerName: String?, e: Throwable?) { + Timber.e(e) + Timber.d("Error on $contextInfo, $readerName") + this@AbstractCardActivity.finish() + } + + protected abstract fun initReaders() + + companion object { + const val CARD_APPLICATION_NUMBER = "cardApplicationNumber" + const val CARD_CONTENT = "cardContent" + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/AbstractDemoActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/AbstractDemoActivity.kt new file mode 100644 index 0000000..ace2267 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/AbstractDemoActivity.kt @@ -0,0 +1,98 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.os.AsyncTask +import android.os.Bundle +import dagger.android.support.DaggerAppCompatActivity +import javax.inject.Inject +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.SharedPrefDataRepository +import org.calypsonet.keyple.demo.reload.remote.data.model.SamStatus +import org.calypsonet.keyple.demo.reload.remote.data.model.ServerStatusEvent +import org.calypsonet.keyple.demo.reload.remote.data.network.RestClient +import org.calypsonet.keyple.demo.reload.remote.databinding.ToolbarBinding +import org.eclipse.keyple.core.util.json.JsonUtil +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory + +/** Each Activity of the app should show status connexion result */ +abstract class AbstractDemoActivity : DaggerAppCompatActivity() { + + private lateinit var client: RestClient + protected lateinit var toolbarBinding: ToolbarBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + client = + Retrofit.Builder() + .baseUrl( + prefData.loadServerProtocol() + + prefData.loadServerIP() + + ":" + + prefData.loadServerPort()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(ScalarsConverterFactory.create()) + .build() + .create(RestClient::class.java) + } + + @Inject lateinit var prefData: SharedPrefDataRepository + + override fun onResume() { + super.onResume() + checkServerStatus() + } + + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + super.onStop() + EventBus.getDefault().unregister(this) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onServerStatusEvent(serverStatusEvent: ServerStatusEvent) { + prefData.saveLastStatus(serverStatusEvent.isUp) + updateServerStatusIndicator() + } + + private fun updateServerStatusIndicator() { + if (prefData.loadLastStatus()) + toolbarBinding.serverStatus.setImageResource(R.drawable.ic_connection_success) + else toolbarBinding.serverStatus.setImageResource(R.drawable.ic_connection_wait) + } + + private fun checkServerStatus() { + PingAsyncTask().execute(client) + } + + class PingAsyncTask : AsyncTask() { + override fun doInBackground(vararg client: RestClient): Long { + try { + val jsonRes = client[0].ping().blockingGet() + val samStatus = JsonUtil.getParser().fromJson(jsonRes.toString(), SamStatus::class.java) + EventBus.getDefault().post(ServerStatusEvent(samStatus.isSamReady)) + } catch (e: Exception) { + EventBus.getDefault().post(ServerStatusEvent(false)) + } + return 0 + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/CardReaderActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/CardReaderActivity.kt new file mode 100644 index 0000000..6a0c221 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/CardReaderActivity.kt @@ -0,0 +1,202 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.nfc.NfcManager +import android.os.Bundle +import android.view.View +import java.lang.IllegalStateException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.calypsonet.keyple.demo.common.constant.RemoteServiceId +import org.calypsonet.keyple.demo.common.dto.SelectAppAndAnalyzeContractsInputDto +import org.calypsonet.keyple.demo.common.dto.SelectAppAndAnalyzeContractsOutputDto +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.model.* +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityCardReaderBinding +import org.calypsonet.keyple.demo.reload.remote.di.scopes.ActivityScoped +import org.calypsonet.keyple.demo.reload.remote.ui.cardsummary.CardSummaryActivity +import org.eclipse.keyple.core.service.KeyplePluginException +import org.eclipse.keypop.reader.CardReaderEvent +import org.eclipse.keypop.reader.ReaderCommunicationException +import timber.log.Timber + +@ActivityScoped +class CardReaderActivity : AbstractCardActivity() { + + private lateinit var activityCardReaderBinding: ActivityCardReaderBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityCardReaderBinding = ActivityCardReaderBinding.inflate(layoutInflater) + toolbarBinding = activityCardReaderBinding.appBarLayout + setContentView(activityCardReaderBinding.root) + } + + override fun initReaders() { + try { + when (device) { + DeviceEnum.CONTACTLESS_CARD -> { + val nfcManager = getSystemService(NFC_SERVICE) as NfcManager + if (nfcManager.defaultAdapter?.isEnabled == true) { + showPresentNfcCardInstructions() + initAndActivateAndroidKeypleNfcReader() + } else { + launchExceptionResponse( + IllegalStateException("NFC not activated"), finishActivity = true) + } + } + DeviceEnum.SIM -> { + showNowLoadingInformation() + initOmapiReader { + GlobalScope.launch { remoteServiceExecution(selectedDeviceReaderName, pluginType) } + } + } + DeviceEnum.WEARABLE -> { + throw KeyplePluginException("Wearable") + } + DeviceEnum.EMBEDDED -> { + throw KeyplePluginException("Embedded") + } + } + } catch (e: ReaderCommunicationException) { + Timber.e(e) + launchExceptionResponse(e, true) + } catch (e: Exception) { + Timber.e(e) + } + } + + override fun onPause() { + activityCardReaderBinding.cardAnimation.cancelAnimation() + activityCardReaderBinding.loadingAnimation.cancelAnimation() + try { + if (DeviceEnum.getDeviceEnum(prefData.loadDeviceType()!!) == DeviceEnum.CONTACTLESS_CARD) { + deactivateAndClearAndroidKeypleNfcReader() + } else { + deactivateAndClearOmapiReader() + } + } catch (e: Exception) { + Timber.e(e) + } + super.onPause() + } + + override fun onReaderEvent(event: CardReaderEvent?) { + if (event?.type == CardReaderEvent.Type.CARD_INSERTED) { + // We'll select Card when SmartCard is presented in field + // Method handlePo is described below + runOnUiThread { showNowLoadingInformation() } + GlobalScope.launch { remoteServiceExecution(selectedDeviceReaderName, pluginType) } + } + } + + private suspend fun remoteServiceExecution(selectedDeviceReaderName: String, pluginType: String) { + withContext(Dispatchers.IO) { + try { + val selectAppAndAnalyzeContractsInputDto = SelectAppAndAnalyzeContractsInputDto(pluginType) + + val selectAppAndAnalyzeContractsOutputDto = + localServiceClient.executeRemoteService( + RemoteServiceId.SELECT_APP_AND_ANALYZE_CONTRACTS.name, + selectedDeviceReaderName, + null, + selectAppAndAnalyzeContractsInputDto, + SelectAppAndAnalyzeContractsOutputDto::class.java) + + when (selectAppAndAnalyzeContractsOutputDto.statusCode) { + 0 -> { + runOnUiThread { + val contracts = selectAppAndAnalyzeContractsOutputDto.validContracts + val status = if (contracts.isNotEmpty()) Status.TICKETS_FOUND else Status.EMPTY_CARD + val finishActivity = + device != + DeviceEnum + .CONTACTLESS_CARD // Only with NFC we can come back to 'wait for device + // screen' + + changeDisplay( + CardReaderResponse( + status, + "", + selectAppAndAnalyzeContractsOutputDto.validContracts.size, + buildCardTitles(selectAppAndAnalyzeContractsOutputDto.validContracts), + arrayListOf(), + ""), + selectAppAndAnalyzeContractsOutputDto.applicationSerialNumber, + finishActivity) + } + } // success, + 1 -> { + launchServerErrorResponse() + } // server not ready, + in 2..4 -> { + launchInvalidCardResponse(selectAppAndAnalyzeContractsOutputDto.message) + } // card rejected, not personalized, expired environment + } + } catch (e: IllegalStateException) { + Timber.e(e) + launchInvalidCardResponse(e.message!!) + } catch (e: Exception) { + Timber.e(e) + val finishActivity = + device != + DeviceEnum + .CONTACTLESS_CARD // Only with NFC we can come back to 'wait for device screen' + launchExceptionResponse( + IllegalStateException("Server error:\n" + e.message), finishActivity) + } + } + } + + private fun buildCardTitles( + contractInfos: List? + ): List { + val cardTitles = contractInfos?.map { CardTitle(it.title, it.description, it.isValid) } + return cardTitles ?: arrayListOf() + } + + override fun changeDisplay( + cardReaderResponse: CardReaderResponse, + applicationSerialNumber: String?, + finishActivity: Boolean? + ) { + activityCardReaderBinding.loadingAnimation.cancelAnimation() + activityCardReaderBinding.cardAnimation.cancelAnimation() + val intent = Intent(this, CardSummaryActivity::class.java) + intent.putExtra(CARD_CONTENT, cardReaderResponse) + intent.putExtra(CARD_APPLICATION_NUMBER, applicationSerialNumber) + startActivity(intent) + if (finishActivity == true) { + finish() + } + } + + private fun showPresentNfcCardInstructions() { + activityCardReaderBinding.presentTxt.text = getString(R.string.present_travel_card_label) + activityCardReaderBinding.cardAnimation.visibility = View.VISIBLE + activityCardReaderBinding.cardAnimation.playAnimation() + activityCardReaderBinding.loadingAnimation.cancelAnimation() + activityCardReaderBinding.loadingAnimation.visibility = View.INVISIBLE + } + + private fun showNowLoadingInformation() { + activityCardReaderBinding.presentTxt.text = getString(R.string.read_in_progress) + activityCardReaderBinding.loadingAnimation.visibility = View.VISIBLE + activityCardReaderBinding.loadingAnimation.playAnimation() + activityCardReaderBinding.cardAnimation.cancelAnimation() + activityCardReaderBinding.cardAnimation.visibility = View.INVISIBLE + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/CheckoutActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/CheckoutActivity.kt new file mode 100644 index 0000000..f20d203 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/CheckoutActivity.kt @@ -0,0 +1,58 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import java.text.SimpleDateFormat +import java.util.Calendar +import org.calypsonet.keyple.demo.common.model.type.PriorityCode +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityCheckoutBinding + +class CheckoutActivity : AbstractDemoActivity() { + + private lateinit var activityCheckoutBinding: ActivityCheckoutBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityCheckoutBinding = ActivityCheckoutBinding.inflate(layoutInflater) + toolbarBinding = activityCheckoutBinding.appBarLayout + setContentView(activityCheckoutBinding.root) + + val selectedTicketPriorityCode = + PriorityCode.findEnumByKey( + intent.getIntExtra( + SelectTicketsActivity.SELECTED_TICKET_PRIORITY_CODE, PriorityCode.MULTI_TRIP.key)) + val ticketNumberCount: Int = intent.getIntExtra(SelectTicketsActivity.TICKETS_NUMBER, 0) + + if (selectedTicketPriorityCode == PriorityCode.SEASON_PASS) { + activityCheckoutBinding.selectionLabel.text = getString(R.string.season_pass_title) + activityCheckoutBinding.selectionPrice.text = getString(R.string.ticket_price, 20) + } else { + activityCheckoutBinding.selectionLabel.text = + resources.getQuantityString(R.plurals.x_tickets, ticketNumberCount, ticketNumberCount) + activityCheckoutBinding.selectionPrice.text = + getString(R.string.ticket_price, ticketNumberCount) + } + activityCheckoutBinding.validateBtn.setOnClickListener { + val intent = Intent(this, PaymentValidatedActivity::class.java) + intent.putExtras(getIntent()) + startActivity(intent) + this@CheckoutActivity.finish() + } + + val now = Calendar.getInstance().time + val sdf = SimpleDateFormat("MM/yy") + activityCheckoutBinding.expiryValue.text = sdf.format(now) + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ConfigurationSettingsActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ConfigurationSettingsActivity.kt new file mode 100644 index 0000000..5c4d7c6 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ConfigurationSettingsActivity.kt @@ -0,0 +1,214 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.os.Bundle +import android.view.View +import android.widget.RadioButton +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.SharedPrefDataRepository +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityConfigurationSettingsBinding + +class ConfigurationSettingsActivity : AbstractDemoActivity() { + private lateinit var activityConfigurationSettingsBinding: ActivityConfigurationSettingsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityConfigurationSettingsBinding = + ActivityConfigurationSettingsBinding.inflate(layoutInflater) + toolbarBinding = activityConfigurationSettingsBinding.appBarLayout + setContentView(activityConfigurationSettingsBinding.root) + + activityConfigurationSettingsBinding.backBtn.setOnClickListener { onBackPressed() } + + setupRadioBtn( + prefData.loadContactlessConfigurationVisibility(), + activityConfigurationSettingsBinding.contactlessCardEnable, + activityConfigurationSettingsBinding.contactlessCardDisable, + activityConfigurationSettingsBinding.contactlessCardHide) + setupRadioBtn( + prefData.loadSimConfigurationVisibility(), + activityConfigurationSettingsBinding.simCardEnable, + activityConfigurationSettingsBinding.simCardDisable, + activityConfigurationSettingsBinding.simCardHide) + setupRadioBtn( + prefData.loadWearableConfigurationVisibility(), + activityConfigurationSettingsBinding.wearableCardEnable, + activityConfigurationSettingsBinding.wearableCardDisable, + activityConfigurationSettingsBinding.wearableCardHide) + setupRadioBtn( + prefData.loadEmbeddedConfigurationVisibility(), + activityConfigurationSettingsBinding.embeddedCardEnable, + activityConfigurationSettingsBinding.embeddedCardDisable, + activityConfigurationSettingsBinding.embeddedCardHide) + } + + private fun setupRadioBtn( + visibility: SharedPrefDataRepository.Companion.Visibility, + enableBtn: RadioButton, + disableBtn: RadioButton, + hideBtn: RadioButton + ) { + when (visibility) { + SharedPrefDataRepository.Companion.Visibility.ENABLE -> { + enableBtn.isChecked = true + enableBtn.setTextColor(resources.getColor(R.color.dark_blue)) + disableBtn.isChecked = false + disableBtn.setTextColor(resources.getColor(R.color.light_grey)) + hideBtn.isChecked = false + hideBtn.setTextColor(resources.getColor(R.color.light_grey)) + } + SharedPrefDataRepository.Companion.Visibility.DISABLE -> { + enableBtn.isChecked = false + enableBtn.setTextColor(resources.getColor(R.color.light_grey)) + disableBtn.isChecked = true + disableBtn.setTextColor(resources.getColor(R.color.dark_blue)) + hideBtn.isChecked = false + hideBtn.setTextColor(resources.getColor(R.color.light_grey)) + } + SharedPrefDataRepository.Companion.Visibility.HIDE -> { + enableBtn.isChecked = false + enableBtn.setTextColor(resources.getColor(R.color.light_grey)) + disableBtn.isChecked = false + disableBtn.setTextColor(resources.getColor(R.color.light_grey)) + hideBtn.isChecked = true + hideBtn.setTextColor(resources.getColor(R.color.dark_blue)) + } + } + } + + fun onContactlessRadioButtonClicked(view: View) { + if (view is RadioButton) { + // Is the button now checked? + val checked = view.isChecked + + // Check which radio button was clicked + when (view.getId()) { + R.id.contactlessCardEnable -> + if (checked) { + prefData.saveContactlessConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.ENABLE) + } + R.id.contactlessCardDisable -> + if (checked) { + prefData.saveContactlessConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.DISABLE) + } + R.id.contactlessCardHide -> + if (checked) { + prefData.saveContactlessConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.HIDE) + } + } + + // Update layout + setupRadioBtn( + prefData.loadContactlessConfigurationVisibility(), + activityConfigurationSettingsBinding.contactlessCardEnable, + activityConfigurationSettingsBinding.contactlessCardDisable, + activityConfigurationSettingsBinding.contactlessCardHide) + } + } + + fun onSimRadioButtonClicked(view: View) { + if (view is RadioButton) { + // Is the button now checked? + val checked = view.isChecked + + // Check which radio button was clicked + when (view.getId()) { + R.id.simCardEnable -> + if (checked) { + prefData.saveSimConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.ENABLE) + } + R.id.simCardDisable -> + if (checked) { + prefData.saveSimConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.DISABLE) + } + R.id.simCardHide -> + if (checked) { + prefData.saveSimConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.HIDE) + } + } + setupRadioBtn( + prefData.loadSimConfigurationVisibility(), + activityConfigurationSettingsBinding.simCardEnable, + activityConfigurationSettingsBinding.simCardDisable, + activityConfigurationSettingsBinding.simCardHide) + } + } + + fun onWearableRadioButtonClicked(view: View) { + if (view is RadioButton) { + // Is the button now checked? + val checked = view.isChecked + + // Check which radio button was clicked + when (view.getId()) { + R.id.wearableCardEnable -> + if (checked) { + prefData.saveWearableConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.ENABLE) + } + R.id.wearableCardDisable -> + if (checked) { + prefData.saveWearableConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.DISABLE) + } + R.id.wearableCardHide -> + if (checked) { + prefData.saveWearableConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.HIDE) + } + } + setupRadioBtn( + prefData.loadWearableConfigurationVisibility(), + activityConfigurationSettingsBinding.wearableCardEnable, + activityConfigurationSettingsBinding.wearableCardDisable, + activityConfigurationSettingsBinding.wearableCardHide) + } + } + + fun onEmbeddedRadioButtonClicked(view: View) { + if (view is RadioButton) { + // Is the button now checked? + val checked = view.isChecked + + // Check which radio button was clicked + when (view.getId()) { + R.id.embeddedCardEnable -> + if (checked) { + prefData.saveEmbeddedConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.ENABLE) + } + R.id.embeddedCardDisable -> + if (checked) { + prefData.saveEmbeddedConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.DISABLE) + } + R.id.embeddedCardHide -> + if (checked) { + prefData.saveEmbeddedConfigurationVisibility( + SharedPrefDataRepository.Companion.Visibility.HIDE) + } + } + setupRadioBtn( + prefData.loadEmbeddedConfigurationVisibility(), + activityConfigurationSettingsBinding.embeddedCardEnable, + activityConfigurationSettingsBinding.embeddedCardDisable, + activityConfigurationSettingsBinding.embeddedCardHide) + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/HomeActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/HomeActivity.kt new file mode 100644 index 0000000..ff2b4e2 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/HomeActivity.kt @@ -0,0 +1,93 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.core.content.ContextCompat +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.SharedPrefDataRepository +import org.calypsonet.keyple.demo.reload.remote.data.model.DeviceEnum +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityHomeBinding + +class HomeActivity : AbstractDemoActivity() { + + private lateinit var activityHomeBinding: ActivityHomeBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityHomeBinding = ActivityHomeBinding.inflate(layoutInflater) + toolbarBinding = activityHomeBinding.appBarLayout + setContentView(activityHomeBinding.root) + if (intent.getBooleanExtra(CHOOSE_DEVICE_FOR_PERSO, false)) { + activityHomeBinding.chooseDeviceTv.append(" ") + activityHomeBinding.chooseDeviceTv.append(getString(R.string.to_be_personalized)) + } + toolbarBinding.menuBtn.visibility = View.VISIBLE + toolbarBinding.menuBtn.setOnClickListener { + startActivity(Intent(this, SettingsMenuActivity::class.java)) + } + } + + override fun onResume() { + super.onResume() + setupBtn( + activityHomeBinding.contactlessCardBtn, + prefData.loadContactlessConfigurationVisibility(), + DeviceEnum.CONTACTLESS_CARD) + setupBtn( + activityHomeBinding.simCardBtn, prefData.loadSimConfigurationVisibility(), DeviceEnum.SIM) + setupBtn( + activityHomeBinding.wearableBtn, + prefData.loadWearableConfigurationVisibility(), + DeviceEnum.WEARABLE) + setupBtn( + activityHomeBinding.embeddedElemBtn, + prefData.loadEmbeddedConfigurationVisibility(), + DeviceEnum.EMBEDDED) + } + + private fun setupBtn( + btn: View, + visibility: SharedPrefDataRepository.Companion.Visibility, + type: DeviceEnum + ) { + btn.setOnClickListener { + prefData.saveDeviceType(type.toString()) + if (intent.getBooleanExtra(CHOOSE_DEVICE_FOR_PERSO, false)) { + intent.putExtras(intent) + startActivity(Intent(this, PersonalizationActivity::class.java)) + this.finish() + } else startActivity(Intent(this, CardReaderActivity::class.java)) + } + when (visibility) { + SharedPrefDataRepository.Companion.Visibility.ENABLE -> { + btn.visibility = View.VISIBLE + btn.background = ContextCompat.getDrawable(this, R.drawable.white_card) + btn.isEnabled = true + } + SharedPrefDataRepository.Companion.Visibility.DISABLE -> { + btn.visibility = View.VISIBLE + btn.background = ContextCompat.getDrawable(this, R.drawable.grey_card) + btn.isEnabled = false + } + SharedPrefDataRepository.Companion.Visibility.HIDE -> { + btn.visibility = View.GONE + } + } + } + + companion object { + const val CHOOSE_DEVICE_FOR_PERSO = "CHOOSE_DEVICE_FOR_PERSO" + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/MainActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/MainActivity.kt new file mode 100644 index 0000000..5509c0b --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/MainActivity.kt @@ -0,0 +1,47 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import dagger.android.support.DaggerAppCompatActivity +import java.util.Timer +import java.util.TimerTask +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityMainBinding + +class MainActivity : DaggerAppCompatActivity() { + + private lateinit var activityMainBinding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + // Make sure this is before calling super.onCreate + super.onCreate(savedInstanceState) + activityMainBinding = ActivityMainBinding.inflate(layoutInflater) + setContentView(activityMainBinding.root) + // Wait for Wizway Device to be connected + Timer() + .schedule( + object : TimerTask() { + override fun run() { + if (!isFinishing) { + startActivity(Intent(applicationContext, HomeActivity::class.java)) + finish() + } + } + }, + SPLASH_MAX_DELAY_MS.toLong()) + } + + companion object { + private const val SPLASH_MAX_DELAY_MS = 6000 + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/PaymentValidatedActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/PaymentValidatedActivity.kt new file mode 100644 index 0000000..9d6bada --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/PaymentValidatedActivity.kt @@ -0,0 +1,43 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityPaymentValidatedBinding + +class PaymentValidatedActivity : AbstractDemoActivity() { + + private lateinit var activityPaymentValidatedBinding: ActivityPaymentValidatedBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityPaymentValidatedBinding = ActivityPaymentValidatedBinding.inflate(layoutInflater) + toolbarBinding = activityPaymentValidatedBinding.appBarLayout + setContentView(activityPaymentValidatedBinding.root) + activityPaymentValidatedBinding.chargeBtn.text = getString(R.string.load_card) + activityPaymentValidatedBinding.chargeBtn.setOnClickListener { + val intent = Intent(this, ReloadActivity::class.java) + intent.putExtras(getIntent()) + startActivity(intent) + this@PaymentValidatedActivity.finish() + } + } + + override fun onResume() { + super.onResume() + activityPaymentValidatedBinding.animation.setAnimation("tick_anim.json") + activityPaymentValidatedBinding.animation.repeatCount = 0 + activityPaymentValidatedBinding.animation.playAnimation() + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/PersonalizationActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/PersonalizationActivity.kt new file mode 100644 index 0000000..d2cee13 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/PersonalizationActivity.kt @@ -0,0 +1,153 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import android.view.View +import java.lang.Exception +import java.lang.IllegalStateException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.calypsonet.keyple.demo.common.constant.RemoteServiceId +import org.calypsonet.keyple.demo.common.dto.SelectAppAndPersonalizeCardInputDto +import org.calypsonet.keyple.demo.common.dto.SelectAppAndPersonalizeCardOutputDto +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.model.CardReaderResponse +import org.calypsonet.keyple.demo.reload.remote.data.model.DeviceEnum +import org.calypsonet.keyple.demo.reload.remote.data.model.Status +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityPersonalizationBinding +import org.calypsonet.keyple.demo.reload.remote.di.scopes.ActivityScoped +import org.eclipse.keypop.reader.CardReaderEvent +import timber.log.Timber + +@ActivityScoped +class PersonalizationActivity : AbstractCardActivity() { + + private lateinit var activityPersonalizationBinding: ActivityPersonalizationBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityPersonalizationBinding = ActivityPersonalizationBinding.inflate(layoutInflater) + toolbarBinding = activityPersonalizationBinding.appBarLayout + setContentView(activityPersonalizationBinding.root) + } + + override fun initReaders() { + try { + if (DeviceEnum.getDeviceEnum(prefData.loadDeviceType()!!) == DeviceEnum.CONTACTLESS_CARD) { + showPresentNfcCardInstructions() + initAndActivateAndroidKeypleNfcReader() + } else { + showNowPersonalizingInformation() + initOmapiReader { GlobalScope.launch { remoteServiceExecution(selectedDeviceReaderName) } } + } + } catch (e: Exception) { + Timber.e(e) + } + } + + override fun onPause() { + activityPersonalizationBinding.cardAnimation.cancelAnimation() + activityPersonalizationBinding.loadingAnimation.cancelAnimation() + try { + if (DeviceEnum.getDeviceEnum(prefData.loadDeviceType()!!) == DeviceEnum.CONTACTLESS_CARD) { + deactivateAndClearAndroidKeypleNfcReader() + } else { + deactivateAndClearOmapiReader() + } + } catch (e: Exception) { + Timber.e(e) + } + super.onPause() + } + + private fun showPresentNfcCardInstructions() { + activityPersonalizationBinding.presentTxt.text = + getString(R.string.present_card_personalization) + activityPersonalizationBinding.cardAnimation.visibility = View.VISIBLE + activityPersonalizationBinding.cardAnimation.playAnimation() + activityPersonalizationBinding.loadingAnimation.cancelAnimation() + activityPersonalizationBinding.loadingAnimation.visibility = View.INVISIBLE + } + + private fun showNowPersonalizingInformation() { + activityPersonalizationBinding.presentTxt.text = getString(R.string.personalization_in_progress) + activityPersonalizationBinding.loadingAnimation.visibility = View.VISIBLE + activityPersonalizationBinding.loadingAnimation.playAnimation() + activityPersonalizationBinding.cardAnimation.cancelAnimation() + activityPersonalizationBinding.cardAnimation.visibility = View.INVISIBLE + } + + override fun changeDisplay( + cardReaderResponse: CardReaderResponse, + applicationSerialNumber: String?, + finishActivity: Boolean? + ) { + val intent = Intent(this, ReloadResultActivity::class.java) + intent.putExtra(ReloadResultActivity.IS_PERSONALIZATION_RESULT, true) + intent.putExtra(ReloadResultActivity.STATUS, cardReaderResponse.status.name) + intent.putExtra(ReloadResultActivity.MESSAGE, cardReaderResponse.errorMessage) + startActivity(intent) + if (finishActivity == true) { + finish() + } + } + + override fun onReaderEvent(event: CardReaderEvent?) { + if (event?.type == CardReaderEvent.Type.CARD_INSERTED) { + runOnUiThread { showNowPersonalizingInformation() } + GlobalScope.launch { remoteServiceExecution(selectedDeviceReaderName) } + } + } + + private suspend fun remoteServiceExecution( + selectedDeviceReaderName: String, + ) { + withContext(Dispatchers.IO) { + try { + val selectAppAndPersonalizeCardInputDto = SelectAppAndPersonalizeCardInputDto(pluginType) + val selectAppAndPersonalizeCardOutputDto = + localServiceClient.executeRemoteService( + RemoteServiceId.SELECT_APP_AND_PERSONALIZE_CARD.name, + selectedDeviceReaderName, + null, + selectAppAndPersonalizeCardInputDto, + SelectAppAndPersonalizeCardOutputDto::class.java) + when (selectAppAndPersonalizeCardOutputDto.statusCode) { + 0 -> { + runOnUiThread { + changeDisplay( + CardReaderResponse(Status.SUCCESS, "", 0, arrayListOf(), arrayListOf(), ""), + applicationSerialNumber = "", + finishActivity = true) + } + } // success, + 1 -> { + launchServerErrorResponse() + } // server not ready, + 2 -> { + launchInvalidCardResponse(selectAppAndPersonalizeCardOutputDto.message) + } // card rejected + } + } catch (e: IllegalStateException) { + Timber.e(e) + launchInvalidCardResponse(e.message!!) + } catch (e: Exception) { + Timber.e(e) + launchExceptionResponse(e) + } + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ReloadActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ReloadActivity.kt new file mode 100644 index 0000000..4ff2e80 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ReloadActivity.kt @@ -0,0 +1,163 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import android.view.View +import java.lang.Exception +import java.lang.IllegalStateException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.calypsonet.keyple.demo.common.constant.RemoteServiceId +import org.calypsonet.keyple.demo.common.dto.SelectAppAndLoadContractInputDto +import org.calypsonet.keyple.demo.common.dto.SelectAppAndLoadContractOutputDto +import org.calypsonet.keyple.demo.common.model.type.PriorityCode +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.model.CardReaderResponse +import org.calypsonet.keyple.demo.reload.remote.data.model.DeviceEnum +import org.calypsonet.keyple.demo.reload.remote.data.model.Status +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityCardReaderBinding +import org.calypsonet.keyple.demo.reload.remote.di.scopes.ActivityScoped +import org.eclipse.keypop.reader.CardReaderEvent +import timber.log.Timber + +@ActivityScoped +class ReloadActivity : AbstractCardActivity() { + + private lateinit var activityCardReaderBinding: ActivityCardReaderBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityCardReaderBinding = ActivityCardReaderBinding.inflate(layoutInflater) + toolbarBinding = activityCardReaderBinding.appBarLayout + setContentView(activityCardReaderBinding.root) + } + + override fun initReaders() { + try { + if (DeviceEnum.getDeviceEnum(prefData.loadDeviceType()!!) == DeviceEnum.CONTACTLESS_CARD) { + showPresentNfcCardInstructions() + initAndActivateAndroidKeypleNfcReader() + } else { + showNowLoadingInformation() + initOmapiReader { + GlobalScope.launch { remoteServiceExecution(selectedDeviceReaderName, pluginType) } + } + } + } catch (e: Exception) { + Timber.e(e) + } + } + + override fun onPause() { + activityCardReaderBinding.cardAnimation.cancelAnimation() + activityCardReaderBinding.loadingAnimation.cancelAnimation() + try { + if (DeviceEnum.getDeviceEnum(prefData.loadDeviceType()!!) == DeviceEnum.CONTACTLESS_CARD) { + deactivateAndClearAndroidKeypleNfcReader() + } + } catch (e: Exception) { + Timber.e(e) + } + super.onPause() + } + + override fun onReaderEvent(event: CardReaderEvent?) { + if (event?.type == CardReaderEvent.Type.CARD_INSERTED) { + runOnUiThread { showNowLoadingInformation() } + GlobalScope.launch { remoteServiceExecution(selectedDeviceReaderName, pluginType) } + } + } + + private suspend fun remoteServiceExecution(selectedDeviceReaderName: String, pluginType: String) { + withContext(Dispatchers.IO) { + try { + val readCardSerialNumber = intent.getStringExtra(CARD_APPLICATION_NUMBER) + val contractTariff = + PriorityCode.findEnumByKey( + intent.getIntExtra(SelectTicketsActivity.SELECTED_TICKET_PRIORITY_CODE, 0)) + val ticketToBeLoaded = intent.getIntExtra(SelectTicketsActivity.TICKETS_NUMBER, 0) + + val selectAppAndLoadContractInputDto = + SelectAppAndLoadContractInputDto( + readCardSerialNumber!!, contractTariff, ticketToBeLoaded, pluginType) + + val selectAppAndLoadContractOutputDto = + localServiceClient.executeRemoteService( + RemoteServiceId.SELECT_APP_AND_LOAD_CONTRACT.name, + selectedDeviceReaderName, + null, + selectAppAndLoadContractInputDto, + SelectAppAndLoadContractOutputDto::class.java) + + when (selectAppAndLoadContractOutputDto.statusCode) { + 0 -> { + runOnUiThread { + changeDisplay( + CardReaderResponse( + Status.SUCCESS, "", ticketToBeLoaded, arrayListOf(), arrayListOf(), ""), + finishActivity = true) + } + } + 1 -> { + launchServerErrorResponse() + } // server not ready, + 2 -> { + launchInvalidCardResponse(selectAppAndLoadContractOutputDto.message) + } // card rejected + } + } catch (e: IllegalStateException) { + Timber.e(e) + launchInvalidCardResponse(e.message!!) + } catch (e: Exception) { + Timber.e(e) + launchExceptionResponse(e) + } + } + } + + override fun changeDisplay( + cardReaderResponse: CardReaderResponse, + applicationSerialNumber: String?, + finishActivity: Boolean? + ) { + activityCardReaderBinding.loadingAnimation.cancelAnimation() + activityCardReaderBinding.cardAnimation.cancelAnimation() + val intent = Intent(this, ReloadResultActivity::class.java) + intent.putExtra(ReloadResultActivity.TICKETS_NUMBER, 0) + intent.putExtra(ReloadResultActivity.STATUS, cardReaderResponse.status.toString()) + intent.putExtra(ReloadResultActivity.MESSAGE, cardReaderResponse.errorMessage) + startActivity(intent) + if (finishActivity == true) { + finish() + } + } + + private fun showPresentNfcCardInstructions() { + activityCardReaderBinding.presentTxt.text = getString(R.string.present_card) + activityCardReaderBinding.cardAnimation.visibility = View.VISIBLE + activityCardReaderBinding.cardAnimation.playAnimation() + activityCardReaderBinding.loadingAnimation.cancelAnimation() + activityCardReaderBinding.loadingAnimation.visibility = View.INVISIBLE + } + + private fun showNowLoadingInformation() { + activityCardReaderBinding.presentTxt.text = getString(R.string.loading_in_progress) + activityCardReaderBinding.loadingAnimation.visibility = View.VISIBLE + activityCardReaderBinding.loadingAnimation.playAnimation() + activityCardReaderBinding.cardAnimation.cancelAnimation() + activityCardReaderBinding.cardAnimation.visibility = View.INVISIBLE + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ReloadResultActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ReloadResultActivity.kt new file mode 100644 index 0000000..b4ff7ea --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ReloadResultActivity.kt @@ -0,0 +1,116 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.media.MediaPlayer +import android.os.Bundle +import android.view.View +import com.airbnb.lottie.LottieDrawable +import java.util.Timer +import java.util.TimerTask +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.model.Status +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityChargeResultBinding + +class ReloadResultActivity : AbstractDemoActivity() { + + private val timer = Timer() + private lateinit var activityChargeResultBinding: ActivityChargeResultBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityChargeResultBinding = ActivityChargeResultBinding.inflate(layoutInflater) + toolbarBinding = activityChargeResultBinding.appBarLayout + setContentView(activityChargeResultBinding.root) + toolbarBinding.toolbarLogo.setImageResource(R.drawable.ic_logo_white) + + val status = Status.getStatus(intent.getStringExtra(STATUS)) + + activityChargeResultBinding.tryBtn.setOnClickListener { onBackPressed() } + activityChargeResultBinding.cancelBtn.setOnClickListener { + val intent = Intent(this, HomeActivity::class.java) + startActivity(intent) + } + + when (status) { + Status.LOADING -> { + activityChargeResultBinding.animation.setAnimation("loading_anim.json") + activityChargeResultBinding.animation.repeatCount = LottieDrawable.INFINITE + activityChargeResultBinding.bigText.visibility = View.INVISIBLE + activityChargeResultBinding.btnLayout.visibility = View.INVISIBLE + } + Status.SUCCESS -> { + activityChargeResultBinding.mainBackground.setBackgroundColor( + resources.getColor(R.color.green)) + activityChargeResultBinding.animation.setAnimation("tick_white.json") + activityChargeResultBinding.animation.repeatCount = 0 + activityChargeResultBinding.animation.playAnimation() + activityChargeResultBinding.bigText.setText(R.string.charging_success_label) + activityChargeResultBinding.bigText.visibility = View.VISIBLE + activityChargeResultBinding.btnLayout.visibility = View.INVISIBLE + + if (intent.getBooleanExtra(IS_PERSONALIZATION_RESULT, false)) { + activityChargeResultBinding.bigText.setText(R.string.perso_success_label) + } else { + activityChargeResultBinding.bigText.setText(R.string.charging_success_label) + } + + Intent(this, HomeActivity::class.java) + timer.schedule( + object : TimerTask() { + override fun run() { + runOnUiThread { this@ReloadResultActivity.finish() } + } + }, + RETURN_DELAY_MS.toLong()) + } + else -> { + activityChargeResultBinding.mainBackground.setBackgroundColor( + resources.getColor(R.color.red)) + activityChargeResultBinding.animation.setAnimation("error_white.json") + activityChargeResultBinding.animation.repeatCount = 0 + activityChargeResultBinding.animation.playAnimation() + + val message = intent.getStringExtra(MESSAGE) + if (intent.getBooleanExtra(IS_PERSONALIZATION_RESULT, false)) { + activityChargeResultBinding.bigText.setText(R.string.perso_failed_label) + activityChargeResultBinding.bigText.append(":\n") + activityChargeResultBinding.bigText.append(message) + } else { + activityChargeResultBinding.bigText.setText(R.string.transaction_cancelled_label) + activityChargeResultBinding.bigText.append(":\n") + activityChargeResultBinding.bigText.append(message) + } + activityChargeResultBinding.bigText.visibility = View.VISIBLE + activityChargeResultBinding.btnLayout.visibility = View.VISIBLE + } + } + + // Play sound + val mp: MediaPlayer = MediaPlayer.create(this, R.raw.reading_sound) + mp.start() + } + + override fun onPause() { + super.onPause() + timer.cancel() + } + + companion object { + private const val RETURN_DELAY_MS = 5000 + const val TICKETS_NUMBER = "ticketsNumber" + const val STATUS = "status" + const val MESSAGE = "message" + const val IS_PERSONALIZATION_RESULT = "isPersonalizationResult" + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/SelectTicketsActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/SelectTicketsActivity.kt new file mode 100644 index 0000000..4acba08 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/SelectTicketsActivity.kt @@ -0,0 +1,79 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import org.calypsonet.keyple.demo.common.model.type.PriorityCode +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivitySelectTicketsBinding + +class SelectTicketsActivity : AbstractDemoActivity() { + private lateinit var activitySelectTicketsBinding: ActivitySelectTicketsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + activitySelectTicketsBinding = ActivitySelectTicketsBinding.inflate(layoutInflater) + toolbarBinding = activitySelectTicketsBinding.appBarLayout + setContentView(activitySelectTicketsBinding.root) + + activitySelectTicketsBinding.ticket1Label.text = + resources.getQuantityString(R.plurals.x_tickets, 1, 1) + activitySelectTicketsBinding.ticket2Label.text = + resources.getQuantityString(R.plurals.x_tickets, 2, 2) + activitySelectTicketsBinding.ticket3Label.text = + resources.getQuantityString(R.plurals.x_tickets, 3, 3) + activitySelectTicketsBinding.ticket4Label.text = + resources.getQuantityString(R.plurals.x_tickets, 4, 4) + activitySelectTicketsBinding.ticket1Price.text = getString(R.string.ticket_price, 1) + activitySelectTicketsBinding.ticket2Price.text = getString(R.string.ticket_price, 2) + activitySelectTicketsBinding.ticket3Price.text = getString(R.string.ticket_price, 3) + activitySelectTicketsBinding.ticket4Price.text = getString(R.string.ticket_price, 4) + activitySelectTicketsBinding.seasonPassPrice.text = getString(R.string.ticket_price, 20) + + activitySelectTicketsBinding.ticket1Btn.setOnClickListener { + startCheckoutActivity(PriorityCode.MULTI_TRIP, 1) + } + activitySelectTicketsBinding.ticket2Btn.setOnClickListener { + startCheckoutActivity(PriorityCode.MULTI_TRIP, 2) + } + activitySelectTicketsBinding.ticket3Btn.setOnClickListener { + startCheckoutActivity(PriorityCode.MULTI_TRIP, 3) + } + activitySelectTicketsBinding.ticket4Btn.setOnClickListener { + startCheckoutActivity(PriorityCode.MULTI_TRIP, 4) + } + + activitySelectTicketsBinding.seasonPassBtn.setOnClickListener { + startCheckoutActivity(PriorityCode.SEASON_PASS) + } + } + + private fun startCheckoutActivity(priorityCode: PriorityCode, ticketNumber: Int? = null) { + val intent = Intent(this, CheckoutActivity::class.java) + intent.putExtra(SELECTED_TICKET_PRIORITY_CODE, priorityCode.key) + if (ticketNumber != null) { + intent.putExtra(TICKETS_NUMBER, ticketNumber) + } + getIntent().getStringExtra(AbstractCardActivity.CARD_APPLICATION_NUMBER)?.let { + intent.putExtra(AbstractCardActivity.CARD_APPLICATION_NUMBER, it) + } + startActivity(intent) + this@SelectTicketsActivity.finish() + } + + companion object { + const val SELECTED_TICKET_PRIORITY_CODE = "SELECTED_TICKET_PRIORITY_CODE" + const val TICKETS_NUMBER = "TICKETS_NUMBER" + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ServerSettingsActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ServerSettingsActivity.kt new file mode 100644 index 0000000..9864ae1 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/ServerSettingsActivity.kt @@ -0,0 +1,140 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import android.util.Patterns +import android.view.View +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import java.lang.NumberFormatException +import kotlin.system.exitProcess +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.network.KeypleSyncEndPointClient +import org.calypsonet.keyple.demo.reload.remote.data.network.RestClient +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityServerSettingsBinding +import org.calypsonet.keyple.demo.reload.remote.di.scopes.ActivityScoped +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import timber.log.Timber + +@ActivityScoped +class ServerSettingsActivity : AbstractDemoActivity() { + + private lateinit var disposables: CompositeDisposable + private lateinit var activityServerSettingsBinding: ActivityServerSettingsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityServerSettingsBinding = ActivityServerSettingsBinding.inflate(layoutInflater) + toolbarBinding = activityServerSettingsBinding.appBarLayout + setContentView(activityServerSettingsBinding.root) + + disposables = CompositeDisposable() + + activityServerSettingsBinding.serverIpEdit.text.append(prefData.loadServerIP() ?: "") + activityServerSettingsBinding.serverPortEdit.text.append(prefData.loadServerPort().toString()) + activityServerSettingsBinding.serverProtocolEdit.text.append(prefData.loadServerProtocol()) + + activityServerSettingsBinding.restart.setOnClickListener { + if (validateEntries(true)) restartApp() + } + + activityServerSettingsBinding.pingBtn.setOnClickListener { + if (validateEntries(false)) { + val serverUrl = + activityServerSettingsBinding.serverProtocolEdit.text.toString() + + activityServerSettingsBinding.serverIpEdit.text.toString() + + ":" + + activityServerSettingsBinding.serverPortEdit.text.toString() + Timber.i("Loaded Rest client with URL: $serverUrl") + val testKeypleEndpointClient = + KeypleSyncEndPointClient( + Retrofit.Builder() + .baseUrl(serverUrl) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(ScalarsConverterFactory.create()) + .build() + .create(RestClient::class.java)) + + disposables.add( + testKeypleEndpointClient + .ping() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { + activityServerSettingsBinding.pingProgressBar.visibility = View.VISIBLE + activityServerSettingsBinding.pingResultText.visibility = View.INVISIBLE + } + .subscribe( + { + activityServerSettingsBinding.pingProgressBar.visibility = View.INVISIBLE + activityServerSettingsBinding.pingResultText.visibility = View.VISIBLE + activityServerSettingsBinding.pingResultText.text = + getString(R.string.ping_success) + }, + { + Timber.e(it) + activityServerSettingsBinding.pingProgressBar.visibility = View.INVISIBLE + activityServerSettingsBinding.pingResultText.visibility = View.VISIBLE + activityServerSettingsBinding.pingResultText.text = + getString(R.string.ping_failed) + })) + } + } + } + + override fun onDestroy() { + disposables.clear() + super.onDestroy() + } + + private fun restartApp() { + startActivity(Intent(applicationContext, MainActivity::class.java)) + exitProcess(0) + } + + private fun validateEntries(saveOnSuccess: Boolean): Boolean { + with(activityServerSettingsBinding.serverIpEdit) { + if (this.text.isNotBlank() && Patterns.IP_ADDRESS.matcher(this.text.toString()).matches()) { + if (saveOnSuccess) prefData.saveServerIP(this.text.toString()) + } else { + this.error = "Please set a valid IP" + return false + } + } + with(activityServerSettingsBinding.serverPortEdit) { + try { + if (this.text.isNotBlank()) { + if (saveOnSuccess) prefData.saveServerPort(Integer.valueOf(this.text.toString())) + } else { + throw NumberFormatException() + } + } catch (e: NumberFormatException) { + this.error = "Please set a valid Port" + return false + } + } + with(activityServerSettingsBinding.serverProtocolEdit) { + if (this.text.isNotBlank() && arrayOf("http://", "https://").contains(this.text.toString())) { + if (saveOnSuccess) prefData.saveServerProtocol(this.text.toString()) + } else { + this.error = "Please set a valid Protocol" + return false + } + } + return true + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/SettingsMenuActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/SettingsMenuActivity.kt new file mode 100644 index 0000000..9a424b2 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/SettingsMenuActivity.kt @@ -0,0 +1,48 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui + +import android.content.Intent +import android.os.Bundle +import org.calypsonet.keyple.demo.reload.remote.BuildConfig +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivitySettingsMenuBinding + +class SettingsMenuActivity : AbstractDemoActivity() { + private lateinit var activitySettingsMenuBinding: ActivitySettingsMenuBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activitySettingsMenuBinding = ActivitySettingsMenuBinding.inflate(layoutInflater) + toolbarBinding = activitySettingsMenuBinding.appBarLayout + setContentView(activitySettingsMenuBinding.root) + + activitySettingsMenuBinding.serverBtn.setOnClickListener { + val intent = Intent(this, ServerSettingsActivity::class.java) + startActivity(intent) + } + + activitySettingsMenuBinding.configurationBtn.setOnClickListener { + val intent = Intent(this, ConfigurationSettingsActivity::class.java) + startActivity(intent) + } + + activitySettingsMenuBinding.personalizationBtn.setOnClickListener { + val intent = Intent(this, HomeActivity::class.java) + intent.putExtra(HomeActivity.CHOOSE_DEVICE_FOR_PERSO, true) + startActivity(intent) + } + + activitySettingsMenuBinding.versionName.text = + getString(R.string.version, BuildConfig.VERSION_NAME) + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/cardsummary/CardSummaryActivity.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/cardsummary/CardSummaryActivity.kt new file mode 100644 index 0000000..986aaf9 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/cardsummary/CardSummaryActivity.kt @@ -0,0 +1,123 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui.cardsummary + +import android.content.Intent +import android.media.MediaPlayer +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import org.calypsonet.keyple.demo.reload.remote.R +import org.calypsonet.keyple.demo.reload.remote.data.model.CardReaderResponse +import org.calypsonet.keyple.demo.reload.remote.data.model.Status +import org.calypsonet.keyple.demo.reload.remote.databinding.ActivityCardSummaryBinding +import org.calypsonet.keyple.demo.reload.remote.ui.AbstractCardActivity +import org.calypsonet.keyple.demo.reload.remote.ui.AbstractDemoActivity +import org.calypsonet.keyple.demo.reload.remote.ui.SelectTicketsActivity + +class CardSummaryActivity : AbstractDemoActivity() { + + private lateinit var titleLinearLayoutManager: LinearLayoutManager + private lateinit var titlesAdapter: TitlesRecyclerAdapter + private lateinit var activityCardSummaryBinding: ActivityCardSummaryBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityCardSummaryBinding = ActivityCardSummaryBinding.inflate(layoutInflater) + toolbarBinding = activityCardSummaryBinding.appBarLayout + setContentView(activityCardSummaryBinding.root) + + val cardContent: CardReaderResponse = + intent.getParcelableExtra(AbstractCardActivity.CARD_CONTENT)!! + + titleLinearLayoutManager = LinearLayoutManager(this) + activityCardSummaryBinding.titlesList.layoutManager = titleLinearLayoutManager + + titlesAdapter = TitlesRecyclerAdapter(cardContent.titlesList) + activityCardSummaryBinding.titlesList.adapter = titlesAdapter + + when (cardContent.status) { + Status.INVALID_CARD -> { + activityCardSummaryBinding.animation.setAnimation("error_orange_anim.json") + activityCardSummaryBinding.animation.playAnimation() + activityCardSummaryBinding.bigText.setText(R.string.card_invalid_label) + activityCardSummaryBinding.bigText.setTextColor(resources.getColor(R.color.orange)) + activityCardSummaryBinding.smallDesc.text = cardContent.errorMessage + activityCardSummaryBinding.smallDesc.setTextColor(resources.getColor(R.color.orange)) + activityCardSummaryBinding.buyBtn.visibility = View.INVISIBLE + activityCardSummaryBinding.titlesList.visibility = View.GONE + activityCardSummaryBinding.lastValidationContent.visibility = View.GONE + activityCardSummaryBinding.contentTitle.visibility = View.GONE + } + Status.TICKETS_FOUND, + Status.SUCCESS -> { + activityCardSummaryBinding.titlesList.visibility = View.VISIBLE + activityCardSummaryBinding.animation.visibility = View.GONE + activityCardSummaryBinding.bigText.visibility = View.GONE + activityCardSummaryBinding.smallDesc.visibility = View.INVISIBLE + activityCardSummaryBinding.buyBtn.visibility = View.VISIBLE + activityCardSummaryBinding.lastValidationContent.visibility = View.VISIBLE + activityCardSummaryBinding.contentTitle.visibility = View.VISIBLE + } + Status.EMPTY_CARD -> { + activityCardSummaryBinding.animation.setAnimation("error_anim.json") + activityCardSummaryBinding.animation.playAnimation() + activityCardSummaryBinding.bigText.text = getString(R.string.no_valid_label) + activityCardSummaryBinding.bigText.setTextColor(resources.getColor(R.color.red)) + activityCardSummaryBinding.smallDesc.visibility = View.VISIBLE + activityCardSummaryBinding.smallDesc.setTextColor(resources.getColor(R.color.red)) + activityCardSummaryBinding.smallDesc.text = getString(R.string.no_valid_desc) + activityCardSummaryBinding.buyBtn.visibility = View.VISIBLE + activityCardSummaryBinding.titlesList.visibility = View.GONE + activityCardSummaryBinding.lastValidationContent.visibility = View.VISIBLE + activityCardSummaryBinding.contentTitle.visibility = View.GONE + } + Status.ERROR -> { + activityCardSummaryBinding.animation.setAnimation("error_anim.json") + activityCardSummaryBinding.animation.playAnimation() + if (cardContent.errorMessage != null) + activityCardSummaryBinding.bigText.text = cardContent.errorMessage + else activityCardSummaryBinding.bigText.setText(R.string.error_label) + activityCardSummaryBinding.bigText.setTextColor(resources.getColor(R.color.red)) + activityCardSummaryBinding.smallDesc.visibility = View.INVISIBLE + activityCardSummaryBinding.buyBtn.visibility = View.INVISIBLE + activityCardSummaryBinding.titlesList.visibility = View.GONE + activityCardSummaryBinding.lastValidationContent.visibility = View.GONE + activityCardSummaryBinding.contentTitle.visibility = View.GONE + } + else -> { + activityCardSummaryBinding.animation.setAnimation("error_anim.json") + activityCardSummaryBinding.animation.playAnimation() + activityCardSummaryBinding.bigText.setText(R.string.error_label) + activityCardSummaryBinding.bigText.setTextColor(resources.getColor(R.color.red)) + activityCardSummaryBinding.smallDesc.visibility = View.INVISIBLE + activityCardSummaryBinding.buyBtn.visibility = View.INVISIBLE + activityCardSummaryBinding.titlesList.visibility = View.GONE + activityCardSummaryBinding.lastValidationContent.visibility = View.GONE + activityCardSummaryBinding.contentTitle.visibility = View.GONE + } + } + activityCardSummaryBinding.animation.playAnimation() + + // Play sound + val mp: MediaPlayer = MediaPlayer.create(this, R.raw.reading_sound) + mp.start() + activityCardSummaryBinding.buyBtn.setOnClickListener { + val intent = Intent(this, SelectTicketsActivity::class.java) + getIntent().getStringExtra(AbstractCardActivity.CARD_APPLICATION_NUMBER)?.let { + intent.putExtra(AbstractCardActivity.CARD_APPLICATION_NUMBER, it) + } + startActivity(intent) + this@CardSummaryActivity.finish() + } + } +} diff --git a/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/cardsummary/TitlesRecyclerAdapter.kt b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/cardsummary/TitlesRecyclerAdapter.kt new file mode 100644 index 0000000..049e4c9 --- /dev/null +++ b/client/android-light/app/src/main/kotlin/org/calypsonet/keyple/demo/reload/remote/ui/cardsummary/TitlesRecyclerAdapter.kt @@ -0,0 +1,48 @@ +/* ************************************************************************************** + * Copyright (c) 2021 Calypso Networks Association https://calypsonet.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + ************************************************************************************** */ +package org.calypsonet.keyple.demo.reload.remote.ui.cardsummary + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.calypsonet.keyple.demo.reload.remote.data.model.CardTitle +import org.calypsonet.keyple.demo.reload.remote.databinding.TitleRecyclerRowBinding +import org.calypsonet.keyple.demo.reload.remote.inflate + +class TitlesRecyclerAdapter(private val titles: List) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TitleHolder { + val binding = + TitleRecyclerRowBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return TitleHolder(binding) + } + + class TitleHolder(private val binding: TitleRecyclerRowBinding) : + RecyclerView.ViewHolder(binding.root) { + + private var title: CardTitle? = null + + fun bindItem(title: CardTitle) { + this.title = title + binding.titleName.text = title.name + binding.titleDescription.text = title.description + } + } + + override fun getItemCount() = titles.size + + override fun onBindViewHolder(holder: TitleHolder, position: Int) { + val titleItem = titles[position] + holder.bindItem(titleItem) + } +} diff --git a/client/android-light/app/src/main/res/drawable-v24/ic_launcher_background.xml b/client/android-light/app/src/main/res/drawable-v24/ic_launcher_background.xml new file mode 100644 index 0000000..d1380fc --- /dev/null +++ b/client/android-light/app/src/main/res/drawable-v24/ic_launcher_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/btn_blue.xml b/client/android-light/app/src/main/res/drawable/btn_blue.xml new file mode 100644 index 0000000..1918b18 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/btn_blue.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/btn_blue_light.xml b/client/android-light/app/src/main/res/drawable/btn_blue_light.xml new file mode 100644 index 0000000..dda7c0e --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/btn_blue_light.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/config_radio_btn_checked.xml b/client/android-light/app/src/main/res/drawable/config_radio_btn_checked.xml new file mode 100644 index 0000000..8a06836 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/config_radio_btn_checked.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/config_radio_btn_selector.xml b/client/android-light/app/src/main/res/drawable/config_radio_btn_selector.xml new file mode 100644 index 0000000..d7cbc67 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/config_radio_btn_selector.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/config_radio_btn_unchecked.xml b/client/android-light/app/src/main/res/drawable/config_radio_btn_unchecked.xml new file mode 100644 index 0000000..4dcc3f5 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/config_radio_btn_unchecked.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/grey_card.xml b/client/android-light/app/src/main/res/drawable/grey_card.xml new file mode 100644 index 0000000..cb6a00c --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/grey_card.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_arrow_left.xml b/client/android-light/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 0000000..7abc3b5 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_arrow_right.xml b/client/android-light/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 0000000..47a219d --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_bracelet.xml b/client/android-light/app/src/main/res/drawable/ic_bracelet.xml new file mode 100644 index 0000000..cec53d2 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_bracelet.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_connection_success.xml b/client/android-light/app/src/main/res/drawable/ic_connection_success.xml new file mode 100644 index 0000000..762adcf --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_connection_success.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_connection_wait.xml b/client/android-light/app/src/main/res/drawable/ic_connection_wait.xml new file mode 100644 index 0000000..c6e9028 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_connection_wait.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_contactlesscard.xml b/client/android-light/app/src/main/res/drawable/ic_contactlesscard.xml new file mode 100644 index 0000000..56278f3 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_contactlesscard.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_cvv_help.xml b/client/android-light/app/src/main/res/drawable/ic_cvv_help.xml new file mode 100644 index 0000000..25bcd06 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_cvv_help.xml @@ -0,0 +1,5 @@ + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_embedded.xml b/client/android-light/app/src/main/res/drawable/ic_embedded.xml new file mode 100644 index 0000000..1330bb3 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_embedded.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_keyple_background_2.xml b/client/android-light/app/src/main/res/drawable/ic_keyple_background_2.xml new file mode 100644 index 0000000..69e335c --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_keyple_background_2.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_logo_blue.xml b/client/android-light/app/src/main/res/drawable/ic_logo_blue.xml new file mode 100644 index 0000000..157fc2f --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_logo_blue.xml @@ -0,0 +1,36 @@ + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_logo_calypso.xml b/client/android-light/app/src/main/res/drawable/ic_logo_calypso.xml new file mode 100644 index 0000000..78d2922 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_logo_calypso.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_logo_eclipse.xml b/client/android-light/app/src/main/res/drawable/ic_logo_eclipse.xml new file mode 100644 index 0000000..89cffcd --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_logo_eclipse.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_logo_white.xml b/client/android-light/app/src/main/res/drawable/ic_logo_white.xml new file mode 100644 index 0000000..1b209a1 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_logo_white.xml @@ -0,0 +1,36 @@ + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_menu.xml b/client/android-light/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000..2eb1fae --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_server.xml b/client/android-light/app/src/main/res/drawable/ic_server.xml new file mode 100644 index 0000000..41dd0e0 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_server.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_simcard.xml b/client/android-light/app/src/main/res/drawable/ic_simcard.xml new file mode 100644 index 0000000..6dbf053 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_simcard.xml @@ -0,0 +1,38 @@ + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/ic_tools.xml b/client/android-light/app/src/main/res/drawable/ic_tools.xml new file mode 100644 index 0000000..db794e0 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/ic_tools.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/client/android-light/app/src/main/res/drawable/logo_keyple.png b/client/android-light/app/src/main/res/drawable/logo_keyple.png new file mode 100644 index 0000000..68819b0 Binary files /dev/null and b/client/android-light/app/src/main/res/drawable/logo_keyple.png differ diff --git a/client/android-light/app/src/main/res/drawable/recycler_view_divider.xml b/client/android-light/app/src/main/res/drawable/recycler_view_divider.xml new file mode 100644 index 0000000..f35d5eb --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/recycler_view_divider.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/white_card.xml b/client/android-light/app/src/main/res/drawable/white_card.xml new file mode 100644 index 0000000..914ec90 --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/white_card.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/android-light/app/src/main/res/drawable/white_rounded_rect.xml b/client/android-light/app/src/main/res/drawable/white_rounded_rect.xml new file mode 100644 index 0000000..6318d0f --- /dev/null +++ b/client/android-light/app/src/main/res/drawable/white_rounded_rect.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/client/android-light/app/src/main/res/font/worksans_bold.ttf b/client/android-light/app/src/main/res/font/worksans_bold.ttf new file mode 100644 index 0000000..7718ada Binary files /dev/null and b/client/android-light/app/src/main/res/font/worksans_bold.ttf differ diff --git a/client/android-light/app/src/main/res/font/worksans_extrabold.ttf b/client/android-light/app/src/main/res/font/worksans_extrabold.ttf new file mode 100644 index 0000000..e571099 Binary files /dev/null and b/client/android-light/app/src/main/res/font/worksans_extrabold.ttf differ diff --git a/client/android-light/app/src/main/res/font/worksans_medium.ttf b/client/android-light/app/src/main/res/font/worksans_medium.ttf new file mode 100644 index 0000000..137cf56 Binary files /dev/null and b/client/android-light/app/src/main/res/font/worksans_medium.ttf differ diff --git a/client/android-light/app/src/main/res/font/worksans_regular.ttf b/client/android-light/app/src/main/res/font/worksans_regular.ttf new file mode 100644 index 0000000..ba11a2d Binary files /dev/null and b/client/android-light/app/src/main/res/font/worksans_regular.ttf differ diff --git a/client/android-light/app/src/main/res/font/worksans_semibold.ttf b/client/android-light/app/src/main/res/font/worksans_semibold.ttf new file mode 100644 index 0000000..5cefd7c Binary files /dev/null and b/client/android-light/app/src/main/res/font/worksans_semibold.ttf differ diff --git a/client/android-light/app/src/main/res/layout/activity_card_reader.xml b/client/android-light/app/src/main/res/layout/activity_card_reader.xml new file mode 100644 index 0000000..8d8a43f --- /dev/null +++ b/client/android-light/app/src/main/res/layout/activity_card_reader.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/android-light/app/src/main/res/layout/activity_card_summary.xml b/client/android-light/app/src/main/res/layout/activity_card_summary.xml new file mode 100644 index 0000000..2256d2f --- /dev/null +++ b/client/android-light/app/src/main/res/layout/activity_card_summary.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +