diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-08-20 22:55:02 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-08-20 22:55:02 +0200 |
commit | 884fa0b47579bef3cf29450acdce73c2ee5304fe (patch) | |
tree | 5da6fd067379ffa1947efd87d233957aa545bd7c | |
download | wallet-android-884fa0b47579bef3cf29450acdce73c2ee5304fe.tar.gz wallet-android-884fa0b47579bef3cf29450acdce73c2ee5304fe.tar.bz2 wallet-android-884fa0b47579bef3cf29450acdce73c2ee5304fe.zip |
import into repo
70 files changed, 2142 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97cbdf1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +/akono/akono.aar diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..1bec35e --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ +<component name="ProjectCodeStyleConfiguration"> + <code_scheme name="Project" version="173"> + <JetCodeStyleSettings> + <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> + </JetCodeStyleSettings> + <codeStyleSettings language="kotlin"> + <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> + </codeStyleSettings> + </code_scheme> +</component>
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ +<component name="ProjectCodeStyleConfiguration"> + <state> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </state> +</component>
\ No newline at end of file diff --git a/.idea/dictionaries/dold.xml b/.idea/dictionaries/dold.xml new file mode 100644 index 0000000..67d533a --- /dev/null +++ b/.idea/dictionaries/dold.xml @@ -0,0 +1,7 @@ +<component name="ProjectDictionaryState"> + <dictionary name="dold"> + <words> + <w>taler</w> + </words> + </dictionary> +</component>
\ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Encoding" addBOMForNewFiles="with NO BOM" /> +</project>
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..afc8041 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="GradleSettings"> + <option name="linkedExternalProjectsSettings"> + <GradleProjectSettings> + <option name="distributionType" value="DEFAULT_WRAPPED" /> + <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="modules"> + <set> + <option value="$PROJECT_DIR$" /> + <option value="$PROJECT_DIR$/akono" /> + <option value="$PROJECT_DIR$/app" /> + </set> + </option> + <option name="resolveModulePerSourceSet" value="false" /> + </GradleProjectSettings> + </option> + </component> +</project>
\ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b6ea2b1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/build/classes" /> + </component> + <component name="ProjectType"> + <option name="id" value="Android" /> + </component> +</project>
\ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="RunConfigurationProducerService"> + <option name="ignoredProducers"> + <set> + <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> + <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> + <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> + </set> + </option> + </component> +</project>
\ No newline at end of file diff --git a/akono/build.gradle b/akono/build.gradle new file mode 100644 index 0000000..17c3bfd --- /dev/null +++ b/akono/build.gradle @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file('akono.aar'))
\ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..741e19c --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/src/main/assets diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..0651d3d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,64 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.1" + defaultConfig { + applicationId "net.taler.wallet" + minSdkVersion 28 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + 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 fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation project(":akono") + implementation 'com.google.guava:guava:28.0-android' + + def nav_version = "2.1.0-rc01" + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + + // ViewModel and LiveData + def lifecycle_version = "2.0.0" + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + kapt "androidx.lifecycle:lifecycle-compiler:$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' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/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/app/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt b/app/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..e6b19ba --- /dev/null +++ b/app/src/androidTest/java/net/taler/wallet/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package net.taler.wallet + +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * 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/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b675499 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="net.taler.wallet"> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <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> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/app/src/main/java/net/taler/wallet/MainActivity.kt b/app/src/main/java/net/taler/wallet/MainActivity.kt new file mode 100644 index 0000000..e539cfd --- /dev/null +++ b/app/src/main/java/net/taler/wallet/MainActivity.kt @@ -0,0 +1,138 @@ +package net.taler.wallet + + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.core.view.GravityCompat +import androidx.drawerlayout.widget.DrawerLayout +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.setupWithNavController +import com.google.android.material.navigation.NavigationView +import com.google.android.material.snackbar.Snackbar +import com.google.zxing.integration.android.IntentIntegrator +import com.google.zxing.integration.android.IntentResult +import me.zhanghai.android.materialprogressbar.MaterialProgressBar + +class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { + + lateinit var model: WalletViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val toolbar: Toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) + val navView: NavigationView = findViewById(R.id.nav_view) + + navView.menu.getItem(0).isChecked = true + + + navView.setNavigationItemSelectedListener(this) + + val navController = findNavController(R.id.nav_host_fragment) + val appBarConfiguration = + AppBarConfiguration(setOf(R.id.showBalance, R.id.settings, R.id.walletHistory), drawerLayout) + + findViewById<Toolbar>(R.id.toolbar) + .setupWithNavController(navController, appBarConfiguration) + + model = ViewModelProviders.of(this)[WalletViewModel::class.java] + + val progressBar = findViewById<MaterialProgressBar>(R.id.progress_bar) + progressBar.visibility = View.INVISIBLE + + model.init() + model.getBalances() + } + + override fun onBackPressed() { + val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) + if (drawerLayout.isDrawerOpen(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START) + } else { + super.onBackPressed() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + return when (item.itemId) { + R.id.action_settings -> true + else -> super.onOptionsItemSelected(item) + } + } + + override fun onNavigationItemSelected(item: MenuItem): Boolean { + // Handle navigation view item clicks here. + when (item.itemId) { + R.id.nav_home -> { + findNavController(R.id.nav_host_fragment).navigate(R.id.showBalance) + } + R.id.nav_settings -> { + findNavController(R.id.nav_host_fragment).navigate(R.id.settings) + } + R.id.nav_history -> { + findNavController(R.id.nav_host_fragment).navigate(R.id.walletHistory) + } + } + val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) + drawerLayout.closeDrawer(GravityCompat.START) + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode != IntentIntegrator.REQUEST_CODE) { + return + } + + val scanResult: IntentResult? = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) + + if (scanResult == null || scanResult.contents == null) { + val bar: Snackbar = Snackbar.make( + findViewById(R.id.nav_host_fragment), + "QR Code scan canceled.", + Snackbar.LENGTH_SHORT + ) + bar.show() + return + } + + val url = scanResult.contents!! + if (!url.startsWith("talerpay:")) { + val bar: Snackbar = Snackbar.make( + findViewById(R.id.nav_host_fragment), + "Scanned QR code doesn't contain Taler payment.", + Snackbar.LENGTH_SHORT + ) + bar.show() + return + } + + Log.v(TAG, "navigating!") + + findNavController(R.id.nav_host_fragment).navigate(R.id.action_showBalance_to_promptPayment) + model.preparePay(url) + } + + +} diff --git a/app/src/main/java/net/taler/wallet/PaymentSuccessful.kt b/app/src/main/java/net/taler/wallet/PaymentSuccessful.kt new file mode 100644 index 0000000..039fa73 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/PaymentSuccessful.kt @@ -0,0 +1,37 @@ +package net.taler.wallet + + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.navigation.findNavController + + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * + */ +class PaymentSuccessful : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_payment_successful, container, false) + view.findViewById<Button>(R.id.button_success_back).setOnClickListener { + activity!!.findNavController(R.id.nav_host_fragment).navigateUp() + } + return view + } + + +} diff --git a/app/src/main/java/net/taler/wallet/PromptPayment.kt b/app/src/main/java/net/taler/wallet/PromptPayment.kt new file mode 100644 index 0000000..9486814 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/PromptPayment.kt @@ -0,0 +1,143 @@ +package net.taler.wallet + + +import android.annotation.SuppressLint +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.navigation.findNavController +import com.google.android.material.snackbar.Snackbar +import me.zhanghai.android.materialprogressbar.MaterialProgressBar + + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * + */ +class PromptPayment : Fragment() { + + lateinit var model: WalletViewModel + + var fragmentView: View? = null + + fun triggerLoading(loading: Boolean) { + val myActivity = activity!! + val progressBar = myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar) + if (loading) { + progressBar.visibility = View.VISIBLE + } else { + progressBar.visibility = View.INVISIBLE + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + model = activity?.run { + ViewModelProviders.of(this)[WalletViewModel::class.java] + } ?: throw Exception("Invalid Activity") + + triggerLoading(true) + } + + override fun onResume() { + super.onResume() + Log.v("taler-wallet", "called onResume on PromptPayment") + triggerLoading(model.payStatus.value == null || model.payStatus.value is PayStatus.Loading) + } + + fun fillOrderInfo(view: View, contractTerms: ContractTerms, totalFees: Amount?) { + val feesAmountView = view.findViewById<TextView>(R.id.order_fees_amount) + val amountView = view.findViewById<TextView>(R.id.order_amount) + val summaryView = view.findViewById<TextView>(R.id.order_summary) + summaryView.text = contractTerms.summary + val amount = contractTerms.amount + @SuppressLint("SetTextI18n") + amountView.text = "${amount.amount} ${amount.currency}" + val feesBox = view.findViewById<View>(R.id.order_fees_box) + if (totalFees != null) { + @SuppressLint("SetTextI18n") + feesAmountView.text = "${totalFees.amount} ${totalFees.currency}" + feesBox.visibility = View.VISIBLE + } else { + feesBox.visibility = View.INVISIBLE + } + + } + + fun showPayStatus(view: View, payStatus: PayStatus) { + val promptPaymentDetails = view.findViewById<View>(R.id.prompt_payment_details) + val balanceInsufficientWarning = view.findViewById<View>(R.id.balance_insufficient_warning) + val confirmPaymentButton = view.findViewById<Button>(R.id.button_confirm_payment) + when (payStatus) { + is PayStatus.Prepared -> { + fillOrderInfo(view, payStatus.contractTerms, payStatus.totalFees) + promptPaymentDetails.visibility = View.VISIBLE + balanceInsufficientWarning.visibility = View.GONE + confirmPaymentButton.isEnabled = true + + confirmPaymentButton.setOnClickListener { + model.confirmPay(payStatus.proposalId) + triggerLoading(true) + confirmPaymentButton.isEnabled = false + } + triggerLoading(false) + } + is PayStatus.InsufficientBalance -> { + fillOrderInfo(view, payStatus.contractTerms, null) + promptPaymentDetails.visibility = View.VISIBLE + balanceInsufficientWarning.visibility = View.VISIBLE + confirmPaymentButton.isEnabled = false + triggerLoading(false) + } + is PayStatus.Success -> { + triggerLoading(false) + activity!!.findNavController(R.id.nav_host_fragment).navigate(R.id.action_promptPayment_to_paymentSuccessful) + } + else -> { + val bar = Snackbar.make(view , "Unexpected result", Snackbar.LENGTH_SHORT) + bar.show() + } + } + } + + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + + val view = inflater.inflate(R.layout.fragment_prompt_payment, container, false) + fragmentView = view + + val promptPaymentDetails = view.findViewById<View>(R.id.prompt_payment_details) + // Set invisible until data is loaded. + promptPaymentDetails.visibility = View.INVISIBLE + + val abortPaymentButton = view.findViewById<Button>(R.id.button_abort_payment) + + abortPaymentButton.setOnClickListener { + activity!!.findNavController(R.id.nav_host_fragment).navigateUp() + } + + triggerLoading(true) + + model.payStatus.observe(this, Observer { + showPayStatus(view, it) + }) + return view + } +} diff --git a/app/src/main/java/net/taler/wallet/Settings.kt b/app/src/main/java/net/taler/wallet/Settings.kt new file mode 100644 index 0000000..bf67f56 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/Settings.kt @@ -0,0 +1,48 @@ +package net.taler.wallet + + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.lifecycle.ViewModelProviders + + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * + */ +class Settings : Fragment() { + + private lateinit var model: WalletViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + model = activity?.run { + ViewModelProviders.of(this)[WalletViewModel::class.java] + } ?: throw Exception("Invalid Activity") + + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_settings, container, false) + view.findViewById<Button>(R.id.button_reset_wallet_dangerously).setOnClickListener { + model.dangerouslyReset() + } + return view + } + + +} diff --git a/app/src/main/java/net/taler/wallet/ShowBalance.kt b/app/src/main/java/net/taler/wallet/ShowBalance.kt new file mode 100644 index 0000000..ba1bf14 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/ShowBalance.kt @@ -0,0 +1,164 @@ +package net.taler.wallet + + +import android.app.Activity +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +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.zxing.integration.android.IntentIntegrator +import me.zhanghai.android.materialprogressbar.MaterialProgressBar + +class MyAdapter(private var myDataset: WalletBalances) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val rowView = LayoutInflater.from(parent.context).inflate(R.layout.balance_row, parent, false) + return MyViewHolder(rowView) + } + + override fun getItemCount(): Int { + return myDataset.byCurrency.size + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val currencyView = holder.rowView.findViewById<TextView>(R.id.balance_currency) + currencyView.text = myDataset.byCurrency[position].currency + val amountView = holder.rowView.findViewById<TextView>(R.id.balance_amount) + amountView.text = myDataset.byCurrency[position].amount + } + + fun update(updatedBalances: WalletBalances) { + this.myDataset = updatedBalances + this.notifyDataSetChanged() + } + + class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) +} + + +/** + * A simple [Fragment] subclass. + * + */ +class ShowBalance : Fragment() { + + lateinit var balancesView: RecyclerView + lateinit var balancesPlaceholderView: TextView + lateinit var model: WalletViewModel + lateinit var balancesAdapter: MyAdapter + + fun triggerLoading() { + val loading: Boolean = + (model.isBalanceLoading.value == true) || (model.balances.value == null) || !model.balances.value!!.initialized + + val myActivity = activity!! + val progressBar = myActivity.findViewById<MaterialProgressBar>(R.id.progress_bar) + if (loading) { + progressBar.visibility = View.VISIBLE + } else { + progressBar.visibility = View.INVISIBLE + } + } + + override fun onResume() { + super.onResume() + triggerLoading() + Log.v("taler-wallet", "called onResume on ShowBalance") + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + model = activity?.run { + ViewModelProviders.of(this)[WalletViewModel::class.java] + } ?: throw Exception("Invalid Activity") + + + model.isBalanceLoading.observe(this, Observer { loading -> + Log.v("taler-wallet", "observing balance loading ${loading} in show balance") + triggerLoading() + }) + } + + + private fun updateBalances(balances: WalletBalances) { + if (!balances.initialized) { + balancesPlaceholderView.visibility = View.GONE + balancesView.visibility = View.GONE + } else if (balances.byCurrency.isEmpty()) { + balancesPlaceholderView.visibility = View.VISIBLE + balancesView.visibility = View.GONE + } else { + balancesPlaceholderView.visibility = View.GONE + balancesView.visibility = View.VISIBLE + } + Log.v(TAG, "updating balances ${balances}") + balancesAdapter.update(balances) + //this.balancesView.adapter = balancesAdapter + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_show_balance, container, false) + val payQrButton = view.findViewById<Button>(R.id.button_pay_qr) + payQrButton.setOnClickListener { + val integrator = IntentIntegrator(activity) + integrator.setPrompt("Place merchant's QR Code inside the viewfinder rectangle to initiate payment.") + integrator.initiateScan(listOf("QR_CODE")) + } + + val withdrawTestkudosButton = view.findViewById<Button>(R.id.button_withdraw_testkudos) + withdrawTestkudosButton.setOnClickListener { + model.withdrawTestkudos() + } + + val payNfcButton = view.findViewById<Button>(R.id.button_pay_nfc) + payNfcButton.setOnClickListener { + val bar: Snackbar = Snackbar.make(view, "Sorry, NFC is not implemented yet!", Snackbar.LENGTH_SHORT) + bar.show() + } + + + this.balancesView = view.findViewById(R.id.list_balances) + this.balancesPlaceholderView = view.findViewById(R.id.list_balances_placeholder) + + val myLayoutManager = LinearLayoutManager(context) + val myItemDecoration = DividerItemDecoration(context, myLayoutManager.orientation) + + val balances = model.balances.value!! + + balancesAdapter = MyAdapter(balances) + + view.findViewById<RecyclerView>(R.id.list_balances).apply { + layoutManager = myLayoutManager + adapter = balancesAdapter + addItemDecoration(myItemDecoration) + } + + updateBalances(balances) + + model.balances.observe(this, Observer { + triggerLoading() + updateBalances(it) + }) + + return view + } +} diff --git a/app/src/main/java/net/taler/wallet/WalletHistory.kt b/app/src/main/java/net/taler/wallet/WalletHistory.kt new file mode 100644 index 0000000..baf825e --- /dev/null +++ b/app/src/main/java/net/taler/wallet/WalletHistory.kt @@ -0,0 +1,31 @@ +package net.taler.wallet + + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * + */ +class WalletHistory : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_show_history, container, false) + } + + +} diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt b/app/src/main/java/net/taler/wallet/WalletViewModel.kt new file mode 100644 index 0000000..7c82f81 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt @@ -0,0 +1,331 @@ +package net.taler.wallet + +import akono.AkonoJni +import akono.ModuleResult +import android.app.Application +import android.content.res.AssetManager +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import org.json.JSONObject +import java.io.InputStream +import java.lang.Exception +import java.nio.file.Paths + +val TAG = "taler-wallet" + +class AssetModuleLoader(private val assetManager: AssetManager, private val rootPath: String = "node_modules") : + AkonoJni.LoadModuleHandler { + + private fun makeResult(localPath: String, stream: InputStream): ModuleResult { + val moduleString = stream.bufferedReader().use { + it.readText() + } + return ModuleResult("/vmodroot/$localPath", moduleString) + } + + private fun tryPath(rawAssetPath: String): ModuleResult? { + val assetPath = Paths.get(rawAssetPath).normalize().toString() + try { + val moduleStream = assetManager.open(assetPath) + return makeResult(assetPath, moduleStream) + } catch (e: Exception) { + } + try { + val jsPath = "$assetPath.js" + val moduleStream = assetManager.open(jsPath) + return makeResult(jsPath, moduleStream) + } catch (e: Exception) { + // ignore + } + val packageJsonPath = "$assetPath/package.json" + try { + val packageStream = assetManager.open(packageJsonPath) + val packageString = packageStream.bufferedReader().use { + it.readText() + } + val packageJson = JSONObject(packageString) + val mainFile = try { + packageJson.getString("main") + } catch (e: Exception) { + Log.w(TAG, "package.json does not have a 'main' filed") + throw e + } + Log.i(TAG, "main field is $mainFile") + try { + val modPath = Paths.get("$assetPath/$mainFile").normalize().toString() + return makeResult(modPath, assetManager.open(modPath)) + } catch (e: Exception) { + // ignore + } + try { + val modPath = Paths.get("$assetPath/$mainFile.js").normalize().toString() + return makeResult(modPath, assetManager.open(modPath)) + } catch (e: Exception) { + } + } catch (e: Exception) { + } + try { + val jsPath = "$assetPath/index.js" + Log.i(TAG, "trying to open $jsPath") + val moduleStream = assetManager.open(jsPath) + return makeResult(jsPath, moduleStream) + } catch (e: Exception) { + } + return null + } + + override fun loadModule(name: String, paths: Array<String>): ModuleResult? { + Log.i(TAG, "loading module $name from paths [${paths.fold("", { acc, s -> "$acc,$s" })}]") + for (path in paths) { + Log.i(TAG, "trying from path $path") + val prefix = "/vmodroot" + if (!path.startsWith(prefix)) { + continue + } + if (path == prefix) { + Log.i(TAG, "path is prefix") + val res = tryPath("$rootPath/$name") + if (res != null) + return res + } else { + Log.i(TAG, "path is not prefix") + val res = tryPath(path.drop(prefix.length + 1) + "/$name") + if (res != null) + return res + } + } + return null + } +} + + +class AssetDataHandler(private val assetManager: AssetManager) : AkonoJni.GetDataHandler { + override fun handleGetData(what: String): ByteArray? { + if (what == "taler-emscripten-lib.wasm") { + Log.i(TAG, "loading emscripten binary from taler-wallet") + val stream = assetManager.open("node_modules/taler-wallet/emscripten/taler-emscripten-lib.wasm") + val bytes: ByteArray = stream.readBytes() + Log.i(TAG, "size of emscripten binary: ${bytes.size}") + return bytes + } else { + Log.w(TAG, "data '$what' requested by akono not found") + return null + } + } +} + +data class Amount(val currency: String, val amount: String) { + companion object { + val FRACTIONAL_BASE = 1e8; + 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]) + } + } +} + + +data class WalletBalances(val initialized: Boolean, val byCurrency: List<Amount>) + +data class ContractTerms(val summary: String, val amount: Amount) + +open class PayStatus { + class Loading : PayStatus() + data class Prepared(val contractTerms: ContractTerms, val proposalId: Int, 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() + class Success : PayStatus() +} + + +class WalletViewModel(val app: Application) : AndroidViewModel(app) { + private lateinit var myAkono: AkonoJni + private var initialized = false + + private var withdrawInProgress: Int = 0 + + val balances: MutableLiveData<WalletBalances> = MutableLiveData() + + val isBalanceLoading: MutableLiveData<Boolean> = MutableLiveData() + + //val isProposalLoading: MutableLiveData<Boolean> = MutableLiveData() + + val payStatus: MutableLiveData<PayStatus> = MutableLiveData() + + init { + isBalanceLoading.value = false + balances.value = WalletBalances(false, listOf()) + } + + fun init() { + if (initialized) { + Log.e(TAG, "WalletViewModel already initialized") + return + } + val app = this.getApplication<Application>() + myAkono = AkonoJni() + myAkono.setLoadModuleHandler(AssetModuleLoader(app.assets)) + myAkono.setGetDataHandler(AssetDataHandler(app.assets)) + myAkono.setMessageHandler(object : AkonoJni.MessageHandler { + override fun handleMessage(messageStr: String) { + Log.v(TAG, "got back message: ${messageStr}") + val message = JSONObject(messageStr) + val type = message.getString("type") + when (type) { + "notification" -> { + getBalances() + } + "response" -> { + val operation = message.getString("operation") + Log.v(TAG, "got response for operation $operation") + when (operation) { + "withdrawTestkudos" -> { + withdrawInProgress-- + if (withdrawInProgress == 0) { + isBalanceLoading.postValue(false) + } + } + "getBalances" -> { + val balanceList = mutableListOf<Amount>(); + val result = message.getJSONObject("result") + 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) + balanceList.add(amount) + } + balances.postValue(WalletBalances(true, balanceList)) + } + "preparePay" -> { + Log.v(TAG, "got preparePay result") + val result = message.getJSONObject("result") + val status = result.getString("status") + var contractTerms: ContractTerms? = null + var proposalId: Int? = null + var totalFees: Amount? = null + if (result.has("proposalId")) { + proposalId = result.getInt("proposalId") + } + if (result.has("contractTerms")) { + val ctJson = result.getJSONObject("contractTerms") + val amount = Amount.fromString(ctJson.getString("amount")) + val summary = ctJson.getString("summary") + contractTerms = ContractTerms(summary, amount) + } + if (result.has("totalFees")) { + totalFees = Amount.fromJson(result.getJSONObject("totalFees")) + } + val res = when (status) { + "payment-possible" -> PayStatus.Prepared(contractTerms!!, proposalId!!, totalFees!!) + "paid" -> PayStatus.AlreadyPaid(contractTerms!!) + "insufficient-balance" -> PayStatus.InsufficientBalance(contractTerms!!) + "error" -> PayStatus.Error("got some error") + else -> PayStatus.Error("unkown status") + } + payStatus.postValue(res) + } + "confirmPay" -> { + payStatus.postValue(PayStatus.Success()) + } + } + + } + } + } + }) + + myAkono.evalNodeCode("console.log('hello world from taler wallet-android')") + myAkono.evalNodeCode("tw = require('taler-wallet');") + myAkono.evalNodeCode("tw.installAndroidWalletListener();") + + sendInitMessage() + + this.initialized = true + } + + private fun sendInitMessage() { + val msg = JSONObject() + msg.put("operation", "init") + val args = JSONObject() + msg.put("args", args) + args.put("persistentStoragePath", "${app.filesDir}/talerwalletdb.json") + + Log.v(TAG, "sending message ${msg}") + + myAkono.sendMessage(msg.toString()) + } + + fun getBalances() { + if (!initialized) { + Log.e(TAG, "WalletViewModel not initialized") + return + } + + val msg = JSONObject() + msg.put("operation", "getBalances") + + myAkono.sendMessage(msg.toString()) + } + + fun withdrawTestkudos() { + if (!initialized) { + Log.e(TAG, "WalletViewModel not initialized") + return + } + + withdrawInProgress++ + this.isBalanceLoading.value = true + + val msg = JSONObject() + msg.put("operation", "withdrawTestkudos") + + myAkono.sendMessage(msg.toString()) + } + + fun preparePay(url: String) { + val msg = JSONObject() + msg.put("operation", "preparePay") + + val args = JSONObject() + msg.put("args", args) + args.put("url", url) + + myAkono.sendMessage(msg.toString()) + } + + fun confirmPay(proposalId: Int) { + val msg = JSONObject() + msg.put("operation", "confirmPay") + + val args = JSONObject() + msg.put("args", args) + args.put("proposalId", proposalId) + + myAkono.sendMessage(msg.toString()) + } + + fun dangerouslyReset() { + val msg = JSONObject() + msg.put("operation", "reset") + + myAkono.sendMessage(msg.toString()) + + sendInitMessage() + + isBalanceLoading.value = false + balances.value = WalletBalances(false, listOf()) + + getBalances() + } +}
\ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..6348baa --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0"/> + <item + android:color="#00000000" + android:offset="1.0"/> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1"/> +</vector> diff --git a/app/src/main/res/drawable/ic_history_black_24dp.xml b/app/src/main/res/drawable/ic_history_black_24dp.xml new file mode 100644 index 0000000..a61de1b --- /dev/null +++ b/app/src/main/res/drawable/ic_history_black_24dp.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="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/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 0000000..70fb291 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_black_24dp.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="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..a0ad202 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:height="108dp" + android:width="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path android:fillColor="#008577" + android:pathData="M0,0h108v108h-108z"/> + <path android:fillColor="#00000000" android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> +</vector> diff --git a/app/src/main/res/drawable/ic_menu_camera.xml b/app/src/main/res/drawable/ic_menu_camera.xml new file mode 100644 index 0000000..41688d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_camera.xml @@ -0,0 +1,12 @@ +<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,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_menu_gallery.xml b/app/src/main/res/drawable/ic_menu_gallery.xml new file mode 100644 index 0000000..ff8ce52 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_gallery.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="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_menu_manage.xml b/app/src/main/res/drawable/ic_menu_manage.xml new file mode 100644 index 0000000..a0e423c --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_manage.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="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z"/> +</vector>
\ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_send.xml b/app/src/main/res/drawable/ic_menu_send.xml new file mode 100644 index 0000000..9745066 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_send.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="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_menu_share.xml b/app/src/main/res/drawable/ic_menu_share.xml new file mode 100644 index 0000000..b3e39e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_share.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="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_menu_slideshow.xml b/app/src/main/res/drawable/ic_menu_slideshow.xml new file mode 100644 index 0000000..ae51e49 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_slideshow.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="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z"/> +</vector> diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 0000000..a33798b --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="135" + android:centerColor="#009688" + android:endColor="#00695C" + android:startColor="#4DB6AC" + android:type="linear"/> +</shape>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..ac9a123 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<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="true" + app:headerLayout="@layout/nav_header_main" + app:menu="@menu/activity_main_drawer"/> + +</androidx.drawerlayout.widget.DrawerLayout> diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml new file mode 100644 index 0000000..0e95cb0 --- /dev/null +++ b/app/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<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_height="wrap_content" + android:layout_width="match_parent" + android:theme="@style/AppTheme.AppBarOverlay"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/AppTheme.PopupOverlay"/> + <me.zhanghai.android.materialprogressbar.MaterialProgressBar + android:id="@+id/progress_bar" + android:layout_width="match_parent" + android:layout_height="4dp" + android:indeterminate="true" + app:mpb_progressStyle="horizontal" + app:mpb_useIntrinsicPadding="false" + android:layout_alignParentBottom="true" + style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"/> + </RelativeLayout> + + </com.google.android.material.appbar.AppBarLayout> + + <include layout="@layout/content_main" android:id="@+id/include"/> + +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/balance_row.xml b/app/src/main/res/layout/balance_row.xml new file mode 100644 index 0000000..75f143c --- /dev/null +++ b/app/src/main/res/layout/balance_row.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/balance_amount" + android:textSize="40sp" tools:text="100.50"> + </TextView> + + <Space android:layout_width="10sp" + android:layout_height="match_parent"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/balance_currency" + android:textSize="20sp" tools:text="TESTKUDOS"> + </TextView> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..112382e --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:showIn="@layout/app_bar_main" + tools:context=".MainActivity"> + + <fragment + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_payment_successful.xml b/app/src/main/res/layout/fragment_payment_successful.xml new file mode 100644 index 0000000..64ddad9 --- /dev/null +++ b/app/src/main/res/layout/fragment_payment_successful.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<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" + android:layout_margin="15dp" + tools:context=".PaymentSuccessful"> + + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Space android:layout_width="match_parent" android:layout_height="0dp" + android:layout_weight="1"/> + + <TextView + android:layout_gravity="center" + android:layout_width="match_parent" + android:textAlignment="center" + android:layout_height="50dp" + android:text="Payment was Successful" + android:autoSizeTextType="uniform" + android:textColor="@android:color/holo_green_dark"/> + + + <Space android:layout_width="match_parent" android:layout_height="0dp" + android:layout_weight="1"/> + <Button + android:text="Go Back" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/button_success_back"/> + + </LinearLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_prompt_payment.xml b/app/src/main/res/layout/fragment_prompt_payment.xml new file mode 100644 index 0000000..8d01126 --- /dev/null +++ b/app/src/main/res/layout/fragment_prompt_payment.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="utf-8"?> +<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" + android:layout_margin="15dp" + tools:context=".PromptPayment"> + + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/prompt_payment_details"> + + <Space android:layout_width="match_parent" + android:layout_height="15dp" + android:layout_weight="1"/> + + <TextView + android:text="Order Summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:id="@+id/textView2"/> + + <TextView + android:text="One Cappuccino" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="25sp" + android:layout_gravity="center" + android:id="@+id/order_summary"/> + + <Space android:layout_width="match_parent" + android:layout_height="25dp"/> + + + <TextView + android:text="Amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:id="@+id/textView3"/> + <TextView + android:text="10 TESTKUDOS" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="25sp" + android:layout_gravity="center" + android:id="@+id/order_amount"/> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_gravity="center" + android:id="@+id/order_fees_box"> + + <TextView + android:text="(plus additional " + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + + <TextView + android:text="0.5 TESTKUDOS" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:id="@+id/order_fees_amount"/> + + <TextView + android:text=" payment fees)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + /> + + </LinearLayout> + + <Space android:layout_width="match_parent" + android:layout_height="15dp" + android:layout_weight="1"/> + + <TextView + android:text="Balance Insufficient!" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="25sp" + android:layout_gravity="center" + android:id="@+id/balance_insufficient_warning" android:textColor="@android:color/holo_red_dark"/> + + + <Space android:layout_width="match_parent" + android:layout_height="15dp" + android:layout_weight="1"/> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <Button android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="Abort Payment" + android:id="@+id/button_abort_payment"/> + + <Space android:layout_width="15dp" android:layout_height="match_parent" + android:layout_weight="1"/> + + <Button android:layout_width="wrap_content" android:layout_height="wrap_content" + android:id="@+id/button_confirm_payment" + android:text="Confirm Payment"/> + </LinearLayout> + + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..d675d98 --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<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" + android:layout_margin="10dp" + tools:context=".Settings"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:text="Reset Wallet (Dangerous!)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:id="@+id/button_reset_wallet_dangerously"/> + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_show_balance.xml b/app/src/main/res/layout/fragment_show_balance.xml new file mode 100644 index 0000000..e4d2e81 --- /dev/null +++ b/app/src/main/res/layout/fragment_show_balance.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.core.widget.NestedScrollView + 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="15dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <View + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_balances" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbars="vertical"/> + + <TextView + android:id="@+id/list_balances_placeholder" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="There is no digital cash in your wallet." + tools:visibility="gone"/> + + <Space android:layout_width="match_parent" android:layout_height="20dp"/> + + <Button + android:text="Withdraw TESTKUDOS" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button_withdraw_testkudos"/> + <Button + android:text="Pay via QR Code" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button_pay_qr"/> + <Button + android:text="Pay via NFC" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button_pay_nfc"/> + </LinearLayout> +</androidx.core.widget.NestedScrollView> diff --git a/app/src/main/res/layout/fragment_show_history.xml b/app/src/main/res/layout/fragment_show_history.xml new file mode 100644 index 0000000..d730425 --- /dev/null +++ b/app/src/main/res/layout/fragment_show_history.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<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" + tools:context=".WalletHistory"> + + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="The history of wallet operations will eventually be shown here. Currently this feature isn't implemented, sorry!"/> + +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml new file mode 100644 index 0000000..92ca611 --- /dev/null +++ b/app/src/main/res/layout/nav_header_main.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="@dimen/nav_header_height" + android:background="@drawable/side_nav_bar" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:theme="@style/ThemeOverlay.AppCompat.Dark" + android:orientation="vertical" + android:gravity="bottom"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="@dimen/nav_header_vertical_spacing" + app:srcCompat="@mipmap/ic_launcher_round" + android:contentDescription="@string/nav_header_desc" + android:id="@+id/imageView"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/nav_header_vertical_spacing" + android:text="@string/nav_header_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/nav_header_subtitle" + android:id="@+id/textView"/> + +</LinearLayout> diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml new file mode 100644 index 0000000..14dc779 --- /dev/null +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:showIn="navigation_view"> + + <group android:checkableBehavior="single"> + <item + android:id="@+id/nav_home" + android:icon="@drawable/ic_home_black_24dp" + android:title="Home"/> + <item + android:id="@+id/nav_history" + android:icon="@drawable/ic_history_black_24dp" + android:title="History"/> + <item + android:id="@+id/nav_settings" + android:icon="@drawable/ic_menu_manage" + android:title="Settings"/> + </group> + +</menu> diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 0000000..d579f6f --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item android:id="@+id/action_settings" + android:title="@string/action_settings" + android:orderInCategory="100" + app:showAsAction="never"/> +</menu> diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background"/> + <foreground android:drawable="@drawable/ic_launcher_foreground"/> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..898f3ed --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..dffca36 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..64ba76f --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..dae5e08 --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..e5ed465 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..14ed0af --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..b0907ca --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..d8ae031 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2c18de9 --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 0000000..beed3cd --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..174c907 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<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"> + + <fragment android:id="@+id/showBalance" android:name="net.taler.wallet.ShowBalance" + android:label="Balances" tools:layout="@layout/fragment_show_balance"> + <action android:id="@+id/action_showBalance_to_promptPayment" app:destination="@id/promptPayment"/> + </fragment> + <fragment android:id="@+id/promptPayment" android:name="net.taler.wallet.PromptPayment" + 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"/> + </fragment> + <fragment android:id="@+id/paymentSuccessful" android:name="net.taler.wallet.PaymentSuccessful" + 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.WalletHistory" + android:label="History" tools:layout="@layout/fragment_show_history"/> +</navigation>
\ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..e546804 --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,7 @@ +<resources> + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + <item name="android:statusBarColor">@android:color/transparent</item> + </style> +</resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..69b2233 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#008577</color> + <color name="colorPrimaryDark">#00574B</color> + <color name="colorAccent">#D81B60</color> +</resources> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..4ab4520 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,8 @@ +<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/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..f059311 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,19 @@ +<resources> + <string name="app_name">GNU Taler Wallet</string> + <string name="navigation_drawer_open">Open navigation drawer</string> + <string name="navigation_drawer_close">Close navigation drawer</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="action_settings">Settings</string> + + <string name="menu_home">Home</string> + <string name="menu_gallery">Gallery</string> + <string name="menu_slideshow">Slideshow</string> + <string name="menu_tools">Tools</string> + <string name="menu_share">Share</string> + <string name="menu_send">Send</string> + + <!-- TODO: Remove or change this placeholder text --> + <string name="hello_blank_fragment">Hello blank fragment</string> +</resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..16dbab3 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,17 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + </style> + <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/> + <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/> + +</resources> diff --git a/app/src/test/java/net/taler/wallet/ExampleUnitTest.kt b/app/src/test/java/net/taler/wallet/ExampleUnitTest.kt new file mode 100644 index 0000000..c01881a --- /dev/null +++ b/app/src/test/java/net/taler/wallet/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +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/build.gradle b/build.gradle new file mode 100644 index 0000000..438d280 --- /dev/null +++ b/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.41' + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..f6b961f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9664d9d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Aug 14 17:39:00 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c34479d --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app', ':akono' |