receive screen: qr code functional

next step is to add the numbering to the address parts and then add the real values for the QR code and the address. After that, this screen is complete.
This commit is contained in:
Kevin Gorham 2018-12-05 02:26:03 -05:00
parent 7b16819a26
commit 4ec6b60398
20 changed files with 344 additions and 91 deletions

View File

@ -52,10 +52,11 @@ dependencies {
kapt deps.dagger.android.processor
kapt deps.dagger.compiler
// FAB speed dial
// Other
implementation deps.speeddial
testImplementation deps.junit
androidTestImplementation deps.androidx.test.runner
androidTestImplementation deps.androidx.test.espresso
compile project(path: ':qrecycler')
}

View File

@ -3,6 +3,8 @@ package cash.z.android.wallet.di.component
import cash.z.android.wallet.ui.activity.MainActivityModule
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.di.module.ApplicationModule
import cash.z.android.wallet.ui.fragment.HomeFragmentModule
import cash.z.android.wallet.ui.fragment.ReceiveFragmentModule
import dagger.Component
import dagger.android.AndroidInjector
import dagger.android.support.AndroidSupportInjectionModule
@ -15,9 +17,11 @@ import javax.inject.Singleton
@Singleton
@Component(
modules = [
MainActivityModule::class,
AndroidSupportInjectionModule::class,
ApplicationModule::class
ApplicationModule::class,
MainActivityModule::class,
HomeFragmentModule::class,
ReceiveFragmentModule::class
]
)
interface ApplicationComponent : AndroidInjector<ZcashWalletApplication> {

View File

@ -1,15 +1,19 @@
package cash.z.android.wallet.di.module
import cash.z.android.qrecycler.QRecycler
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Module that contributes all the objects with application scope. Anything that should live globally belongs here.
*/
@Module
class ApplicationModule {
@Singleton
internal object ApplicationModule {
@JvmStatic
@Provides
fun provideSanity(): SanityCheck = SanityCheck(true)
@JvmStatic
@Provides
fun provideQRecycler(): QRecycler = QRecycler()
}

View File

@ -0,0 +1,24 @@
package cash.z.android.wallet.ui.fragment
import android.content.Context
import androidx.fragment.app.Fragment
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.AndroidSupportInjection
import dagger.android.support.HasSupportFragmentInjector
import javax.inject.Inject
abstract class BaseFragment : Fragment(), HasSupportFragmentInjector {
@Inject
internal lateinit var childFragmentInjector: DispatchingAndroidInjector<Fragment>
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment>? {
return childFragmentInjector
}
}

View File

@ -10,20 +10,22 @@ import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.core.view.doOnLayout
import cash.z.android.wallet.R
import cash.z.android.wallet.di.module.SanityCheck
import cash.z.android.wallet.ui.activity.MainActivity
import cash.z.wallet.sdk.jni.JniConverter
import com.leinardi.android.speeddial.SpeedDialActionItem
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.fragment_home.*
import javax.inject.Inject
/**
* Fragment representing the home screen of the app. This is the screen most often seen by the user when launching the
* application.
*/
class HomeFragment : Fragment() {
class HomeFragment : BaseFragment() {
// TODO: remove this test object. it is currently just used to exercise the rust code
var converter: JniConverter = JniConverter()
@ -120,3 +122,9 @@ class HomeFragment : Fragment() {
}
}
@Module
abstract class HomeFragmentModule {
@ContributesAndroidInjector
abstract fun contributeHomeFragment(): HomeFragment
}

View File

@ -1,21 +1,18 @@
package cash.z.android.wallet.ui.fragment
import android.content.Context
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import cash.z.android.qrecycler.QRecycler
import cash.z.android.wallet.R
import cash.z.android.wallet.ui.activity.MainActivity
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.fragment_home.*
// 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"
import kotlinx.android.synthetic.main.fragment_receive.*
import javax.inject.Inject
/**
* A simple [Fragment] subclass.
@ -26,19 +23,10 @@ private const val ARG_PARAM2 = "param2"
* create an instance of this fragment.
*
*/
class ReceiveFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private var listener: OnFragmentInteractionListener? = null
class ReceiveFragment : BaseFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
@Inject
lateinit var qrecycler: QRecycler
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@ -52,60 +40,14 @@ class ReceiveFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
(activity as MainActivity).setSupportActionBar(toolbar)
(activity as MainActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
qrecycler.load("https://z.cash").into(receive_qr_code)
}
// TODO: Rename method, update argument and hook method into UI event
fun onButtonPressed(uri: Uri) {
listener?.onFragmentInteraction(uri)
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnFragmentInteractionListener) {
listener = context
} else {
// throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
*
*
* See the Android Training lesson [Communicating with Other Fragments]
* (http://developer.android.com/training/basics/fragments/communicating.html)
* for more information.
*/
interface OnFragmentInteractionListener {
// TODO: Update argument type and name
fun onFragmentInteraction(uri: Uri)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ReceiveFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ReceiveFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
@Module
abstract class ReceiveFragmentModule {
@ContributesAndroidInjector
abstract fun contributeReceiveFragment(): ReceiveFragment
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,80 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="174dp"
android:height="141dp"
android:viewportWidth="174"
android:viewportHeight="141">
<path
android:pathData="M0,100.5a87,40.5 0,1 0,174 0a87,40.5 0,1 0,-174 0z"
android:strokeAlpha="0.09"
android:strokeWidth="1"
android:fillColor="#2A4E5D"
android:fillType="nonZero"
android:strokeColor="#00000000"
android:fillAlpha="0.09"/>
<path
android:pathData="M89.232,0L89.232,0A2.232,2.232 0,0 1,91.464 2.232L91.464,11.161A2.232,2.232 0,0 1,89.232 13.393L89.232,13.393A2.232,2.232 0,0 1,87 11.161L87,2.232A2.232,2.232 0,0 1,89.232 0z"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M116.875,8.391L116.875,8.391C117.897,9.08 118.167,10.468 117.478,11.49L112.485,18.892C111.796,19.914 110.408,20.184 109.386,19.494L109.386,19.494C108.364,18.805 108.095,17.418 108.784,16.396L113.777,8.993C114.466,7.971 115.853,7.702 116.875,8.391Z"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M59.877,8.039L59.877,8.039C60.945,7.423 62.31,7.789 62.927,8.856L67.391,16.589C68.007,17.656 67.641,19.022 66.574,19.638L66.574,19.638C65.506,20.254 64.141,19.889 63.525,18.821L59.06,11.089C58.444,10.021 58.81,8.656 59.877,8.039Z"
android:strokeWidth="1"
android:fillColor="#D8D8D8"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M45.893,32.143L126.303,32.143A8,8 0,0 1,134.303 40.143L134.303,86.643A8,8 0,0 1,126.303 94.643L45.893,94.643A8,8 0,0 1,37.893 86.643L37.893,40.143A8,8 0,0 1,45.893 32.143z"
android:strokeWidth="1"
android:fillColor="#CC9B1F"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M45.893,30.143L126.303,30.143A8,8 0,0 1,134.303 38.143L134.303,81.643A8,8 0,0 1,126.303 89.643L45.893,89.643A8,8 0,0 1,37.893 81.643L37.893,38.143A8,8 0,0 1,45.893 30.143z"
android:strokeWidth="1"
android:fillColor="#FFD970"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M45.893,32.143L126.303,32.143A8,8 0,0 1,134.303 40.143L134.303,83.643A8,8 0,0 1,126.303 91.643L45.893,91.643A8,8 0,0 1,37.893 83.643L37.893,40.143A8,8 0,0 1,45.893 32.143z"
android:strokeWidth="1"
android:fillColor="#F4B728"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M60,46.429h63.393v4.464h-63.393z"
android:strokeWidth="1"
android:fillColor="#2A4E5D"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M60,58.929h63.393v4.464h-63.393z"
android:strokeWidth="1"
android:fillColor="#2A4E5D"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M44.939,37.629L99.76,49.681C103.561,50.434 106.326,51.926 108.054,54.156C109.782,56.386 110.646,58.912 110.646,61.735L110.646,104.856C110.025,108.213 108.895,110.422 107.257,111.483C105.618,112.544 103.119,113.077 99.76,113.081L48.539,102.492C45.358,101.713 42.694,100.36 40.545,98.432C38.397,96.504 37.608,94.713 38.18,93.061L37.668,38.822C38.322,37.151 38.84,36.058 39.222,35.544C39.604,35.029 40.33,34.336 41.399,33.466C42.571,32.731 42.859,32.582 42.263,33.02C41.369,33.676 41.017,34.208 41.017,35.242C41.017,35.931 42.325,36.727 44.939,37.629Z"
android:strokeWidth="1"
android:fillColor="#FFD970"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M38.954,37L40.545,90C40.545,94.667 42.027,97.333 44.99,98C62.321,101.9 104,111.483 108,109.302C111.245,107.532 107.518,111.314 107.257,111.483C105.618,112.544 103.119,113.077 99.76,113.081L48.539,102.492C44.912,101.947 42.131,100.783 40.196,99C38.261,97.217 37.414,95.088 37.657,92.612L37.657,38.734C37.694,36.455 38.126,34.826 38.954,33.845C40.196,32.374 40.79,31.843 39.96,33.239C39.406,34.169 39.071,35.423 38.954,37Z"
android:strokeWidth="1"
android:fillColor="#F5C642"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M97.141,85.556C99.87,85.995 102.371,84 102.727,81.099C103.083,78.198 101.16,75.49 98.43,75.051C95.701,74.612 93.2,76.608 92.844,79.509C92.488,82.41 94.412,85.117 97.141,85.556Z"
android:strokeWidth="2"
android:fillColor="#E7EAEB"
android:strokeColor="#607D89"
android:fillType="nonZero"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="451dp"
android:height="451dp"
android:viewportWidth="451"
android:viewportHeight="451">
<path
android:pathData="M225.4,0C101.11,0 0,101.11 0,225.39C0,349.67 101.11,450.79 225.4,450.79C349.69,450.79 450.79,349.68 450.79,225.39C450.79,101.1 349.68,0 225.4,0ZM225.4,401.79C128.131,401.79 49,322.659 49,225.39C49,128.122 128.131,49 225.4,49C322.668,49 401.79,128.131 401.79,225.39C401.79,322.65 322.659,401.79 225.4,401.79Z"
android:strokeWidth="1"
android:fillColor="#231F20"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M225.4,225.39m-177.65,0a177.65,177.65 0,1 1,355.3 0a177.65,177.65 0,1 1,-355.3 0"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M305.78,155.13l0,-34.38l-61.5,0l0,-37.73l-37.77,0l0,37.73l-61.49,0l0,45.54l95.33,0l-77.96,107.29l-17.37,22.08l0,34.38l61.49,0l0,37.61l4.53,0l0,0.16l28.71,0l0,-0.16l4.53,0l0,-37.61l61.5,0l0,-45.54l-95.34,0l77.96,-107.29z"
android:strokeWidth="1"
android:fillColor="#231F20"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -28,7 +28,7 @@
android:id="@+id/image_empty_wallet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/empty_wallet"
app:srcCompat="@drawable/ic_emptywallet"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@ -3,7 +3,8 @@
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_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_receive_fragment"
@ -38,17 +39,29 @@
android:id="@+id/receive_qr_code"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/zcashWhite"
android:scaleType="centerInside"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/include_toolbar"
app:layout_constraintVertical_bias="0.172"
app:layout_constraintVertical_bias="0.198"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.58111" />
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
app:srcCompat="@drawable/ic_zcash_white"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.12"
app:layout_constraintStart_toStartOf="@id/receive_qr_code"
app:layout_constraintEnd_toEndOf="@id/receive_qr_code"
app:layout_constraintTop_toTopOf="@id/receive_qr_code"
app:layout_constraintBottom_toBottomOf="@id/receive_qr_code"
/>
<View
android:id="@+id/receive_address_background"
android:layout_width="0dp"

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,37 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode 3
versionName "1.0.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation deps.androidx.coreKtx
implementation deps.kotlin.stdlib
implementation 'com.google.zxing:core:3.2.1'
implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View File

@ -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

View File

@ -0,0 +1,26 @@
package cash.z.android.qrecycler;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("cash.z.android.qrecycler.test", appContext.getPackageName());
}
}

View File

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cash.z.android.qrecycler" />

View File

@ -0,0 +1,46 @@
package cash.z.android.qrecycler
import android.graphics.Bitmap
import android.graphics.Color
import android.widget.ImageView
import androidx.core.view.doOnLayout
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType.ERROR_CORRECTION
import com.google.zxing.EncodeHintType.MARGIN
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.Q
class QRecycler {
fun load(content: String): Builder {
return Builder(content)
}
fun encode(builder: Builder) {
builder.target.doOnLayout { measuredView ->
val w = measuredView.width
val h = measuredView.height
val hints = mapOf(ERROR_CORRECTION to Q, MARGIN to 2)
val bitMatrix = QRCodeWriter().encode(builder.content, BarcodeFormat.QR_CODE, w, h, hints)
val pixels = IntArray(w * h)
for (y in 0 until h) {
val offset = y * w
for (x in 0 until w) {
pixels[offset + x] = if (bitMatrix.get(x, y)) Color.TRANSPARENT else Color.WHITE
}
}
// TODO: RECYCLE THIS BITMAP MEMORY!!! Do it in a way that is lifecycle-aware and disposes of the memory when the fragment is off-screen
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
(measuredView as ImageView).setImageBitmap(bitmap)
}
}
inner class Builder(val content: String) {
lateinit var target: ImageView
fun into(imageView: ImageView) {
target = imageView
encode(this)
}
}
}

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">QRecycler</string>
</resources>

View File

@ -0,0 +1,17 @@
package cash.z.android.qrecycler;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -1 +1 @@
include ':app'
include ':app', ':qrecycler'