├── .github ├── pull_request_template.md └── workflows │ ├── master_build.yml │ └── pr_scan.yml ├── .gitignore ├── COPYRIGHT.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── app ├── .gitignore ├── build.gradle ├── build │ └── outputs │ │ └── apk │ │ └── debug │ │ ├── app-debug.apk │ │ └── dependencies.txt ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── circle │ │ └── w3s │ │ └── sample │ │ └── wallet │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── circle │ │ │ └── w3s │ │ │ └── sample │ │ │ └── wallet │ │ │ ├── CustomActivity.kt │ │ │ ├── ExecuteActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── PerformLoginActivity.kt │ │ │ ├── pwcustom │ │ │ ├── MyLayoutProvider.kt │ │ │ └── MyViewSetterProvider.kt │ │ │ ├── ui │ │ │ ├── NecessaryTextView.kt │ │ │ ├── alert │ │ │ │ └── AlertBar.kt │ │ │ └── main │ │ │ │ ├── ExecuteFormState.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── ResultFragment.kt │ │ │ │ └── tabs │ │ │ │ ├── ITabPage.kt │ │ │ │ ├── TabPageEmail.kt │ │ │ │ ├── TabPagePin.kt │ │ │ │ ├── TabPageSocial.kt │ │ │ │ └── TabPagerAdapter.kt │ │ │ └── util │ │ │ └── KeyboardUtils.kt │ └── res │ │ ├── drawable │ │ ├── activated_round_input_bg.xml │ │ ├── background_alert_negative_panel.xml │ │ ├── background_alert_positive_panel.xml │ │ ├── background_rounded_alert_panel.xml │ │ ├── background_rounded_login_btn.xml │ │ ├── background_rounded_login_btn_text.xml │ │ ├── background_rounded_panel.xml │ │ ├── background_rounded_panel_primary10_r12.xml │ │ ├── background_rounded_social_execute_btn.xml │ │ ├── background_rounded_tab_selected.xml │ │ ├── disabled_blue_text_color.xml │ │ ├── disabled_white_text_color.xml │ │ ├── ic_alert.xml │ │ ├── ic_alert_negative.xml │ │ ├── ic_alert_positive.xml │ │ ├── ic_apple.xml │ │ ├── ic_back.xml │ │ ├── ic_biometrics_general.xml │ │ ├── ic_checkmark.xml │ │ ├── ic_close.xml │ │ ├── ic_confirm_main_icon.png │ │ ├── ic_copy.xml │ │ ├── ic_currency_usdc.xml │ │ ├── ic_dropdown_arrow.xml │ │ ├── ic_error_info.xml │ │ ├── ic_facebook.xml │ │ ├── ic_fee_info.xml │ │ ├── ic_google.xml │ │ ├── ic_hide_pin.xml │ │ ├── ic_intro_item0_icon.png │ │ ├── ic_intro_item1_icon.png │ │ ├── ic_intro_item2_icon.png │ │ ├── ic_intro_main_icon.png │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_show_less_arrow.xml │ │ ├── ic_show_more_arrow.xml │ │ ├── ic_show_pin.xml │ │ ├── ic_small_biomatrics_general.xml │ │ ├── ic_social_execute.xml │ │ ├── ic_swipe.xml │ │ ├── icon_close.xml │ │ ├── no_focus_bg.xml │ │ ├── pressed_round_main_bt.xml │ │ ├── pressed_round_secondary_bt.xml │ │ ├── round_input_bg_focused.xml │ │ ├── tabs_background.xml │ │ └── tabs_selector.xml │ │ ├── font │ │ ├── inter_extra_light.ttf │ │ ├── inter_light.ttf │ │ ├── inter_medium.ttf │ │ ├── inter_regular.ttf │ │ └── inter_semi_bold.ttf │ │ ├── layout │ │ ├── activity_custom.xml │ │ ├── activity_custom_alert.xml │ │ ├── activity_main.xml │ │ ├── activity_social.xml │ │ ├── activity_social_execute.xml │ │ ├── fragment_main.xml │ │ ├── fragment_result.xml │ │ ├── layout_alert_snack_bar.xml │ │ ├── layout_copy_field.xml │ │ ├── layout_display_field.xml │ │ ├── layout_input_field.xml │ │ ├── layout_input_toggle.xml │ │ ├── pager_email.xml │ │ ├── pager_pin.xml │ │ └── pager_social.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── config.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── circle │ └── w3s │ └── sample │ └── wallet │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── readme_images ├── open_project.png ├── run_project.png ├── running_app.png ├── running_app_email.png ├── running_app_pin.png └── running_app_social.png └── settings.gradle /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | ## Detail 4 | 5 | ## Testing 6 | 7 | ## Documentation 8 | 9 | --- 10 | 11 | **Requested Reviewers:** @mention 12 | -------------------------------------------------------------------------------- /.github/workflows/master_build.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [master] 5 | permissions: 6 | contents: write 7 | jobs: 8 | dependencies: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-java@v4 13 | with: 14 | distribution: 'adopt' 15 | java-version: '17' 16 | - name: Setup Gradle to generate and submit dependency graphs 17 | uses: gradle/gradle-build-action@v2 18 | with: 19 | dependency-graph: generate-and-submit 20 | - name: Configure local.properties 21 | env: 22 | USERNAME: ${{ github.actor }} 23 | run: |- 24 | cat >local.properties < Sample app for integrating Circle Programmable Wallet SDK. 4 | 5 | - Bookmark 6 | - [Requirement](#requirement) 7 | - [Run the Sample App](#run-the-sample-app) 8 | --- 9 | 10 | 11 | ## Requirement 12 | 13 | 1. Java 17 is required for the sample app. 14 | 15 | ## Run the Sample App 16 | You can install [the latest APK](https://github.com/circlefin/w3s-android-sample-app-wallets/blob/master/app/build/outputs/apk/debug/app-debug.apk) or follow the instructions below to run on a device / emulator directly. 17 | 1. Open the project by [Android Studio](https://developer.android.com/studio): File ➜ Open ➜ choose the project root folder. 18 | 19 | drawing 20 | 21 | 2. Edit `values/config.xml` ➜ `pw_app_id` to fill in your `APP ID` 22 | 3. Add/Edit `local.properties` in the project's root with the following maven repository settings: 23 | ```properties 24 | pwsdk.maven.url=https://maven.pkg.github.com/circlefin/w3s-android-sdk 25 | pwsdk.maven.username= 26 | # Fine-grained personal access tokens or classic with package read permission. 27 | pwsdk.maven.password= 28 | ``` 29 | > **Note** 30 | > When pasting the values above for `` and ``, make sure to not surround the values with quotes. 31 | 32 | - Check the following links for creating PAT. 33 | - [Creating a personal access token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) 34 | - [Creating a fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) 35 | 36 | 4. If there's no error after Gradle sync, select a device and click `Debug 'app'`. 37 | 38 | drawing 39 | 40 | 5. There are three tabs corresponding to different login methods. Fill in the `App ID` and fill in the relevant fields in each tab according to the requirements of different login methods for following execution action. 41 | 42 | 43 | drawingdrawingdrawing 44 | 45 | 6. (Optional) Auth configs setup. If you want to use social login for test , please follow below steps to and Social login infos. 46 | - [Google and Facebook] Add/Edit value with a specific key-name in `strings.xml` (please refer to the sample strings.xml below) 47 | 48 | ```properties 49 | YOUR_GOOGLE_WEB_CLIENT_ID 50 | 51 | YOUR_FACEBOOK_APP_ID 52 | your_fb_protocol_scheme 53 | YOUR_FACEBOOK_CLIENT_TOKEN 54 | 55 | ``` 56 | - [Apple] Add your Apple `service-id` as manifestPlaceholders to app’s `build.gradle` 57 | 58 | ```properties 59 | android { 60 | 61 | defaultConfig { 62 | … 63 | … 64 | manifestPlaceholders = [appAuthRedirectScheme: 'YOUR_APPLE_SERVICE_ID'] 65 | } 66 | } 67 | 68 | 69 | ``` 70 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please do not file public issues on Github for security vulnerabilities. All security vulnerabilities should be reported to Circle privately, through Circle's [Vulnerability Disclosure Program](https://hackerone.com/circle). Please read through the program policy before submitting a report. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'androidx.navigation.safeargs.kotlin' 5 | id 'com.google.gms.google-services' 6 | } 7 | 8 | android { 9 | namespace 'com.circle.w3s.sample.wallet' 10 | compileSdk 34 11 | 12 | defaultConfig { 13 | applicationId "com.circle.w3s.sample.wallet" 14 | minSdk 24 15 | targetSdk 34 16 | versionCode 1 17 | versionName "1.0" 18 | 19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 20 | manifestPlaceholders = [appAuthRedirectScheme: 'YOUR_APPLE_SERVICE_ID'] 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled true 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | debug { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | buildFeatures { 41 | viewBinding true 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation "circle.programmablewallet:sdk:1.0.+" 47 | implementation 'androidx.core:core-ktx:1.8.0' 48 | implementation 'androidx.appcompat:appcompat:1.6.1' 49 | implementation 'com.google.android.material:material:1.5.0' 50 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 51 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.2' 52 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.2' 53 | implementation 'androidx.annotation:annotation:1.3.0' 54 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' 55 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 56 | testImplementation 'junit:junit:4.13.2' 57 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 58 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 59 | 60 | def nav_version = "2.5.3" 61 | implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" 62 | implementation "androidx.navigation:navigation-ui-ktx:$nav_version" 63 | implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" 64 | 65 | implementation platform('com.google.firebase:firebase-bom:32.7.0') 66 | implementation 'com.google.firebase:firebase-analytics' 67 | implementation 'com.google.android.gms:play-services-auth:19.2.0' 68 | 69 | //Apple login 70 | implementation 'net.openid:appauth:0.9.1' 71 | } -------------------------------------------------------------------------------- /app/build/outputs/apk/debug/app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/build/outputs/apk/debug/app-debug.apk -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). 24 | -keep,allowobfuscation,allowshrinking interface retrofit2.Call 25 | -keep,allowobfuscation,allowshrinking class retrofit2.Response 26 | 27 | # With R8 full mode generic signatures are stripped for classes that are not 28 | # kept. Suspend functions are wrapped in continuations where the type argument 29 | # is used. 30 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation -------------------------------------------------------------------------------- /app/src/androidTest/java/com/circle/w3s/sample/wallet/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Circle Technologies, LLC. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.circle.w3s.sample.wallet 16 | 17 | import androidx.test.platform.app.InstrumentationRegistry 18 | import androidx.test.ext.junit.runners.AndroidJUnit4 19 | 20 | import org.junit.Test 21 | import org.junit.runner.RunWith 22 | 23 | import org.junit.Assert.* 24 | 25 | @RunWith(AndroidJUnit4::class) 26 | class ExampleInstrumentedTest { 27 | @Test 28 | fun useAppContext() { 29 | // Context of the app under test. 30 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 31 | assertEquals("com.circle.w3s.sample.wallet", appContext.packageName) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 35 | 38 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/CustomActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet 18 | 19 | import android.os.Bundle 20 | import android.view.View 21 | import android.widget.TextView 22 | import androidx.appcompat.app.AppCompatActivity 23 | import circle.programmablewallet.sdk.WalletSdk 24 | 25 | class CustomActivity: AppCompatActivity() { 26 | companion object { 27 | const val ARG_MSG = "msg" 28 | } 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | setContentView(R.layout.activity_custom_alert) 33 | 34 | 35 | val msgTv = findViewById(R.id.msg) 36 | val btMain = findViewById(R.id.btMain) 37 | btMain.setOnClickListener { v: View? -> 38 | finish() 39 | } 40 | val b = intent.extras ?: return 41 | val msg = b.getString(ARG_MSG) 42 | if (msg != null) { 43 | msgTv.text = msg 44 | } 45 | } 46 | 47 | override fun onBackPressed() { 48 | finish() 49 | super.onBackPressed() 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ExecuteActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet 18 | 19 | import android.content.ClipData 20 | import android.content.ClipboardManager 21 | import android.os.Bundle 22 | import android.text.TextUtils 23 | import android.util.Log 24 | import android.view.View 25 | import androidx.appcompat.app.AppCompatActivity 26 | import androidx.core.widget.doAfterTextChanged 27 | import circle.programmablewallet.sdk.WalletSdk 28 | import circle.programmablewallet.sdk.api.ApiError 29 | import circle.programmablewallet.sdk.api.Callback 30 | import circle.programmablewallet.sdk.api.ExecuteWarning 31 | import circle.programmablewallet.sdk.result.ExecuteResult 32 | import com.circle.w3s.sample.wallet.databinding.ActivitySocialExecuteBinding 33 | import com.circle.w3s.sample.wallet.ui.alert.AlertBar 34 | 35 | 36 | class ExecuteActivity : AppCompatActivity(), View.OnClickListener, 37 | Callback { 38 | private val TAG: String = "APP.SocialExecuteActivity" 39 | private lateinit var binding: ActivitySocialExecuteBinding 40 | 41 | 42 | companion object { 43 | const val ARG_ENCRYPTION_KEY = "encryptionKey" 44 | const val ARG_USER_TOKEN = "userToken" 45 | } 46 | 47 | override fun onCreate(savedInstanceState: Bundle?) { 48 | super.onCreate(savedInstanceState) 49 | 50 | binding = ActivitySocialExecuteBinding.inflate(layoutInflater) 51 | setContentView(binding.root) 52 | 53 | binding.close.setOnClickListener(this) 54 | 55 | val extra = intent.extras 56 | val encryptionKey = extra?.getString(ARG_ENCRYPTION_KEY) 57 | val userToken = extra?.getString(ARG_USER_TOKEN) 58 | 59 | binding.labelEncryptionKey.setText(R.string.label_encryption_key) 60 | binding.encryptionKey.text = encryptionKey 61 | binding.userToken.title.setText(R.string.label_user_token) 62 | binding.userToken.content.text = userToken 63 | binding.userToken.content.setOnClickListener(this) 64 | binding.challengeId.inputTitle.setNecessary(true) 65 | binding.challengeId.inputTitle.setText(R.string.label_challenge_id) 66 | binding.challengeId.inputValue.doAfterTextChanged { 67 | if (!TextUtils.isEmpty(binding.challengeId.inputValue.text)) { 68 | binding.btMain.isEnabled = true 69 | } else { 70 | binding.btMain.isEnabled = false 71 | } 72 | } 73 | binding.btMain.isEnabled = false 74 | binding.btMain.setOnClickListener(this) 75 | 76 | // FIXME For test convenience. 77 | // binding.encryptionKey.setOnClickListener(this) 78 | // binding.challengeId.inputValue.setText(R.string.pw_challengeId) 79 | } 80 | 81 | private fun executeSocial() { 82 | WalletSdk.execute( 83 | this, 84 | binding.userToken.content.text.toString(), 85 | binding.encryptionKey.text.toString(), 86 | arrayOf(binding.challengeId.inputValue.text.toString()), 87 | this 88 | ) 89 | } 90 | 91 | override fun onBackPressed() { 92 | goBackToSdkUi() 93 | super.onBackPressed() 94 | } 95 | 96 | /** 97 | * Bring SDK UI to the front and finish the Activity. 98 | */ 99 | private fun goBackToSdkUi() { 100 | WalletSdk.moveTaskToFront(this) 101 | finish() 102 | } 103 | 104 | override fun onClick(v: View?) { 105 | v ?: return 106 | when (v.id) { 107 | R.id.close -> finish() 108 | R.id.encryptionKey -> copyEncryptionKey() 109 | R.id.content -> copyUserToken() 110 | R.id.btMain -> executeSocial() 111 | } 112 | } 113 | 114 | private fun copyEncryptionKey() { 115 | val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager 116 | val clip = ClipData.newPlainText(binding.encryptionKey.text, binding.encryptionKey.text) 117 | clipboard.setPrimaryClip(clip) 118 | } 119 | 120 | private fun copyUserToken() { 121 | val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager 122 | val clip = ClipData.newPlainText(binding.userToken.content.text, binding.userToken.content.text) 123 | clipboard.setPrimaryClip(clip) 124 | } 125 | 126 | override fun onError(error: Throwable): Boolean { 127 | error.printStackTrace() 128 | if (error !is ApiError) { 129 | AlertBar.showAlert( 130 | binding.root, 131 | AlertBar.Type.ALERT_FAILED, 132 | error.message ?: "onError null" 133 | ) 134 | return false // App won't handle next step, SDK will finish the Activity. 135 | } 136 | when (error.code) { 137 | ApiError.ErrorCode.userCanceled, 138 | ApiError.ErrorCode.networkError -> { 139 | AlertBar.showAlert( 140 | binding.root, 141 | AlertBar.Type.ALERT_FAILED, 142 | error.code.value.toString() + " " + error.message 143 | ) 144 | return false // App won't handle next step, SDK will finish the Activity. 145 | } 146 | 147 | else -> { 148 | AlertBar.showAlert( 149 | binding.root, 150 | AlertBar.Type.ALERT_FAILED, 151 | error.message 152 | ) 153 | } 154 | } 155 | return true // App will handle next step, SDK will keep the Activity. 156 | } 157 | 158 | override fun onWarning(warning: ExecuteWarning, result: ExecuteResult?): Boolean { 159 | AlertBar.showAlert( 160 | binding.root, 161 | AlertBar.Type.ALERT_FAILED, 162 | "${warning?.warningType}, ${warning?.warningString}, ${result?.resultType?.name}, ${result?.status?.name}, ${result?.data?.signature}" 163 | ) 164 | //return true, App will handle next step, SDK will keep the Activity. 165 | //return false, App won't handle next step, SDK will finish the Activity. 166 | return false 167 | } 168 | 169 | override fun onResult(result: ExecuteResult) { 170 | AlertBar.showAlert( 171 | binding.root, 172 | AlertBar.Type.ALERT_SUCCESS, 173 | getString(R.string.execute_successful) 174 | ) 175 | } 176 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/MainActivity.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet 18 | 19 | import android.os.Bundle 20 | import android.view.View 21 | import androidx.appcompat.app.AppCompatActivity 22 | import androidx.lifecycle.ViewModelProvider 23 | import androidx.viewpager.widget.ViewPager 24 | import com.circle.w3s.sample.wallet.databinding.ActivityMainBinding 25 | import com.circle.w3s.sample.wallet.ui.main.MainViewModel 26 | import com.circle.w3s.sample.wallet.ui.main.tabs.TabPagerAdapter 27 | import com.google.android.material.tabs.TabLayout 28 | 29 | 30 | class MainActivity : AppCompatActivity() { 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | val binding = ActivityMainBinding.inflate(layoutInflater) 34 | val view: View = binding.getRoot() 35 | setContentView(view) 36 | 37 | val viewModel = ViewModelProvider(this)[MainViewModel::class.java] 38 | val endPoint = getString(R.string.pw_endpoint) 39 | val appId = getString(R.string.pw_app_id) 40 | viewModel.executeDataChanged(endPoint, appId, null, null, null, null, null, null, null) 41 | 42 | val tabs: TabLayout = binding.tabs 43 | 44 | val viewpager: ViewPager = binding.viewpager 45 | viewpager.adapter = TabPagerAdapter(this) 46 | viewpager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) 47 | tabs.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewpager)) 48 | tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { 49 | override fun onTabSelected(tab: TabLayout.Tab) { 50 | viewpager.currentItem = tab.position 51 | } 52 | 53 | override fun onTabUnselected(tab: TabLayout.Tab) { 54 | 55 | } 56 | 57 | override fun onTabReselected(tab: TabLayout.Tab) { 58 | 59 | } 60 | }) 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/pwcustom/MyLayoutProvider.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.pwcustom 18 | 19 | import android.content.Context 20 | import android.graphics.Color 21 | import androidx.core.content.res.ResourcesCompat 22 | import circle.programmablewallet.sdk.api.ApiError.ErrorCode 23 | import circle.programmablewallet.sdk.presentation.IconTextConfig 24 | import circle.programmablewallet.sdk.presentation.LayoutProvider 25 | import circle.programmablewallet.sdk.presentation.RemoteImageSetter 26 | import circle.programmablewallet.sdk.presentation.Resource 27 | import circle.programmablewallet.sdk.presentation.Resource.IconTextsKey 28 | import circle.programmablewallet.sdk.presentation.Resource.TextsKey 29 | import circle.programmablewallet.sdk.presentation.TextConfig 30 | import com.circle.w3s.sample.wallet.R 31 | 32 | class MyLayoutProvider(private var context: Context) : LayoutProvider() { 33 | 34 | val typeface = ResourcesCompat.getFont(context, R.font.inter_semi_bold); 35 | override fun getTextConfig(key: String): TextConfig? { 36 | when(key){ 37 | Resource.Key.circlepw_transaction_request_main_currency -> return TextConfig("USDC", font = typeface) 38 | Resource.Key.circlepw_transaction_request_exchange_value -> return TextConfig("≈\$20 USD", font = typeface) 39 | Resource.Key.circlepw_transaction_request_from -> return TextConfig("0x9988770123456789ccii", font = typeface) 40 | Resource.Key.circlepw_transaction_request_to_config -> return TextConfig(font = typeface) 41 | Resource.Key.circlepw_transaction_request_to_contract_name -> return TextConfig("uniswap.org", font = typeface) 42 | Resource.Key.circlepw_transaction_request_to_contract_url -> return TextConfig("https://uniswape.org", font = typeface) 43 | Resource.Key.circlepw_transaction_request_network_fee -> return TextConfig("0.1234 ETH", font = typeface) 44 | Resource.Key.circlepw_transaction_request_exchange_network_fee -> return TextConfig("≈\$1.1 USD") 45 | Resource.Key.circlepw_transaction_request_total_config, 46 | Resource.Key.circlepw_contract_interaction_abi_function_config, 47 | Resource.Key.circlepw_contract_interaction_data_details, 48 | -> { 49 | return TextConfig(font = typeface) 50 | } 51 | Resource.Key.circlepw_transaction_request_exchange_total_value -> return TextConfig("≈\$21.1 USD") 52 | Resource.Key.circlepw_signature_request_contract_name -> return TextConfig("uniswap.org", font = typeface) 53 | Resource.Key.circlepw_signature_request_contract_url -> return TextConfig("https://uniswape.org", font = typeface) 54 | } 55 | return super.getTextConfig(key) 56 | } 57 | 58 | private fun getHeadingColors(): Int { 59 | return Color.parseColor("#0073C3") 60 | } 61 | 62 | override fun getTextConfigs(key: TextsKey): Array? { 63 | when (key) { 64 | TextsKey.securityQuestionHeaders -> return arrayOf( 65 | TextConfig("Choose your 1st question"), 66 | TextConfig("Choose your 2nd question") 67 | ) 68 | 69 | TextsKey.securitySummaryQuestionHeaders -> return arrayOf( 70 | TextConfig("1st Question"), 71 | TextConfig("2nd Question") 72 | ) 73 | 74 | TextsKey.enterPinCodeHeadline -> return arrayOf( 75 | TextConfig("Enter your "), 76 | TextConfig("PIN", getHeadingColors(), null) 77 | ) 78 | 79 | TextsKey.securityIntroHeadline -> return arrayOf( 80 | TextConfig("Set up your "), 81 | TextConfig("Recovery Method", getHeadingColors(), null) 82 | ) 83 | 84 | TextsKey.newPinCodeHeadline -> return arrayOf( 85 | TextConfig("Enter your "), 86 | TextConfig("PIN", getHeadingColors(), null) 87 | ) 88 | 89 | TextsKey.securityIntroLink -> return arrayOf( 90 | TextConfig("Learn more"), 91 | TextConfig("https://path/terms-policies/privacy-notice/") 92 | ) 93 | 94 | TextsKey.recoverPinCodeHeadline -> return arrayOf( 95 | TextConfig("Recover your "), 96 | TextConfig("PIN", getHeadingColors(), null) 97 | ) 98 | 99 | else -> {} 100 | } 101 | return super.getTextConfigs(key) 102 | } 103 | 104 | override fun getIconTextConfigs(key: IconTextsKey): Array? { 105 | val url = arrayOf( 106 | "https://path/intro_item0", 107 | "https://path/intro_item1", 108 | "https://path/intro_item2" 109 | ) 110 | when (key) { 111 | IconTextsKey.securityConfirmationItems -> return arrayOf( 112 | IconTextConfig( 113 | RemoteImageSetter(R.drawable.ic_intro_item0_icon, url[0]), 114 | TextConfig("This is the only way to recover my account access. ") 115 | ), 116 | IconTextConfig( 117 | RemoteImageSetter(R.drawable.ic_intro_item1_icon, url[1]), 118 | TextConfig("Circle won’t store my answers so it’s my responsibility to remember them.") 119 | ), 120 | IconTextConfig( 121 | RemoteImageSetter(R.drawable.ic_intro_item2_icon, url[2]), 122 | TextConfig("I will lose access to my wallet and my digital assets if I forget my answers. ") 123 | ) 124 | ) 125 | 126 | else -> {} 127 | } 128 | return super.getIconTextConfigs(key) 129 | } 130 | 131 | override fun getErrorString(code: ErrorCode): String? { 132 | return super.getErrorString(code) 133 | } 134 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/pwcustom/MyViewSetterProvider.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.pwcustom 18 | 19 | import android.content.Context 20 | import circle.programmablewallet.sdk.presentation.IImageViewSetter 21 | import circle.programmablewallet.sdk.presentation.IToolbarSetter 22 | import circle.programmablewallet.sdk.presentation.LocalImageSetter 23 | import circle.programmablewallet.sdk.presentation.RemoteImageSetter 24 | import circle.programmablewallet.sdk.presentation.RemoteToolbarImageSetter 25 | import circle.programmablewallet.sdk.presentation.Resource 26 | import circle.programmablewallet.sdk.presentation.Resource.ToolbarIcon 27 | import circle.programmablewallet.sdk.presentation.ViewSetterProvider 28 | import com.circle.w3s.sample.wallet.R 29 | 30 | class MyViewSetterProvider(var context: Context) : ViewSetterProvider() { 31 | 32 | 33 | override fun getToolbarImageSetter(type: ToolbarIcon): IToolbarSetter? { 34 | when (type) { 35 | ToolbarIcon.back -> return RemoteToolbarImageSetter( 36 | R.drawable.ic_back, 37 | "https://path/ic_back" 38 | ) 39 | 40 | ToolbarIcon.close -> return RemoteToolbarImageSetter( 41 | R.drawable.ic_close, 42 | "https://path/ic_close" 43 | ) 44 | 45 | else -> {} 46 | } 47 | return super.getToolbarImageSetter(type) 48 | } 49 | 50 | override fun getImageSetter(type: Resource.Icon): IImageViewSetter? { 51 | when (type) { 52 | Resource.Icon.securityIntroMain -> return RemoteImageSetter( 53 | R.drawable.ic_intro_main_icon, 54 | "https://path/ic_intro_main_icon" 55 | ) 56 | 57 | Resource.Icon.selectCheckMark -> return RemoteImageSetter( 58 | R.drawable.ic_checkmark, 59 | "https://path/ic_checkmark" 60 | ) 61 | 62 | Resource.Icon.dropdownArrow -> return RemoteImageSetter( 63 | R.drawable.ic_dropdown_arrow, 64 | "https://path/ic_dropdown_arrow" 65 | ) 66 | 67 | Resource.Icon.errorInfo -> return RemoteImageSetter( 68 | R.drawable.ic_error_info, 69 | "https://path/ic_error_info" 70 | ) 71 | 72 | Resource.Icon.securityConfirmMain -> return RemoteImageSetter( 73 | R.drawable.ic_confirm_main_icon, 74 | "https://path/ic_confirm_main_icon" 75 | ) 76 | 77 | Resource.Icon.requestIcon -> return RemoteImageSetter( 78 | 0, 79 | "https://avatars.githubusercontent.com/u/37784886" 80 | ) 81 | 82 | Resource.Icon.biometricsAllowMain -> return LocalImageSetter(R.drawable.ic_biometrics_general) 83 | Resource.Icon.showPin -> return LocalImageSetter(R.drawable.ic_show_pin) 84 | Resource.Icon.hidePin -> return LocalImageSetter(R.drawable.ic_hide_pin) 85 | Resource.Icon.transactionTokenIcon -> return LocalImageSetter(R.drawable.ic_currency_usdc) 86 | Resource.Icon.networkFeeTipIcon -> return LocalImageSetter(R.drawable.ic_fee_info) 87 | Resource.Icon.showLessDetailArrow -> return LocalImageSetter(R.drawable.ic_show_less_arrow) 88 | Resource.Icon.showMoreDetailArrow -> return LocalImageSetter(R.drawable.ic_show_more_arrow) 89 | 90 | 91 | else -> {} 92 | } 93 | return super.getImageSetter(type) 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/NecessaryTextView.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui 18 | 19 | import android.content.Context 20 | import android.graphics.Color 21 | import android.text.Spannable 22 | import android.text.SpannableString 23 | import android.text.TextUtils 24 | import android.text.style.ForegroundColorSpan 25 | import android.util.AttributeSet 26 | import androidx.appcompat.widget.AppCompatTextView 27 | import com.circle.w3s.sample.wallet.R 28 | 29 | class NecessaryTextView : AppCompatTextView { 30 | private var necessary = false 31 | 32 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 33 | val typedArray = context.obtainStyledAttributes(attrs, R.styleable.NecessaryTextView) 34 | necessary = typedArray.getBoolean(R.styleable.NecessaryTextView_necessary, false) 35 | typedArray.recycle() 36 | setText(text, null) 37 | } 38 | 39 | fun setNecessary(necessary: Boolean) { 40 | this.necessary = necessary 41 | } 42 | 43 | override fun setText(text: CharSequence?, type: BufferType?) { 44 | if (necessary) { 45 | val span = SpannableString("$text *") 46 | if (text != null && !TextUtils.isEmpty(text)) { 47 | span.setSpan( 48 | ForegroundColorSpan(Color.RED), 49 | text.length + 1, 50 | text.length + 2, 51 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE 52 | ) 53 | } 54 | super.setText(span, type) 55 | } else { 56 | super.setText(text, type) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/alert/AlertBar.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.alert 18 | 19 | import android.annotation.SuppressLint 20 | import android.graphics.Color 21 | import android.view.Gravity 22 | import android.view.LayoutInflater 23 | import android.view.View 24 | import android.widget.FrameLayout 25 | import android.widget.ImageView 26 | import android.widget.TextView 27 | import androidx.constraintlayout.widget.ConstraintLayout 28 | import androidx.coordinatorlayout.widget.CoordinatorLayout 29 | import androidx.core.content.ContextCompat 30 | import com.circle.w3s.sample.wallet.R 31 | import com.google.android.material.snackbar.Snackbar 32 | import com.google.android.material.snackbar.Snackbar.SnackbarLayout 33 | 34 | @SuppressLint("RestrictedApi") 35 | class AlertBar { 36 | enum class Type { 37 | ALERT_SUCCESS, 38 | ALERT_FAILED 39 | } 40 | 41 | companion object { 42 | val MAX_DISPLAY_DURATION: Int = 3 * 1000 43 | 44 | fun showAlert(view: View, style: Type, message: String) { 45 | val snackbar: Snackbar = Snackbar.make(view, message, MAX_DISPLAY_DURATION) 46 | val snackbarView: View = snackbar.view 47 | 48 | try { 49 | val params = snackbarView.layoutParams as CoordinatorLayout.LayoutParams 50 | params.gravity = Gravity.BOTTOM 51 | snackbarView.layoutParams = params 52 | } catch (t: Throwable) { 53 | val params = snackbarView.layoutParams as FrameLayout.LayoutParams 54 | params.gravity = Gravity.BOTTOM 55 | snackbarView.layoutParams = params 56 | } 57 | 58 | snackbarView.setBackgroundColor(Color.TRANSPARENT) 59 | 60 | snackbarView.setPadding(0, 0, 0, 0) 61 | 62 | val messageTv: TextView = 63 | snackbarView.findViewById(com.google.android.material.R.id.snackbar_text) 64 | messageTv.visibility = View.INVISIBLE 65 | 66 | val inflater = LayoutInflater.from(view.context) 67 | val customSnackView: View = inflater.inflate(R.layout.layout_alert_snack_bar, null) 68 | val mainUi: ConstraintLayout = customSnackView.findViewById(R.id.main) 69 | val icon: ImageView = customSnackView.findViewById(R.id.icon) 70 | val msg: TextView = customSnackView.findViewById(R.id.message) 71 | msg.text = message 72 | val close: ImageView = customSnackView.findViewById(R.id.close) 73 | close.setOnClickListener { snackbar.dismiss() } 74 | val context = view.context 75 | if (Type.ALERT_FAILED == style) { 76 | mainUi.setBackgroundResource(R.drawable.background_alert_negative_panel) 77 | icon.setImageResource(R.drawable.ic_alert_negative) 78 | msg.setTextColor(context.getColor(R.color.alert_text_negative)) 79 | close.setColorFilter( 80 | ContextCompat.getColor(context, R.color.alert_text_negative), 81 | android.graphics.PorterDuff.Mode.SRC_IN 82 | ) 83 | } else { 84 | mainUi.setBackgroundResource(R.drawable.background_alert_positive_panel) 85 | icon.setImageResource(R.drawable.ic_alert_positive) 86 | msg.setTextColor(view.context.getColor(R.color.alert_text_positive)) 87 | close.setColorFilter( 88 | ContextCompat.getColor(context, R.color.alert_text_positive), 89 | android.graphics.PorterDuff.Mode.SRC_IN 90 | ) 91 | } 92 | 93 | val layout = snackbar.view as SnackbarLayout 94 | layout.addView(customSnackView, 0); 95 | snackbar.show() 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/main/ExecuteFormState.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.main 18 | 19 | data class ExecuteFormState( 20 | val endpoint: String? = null, 21 | val appId: String? = null, 22 | val userToken: String? = null, 23 | val encryptionKey: String? = null, 24 | val socialUserToken: String? = null, 25 | val socialEncryptionKey: String? = null, 26 | val emailUserToken: String? = null, 27 | val emailEncryptionKey: String? = null, 28 | val challengeId: String? = null, 29 | val isExecuteDataValid: Boolean = false, 30 | val isSetBiometricsPinInputDataValid: Boolean = false 31 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.main 18 | 19 | import androidx.lifecycle.LiveData 20 | import androidx.lifecycle.MutableLiveData 21 | import androidx.lifecycle.ViewModel 22 | import androidx.navigation.NavDirections 23 | 24 | class MainViewModel : ViewModel() { 25 | 26 | private val _executeForm = MutableLiveData() 27 | val executeFormState: LiveData = _executeForm 28 | private val _naviDirections = MutableLiveData() 29 | val naviDirections: LiveData = _naviDirections 30 | private val _enableBiometrics = MutableLiveData(false) 31 | val enableBiometrics: LiveData = _enableBiometrics 32 | private val _isSetBiometricsPinDataValid = MutableLiveData(false) 33 | val isSetBiometricsPinDataValid: LiveData = _isSetBiometricsPinDataValid 34 | private val _disableConfirmationUI = MutableLiveData(false) 35 | val disableConfirmationUI: LiveData = _disableConfirmationUI 36 | 37 | 38 | fun executeDataChanged(endpoint: String?, appId: String?, userToken: String?, encryptionKey: String?, socialUserToken: String?, socialEncryptionKey: String?, emailUserToken: String?, emailEncryptionKey: String?, challengeId: String?) { 39 | val isSetBiometricsPinInputDataValid = endpoint?.isNotBlank() ?: false && appId?.isNotBlank() ?: false && userToken?.isNotBlank() ?: false && encryptionKey?.isNotBlank() ?: false 40 | _isSetBiometricsPinDataValid.value = isSetBiometricsPinInputDataValid && _enableBiometrics.value == true 41 | _executeForm.value = ExecuteFormState( 42 | isSetBiometricsPinInputDataValid = isSetBiometricsPinInputDataValid, 43 | isExecuteDataValid = isSetBiometricsPinInputDataValid && challengeId?.isNotBlank() ?: false, 44 | endpoint = endpoint, 45 | appId = appId, 46 | userToken = userToken, 47 | encryptionKey = encryptionKey, 48 | socialUserToken = socialUserToken, 49 | socialEncryptionKey = socialEncryptionKey, 50 | emailUserToken = emailUserToken, 51 | emailEncryptionKey = emailEncryptionKey, 52 | challengeId = challengeId, 53 | ) 54 | } 55 | 56 | fun setNaviDirections(directions: NavDirections?) { 57 | _naviDirections.value = directions 58 | } 59 | 60 | fun setEnableBiometrics(value: Boolean){ 61 | _enableBiometrics.value = value 62 | _isSetBiometricsPinDataValid.value = value && _executeForm.value?.isSetBiometricsPinInputDataValid == true 63 | } 64 | 65 | fun setDisableConfirmationUI(value: Boolean){ 66 | _disableConfirmationUI.value = value 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/main/ResultFragment.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.main 18 | 19 | import android.os.Bundle 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import androidx.fragment.app.Fragment 24 | import androidx.navigation.fragment.findNavController 25 | import androidx.navigation.fragment.navArgs 26 | import com.circle.w3s.sample.wallet.R 27 | import com.circle.w3s.sample.wallet.databinding.FragmentResultBinding 28 | 29 | class ResultFragment : Fragment() { 30 | private val args: ResultFragmentArgs by navArgs() 31 | 32 | private lateinit var binding: FragmentResultBinding 33 | 34 | override fun onCreateView( 35 | inflater: LayoutInflater, container: ViewGroup?, 36 | savedInstanceState: Bundle? 37 | ): View { 38 | binding = FragmentResultBinding.inflate(inflater) 39 | return binding.root 40 | } 41 | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | binding.btMain.setOnClickListener { 44 | findNavController().popBackStack() 45 | } 46 | args.challengeId?.let { 47 | binding.challengeId.apply { 48 | root.visibility = View.VISIBLE 49 | title.setText(R.string.label_challenge_id) 50 | value.text = it 51 | } 52 | } 53 | args.challengeType?.let { 54 | binding.successFail1.apply { 55 | root.visibility = View.VISIBLE 56 | title.setText(R.string.label_challenge_type) 57 | value.text = it 58 | } 59 | } 60 | args.challengeStatus?.let { 61 | binding.successFail2.apply { 62 | root.visibility = View.VISIBLE 63 | title.setText(R.string.label_challenge_status) 64 | value.text = it 65 | } 66 | } 67 | args.errorCode?.let { 68 | binding.successFail1.apply { 69 | root.visibility = View.VISIBLE 70 | title.setText(R.string.label_error_code) 71 | value.text = it 72 | } 73 | } 74 | args.errorMessage?.let { 75 | binding.successFail2.apply { 76 | root.visibility = View.VISIBLE 77 | title.setText(R.string.label_error_message) 78 | value.text = it 79 | } 80 | } 81 | args.signature?.let { 82 | binding.signature.apply { 83 | root.visibility = View.VISIBLE 84 | title.setText(R.string.label_signature) 85 | value.text = it 86 | } 87 | } 88 | args.warningType?.let { 89 | binding.warningType.apply { 90 | root.visibility = View.VISIBLE 91 | title.setText(R.string.label_warning_type) 92 | value.text = it 93 | } 94 | } 95 | args.warningMessage?.let { 96 | binding.warningMessage.apply { 97 | root.visibility = View.VISIBLE 98 | title.setText(R.string.label_warning_message) 99 | value.text = it 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/main/tabs/ITabPage.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.main.tabs 18 | 19 | import android.content.Context 20 | import android.view.View 21 | import androidx.lifecycle.ViewModelProvider 22 | import com.circle.w3s.sample.wallet.MainActivity 23 | import com.circle.w3s.sample.wallet.ui.main.MainViewModel 24 | 25 | abstract class ITabPage(activity: MainActivity) { 26 | internal val viewModel: MainViewModel = 27 | ViewModelProvider(activity)[MainViewModel::class.java] 28 | abstract fun initPage(context: Context): View 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/main/tabs/TabPagePin.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.main.tabs 18 | 19 | import android.content.Context 20 | import android.content.pm.PackageManager 21 | import android.graphics.Color 22 | import android.os.Build 23 | import android.view.LayoutInflater 24 | import android.view.View 25 | import android.widget.TextView 26 | import androidx.core.widget.doAfterTextChanged 27 | import androidx.lifecycle.Observer 28 | import circle.programmablewallet.sdk.WalletSdk 29 | import circle.programmablewallet.sdk.api.ApiError 30 | import circle.programmablewallet.sdk.api.Callback 31 | import circle.programmablewallet.sdk.api.ExecuteWarning 32 | import circle.programmablewallet.sdk.presentation.SettingsManagement 33 | import circle.programmablewallet.sdk.result.ExecuteResult 34 | import com.circle.w3s.sample.wallet.MainActivity 35 | import com.circle.w3s.sample.wallet.R 36 | import com.circle.w3s.sample.wallet.databinding.PagerPinBinding 37 | import com.circle.w3s.sample.wallet.ui.NecessaryTextView 38 | import com.circle.w3s.sample.wallet.ui.alert.AlertBar 39 | import com.circle.w3s.sample.wallet.util.KeyboardUtils 40 | import com.google.android.material.snackbar.Snackbar 41 | 42 | class TabPagePin(activity: MainActivity) : ITabPage(activity), 43 | Callback { 44 | private val activity = activity 45 | private lateinit var binding: PagerPinBinding 46 | override fun initPage(context: Context): View { 47 | val inflater = LayoutInflater.from(context) 48 | binding = PagerPinBinding.inflate(inflater) 49 | initPagerPin(activity, binding) 50 | return binding.root 51 | } 52 | 53 | private fun initPagerPin(context: Context, binding: PagerPinBinding) { 54 | context?.let { 55 | val versionName = getVersionName(it) 56 | binding.version.text = versionName 57 | } 58 | binding.execute.setOnClickListener { 59 | initAndLaunchSdk({ 60 | WalletSdk.execute( 61 | activity, 62 | binding.userToken.inputValue.text.toString(), 63 | binding.encryptionKey.inputValue.text.toString(), 64 | arrayOf(binding.challengeId.inputValue.text.toString()), 65 | this 66 | ) 67 | }, binding) 68 | } 69 | viewModel.executeFormState.observe(activity, Observer { 70 | it ?: return@Observer 71 | binding.execute.isEnabled = it.isExecuteDataValid 72 | }) 73 | viewModel.isSetBiometricsPinDataValid.observe(activity, Observer { 74 | it ?: return@Observer 75 | binding.setBiometricsPin.isEnabled = it 76 | }) 77 | 78 | binding.setBiometricsPin.setOnClickListener { 79 | initAndLaunchSdk({ 80 | WalletSdk.setBiometricsPin( 81 | activity, 82 | binding.userToken.inputValue.text.toString(), 83 | binding.encryptionKey.inputValue.text.toString(), 84 | this 85 | ) 86 | }, binding) 87 | } 88 | binding.challengeId.inputTitle.setText(R.string.label_challenge_id) 89 | 90 | val epTitle: NecessaryTextView = binding.endpoint.inputTitle 91 | epTitle.setNecessary(true) 92 | epTitle.setText(R.string.label_endpoint) 93 | 94 | val appIdTitle: NecessaryTextView = binding.appId.inputTitle 95 | appIdTitle.setNecessary(true) 96 | appIdTitle.setText(R.string.label_app_id) 97 | 98 | val userTokenTitle: NecessaryTextView = binding.userToken.inputTitle 99 | userTokenTitle.setNecessary(true) 100 | userTokenTitle.setText(R.string.label_user_token) 101 | 102 | val encryptionKeyTitle: NecessaryTextView = binding.encryptionKey.inputTitle 103 | encryptionKeyTitle.setNecessary(true) 104 | encryptionKeyTitle.setText(R.string.label_encryption_key) 105 | 106 | binding.enableBiometrics.inputTitle.setText(R.string.label_biometrics_setting) 107 | binding.enableBiometrics.inputSubtitle.setText(R.string.label_sub_biometrics_setting) 108 | binding.enableBiometrics.toggleBtn.isChecked = 109 | viewModel.enableBiometrics.value ?: false 110 | binding.enableBiometrics.toggleBtn.setOnCheckedChangeListener { _, isChecked -> 111 | viewModel.setEnableBiometrics( 112 | isChecked 113 | ) 114 | } 115 | 116 | binding.endpoint.inputValue.setText(viewModel.executeFormState.value?.endpoint ?: "") 117 | binding.appId.inputValue.setText(viewModel.executeFormState.value?.appId ?: "") 118 | 119 | binding.endpoint.inputValue.doAfterTextChanged { 120 | executeDataChanged(binding) 121 | } 122 | binding.appId.inputValue.doAfterTextChanged { 123 | executeDataChanged(binding) 124 | } 125 | binding.userToken.inputValue.doAfterTextChanged { 126 | executeDataChanged(binding) 127 | } 128 | binding.encryptionKey.inputValue.doAfterTextChanged { 129 | executeDataChanged(binding) 130 | } 131 | binding.challengeId.inputValue.doAfterTextChanged { 132 | executeDataChanged(binding) 133 | } 134 | 135 | } 136 | 137 | private fun getVersionName(context: Context): String { 138 | val packageManager = context.packageManager 139 | val packageName = context.packageName 140 | val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 141 | packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) 142 | } else { 143 | packageManager.getPackageInfo(packageName, 0) 144 | } 145 | return packageInfo.versionName; 146 | } 147 | 148 | private inline fun initAndLaunchSdk(launchBlock: () -> Unit, binding: PagerPinBinding) { 149 | KeyboardUtils.hideKeyboard(binding.challengeId.inputValue) 150 | binding.ll.requestFocus() 151 | try { 152 | val settingsManagement = SettingsManagement() 153 | settingsManagement.isEnableBiometricsPin = binding.enableBiometrics.toggleBtn.isChecked 154 | WalletSdk.init( 155 | binding.main.context, 156 | WalletSdk.Configuration( 157 | binding.endpoint.inputValue.text.toString(), 158 | binding.appId.inputValue.text.toString(), 159 | settingsManagement 160 | ) 161 | ) 162 | } catch (t: Throwable) { 163 | showSnack(t.message ?: "initSdk catch null", binding.main) 164 | return 165 | } 166 | setInProgress(true) 167 | launchBlock() 168 | } 169 | 170 | private fun setInProgress(inProgress: Boolean) { 171 | activity.runOnUiThread({ 172 | binding.execute.isClickable = !inProgress 173 | binding.loading.visibility = if (inProgress) View.VISIBLE else View.GONE 174 | }) 175 | } 176 | 177 | private fun showSnack( 178 | message: String, view: View 179 | ) { 180 | // Log.i(TAG, message) 181 | val snackbar = Snackbar.make( 182 | view, message, 183 | Snackbar.LENGTH_LONG 184 | ).setAction("", null) 185 | snackbar.view.setBackgroundColor(Color.BLACK) 186 | val textView = 187 | snackbar.view.findViewById(com.google.android.material.R.id.snackbar_text) as TextView 188 | textView.maxLines = 10 189 | textView.setTextColor(Color.WHITE) 190 | snackbar.show() 191 | } 192 | 193 | private fun executeDataChanged(binding: PagerPinBinding) { 194 | viewModel.executeDataChanged( 195 | binding.endpoint.inputValue.text.toString(), 196 | binding.appId.inputValue.text.toString(), 197 | binding.userToken.inputValue.text.toString(), 198 | binding.encryptionKey.inputValue.text.toString(), 199 | viewModel.executeFormState.value?.socialUserToken , 200 | viewModel.executeFormState.value?.socialEncryptionKey , 201 | viewModel.executeFormState.value?.emailUserToken , 202 | viewModel.executeFormState.value?.emailEncryptionKey , 203 | binding.challengeId.inputValue.text.toString(), 204 | ) 205 | } 206 | 207 | override fun onError(error: Throwable): Boolean { 208 | setInProgress(false) 209 | error.printStackTrace() 210 | if (error !is ApiError) { 211 | AlertBar.showAlert( 212 | binding.root, 213 | AlertBar.Type.ALERT_FAILED, 214 | error.message ?: "onError null" 215 | ) 216 | return false // App won't handle next step, SDK will finish the Activity. 217 | } 218 | when (error.code) { 219 | ApiError.ErrorCode.userCanceled, 220 | ApiError.ErrorCode.networkError -> { 221 | AlertBar.showAlert( 222 | binding.root, 223 | AlertBar.Type.ALERT_FAILED, 224 | error.code.value.toString() + " " + error.message 225 | ) 226 | return false // App won't handle next step, SDK will finish the Activity. 227 | } 228 | 229 | else -> { 230 | AlertBar.showAlert( 231 | binding.root, 232 | AlertBar.Type.ALERT_FAILED, 233 | error.message 234 | ) 235 | } 236 | } 237 | return true // App will handle next step, SDK will keep the Activity. 238 | } 239 | 240 | override fun onWarning(warning: ExecuteWarning, result: ExecuteResult?): Boolean { 241 | setInProgress(false) 242 | AlertBar.showAlert( 243 | binding.root, 244 | AlertBar.Type.ALERT_FAILED, 245 | "${warning?.warningType}, ${warning?.warningString}, ${result?.resultType?.name}, ${result?.status?.name}, ${result?.data?.signature}" 246 | ) 247 | //return true, App will handle next step, SDK will keep the Activity. 248 | //return false, App won't handle next step, SDK will finish the Activity. 249 | return false 250 | } 251 | 252 | override fun onResult(result: ExecuteResult) { 253 | setInProgress(false) 254 | AlertBar.showAlert( 255 | binding.root, 256 | AlertBar.Type.ALERT_SUCCESS, 257 | binding.root.context.getString(R.string.execute_successful) 258 | ) 259 | } 260 | 261 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/ui/main/tabs/TabPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.ui.main.tabs 18 | 19 | import android.app.ActivityManager 20 | import android.content.Context 21 | import android.content.Intent 22 | import android.os.Bundle 23 | import android.util.Log 24 | import android.view.View 25 | import android.view.ViewGroup 26 | import androidx.fragment.app.viewModels 27 | import androidx.lifecycle.ViewModelProvider 28 | import androidx.viewpager.widget.PagerAdapter 29 | import circle.programmablewallet.sdk.WalletSdk 30 | import circle.programmablewallet.sdk.api.ExecuteEvent 31 | import circle.programmablewallet.sdk.presentation.EventListener 32 | import circle.programmablewallet.sdk.presentation.SecurityQuestion 33 | import com.circle.w3s.sample.wallet.CustomActivity 34 | import com.circle.w3s.sample.wallet.MainActivity 35 | import com.circle.w3s.sample.wallet.R 36 | import com.circle.w3s.sample.wallet.pwcustom.MyLayoutProvider 37 | import com.circle.w3s.sample.wallet.pwcustom.MyViewSetterProvider 38 | import com.circle.w3s.sample.wallet.ui.alert.AlertBar 39 | import com.circle.w3s.sample.wallet.ui.main.MainViewModel 40 | 41 | class TabPagerAdapter(mainActivity: MainActivity) : PagerAdapter(), EventListener { 42 | private val activity = mainActivity 43 | override fun getCount(): Int { 44 | return 3 45 | } 46 | 47 | override fun isViewFromObject(view: View, o: Any): Boolean { 48 | return o === view 49 | } 50 | 51 | override fun getPageTitle(position: Int): CharSequence? { 52 | return "Page: Item${position + 1}" 53 | } 54 | 55 | override fun instantiateItem(container: ViewGroup, position: Int): Any { 56 | val view: View 57 | val page: ITabPage 58 | when (position) { 59 | 0 -> { 60 | page = TabPageSocial(activity) 61 | } 62 | 63 | 1 -> { 64 | page = TabPageEmail(activity) 65 | } 66 | 67 | 2 -> { 68 | page = TabPagePin(activity) 69 | } 70 | 71 | else -> { 72 | page = TabPagePin(activity) 73 | } 74 | } 75 | 76 | page.let { 77 | view = page.initPage(activity.applicationContext) 78 | container.addView(view) 79 | } 80 | setupSdk(activity.applicationContext) 81 | 82 | return view 83 | } 84 | 85 | override fun destroyItem(container: ViewGroup, position: Int, any: Any) { 86 | container.removeView(any as View) 87 | } 88 | 89 | 90 | private fun setupSdk(context: Context) { 91 | WalletSdk.addEventListener(this) 92 | context?.let { 93 | WalletSdk.setLayoutProvider(MyLayoutProvider(it)) 94 | WalletSdk.setViewSetterProvider(MyViewSetterProvider(it)) 95 | WalletSdk.setCustomUserAgent("ANDROID-SAMPLE-APP-WALLETS") 96 | } 97 | WalletSdk.setSecurityQuestions( 98 | arrayOf( 99 | SecurityQuestion("What is your father’s middle name?"), 100 | SecurityQuestion("What is your favorite sports team?"), 101 | SecurityQuestion("What is your mother’s maiden name?"), 102 | SecurityQuestion("What is the name of your first pet?"), 103 | SecurityQuestion("What is the name of the city you were born in?"), 104 | SecurityQuestion("What is the name of the first street you lived on?"), 105 | SecurityQuestion( 106 | "When is your father’s birthday?", 107 | SecurityQuestion.InputType.datePicker 108 | ) 109 | ) 110 | ) 111 | } 112 | 113 | 114 | override fun onEvent(event: ExecuteEvent) { 115 | if (event == ExecuteEvent.forgotPin || event == ExecuteEvent.resendOtp) { 116 | goCustom(activity, activity.getString(R.string.register_callback)) 117 | } 118 | } 119 | 120 | private fun goCustom(context: Context?, msg: String?) { 121 | context ?: return 122 | val b = Bundle() 123 | b.putString(CustomActivity.ARG_MSG, msg) 124 | val intent = Intent( 125 | context, 126 | CustomActivity::class.java 127 | ).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) 128 | intent.putExtras(b) 129 | context.startActivity(intent) 130 | activity.overridePendingTransition(circle.programmablewallet.sdk.R.anim.no_anim, circle.programmablewallet.sdk.R.anim.no_anim) 131 | } 132 | } -------------------------------------------------------------------------------- /app/src/main/java/com/circle/w3s/sample/wallet/util/KeyboardUtils.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Circle Internet Financial, LTD. All rights reserved. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package com.circle.w3s.sample.wallet.util 18 | 19 | import android.content.Context 20 | import android.view.View 21 | import android.view.inputmethod.InputMethodManager 22 | 23 | class KeyboardUtils { 24 | companion object{ 25 | fun showKeyboard(view: View) { 26 | if (view.context == null) return 27 | view.requestFocus() 28 | val inputMethodManager = 29 | view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 30 | inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_FORCED) 31 | } 32 | 33 | fun hideKeyboard(view: View) { 34 | if (view.context == null) return 35 | val inputMethodManager = 36 | view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 37 | inputMethodManager.hideSoftInputFromWindow( 38 | view.windowToken, 0 39 | ) 40 | view.clearFocus() 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/activated_round_input_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_alert_negative_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_alert_positive_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_alert_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_login_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_login_btn_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_panel_primary10_r12.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_social_execute_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_rounded_tab_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/disabled_blue_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/disabled_white_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alert.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alert_negative.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alert_positive.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_apple.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_biometrics_general.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_checkmark.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_confirm_main_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/drawable/ic_confirm_main_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_currency_usdc.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dropdown_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_facebook.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fee_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_google.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hide_pin.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_intro_item0_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/drawable/ic_intro_item0_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_intro_item1_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/drawable/ic_intro_item1_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_intro_item2_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/drawable/ic_intro_item2_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_intro_main_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/drawable/ic_intro_main_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_show_less_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_show_more_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_show_pin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_small_biomatrics_general.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_social_execute.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_swipe.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/no_focus_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pressed_round_main_bt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pressed_round_secondary_bt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_input_bg_focused.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tabs_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tabs_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/font/inter_extra_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/font/inter_extra_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/inter_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/font/inter_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/inter_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/font/inter_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/inter_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/font/inter_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/inter_semi_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/font/inter_semi_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_custom_alert.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 | 22 | 30 | 31 | 44 | 45 | 57 | 58 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 23 | 24 | 36 | 37 | 47 | 48 | 49 | 50 | 51 | 61 | 62 | 66 | 67 | 71 | 72 | 76 | 77 | 78 | 79 | 84 | 85 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_social.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 23 | 24 | 25 | 35 | 36 | 47 | 48 | 57 | 58 | 67 | 68 | 82 | 83 | 97 | 98 | 112 | 113 | 127 | 128 | 142 | 143 | 157 | 158 | 167 | 168 | 182 | 183 | 196 | 197 | 198 | 199 | 200 | 211 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_social_execute.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 32 | 41 | 45 | 46 | 57 | 58 | 70 | 71 | 79 | 80 | 89 | 90 | 91 | 92 | 103 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 24 | 25 | 36 | 37 | 42 | 43 | 48 | 49 | 54 | 55 | 60 | 61 | 66 | 67 | 72 | 73 | 78 | 79 | 84 | 85 | 95 | 96 | 105 | 106 | 115 | 116 | 124 | 125 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 22 | 23 | 28 | 29 | 36 | 37 | 44 | 45 | 52 | 53 | 60 | 61 | 68 | 69 | 76 | 77 | 78 | 79 | 80 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_alert_snack_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 23 | 24 | 37 | 38 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_copy_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 23 | 24 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_display_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 21 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_input_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 23 | 35 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_input_toggle.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 24 | 25 | 29 | 30 | 35 | 36 | 45 | 46 | 55 | 56 | 57 | 58 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pager_email.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 22 | 29 | 30 | 35 | 36 | 41 | 42 | 47 | 48 | 53 | 54 | 59 | 60 | 69 | 70 | 79 | 80 | 85 | 86 | 90 | 91 | 98 | 99 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pager_pin.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 19 | 20 | 32 | 33 | 38 | 39 | 44 | 45 | 50 | 51 | 56 | 57 | 62 | 63 | 68 | 69 | 79 | 80 | 89 | 90 | 98 | 99 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pager_social.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 29 | 30 | 35 | 36 | 41 | 42 | 47 | 48 | 53 | 54 | 59 | 60 | 70 | 71 | 82 | 83 | 93 | 94 | 95 | 96 | 102 | 103 | 107 | 108 | 119 | 120 | 121 | 128 | 129 | 133 | 134 | 145 | 146 | 147 | 153 | 154 | 158 | 159 | 170 | 171 | 172 | 176 | 177 | 186 | 187 | 192 | 193 | 197 | 198 | 205 | 206 | 212 | 213 | 214 | 215 | 216 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 17 | 21 | 25 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 57 | 58 | 63 | 67 | 71 | 74 | 78 | 82 | 86 | 90 | 94 | 98 | 102 | 103 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF3700B3 4 | #FFBB86FC 5 | #0073c3 6 | #800073C3 7 | #FF03DAC5 8 | #FF018786 9 | #FF000000 10 | #FFFFFFFF 11 | 12 | #1A1A1A 13 | #3D3D3D 14 | #A3A3A3 15 | #80FFFFFF 16 | @color/blue_main_50 17 | @color/white 18 | @color/blue_main 19 | @color/white 20 | #1AA3FF 21 | #F5F5F5 22 | #46B5FF 23 | #E8E8E8 24 | #F5F5F8 25 | #1A111827 26 | #8A849C 27 | #1d1b201f 28 | #000000 29 | #938F99 30 | #CAC4D0 31 | #008339 32 | #FFEAEF 33 | #4DBC0016 34 | #FFFFFF 35 | #BC0016 36 | #29233B 37 | #1d1b20 38 | #F1F9FE 39 | #49454F 40 | -------------------------------------------------------------------------------- /app/src/main/res/values/config.xml: -------------------------------------------------------------------------------- 1 | 2 | https://enduser-sdk.circle.com/v1/w3s/ 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 25dp 3 | 24dp 4 | 39dp 5 | 56dp 6 | 24dp 7 | 8dp 8 | 16dp 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #09102A 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | W3s Sample Wallet 3 | Programmable Wallet SDK Sample App 4 | End Point 5 | APP ID 6 | User Token 7 | Encryption Key 8 | Challenge ID 9 | User Secret 10 | Biometrics 11 | Disable Confirmation UI 12 | Get Social Information 13 | Please input endpoint 14 | Please input app ID 15 | Please input user token 16 | Please input encryption key 17 | Please input challenge ID 18 | Execute 19 | Continue 20 | Set up Biometrics 21 | Challenge Type 22 | Challenge Status 23 | Error Code 24 | Error Message 25 | Signature 26 | Warning Type 27 | Warning Message 28 | Device ID 29 | Device Token 30 | Device Encryption Key 31 | OTP Token 32 | Execute Successful 33 | 34 | YOUR_GOOGLE_WEB_CLIENT_ID 35 | YOUR_FACEBOOK_APP_ID 36 | your_fb_protocol_scheme 37 | YOUR_FACEBOOK_CLIENT_TOKEN 38 | User Controlled Wallet Sample App 39 | Choose one Auth Method to start 40 | Please configure OAuth credentials in the Console before testing the login flows. View docs for more guidance. 41 | docs 42 | 43 | Execute Challenge 44 | Enter Challenge ID to test wallet creation or signing flows. 45 | Log in with Email 46 | Login Successful 47 | Enable Biometrics Setting 48 | Please create PIN before setting up Biometrics 49 | Register a callback function is required during the actual implementation. 50 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 26 | 27 | 36 | 37 | 42 | 43 | 48 | 49 | 54 | 55 | 60 | 61 | 66 | 67 | 72 | 73 | 78 | 79 | 84 | 85 | 89 | 90 | 94 | 95 | 100 | 101 | 106 | 107 | 112 | 113 | 118 | 119 | 123 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/test/java/com/circle/w3s/sample/wallet/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, Circle Technologies, LLC. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.circle.w3s.sample.wallet 16 | 17 | import org.junit.Test 18 | 19 | import org.junit.Assert.* 20 | 21 | class ExampleUnitTest { 22 | @Test 23 | fun addition_isCorrect() { 24 | assertEquals(4, 2 + 2) 25 | } 26 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | dependencies { 4 | def nav_version = "2.5.3" 5 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" 6 | } 7 | } 8 | plugins { 9 | id 'com.android.application' version '8.0.2' apply false 10 | id 'com.android.library' version '8.0.2' apply false 11 | id 'org.jetbrains.kotlin.android' version '1.8.20' apply false 12 | id 'com.google.gms.google-services' version '4.4.0' apply false 13 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.enableR8.fullMode=false 25 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jun 26 20:35:41 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /readme_images/open_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/readme_images/open_project.png -------------------------------------------------------------------------------- /readme_images/run_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/readme_images/run_project.png -------------------------------------------------------------------------------- /readme_images/running_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/readme_images/running_app.png -------------------------------------------------------------------------------- /readme_images/running_app_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/readme_images/running_app_email.png -------------------------------------------------------------------------------- /readme_images/running_app_pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/readme_images/running_app_pin.png -------------------------------------------------------------------------------- /readme_images/running_app_social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/circlefin/w3s-android-sample-app-wallets/f4d0010e7deeddd9afd336ee13e0356ba84727d3/readme_images/running_app_social.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { 14 | Properties properties = new Properties() 15 | // Load local.properties. 16 | properties.load(new File(rootDir.absolutePath + "/local.properties").newDataInputStream()) 17 | url properties.getProperty('pwsdk.maven.url') 18 | credentials { 19 | username properties.getProperty('pwsdk.maven.username') 20 | password properties.getProperty('pwsdk.maven.password') 21 | } 22 | } 23 | } 24 | } 25 | rootProject.name = "W3s Sample Wallet" 26 | include ':app' 27 | --------------------------------------------------------------------------------