taler-code-examples

Reference examples (sample code)
Log | Files | Refs | README | LICENSE

UsageExamples.kt (18640B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2024 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.anastasistestdriverapp.components
     18 
     19 import android.app.PendingIntent
     20 import android.content.ClipData
     21 import android.content.Intent
     22 import android.net.Uri
     23 import androidx.activity.compose.rememberLauncherForActivityResult
     24 import androidx.activity.result.contract.ActivityResultContracts
     25 import androidx.compose.foundation.layout.Arrangement
     26 import androidx.compose.foundation.layout.Column
     27 import androidx.compose.foundation.layout.fillMaxSize
     28 import androidx.compose.foundation.layout.fillMaxWidth
     29 import androidx.compose.foundation.layout.padding
     30 import androidx.compose.foundation.text.selection.SelectionContainer
     31 import androidx.compose.material3.Button
     32 import androidx.compose.material3.MaterialTheme
     33 import androidx.compose.material3.OutlinedButton
     34 import androidx.compose.material3.Text
     35 import androidx.compose.material3.TextField
     36 import androidx.compose.runtime.Composable
     37 import androidx.compose.runtime.derivedStateOf
     38 import androidx.compose.runtime.getValue
     39 import androidx.compose.runtime.mutableStateOf
     40 import androidx.compose.runtime.remember
     41 import androidx.compose.runtime.setValue
     42 import androidx.compose.ui.Alignment
     43 import androidx.compose.ui.Modifier
     44 import androidx.compose.ui.graphics.Color
     45 import androidx.compose.ui.platform.LocalContext
     46 import androidx.compose.ui.text.TextStyle
     47 import androidx.compose.ui.text.font.FontWeight
     48 import androidx.compose.ui.text.style.TextAlign
     49 import androidx.compose.ui.unit.dp
     50 import androidx.compose.ui.unit.sp
     51 import androidx.core.content.FileProvider
     52 import net.taler.anastasistestdriverapp.utils.Utils
     53 import java.io.File
     54 
     55 @Composable
     56 fun TextExample(modifier: Modifier = Modifier) {
     57     var appId by remember {
     58         mutableStateOf("net.taler.anastasistestdriverapp")
     59     }
     60 
     61     var appName by remember {
     62         mutableStateOf("Gmail App")
     63     }
     64 
     65     var secretName by remember {
     66         mutableStateOf("Account password")
     67     }
     68 
     69     var secret by remember {
     70         mutableStateOf("123456")
     71     }
     72 
     73     var expirationDateTime by remember {
     74         mutableStateOf("2030-01-01T08:30:15")
     75     }
     76 
     77     val uri by remember {
     78         derivedStateOf {
     79             val uriBuilder = Uri.Builder()
     80                 .scheme("anastasis")
     81                 .authority("backup")
     82                 .appendEncodedPath(appId) // appId is actually not encoded and we don't want to.
     83                 .appendPath(secretName)
     84                 .appendQueryParameter(
     85                     "secret",
     86                     Utils.encodeSecretToBase32(secret.toByteArray())
     87                 )
     88             if (appName.isNotBlank()) {
     89                 uriBuilder.appendQueryParameter("app_name", appName)
     90             }
     91             if (expirationDateTime.isNotBlank()) {
     92                 uriBuilder.appendQueryParameter("expiration_datetime", expirationDateTime)
     93             }
     94             uriBuilder.build()
     95         }
     96     }
     97 
     98     var backupResult by remember { mutableStateOf("") }
     99     val anastasisLauncher = rememberLauncherForActivityResult(
    100         contract = ActivityResultContracts.StartActivityForResult()
    101     ) { activityResult ->
    102         val data = activityResult.data?.data?.pathSegments
    103         when (activityResult.resultCode) {
    104             0 -> backupResult = "User aborted the process"
    105             1 -> backupResult = "Invalid input: ${data!![1]}"
    106             2 -> backupResult = "Internal error occurred"
    107             100 -> backupResult = "Backup succeeded: \n" +
    108                     "(secret name: ${data!![1]}) \n" +
    109                     "(expiration date: ${data[2]})"
    110         }
    111     }
    112 
    113     Column(
    114         horizontalAlignment = Alignment.CenterHorizontally,
    115         verticalArrangement = Arrangement.spacedBy(16.dp),
    116         modifier = modifier.fillMaxSize()
    117     ) {
    118         Text(
    119             "Text Example",
    120             style = MaterialTheme.typography.headlineMedium,
    121             fontWeight = FontWeight.Bold,
    122             color = Color.Blue,
    123             modifier = Modifier.padding(16.dp),
    124         )
    125 
    126         TextField(
    127             label = {
    128                 Text(text = "App ID")
    129             },
    130             value = appId,
    131             onValueChange = { appId = it },
    132             textStyle = TextStyle(fontSize = 16.sp),
    133             modifier = Modifier.fillMaxWidth(),
    134         )
    135 
    136         TextField(
    137             label = {
    138                 Text(text = "App Name (Optional)")
    139             },
    140             value = appName,
    141             onValueChange = { appName = it },
    142             textStyle = TextStyle(fontSize = 16.sp),
    143             modifier = Modifier.fillMaxWidth(),
    144         )
    145 
    146         TextField(
    147             label = {
    148                 Text(text = "Secret Name")
    149             },
    150             value = secretName,
    151             onValueChange = { secretName = it },
    152             textStyle = TextStyle(fontSize = 16.sp),
    153             modifier = Modifier.fillMaxWidth(),
    154         )
    155 
    156         TextField(
    157             label = {
    158                 Text(text = "Secret")
    159             },
    160             value = secret,
    161             onValueChange = { secret = it },
    162             textStyle = TextStyle(fontSize = 16.sp),
    163             modifier = Modifier.fillMaxWidth(),
    164         )
    165 
    166         TextField(
    167             label = {
    168                 Text(text = "Expiration date-time (Optional)")
    169             },
    170             value = expirationDateTime,
    171             onValueChange = { expirationDateTime = it },
    172             textStyle = TextStyle(fontSize = 16.sp),
    173             modifier = Modifier.fillMaxWidth(),
    174         )
    175 
    176         Text(
    177             "URI:",
    178             textAlign = TextAlign.Center,
    179             fontWeight = FontWeight.Bold,
    180             modifier = Modifier.padding(horizontal = 8.dp)
    181         )
    182         Text(
    183             "$uri",
    184             textAlign = TextAlign.Center,
    185             modifier = Modifier.padding(horizontal = 8.dp)
    186         )
    187 
    188         val context = LocalContext.current
    189         Button(
    190             onClick = {
    191                 val intent = Intent("anastasis.intent.action.backup").run {
    192                     data = uri
    193                     val pendingIntent = PendingIntent.getActivity(
    194                         context,
    195                         0,
    196                         Intent(),
    197                         PendingIntent.FLAG_IMMUTABLE
    198                     )
    199                     putExtra("pending_intent", pendingIntent)
    200                     Intent.createChooser(this, "")
    201                 }
    202                 anastasisLauncher.launch(intent)
    203             },
    204         ) {
    205             Text("Back up using Anastasis")
    206         }
    207 
    208         Text(
    209             text = backupResult,
    210             fontSize = 16.sp,
    211             textAlign = TextAlign.Center,
    212             modifier = Modifier
    213                 .padding(horizontal = 16.dp)
    214                 .fillMaxWidth()
    215         )
    216     }
    217 }
    218 
    219 @Composable
    220 fun ExternalFileExample(modifier: Modifier = Modifier) {
    221     var secretFileContentUri by remember { mutableStateOf<Uri?>(null) }
    222     val uri by remember {
    223         derivedStateOf {
    224             Uri.Builder()
    225                 .scheme("anastasis")
    226                 .authority("backup")
    227                 .appendEncodedPath("net.taler.anastasistestdriverapp") // appId is actually not encoded and we don't want to.
    228                 .appendPath("Account Credentials")
    229                 .build()
    230         }
    231     }
    232 
    233     var backupResult by remember { mutableStateOf("") }
    234     val anastasisLauncher = rememberLauncherForActivityResult(
    235         contract = ActivityResultContracts.StartActivityForResult()
    236     ) { activityResult ->
    237         val data = activityResult.data?.data?.pathSegments
    238         when (activityResult.resultCode) {
    239             0 -> backupResult = "User aborted the process"
    240             1 -> backupResult = "Invalid input: ${data!![1]}"
    241             2 -> backupResult = "Internal error occurred"
    242             100 -> backupResult = "Backup succeeded: \n" +
    243                     "(secret name: ${data!![1]}) \n" +
    244                     "(expiration date: ${data[2]})"
    245         }
    246     }
    247     val filePickerLauncher = rememberLauncherForActivityResult(
    248         contract = ActivityResultContracts.GetContent(),
    249         onResult = {
    250             it?.let { uri -> secretFileContentUri = uri }
    251         }
    252     )
    253 
    254     Column(
    255         horizontalAlignment = Alignment.CenterHorizontally,
    256         verticalArrangement = Arrangement.spacedBy(16.dp),
    257         modifier = modifier.fillMaxWidth()
    258     ) {
    259 
    260         Text(
    261             "External File Example",
    262             style = MaterialTheme.typography.headlineMedium,
    263             fontWeight = FontWeight.Bold,
    264             color = Color.Blue,
    265             modifier = Modifier.padding(horizontal = 16.dp),
    266         )
    267 
    268         OutlinedButton(
    269             onClick = { filePickerLauncher.launch("*/*") },
    270             shape = MaterialTheme.shapes.small,
    271             modifier = Modifier.padding(top = 16.dp)
    272         ) {
    273             Text("Pick a file")
    274         }
    275 
    276         SelectionContainer {
    277             Text(
    278                 text = secretFileContentUri?.toString() ?: "No selected file",
    279                 fontSize = 16.sp,
    280                 textAlign = TextAlign.Center,
    281                 modifier = Modifier
    282                     .padding(horizontal = 16.dp)
    283                     .fillMaxWidth()
    284             )
    285         }
    286 
    287         val context = LocalContext.current
    288         Button(
    289             onClick = {
    290                 val intent = Intent("anastasis.intent.action.backup").run {
    291                     data = uri
    292                     clipData = ClipData.newRawUri("", secretFileContentUri)
    293                     addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    294                     val pendingIntent = PendingIntent.getActivity(
    295                         context,
    296                         0,
    297                         Intent(),
    298                         PendingIntent.FLAG_IMMUTABLE
    299                     )
    300                     putExtra("pending_intent", pendingIntent)
    301                     Intent.createChooser(this, "")
    302                 }
    303                 anastasisLauncher.launch(intent)
    304             },
    305         ) {
    306             Text("Back up using Anastasis")
    307         }
    308 
    309         Text(
    310             text = backupResult,
    311             fontSize = 16.sp,
    312             textAlign = TextAlign.Center,
    313             modifier = Modifier
    314                 .padding(horizontal = 16.dp)
    315                 .fillMaxWidth()
    316         )
    317     }
    318 }
    319 
    320 @Composable
    321 fun OwnFileExample(modifier: Modifier = Modifier) {
    322     val context = LocalContext.current
    323     val uri by remember {
    324         derivedStateOf {
    325             Uri.Builder()
    326                 .scheme("anastasis")
    327                 .authority("backup")
    328                 .appendEncodedPath("net.taler.anastasistestdriverapp") // appId is actually not encoded and we don't want to.
    329                 .appendPath("Account password")
    330                 .build()
    331         }
    332     }
    333 
    334     var backupResult by remember { mutableStateOf("") }
    335     val anastasisLauncher = rememberLauncherForActivityResult(
    336         contract = ActivityResultContracts.StartActivityForResult()
    337     ) { activityResult ->
    338         val data = activityResult.data?.data?.pathSegments
    339         when (activityResult.resultCode) {
    340             0 -> backupResult = "User aborted the process"
    341             1 -> backupResult = "Invalid input: ${data!![1]}"
    342             2 -> backupResult = "Internal error occurred"
    343             100 -> backupResult = "Backup succeeded: \n" +
    344                     "(secret name: ${data!![1]}) \n" +
    345                     "(expiration date: ${data!![2]})"
    346         }
    347     }
    348 
    349     Column(
    350         horizontalAlignment = Alignment.CenterHorizontally,
    351         verticalArrangement = Arrangement.spacedBy(16.dp),
    352         modifier = modifier.fillMaxWidth()
    353     ) {
    354         Text(
    355             "Own File Example",
    356             style = MaterialTheme.typography.headlineMedium,
    357             fontWeight = FontWeight.Bold,
    358             color = Color.Blue,
    359             modifier = Modifier.padding(horizontal = 16.dp),
    360         )
    361 
    362         val file = File(context.cacheDir, "example-secret-file.txt")
    363         file.writeText(
    364             "Long secret data included in the file.\n" +
    365                     "Email: TestAcc@gmail.com\n" +
    366                     "Password: 123456789"
    367         )
    368 
    369         val secretFileContentUri = FileProvider.getUriForFile(
    370             context,
    371             "net.taler.anastasistestdriverapp.fileprovider",
    372             file,
    373         )
    374 
    375         SelectionContainer {
    376             Text(
    377                 text = secretFileContentUri!!.toString(),
    378                 fontSize = 16.sp,
    379                 textAlign = TextAlign.Center,
    380                 modifier = Modifier
    381                     .padding(16.dp)
    382                     .fillMaxWidth()
    383             )
    384         }
    385 
    386         Button(
    387             onClick = {
    388                 val intent = Intent("anastasis.intent.action.backup").run {
    389                     data = uri
    390                     clipData = ClipData.newRawUri("", secretFileContentUri)
    391                     addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    392                     val pendingIntent = PendingIntent.getActivity(
    393                         context,
    394                         0,
    395                         Intent(),
    396                         PendingIntent.FLAG_IMMUTABLE
    397                     )
    398                     putExtra("pending_intent", pendingIntent)
    399                     Intent.createChooser(this, "")
    400                 }
    401                 anastasisLauncher.launch(intent)
    402             },
    403         ) {
    404             Text("Back up using Anastasis")
    405         }
    406 
    407         Text(
    408             text = backupResult,
    409             fontSize = 16.sp,
    410             textAlign = TextAlign.Center,
    411             modifier = Modifier
    412                 .padding(horizontal = 16.dp)
    413                 .fillMaxWidth()
    414         )
    415     }
    416 }
    417 
    418 @Composable
    419 fun RecoveryExample(modifier: Modifier = Modifier) {
    420     var appId by remember {
    421         mutableStateOf("net.taler.anastasistestdriverapp")
    422     }
    423 
    424     var appName by remember {
    425         mutableStateOf("Gmail App")
    426     }
    427 
    428     var secretName by remember {
    429         mutableStateOf("Account password")
    430     }
    431 
    432     val uri by remember {
    433         derivedStateOf {
    434             val uriBuilder = Uri.Builder()
    435                 .scheme("anastasis")
    436                 .authority("recovery")
    437             if (appId.isNotBlank()) {
    438                 uriBuilder.appendEncodedPath(appId)  // appId is actually not encoded and we don't want to.
    439             }
    440             if (appId.isNotBlank() && secretName.isNotBlank()) {
    441                 uriBuilder.appendPath(secretName)
    442             }
    443             if (appName.isNotBlank()) {
    444                 uriBuilder.appendQueryParameter("app_name", appName)
    445             }
    446             uriBuilder.build()
    447         }
    448     }
    449 
    450     var recoveryResult by remember { mutableStateOf("") }
    451     val anastasisLauncher = rememberLauncherForActivityResult(
    452         contract = ActivityResultContracts.StartActivityForResult()
    453     ) { activityResult ->
    454         val data = activityResult.data?.data?.pathSegments
    455         when (activityResult.resultCode) {
    456             0 -> recoveryResult = "User aborted the process"
    457             1 -> recoveryResult = "Invalid input: ${data!![1]}"
    458             2 -> recoveryResult = "Internal error occurred"
    459             3 -> recoveryResult = "Secret not found."
    460             101 -> {
    461                 val secretUri = activityResult.data?.clipData?.getItemAt(0)?.uri
    462                 recoveryResult = "Recovered secret Content URI: $secretUri\n" +
    463                         "File name: ${Utils.resolveDocFilename(secretUri!!)}\n"
    464             }
    465         }
    466     }
    467 
    468     Column(
    469         horizontalAlignment = Alignment.CenterHorizontally,
    470         verticalArrangement = Arrangement.spacedBy(16.dp),
    471         modifier = modifier.fillMaxSize()
    472     ) {
    473         Text(
    474             "Recovery Example",
    475             style = MaterialTheme.typography.headlineMedium,
    476             fontWeight = FontWeight.Bold,
    477             color = Color.Blue,
    478             modifier = Modifier.padding(16.dp),
    479         )
    480 
    481         TextField(
    482             label = {
    483                 Text(text = "App ID")
    484             },
    485             value = appId,
    486             onValueChange = { appId = it },
    487             textStyle = TextStyle(fontSize = 16.sp),
    488             modifier = Modifier.fillMaxWidth(),
    489         )
    490 
    491         TextField(
    492             label = {
    493                 Text(text = "App Name (Optional)")
    494             },
    495             value = appName,
    496             onValueChange = { appName = it },
    497             textStyle = TextStyle(fontSize = 16.sp),
    498             modifier = Modifier.fillMaxWidth(),
    499         )
    500 
    501         TextField(
    502             label = {
    503                 Text(text = "Secret Name")
    504             },
    505             value = secretName,
    506             onValueChange = { secretName = it },
    507             textStyle = TextStyle(fontSize = 16.sp),
    508             modifier = Modifier.fillMaxWidth(),
    509         )
    510 
    511         Text(
    512             "URI:",
    513             textAlign = TextAlign.Center,
    514             fontWeight = FontWeight.Bold,
    515             modifier = Modifier.padding(horizontal = 8.dp)
    516         )
    517         Text(
    518             "$uri",
    519             textAlign = TextAlign.Center,
    520             modifier = Modifier.padding(horizontal = 8.dp)
    521         )
    522 
    523         val context = LocalContext.current
    524         Button(
    525             onClick = {
    526                 val intent = Intent("anastasis.intent.action.recovery").run {
    527                     data = uri
    528                     val pendingIntent = PendingIntent.getActivity(
    529                         context,
    530                         0,
    531                         Intent(),
    532                         PendingIntent.FLAG_IMMUTABLE
    533                     )
    534                     putExtra("pending_intent", pendingIntent)
    535                     Intent.createChooser(this, "")
    536                 }
    537                 anastasisLauncher.launch(intent)
    538             },
    539         ) {
    540             Text("Recover using Anastasis")
    541         }
    542 
    543         Text(
    544             text = recoveryResult,
    545             fontSize = 16.sp,
    546             textAlign = TextAlign.Center,
    547             modifier = Modifier
    548                 .padding(horizontal = 16.dp)
    549                 .fillMaxWidth()
    550         )
    551     }
    552 }