diff options
author | Torsten Grote <t@grobox.de> | 2020-03-18 14:24:41 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-03-18 14:24:41 -0300 |
commit | a4796ec47d89a851b260b6fc195494547208a025 (patch) | |
tree | d2941b68ff2ce22c523e7aa634965033b1100560 /wallet | |
download | taler-android-a4796ec47d89a851b260b6fc195494547208a025.tar.gz taler-android-a4796ec47d89a851b260b6fc195494547208a025.tar.bz2 taler-android-a4796ec47d89a851b260b6fc195494547208a025.zip |
Merge all three apps into one repository
Diffstat (limited to 'wallet')
114 files changed, 7933 insertions, 0 deletions
diff --git a/wallet/.gitignore b/wallet/.gitignore new file mode 100644 index 0000000..741e19c --- /dev/null +++ b/wallet/.gitignore @@ -0,0 +1,2 @@ +/build +/src/main/assets diff --git a/wallet/.gitlab-ci.yml b/wallet/.gitlab-ci.yml new file mode 100644 index 0000000..4c1f9a8 --- /dev/null +++ b/wallet/.gitlab-ci.yml @@ -0,0 +1,42 @@ +.binary_deps: + only: + changes: + - "wallet" + before_script: + - wget "https://git.taler.net/wallet-android.git/plain/akono.aar?h=binary-deps" -O akono/akono.aar + - mkdir -p app/src/main/assets + - wget "https://git.taler.net/wallet-android.git/plain/taler-wallet-android.js?h=binary-deps" -O app/src/main/assets/taler-wallet-android.js + +wallet_test: + stage: test + extends: .binary_deps + script: ./gradlew :wallet:lint :wallet:assembleRelease + +wallet_deploy_nightly: + stage: deploy + extends: .binary_deps + only: + refs: + - master + script: + # Ensure that key exists + - test -z "$DEBUG_KEYSTORE" && exit 0 + # Rename nightly app + - sed -i + 's,<string name="app_name">.*</string>,<string name="app_name">Taler Wallet Nightly</string>,' + wallet/src/main/res/values*/strings.xml + # Set time-based version code + - export versionCode=$(date '+%s') + - sed -i "s,^\(\s*versionCode\) *[0-9].*,\1 $versionCode," wallet/build.gradle + # Add commit to version name + - export versionName=$(git rev-parse --short=7 HEAD) + - sed -i "s,^\(\s*versionName\ *\"[0-9].*\)\",\1 ($versionName)\"," wallet/build.gradle + # Set nightly application ID + - sed -i "s,^\(\s*applicationId\) \"*[a-z\.].*\",\1 \"net.taler.wallet.nightly\"," wallet/build.gradle + # Build the APK + - ./gradlew :wallet:assembleDebug + # START only needed while patch not accepted/released upstream + - apt update && apt install patch + - patch /usr/lib/python3/dist-packages/fdroidserver/nightly.py nightly-stats.patch + # END + - CI_PROJECT_URL="https://gitlab.com/gnu-taler/fdroid-repo" CI_PROJECT_PATH="gnu-taler/fdroid-repo" fdroid nightly -v diff --git a/wallet/README.md b/wallet/README.md new file mode 100644 index 0000000..63b128b --- /dev/null +++ b/wallet/README.md @@ -0,0 +1,40 @@ +GNU Taler Wallet +================ + +This package implements a GNU Taler wallet for Android. +It is currently a UI for the wallet writen in TypeScript. + + +Building +======== + +Currently, building the wallet for Android requires manually copying two +dependencies: + +`akono.aar` -> `../akono/akono.aar` +`taler-wallet-android.js` -> `src/main/assets/taler-wallet-android.js` + +After that, the Android wallet can be built with Gradle: + + $ ./gradlew build + + +Obtaining Dependencies +====================== + +There are two ways of obtaining the dependencies. The easiest one is +to use the pre-built versions, which are stored in the "binary-deps" +branch of this repository. + +An easy way to access them is using a git worktree: + + $ git fetch origin binary-deps + $ git worktree add binary-deps binary-deps + $ cp binary-deps/akono.aar ../akono/akono.aar + $ cp binary-deps/taler-wallet-android.js src/main/assets/taler-wallet-android.js + $ git worktree remove binary-deps + +Alternatively, you can build them yourself from the respective repositories: + + * git://git.taler.net/akono.git + * git://git.taler.net/wallet-core.git diff --git a/wallet/build.gradle b/wallet/build.gradle new file mode 100644 index 0000000..c31e392 --- /dev/null +++ b/wallet/build.gradle @@ -0,0 +1,81 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + defaultConfig { + applicationId "net.taler.wallet" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 6 + versionName "0.6.0pre8" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation project(":akono") + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + // Navigation Library + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + + // ViewModel and LiveData + def lifecycle_version = "2.2.0" + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + + // QR codes + implementation 'com.google.zxing:core:3.4.0' + implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar' + + // Nicer ProgressBar + implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' + + // JSON parsing and serialization + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2' + + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} diff --git a/wallet/proguard-rules.pro b/wallet/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/wallet/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/wallet/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt b/wallet/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..5f0c423 --- /dev/null +++ b/wallet/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt @@ -0,0 +1,38 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("net.taler.wallet", appContext.packageName) + } +} diff --git a/wallet/src/main/AndroidManifest.xml b/wallet/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a61483d --- /dev/null +++ b/wallet/src/main/AndroidManifest.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="net.taler.wallet"> + + <uses-permission android:name="android.permission.NFC" /> + + <uses-feature + android:name="android.hardware.telephony" + android:required="false" /> + <uses-feature + android:name="android.hardware.nfc.hce" + android:required="false" /> + + <application + android:allowBackup="true" + android:fullBackupContent="@xml/backup_descriptor" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> + + <activity + android:name=".MainActivity" + android:label="@string/app_name" + android:theme="@style/AppTheme.NoActionBar"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:scheme="taler" /> + </intent-filter> + </activity> + + <activity + android:name="com.journeyapps.barcodescanner.CaptureActivity" + android:screenOrientation="fullSensor" + tools:replace="screenOrientation" /> + + <service + android:name=".HostCardEmulatorService" + android:exported="true" + android:permission="android.permission.BIND_NFC_SERVICE"> + <intent-filter> + <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" /> + </intent-filter> + + <meta-data + android:name="android.nfc.cardemulation.host_apdu_service" + android:resource="@xml/apduservice" /> + </service> + + <service + android:name=".backend.WalletBackendService" + android:process=":WalletBackendService" /> + </application> + +</manifest> diff --git a/wallet/src/main/ic_launcher-web.png b/wallet/src/main/ic_launcher-web.png Binary files differnew file mode 100644 index 0000000..f0f6be7 --- /dev/null +++ b/wallet/src/main/ic_launcher-web.png diff --git a/wallet/src/main/java/net/taler/wallet/Amount.kt b/wallet/src/main/java/net/taler/wallet/Amount.kt new file mode 100644 index 0000000..a19e9bc --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/Amount.kt @@ -0,0 +1,141 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package net.taler.wallet + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import org.json.JSONObject +import kotlin.math.round + +private const val FRACTIONAL_BASE = 1e8 + +@JsonDeserialize(using = AmountDeserializer::class) +data class Amount(val currency: String, val amount: String) { + fun isZero(): Boolean { + return amount.toDouble() == 0.0 + } + + companion object { + fun fromJson(jsonAmount: JSONObject): Amount { + val amountCurrency = jsonAmount.getString("currency") + val amountValue = jsonAmount.getString("value") + val amountFraction = jsonAmount.getString("fraction") + val amountIntValue = Integer.parseInt(amountValue) + val amountIntFraction = Integer.parseInt(amountFraction) + return Amount( + amountCurrency, + (amountIntValue + amountIntFraction / FRACTIONAL_BASE).toString() + ) + } + + fun fromString(strAmount: String): Amount { + val components = strAmount.split(":") + return Amount(components[0], components[1]) + } + } + + override fun toString(): String { + return String.format("%.2f $currency", amount.toDouble()) + } +} + +class AmountDeserializer : StdDeserializer<Amount>(Amount::class.java) { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Amount { + val node = p.codec.readValue(p, String::class.java) + return Amount.fromString(node) + } +} + +class ParsedAmount( + /** + * name of the currency using either a three-character ISO 4217 currency code, + * or a regional currency identifier starting with a "*" followed by at most 10 characters. + * ISO 4217 exponents in the name are not supported, + * although the "fraction" is corresponds to an ISO 4217 exponent of 6. + */ + val currency: String, + + /** + * unsigned 32 bit value in the currency, + * note that "1" here would correspond to 1 EUR or 1 USD, depending on currency, not 1 cent. + */ + val value: UInt, + + /** + * unsigned 32 bit fractional value to be added to value + * representing an additional currency fraction, + * in units of one millionth (1e-6) of the base currency value. + * For example, a fraction of 500,000 would correspond to 50 cents. + */ + val fraction: Double +) { + companion object { + fun parseAmount(str: String): ParsedAmount { + val split = str.split(":") + check(split.size == 2) + val currency = split[0] + val valueSplit = split[1].split(".") + val value = valueSplit[0].toUInt() + val fraction: Double = if (valueSplit.size > 1) { + round("0.${valueSplit[1]}".toDouble() * FRACTIONAL_BASE) + } else 0.0 + return ParsedAmount(currency, value, fraction) + } + } + + operator fun minus(other: ParsedAmount): ParsedAmount { + check(currency == other.currency) { "Can only subtract from same currency" } + var resultValue = value + var resultFraction = fraction + if (resultFraction < other.fraction) { + if (resultValue < 1u) { + return ParsedAmount(currency, 0u, 0.0) + } + resultValue-- + resultFraction += FRACTIONAL_BASE + } + check(resultFraction >= other.fraction) + resultFraction -= other.fraction + if (resultValue < other.value) { + return ParsedAmount(currency, 0u, 0.0) + } + resultValue -= other.value + return ParsedAmount(currency, resultValue, resultFraction) + } + + fun isZero(): Boolean { + return value == 0u && fraction == 0.0 + } + + @Suppress("unused") + fun toJSONString(): String { + return "$currency:${getValueString()}" + } + + override fun toString(): String { + return "${getValueString()} $currency" + } + + private fun getValueString(): String { + return "$value${(fraction / FRACTIONAL_BASE).toString().substring(1)}" + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt new file mode 100644 index 0000000..84a1b3c --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/BalanceFragment.kt @@ -0,0 +1,198 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import android.os.Bundle +import android.transition.TransitionManager.beginDelayedTransition +import android.util.Log +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.google.zxing.integration.android.IntentIntegrator +import com.google.zxing.integration.android.IntentIntegrator.QR_CODE_TYPES +import kotlinx.android.synthetic.main.fragment_show_balance.* +import net.taler.wallet.BalanceAdapter.BalanceViewHolder + +class BalanceFragment : Fragment() { + + private val model: WalletViewModel by activityViewModels() + private val withdrawManager by lazy { model.withdrawManager } + + private var reloadBalanceMenuItem: MenuItem? = null + private val balancesAdapter = BalanceAdapter() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_show_balance, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + balancesList.apply { + layoutManager = LinearLayoutManager(context) + adapter = balancesAdapter + addItemDecoration(DividerItemDecoration(context, VERTICAL)) + } + + model.balances.observe(viewLifecycleOwner, Observer { + onBalancesChanged(it) + }) + + model.devMode.observe(viewLifecycleOwner, Observer { enabled -> + delayedTransition() + testWithdrawButton.visibility = if (enabled) VISIBLE else GONE + reloadBalanceMenuItem?.isVisible = enabled + }) + testWithdrawButton.setOnClickListener { + withdrawManager.withdrawTestkudos() + } + withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, Observer { loading -> + Log.v("taler-wallet", "observing balance loading $loading in show balance") + testWithdrawButton.isEnabled = !loading + model.showProgressBar.value = loading + }) + + scanButton.setOnClickListener { + IntentIntegrator(activity).apply { + setPrompt("") + setBeepEnabled(true) + setOrientationLocked(false) + }.initiateScan(QR_CODE_TYPES) + } + } + + override fun onStart() { + super.onStart() + model.loadBalances() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.reload_balance -> { + model.loadBalances() + true + } + R.id.developer_mode -> { + item.isChecked = !item.isChecked + model.devMode.value = item.isChecked + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.balance, menu) + menu.findItem(R.id.developer_mode).isChecked = model.devMode.value!! + reloadBalanceMenuItem = menu.findItem(R.id.reload_balance).apply { + isVisible = model.devMode.value!! + } + super.onCreateOptionsMenu(menu, inflater) + } + + private fun onBalancesChanged(balances: List<BalanceItem>) { + delayedTransition() + if (balances.isEmpty()) { + balancesEmptyState.visibility = VISIBLE + balancesList.visibility = GONE + } else { + balancesAdapter.setItems(balances) + balancesEmptyState.visibility = GONE + balancesList.visibility = VISIBLE + } + } + + private fun delayedTransition() { + beginDelayedTransition(view as ViewGroup) + } + +} + +class BalanceAdapter : Adapter<BalanceViewHolder>() { + + private var items = emptyList<BalanceItem>() + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalanceViewHolder { + val v = + LayoutInflater.from(parent.context).inflate(R.layout.list_item_balance, parent, false) + return BalanceViewHolder(v) + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder(holder: BalanceViewHolder, position: Int) { + val item = items[position] + holder.bind(item) + } + + fun setItems(items: List<BalanceItem>) { + this.items = items + this.notifyDataSetChanged() + } + + class BalanceViewHolder(private val v: View) : ViewHolder(v) { + private val currencyView: TextView = v.findViewById(R.id.balance_currency) + private val amountView: TextView = v.findViewById(R.id.balance_amount) + private val balanceInboundAmount: TextView = v.findViewById(R.id.balanceInboundAmount) + private val balanceInboundLabel: TextView = v.findViewById(R.id.balanceInboundLabel) + + fun bind(item: BalanceItem) { + currencyView.text = item.available.currency + amountView.text = item.available.amount + + val amountIncoming = item.pendingIncoming + if (amountIncoming.isZero()) { + balanceInboundAmount.visibility = GONE + balanceInboundLabel.visibility = GONE + } else { + balanceInboundAmount.visibility = VISIBLE + balanceInboundLabel.visibility = VISIBLE + balanceInboundAmount.text = v.context.getString( + R.string.balances_inbound_amount, + amountIncoming.amount, + amountIncoming.currency + ) + } + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/HostCardEmulatorService.kt b/wallet/src/main/java/net/taler/wallet/HostCardEmulatorService.kt new file mode 100644 index 0000000..93f1d3f --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/HostCardEmulatorService.kt @@ -0,0 +1,187 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import android.nfc.cardemulation.HostApduService +import android.os.Bundle +import android.util.Log +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.concurrent.ConcurrentLinkedDeque + +fun makeApduSuccessResponse(payload: ByteArray): ByteArray { + val stream = ByteArrayOutputStream() + stream.write(payload) + stream.write(0x90) + stream.write(0x00) + return stream.toByteArray() +} + + +fun makeApduFailureResponse(): ByteArray { + val stream = ByteArrayOutputStream() + stream.write(0x6F) + stream.write(0x00) + return stream.toByteArray() +} + + +fun readApduBodySize(stream: ByteArrayInputStream): Int { + val b0 = stream.read() + if (b0 == -1) { + return 0 + } + if (b0 != 0) { + return b0 + } + val b1 = stream.read() + val b2 = stream.read() + + return (b1 shl 8) and b2 +} + + +class HostCardEmulatorService: HostApduService() { + + val queuedRequests: ConcurrentLinkedDeque<String> = ConcurrentLinkedDeque() + private lateinit var receiver: BroadcastReceiver + + override fun onCreate() { + super.onCreate() + receiver = object : BroadcastReceiver() { + override fun onReceive(p0: Context?, p1: Intent?) { + queuedRequests.addLast(p1!!.getStringExtra("tunnelMessage")) + } + } + IntentFilter(HTTP_TUNNEL_REQUEST).also { filter -> + registerReceiver(receiver, filter) + } + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(receiver) + } + + override fun onDeactivated(reason: Int) { + Log.d(TAG, "Deactivated: $reason") + Intent().also { intent -> + intent.action = MERCHANT_NFC_DISCONNECTED + sendBroadcast(intent) + } + } + + override fun processCommandApdu(commandApdu: ByteArray?, + extras: Bundle?): ByteArray { + + Log.d(TAG, "Processing command APDU") + + if (commandApdu == null) { + Log.d(TAG, "APDU is null") + return makeApduFailureResponse() + } + + val stream = ByteArrayInputStream(commandApdu) + + val command = stream.read() + + if (command != 0) { + Log.d(TAG, "APDU has invalid command") + return makeApduFailureResponse() + } + + val instruction = stream.read() + + // Read instruction parameters, currently ignored. + stream.read() + stream.read() + + if (instruction == SELECT_INS) { + // FIXME: validate body! + return makeApduSuccessResponse(ByteArray(0)) + } + + if (instruction == GET_INS) { + val req = queuedRequests.poll() + return if (req != null) { + Log.v(TAG,"sending tunnel request") + makeApduSuccessResponse(req.toByteArray(Charsets.UTF_8)) + } else { + makeApduSuccessResponse(ByteArray(0)) + } + } + + if (instruction == PUT_INS) { + val bodySize = readApduBodySize(stream) + val talerInstr = stream.read() + val bodyBytes = stream.readBytes() + if (1 + bodyBytes.size != bodySize) { + Log.w(TAG, "mismatched body size ($bodySize vs ${bodyBytes.size}") + } + + when (talerInstr) { + 1 -> { + val url = String(bodyBytes, Charsets.UTF_8) + Log.v(TAG, "got URL: '$url'") + + Intent(this, MainActivity::class.java).also { intent -> + intent.data = Uri.parse(url) + intent.action = Intent.ACTION_VIEW + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } + } + 2 -> { + Log.v(TAG, "got http response: ${bodyBytes.toString(Charsets.UTF_8)}") + + Intent().also { intent -> + intent.action = HTTP_TUNNEL_RESPONSE + intent.putExtra("response", bodyBytes.toString(Charsets.UTF_8)) + sendBroadcast(intent) + } + } + else -> { + Log.v(TAG, "taler instruction $talerInstr unknown") + } + } + + return makeApduSuccessResponse(ByteArray(0)) + } + + return makeApduFailureResponse() + } + + companion object { + const val TAG = "taler-wallet-hce" + const val SELECT_INS = 0xA4 + const val PUT_INS = 0xDA + const val GET_INS = 0xCA + + const val TRIGGER_PAYMENT_ACTION = "net.taler.TRIGGER_PAYMENT_ACTION" + + const val MERCHANT_NFC_CONNECTED = "net.taler.MERCHANT_NFC_CONNECTED" + const val MERCHANT_NFC_DISCONNECTED = "net.taler.MERCHANT_NFC_DISCONNECTED" + + const val HTTP_TUNNEL_RESPONSE = "net.taler.HTTP_TUNNEL_RESPONSE" + const val HTTP_TUNNEL_REQUEST = "net.taler.HTTP_TUNNEL_REQUEST" + } +}
\ No newline at end of file diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt b/wallet/src/main/java/net/taler/wallet/MainActivity.kt new file mode 100644 index 0000000..c2f20f7 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt @@ -0,0 +1,209 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_VIEW +import android.content.IntentFilter +import android.os.Bundle +import android.util.Log +import android.view.MenuItem +import android.view.View.GONE +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.widget.TextView +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.GravityCompat.START +import androidx.lifecycle.Observer +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.setupWithNavController +import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT +import com.google.zxing.integration.android.IntentIntegrator +import com.google.zxing.integration.android.IntentIntegrator.parseActivityResult +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.android.synthetic.main.app_bar_main.* +import net.taler.wallet.BuildConfig.VERSION_CODE +import net.taler.wallet.BuildConfig.VERSION_NAME +import net.taler.wallet.HostCardEmulatorService.Companion.HTTP_TUNNEL_RESPONSE +import net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_CONNECTED +import net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_DISCONNECTED +import net.taler.wallet.HostCardEmulatorService.Companion.TRIGGER_PAYMENT_ACTION +import java.util.Locale.ROOT + +class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener, + ResetDialogEventListener { + + private val model: WalletViewModel by viewModels() + + private lateinit var nav: NavController + + @SuppressLint("SetTextI18n") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + nav = navHostFragment.navController + nav_view.setupWithNavController(nav) + nav_view.setNavigationItemSelectedListener(this) + if (savedInstanceState == null) { + nav_view.menu.getItem(0).isChecked = true + } + + setSupportActionBar(toolbar) + val appBarConfiguration = AppBarConfiguration( + setOf(R.id.showBalance, R.id.settings, R.id.walletHistory, R.id.nav_pending_operations), drawer_layout + ) + toolbar.setupWithNavController(nav, appBarConfiguration) + + model.showProgressBar.observe(this, Observer { show -> + progress_bar.visibility = if (show) VISIBLE else INVISIBLE + }) + + val versionView: TextView = nav_view.getHeaderView(0).findViewById(R.id.versionView) + model.devMode.observe(this, Observer { enabled -> + nav_view.menu.findItem(R.id.nav_pending_operations).isVisible = enabled + if (enabled) { + @SuppressLint("SetTextI18n") + versionView.text = "$VERSION_NAME ($VERSION_CODE)" + versionView.visibility = VISIBLE + } else versionView.visibility = GONE + }) + + if (intent.action == ACTION_VIEW) intent.dataString?.let { uri -> + handleTalerUri(uri, "intent") + } + + //model.startTunnel() + + registerReceiver(triggerPaymentReceiver, IntentFilter(TRIGGER_PAYMENT_ACTION)) + registerReceiver(nfcConnectedReceiver, IntentFilter(MERCHANT_NFC_CONNECTED)) + registerReceiver(nfcDisconnectedReceiver, IntentFilter(MERCHANT_NFC_DISCONNECTED)) + registerReceiver(tunnelResponseReceiver, IntentFilter(HTTP_TUNNEL_RESPONSE)) + } + + override fun onBackPressed() { + if (drawer_layout.isDrawerOpen(START)) drawer_layout.closeDrawer(START) + else super.onBackPressed() + } + + override fun onNavigationItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.nav_home -> nav.navigate(R.id.showBalance) + R.id.nav_settings -> nav.navigate(R.id.settings) + R.id.nav_history -> nav.navigate(R.id.walletHistory) + R.id.nav_pending_operations -> nav.navigate(R.id.nav_pending_operations) + } + drawer_layout.closeDrawer(START) + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == IntentIntegrator.REQUEST_CODE) { + parseActivityResult(requestCode, resultCode, data)?.contents?.let { contents -> + handleTalerUri(contents, "QR code") + } + } + } + + override fun onDestroy() { + unregisterReceiver(triggerPaymentReceiver) + unregisterReceiver(nfcConnectedReceiver) + unregisterReceiver(nfcDisconnectedReceiver) + unregisterReceiver(tunnelResponseReceiver) + super.onDestroy() + } + + private fun handleTalerUri(url: String, from: String) { + when { + url.toLowerCase(ROOT).startsWith("taler://pay/") -> { + Log.v(TAG, "navigating!") + nav.navigate(R.id.action_showBalance_to_promptPayment) + model.paymentManager.preparePay(url) + } + url.toLowerCase(ROOT).startsWith("taler://withdraw/") -> { + Log.v(TAG, "navigating!") + nav.navigate(R.id.action_showBalance_to_promptWithdraw) + model.withdrawManager.getWithdrawalInfo(url) + } + url.toLowerCase(ROOT).startsWith("taler://refund/") -> { + // TODO implement refunds + Snackbar.make(nav_view, "Refunds are not yet implemented", LENGTH_SHORT).show() + } + else -> { + Snackbar.make( + nav_view, + "URL from $from doesn't contain a supported Taler Uri.", + LENGTH_SHORT + ).show() + } + } + } + + private val triggerPaymentReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (nav.currentDestination?.id == R.id.promptPayment) return + intent.extras?.getString("contractUrl")?.let { url -> + nav.navigate(R.id.action_global_promptPayment) + model.paymentManager.preparePay(url) + } + } + } + + private val nfcConnectedReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + Log.v(TAG, "got MERCHANT_NFC_CONNECTED") + //model.startTunnel() + } + } + + private val nfcDisconnectedReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + Log.v(TAG, "got MERCHANT_NFC_DISCONNECTED") + //model.stopTunnel() + } + } + + private val tunnelResponseReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + Log.v("taler-tunnel", "got HTTP_TUNNEL_RESPONSE") + intent.getStringExtra("response")?.let { + model.tunnelResponse(it) + } + } + } + + override fun onResetConfirmed() { + model.dangerouslyReset() + Snackbar.make(nav_view, "Wallet has been reset", LENGTH_SHORT).show() + } + + override fun onResetCancelled() { + Snackbar.make(nav_view, "Reset cancelled", LENGTH_SHORT).show() + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/Settings.kt b/wallet/src/main/java/net/taler/wallet/Settings.kt new file mode 100644 index 0000000..6d10412 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/Settings.kt @@ -0,0 +1,140 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import android.app.Dialog +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_CREATE_DOCUMENT +import android.content.Intent.ACTION_OPEN_DOCUMENT +import android.content.Intent.CATEGORY_OPENABLE +import android.content.Intent.EXTRA_TITLE +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import kotlinx.android.synthetic.main.fragment_settings.* + + +interface ResetDialogEventListener { + fun onResetConfirmed() + fun onResetCancelled() +} + + +class ResetDialogFragment : DialogFragment() { + private lateinit var listener: ResetDialogEventListener + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { + // Use the Builder class for convenient dialog construction + val builder = AlertDialog.Builder(it) + builder.setMessage("Do you really want to reset the wallet and lose all coins and purchases? Consider making a backup first.") + .setPositiveButton("Reset") { _, _ -> + listener.onResetConfirmed() + } + .setNegativeButton("Cancel") { _, _ -> + listener.onResetCancelled() + } + // Create the AlertDialog object and return it + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + + override fun onAttach(context: Context) { + super.onAttach(context) + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + listener = context as ResetDialogEventListener + } catch (e: ClassCastException) { + // The activity doesn't implement the interface, throw exception + throw ClassCastException((context.toString() + + " must implement ResetDialogEventListener")) + } + } +} + +class Settings : Fragment() { + + companion object { + private const val TAG = "taler-wallet" + private const val CREATE_FILE = 1 + private const val PICK_FILE = 2 + } + + private val model: WalletViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_settings, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + model.devMode.observe(viewLifecycleOwner, Observer { enabled -> + val visibility = if (enabled) VISIBLE else GONE + devSettingsTitle.visibility = visibility + button_reset_wallet_dangerously.visibility = visibility + }) + + textView4.text = BuildConfig.VERSION_NAME + button_reset_wallet_dangerously.setOnClickListener { + val d = ResetDialogFragment() + d.show(parentFragmentManager, "walletResetDialog") + } + button_backup_export.setOnClickListener { + val intent = Intent(ACTION_CREATE_DOCUMENT).apply { + addCategory(CATEGORY_OPENABLE) + type = "application/json" + putExtra(EXTRA_TITLE, "taler-wallet-backup.json") + + // Optionally, specify a URI for the directory that should be opened in + // the system file picker before your app creates the document. + //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) + } + startActivityForResult(intent, CREATE_FILE) + } + button_backup_import.setOnClickListener { + val intent = Intent(ACTION_OPEN_DOCUMENT).apply { + addCategory(CATEGORY_OPENABLE) + type = "application/json" + + //putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) + } + startActivityForResult(intent, PICK_FILE) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (data == null) return + when (requestCode) { + CREATE_FILE -> Log.i(TAG, "got createFile result with URL ${data.data}") + PICK_FILE -> Log.i(TAG, "got pickFile result with URL ${data.data}") + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/Utils.kt b/wallet/src/main/java/net/taler/wallet/Utils.kt new file mode 100644 index 0000000..fb0b3ae --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/Utils.kt @@ -0,0 +1,40 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE + +fun View.fadeIn(endAction: () -> Unit = {}) { + if (visibility == VISIBLE) return + alpha = 0f + visibility = VISIBLE + animate().alpha(1f).withEndAction { + if (context != null) endAction.invoke() + }.start() +} + +fun View.fadeOut(endAction: () -> Unit = {}) { + if (visibility == INVISIBLE) return + animate().alpha(0f).withEndAction { + if (context == null) return@withEndAction + visibility = INVISIBLE + alpha = 1f + endAction.invoke() + }.start() +} diff --git a/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt new file mode 100644 index 0000000..14a800f --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/WalletViewModel.kt @@ -0,0 +1,124 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import android.app.Application +import android.util.Log +import androidx.annotation.UiThread +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.distinctUntilChanged +import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.history.HistoryManager +import net.taler.wallet.payment.PaymentManager +import net.taler.wallet.pending.PendingOperationsManager +import net.taler.wallet.withdraw.WithdrawManager +import org.json.JSONObject + +const val TAG = "taler-wallet" + +data class BalanceItem(val available: Amount, val pendingIncoming: Amount) + +class WalletViewModel(val app: Application) : AndroidViewModel(app) { + + private val mBalances = MutableLiveData<List<BalanceItem>>() + val balances: LiveData<List<BalanceItem>> = mBalances.distinctUntilChanged() + + val devMode = MutableLiveData(BuildConfig.DEBUG) + val showProgressBar = MutableLiveData<Boolean>() + + private var activeGetBalance = 0 + + private val walletBackendApi = WalletBackendApi(app, { + activeGetBalance = 0 + loadBalances() + pendingOperationsManager.getPending() + }) { + Log.i(TAG, "Received notification from wallet-core") + loadBalances() + pendingOperationsManager.getPending() + } + + private val mapper = ObjectMapper() + .registerModule(KotlinModule()) + .configure(FAIL_ON_UNKNOWN_PROPERTIES, false) + + val withdrawManager = WithdrawManager(walletBackendApi) + val paymentManager = PaymentManager(walletBackendApi, mapper) + val pendingOperationsManager: PendingOperationsManager = + PendingOperationsManager(walletBackendApi) + val historyManager = HistoryManager(walletBackendApi, mapper) + + override fun onCleared() { + walletBackendApi.destroy() + super.onCleared() + } + + @UiThread + fun loadBalances() { + if (activeGetBalance > 0) { + return + } + activeGetBalance++ + showProgressBar.value = true + walletBackendApi.sendRequest("getBalances", null) { isError, result -> + activeGetBalance-- + if (isError) { + return@sendRequest + } + val balanceList = mutableListOf<BalanceItem>() + val byCurrency = result.getJSONObject("byCurrency") + val currencyList = byCurrency.keys().asSequence().toList().sorted() + for (currency in currencyList) { + val jsonAmount = byCurrency.getJSONObject(currency) + .getJSONObject("available") + val amount = Amount.fromJson(jsonAmount) + val jsonAmountIncoming = byCurrency.getJSONObject(currency) + .getJSONObject("pendingIncoming") + val amountIncoming = Amount.fromJson(jsonAmountIncoming) + balanceList.add(BalanceItem(amount, amountIncoming)) + } + mBalances.postValue(balanceList) + showProgressBar.postValue(false) + } + } + + @UiThread + fun dangerouslyReset() { + walletBackendApi.sendRequest("reset", null) + withdrawManager.testWithdrawalInProgress.value = false + mBalances.value = emptyList() + } + + fun startTunnel() { + walletBackendApi.sendRequest("startTunnel", null) + } + + fun stopTunnel() { + walletBackendApi.sendRequest("stopTunnel", null) + } + + fun tunnelResponse(resp: String) { + val respJson = JSONObject(resp) + walletBackendApi.sendRequest("tunnelResponse", respJson) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt new file mode 100644 index 0000000..d447287 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt @@ -0,0 +1,141 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + + +package net.taler.wallet.backend + +import android.app.Application +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Handler +import android.os.IBinder +import android.os.Message +import android.os.Messenger +import android.util.Log +import android.util.SparseArray +import org.json.JSONObject +import java.lang.ref.WeakReference +import java.util.* + +class WalletBackendApi( + private val app: Application, + private val onConnected: (() -> Unit), + private val notificationHandler: (() -> Unit) +) { + + private var walletBackendMessenger: Messenger? = null + private val queuedMessages = LinkedList<Message>() + private val handlers = SparseArray<(isError: Boolean, message: JSONObject) -> Unit>() + private var nextRequestID = 1 + + private val walletBackendConn = object : ServiceConnection { + override fun onServiceDisconnected(p0: ComponentName?) { + Log.w(TAG, "wallet backend service disconnected (crash?)") + walletBackendMessenger = null + } + + override fun onServiceConnected(componentName: ComponentName?, binder: IBinder?) { + Log.i(TAG, "connected to wallet backend service") + val bm = Messenger(binder) + walletBackendMessenger = bm + pumpQueue(bm) + val msg = Message.obtain(null, WalletBackendService.MSG_SUBSCRIBE_NOTIFY) + msg.replyTo = incomingMessenger + bm.send(msg) + onConnected.invoke() + } + } + + private class IncomingHandler(strongApi: WalletBackendApi) : Handler() { + private val weakApi = WeakReference(strongApi) + override fun handleMessage(msg: Message) { + val api = weakApi.get() ?: return + when (msg.what) { + WalletBackendService.MSG_REPLY -> { + val requestID = msg.data.getInt("requestID", 0) + val operation = msg.data.getString("operation", "??") + Log.i(TAG, "got reply for operation $operation ($requestID)") + val h = api.handlers.get(requestID) + if (h == null) { + Log.e(TAG, "request ID not associated with a handler") + return + } + val response = msg.data.getString("response") + if (response == null) { + Log.e(TAG, "response did not contain response payload") + return + } + val isError = msg.data.getBoolean("isError") + val json = JSONObject(response) + h(isError, json) + } + WalletBackendService.MSG_NOTIFY -> { + api.notificationHandler.invoke() + } + } + } + } + + private val incomingMessenger = Messenger(IncomingHandler(this)) + + init { + Intent(app, WalletBackendService::class.java).also { intent -> + app.bindService(intent, walletBackendConn, Context.BIND_AUTO_CREATE) + } + } + + private fun pumpQueue(bm: Messenger) { + while (true) { + val msg = queuedMessages.pollFirst() ?: return + bm.send(msg) + } + } + + + fun sendRequest( + operation: String, + args: JSONObject?, + onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ -> } + ) { + val requestID = nextRequestID++ + Log.i(TAG, "sending request for operation $operation ($requestID)") + val msg = Message.obtain(null, WalletBackendService.MSG_COMMAND) + handlers.put(requestID, onResponse) + msg.replyTo = incomingMessenger + val data = msg.data + data.putString("operation", operation) + data.putInt("requestID", requestID) + if (args != null) { + data.putString("args", args.toString()) + } + val bm = walletBackendMessenger + if (bm != null) { + bm.send(msg) + } else { + queuedMessages.add(msg) + } + } + + fun destroy() { + // FIXME: implement this! + } + + companion object { + const val TAG = "WalletBackendApi" + } +} diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt new file mode 100644 index 0000000..0b71774 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt @@ -0,0 +1,239 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + + +package net.taler.wallet.backend + +import akono.AkonoJni +import android.app.Service +import android.content.Intent +import android.os.Handler +import android.os.IBinder +import android.os.Message +import android.os.Messenger +import android.os.RemoteException +import android.util.Log +import net.taler.wallet.HostCardEmulatorService +import org.json.JSONObject +import java.lang.ref.WeakReference +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import kotlin.system.exitProcess + +private const val TAG = "taler-wallet-backend" + +class RequestData(val clientRequestID: Int, val messenger: Messenger) + + +class WalletBackendService : Service() { + /** + * Target we publish for clients to send messages to IncomingHandler. + */ + private val messenger: Messenger = Messenger(IncomingHandler(this)) + + private lateinit var akono: AkonoJni + + private var initialized = false + + private var nextRequestID = 1 + + private val requests = ConcurrentHashMap<Int, RequestData>() + + private val subscribers = LinkedList<Messenger>() + + override fun onCreate() { + val talerWalletAndroidCode = assets.open("taler-wallet-android.js").use { + it.readBytes().toString(Charsets.UTF_8) + } + + + Log.i(TAG, "onCreate in wallet backend service") + akono = AkonoJni() + akono.putModuleCode("taler-wallet-android", talerWalletAndroidCode) + akono.setMessageHandler(object : AkonoJni.MessageHandler { + override fun handleMessage(message: String) { + this@WalletBackendService.handleAkonoMessage(message) + } + }) + akono.evalNodeCode("console.log('hello world from taler wallet-android')") + //akono.evalNodeCode("require('source-map-support').install();") + akono.evalNodeCode("require('akono');") + akono.evalNodeCode("tw = require('taler-wallet-android');") + akono.evalNodeCode("tw.installAndroidWalletListener();") + sendInitMessage() + initialized = true + super.onCreate() + } + + private fun sendInitMessage() { + val msg = JSONObject() + msg.put("operation", "init") + val args = JSONObject() + msg.put("args", args) + args.put("persistentStoragePath", "${application.filesDir}/talerwalletdb-v30.json") + akono.sendMessage(msg.toString()) + } + + /** + * Handler of incoming messages from clients. + */ + class IncomingHandler( + service: WalletBackendService + ) : Handler() { + + private val serviceWeakRef = WeakReference(service) + + override fun handleMessage(msg: Message) { + val svc = serviceWeakRef.get() ?: return + when (msg.what) { + MSG_COMMAND -> { + val data = msg.data + val serviceRequestID = svc.nextRequestID++ + val clientRequestID = data.getInt("requestID", 0) + if (clientRequestID == 0) { + Log.e(TAG, "client requestID missing") + return + } + val args = data.getString("args") + val argsObj = if (args == null) { + JSONObject() + } else { + JSONObject(args) + } + val operation = data.getString("operation", "") + if (operation == "") { + Log.e(TAG, "client command missing") + return + } + Log.i(TAG, "got request for operation $operation") + val request = JSONObject() + request.put("operation", operation) + request.put("id", serviceRequestID) + request.put("args", argsObj) + svc.akono.sendMessage(request.toString(2)) + Log.i( + TAG, + "mapping service request ID $serviceRequestID to client request ID $clientRequestID" + ) + svc.requests[serviceRequestID] = RequestData(clientRequestID, msg.replyTo) + } + MSG_SUBSCRIBE_NOTIFY -> { + Log.i(TAG, "subscribing client") + val r = msg.replyTo + if (r == null) { + Log.e( + TAG, + "subscriber did not specify replyTo object in MSG_SUBSCRIBE_NOTIFY" + ) + } else { + svc.subscribers.add(msg.replyTo) + } + } + MSG_UNSUBSCRIBE_NOTIFY -> { + Log.i(TAG, "unsubscribing client") + svc.subscribers.remove(msg.replyTo) + } + else -> { + Log.e(TAG, "unknown message from client") + super.handleMessage(msg) + } + } + } + } + + override fun onBind(p0: Intent?): IBinder? { + return messenger.binder + } + + private fun sendNotify() { + var rm: LinkedList<Messenger>? = null + for (s in subscribers) { + val m = Message.obtain(null, MSG_NOTIFY) + try { + s.send(m) + } catch (e: RemoteException) { + if (rm == null) { + rm = LinkedList() + } + rm.add(s) + subscribers.remove(s) + } + } + if (rm != null) { + for (s in rm) { + subscribers.remove(s) + } + } + } + + private fun handleAkonoMessage(messageStr: String) { + Log.v(TAG, "got back message: $messageStr") + val message = JSONObject(messageStr) + when (message.getString("type")) { + "notification" -> { + sendNotify() + } + "tunnelHttp" -> { + Log.v(TAG, "got http tunnel request!") + Intent().also { intent -> + intent.action = HostCardEmulatorService.HTTP_TUNNEL_REQUEST + intent.putExtra("tunnelMessage", messageStr) + application.sendBroadcast(intent) + } + } + "response" -> { + when (val operation = message.getString("operation")) { + "init" -> { + Log.v(TAG, "got response for init operation") + sendNotify() + } + "reset" -> { + exitProcess(1) + } + else -> { + val id = message.getInt("id") + Log.v(TAG, "got response for operation $operation") + val rd = requests[id] + if (rd == null) { + Log.e(TAG, "wallet returned unknown request ID ($id)") + return + } + val m = Message.obtain(null, MSG_REPLY) + val b = m.data + if (message.has("result")) { + val respJson = message.getJSONObject("result") + b.putString("response", respJson.toString(2)) + } else { + b.putString("response", "{}") + } + b.putBoolean("isError", message.getBoolean("isError")) + b.putInt("requestID", rd.clientRequestID) + b.putString("operation", operation) + rd.messenger.send(m) + } + } + } + } + } + + companion object { + const val MSG_SUBSCRIBE_NOTIFY = 1 + const val MSG_UNSUBSCRIBE_NOTIFY = 2 + const val MSG_COMMAND = 3 + const val MSG_REPLY = 4 + const val MSG_NOTIFY = 5 + } +} diff --git a/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt new file mode 100644 index 0000000..25a59be --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/crypto/Encoding.kt @@ -0,0 +1,134 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.crypto + +import java.io.ByteArrayOutputStream + +class EncodingException : Exception("Invalid encoding") + + +object Base32Crockford { + + private fun ByteArray.getIntAt(index: Int): Int { + val x = this[index].toInt() + return if (x >= 0) x else (x + 256) + } + + private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + + fun encode(data: ByteArray): String { + val sb = StringBuilder() + val size = data.size + var bitBuf = 0 + var numBits = 0 + var pos = 0 + while (pos < size || numBits > 0) { + if (pos < size && numBits < 5) { + val d = data.getIntAt(pos++) + bitBuf = (bitBuf shl 8) or d + numBits += 8 + } + if (numBits < 5) { + // zero-padding + bitBuf = bitBuf shl (5 - numBits) + numBits = 5 + } + val v = bitBuf.ushr(numBits - 5) and 31 + sb.append(encTable[v]) + numBits -= 5 + } + return sb.toString() + } + + fun decode(encoded: String, out: ByteArrayOutputStream) { + val size = encoded.length + var bitpos = 0 + var bitbuf = 0 + var readPosition = 0 + + while (readPosition < size || bitpos > 0) { + //println("at position $readPosition with bitpos $bitpos") + if (readPosition < size) { + val v = getValue(encoded[readPosition++]) + bitbuf = (bitbuf shl 5) or v + bitpos += 5 + } + while (bitpos >= 8) { + val d = (bitbuf ushr (bitpos - 8)) and 0xFF + out.write(d) + bitpos -= 8 + } + if (readPosition == size && bitpos > 0) { + bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF + bitpos = if (bitbuf == 0) 0 else 8 + } + } + } + + fun decode(encoded: String): ByteArray { + val out = ByteArrayOutputStream() + decode(encoded, out) + return out.toByteArray() + } + + private fun getValue(chr: Char): Int { + var a = chr + when (a) { + 'O', 'o' -> a = '0' + 'i', 'I', 'l', 'L' -> a = '1' + 'u', 'U' -> a = 'V' + } + if (a in '0'..'9') + return a - '0' + if (a in 'a'..'z') + a = Character.toUpperCase(a) + var dec = 0 + if (a in 'A'..'Z') { + if ('I' < a) dec++ + if ('L' < a) dec++ + if ('O' < a) dec++ + if ('U' < a) dec++ + return a - 'A' + 10 - dec + } + throw EncodingException() + } + + /** + * Compute the length of the resulting string when encoding data of the given size + * in bytes. + * + * @param dataSize size of the data to encode in bytes + * @return size of the string that would result from encoding + */ + @Suppress("unused") + fun calculateEncodedStringLength(dataSize: Int): Int { + return (dataSize * 8 + 4) / 5 + } + + /** + * Compute the length of the resulting data in bytes when decoding a (valid) string of the + * given size. + * + * @param stringSize size of the string to decode + * @return size of the resulting data in bytes + */ + @Suppress("unused") + fun calculateDecodedDataLength(stringSize: Int): Int { + return stringSize * 5 / 8 + } +} + diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt new file mode 100644 index 0000000..9e5c99d --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt @@ -0,0 +1,452 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import androidx.annotation.DrawableRes +import androidx.annotation.LayoutRes +import androidx.annotation.StringRes +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonSubTypes.Type +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME +import com.fasterxml.jackson.annotation.JsonTypeName +import net.taler.wallet.ParsedAmount.Companion.parseAmount +import net.taler.wallet.R +import org.json.JSONObject + +enum class ReserveType { + /** + * Manually created. + */ + @JsonProperty("manual") + MANUAL, + /** + * Withdrawn from a bank that has "tight" Taler integration + */ + @JsonProperty("taler-bank-withdraw") + @Suppress("unused") + TALER_BANK_WITHDRAW, +} + +@JsonInclude(NON_EMPTY) +class ReserveCreationDetail(val type: ReserveType, val bankUrl: String?) + +enum class RefreshReason { + @JsonProperty("manual") + @Suppress("unused") + MANUAL, + @JsonProperty("pay") + PAY, + @JsonProperty("refund") + @Suppress("unused") + REFUND, + @JsonProperty("abort-pay") + @Suppress("unused") + ABORT_PAY, + @JsonProperty("recoup") + @Suppress("unused") + RECOUP, + @JsonProperty("backup-restored") + @Suppress("unused") + BACKUP_RESTORED +} + + +@JsonInclude(NON_EMPTY) +class Timestamp( + @JsonProperty("t_ms") + val ms: Long +) + +@JsonInclude(NON_EMPTY) +class ReserveShortInfo( + /** + * The exchange that the reserve will be at. + */ + val exchangeBaseUrl: String, + /** + * Key to query more details + */ + val reservePub: String, + /** + * Detail about how the reserve has been created. + */ + val reserveCreationDetail: ReserveCreationDetail +) + +typealias History = ArrayList<HistoryEvent> + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type", + defaultImpl = HistoryUnknownEvent::class +) +/** missing: +AuditorComplaintSent = "auditor-complained-sent", +AuditorComplaintProcessed = "auditor-complaint-processed", +AuditorTrustAdded = "auditor-trust-added", +AuditorTrustRemoved = "auditor-trust-removed", +ExchangeTermsAccepted = "exchange-terms-accepted", +ExchangePolicyChanged = "exchange-policy-changed", +ExchangeTrustAdded = "exchange-trust-added", +ExchangeTrustRemoved = "exchange-trust-removed", +FundsDepositedToSelf = "funds-deposited-to-self", +FundsRecouped = "funds-recouped", +ReserveCreated = "reserve-created", + */ +@JsonSubTypes( + Type(value = ExchangeAddedEvent::class, name = "exchange-added"), + Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"), + Type(value = ReserveBalanceUpdatedEvent::class, name = "reserve-balance-updated"), + Type(value = HistoryWithdrawnEvent::class, name = "withdrawn"), + Type(value = HistoryOrderAcceptedEvent::class, name = "order-accepted"), + Type(value = HistoryOrderRefusedEvent::class, name = "order-refused"), + Type(value = HistoryOrderRedirectedEvent::class, name = "order-redirected"), + Type(value = HistoryPaymentSentEvent::class, name = "payment-sent"), + Type(value = HistoryPaymentAbortedEvent::class, name = "payment-aborted"), + Type(value = HistoryTipAcceptedEvent::class, name = "tip-accepted"), + Type(value = HistoryTipDeclinedEvent::class, name = "tip-declined"), + Type(value = HistoryRefundedEvent::class, name = "refund"), + Type(value = HistoryRefreshedEvent::class, name = "refreshed") +) +@JsonIgnoreProperties( + value = [ + "eventId" + ] +) +abstract class HistoryEvent( + val timestamp: Timestamp, + @get:LayoutRes + open val layout: Int = R.layout.history_row, + @get:StringRes + open val title: Int = 0, + @get:DrawableRes + open val icon: Int = R.drawable.ic_account_balance, + open val showToUser: Boolean = false +) { + open lateinit var json: JSONObject +} + + +class HistoryUnknownEvent(timestamp: Timestamp) : HistoryEvent(timestamp) { + override val title = R.string.history_event_unknown +} + +@JsonTypeName("exchange-added") +class ExchangeAddedEvent( + timestamp: Timestamp, + val exchangeBaseUrl: String, + val builtIn: Boolean +) : HistoryEvent(timestamp) { + override val title = R.string.history_event_exchange_added +} + +@JsonTypeName("exchange-updated") +class ExchangeUpdatedEvent( + timestamp: Timestamp, + val exchangeBaseUrl: String +) : HistoryEvent(timestamp) { + override val title = R.string.history_event_exchange_updated +} + + +@JsonTypeName("reserve-balance-updated") +class ReserveBalanceUpdatedEvent( + timestamp: Timestamp, + val newHistoryTransactions: List<ReserveTransaction>, + /** + * Condensed information about the reserve. + */ + val reserveShortInfo: ReserveShortInfo, + /** + * Amount currently left in the reserve. + */ + val amountReserveBalance: String, + /** + * Amount we expected to be in the reserve at that time, + * considering ongoing withdrawals from that reserve. + */ + val amountExpected: String +) : HistoryEvent(timestamp) { + override val title = R.string.history_event_reserve_balance_updated +} + +@JsonTypeName("withdrawn") +class HistoryWithdrawnEvent( + timestamp: Timestamp, + /** + * Exchange that was withdrawn from. + */ + val exchangeBaseUrl: String, + /** + * Unique identifier for the withdrawal session, can be used to + * query more detailed information from the wallet. + */ + val withdrawSessionId: String, + val withdrawalSource: WithdrawalSource, + /** + * Amount that has been subtracted from the reserve's balance + * for this withdrawal. + */ + val amountWithdrawnRaw: String, + /** + * Amount that actually was added to the wallet's balance. + */ + val amountWithdrawnEffective: String +) : HistoryEvent(timestamp) { + override val layout = R.layout.history_receive + override val title = R.string.history_event_withdrawn + override val icon = R.drawable.history_withdrawn + override val showToUser = true +} + +@JsonTypeName("order-accepted") +class HistoryOrderAcceptedEvent( + timestamp: Timestamp, + /** + * Condensed info about the order. + */ + val orderShortInfo: OrderShortInfo +) : HistoryEvent(timestamp) { + override val icon = R.drawable.ic_add_circle + override val title = R.string.history_event_order_accepted +} + +@JsonTypeName("order-refused") +class HistoryOrderRefusedEvent( + timestamp: Timestamp, + /** + * Condensed info about the order. + */ + val orderShortInfo: OrderShortInfo +) : HistoryEvent(timestamp) { + override val icon = R.drawable.ic_cancel + override val title = R.string.history_event_order_refused +} + +@JsonTypeName("payment-sent") +class HistoryPaymentSentEvent( + timestamp: Timestamp, + /** + * Condensed info about the order that we already paid for. + */ + val orderShortInfo: OrderShortInfo, + /** + * Set to true if the payment has been previously sent + * to the merchant successfully, possibly with a different session ID. + */ + val replay: Boolean, + /** + * Number of coins that were involved in the payment. + */ + val numCoins: Int, + /** + * Amount that was paid, including deposit and wire fees. + */ + val amountPaidWithFees: String, + /** + * Session ID that the payment was (re-)submitted under. + */ + val sessionId: String? +) : HistoryEvent(timestamp) { + override val layout = R.layout.history_payment + override val title = R.string.history_event_payment_sent + override val icon = R.drawable.ic_cash_usd_outline + override val showToUser = true +} + +@JsonTypeName("payment-aborted") +class HistoryPaymentAbortedEvent( + timestamp: Timestamp, + /** + * Condensed info about the order that we already paid for. + */ + val orderShortInfo: OrderShortInfo, + /** + * Amount that was lost due to refund and refreshing fees. + */ + val amountLost: String +) : HistoryEvent(timestamp) { + override val layout = R.layout.history_payment + override val title = R.string.history_event_payment_aborted + override val icon = R.drawable.history_payment_aborted + override val showToUser = true +} + +@JsonTypeName("refreshed") +class HistoryRefreshedEvent( + timestamp: Timestamp, + /** + * Amount that is now available again because it has + * been refreshed. + */ + val amountRefreshedEffective: String, + /** + * Amount that we spent for refreshing. + */ + val amountRefreshedRaw: String, + /** + * Why was the refreshing done? + */ + val refreshReason: RefreshReason, + val numInputCoins: Int, + val numRefreshedInputCoins: Int, + val numOutputCoins: Int, + /** + * Identifier for a refresh group, contains one or + * more refresh session IDs. + */ + val refreshGroupId: String +) : HistoryEvent(timestamp) { + override val layout = R.layout.history_payment + override val icon = R.drawable.history_refresh + override val title = R.string.history_event_refreshed + override val showToUser = + !(parseAmount(amountRefreshedRaw) - parseAmount(amountRefreshedEffective)).isZero() +} + +@JsonTypeName("order-redirected") +class HistoryOrderRedirectedEvent( + timestamp: Timestamp, + /** + * Condensed info about the new order that contains a + * product (identified by the fulfillment URL) that we've already paid for. + */ + val newOrderShortInfo: OrderShortInfo, + /** + * Condensed info about the order that we already paid for. + */ + val alreadyPaidOrderShortInfo: OrderShortInfo +) : HistoryEvent(timestamp) { + override val icon = R.drawable.ic_directions + override val title = R.string.history_event_order_redirected +} + +@JsonTypeName("tip-accepted") +class HistoryTipAcceptedEvent( + timestamp: Timestamp, + /** + * Unique identifier for the tip to query more information. + */ + val tipId: String, + /** + * Raw amount of the tip, without extra fees that apply. + */ + val tipRaw: String +) : HistoryEvent(timestamp) { + override val icon = R.drawable.history_tip_accepted + override val title = R.string.history_event_tip_accepted + override val layout = R.layout.history_receive + override val showToUser = true +} + +@JsonTypeName("tip-declined") +class HistoryTipDeclinedEvent( + timestamp: Timestamp, + /** + * Unique identifier for the tip to query more information. + */ + val tipId: String, + /** + * Raw amount of the tip, without extra fees that apply. + */ + val tipAmount: String +) : HistoryEvent(timestamp) { + override val icon = R.drawable.history_tip_declined + override val title = R.string.history_event_tip_declined + override val layout = R.layout.history_receive + override val showToUser = true +} + +@JsonTypeName("refund") +class HistoryRefundedEvent( + timestamp: Timestamp, + val orderShortInfo: OrderShortInfo, + /** + * Unique identifier for this refund. + * (Identifies multiple refund permissions that were obtained at once.) + */ + val refundGroupId: String, + /** + * Part of the refund that couldn't be applied because + * the refund permissions were expired. + */ + val amountRefundedInvalid: String, + /** + * Amount that has been refunded by the merchant. + */ + val amountRefundedRaw: String, + /** + * Amount will be added to the wallet's balance after fees and refreshing. + */ + val amountRefundedEffective: String +) : HistoryEvent(timestamp) { + override val icon = R.drawable.history_refund + override val title = R.string.history_event_refund + override val layout = R.layout.history_receive + override val showToUser = true +} + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type" +) +@JsonSubTypes( + Type(value = WithdrawalSourceReserve::class, name = "reserve") +) +abstract class WithdrawalSource + +@Suppress("unused") +@JsonTypeName("tip") +class WithdrawalSourceTip( + val tipId: String +) : WithdrawalSource() + +@JsonTypeName("reserve") +class WithdrawalSourceReserve( + val reservePub: String +) : WithdrawalSource() + +data class OrderShortInfo( + /** + * Wallet-internal identifier of the proposal. + */ + val proposalId: String, + /** + * Order ID, uniquely identifies the order within a merchant instance. + */ + val orderId: String, + /** + * Base URL of the merchant. + */ + val merchantBaseUrl: String, + /** + * Amount that must be paid for the contract. + */ + val amount: String, + /** + * Summary of the proposal, given by the merchant. + */ + val summary: String +) diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt new file mode 100644 index 0000000..c350daa --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryManager.kt @@ -0,0 +1,71 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.switchMap +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import net.taler.wallet.backend.WalletBackendApi + +@Suppress("EXPERIMENTAL_API_USAGE") +class HistoryManager( + private val walletBackendApi: WalletBackendApi, + private val mapper: ObjectMapper +) { + + private val mProgress = MutableLiveData<Boolean>() + val progress: LiveData<Boolean> = mProgress + + val showAll = MutableLiveData<Boolean>() + + val history: LiveData<History> = showAll.switchMap { showAll -> + loadHistory(showAll) + .onStart { mProgress.postValue(true) } + .onCompletion { mProgress.postValue(false) } + .asLiveData(Dispatchers.IO) + } + + private fun loadHistory(showAll: Boolean) = callbackFlow { + walletBackendApi.sendRequest("getHistory", null) { isError, result -> + if (isError) { + // TODO show error message in [WalletHistory] fragment + close() + return@sendRequest + } + val history = History() + val json = result.getJSONArray("history") + for (i in 0 until json.length()) { + val event: HistoryEvent = mapper.readValue(json.getString(i)) + event.json = json.getJSONObject(i) + history.add(event) + } + history.reverse() // show latest first + offer(if (showAll) history else history.filter { it.showToUser } as History) + close() + } + awaitClose() + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt new file mode 100644 index 0000000..f51dba9 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt @@ -0,0 +1,50 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.fragment_json.* +import net.taler.wallet.R + +class JsonDialogFragment : DialogFragment() { + + companion object { + fun new(json: String): JsonDialogFragment { + return JsonDialogFragment().apply { + arguments = Bundle().apply { putString("json", json) } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_json, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val json = arguments!!.getString("json") + jsonView.text = json + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt new file mode 100644 index 0000000..45c539c --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt @@ -0,0 +1,58 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME +import com.fasterxml.jackson.annotation.JsonTypeName + + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type" +) +@JsonSubTypes( + JsonSubTypes.Type(value = ReserveDepositTransaction::class, name = "DEPOSIT") +) +abstract class ReserveTransaction + + +@JsonTypeName("DEPOSIT") +class ReserveDepositTransaction( + /** + * Amount withdrawn. + */ + val amount: String, + /** + * Sender account payto://-URL + */ + @JsonProperty("sender_account_url") + val senderAccountUrl: String, + /** + * Transfer details uniquely identifying the transfer. + */ + @JsonProperty("wire_reference") + val wireReference: String, + /** + * Timestamp of the incoming wire transfer. + */ + val timestamp: Timestamp +) : ReserveTransaction() diff --git a/wallet/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt new file mode 100644 index 0000000..71bdebc --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt @@ -0,0 +1,243 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import android.annotation.SuppressLint +import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG +import android.text.format.DateUtils.DAY_IN_MILLIS +import android.text.format.DateUtils.FORMAT_ABBREV_MONTH +import android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE +import android.text.format.DateUtils.FORMAT_NO_YEAR +import android.text.format.DateUtils.FORMAT_SHOW_DATE +import android.text.format.DateUtils.FORMAT_SHOW_TIME +import android.text.format.DateUtils.MINUTE_IN_MILLIS +import android.text.format.DateUtils.formatDateTime +import android.text.format.DateUtils.getRelativeTimeSpanString +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.CallSuper +import androidx.core.net.toUri +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import net.taler.wallet.BuildConfig +import net.taler.wallet.ParsedAmount +import net.taler.wallet.ParsedAmount.Companion.parseAmount +import net.taler.wallet.R + + +internal class WalletHistoryAdapter( + private val listener: OnEventClickListener, + private var history: History = History() +) : Adapter<WalletHistoryAdapter.HistoryEventViewHolder>() { + + init { + setHasStableIds(false) + } + + override fun getItemViewType(position: Int): Int = history[position].layout + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryEventViewHolder { + val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + return when (viewType) { + R.layout.history_receive -> HistoryReceiveViewHolder(view) + R.layout.history_payment -> HistoryPaymentViewHolder(view) + else -> GenericHistoryEventViewHolder(view) + } + } + + override fun getItemCount(): Int = history.size + + override fun onBindViewHolder(holder: HistoryEventViewHolder, position: Int) { + val event = history[position] + holder.bind(event) + } + + fun update(updatedHistory: History) { + this.history = updatedHistory + this.notifyDataSetChanged() + } + + internal abstract inner class HistoryEventViewHolder(protected val v: View) : ViewHolder(v) { + + private val icon: ImageView = v.findViewById(R.id.icon) + protected val title: TextView = v.findViewById(R.id.title) + private val time: TextView = v.findViewById(R.id.time) + + @CallSuper + open fun bind(event: HistoryEvent) { + if (BuildConfig.DEBUG) { // doesn't produce recycling issues, no need to cover all cases + v.setOnClickListener { listener.onEventClicked(event) } + } else { + v.background = null + } + icon.setImageResource(event.icon) + if (event.title == 0) title.text = event::class.java.simpleName + else title.setText(event.title) + time.text = getRelativeTime(event.timestamp.ms) + } + + private fun getRelativeTime(timestamp: Long): CharSequence { + val now = System.currentTimeMillis() + return if (now - timestamp > DAY_IN_MILLIS * 2) { + formatDateTime( + v.context, + timestamp, + FORMAT_SHOW_TIME or FORMAT_SHOW_DATE or FORMAT_ABBREV_MONTH or FORMAT_NO_YEAR + ) + } else { + getRelativeTimeSpanString(timestamp, now, MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE) + } + } + + } + + internal inner class GenericHistoryEventViewHolder(v: View) : HistoryEventViewHolder(v) { + + private val info: TextView = v.findViewById(R.id.info) + + override fun bind(event: HistoryEvent) { + super.bind(event) + info.text = when (event) { + is ExchangeAddedEvent -> event.exchangeBaseUrl + is ExchangeUpdatedEvent -> event.exchangeBaseUrl + is ReserveBalanceUpdatedEvent -> parseAmount(event.amountReserveBalance).toString() + is HistoryPaymentSentEvent -> event.orderShortInfo.summary + is HistoryOrderAcceptedEvent -> event.orderShortInfo.summary + is HistoryOrderRefusedEvent -> event.orderShortInfo.summary + is HistoryOrderRedirectedEvent -> event.newOrderShortInfo.summary + else -> "" + } + } + + } + + internal inner class HistoryReceiveViewHolder(v: View) : HistoryEventViewHolder(v) { + + private val summary: TextView = v.findViewById(R.id.summary) + private val amountWithdrawn: TextView = v.findViewById(R.id.amountWithdrawn) + private val feeLabel: TextView = v.findViewById(R.id.feeLabel) + private val fee: TextView = v.findViewById(R.id.fee) + + override fun bind(event: HistoryEvent) { + super.bind(event) + when (event) { + is HistoryWithdrawnEvent -> bind(event) + is HistoryRefundedEvent -> bind(event) + is HistoryTipAcceptedEvent -> bind(event) + is HistoryTipDeclinedEvent -> bind(event) + } + } + + private fun bind(event: HistoryWithdrawnEvent) { + title.text = getHostname(event.exchangeBaseUrl) + summary.setText(event.title) + + val parsedEffective = parseAmount(event.amountWithdrawnEffective) + val parsedRaw = parseAmount(event.amountWithdrawnRaw) + showAmounts(parsedEffective, parsedRaw) + } + + private fun bind(event: HistoryRefundedEvent) { + title.text = event.orderShortInfo.summary + summary.setText(event.title) + + val parsedEffective = parseAmount(event.amountRefundedEffective) + val parsedRaw = parseAmount(event.amountRefundedRaw) + showAmounts(parsedEffective, parsedRaw) + } + + private fun bind(event: HistoryTipAcceptedEvent) { + title.setText(event.title) + summary.text = null + val amount = parseAmount(event.tipRaw) + showAmounts(amount, amount) + } + + private fun bind(event: HistoryTipDeclinedEvent) { + title.setText(event.title) + summary.text = null + val amount = parseAmount(event.tipAmount) + showAmounts(amount, amount) + amountWithdrawn.paintFlags = amountWithdrawn.paintFlags or STRIKE_THRU_TEXT_FLAG + } + + private fun showAmounts(effective: ParsedAmount, raw: ParsedAmount) { + @SuppressLint("SetTextI18n") + amountWithdrawn.text = "+$raw" + val calculatedFee = raw - effective + if (calculatedFee.isZero()) { + fee.visibility = GONE + feeLabel.visibility = GONE + } else { + @SuppressLint("SetTextI18n") + fee.text = "-$calculatedFee" + fee.visibility = VISIBLE + feeLabel.visibility = VISIBLE + } + amountWithdrawn.paintFlags = fee.paintFlags + } + + private fun getHostname(url: String): String { + return url.toUri().host!! + } + + } + + internal inner class HistoryPaymentViewHolder(v: View) : HistoryEventViewHolder(v) { + + private val summary: TextView = v.findViewById(R.id.summary) + private val amountPaidWithFees: TextView = v.findViewById(R.id.amountPaidWithFees) + + override fun bind(event: HistoryEvent) { + super.bind(event) + summary.setText(event.title) + when (event) { + is HistoryPaymentSentEvent -> bind(event) + is HistoryPaymentAbortedEvent -> bind(event) + is HistoryRefreshedEvent -> bind(event) + } + } + + private fun bind(event: HistoryPaymentSentEvent) { + title.text = event.orderShortInfo.summary + @SuppressLint("SetTextI18n") + amountPaidWithFees.text = "-${parseAmount(event.amountPaidWithFees)}" + } + + private fun bind(event: HistoryPaymentAbortedEvent) { + title.text = event.orderShortInfo.summary + @SuppressLint("SetTextI18n") + amountPaidWithFees.text = "-${parseAmount(event.amountLost)}" + } + + private fun bind(event: HistoryRefreshedEvent) { + title.text = "" + val fee = + parseAmount(event.amountRefreshedRaw) - parseAmount(event.amountRefreshedEffective) + @SuppressLint("SetTextI18n") + if (fee.isZero()) amountPaidWithFees.text = null + else amountPaidWithFees.text = "-$fee" + } + + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/WalletHistoryFragment.kt b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryFragment.kt new file mode 100644 index 0000000..4f8ab82 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/WalletHistoryFragment.kt @@ -0,0 +1,115 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL +import kotlinx.android.synthetic.main.fragment_show_balance.* +import kotlinx.android.synthetic.main.fragment_show_history.* +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel + +interface OnEventClickListener { + fun onEventClicked(event: HistoryEvent) +} + +class WalletHistoryFragment : Fragment(), OnEventClickListener { + + private val model: WalletViewModel by activityViewModels() + private val historyManager by lazy { model.historyManager } + private lateinit var showAllItem: MenuItem + private var reloadHistoryItem: MenuItem? = null + private val historyAdapter = WalletHistoryAdapter(this) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_show_history, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + historyList.apply { + layoutManager = LinearLayoutManager(context) + adapter = historyAdapter + addItemDecoration(DividerItemDecoration(context, VERTICAL)) + } + + model.devMode.observe(viewLifecycleOwner, Observer { enabled -> + reloadHistoryItem?.isVisible = enabled + }) + historyManager.progress.observe(viewLifecycleOwner, Observer { show -> + historyProgressBar.visibility = if (show) VISIBLE else INVISIBLE + }) + historyManager.history.observe(viewLifecycleOwner, Observer { history -> + historyEmptyState.visibility = if (history.isEmpty()) VISIBLE else INVISIBLE + historyAdapter.update(history) + }) + + // kicks off initial load, needs to be adapted if showAll state is ever saved + if (savedInstanceState == null) historyManager.showAll.value = model.devMode.value + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.history, menu) + showAllItem = menu.findItem(R.id.show_all_history) + showAllItem.isChecked = historyManager.showAll.value == true + reloadHistoryItem = menu.findItem(R.id.reload_history).apply { + isVisible = model.devMode.value!! + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.show_all_history -> { + item.isChecked = !item.isChecked + historyManager.showAll.value = item.isChecked + true + } + R.id.reload_history -> { + historyManager.showAll.value = showAllItem.isChecked + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onEventClicked(event: HistoryEvent) { + if (model.devMode.value != true) return + JsonDialogFragment.new(event.json.toString(4)) + .show(parentFragmentManager, null) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt new file mode 100644 index 0000000..33e3a1d --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt @@ -0,0 +1,47 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_already_paid.* +import net.taler.wallet.R + +/** + * Display the message that the user already paid for the order + * that the merchant is proposing. + */ +class AlreadyPaidFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_already_paid, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + backButton.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/ContractTerms.kt b/wallet/src/main/java/net/taler/wallet/payment/ContractTerms.kt new file mode 100644 index 0000000..da91dea --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/ContractTerms.kt @@ -0,0 +1,56 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import net.taler.wallet.Amount + + +@JsonIgnoreProperties(ignoreUnknown = true) +data class ContractTerms( + val summary: String, + val products: List<ContractProduct>, + val amount: Amount +) + +interface Product { + val id: String? + val description: String + val price: Amount + val location: String? + val image: String? +} + +@JsonIgnoreProperties("totalPrice") +data class ContractProduct( + @JsonProperty("product_id") + override val id: String?, + override val description: String, + override val price: Amount, + @JsonProperty("delivery_location") + override val location: String?, + override val image: String?, + val quantity: Int +) : Product { + + val totalPrice: Amount by lazy { + val amount = price.amount.toDouble() * quantity + Amount(price.currency, amount.toString()) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt new file mode 100644 index 0000000..ee0edaf --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt @@ -0,0 +1,160 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.util.Log +import androidx.annotation.UiThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import net.taler.wallet.Amount +import net.taler.wallet.TAG +import net.taler.wallet.backend.WalletBackendApi +import org.json.JSONObject +import java.net.MalformedURLException + +val REGEX_PRODUCT_IMAGE = Regex("^data:image/(jpeg|png);base64,([A-Za-z0-9+/=]+)$") + +class PaymentManager( + private val walletBackendApi: WalletBackendApi, + private val mapper: ObjectMapper +) { + + private val mPayStatus = MutableLiveData<PayStatus>(PayStatus.None) + internal val payStatus: LiveData<PayStatus> = mPayStatus + + private val mDetailsShown = MutableLiveData<Boolean>() + internal val detailsShown: LiveData<Boolean> = mDetailsShown + + private var currentPayRequestId = 0 + + @UiThread + fun preparePay(url: String) { + mPayStatus.value = PayStatus.Loading + mDetailsShown.value = false + + val args = JSONObject(mapOf("url" to url)) + + currentPayRequestId += 1 + val payRequestId = currentPayRequestId + + walletBackendApi.sendRequest("preparePay", args) { isError, result -> + when { + isError -> { + Log.v(TAG, "got preparePay error result") + mPayStatus.value = PayStatus.Error(result.toString()) + } + payRequestId != this.currentPayRequestId -> { + Log.v(TAG, "preparePay result was for old request") + } + else -> { + val status = result.getString("status") + try { + mPayStatus.postValue(getPayStatusUpdate(status, result)) + } catch (e: Exception) { + Log.e(TAG, "Error getting PayStatusUpdate", e) + mPayStatus.postValue(PayStatus.Error(e.message ?: "unknown error")) + } + } + } + } + } + + private fun getPayStatusUpdate(status: String, json: JSONObject) = when (status) { + "payment-possible" -> PayStatus.Prepared( + contractTerms = getContractTerms(json), + proposalId = json.getString("proposalId"), + totalFees = Amount.fromJson(json.getJSONObject("totalFees")) + ) + "paid" -> PayStatus.AlreadyPaid(getContractTerms(json)) + "insufficient-balance" -> PayStatus.InsufficientBalance(getContractTerms(json)) + "error" -> PayStatus.Error("got some error") + else -> PayStatus.Error("unknown status") + } + + private fun getContractTerms(json: JSONObject): ContractTerms { + val terms: ContractTerms = mapper.readValue(json.getString("contractTermsRaw")) + // validate product images + terms.products.forEach { product -> + product.image?.let { image -> + if (REGEX_PRODUCT_IMAGE.matchEntire(image) == null) { + throw MalformedURLException("Invalid image data URL for ${product.description}") + } + } + } + return terms + } + + @UiThread + fun toggleDetailsShown() { + val oldValue = mDetailsShown.value ?: false + mDetailsShown.value = !oldValue + } + + fun confirmPay(proposalId: String) { + val args = JSONObject(mapOf("proposalId" to proposalId)) + + walletBackendApi.sendRequest("confirmPay", args) { _, _ -> + mPayStatus.postValue(PayStatus.Success) + } + } + + @UiThread + fun abortPay() { + val ps = payStatus.value + if (ps is PayStatus.Prepared) { + abortProposal(ps.proposalId) + } + resetPayStatus() + } + + internal fun abortProposal(proposalId: String) { + val args = JSONObject(mapOf("proposalId" to proposalId)) + + Log.i(TAG, "aborting proposal") + + walletBackendApi.sendRequest("abortProposal", args) { isError, _ -> + if (isError) { + Log.e(TAG, "received error response to abortProposal") + return@sendRequest + } + mPayStatus.postValue(PayStatus.None) + } + } + + @UiThread + fun resetPayStatus() { + mPayStatus.value = PayStatus.None + } + +} + +sealed class PayStatus { + object None : PayStatus() + object Loading : PayStatus() + data class Prepared( + val contractTerms: ContractTerms, + val proposalId: String, + val totalFees: Amount + ) : PayStatus() + + data class InsufficientBalance(val contractTerms: ContractTerms) : PayStatus() + data class AlreadyPaid(val contractTerms: ContractTerms) : PayStatus() + data class Error(val error: String) : PayStatus() + object Success : PayStatus() +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt new file mode 100644 index 0000000..2084c45 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentSuccessfulFragment.kt @@ -0,0 +1,49 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_payment_successful.* +import net.taler.wallet.R +import net.taler.wallet.fadeIn + +/** + * Fragment that shows the success message for a payment. + */ +class PaymentSuccessfulFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_payment_successful, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + successImageView.fadeIn() + successTextView.fadeIn() + backButton.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt b/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt new file mode 100644 index 0000000..4b1b062 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/ProductAdapter.kt @@ -0,0 +1,92 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.graphics.Bitmap +import android.graphics.BitmapFactory.decodeByteArray +import android.util.Base64 +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import net.taler.wallet.R +import net.taler.wallet.payment.ProductAdapter.ProductViewHolder + +internal interface ProductImageClickListener { + fun onImageClick(image: Bitmap) +} + +internal class ProductAdapter(private val listener: ProductImageClickListener) : + RecyclerView.Adapter<ProductViewHolder>() { + + private val items = ArrayList<ContractProduct>() + + override fun getItemCount() = items.size + + override fun getItemViewType(position: Int): Int { + return if (itemCount == 1) 1 else 0 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder { + val res = + if (viewType == 1) R.layout.list_item_product_single else R.layout.list_item_product + val view = LayoutInflater.from(parent.context).inflate(res, parent, false) + return ProductViewHolder(view) + } + + override fun onBindViewHolder(holder: ProductViewHolder, position: Int) { + holder.bind(items[position]) + } + + fun setItems(items: List<ContractProduct>) { + this.items.clear() + this.items.addAll(items) + notifyDataSetChanged() + } + + internal inner class ProductViewHolder(v: View) : ViewHolder(v) { + private val quantity: TextView = v.findViewById(R.id.quantity) + private val image: ImageView = v.findViewById(R.id.image) + private val name: TextView = v.findViewById(R.id.name) + private val price: TextView = v.findViewById(R.id.price) + + fun bind(product: ContractProduct) { + quantity.text = product.quantity.toString() + if (product.image == null) { + image.visibility = GONE + } else { + image.visibility = VISIBLE + // product.image was validated before, so non-null below + val match = REGEX_PRODUCT_IMAGE.matchEntire(product.image)!! + val decodedString = Base64.decode(match.groups[2]!!.value, Base64.DEFAULT) + val bitmap = decodeByteArray(decodedString, 0, decodedString.size) + image.setImageBitmap(bitmap) + if (itemCount > 1) image.setOnClickListener { + listener.onImageClick(bitmap) + } + } + name.text = product.description + price.text = product.totalPrice.toString() + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt new file mode 100644 index 0000000..02414a6 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt @@ -0,0 +1,52 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.graphics.Bitmap +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.fragment_product_image.* +import net.taler.wallet.R + +class ProductImageFragment private constructor() : DialogFragment() { + + companion object { + private const val IMAGE = "image" + + fun new(image: Bitmap) = ProductImageFragment().apply { + arguments = Bundle().apply { + putParcelable(IMAGE, image) + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_product_image, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val bitmap = arguments!!.getParcelable<Bitmap>(IMAGE) + productImageView.setImageBitmap(bitmap) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt new file mode 100644 index 0000000..44dcf26 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt @@ -0,0 +1,168 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.payment + +import android.annotation.SuppressLint +import android.graphics.Bitmap +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.lifecycle.observe +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.transition.TransitionManager.beginDelayedTransition +import kotlinx.android.synthetic.main.payment_bottom_bar.* +import kotlinx.android.synthetic.main.payment_details.* +import net.taler.wallet.Amount +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel +import net.taler.wallet.fadeIn +import net.taler.wallet.fadeOut + +/** + * Show a payment and ask the user to accept/decline. + */ +class PromptPaymentFragment : Fragment(), ProductImageClickListener { + + private val model: WalletViewModel by activityViewModels() + private val paymentManager by lazy { model.paymentManager } + private val adapter = ProductAdapter(this) + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_prompt_payment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + paymentManager.payStatus.observe(viewLifecycleOwner, this::onPaymentStatusChanged) + paymentManager.detailsShown.observe(viewLifecycleOwner, Observer { shown -> + beginDelayedTransition(view as ViewGroup) + val res = if (shown) R.string.payment_hide_details else R.string.payment_show_details + detailsButton.setText(res) + productsList.visibility = if (shown) VISIBLE else GONE + }) + + detailsButton.setOnClickListener { + paymentManager.toggleDetailsShown() + } + productsList.apply { + adapter = this@PromptPaymentFragment.adapter + layoutManager = LinearLayoutManager(requireContext()) + } + + abortButton.setOnClickListener { + paymentManager.abortPay() + findNavController().navigateUp() + } + } + + override fun onDestroy() { + super.onDestroy() + if (!requireActivity().isChangingConfigurations) { + paymentManager.abortPay() + } + } + + private fun showLoading(show: Boolean) { + model.showProgressBar.value = show + if (show) { + progressBar.fadeIn() + } else { + progressBar.fadeOut() + } + } + + private fun onPaymentStatusChanged(payStatus: PayStatus) { + when (payStatus) { + is PayStatus.Prepared -> { + showLoading(false) + showOrder(payStatus.contractTerms, payStatus.totalFees) + confirmButton.isEnabled = true + confirmButton.setOnClickListener { + model.showProgressBar.value = true + paymentManager.confirmPay(payStatus.proposalId) + confirmButton.fadeOut() + confirmProgressBar.fadeIn() + } + } + is PayStatus.InsufficientBalance -> { + showLoading(false) + showOrder(payStatus.contractTerms, null) + errorView.setText(R.string.payment_balance_insufficient) + errorView.fadeIn() + } + is PayStatus.Success -> { + showLoading(false) + paymentManager.resetPayStatus() + findNavController().navigate(R.id.action_promptPayment_to_paymentSuccessful) + } + is PayStatus.AlreadyPaid -> { + showLoading(false) + paymentManager.resetPayStatus() + findNavController().navigate(R.id.action_promptPayment_to_alreadyPaid) + } + is PayStatus.Error -> { + showLoading(false) + errorView.text = getString(R.string.payment_error, payStatus.error) + errorView.fadeIn() + } + is PayStatus.None -> { + // No payment active. + showLoading(false) + } + is PayStatus.Loading -> { + // Wait until loaded ... + showLoading(true) + } + } + } + + private fun showOrder(contractTerms: ContractTerms, totalFees: Amount?) { + orderView.text = contractTerms.summary + adapter.setItems(contractTerms.products) + if (contractTerms.products.size == 1) paymentManager.toggleDetailsShown() + val amount = contractTerms.amount + @SuppressLint("SetTextI18n") + totalView.text = "${amount.amount} ${amount.currency}" + if (totalFees != null && !totalFees.isZero()) { + val fee = "${totalFees.amount} ${totalFees.currency}" + feeView.text = getString(R.string.payment_fee, fee) + feeView.fadeIn() + } else { + feeView.visibility = GONE + } + orderLabelView.fadeIn() + orderView.fadeIn() + if (contractTerms.products.size > 1) detailsButton.fadeIn() + totalLabelView.fadeIn() + totalView.fadeIn() + } + + override fun onImageClick(image: Bitmap) { + val f = ProductImageFragment.new(image) + f.show(parentFragmentManager, "image") + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt new file mode 100644 index 0000000..946e5ba --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt @@ -0,0 +1,180 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.pending + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT +import kotlinx.android.synthetic.main.fragment_pending_operations.* +import net.taler.wallet.R +import net.taler.wallet.TAG +import net.taler.wallet.WalletViewModel +import org.json.JSONObject + +interface PendingOperationClickListener { + fun onPendingOperationClick(type: String, detail: JSONObject) + fun onPendingOperationActionClick(type: String, detail: JSONObject) +} + +class PendingOperationsFragment : Fragment(), PendingOperationClickListener { + + private val model: WalletViewModel by activityViewModels() + private val pendingOperationsManager by lazy { model.pendingOperationsManager } + + private val pendingAdapter = PendingOperationsAdapter(emptyList(), this) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_pending_operations, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + list_pending.apply { + val myLayoutManager = LinearLayoutManager(requireContext()) + val myItemDecoration = + DividerItemDecoration(requireContext(), myLayoutManager.orientation) + layoutManager = myLayoutManager + adapter = pendingAdapter + addItemDecoration(myItemDecoration) + } + + pendingOperationsManager.pendingOperations.observe(viewLifecycleOwner, Observer { + updatePending(it) + }) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.retry_pending -> { + pendingOperationsManager.retryPendingNow() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.pending_operations, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + private fun updatePending(pendingOperations: List<PendingOperationInfo>) { + pendingAdapter.update(pendingOperations) + } + + override fun onPendingOperationClick(type: String, detail: JSONObject) { + Snackbar.make(view!!, "No detail view for $type implemented yet.", LENGTH_SHORT).show() + } + + override fun onPendingOperationActionClick(type: String, detail: JSONObject) { + when (type) { + "proposal-choice" -> { + Log.v(TAG, "got action click on proposal-choice") + val proposalId = detail.optString("proposalId", "") + if (proposalId == "") { + return + } + model.paymentManager.abortProposal(proposalId) + } + } + } + +} + +class PendingOperationsAdapter( + private var items: List<PendingOperationInfo>, + private val listener: PendingOperationClickListener +) : + RecyclerView.Adapter<PendingOperationsAdapter.MyViewHolder>() { + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val rowView = + LayoutInflater.from(parent.context).inflate(R.layout.pending_row, parent, false) + return MyViewHolder(rowView) + } + + override fun getItemCount(): Int { + return items.size + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val p = items[position] + val pendingContainer = holder.rowView.findViewById<LinearLayout>(R.id.pending_container) + pendingContainer.setOnClickListener { + listener.onPendingOperationClick(p.type, p.detail) + } + when (p.type) { + "proposal-choice" -> { + val btn1 = holder.rowView.findViewById<TextView>(R.id.button_pending_action_1) + btn1.text = btn1.context.getString(R.string.pending_operations_refuse) + btn1.visibility = VISIBLE + btn1.setOnClickListener { + listener.onPendingOperationActionClick(p.type, p.detail) + } + } + else -> { + val btn1 = holder.rowView.findViewById<TextView>(R.id.button_pending_action_1) + btn1.text = btn1.context.getString(R.string.pending_operations_no_action) + btn1.visibility = GONE + btn1.setOnClickListener {} + } + } + val textView = holder.rowView.findViewById<TextView>(R.id.pending_text) + val subTextView = holder.rowView.findViewById<TextView>(R.id.pending_subtext) + subTextView.text = p.detail.toString(1) + textView.text = p.type + } + + fun update(items: List<PendingOperationInfo>) { + this.items = items + this.notifyDataSetChanged() + } + + class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) + +} diff --git a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt new file mode 100644 index 0000000..2125dbc --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt @@ -0,0 +1,64 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.pending + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import net.taler.wallet.TAG +import net.taler.wallet.backend.WalletBackendApi +import org.json.JSONObject + +open class PendingOperationInfo( + val type: String, + val detail: JSONObject +) + +class PendingOperationsManager(private val walletBackendApi: WalletBackendApi) { + + private var activeGetPending = 0 + + val pendingOperations = MutableLiveData<List<PendingOperationInfo>>() + + internal fun getPending() { + if (activeGetPending > 0) { + return + } + activeGetPending++ + walletBackendApi.sendRequest("getPendingOperations", null) { isError, result -> + activeGetPending-- + if (isError) { + Log.i(TAG, "got getPending error result") + return@sendRequest + } + Log.i(TAG, "got getPending result") + val pendingList = mutableListOf<PendingOperationInfo>() + val pendingJson = result.getJSONArray("pendingOperations") + for (i in 0 until pendingJson.length()) { + val p = pendingJson.getJSONObject(i) + val type = p.getString("type") + pendingList.add(PendingOperationInfo(type, p)) + } + Log.i(TAG, "Got ${pendingList.size} pending operations") + pendingOperations.postValue((pendingList)) + } + } + + fun retryPendingNow() { + walletBackendApi.sendRequest("retryPendingNow", null) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt new file mode 100644 index 0000000..f0f6610 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt @@ -0,0 +1,64 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.withdraw + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_error.* +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel + +class ErrorFragment : Fragment() { + + private val model: WalletViewModel by activityViewModels() + private val withdrawManager by lazy { model.withdrawManager } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_error, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + errorTitle.setText(R.string.withdraw_error_title) + errorMessage.setText(R.string.withdraw_error_message) + + // show dev error message if dev mode is on + val status = withdrawManager.withdrawStatus.value + if (model.devMode.value == true && status is WithdrawStatus.Error) { + errorDevMessage.visibility = VISIBLE + errorDevMessage.text = status.message + } else { + errorDevMessage.visibility = GONE + } + + backButton.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt new file mode 100644 index 0000000..454816b --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt @@ -0,0 +1,109 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.withdraw + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_prompt_withdraw.* +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel +import net.taler.wallet.fadeIn +import net.taler.wallet.fadeOut +import net.taler.wallet.withdraw.WithdrawStatus.Loading +import net.taler.wallet.withdraw.WithdrawStatus.TermsOfServiceReviewRequired +import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing + +class PromptWithdrawFragment : Fragment() { + + private val model: WalletViewModel by activityViewModels() + private val withdrawManager by lazy { model.withdrawManager } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_prompt_withdraw, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + button_cancel_withdraw.setOnClickListener { + withdrawManager.cancelCurrentWithdraw() + findNavController().navigateUp() + } + + button_confirm_withdraw.setOnClickListener { + val status = withdrawManager.withdrawStatus.value + if (status !is WithdrawStatus.ReceivedDetails) throw AssertionError() + it.fadeOut() + confirmProgressBar.fadeIn() + withdrawManager.acceptWithdrawal(status.talerWithdrawUri, status.suggestedExchange) + } + + withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { + showWithdrawStatus(it) + }) + } + + private fun showWithdrawStatus(status: WithdrawStatus?) = when (status) { + is WithdrawStatus.ReceivedDetails -> { + model.showProgressBar.value = false + progressBar.fadeOut() + + introView.fadeIn() + @SuppressLint("SetTextI18n") + withdrawAmountView.text = "${status.amount.amount} ${status.amount.currency}" + withdrawAmountView.fadeIn() + feeView.fadeIn() + + exchangeIntroView.fadeIn() + withdrawExchangeUrl.text = status.suggestedExchange + withdrawExchangeUrl.fadeIn() + + button_confirm_withdraw.isEnabled = true + } + is WithdrawStatus.Success -> { + model.showProgressBar.value = false + withdrawManager.withdrawStatus.value = null + findNavController().navigate(R.id.action_promptWithdraw_to_withdrawSuccessful) + } + is Loading -> { + model.showProgressBar.value = true + } + is Withdrawing -> { + model.showProgressBar.value = true + } + is TermsOfServiceReviewRequired -> { + model.showProgressBar.value = false + findNavController().navigate(R.id.action_promptWithdraw_to_reviewExchangeTOS) + } + is WithdrawStatus.Error -> { + model.showProgressBar.value = false + findNavController().navigate(R.id.action_promptWithdraw_to_errorFragment) + } + null -> model.showProgressBar.value = false + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt new file mode 100644 index 0000000..cd01a33 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt @@ -0,0 +1,80 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.withdraw + + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_review_exchange_tos.* +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel +import net.taler.wallet.fadeIn +import net.taler.wallet.fadeOut + +class ReviewExchangeTosFragment : Fragment() { + + private val model: WalletViewModel by activityViewModels() + private val withdrawManager by lazy { model.withdrawManager } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_review_exchange_tos, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + acceptTosCheckBox.isChecked = false + acceptTosCheckBox.setOnCheckedChangeListener { _, isChecked -> + acceptTosButton.isEnabled = isChecked + } + abortTosButton.setOnClickListener { + withdrawManager.cancelCurrentWithdraw() + findNavController().navigateUp() + } + acceptTosButton.setOnClickListener { + withdrawManager.acceptCurrentTermsOfService() + } + withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { + when (it) { + is WithdrawStatus.TermsOfServiceReviewRequired -> { + tosTextView.text = it.tosText + tosTextView.fadeIn() + acceptTosCheckBox.fadeIn() + progressBar.fadeOut() + } + is WithdrawStatus.Loading -> { + findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw) + } + is WithdrawStatus.ReceivedDetails -> { + findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw) + } + else -> { + } + } + }) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt new file mode 100644 index 0000000..e3af757 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt @@ -0,0 +1,209 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.withdraw + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import net.taler.wallet.Amount +import net.taler.wallet.TAG +import net.taler.wallet.backend.WalletBackendApi +import org.json.JSONObject + +sealed class WithdrawStatus { + data class Loading(val talerWithdrawUri: String) : WithdrawStatus() + data class TermsOfServiceReviewRequired( + val talerWithdrawUri: String, + val exchangeBaseUrl: String, + val tosText: String, + val tosEtag: String, + val amount: Amount, + val suggestedExchange: String + ) : WithdrawStatus() + + data class ReceivedDetails( + val talerWithdrawUri: String, + val amount: Amount, + val suggestedExchange: String + ) : WithdrawStatus() + + data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus() + + object Success : WithdrawStatus() + data class Error(val message: String?) : WithdrawStatus() +} + +class WithdrawManager(private val walletBackendApi: WalletBackendApi) { + + val withdrawStatus = MutableLiveData<WithdrawStatus>() + val testWithdrawalInProgress = MutableLiveData(false) + + private var currentWithdrawRequestId = 0 + + fun withdrawTestkudos() { + testWithdrawalInProgress.value = true + + walletBackendApi.sendRequest("withdrawTestkudos", null) { _, _ -> + testWithdrawalInProgress.postValue(false) + } + } + + fun getWithdrawalInfo(talerWithdrawUri: String) { + val args = JSONObject() + args.put("talerWithdrawUri", talerWithdrawUri) + + withdrawStatus.value = WithdrawStatus.Loading(talerWithdrawUri) + + this.currentWithdrawRequestId++ + val myWithdrawRequestId = this.currentWithdrawRequestId + + walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result -> + if (isError) { + Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}") + val message = if (result.has("message")) result.getString("message") else null + withdrawStatus.postValue(WithdrawStatus.Error(message)) + return@sendRequest + } + if (myWithdrawRequestId != this.currentWithdrawRequestId) { + val mismatch = "$myWithdrawRequestId != ${this.currentWithdrawRequestId}" + Log.w(TAG, "Got withdraw result for different request id $mismatch") + return@sendRequest + } + Log.v(TAG, "got getWithdrawDetailsForUri result") + val status = withdrawStatus.value + if (status !is WithdrawStatus.Loading) { + Log.v(TAG, "ignoring withdrawal info result, not loading.") + return@sendRequest + } + val wi = result.getJSONObject("bankWithdrawDetails") + val suggestedExchange = wi.getString("suggestedExchange") + // We just use the suggested exchange, in the future there will be + // a selection dialog. + getWithdrawalInfoWithExchange(talerWithdrawUri, suggestedExchange) + } + } + + private fun getWithdrawalInfoWithExchange(talerWithdrawUri: String, selectedExchange: String) { + val args = JSONObject() + args.put("talerWithdrawUri", talerWithdrawUri) + args.put("selectedExchange", selectedExchange) + + this.currentWithdrawRequestId++ + val myWithdrawRequestId = this.currentWithdrawRequestId + + walletBackendApi.sendRequest("getWithdrawDetailsForUri", args) { isError, result -> + if (isError) { + Log.e(TAG, "Error getWithdrawDetailsForUri ${result.toString(4)}") + val message = if (result.has("message")) result.getString("message") else null + withdrawStatus.postValue(WithdrawStatus.Error(message)) + return@sendRequest + } + if (myWithdrawRequestId != this.currentWithdrawRequestId) { + val mismatch = "$myWithdrawRequestId != ${this.currentWithdrawRequestId}" + Log.w(TAG, "Got withdraw result for different request id $mismatch") + return@sendRequest + } + Log.v(TAG, "got getWithdrawDetailsForUri result (with exchange details)") + val status = withdrawStatus.value + if (status !is WithdrawStatus.Loading) { + Log.v(TAG, "ignoring withdrawal info result, not loading.") + return@sendRequest + } + val wi = result.getJSONObject("bankWithdrawDetails") + val suggestedExchange = wi.getString("suggestedExchange") + val amount = Amount.fromJson(wi.getJSONObject("amount")) + + val ei = result.getJSONObject("exchangeWithdrawDetails") + val termsOfServiceAccepted = ei.getBoolean("termsOfServiceAccepted") + + if (!termsOfServiceAccepted) { + val exchange = ei.getJSONObject("exchangeInfo") + val tosText = exchange.getString("termsOfServiceText") + val tosEtag = exchange.optString("termsOfServiceLastEtag", "undefined") + withdrawStatus.postValue( + WithdrawStatus.TermsOfServiceReviewRequired( + status.talerWithdrawUri, + selectedExchange, + tosText, + tosEtag, + amount, + suggestedExchange + ) + ) + } else { + withdrawStatus.postValue( + WithdrawStatus.ReceivedDetails( + status.talerWithdrawUri, + amount, + suggestedExchange + ) + ) + } + } + } + + fun acceptWithdrawal(talerWithdrawUri: String, selectedExchange: String) { + val args = JSONObject() + args.put("talerWithdrawUri", talerWithdrawUri) + args.put("selectedExchange", selectedExchange) + + withdrawStatus.value = WithdrawStatus.Withdrawing(talerWithdrawUri) + + walletBackendApi.sendRequest("acceptWithdrawal", args) { isError, _ -> + if (isError) { + Log.v(TAG, "got acceptWithdrawal error result") + return@sendRequest + } + Log.v(TAG, "got acceptWithdrawal result") + val status = withdrawStatus.value + if (status !is WithdrawStatus.Withdrawing) { + Log.v(TAG, "ignoring acceptWithdrawal result, invalid state") + } + withdrawStatus.postValue(WithdrawStatus.Success) + } + } + + /** + * Accept the currently displayed terms of service. + */ + fun acceptCurrentTermsOfService() { + when (val s = withdrawStatus.value) { + is WithdrawStatus.TermsOfServiceReviewRequired -> { + val args = JSONObject() + args.put("exchangeBaseUrl", s.exchangeBaseUrl) + args.put("etag", s.tosEtag) + walletBackendApi.sendRequest("acceptExchangeTermsOfService", args) { isError, _ -> + if (isError) { + return@sendRequest + } + withdrawStatus.postValue( + WithdrawStatus.ReceivedDetails( + s.talerWithdrawUri, + s.amount, + s.suggestedExchange + ) + ) + } + } + } + } + + fun cancelCurrentWithdraw() { + currentWithdrawRequestId++ + withdrawStatus.value = null + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt new file mode 100644 index 0000000..5daeff1 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawSuccessfulFragment.kt @@ -0,0 +1,44 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.withdraw + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.fragment_withdraw_successful.* +import net.taler.wallet.R + +class WithdrawSuccessfulFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_withdraw_successful, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + backButton.setOnClickListener { + findNavController().navigateUp() + } + } + +} diff --git a/wallet/src/main/res/drawable/history_payment_aborted.xml b/wallet/src/main/res/drawable/history_payment_aborted.xml new file mode 100644 index 0000000..03cd7b2 --- /dev/null +++ b/wallet/src/main/res/drawable/history_payment_aborted.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M15.46 18.12L16.88 19.54L19 17.41L21.12 19.54L22.54 18.12L20.41 16L22.54 13.88L21.12 12.46L19 14.59L16.88 12.46L15.46 13.88L17.59 16M14.97 11.62C14.86 10.28 13.58 8.97 12 9C10.3 9.04 9 10.3 9 12C9 13.7 10.3 14.94 12 15C12.39 15 12.77 14.92 13.14 14.77C13.41 13.67 13.86 12.63 14.97 11.62M13 16H7C7 14.9 6.1 14 5 14V10C6.1 10 7 9.1 7 8H17C17 9.1 17.9 10 19 10V10.05C19.67 10.06 20.34 10.18 21 10.4V6H3V18H13.32C13.1 17.33 13 16.66 13 16Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/history_refresh.xml b/wallet/src/main/res/drawable/history_refresh.xml new file mode 100644 index 0000000..f5d8972 --- /dev/null +++ b/wallet/src/main/res/drawable/history_refresh.xml @@ -0,0 +1,28 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M14.97,11.62C14.86,10.28 13.58,8.97 12,9c-1.7,0.04 -3,1.3 -3,3 0,1.7 1.3,2.94 3,3 0.39,0 0.77,-0.08 1.14,-0.23 0.27,-1.1 0.72,-2.14 1.83,-3.15M13,16H7C7,14.9 6.1,14 5,14V10C6.1,10 7,9.1 7,8h10c0,1.1 0.9,2 2,2v0.05c0.67,0.01 1.34,0.13 2,0.35V6H3V18H13.32C13.1,17.33 13,16.66 13,16Z" /> + <path + android:fillColor="#FF000000" + android:pathData="M19,12 L16.75,14.25 19,16.5V15c1.38,0 2.5,1.12 2.5,2.5 0,0.4 -0.09,0.78 -0.26,1.12l1.09,1.09C22.75,19.08 23,18.32 23,17.5c0,-2.21 -1.79,-4 -4,-4V12m-3.33,3.29C15.25,15.92 15,16.68 15,17.5c0,2.21 1.79,4 4,4V23L21.25,20.75 19,18.5V20c-1.38,0 -2.5,-1.12 -2.5,-2.5 0,-0.4 0.09,-0.78 0.26,-1.12z" /> +</vector> diff --git a/wallet/src/main/res/drawable/history_refund.xml b/wallet/src/main/res/drawable/history_refund.xml new file mode 100644 index 0000000..60872a9 --- /dev/null +++ b/wallet/src/main/res/drawable/history_refund.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M3,11H21V23H3V11M12,15A2,2 0 0,1 14,17A2,2 0 0,1 12,19A2,2 0 0,1 10,17A2,2 0 0,1 12,15M7,13A2,2 0 0,1 5,15V19A2,2 0 0,1 7,21H17A2,2 0 0,1 19,19V15A2,2 0 0,1 17,13H7M17,5V10H15.5V6.5H9.88L12.3,8.93L11.24,10L7,5.75L11.24,1.5L12.3,2.57L9.88,5H17Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/history_tip_accepted.xml b/wallet/src/main/res/drawable/history_tip_accepted.xml new file mode 100644 index 0000000..794d1bf --- /dev/null +++ b/wallet/src/main/res/drawable/history_tip_accepted.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M18,21L15,18L18,15V17H22V19H18V21M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,14C11.15,14 12.25,14.12 13.24,14.34C12.46,15.35 12,16.62 12,18C12,18.7 12.12,19.37 12.34,20H2V18C2,15.79 5.58,14 10,14Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/history_tip_declined.xml b/wallet/src/main/res/drawable/history_tip_declined.xml new file mode 100644 index 0000000..4838ee4 --- /dev/null +++ b/wallet/src/main/res/drawable/history_tip_declined.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M10 4A4 4 0 0 0 6 8A4 4 0 0 0 10 12A4 4 0 0 0 14 8A4 4 0 0 0 10 4M17.5 13C15 13 13 15 13 17.5C13 20 15 22 17.5 22C20 22 22 20 22 17.5C22 15 20 13 17.5 13M10 14C5.58 14 2 15.79 2 18V20H11.5A6.5 6.5 0 0 1 11 17.5A6.5 6.5 0 0 1 11.95 14.14C11.32 14.06 10.68 14 10 14M17.5 14.5C19.16 14.5 20.5 15.84 20.5 17.5C20.5 18.06 20.35 18.58 20.08 19L16 14.92C16.42 14.65 16.94 14.5 17.5 14.5M14.92 16L19 20.08C18.58 20.35 18.06 20.5 17.5 20.5C15.84 20.5 14.5 19.16 14.5 17.5C14.5 16.94 14.65 16.42 14.92 16Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/history_withdrawn.xml b/wallet/src/main/res/drawable/history_withdrawn.xml new file mode 100644 index 0000000..f524474 --- /dev/null +++ b/wallet/src/main/res/drawable/history_withdrawn.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M3 0V3H0V5H3V8H5V5H8V3H5V0H3M9 3V6H6V9H3V19C3 20.1 3.89 21 5 21H19C20.11 21 21 20.11 21 19V18H12C10.9 18 10 17.11 10 16V8C10 6.9 10.89 6 12 6H21V5C21 3.9 20.11 3 19 3H9M12 8V16H22V8H12M16 10.5C16.83 10.5 17.5 11.17 17.5 12C17.5 12.83 16.83 13.5 16 13.5C15.17 13.5 14.5 12.83 14.5 12C14.5 11.17 15.17 10.5 16 10.5Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_account_balance.xml b/wallet/src/main/res/drawable/ic_account_balance.xml new file mode 100644 index 0000000..e9f51a2 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_account_balance.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M4,10v7h3v-7L4,10zM10,10v7h3v-7h-3zM2,22h19v-3L2,19v3zM16,10v7h3v-7h-3zM11.5,1L2,6v2h19L21,6l-9.5,-5z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_account_balance_wallet.xml b/wallet/src/main/res/drawable/ic_account_balance_wallet.xml new file mode 100644 index 0000000..514b118 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_account_balance_wallet.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M21,18v1c0,1.1 -0.9,2 -2,2L5,21c-1.11,0 -2,-0.9 -2,-2L3,5c0,-1.1 0.89,-2 2,-2h14c1.1,0 2,0.9 2,2v1h-9c-1.11,0 -2,0.9 -2,2v8c0,1.1 0.89,2 2,2h9zM12,16h10L22,8L12,8v8zM16,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_add_circle.xml b/wallet/src/main/res/drawable/ic_add_circle.xml new file mode 100644 index 0000000..c32faa6 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_add_circle.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_cancel.xml b/wallet/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000..6dc55cf --- /dev/null +++ b/wallet/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/> +</vector> diff --git a/wallet/src/main/res/drawable/ic_cash_usd_outline.xml b/wallet/src/main/res/drawable/ic_cash_usd_outline.xml new file mode 100644 index 0000000..428a466 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_cash_usd_outline.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M20,18H4V6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4M11,17H13V16H14A1,1 0 0,0 15,15V12A1,1 0 0,0 14,11H11V10H15V8H13V7H11V8H10A1,1 0 0,0 9,9V12A1,1 0 0,0 10,13H13V14H9V16H11V17Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_check_circle.xml b/wallet/src/main/res/drawable/ic_check_circle.xml new file mode 100644 index 0000000..c299cc3 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_check_circle.xml @@ -0,0 +1,26 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.56" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="@color/green" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_directions.xml b/wallet/src/main/res/drawable/ic_directions.xml new file mode 100644 index 0000000..7fc7fe7 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_directions.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z"/> +</vector> diff --git a/wallet/src/main/res/drawable/ic_error.xml b/wallet/src/main/res/drawable/ic_error.xml new file mode 100644 index 0000000..1f705af --- /dev/null +++ b/wallet/src/main/res/drawable/ic_error.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/> +</vector> diff --git a/wallet/src/main/res/drawable/ic_history_black_24dp.xml b/wallet/src/main/res/drawable/ic_history_black_24dp.xml new file mode 100644 index 0000000..4404ee4 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_history_black_24dp.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/> +</vector> diff --git a/wallet/src/main/res/drawable/ic_home_black_24dp.xml b/wallet/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 0000000..ed8aa1e --- /dev/null +++ b/wallet/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,25 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/> +</vector> diff --git a/wallet/src/main/res/drawable/ic_launcher_foreground.xml b/wallet/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..028c873 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,68 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="237.28813" + android:viewportHeight="237.2881"> + <group + android:translateX="48.64407" + android:translateY="48.644062"> + <path + android:fillAlpha="1" + android:fillColor="#ffffff" + android:pathData="m31.669,82.748h-4.702v-19.684h-6.041v-4.112h16.783v4.112L31.669,63.064Z" + android:strokeWidth="0.81604069" + android:strokeColor="#00000000" /> + <path + android:fillAlpha="1" + android:fillColor="#ffffff" + android:pathData="m50.301,74.364q-2.614,0 -3.65,0.669 -1.036,0.669 -1.036,2.295 0,1.211 0.717,1.929 0.717,0.717 1.944,0.717 1.849,0 2.869,-1.387 1.02,-1.403 1.02,-3.905v-0.319zM56.804,72.563v10.185h-4.638v-1.992q-0.845,1.179 -2.168,1.817 -1.323,0.638 -2.917,0.638 -3.044,0 -4.75,-1.61 -1.689,-1.61 -1.689,-4.495 0,-3.124 2.024,-4.606 2.024,-1.498 6.264,-1.498h3.235v-0.781q0,-1.132 -0.829,-1.705 -0.813,-0.59 -2.407,-0.59 -1.674,0 -3.251,0.43 -1.562,0.414 -3.267,1.339v-3.985q1.546,-0.638 3.14,-0.94 1.594,-0.303 3.379,-0.303 4.351,0 6.104,1.769 1.769,1.769 1.769,6.328z" + android:strokeWidth="0.81604069" + android:strokeColor="#00000000" /> + <path + android:fillAlpha="1" + android:fillColor="#ffffff" + android:pathData="m64.964,75.305v-13.771h-4.734v-3.586h9.404v17.357q0,2.104 0.653,2.98 0.653,0.877 2.215,0.877h3.73v3.586h-5.037q-3.331,0 -4.781,-1.721 -1.45,-1.721 -1.45,-5.722z" + android:strokeWidth="0.81604069" + android:strokeColor="#00000000" /> + <path + android:fillAlpha="1" + android:fillColor="#ffffff" + android:pathData="m96.012,81.871q-1.626,0.669 -3.315,1.004 -1.689,0.335 -3.57,0.335 -4.479,0 -6.853,-2.391 -2.359,-2.407 -2.359,-6.917 0,-4.367 2.279,-6.901 2.279,-2.534 6.216,-2.534 3.969,0 6.152,2.359 2.199,2.343 2.199,6.614v1.897h-12.097q0.016,2.104 1.243,3.14 1.227,1.036 3.666,1.036 1.61,0 3.172,-0.462 1.562,-0.462 3.267,-1.466zM92.059,71.83q-0.032,-1.849 -0.956,-2.789 -0.908,-0.956 -2.694,-0.956 -1.61,0 -2.566,0.988 -0.956,0.972 -1.132,2.773z" + android:strokeWidth="0.81604069" + android:strokeColor="#00000000" /> + <path + android:fillAlpha="1" + android:fillColor="#ffffff" + android:pathData="m116.445,69.822q-0.765,-0.701 -1.801,-1.052 -1.02,-0.351 -2.247,-0.351 -1.482,0 -2.598,0.526 -1.1,0.51 -1.705,1.498 -0.383,0.606 -0.542,1.466 -0.143,0.861 -0.143,2.614v8.224h-4.67v-17.851h4.67v2.773q0.685,-1.53 2.104,-2.359 1.419,-0.845 3.315,-0.845 0.956,0 1.865,0.239 0.924,0.223 1.753,0.669z" + android:strokeWidth="0.81604069" + android:strokeColor="#00000000" /> + <path + android:fillAlpha="1" + android:fillColor="#ae1010" + android:pathData="M25.843,97.583L16.433,97.583L0,70.865 16.433,44.111l9.409,0l-16.522,26.754z" + android:strokeWidth="2.03518677" + android:strokeColor="#00000000" /> + <path + android:fillAlpha="1" + android:fillColor="#ae1010" + android:pathData="m109.483,97.667 l17.087,-27.134 -17.041,-27.171l9.712,0l17.041,27.171 -17.041,27.134z" + android:strokeWidth="2.08855891" + android:strokeColor="#00000000" /> + </group> +</vector> diff --git a/wallet/src/main/res/drawable/ic_scan_qr.xml b/wallet/src/main/res/drawable/ic_scan_qr.xml new file mode 100644 index 0000000..2ca8a69 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_scan_qr.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorOnPrimarySurface" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000" + android:pathData="M4,4H10V10H4V4M20,4V10H14V4H20M14,15H16V13H14V11H16V13H18V11H20V13H18V15H20V18H18V20H16V18H13V20H11V16H14V15M16,15V18H18V15H16M4,20V14H10V20H4M6,6V8H8V6H6M16,6V8H18V6H16M6,16V18H8V16H6M4,11H6V13H4V11M9,11H13V15H11V13H9V11M11,6H13V10H11V6M2,2V6H0V2A2,2 0 0,1 2,0H6V2H2M22,0A2,2 0 0,1 24,2V6H22V2H18V0H22M2,18V22H6V24H2A2,2 0 0,1 0,22V18H2M22,22V18H24V22A2,2 0 0,1 22,24H18V22H22Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_settings.xml b/wallet/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..7cadd58 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FF000000" + android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" /> +</vector> diff --git a/wallet/src/main/res/drawable/ic_sync.xml b/wallet/src/main/res/drawable/ic_sync.xml new file mode 100644 index 0000000..78593fc --- /dev/null +++ b/wallet/src/main/res/drawable/ic_sync.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" /> +</vector> diff --git a/wallet/src/main/res/drawable/pending_border.xml b/wallet/src/main/res/drawable/pending_border.xml new file mode 100644 index 0000000..bb50fea --- /dev/null +++ b/wallet/src/main/res/drawable/pending_border.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" > + + <!-- View background color --> + <solid + android:color="@android:color/transparent" > + </solid> + + <!-- View border color and width --> + <stroke + android:width="1dp" + android:color="@color/colorPrimary" > + </stroke> + + <!-- The radius makes the corners rounded --> + <corners + android:radius="2dp" > + </corners> + +</shape>
\ No newline at end of file diff --git a/wallet/src/main/res/drawable/side_nav_bar.xml b/wallet/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 0000000..6be80a8 --- /dev/null +++ b/wallet/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,24 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="135" + android:startColor="@color/colorPrimary" + android:endColor="@color/colorPrimaryDark" + android:type="linear"/> +</shape>
\ No newline at end of file diff --git a/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml b/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml new file mode 100644 index 0000000..d9e2f59 --- /dev/null +++ b/wallet/src/main/res/layout-w550dp/payment_bottom_bar.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/bottomView" + style="@style/BottomCard" + android:layout_width="0dp" + android:layout_height="wrap_content" + tools:showIn="@layout/fragment_prompt_payment"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/totalLabelView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:text="@string/payment_label_amount_total" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/totalView" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toEndOf="@+id/abortButton" + app:layout_constraintTop_toTopOf="@+id/totalView" + app:layout_constraintVertical_bias="0.0" + tools:visibility="visible" /> + + <TextView + android:id="@+id/totalView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:textColor="?android:attr/textColorPrimary" + android:textStyle="bold" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/feeView" + app:layout_constraintEnd_toStartOf="@+id/confirmButton" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toEndOf="@+id/totalLabelView" + app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginBottom="8dp" + tools:text="10 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/feeView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/confirmButton" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/totalView" + tools:text="@string/payment_fee" + tools:visibility="visible" /> + + <Button + android:id="@+id/abortButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:backgroundTint="@color/red" + android:text="@string/payment_button_abort" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/confirmButton" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/confirmButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/payment_button_confirm" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/abortButton" + app:layout_constraintTop_toTopOf="parent" + tools:enabled="true" /> + + <ProgressBar + android:id="@+id/confirmProgressBar" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/confirmButton" + app:layout_constraintEnd_toEndOf="@+id/confirmButton" + app:layout_constraintStart_toStartOf="@+id/confirmButton" + app:layout_constraintTop_toTopOf="@+id/confirmButton" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</com.google.android.material.card.MaterialCardView> diff --git a/wallet/src/main/res/layout/activity_main.xml b/wallet/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0612306 --- /dev/null +++ b/wallet/src/main/res/layout/activity_main.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/drawer_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + tools:openDrawer="start"> + + <include + layout="@layout/app_bar_main" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <com.google.android.material.navigation.NavigationView + android:id="@+id/nav_view" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="start" + android:fitsSystemWindows="false" + app:headerLayout="@layout/nav_header_main" + app:menu="@menu/activity_main_drawer" /> + +</androidx.drawerlayout.widget.DrawerLayout> diff --git a/wallet/src/main/res/layout/app_bar_main.xml b/wallet/src/main/res/layout/app_bar_main.xml new file mode 100644 index 0000000..d976be8 --- /dev/null +++ b/wallet/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity"> + + <com.google.android.material.appbar.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/relativeLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + style="@style/AppTheme.Toolbar" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <me.zhanghai.android.materialprogressbar.MaterialProgressBar + android:id="@+id/progress_bar" + style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal" + android:layout_width="0dp" + android:layout_height="4dp" + android:elevation="4dp" + android:indeterminate="true" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/toolbar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:mpb_progressStyle="horizontal" + app:mpb_useIntrinsicPadding="false" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.appbar.AppBarLayout> + + <androidx.fragment.app.FragmentContainerView + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:defaultNavHost="true" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navGraph="@navigation/nav_graph" /> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/wallet/src/main/res/layout/fragment_already_paid.xml b/wallet/src/main/res/layout/fragment_already_paid.xml new file mode 100644 index 0000000..d36fe69 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_already_paid.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="15dp" + android:orientation="vertical" + tools:context=".payment.PaymentSuccessfulFragment"> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="50dp" + android:layout_gravity="center" + android:text="@string/payment_already_paid" + android:textAlignment="center" + android:textColor="@android:color/holo_green_dark" + app:autoSizeTextType="uniform" /> + + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <Button + android:id="@+id/backButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/button_back" /> + +</LinearLayout> diff --git a/wallet/src/main/res/layout/fragment_error.xml b/wallet/src/main/res/layout/fragment_error.xml new file mode 100644 index 0000000..3d977dd --- /dev/null +++ b/wallet/src/main/res/layout/fragment_error.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".withdraw.ErrorFragment"> + + <ImageView + android:id="@+id/errorImageView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="16dp" + android:alpha="0.56" + android:src="@drawable/ic_error" + android:tint="@color/red" + app:layout_constraintBottom_toTopOf="@+id/errorTitle" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + app:layout_constraintVertical_chainStyle="packed" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/errorTitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center_horizontal|top" + android:minHeight="64dp" + android:textColor="@color/red" + app:autoSizeMaxTextSize="40sp" + app:autoSizeTextType="uniform" + app:layout_constraintBottom_toTopOf="@+id/errorMessage" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/errorImageView" + tools:text="@string/withdraw_error_title" /> + + <TextView + android:id="@+id/errorMessage" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" + app:layout_constraintBottom_toTopOf="@+id/errorDevMessage" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/errorTitle" + tools:text="@string/withdraw_error_message" /> + + <TextView + android:id="@+id/errorDevMessage" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:fontFamily="monospace" + android:gravity="center" + android:textColor="@color/red" + android:textIsSelectable="true" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@+id/backButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/errorMessage" + tools:text="Error: Fetching keys failed: unexpected status for keys: 502" + tools:visibility="visible" /> + + <Button + android:id="@+id/backButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="@string/button_back" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_json.xml b/wallet/src/main/res/layout/fragment_json.xml new file mode 100644 index 0000000..1e0c047 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_json.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/jsonView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp" + android:fontFamily="monospace" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="[JSON]" /> + +</ScrollView> diff --git a/wallet/src/main/res/layout/fragment_payment_successful.xml b/wallet/src/main/res/layout/fragment_payment_successful.xml new file mode 100644 index 0000000..cf9e5e8 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_payment_successful.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/frameLayout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="16dp" + tools:context=".payment.PaymentSuccessfulFragment"> + + <ImageView + android:id="@+id/successImageView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="32dp" + android:src="@drawable/ic_check_circle" + app:layout_constraintBottom_toTopOf="@+id/successTextView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="ContentDescription" /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/successTextView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginBottom="16dp" + android:text="@string/payment_successful" + android:textAlignment="center" + android:textColor="@color/green" + app:autoSizeMaxTextSize="48sp" + app:autoSizeMinTextSize="10sp" + app:autoSizeTextType="uniform" + app:layout_constraintBottom_toTopOf="@+id/backButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/successImageView" /> + + <Button + android:id="@+id/backButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/payment_back_button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_pending_operations.xml b/wallet/src/main/res/layout/fragment_pending_operations.xml new file mode 100644 index 0000000..26c1be1 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_pending_operations.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_pending" + android:layout_width="0dp" + android:layout_height="0dp" + android:scrollbars="vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:listitem="@layout/pending_row" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_product_image.xml b/wallet/src/main/res/layout/fragment_product_image.xml new file mode 100644 index 0000000..9f65d4d --- /dev/null +++ b/wallet/src/main/res/layout/fragment_product_image.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/productImageView" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:ignore="ContentDescription"> + +</ImageView>
\ No newline at end of file diff --git a/wallet/src/main/res/layout/fragment_prompt_payment.xml b/wallet/src/main/res/layout/fragment_prompt_payment.xml new file mode 100644 index 0000000..26cbeb6 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_prompt_payment.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".payment.PromptPaymentFragment"> + + <include + android:id="@+id/scrollView" + layout="@layout/payment_details" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@+id/bottomView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <include + android:id="@+id/bottomView" + layout="@layout/payment_bottom_bar" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/scrollView" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_prompt_withdraw.xml b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml new file mode 100644 index 0000000..1114c17 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_prompt_withdraw.xml @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".withdraw.PromptWithdrawFragment"> + + <TextView + android:id="@+id/introView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/withdraw_do_you_want" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawAmountView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:visibility="visible" /> + + <TextView + android:id="@+id/withdrawAmountView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:textAppearance="@style/TextAppearance.AppCompat.Headline" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/feeView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/introView" + tools:text="10.00 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/feeView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:text="@string/withdraw_fees" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/exchangeIntroView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawAmountView" + tools:visibility="visible" /> + + <TextView + android:id="@+id/exchangeIntroView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="32dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:gravity="center" + android:text="@string/withdraw_exchange" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawExchangeUrl" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/feeView" + tools:visibility="visible" /> + + <TextView + android:id="@+id/withdrawExchangeUrl" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:gravity="center" + android:textSize="25sp" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/withdrawCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/exchangeIntroView" + tools:text="(exchange base url)" + tools:visibility="visible" /> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toTopOf="@+id/withdrawCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/withdrawCard" + style="@style/BottomCard" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp"> + + <Button + android:id="@+id/button_cancel_withdraw" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/red" + android:text="@string/button_cancel" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/button_confirm_withdraw" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/button_confirm_withdraw" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/withdraw_button_confirm" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/button_cancel_withdraw" /> + + <ProgressBar + android:id="@+id/confirmProgressBar" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/button_confirm_withdraw" + app:layout_constraintEnd_toEndOf="@+id/button_confirm_withdraw" + app:layout_constraintStart_toStartOf="@+id/button_confirm_withdraw" + app:layout_constraintTop_toTopOf="@+id/button_confirm_withdraw" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.card.MaterialCardView> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_review_exchange_tos.xml b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml new file mode 100644 index 0000000..61a61f1 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".withdraw.ReviewExchangeTosFragment"> + + <ScrollView + android:id="@+id/tosScrollView" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@+id/buttonCard" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <TextView + android:id="@+id/tosTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:visibility="invisible" + tools:text="@tools:sample/lorem/random" + tools:visibility="visible" /> + + </ScrollView> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="@+id/tosScrollView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/buttonCard" + style="@style/BottomCard" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp"> + + <CheckBox + android:id="@+id/acceptTosCheckBox" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/exchange_tos_accept" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/acceptTosButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:visibility="visible" /> + + <Button + android:id="@+id/abortTosButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/red" + android:text="@string/button_cancel" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/acceptTosButton" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/acceptTosButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/exchange_tos_button_continue" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/abortTosButton" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.card.MaterialCardView> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_settings.xml b/wallet/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..2fa0fcc --- /dev/null +++ b/wallet/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="10dp" + android:orientation="vertical" + tools:context=".Settings"> + + + <TextView + android:id="@+id/editText2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:text="@string/settings_version" + android:textSize="18sp" /> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:id="@+id/textView5" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/app_name" /> + + <TextView + android:id="@+id/textView4" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + tools:text="0.6.0pre8" /> + + </LinearLayout> + + <Space + android:layout_width="0dp" + android:layout_height="15dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:text="@string/settings_backups" + android:textSize="18sp" + android:visibility="gone" /> + + <Button + android:id="@+id/button_backup_export" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/settings_export_to_file" + android:visibility="gone" /> + + <Button + android:id="@+id/button_backup_import" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/settings_import_from_file" + android:visibility="gone" /> + + + <TextView + android:id="@+id/devSettingsTitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:text="@string/settings_developer" + android:textSize="18sp" /> + + <!-- + <Button + android:text="Withdraw TESTKUDOS" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button_withdraw_testkudos"/>--> + + <Button + android:id="@+id/button_reset_wallet_dangerously" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/settings_reset" /> + +</LinearLayout> diff --git a/wallet/src/main/res/layout/fragment_show_balance.xml b/wallet/src/main/res/layout/fragment_show_balance.xml new file mode 100644 index 0000000..5bc6ee8 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_show_balance.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/balancesList" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@+id/scanButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + app:layout_constraintVertical_chainStyle="packed" + tools:layout_height="200dp" + tools:listitem="@layout/list_item_balance" + tools:visibility="visible" /> + + <TextView + android:id="@+id/balancesEmptyState" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:autoLink="web" + android:gravity="center" + android:padding="16dp" + android:text="@string/balances_empty_state" + android:textSize="18sp" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@+id/scanButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="gone" /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/barrier" + android:layout_width="0dp" + android:layout_height="0dp" + app:barrierAllowsGoneWidgets="false" + app:barrierDirection="bottom" + app:constraint_referenced_ids="balancesList, balancesEmptyState" /> + + <Button + android:id="@+id/scanButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:drawableLeft="@drawable/ic_scan_qr" + android:padding="16dp" + android:text="@string/button_scan_qr_code" + app:layout_constraintBottom_toTopOf="@+id/testWithdrawButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/barrier" + app:layout_constraintVertical_chainStyle="packed" + tools:ignore="RtlHardcoded" /> + + <Button + android:id="@+id/testWithdrawButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:padding="16dp" + android:text="@string/withdraw_button_testkudos" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/scanButton" + tools:visibility="visible" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/fragment_show_history.xml b/wallet/src/main/res/layout/fragment_show_history.xml new file mode 100644 index 0000000..3e84b0f --- /dev/null +++ b/wallet/src/main/res/layout/fragment_show_history.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/historyList" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical" /> + + <TextView + android:id="@+id/historyEmptyState" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/history_empty" + android:visibility="invisible" + tools:visibility="visible" /> + + <ProgressBar + android:id="@+id/historyProgressBar" + style="?android:progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:visibility="invisible" + tools:visibility="visible" /> + +</FrameLayout> diff --git a/wallet/src/main/res/layout/fragment_withdraw_successful.xml b/wallet/src/main/res/layout/fragment_withdraw_successful.xml new file mode 100644 index 0000000..2b7c308 --- /dev/null +++ b/wallet/src/main/res/layout/fragment_withdraw_successful.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".withdraw.WithdrawSuccessfulFragment"> + + <TextView + android:id="@+id/withdrawHeadlineView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="16dp" + android:gravity="center_horizontal|bottom" + android:text="@string/withdraw_accepted" + android:textColor="@color/green" + app:autoSizeMaxTextSize="40sp" + app:autoSizeTextType="uniform" + app:layout_constraintBottom_toTopOf="@+id/withdrawInfoView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/withdrawInfoView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="16dp" + android:text="@string/withdraw_success_info" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.AppCompat.Medium" + app:layout_constraintBottom_toTopOf="@+id/backButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawHeadlineView" /> + + <Button + android:id="@+id/backButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="@string/button_continue" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/withdrawInfoView" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/history_payment.xml b/wallet/src/main/res/layout/history_payment.xml new file mode 100644 index 0000000..dd135e7 --- /dev/null +++ b/wallet/src/main/res/layout/history_payment.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:background="?attr/selectableItemBackground"> + + <ImageView + android:id="@+id/icon" + android:layout_width="32dp" + android:layout_height="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/history_withdrawn" + app:tint="?android:colorControlNormal" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/title" + style="@style/HistoryTitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + app:layout_constraintEnd_toStartOf="@+id/amountPaidWithFees" + app:layout_constraintStart_toEndOf="@+id/icon" + app:layout_constraintTop_toTopOf="parent" + tools:text="Lots of books with very long titles" /> + + <TextView + android:id="@+id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + app:layout_constrainedWidth="true" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/amountPaidWithFees" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toEndOf="@+id/icon" + app:layout_constraintTop_toBottomOf="@+id/title" + app:layout_constraintVertical_bias="0.0" + tools:text="@string/history_event_payment_sent" /> + + <TextView + android:id="@+id/amountPaidWithFees" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/red" + android:textSize="16sp" + app:layout_constraintBottom_toTopOf="@+id/time" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="0.2 TESTKUDOS" /> + + <TextView + android:id="@+id/time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:text="23 min ago" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/history_receive.xml b/wallet/src/main/res/layout/history_receive.xml new file mode 100644 index 0000000..1f76376 --- /dev/null +++ b/wallet/src/main/res/layout/history_receive.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" + android:background="?attr/selectableItemBackground"> + + <ImageView + android:id="@+id/icon" + android:layout_width="32dp" + android:layout_height="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/history_withdrawn" + app:tint="?android:colorControlNormal" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/title" + style="@style/HistoryTitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:text="@string/history_event_withdrawn" + app:layout_constraintEnd_toStartOf="@+id/amountWithdrawn" + app:layout_constraintStart_toEndOf="@+id/icon" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/summary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp" + app:layout_constrainedWidth="true" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/feeLabel" + app:layout_constraintStart_toEndOf="@+id/icon" + app:layout_constraintTop_toBottomOf="@+id/title" + tools:text="http://taler.quite-long-exchange.url" /> + + <TextView + android:id="@+id/feeLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="2dp" + android:text="@string/history_fee_label" + app:layout_constraintEnd_toStartOf="@+id/fee" + app:layout_constraintTop_toTopOf="@+id/fee" /> + + <TextView + android:id="@+id/fee" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/red" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/amountWithdrawn" + tools:text="0.2 TESTKUDOS" /> + + <TextView + android:id="@+id/amountWithdrawn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/green" + android:textSize="16sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="10 TESTKUDOS" /> + + <TextView + android:id="@+id/time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textSize="14sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/fee" + tools:text="23 min. ago" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/history_row.xml b/wallet/src/main/res/layout/history_row.xml new file mode 100644 index 0000000..8f0db1f --- /dev/null +++ b/wallet/src/main/res/layout/history_row.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="15dp" + android:background="?attr/selectableItemBackground"> + + <ImageView + android:id="@+id/icon" + android:layout_width="32dp" + android:layout_height="32dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_account_balance" + app:tint="?android:colorControlNormal" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/title" + style="@style/HistoryTitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/icon" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="My History Event" /> + + <TextView + android:id="@+id/info" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/time" + app:layout_constraintStart_toEndOf="@+id/icon" + app:layout_constraintTop_toBottomOf="@+id/title" + tools:text="TextView" /> + + <TextView + android:id="@+id/time" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:gravity="end" + android:textSize="14sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/title" + tools:text="3 days ago" /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/wallet/src/main/res/layout/list_item_balance.xml b/wallet/src/main/res/layout/list_item_balance.xml new file mode 100644 index 0000000..f9c37b7 --- /dev/null +++ b/wallet/src/main/res/layout/list_item_balance.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp"> + + <TextView + android:id="@+id/balance_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:textSize="40sp" + app:layout_constraintEnd_toStartOf="@+id/balance_currency" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="100.50" /> + + <TextView + android:id="@+id/balance_currency" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="20sp" + app:layout_constraintBottom_toBottomOf="@+id/balance_amount" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/balance_amount" + app:layout_constraintTop_toTopOf="@+id/balance_amount" + tools:text="TESTKUDOS" /> + + <TextView + android:id="@+id/balanceInboundAmount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/green" + android:textSize="20sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/balanceInboundLabel" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/balance_amount" + tools:text="+10 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/balanceInboundLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:text="@string/balances_inbound_label" + android:textColor="@color/green" + app:layout_constraintBottom_toBottomOf="@+id/balanceInboundAmount" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/balanceInboundAmount" + app:layout_constraintTop_toTopOf="@+id/balanceInboundAmount" + tools:visibility="visible" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/list_item_product.xml b/wallet/src/main/res/layout/list_item_product.xml new file mode 100644 index 0000000..fe6ba23 --- /dev/null +++ b/wallet/src/main/res/layout/list_item_product.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp"> + + <TextView + android:id="@+id/quantity" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:gravity="end" + android:minWidth="24dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="31" /> + + <ImageView + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_marginStart="8dp" + app:layout_constrainedWidth="true" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintDimensionRatio="H,4:3" + app:layout_constraintEnd_toStartOf="@+id/name" + app:layout_constraintStart_toEndOf="@+id/quantity" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_max="64dp" + tools:ignore="ContentDescription" + tools:srcCompat="@tools:sample/avatars" + tools:visibility="visible" /> + + <TextView + android:id="@+id/name" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/price" + app:layout_constraintStart_toEndOf="@+id/image" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="A product item that in some cases could have a very long name" /> + + <TextView + android:id="@+id/price" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="23.42" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/list_item_product_single.xml b/wallet/src/main/res/layout/list_item_product_single.xml new file mode 100644 index 0000000..a08f1f8 --- /dev/null +++ b/wallet/src/main/res/layout/list_item_product_single.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp"> + + <TextView + android:id="@+id/quantity" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:layout_constraintEnd_toStartOf="@+id/name" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="31" /> + + <ImageView + android:id="@+id/image" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/name" + tools:ignore="ContentDescription" + tools:srcCompat="@tools:sample/avatars" + tools:visibility="visible" /> + + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:visibility="gone" + app:layout_constrainedWidth="true" + app:layout_constraintBottom_toTopOf="@+id/image" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/price" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/quantity" + app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginEnd="0dp" + tools:text="A product item that can have a very long name that wraps over two lines" /> + + <TextView + android:id="@+id/price" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="23.42" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/nav_header_main.xml b/wallet/src/main/res/layout/nav_header_main.xml new file mode 100644 index 0000000..5574c1f --- /dev/null +++ b/wallet/src/main/res/layout/nav_header_main.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/side_nav_bar" + android:theme="@style/ThemeOverlay.AppCompat.Dark"> + + <ImageView + android:id="@+id/talerLogoView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:contentDescription="@string/nav_header_desc" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@mipmap/ic_launcher_round" /> + + <TextView + android:id="@+id/gnuView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:text="@string/nav_header_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/talerLogoView" /> + + <TextView + android:id="@+id/walletView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="16dp" + android:text="@string/nav_header_subtitle" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/gnuView" /> + + <TextView + android:id="@+id/versionView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="16dp" + app:layout_constraintBottom_toBottomOf="@+id/walletView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toEndOf="@+id/walletView" + app:layout_constraintTop_toTopOf="@+id/walletView" + tools:text="0.6.9-pre15" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/wallet/src/main/res/layout/payment_bottom_bar.xml b/wallet/src/main/res/layout/payment_bottom_bar.xml new file mode 100644 index 0000000..8fdf0f8 --- /dev/null +++ b/wallet/src/main/res/layout/payment_bottom_bar.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + style="@style/BottomCard" + android:layout_width="0dp" + android:layout_height="wrap_content" + tools:showIn="@layout/fragment_prompt_payment"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/totalLabelView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:text="@string/payment_label_amount_total" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/abortButton" + app:layout_constraintEnd_toStartOf="@+id/totalView" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:visibility="visible" /> + + <TextView + android:id="@+id/totalView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:textColor="?android:attr/textColorPrimary" + android:textStyle="bold" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/feeView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintStart_toEndOf="@+id/totalLabelView" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.0" + tools:text="10 TESTKUDOS" + tools:visibility="visible" /> + + <TextView + android:id="@+id/feeView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@+id/confirmButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/totalView" + tools:text="@string/payment_fee" + tools:visibility="visible" /> + + <Button + android:id="@+id/abortButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:backgroundTint="@color/red" + android:text="@string/payment_button_abort" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/confirmButton" + app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintStart_toStartOf="parent" /> + + <Button + android:id="@+id/confirmButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:backgroundTint="@color/green" + android:enabled="false" + android:text="@string/payment_button_confirm" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/abortButton" + app:layout_constraintTop_toBottomOf="@+id/feeView" + tools:enabled="true" /> + + <ProgressBar + android:id="@+id/confirmProgressBar" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/confirmButton" + app:layout_constraintEnd_toEndOf="@+id/confirmButton" + app:layout_constraintStart_toStartOf="@+id/confirmButton" + app:layout_constraintTop_toTopOf="@+id/confirmButton" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</com.google.android.material.card.MaterialCardView> diff --git a/wallet/src/main/res/layout/payment_details.xml b/wallet/src/main/res/layout/payment_details.xml new file mode 100644 index 0000000..60d1d73 --- /dev/null +++ b/wallet/src/main/res/layout/payment_details.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="0dp" + android:layout_height="0dp" + android:fillViewport="true" + tools:showIn="@layout/fragment_prompt_payment"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/errorView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/activity_horizontal_margin" + android:textAlignment="center" + android:textColor="@android:color/holo_red_dark" + android:textSize="22sp" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@+id/orderLabelView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@string/payment_balance_insufficient" + tools:visibility="visible" /> + + <TextView + android:id="@+id/orderLabelView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_horizontal_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:text="@string/payment_label_order_summary" + android:textAlignment="center" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/orderView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/errorView" + tools:visibility="visible" /> + + <TextView + android:id="@+id/orderView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/activity_horizontal_margin" + android:layout_marginTop="16dp" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.AppCompat.Headline" + android:textSize="25sp" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@+id/detailsButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/orderLabelView" + tools:text="2 x Cappuccino, 1 x Hot Meals, 1 x Dessert" + tools:visibility="visible" /> + + <Button + android:id="@+id/detailsButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/payment_show_details" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@+id/productsList" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/orderView" + tools:visibility="visible" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/productsList" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/activity_horizontal_margin" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/detailsButton" + tools:listitem="@layout/list_item_product" + tools:visibility="visible" /> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:indeterminate="false" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> diff --git a/wallet/src/main/res/layout/pending_row.xml b/wallet/src/main/res/layout/pending_row.xml new file mode 100644 index 0000000..3505398 --- /dev/null +++ b/wallet/src/main/res/layout/pending_row.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/pending_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="3dp" + android:padding="3dp" + android:background="@drawable/pending_border" + android:orientation="vertical"> + + <TextView + android:id="@+id/pending_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="24sp" + tools:text="My Pending Operation" /> + + <Button + android:id="@+id/button_pending_action_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="Cancel Operation" /> + + <TextView + android:id="@+id/pending_subtext" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + tools:text="My further details" /> + +</LinearLayout>
\ No newline at end of file diff --git a/wallet/src/main/res/menu/activity_main_drawer.xml b/wallet/src/main/res/menu/activity_main_drawer.xml new file mode 100644 index 0000000..5eee6cc --- /dev/null +++ b/wallet/src/main/res/menu/activity_main_drawer.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:showIn="@layout/activity_main"> + + <group android:checkableBehavior="single"> + <item + android:id="@+id/nav_home" + android:icon="@drawable/ic_account_balance_wallet" + android:title="@string/balances_title" + tools:checked="true" /> + <item + android:id="@+id/nav_history" + android:icon="@drawable/ic_history_black_24dp" + android:title="@string/menu_history" /> + <item + android:id="@+id/nav_settings" + android:icon="@drawable/ic_settings" + android:title="@string/menu_settings" /> + <item + android:id="@+id/nav_pending_operations" + android:icon="@drawable/ic_sync" + android:title="@string/pending_operations_title" /> + </group> + +</menu> diff --git a/wallet/src/main/res/menu/balance.xml b/wallet/src/main/res/menu/balance.xml new file mode 100644 index 0000000..7ac3a9f --- /dev/null +++ b/wallet/src/main/res/menu/balance.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/reload_balance" + android:title="@string/menu_balance_reload" + app:showAsAction="never" /> + <item + android:id="@+id/developer_mode" + android:checkable="true" + android:title="@string/menu_developer_mode" + app:showAsAction="never" /> +</menu> diff --git a/wallet/src/main/res/menu/history.xml b/wallet/src/main/res/menu/history.xml new file mode 100644 index 0000000..755323b --- /dev/null +++ b/wallet/src/main/res/menu/history.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/show_all_history" + android:checkable="true" + android:checked="false" + android:title="@string/history_show_all" + app:showAsAction="never" /> + <item + android:id="@+id/reload_history" + android:orderInCategory="100" + android:title="@string/history_reload" + app:showAsAction="never" /> +</menu> diff --git a/wallet/src/main/res/menu/pending_operations.xml b/wallet/src/main/res/menu/pending_operations.xml new file mode 100644 index 0000000..980ea66 --- /dev/null +++ b/wallet/src/main/res/menu/pending_operations.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/retry_pending" + android:orderInCategory="100" + android:title="@string/menu_retry_pending_operations" + app:showAsAction="never" /> +</menu> diff --git a/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..7acad4e --- /dev/null +++ b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7acad4e --- /dev/null +++ b/wallet/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/wallet/src/main/res/mipmap-hdpi/ic_launcher.png b/wallet/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2bfb696 --- /dev/null +++ b/wallet/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/wallet/src/main/res/mipmap-hdpi/ic_launcher_round.png b/wallet/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..c7ae940 --- /dev/null +++ b/wallet/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/wallet/src/main/res/mipmap-mdpi/ic_launcher.png b/wallet/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..c104056 --- /dev/null +++ b/wallet/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.png b/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..1130914 --- /dev/null +++ b/wallet/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/wallet/src/main/res/mipmap-xhdpi/ic_launcher.png b/wallet/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..7144a11 --- /dev/null +++ b/wallet/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..d63ccd3 --- /dev/null +++ b/wallet/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..bc3155e --- /dev/null +++ b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..028fe60 --- /dev/null +++ b/wallet/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..ccc81eb --- /dev/null +++ b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..da3ce45 --- /dev/null +++ b/wallet/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/wallet/src/main/res/navigation/nav_graph.xml b/wallet/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..549ca01 --- /dev/null +++ b/wallet/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<navigation xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/nav_graph" + app:startDestination="@id/showBalance" + tools:ignore="UnusedNavigation"> + + <fragment + android:id="@+id/showBalance" + android:name="net.taler.wallet.BalanceFragment" + android:label="@string/balances_title" + tools:layout="@layout/fragment_show_balance"> + <action + android:id="@+id/action_showBalance_to_promptPayment" + app:destination="@id/promptPayment" /> + <action + android:id="@+id/action_showBalance_to_promptWithdraw" + app:destination="@id/promptWithdraw" /> + </fragment> + <fragment + android:id="@+id/promptPayment" + android:name="net.taler.wallet.payment.PromptPaymentFragment" + android:label="Review Payment" + tools:layout="@layout/fragment_prompt_payment"> + <action + android:id="@+id/action_promptPayment_to_paymentSuccessful" + app:destination="@id/paymentSuccessful" + app:popUpTo="@id/showBalance" /> + <action + android:id="@+id/action_promptPayment_to_alreadyPaid" + app:destination="@id/alreadyPaid" + app:popUpTo="@id/showBalance" /> + </fragment> + <fragment + android:id="@+id/paymentSuccessful" + android:name="net.taler.wallet.payment.PaymentSuccessfulFragment" + android:label="Payment Successful" + tools:layout="@layout/fragment_payment_successful" /> + <fragment + android:id="@+id/settings" + android:name="net.taler.wallet.Settings" + android:label="Settings" + tools:layout="@layout/fragment_settings" /> + <fragment + android:id="@+id/walletHistory" + android:name="net.taler.wallet.history.WalletHistoryFragment" + android:label="@string/history_title" + tools:layout="@layout/fragment_show_history" /> + <fragment + android:id="@+id/alreadyPaid" + android:name="net.taler.wallet.payment.AlreadyPaidFragment" + android:label="Already Paid" + tools:layout="@layout/fragment_already_paid" /> + + <fragment + android:id="@+id/promptWithdraw" + android:name="net.taler.wallet.withdraw.PromptWithdrawFragment" + android:label="@string/nav_prompt_withdraw" + tools:layout="@layout/fragment_prompt_withdraw"> + <action + android:id="@+id/action_promptWithdraw_to_withdrawSuccessful" + app:destination="@id/withdrawSuccessful" + app:popUpTo="@id/showBalance" /> + <action + android:id="@+id/action_promptWithdraw_to_reviewExchangeTOS" + app:destination="@id/reviewExchangeTOS" + app:popUpTo="@id/showBalance" /> + <action + android:id="@+id/action_promptWithdraw_to_errorFragment" + app:destination="@id/errorFragment" + app:popUpTo="@id/showBalance" /> + </fragment> + + <fragment + android:id="@+id/withdrawSuccessful" + android:name="net.taler.wallet.withdraw.WithdrawSuccessfulFragment" + android:label="Withdrawal Confirmed" + tools:layout="@layout/fragment_withdraw_successful" /> + <fragment + android:id="@+id/reviewExchangeTOS" + android:name="net.taler.wallet.withdraw.ReviewExchangeTosFragment" + android:label="@string/nav_exchange_tos" + tools:layout="@layout/fragment_review_exchange_tos"> + <action + android:id="@+id/action_reviewExchangeTOS_to_promptWithdraw" + app:destination="@id/promptWithdraw" + app:popUpTo="@id/showBalance" /> + </fragment> + + <fragment + android:id="@+id/nav_pending_operations" + android:name="net.taler.wallet.pending.PendingOperationsFragment" + android:label="Pending Operations" + tools:layout="@layout/fragment_pending_operations" /> + <fragment + android:id="@+id/errorFragment" + android:name="net.taler.wallet.withdraw.ErrorFragment" + android:label="@string/nav_error" + tools:layout="@layout/fragment_error" /> + + <action + android:id="@+id/action_global_promptPayment" + app:destination="@id/promptPayment" /> + + <action + android:id="@+id/action_global_pending_operations" + app:destination="@id/nav_pending_operations" /> + +</navigation>
\ No newline at end of file diff --git a/wallet/src/main/res/values/colors.xml b/wallet/src/main/res/values/colors.xml new file mode 100644 index 0000000..2d1f0d7 --- /dev/null +++ b/wallet/src/main/res/values/colors.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<resources> + <color name="colorPrimary">#283593</color> + <color name="colorPrimaryDark">#1A237E</color> + <color name="colorAccent">#AE1010</color> + + <color name="red">#C62828</color> + <color name="green">#558B2F</color> +</resources> diff --git a/wallet/src/main/res/values/dimens.xml b/wallet/src/main/res/values/dimens.xml new file mode 100644 index 0000000..2bbc14d --- /dev/null +++ b/wallet/src/main/res/values/dimens.xml @@ -0,0 +1,24 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + <dimen name="nav_header_vertical_spacing">8dp</dimen> + <dimen name="nav_header_height">176dp</dimen> + <dimen name="fab_margin">16dp</dimen> +</resources>
\ No newline at end of file diff --git a/wallet/src/main/res/values/ic_launcher_background.xml b/wallet/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..758b965 --- /dev/null +++ b/wallet/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<resources> + <color name="ic_launcher_background">#000000</color> +</resources>
\ No newline at end of file diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml new file mode 100644 index 0000000..8981e04 --- /dev/null +++ b/wallet/src/main/res/values/strings.xml @@ -0,0 +1,105 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<resources> + <string name="app_name">Taler Wallet</string> + + <string name="nav_header_title">GNU Taler</string> + <string name="nav_header_subtitle">Wallet</string> + <string name="nav_header_desc">Navigation header</string> + + <string name="nav_prompt_withdraw">Withdraw Digital Cash</string> + <string name="nav_exchange_tos">Exchange\'s Terms of Service</string> + <string name="nav_error">Error</string> + + <string name="button_back">Go Back</string> + <string name="button_cancel">Cancel</string> + <string name="button_continue">Continue</string> + <string name="button_scan_qr_code">Scan Taler QR Code</string> + + <string name="menu_history">History</string> + <string name="menu_settings">Settings</string> + <string name="menu_balance_reload">Reload balances</string> + <string name="menu_developer_mode">Developer Mode</string> + <string name="menu_retry_pending_operations">Retry Pending Operations</string> + + <string name="servicedesc">my service</string> + <string name="aiddescription">my aid</string> + + <string name="balances_title">Balances</string> + <string name="balances_inbound_amount">+%1s %2s</string> + <string name="balances_inbound_label">inbound</string> + <string name="balances_empty_state">There is no digital cash in your wallet.\n\nYou can get test money from the demo bank:\n\nhttps://bank.demo.taler.net</string> + + <string name="history_title">History</string> + <string name="history_fee_label">Fee:</string> + <string name="history_show_all">Show All</string> + <string name="history_reload">Reload History</string> + <string name="history_empty">The wallet history is empty</string> + + <!-- HistoryEvents --> + <string name="history_event_exchange_added">Exchange Added</string> + <string name="history_event_exchange_updated">Exchange Updated</string> + <string name="history_event_reserve_balance_updated">Reserve Balance Updated</string> + <string name="history_event_payment_sent">Payment</string> + <string name="history_event_payment_aborted">Payment Aborted</string> + <string name="history_event_withdrawn">Withdraw</string> + <string name="history_event_order_accepted">Purchase Confirmed</string> + <string name="history_event_order_refused">Purchase Cancelled</string> + <string name="history_event_tip_accepted">Tip Accepted</string> + <string name="history_event_tip_declined">Tip Declined</string> + <string name="history_event_order_redirected">Purchase Redirected</string> + <string name="history_event_refund">Refund</string> + <string name="history_event_refreshed">Obtained change</string> + <string name="history_event_unknown">Unknown Event</string> + + <string name="payment_fee">+%s payment fee</string> + <string name="payment_button_confirm">Confirm Payment</string> + <string name="payment_button_abort">Abort</string> + <string name="payment_label_amount_total">Total Amount:</string> + <string name="payment_label_order_summary">Order</string> + <string name="payment_error">Error: %s</string> + <string name="payment_balance_insufficient">Balance insufficient!</string> + <string name="payment_show_details">Show Details</string> + <string name="payment_hide_details">Hide Details</string> + <string name="payment_successful">Payment was successful</string> + <string name="payment_back_button">OK</string> + <string name="payment_already_paid">You\'ve already paid for this order.</string> + + <string name="withdraw_accepted">Withdrawal accepted</string> + <string name="withdraw_success_info">The wire transfer now needs to be confirmed with the bank. Once the wire transfer is complete, the digital cash will automatically show in this wallet.</string> + <string name="withdraw_do_you_want">Do you want to withdraw</string> + <string name="withdraw_fees">(minus exchange fees not shown in this prototype)</string> + <string name="withdraw_exchange">Using the exchange provider</string> + <string name="withdraw_button_testkudos">Withdraw TESTKUDOS</string> + <string name="withdraw_button_confirm">Confirm Withdraw</string> + <string name="withdraw_error_title">Withdrawal Error</string> + <string name="withdraw_error_message">Withdrawing is currently not possible. Please try again later!</string> + + <string name="pending_operations_title">Pending Operations</string> + <string name="pending_operations_refuse">Refuse Proposal</string> + <string name="pending_operations_no_action">(no action)</string> + + <string name="settings_version">Version Information</string> + <string name="exchange_tos_accept">Accept Terms of Service</string> + <string name="exchange_tos_button_continue">Continue</string> + <string name="settings_backups">Backups</string> + <string name="settings_export_to_file">Export wallet to file</string> + <string name="settings_import_from_file">Import from file</string> + <string name="settings_developer">Developer Settings (use with caution!)</string> + <string name="settings_reset">Reset Wallet (dangerous!)</string> + +</resources> diff --git a/wallet/src/main/res/values/styles.xml b/wallet/src/main/res/values/styles.xml new file mode 100644 index 0000000..83f3e3a --- /dev/null +++ b/wallet/src/main/res/values/styles.xml @@ -0,0 +1,46 @@ +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<resources> + + <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + <item name="colorOnPrimary">@android:color/white</item> + </style> + + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + <item name="android:statusBarColor">@android:color/transparent</item> + </style> + + <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.ActionBar" /> + + <style name="AppTheme.Toolbar" parent="Widget.MaterialComponents.Toolbar.Primary" /> + + <style name="HistoryTitle"> + <item name="android:textSize">17sp</item> + <item name="android:textColor">?android:textColorPrimary</item> + </style> + + <style name="BottomCard"> + <item name="cardCornerRadius">0dp</item> + <item name="cardElevation">8dp</item> + </style> + +</resources> diff --git a/wallet/src/main/res/xml/apduservice.xml b/wallet/src/main/res/xml/apduservice.xml new file mode 100644 index 0000000..fde348c --- /dev/null +++ b/wallet/src/main/res/xml/apduservice.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" + android:description="@string/servicedesc" + android:requireDeviceUnlock="true"> + <aid-group android:description="@string/aiddescription" + android:category="other"> + <aid-filter android:name="F00054414C4552"/> + </aid-group> +</host-apdu-service>
\ No newline at end of file diff --git a/wallet/src/main/res/xml/backup_descriptor.xml b/wallet/src/main/res/xml/backup_descriptor.xml new file mode 100644 index 0000000..731d404 --- /dev/null +++ b/wallet/src/main/res/xml/backup_descriptor.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ This file is part of GNU Taler + ~ (C) 2020 Taler Systems S.A. + ~ + ~ GNU Taler is free software; you can redistribute it and/or modify it under the + ~ terms of the GNU General Public License as published by the Free Software + ~ Foundation; either version 3, or (at your option) any later version. + ~ + ~ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + ~ A PARTICULAR PURPOSE. See the GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along with + ~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + --> + +<full-backup-content> + <!-- Exclude specific shared preferences that contain GCM registration Id --> +</full-backup-content> diff --git a/wallet/src/test/java/net/taler/wallet/ExampleUnitTest.kt b/wallet/src/test/java/net/taler/wallet/ExampleUnitTest.kt new file mode 100644 index 0000000..de74f68 --- /dev/null +++ b/wallet/src/test/java/net/taler/wallet/ExampleUnitTest.kt @@ -0,0 +1,33 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt new file mode 100644 index 0000000..7c8cb4c --- /dev/null +++ b/wallet/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt @@ -0,0 +1,35 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.crypto + +import org.junit.Test + +class Base32CrockfordTest { + @Test + fun testBasic() { + val inputStr = "Hello, World" + val data = inputStr.toByteArray(Charsets.UTF_8) + val enc = Base32Crockford.encode(data) + println(enc) + val dec = Base32Crockford.decode(enc) + val recoveredInputStr = dec.toString(Charsets.UTF_8) + println(recoveredInputStr) + + val foo = Base32Crockford.decode("51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H2365338E9G6RT4AH1N6H13EGHR70RK6H1S6X2M4CSP8CSK8E1G88VKJH25610KGCHR8RWM4DJ47123CH9K89334D1S8N24ACJ48CR3EH256MR3AH1R711KCE9N6S134GSN6RW46D1H6CV3CDHJ6D0KEDHR6D24CD248MWKADHJ6WT34D25712KCD2474V46EA18H2M4GHM6WTK2E216S14CD238GSK0G9G692KCDHM6RW34CT16MV3CG9P60S34C1G70SMCHHQ8CVKJG9K6CVK6GHK70R46HJ26CR4AE9M8523ADHS8RR3EE1R74S32CHP6N1K0GT38D1M6C1R84TM2E9N8MSK2C1J71248E9H6H1MCD9J70VK4GSG6124CCHR6RS4ADSH8N0M4H1G84R4CD1G8D24AG9N6RR48DT1712K6GJ26X232DT36N0K4C9M8H236HJ48N2K4G9H8GVM8E1P8GSM6E9K891K4CSN65348C26611M8DHJ8S1M6H9G8H338CHS6GV3CD9K64S3GCHR8H2M6GJ58MT3EHA26S232GSJ6GTMAGA570W44DA2852KEDSR8MTKEGA460T3CCT18MR48CHK6WWKEGJ460WK4EA568VM6GSJ70T32CA461234DJ66RS34DHM6D242CT46MV3JDA584S4ADSM6S1MAE1P6GTKEGA68N1M8E216WRMAGHM6RR4ADSJ8MR3EDJ2690KAD9H6H346D9R88RKECSN8RRKJC1N74W34DSQ60W48DSJ8S1K0DSH8D1M4E1J6H1M2D1S8S33CG9R6RSMCH9K4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00") + println(foo.toString(Charsets.UTF_8)) + } +} diff --git a/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt b/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt new file mode 100644 index 0000000..ba18dfb --- /dev/null +++ b/wallet/src/test/java/net/taler/wallet/history/HistoryEventTest.kt @@ -0,0 +1,459 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.readValue +import net.taler.wallet.history.RefreshReason.PAY +import net.taler.wallet.history.ReserveType.MANUAL +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import kotlin.random.Random + +class HistoryEventTest { + + private val mapper = ObjectMapper().registerModule(KotlinModule()) + + private val timestamp = Random.nextLong() + private val exchangeBaseUrl = "https://exchange.test.taler.net/" + private val orderShortInfo = OrderShortInfo( + proposalId = "EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG", + orderId = "2019.364-01RAQ68DQ7AWR", + merchantBaseUrl = "https://backend.demo.taler.net/public/instances/FSF/", + amount = "KUDOS:0.5", + summary = "Essay: Foreword" + ) + + @Test + fun `test ExchangeAddedEvent`() { + val builtIn = Random.nextBoolean() + val json = """{ + "type": "exchange-added", + "builtIn": $builtIn, + "eventId": "exchange-added;https%3A%2F%2Fexchange.test.taler.net%2F", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: ExchangeAddedEvent = mapper.readValue(json) + + assertEquals(builtIn, event.builtIn) + assertEquals(exchangeBaseUrl, event.exchangeBaseUrl) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test ExchangeUpdatedEvent`() { + val json = """{ + "type": "exchange-updated", + "eventId": "exchange-updated;https%3A%2F%2Fexchange.test.taler.net%2F", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: ExchangeUpdatedEvent = mapper.readValue(json) + + assertEquals(exchangeBaseUrl, event.exchangeBaseUrl) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test ReserveShortInfo`() { + val json = """{ + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "reserveCreationDetail": { + "type": "manual" + }, + "reservePub": "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G" + }""".trimIndent() + val info: ReserveShortInfo = mapper.readValue(json) + + assertEquals(exchangeBaseUrl, info.exchangeBaseUrl) + assertEquals(MANUAL, info.reserveCreationDetail.type) + assertEquals("BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G", info.reservePub) + } + + @Test + fun `test ReserveBalanceUpdatedEvent`() { + val json = """{ + "type": "reserve-balance-updated", + "eventId": "reserve-balance-updated;K0H10Q6HB9WH0CKHQQMNH5C6GA7A9AR1E2XSS9G1KG3ZXMBVT26G", + "amountExpected": "TESTKUDOS:23", + "amountReserveBalance": "TESTKUDOS:10", + "timestamp": { + "t_ms": $timestamp + }, + "newHistoryTransactions": [ + { + "amount": "TESTKUDOS:10", + "sender_account_url": "payto:\/\/x-taler-bank\/bank.test.taler.net\/894", + "timestamp": { + "t_ms": $timestamp + }, + "wire_reference": "00000000004TR", + "type": "DEPOSIT" + } + ], + "reserveShortInfo": { + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "reserveCreationDetail": { + "type": "manual" + }, + "reservePub": "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G" + } + }""".trimIndent() + val event: ReserveBalanceUpdatedEvent = mapper.readValue(json) + + assertEquals(timestamp, event.timestamp.ms) + assertEquals("TESTKUDOS:23", event.amountExpected) + assertEquals("TESTKUDOS:10", event.amountReserveBalance) + assertEquals(1, event.newHistoryTransactions.size) + assertTrue(event.newHistoryTransactions[0] is ReserveDepositTransaction) + assertEquals(exchangeBaseUrl, event.reserveShortInfo.exchangeBaseUrl) + } + + @Test + fun `test HistoryWithdrawnEvent`() { + val json = """{ + "type": "withdrawn", + "withdrawSessionId": "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0", + "eventId": "withdrawn;974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0", + "amountWithdrawnEffective": "TESTKUDOS:9.8", + "amountWithdrawnRaw": "TESTKUDOS:10", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + }, + "withdrawalSource": { + "type": "reserve", + "reservePub": "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G" + } + }""".trimIndent() + val event: HistoryWithdrawnEvent = mapper.readValue(json) + + assertEquals( + "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0", + event.withdrawSessionId + ) + assertEquals("TESTKUDOS:9.8", event.amountWithdrawnEffective) + assertEquals("TESTKUDOS:10", event.amountWithdrawnRaw) + assertTrue(event.withdrawalSource is WithdrawalSourceReserve) + assertEquals( + "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G", + (event.withdrawalSource as WithdrawalSourceReserve).reservePub + ) + assertEquals(exchangeBaseUrl, event.exchangeBaseUrl) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test OrderShortInfo`() { + val json = """{ + "amount": "KUDOS:0.5", + "orderId": "2019.364-01RAQ68DQ7AWR", + "merchantBaseUrl": "https:\/\/backend.demo.taler.net\/public\/instances\/FSF\/", + "proposalId": "EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG", + "summary": "Essay: Foreword" + }""".trimIndent() + val info: OrderShortInfo = mapper.readValue(json) + + assertEquals("KUDOS:0.5", info.amount) + assertEquals("2019.364-01RAQ68DQ7AWR", info.orderId) + assertEquals("Essay: Foreword", info.summary) + } + + @Test + fun `test HistoryOrderAcceptedEvent`() { + val json = """{ + "type": "order-accepted", + "eventId": "order-accepted;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG", + "orderShortInfo": { + "amount": "${orderShortInfo.amount}", + "orderId": "${orderShortInfo.orderId}", + "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}", + "proposalId": "${orderShortInfo.proposalId}", + "summary": "${orderShortInfo.summary}" + }, + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: HistoryOrderAcceptedEvent = mapper.readValue(json) + + assertEquals(orderShortInfo, event.orderShortInfo) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryOrderRefusedEvent`() { + val json = """{ + "type": "order-refused", + "eventId": "order-refused;9RJGAYXKWX0Y3V37H66606SXSA7V2CV255EBFS4G1JSH6W1EG7F0", + "orderShortInfo": { + "amount": "${orderShortInfo.amount}", + "orderId": "${orderShortInfo.orderId}", + "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}", + "proposalId": "${orderShortInfo.proposalId}", + "summary": "${orderShortInfo.summary}" + }, + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: HistoryOrderRefusedEvent = mapper.readValue(json) + + assertEquals(orderShortInfo, event.orderShortInfo) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryPaymentSentEvent`() { + val json = """{ + "type": "payment-sent", + "eventId": "payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG", + "orderShortInfo": { + "amount": "${orderShortInfo.amount}", + "orderId": "${orderShortInfo.orderId}", + "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}", + "proposalId": "${orderShortInfo.proposalId}", + "summary": "${orderShortInfo.summary}" + }, + "replay": false, + "sessionId": "e4f436c4-3c5c-4aee-81d2-26e425c09520", + "timestamp": { + "t_ms": $timestamp + }, + "numCoins": 6, + "amountPaidWithFees": "KUDOS:0.6" + }""".trimIndent() + val event: HistoryPaymentSentEvent = mapper.readValue(json) + + assertEquals(orderShortInfo, event.orderShortInfo) + assertEquals(false, event.replay) + assertEquals(6, event.numCoins) + assertEquals("KUDOS:0.6", event.amountPaidWithFees) + assertEquals("e4f436c4-3c5c-4aee-81d2-26e425c09520", event.sessionId) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryPaymentSentEvent without sessionId`() { + val json = """{ + "type": "payment-sent", + "eventId": "payment-sent;EP5MH4R5C9RMNA06YS1QGEJ3EY682PY8R1SGRFRP74EV735N3ATG", + "orderShortInfo": { + "amount": "${orderShortInfo.amount}", + "orderId": "${orderShortInfo.orderId}", + "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}", + "proposalId": "${orderShortInfo.proposalId}", + "summary": "${orderShortInfo.summary}" + }, + "replay": true, + "timestamp": { + "t_ms": $timestamp + }, + "numCoins": 6, + "amountPaidWithFees": "KUDOS:0.6" + }""".trimIndent() + val event: HistoryPaymentSentEvent = mapper.readValue(json) + + assertEquals(orderShortInfo, event.orderShortInfo) + assertEquals(true, event.replay) + assertEquals(6, event.numCoins) + assertEquals("KUDOS:0.6", event.amountPaidWithFees) + assertEquals(null, event.sessionId) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryPaymentAbortedEvent`() { + val json = """{ + "type": "payment-aborted", + "eventId": "payment-sent;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "orderShortInfo": { + "amount": "${orderShortInfo.amount}", + "orderId": "${orderShortInfo.orderId}", + "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}", + "proposalId": "${orderShortInfo.proposalId}", + "summary": "${orderShortInfo.summary}" + }, + "timestamp": { + "t_ms": $timestamp + }, + "amountLost": "KUDOS:0.1" + }""".trimIndent() + val event: HistoryPaymentAbortedEvent = mapper.readValue(json) + + assertEquals(orderShortInfo, event.orderShortInfo) + assertEquals("KUDOS:0.1", event.amountLost) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryTipAcceptedEvent`() { + val json = """{ + "type": "tip-accepted", + "timestamp": { + "t_ms": $timestamp + }, + "eventId": "tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "tipId": "tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "tipRaw": "KUDOS:4" + }""".trimIndent() + val event: HistoryTipAcceptedEvent = mapper.readValue(json) + + assertEquals("tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", event.tipId) + assertEquals("KUDOS:4", event.tipRaw) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryTipDeclinedEvent`() { + val json = """{ + "type": "tip-declined", + "timestamp": { + "t_ms": $timestamp + }, + "eventId": "tip-accepted;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "tipId": "tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "tipAmount": "KUDOS:4" + }""".trimIndent() + val event: HistoryTipDeclinedEvent = mapper.readValue(json) + + assertEquals("tip-accepted;998724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", event.tipId) + assertEquals("KUDOS:4", event.tipAmount) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryRefundedEvent`() { + val json = """{ + "type": "refund", + "eventId": "refund;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "refundGroupId": "refund;998724", + "orderShortInfo": { + "amount": "${orderShortInfo.amount}", + "orderId": "${orderShortInfo.orderId}", + "merchantBaseUrl": "${orderShortInfo.merchantBaseUrl}", + "proposalId": "${orderShortInfo.proposalId}", + "summary": "${orderShortInfo.summary}" + }, + "timestamp": { + "t_ms": $timestamp + }, + "amountRefundedRaw": "KUDOS:1.0", + "amountRefundedInvalid": "KUDOS:0.5", + "amountRefundedEffective": "KUDOS:0.4" + }""".trimIndent() + val event: HistoryRefundedEvent = mapper.readValue(json) + + assertEquals("refund;998724", event.refundGroupId) + assertEquals("KUDOS:1.0", event.amountRefundedRaw) + assertEquals("KUDOS:0.5", event.amountRefundedInvalid) + assertEquals("KUDOS:0.4", event.amountRefundedEffective) + assertEquals(orderShortInfo, event.orderShortInfo) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryRefreshedEvent`() { + val json = """{ + "type": "refreshed", + "refreshGroupId": "8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640", + "eventId": "refreshed;8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640", + "timestamp": { + "t_ms": $timestamp + }, + "refreshReason": "pay", + "amountRefreshedEffective": "KUDOS:0", + "amountRefreshedRaw": "KUDOS:1", + "numInputCoins": 6, + "numOutputCoins": 0, + "numRefreshedInputCoins": 1 + }""".trimIndent() + val event: HistoryRefreshedEvent = mapper.readValue(json) + + assertEquals("KUDOS:0", event.amountRefreshedEffective) + assertEquals("KUDOS:1", event.amountRefreshedRaw) + assertEquals(6, event.numInputCoins) + assertEquals(0, event.numOutputCoins) + assertEquals(1, event.numRefreshedInputCoins) + assertEquals("8AVHKJFAN4QV4C11P56NEY83AJMGFF2KF412AN3Y0QBP09RSN640", event.refreshGroupId) + assertEquals(PAY, event.refreshReason) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryOrderRedirectedEvent`() { + val json = """{ + "type": "order-redirected", + "eventId": "order-redirected;621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G", + "alreadyPaidOrderShortInfo": { + "amount": "KUDOS:0.5", + "orderId": "2019.354-01P25CD66P8NG", + "merchantBaseUrl": "https://backend.demo.taler.net/public/instances/FSF/", + "proposalId": "898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", + "summary": "Essay: 1. The Free Software Definition" + }, + "newOrderShortInfo": { + "amount": "KUDOS:0.5", + "orderId": "2019.364-01M4QH6KPMJY4", + "merchantBaseUrl": "https://backend.demo.taler.net/public/instances/FSF/", + "proposalId": "621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G", + "summary": "Essay: 1. The Free Software Definition" + }, + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: HistoryOrderRedirectedEvent = mapper.readValue(json) + + assertEquals("898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0", event.alreadyPaidOrderShortInfo.proposalId) + assertEquals("https://backend.demo.taler.net/public/instances/FSF/", event.alreadyPaidOrderShortInfo.merchantBaseUrl) + assertEquals("2019.354-01P25CD66P8NG", event.alreadyPaidOrderShortInfo.orderId) + assertEquals("KUDOS:0.5", event.alreadyPaidOrderShortInfo.amount) + assertEquals("Essay: 1. The Free Software Definition", event.alreadyPaidOrderShortInfo.summary) + + assertEquals("621J6D5SXG7M17TYA26945DYKNQZPW4600MZ1W8MADA1RRR49F8G", event.newOrderShortInfo.proposalId) + assertEquals("https://backend.demo.taler.net/public/instances/FSF/", event.newOrderShortInfo.merchantBaseUrl) + assertEquals("2019.364-01M4QH6KPMJY4", event.newOrderShortInfo.orderId) + assertEquals("KUDOS:0.5", event.newOrderShortInfo.amount) + assertEquals("Essay: 1. The Free Software Definition", event.newOrderShortInfo.summary) + + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test HistoryUnknownEvent`() { + val json = """{ + "type": "does not exist", + "timestamp": { + "t_ms": $timestamp + }, + "eventId": "does-not-exist;898724XGQ1GGMZB4WY3KND582NSP74FZ60BX0Y87FF81H0FJ8XD0" + }""".trimIndent() + val event: HistoryEvent = mapper.readValue(json) + + assertEquals(HistoryUnknownEvent::class.java, event.javaClass) + assertEquals(timestamp, event.timestamp.ms) + } + +} diff --git a/wallet/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt b/wallet/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt new file mode 100644 index 0000000..d3d66f5 --- /dev/null +++ b/wallet/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt @@ -0,0 +1,52 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +package net.taler.wallet.history + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.readValue +import org.junit.Assert.assertEquals +import org.junit.Test +import kotlin.random.Random + +class ReserveTransactionTest { + + private val mapper = ObjectMapper().registerModule(KotlinModule()) + + private val timestamp = Random.nextLong() + + @Test + fun `test ExchangeAddedEvent`() { + val senderAccountUrl = "payto://x-taler-bank/bank.test.taler.net/894" + val json = """{ + "amount": "TESTKUDOS:10", + "sender_account_url": "payto:\/\/x-taler-bank\/bank.test.taler.net\/894", + "timestamp": { + "t_ms": $timestamp + }, + "wire_reference": "00000000004TR", + "type": "DEPOSIT" + }""".trimIndent() + val transaction: ReserveDepositTransaction = mapper.readValue(json) + + assertEquals("TESTKUDOS:10", transaction.amount) + assertEquals(senderAccountUrl, transaction.senderAccountUrl) + assertEquals("00000000004TR", transaction.wireReference) + assertEquals(timestamp, transaction.timestamp.ms) + } + +} |