Skip to content

Commit afe1579

Browse files
committed
Merge branch 'master' into mainnet
2 parents 0225c6b + 955833c commit afe1579

File tree

13 files changed

+205
-20
lines changed

13 files changed

+205
-20
lines changed

app/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ android {
3333
applicationId "fr.acinq.phoenix.mainnet"
3434
minSdkVersion 24
3535
targetSdkVersion 30
36-
versionCode 29
37-
versionName "1.4.14"
36+
versionCode 30
37+
versionName "${gitCommitHash}"
3838
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
3939
}
4040
buildTypes {
@@ -153,7 +153,7 @@ dependencies {
153153
implementation 'com.github.tony19:logback-android:2.0.0'
154154

155155
// eclair core
156-
def eclair_version = "0.4.12-android-phoenix"
156+
def eclair_version = "0.4.13-android-phoenix"
157157
implementation "fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.5.1"
158158
implementation "fr.acinq.eclair:eclair-core_2.11:$eclair_version"
159159

app/src/androidTest/java/fr/acinq/phoenix/LNObjectParserTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class LNObjectParserTest {
4747
// lnurl
4848
Wallet.parseLNObject("lnurl1dp68gurn8ghj7ctsdyhxcmndv9exket5wvhxxmmd9akxuatjdshkz0m5v9nn6mr0va5kufntxy7kzvfnv5ckxvmzxu6r2enrxscnxc33vvunsdrx8qmnxdfkxa3xgc3sv9jkgctyvgmrxde5xqcr2veh8ymrycf5vvuxydp5vyexgdpexgnxsmtpvv7nwc3cxyuxgdf58qmrsvehvvcxyvmrxvurxc3jx93rsetyvfjnyepn89jxvcmpxccnyvejxe3kxc3cxumxvde5v3jxvc3sxcmrycnxxuj63qyj") as LNUrlAuth
4949
Wallet.parseLNObject("lightning:LNURL1DP68GURN8GHJ7MRWW4EXCTNZD9NHXATW9EU8J730D3H82UNV94MKJARGV3EXZAELWDJHXUMFDAHR6WPSXU6NYVRRVE3K2VFJX9JXYCF3XA3RZDMPV43NWDPSVCUKXDFHVVEKXDMYXD3XVCN9XSEK2VPHVS6KVERYXUUNJWR9VS6XYCEHX5CQQCJJXZ") as LNUrlWithdraw
50+
Wallet.parseLNObject("lnurl:lnurl1dp68gurn8ghj7mrww4exctt5dahkccn00qhxget8wfjk2um0veax2un09e3k7mf0w5lhgct884kx7emfdcnxkvfa8p3nqepjv5cnsdesxdjnxe33x5cngvfev5enxdtzvdnxze33xpjrvwrr8q6k2epkxgux2dnpxe3rgefevg6x2vp4v4nx2d3exenryesuzq939") as LNUrlAuth
5051
Wallet.parseLNObject("https://service.com/giftcard/redeem?id=123&lightning=LNURL1DP68GURN8GHJ7MRWW4EXCTNZD9NHXATW9EU8J730D3H82UNV94MKJARGV3EXZAELWDJHXUMFDAHR6WPSXU6NYVRRVE3K2VFJX9JXYCF3XA3RZDMPV43NWDPSVCUKXDFHVVEKXDMYXD3XVCN9XSEK2VPHVS6KVERYXUUNJWR9VS6XYCEHX5CQQCJJXZ") as LNUrlWithdraw
5152
Wallet.parseLNObject("http://foo.bar?lightning=LNURL1DP68GURN8GHJ7MRWW4EXCTNZD9NHXATW9EU8J730D3H82UNV94KX7EMFDCLHGCT884KX7EMFDCNXKVFAX5CRGCFJXANRWVN9VSUK2WTPVF3NXVP4V93KXD3HVS6RWVTZXY6NWVEHV5CNQCFN893RJWF4V9NRQVM9XANRYVR9X4NXGETY8Q6KYDC0Q6NTC") as LNUrlAuth
5253
Wallet.parseLNObject("foobar://test?lightning=LNURL1DP68GURN8GHJ7MRWW4EXCTNZD9NHXATW9EU8J730D3H82UNV94MKJARGV3EXZAELWDJHXUMFDAHR6WPNXF3KGVFN89JNZENPVY6NSVRP8QCKZCEKVCCRGCTPXY6RJVT9XGMK2DPE893KZEPJXE3RGVFKXYUNWV3EVV6R2DRPXG6RWWP5VDSSSQSUZT") as LNUrlWithdraw

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858

5959
<data android:scheme="bitcoin" />
6060
<data android:scheme="lightning" />
61+
<data android:scheme="lnurl" />
6162
</intent-filter>
6263
</activity>
6364

app/src/main/java/fr/acinq/phoenix/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class MainActivity : AppCompatActivity() {
136136
log.debug("reading URI intent=$intent with data=$data")
137137
if (data != null && data.scheme != null) {
138138
when (data.scheme) {
139-
"bitcoin", "lightning" -> {
139+
"bitcoin", "lightning", "lnurl" -> {
140140
app.currentURIIntent.value = data.toString()
141141
}
142142
else -> log.info("unhandled payment scheme $data")

app/src/main/java/fr/acinq/phoenix/send/ReadInputFragment.kt

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,27 @@
1717
package fr.acinq.phoenix.send
1818

1919
import android.Manifest
20+
import android.app.Activity
2021
import android.app.AlertDialog
2122
import android.content.Intent
2223
import android.content.pm.PackageManager
24+
import android.graphics.BitmapFactory
25+
import android.net.Uri
2326
import android.os.Bundle
24-
import android.os.Handler
2527
import android.view.LayoutInflater
2628
import android.view.View
2729
import android.view.ViewGroup
2830
import androidx.core.app.ActivityCompat
2931
import androidx.core.content.ContextCompat
3032
import androidx.lifecycle.ViewModelProvider
33+
import androidx.lifecycle.lifecycleScope
3134
import androidx.navigation.fragment.findNavController
3235
import androidx.navigation.fragment.navArgs
3336
import com.google.common.base.Strings
34-
import com.google.zxing.BarcodeFormat
35-
import com.google.zxing.ResultPoint
37+
import com.google.zxing.*
3638
import com.google.zxing.client.android.Intents
39+
import com.google.zxing.common.HybridBinarizer
40+
import com.google.zxing.multi.qrcode.QRCodeMultiReader
3741
import com.journeyapps.barcodescanner.BarcodeCallback
3842
import com.journeyapps.barcodescanner.BarcodeResult
3943
import fr.acinq.eclair.payment.PaymentRequest
@@ -46,9 +50,14 @@ import fr.acinq.phoenix.lnurl.LNUrlPay
4650
import fr.acinq.phoenix.lnurl.LNUrlWithdraw
4751
import fr.acinq.phoenix.utils.*
4852
import fr.acinq.phoenix.utils.customviews.ButtonView
53+
import kotlinx.coroutines.CoroutineExceptionHandler
54+
import kotlinx.coroutines.Dispatchers
55+
import kotlinx.coroutines.launch
56+
import kotlinx.coroutines.withContext
4957
import org.slf4j.Logger
5058
import org.slf4j.LoggerFactory
5159

60+
5261
class ReadInputFragment : BaseFragment() {
5362

5463
override val log: Logger = LoggerFactory.getLogger(this::class.java)
@@ -91,8 +100,6 @@ class ReadInputFragment : BaseFragment() {
91100
mBinding.scanView.pause()
92101
}
93102
is ReadInputState.Done.Lightning -> {
94-
// check payment request chain
95-
val acceptedPrefix = PaymentRequest.prefixes().get(Wallet.getChainHash())
96103
// additional controls
97104
if (app.state.value?.getNodeId() == it.pr.nodeId()) {
98105
log.debug("abort payment to self")
@@ -170,6 +177,13 @@ class ReadInputFragment : BaseFragment() {
170177
context?.let { model.readInput(ClipboardHelper.read(it)) }
171178
}
172179

180+
mBinding.browseButton.setOnClickListener {
181+
startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
182+
addCategory(Intent.CATEGORY_OPENABLE)
183+
type = "image/*"
184+
}, Constants.INTENT_PICK_IMAGE_FILE)
185+
}
186+
173187
mBinding.cancelButton.setOnClickListener { findNavController().popBackStack() }
174188

175189
mBinding.errorButton.setOnClickListener {
@@ -202,6 +216,13 @@ class ReadInputFragment : BaseFragment() {
202216
mBinding.scanView.pause()
203217
}
204218

219+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
220+
super.onActivityResult(requestCode, resultCode, data)
221+
if (requestCode == Constants.INTENT_PICK_IMAGE_FILE && resultCode == Activity.RESULT_OK) {
222+
readBitmap(data?.data)
223+
}
224+
}
225+
205226
private fun startScanning() {
206227
if (model.inputState.value is ReadInputState.Error || model.inputState.value is ReadInputState.Done) {
207228
model.inputState.postValue(ReadInputState.Scanning)
@@ -224,4 +245,25 @@ class ReadInputFragment : BaseFragment() {
224245
}
225246
}
226247

248+
private fun readBitmap(uri: Uri?) {
249+
lifecycleScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, e ->
250+
log.error("failed to load or read QR code image from uri=$uri: ", e)
251+
model.inputState.postValue(ReadInputState.Error.UnhandledInput)
252+
}) {
253+
val bitmap = requireContext().contentResolver.openFileDescriptor(uri!!, "r")?.use {
254+
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
255+
}!!
256+
val pixels = IntArray(bitmap.width * bitmap.height)
257+
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
258+
val binaryBitmap = BinaryBitmap(HybridBinarizer(RGBLuminanceSource(bitmap.width, bitmap.height, pixels)))
259+
val result = QRCodeMultiReader().decodeMultiple(
260+
binaryBitmap
261+
)
262+
log.info("successfully read QR code with result=$result")
263+
withContext(Dispatchers.Main) {
264+
model.readInput(result.first().text)
265+
}
266+
}
267+
}
268+
227269
}

app/src/main/java/fr/acinq/phoenix/utils/Constants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ object Constants {
4545

4646
// -- intents
4747
const val INTENT_CAMERA_PERMISSION_REQUEST = 1
48+
const val INTENT_PICK_IMAGE_FILE = 2
4849

4950
// -- android notifications
5051
const val DELAY_BEFORE_BACKGROUND_WARNING = DateUtils.DAY_IN_MILLIS * 5

app/src/main/java/fr/acinq/phoenix/utils/Wallet.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ object Wallet {
110110
return when {
111111
trimmed.startsWith("lightning://", true) -> trimmed.drop(12)
112112
trimmed.startsWith("lightning:", true) -> trimmed.drop(10)
113+
trimmed.startsWith("lnurl://", true) -> trimmed.drop(8)
114+
trimmed.startsWith("lnurl:", true) -> trimmed.drop(6)
113115
else -> trimmed
114116
}
115117
}
@@ -135,7 +137,7 @@ object Wallet {
135137
try {
136138
val uri = URI(input)
137139
if (uri.scheme != null) {
138-
uri.getParams()["lightning"] ?: throw RuntimeException("not a valid LNURL fallback scheme")
140+
uri.getParams()["lightning"] ?: throw RuntimeException("uri does not contain a valid LNURL fallback")
139141
} else {
140142
input
141143
}.let { LNUrl.extractLNUrl(it) }
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!--
2+
~ Copyright 2021 ACINQ SAS
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:width="@dimen/button_height"
19+
android:height="@dimen/button_height"
20+
android:viewportWidth="24"
21+
android:viewportHeight="24">
22+
<path
23+
android:fillColor="#00000000"
24+
android:pathData="M5,3L19,3A2,2 0,0 1,21 5L21,19A2,2 0,0 1,19 21L5,21A2,2 0,0 1,3 19L3,5A2,2 0,0 1,5 3z"
25+
android:strokeWidth="2"
26+
android:strokeColor="?attr/textColor"
27+
android:strokeLineCap="round"
28+
android:strokeLineJoin="round" />
29+
<path
30+
android:fillColor="#00000000"
31+
android:pathData="M8.5,8.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
32+
android:strokeWidth="1"
33+
android:strokeColor="?attr/textColor"
34+
android:strokeLineCap="round"
35+
android:strokeLineJoin="round" />
36+
<path
37+
android:fillColor="#00000000"
38+
android:pathData="M21,15l-5,-5l-11,11"
39+
android:strokeWidth="2"
40+
android:strokeColor="?attr/textColor"
41+
android:strokeLineCap="round"
42+
android:strokeLineJoin="round" />
43+
</vector>

app/src/main/res/layout/fragment_read_invoice.xml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,42 @@
4646
android:layout_width="match_parent"
4747
android:layout_height="wrap_content"
4848
android:background="@drawable/button_bg_square"
49-
android:padding="@dimen/space_md_p"
49+
android:padding="@dimen/space_md"
5050
app:enableOrFade="@{model.inputState instanceof ReadInputState.Scanning}"
5151
app:icon="@drawable/ic_clipboard"
5252
app:layout_constraintTop_toTopOf="parent"
5353
app:text="@string/send_init_paste" />
5454

5555
<View
56-
android:id="@+id/sep"
56+
android:id="@+id/sep_paste"
5757
style="@style/HLineSeparator"
5858
app:layout_constraintStart_toStartOf="parent"
5959
app:layout_constraintTop_toBottomOf="@id/paste_button" />
6060

61+
<fr.acinq.phoenix.utils.customviews.ButtonView
62+
android:id="@+id/browse_button"
63+
android:layout_width="match_parent"
64+
android:layout_height="wrap_content"
65+
android:background="@drawable/button_bg_square"
66+
android:padding="@dimen/space_md"
67+
app:enableOrFade="@{model.inputState instanceof ReadInputState.Scanning}"
68+
app:icon="@drawable/ic_image"
69+
app:layout_constraintTop_toBottomOf="@id/sep_paste"
70+
app:text="@string/send_init_browse" />
71+
72+
<View
73+
android:id="@+id/sep_browse"
74+
style="@style/HLineSeparator"
75+
app:layout_constraintStart_toStartOf="parent"
76+
app:layout_constraintTop_toBottomOf="@id/browse_button" />
77+
6178
<fr.acinq.phoenix.utils.customviews.ButtonView
6279
android:id="@+id/cancel_button"
6380
android:layout_width="match_parent"
6481
android:layout_height="wrap_content"
6582
android:background="@drawable/button_bg_square"
66-
android:padding="@dimen/space_md_p"
67-
app:layout_constraintTop_toBottomOf="@id/sep"
83+
android:padding="@dimen/space_md"
84+
app:layout_constraintTop_toBottomOf="@id/sep_browse"
6885
app:text="@string/btn_cancel" />
6986

7087
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)