From 5498e788a546138dc38c3b96b61f872ed0c0b166 Mon Sep 17 00:00:00 2001 From: thesanjaysargam Date: Thu, 20 Nov 2025 09:43:12 +0530 Subject: [PATCH] feat: use compose instead of XML --- app/build.gradle | 24 +- .../com/solana/pay/sample/MainActivity.kt | 53 ++++- .../solana/pay/sample/SolanaPayActivity.kt | 218 ++++++++++++++---- app/src/main/res/layout/activity_main.xml | 23 -- .../main/res/layout/activity_solana_pay.xml | 129 ----------- 5 files changed, 243 insertions(+), 204 deletions(-) delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/activity_solana_pay.xml diff --git a/app/build.gradle b/app/build.gradle index 8719953..57ddff8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.compose' version '2.0.21' } android { @@ -27,9 +28,10 @@ android { } buildFeatures { - viewBinding true + compose true } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -41,12 +43,26 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.7.1' - implementation 'androidx.constraintlayout:constraintlayout:2.2.1' - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0' + // Compose BOM + implementation platform('androidx.compose:compose-bom:2024.10.01') + + // Core Android dependencies implementation 'androidx.core:core-ktx:1.17.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.9.4' + implementation 'androidx.appcompat:appcompat:1.7.1' + + // Compose dependencies + implementation 'androidx.activity:activity-compose:1.9.3' + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.compose.material3:material3' + implementation 'androidx.compose.material:material-icons-extended' + + // Material Components (needed for themes.xml) implementation 'com.google.android.material:material:1.13.0' + + // Other dependencies implementation 'com.solanamobile:digital-asset-links-android:1.0.4' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2' implementation project(path: ':pay') diff --git a/app/src/main/java/com/solana/pay/sample/MainActivity.kt b/app/src/main/java/com/solana/pay/sample/MainActivity.kt index 5728078..e15eb0c 100644 --- a/app/src/main/java/com/solana/pay/sample/MainActivity.kt +++ b/app/src/main/java/com/solana/pay/sample/MainActivity.kt @@ -8,15 +8,23 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContract +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.solana.pay.SolanaPayAndroidContract -import com.solana.pay.sample.databinding.ActivityMainBinding -class MainActivity : AppCompatActivity() { - private lateinit var viewBinding: ActivityMainBinding +class MainActivity : ComponentActivity() { private data class SolanaPayResult( val result: Int, @@ -42,12 +50,39 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewBinding = ActivityMainBinding.inflate(layoutInflater) - setContentView(viewBinding.root) + setContent { + MaterialTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + MainScreen( + onLaunchSolanaPay = { + // NOTE: this is an arbitrary address - please don't perform any real transfers to it! + solanaPayActivityResult.launch(Uri.parse("solana:84npKJKZy8ixjdq8UChZULDUea2Twt8ThxjiqKd7QZ54?amount=100&memo=Test%20xfer")) + } + ) + } + } + } + } +} - viewBinding.buttonInternalUri.setOnClickListener { - // NOTE: this is an arbitrary address - please don't perform any real transfers to it! - solanaPayActivityResult.launch(Uri.parse("solana:84npKJKZy8ixjdq8UChZULDUea2Twt8ThxjiqKd7QZ54?amount=100&memo=Test%20xfer")) +@Composable +fun MainScreen(onLaunchSolanaPay: () -> Unit) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(8.dp), + contentAlignment = Alignment.TopStart + ) { + Button( + onClick = onLaunchSolanaPay + ) { + Text( + text = stringResource(R.string.label_internal_solana_pay_uri), + fontSize = 22.sp + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/solana/pay/sample/SolanaPayActivity.kt b/app/src/main/java/com/solana/pay/sample/SolanaPayActivity.kt index ccc6b7f..c9f14ba 100644 --- a/app/src/main/java/com/solana/pay/sample/SolanaPayActivity.kt +++ b/app/src/main/java/com/solana/pay/sample/SolanaPayActivity.kt @@ -8,21 +8,31 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.util.Log -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.lifecycle.lifecycleScope import com.solana.digitalassetlinks.AndroidAppPackageVerifier import com.solana.pay.SolanaPayAndroidContract import com.solana.pay.SolanaPayTransactionRequest import com.solana.pay.SolanaPayURI -import com.solana.pay.sample.databinding.ActivitySolanaPayBinding import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.net.URI import kotlin.random.Random -class SolanaPayActivity : AppCompatActivity() { - private lateinit var viewBinding: ActivitySolanaPayBinding +class SolanaPayActivity : ComponentActivity() { private lateinit var entrypoint: Entrypoint private lateinit var sourceVerificationStatus: SourceVerification @@ -30,12 +40,11 @@ class SolanaPayActivity : AppCompatActivity() { private var verifier: AndroidAppPackageVerifier? = null + private val uiState = mutableStateOf(SolanaPayUiState()) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewBinding = ActivitySolanaPayBinding.inflate(layoutInflater) - setContentView(viewBinding.root) - setResult(Activity.RESULT_CANCELED) val uri = intent.data @@ -58,32 +67,40 @@ class SolanaPayActivity : AppCompatActivity() { deriveEntrypoint() doSourceVerification() - viewBinding.apply { - btnSimulateAuthorizeSubmit.setOnClickListener { - Log.d(TAG, "Simulating authorization and successful submission of transaction") - val result = Intent().putExtra(SolanaPayAndroidContract.EXTRA_SIGNATURE, createFakeTransactionSignatureBase58()) - setResult(Activity.RESULT_OK, result) - finish() - } - btnSimulateAuthorizeButSubmitError.setOnClickListener { - Log.d(TAG, "Simulating authorization and unsuccessful submission of transaction") - val result = Intent().putExtra(SolanaPayAndroidContract.EXTRA_SIGNATURE, createFakeTransactionSignatureBase58()) - setResult(SolanaPayAndroidContract.RESULT_FAILED, result) - finish() - } - btnSimulateNotAuthorized.setOnClickListener { - Log.d(TAG, "Simulating user declined to authorize transaction") - setResult(SolanaPayAndroidContract.RESULT_DECLINED) - finish() - } - btnSimulateWalletRejectsTransaction.setOnClickListener { - Log.d(TAG, "Simulating wallet failed to verify transaction validity") - setResult(SolanaPayAndroidContract.RESULT_NOT_VERIFIED) - finish() + setContent { + MaterialTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + SolanaPayScreen( + uiState = uiState.value, + onSimulateAuthorizeSubmit = { + Log.d(TAG, "Simulating authorization and successful submission of transaction") + val result = Intent().putExtra(SolanaPayAndroidContract.EXTRA_SIGNATURE, createFakeTransactionSignatureBase58()) + setResult(Activity.RESULT_OK, result) + finish() + }, + onSimulateAuthorizeButSubmitError = { + Log.d(TAG, "Simulating authorization and unsuccessful submission of transaction") + val result = Intent().putExtra(SolanaPayAndroidContract.EXTRA_SIGNATURE, createFakeTransactionSignatureBase58()) + setResult(SolanaPayAndroidContract.RESULT_FAILED, result) + finish() + }, + onSimulateNotAuthorized = { + Log.d(TAG, "Simulating user declined to authorize transaction") + setResult(SolanaPayAndroidContract.RESULT_DECLINED) + finish() + }, + onSimulateWalletRejectsTransaction = { + Log.d(TAG, "Simulating wallet failed to verify transaction validity") + setResult(SolanaPayAndroidContract.RESULT_NOT_VERIFIED) + finish() + } + ) + } } } - - updateUI() } override fun onDestroy() { @@ -175,16 +192,14 @@ class SolanaPayActivity : AppCompatActivity() { } private fun updateUI() { - viewBinding.apply { - tvEntrypointType.text = entrypoint.toString() - tvSourceVerification.text = sourceVerificationStatus.toString() - tvSolanaPayUri.text = solanaPayUri.uri.toString() - val en = sourceVerificationStatus in listOf( + uiState.value = SolanaPayUiState( + entrypointType = entrypoint.toString(), + sourceVerification = sourceVerificationStatus.toString(), + solanaPayUri = solanaPayUri.uri.toString(), + isButtonsEnabled = sourceVerificationStatus in listOf( SourceVerification.VERIFIED, SourceVerification.NOT_VERIFIABLE ) - btnSimulateAuthorizeSubmit.isEnabled = en - btnSimulateAuthorizeButSubmitError.isEnabled = en - } + ) } private fun createFakeTransactionSignatureBase58(): String = Base58EncodeUseCase(Random.Default.nextBytes(64)) @@ -200,4 +215,129 @@ class SolanaPayActivity : AppCompatActivity() { companion object { private val TAG = SolanaPayActivity::class.simpleName } +} + +data class SolanaPayUiState( + val entrypointType: String = "", + val sourceVerification: String = "", + val solanaPayUri: String = "", + val isButtonsEnabled: Boolean = false +) + + +@Composable +fun SolanaPayScreen( + uiState: SolanaPayUiState, + onSimulateAuthorizeSubmit: () -> Unit, + onSimulateAuthorizeButSubmitError: () -> Unit, + onSimulateNotAuthorized: () -> Unit, + onSimulateWalletRejectsTransaction: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(8.dp) + ) { + // Entrypoint Type Row + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.label_entrypoint_type), + fontSize = 26.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = uiState.entrypointType, + fontSize = 26.sp, + modifier = Modifier.weight(1f) + ) + } + + // Source Verification Row + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.label_source_verification), + fontSize = 26.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = uiState.sourceVerification, + fontSize = 26.sp, + modifier = Modifier.weight(1f) + ) + } + + // Solana Pay URI Section + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.label_solana_pay_uri), + fontSize = 26.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = uiState.solanaPayUri, + fontSize = 26.sp, + modifier = Modifier.fillMaxWidth() + ) + + // Simulation Options Section + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.label_simulation_options), + fontSize = 26.sp, + fontWeight = FontWeight.Bold + ) + + // Simulation Buttons + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = onSimulateAuthorizeSubmit, + enabled = uiState.isButtonsEnabled + ) { + Text( + text = stringResource(R.string.label_simulate_authorize_submit), + fontSize = 22.sp + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = onSimulateAuthorizeButSubmitError, + enabled = uiState.isButtonsEnabled + ) { + Text( + text = stringResource(R.string.label_simulate_authorize_but_submit_error), + fontSize = 22.sp + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = onSimulateNotAuthorized + ) { + Text( + text = stringResource(R.string.label_simulate_not_authorized), + fontSize = 22.sp + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + Button( + onClick = onSimulateWalletRejectsTransaction + ) { + Text( + text = stringResource(R.string.label_simulate_wallet_rejects_transaction), + fontSize = 22.sp + ) + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 3e9e08b..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_solana_pay.xml b/app/src/main/res/layout/activity_solana_pay.xml deleted file mode 100644 index 0999a8e..0000000 --- a/app/src/main/res/layout/activity_solana_pay.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file