├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ ├── android-master.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── lvaccaro
│ │ └── lamp
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ │ └── com
│ │ │ └── lvaccaro
│ │ │ └── lamp
│ │ │ ├── LightningCli.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── activities
│ │ │ ├── BuildInvoiceActivity.kt
│ │ │ ├── ChannelsActivity.kt
│ │ │ ├── ConsoleActivity.kt
│ │ │ ├── LogActivity.kt
│ │ │ ├── ScanActivity.kt
│ │ │ ├── SendActivity.kt
│ │ │ ├── SettingsActivity.kt
│ │ │ └── UriResultActivity.kt
│ │ │ ├── adapters
│ │ │ ├── BalanceAdapter.kt
│ │ │ └── HashMapAdapter.kt
│ │ │ ├── fragments
│ │ │ ├── ChannelFragment.kt
│ │ │ ├── DecodedInvoiceFragment.kt
│ │ │ ├── FundChannelFragment.kt
│ │ │ ├── PeerInfoFragment.kt
│ │ │ ├── RecyclerViewFragment.kt
│ │ │ └── WithdrawFragment.kt
│ │ │ ├── handlers
│ │ │ ├── BrokenStatus.kt
│ │ │ ├── IEventHandler.kt
│ │ │ ├── NewBlockHandler.kt
│ │ │ ├── NewChannelPayment.kt
│ │ │ ├── NewTransaction.kt
│ │ │ ├── NodeUpHandler.kt
│ │ │ ├── PaidInvoice.kt
│ │ │ └── ShutdownNode.kt
│ │ │ ├── services
│ │ │ ├── CLightningException.kt
│ │ │ ├── Globber.kt
│ │ │ ├── LightningService.kt
│ │ │ └── TorService.kt
│ │ │ ├── utils
│ │ │ ├── Archive.kt
│ │ │ ├── LampKeys.kt
│ │ │ ├── LogObserver.kt
│ │ │ ├── SimulatorPlugin.kt
│ │ │ ├── UI.kt
│ │ │ └── Validator.kt
│ │ │ └── views
│ │ │ ├── HistoryBottomSheet.kt
│ │ │ └── PowerImageView.kt
│ └── res
│ │ ├── drawable-anydpi
│ │ ├── ic_camera.xml
│ │ ├── ic_channels.xml
│ │ └── ic_paste.xml
│ │ ├── drawable-hdpi
│ │ ├── ic_camera.png
│ │ ├── ic_channels.png
│ │ ├── ic_notification.png
│ │ ├── ic_paste.png
│ │ ├── ic_sats.png
│ │ └── ic_tor.png
│ │ ├── drawable-mdpi
│ │ ├── ic_camera.png
│ │ ├── ic_channels.png
│ │ ├── ic_notification.png
│ │ ├── ic_paste.png
│ │ ├── ic_sats.png
│ │ └── ic_tor.png
│ │ ├── drawable-xhdpi
│ │ ├── ic_camera.png
│ │ ├── ic_channels.png
│ │ ├── ic_notification.png
│ │ ├── ic_paste.png
│ │ ├── ic_sats.png
│ │ └── ic_tor.png
│ │ ├── drawable-xxhdpi
│ │ ├── ic_camera.png
│ │ ├── ic_channels.png
│ │ ├── ic_notification.png
│ │ ├── ic_paste.png
│ │ ├── ic_sats.png
│ │ └── ic_tor.png
│ │ ├── drawable-xxxhdpi
│ │ ├── ic_notification.png
│ │ ├── ic_sats.png
│ │ └── ic_tor.png
│ │ ├── drawable
│ │ ├── divider.xml
│ │ ├── ic_arrow_down.xml
│ │ ├── ic_arrow_up.xml
│ │ ├── ic_baseline_account_balance_24.xml
│ │ ├── ic_baseline_account_box_24.xml
│ │ ├── ic_baseline_call_received_24.xml
│ │ ├── ic_baseline_send_24.xml
│ │ ├── ic_baseline_share_24.xml
│ │ ├── ic_blockchain.xml
│ │ ├── ic_lamp.xml
│ │ ├── ic_lamp1.png
│ │ ├── ic_lamp2.png
│ │ ├── ic_lamp3.png
│ │ ├── ic_lamp4.png
│ │ ├── ic_lamp_off.png
│ │ ├── ic_lamp_on.png
│ │ └── ic_lightning.xml
│ │ ├── layout
│ │ ├── activity_build_invoice.xml
│ │ ├── activity_channels.xml
│ │ ├── activity_console.xml
│ │ ├── activity_log.xml
│ │ ├── activity_main.xml
│ │ ├── activity_scan.xml
│ │ ├── activity_send.xml
│ │ ├── activity_settings.xml
│ │ ├── content_main_off.xml
│ │ ├── content_main_on.xml
│ │ ├── fragment_channel.xml
│ │ ├── fragment_decoded_invoice.xml
│ │ ├── fragment_fundchannel.xml
│ │ ├── fragment_history.xml
│ │ ├── fragment_on_main_view.xml
│ │ ├── fragment_peer_info.xml
│ │ ├── fragment_recyclerview.xml
│ │ ├── fragment_withdraw.xml
│ │ ├── list_balance.xml
│ │ ├── list_channel.xml
│ │ └── list_tx.xml
│ │ ├── menu
│ │ ├── menu.xml
│ │ ├── menu_channels.xml
│ │ ├── menu_invoice.xml
│ │ ├── menu_log.xml
│ │ └── menu_scan.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ └── ic_launcher_foreground.png
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── root_preferences.xml
│ └── test
│ └── java
│ └── com
│ └── lvaccaro
│ └── lamp
│ └── ValidatorUnitTest.kt
├── build.gradle
├── doc
├── cmdline-tools-setup.md
└── img
│ ├── Screen1.png
│ ├── Screen2.png
│ └── Screen3.png
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Note that in this case 'import-ordering' rule will be active and 'indent' will be disabled
2 | [api/*.{kt,kts}]
3 | root = true
4 | disabled_rules=indent
5 |
6 | # Comma-separated list of rules to disable (Since 0.34.0)
7 | # Note that rules in any ruleset other than the standard ruleset will need to be prefixed
8 | # by the ruleset identifier.
9 | disabled_rules=no-wildcard-imports,experimental:annotation,my-custom-ruleset
10 |
11 | # Defines the imports layout. The layout can be composed by the following symbols:
12 | # "*" - wildcard. There must be at least one entry of a single wildcard to match all other imports. Matches anything after a specified symbol/import as well.
13 | # "|" - blank line. Supports only single blank lines between imports. No blank line is allowed in the beginning or end of the layout.
14 | # "^" - alias import, e.g. "^android.*" will match all android alias imports, "^" will match all other alias imports.
15 | # import paths - these can be full paths, e.g. "java.util.List.*" as well as wildcard paths, e.g. "kotlin.**"
16 | # Examples (we use ij_kotlin_imports_layout to set an imports layout for both ktlint and IDEA via a single property):
17 | # ij_kotlin_imports_layout=* # alphabetical with capital letters before lower case letters (e.g. Z before a), no blank lines
18 | # ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^ # default IntelliJ IDEA style, same as alphabetical, but with "java", "javax", "kotlin" and alias imports in the end of the imports list
19 | # ij_kotlin_imports_layout=android.**,|,^org.junit.**,kotlin.io.Closeable.*,|,*,^ # custom imports layout
20 |
21 | # According to https://kotlinlang.org/docs/reference/coding-conventions.html#names-for-test-methods it is acceptable to write method names
22 | # in natural language. When using natural language, the description tends to be longer. Allow lines containing an identifier between
23 | # backticks to be longer than the maximum line length. (Since 0.41.0)
24 | [**/test/**.kt]
25 | ktlint_ignore_back_ticked_identifier=true
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [vincenzopalazzo, lvaccaro]
2 | custom: [https://btctip.lvaccaro.com]
--------------------------------------------------------------------------------
/.github/workflows/android-master.yml:
--------------------------------------------------------------------------------
1 | name: android-master
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - 'master'
7 | push:
8 | branches:
9 | - 'master'
10 |
11 | jobs:
12 | apk:
13 | name: Generate APK
14 | runs-on: ubuntu-18.04
15 |
16 | steps:
17 | - uses: actions/checkout@v1
18 | - name: set up JDK 11
19 | uses: actions/setup-java@v1
20 | with:
21 | java-version: 11
22 | - name: Check koltin formatting
23 | run: bash ./gradlew lintKotlin
24 | - name: Build debug APK
25 | run: bash ./gradlew assembleDebug --stacktrace
26 | - name: Upload APK
27 | uses: actions/upload-artifact@v1
28 | with:
29 | name: app
30 | path: app/build/outputs/apk/debug/app-debug.apk
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'release*'
7 |
8 | jobs:
9 | apk:
10 | name: Generate Signed APK
11 | runs-on: ubuntu-18.04
12 |
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: set up JDK 1.8
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 1.8
19 | - name: Build debug APK
20 | run: bash ./gradlew assembleRelease --stacktrace
21 | - name: Sign App release
22 | uses: r0adkll/sign-android-release@v1
23 | id: sign_app
24 | with:
25 | releaseDirectory: app/build/outputs/apk/release
26 | signingKeyBase64: ${{ secrets.SIGNING_KEY }}
27 | alias: ${{ secrets.ALIAS }}
28 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
29 | keyPassword: ${{ secrets.KEY_PASSWORD }}
30 |
31 | - uses: actions/upload-artifact@v2
32 | with:
33 | name: Signed app bundle
34 | path: ${{steps.sign_app.outputs.signedReleaseFile}}
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | app/release
11 | app/libs
12 | app/build
13 | keystore*
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Luca Vaccaro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
29 |
30 | ## Table of Content
31 |
32 | - Introduction
33 | - How to Use
34 | - Build
35 | - How to Contribute
36 | - References
37 | - License
38 |
39 | ## Introduction
40 |
41 | > This is an experimenting lightning wallet. Use it on testnet or only with amounts you can afford to lose on mainnet.
42 |
43 | Touch the lamp to download and run c-lightning from cross-compiled binaries for Android are available [here](https://github.com/clightning4j/lightning_ndk/releases).
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ## How to Use
52 |
53 | ### Bitcoin Setup
54 |
55 | ##### Automatic with esplora plugin
56 |
57 | This is the default behaviour.
58 |
59 | Lamp is using [the C Esplora plugin for C-lightning](https://github.com/clightning4j/esplora_clnd_plugin) as the Bitcoin backend of the lightning node (to fetch chain/blocks/transactions information and send transactions).
60 |
61 | You can point it to your own [Esplora](github.com/Blockstream/esplora) instance in the settings, and it uses [blockstream.info](https://blockstream.info) by default.
62 |
63 |
64 | ##### Manually with bitcoind rpc node
65 | On Lamp settings, disable Esplora plugin and set the current Bitcoin RPC options:
66 |
67 | - Bitcoin RPC username
68 | - Bitcoin RPC password
69 | - Bitcoin RPC host (default 127.0.0.1)
70 | - Bitcoin RPC port (default 18332 for testnet)
71 |
72 | ### Tor Setup
73 |
74 | ##### Automatic with internal tor service
75 |
76 | Lamp is using tor hidden service as default. A new hidden service will be created at the first running time.
77 |
78 | ##### Manually with Orbot
79 |
80 | Open [Orbot](https://github.com/guardianproject/Orbot) and setup a fixed tor address by menu: Onion Services -> Hosted Services -> set a service name and port 9735. Restarting tor to discover and copy the local address.
81 |
82 | On Lamp settings, enable proxy using orbot localhost gateway:
83 |
84 | - proxy: 127.0.0.1:9050
85 | - announce address: tor_address
86 | - bind address: 127.0.0.1:9735
87 |
88 | Read the following instructions at [Tor on clightning](https://lightning.readthedocs.io/TOR.html) to setup address on different network scenario.
89 |
90 | ## Building
91 |
92 | * [in Linux using cmdline tools](doc/cmdline-tools-setup.md)
93 |
94 | ## How to Contribute
95 |
96 | Just propose new stuff to add or bug fixing with a new PR.
97 |
98 | ### Code Style
99 | [](https://ktlint.github.io/)
100 |
101 | > We live in a world where robots can drive a car, so we shouldn't just write code, we should write elegant code.
102 |
103 | This repository use [ktlint](https://github.com/pinterest/ktlint) to maintains the code of the repository elegant, so
104 | before submit the code check the Kotlin format with the following command on the root of the directory
105 |
106 | ```bash
107 | ./gradlew formatKotlin
108 | ```
109 |
110 | ## References
111 |
112 | - [ABCore](https://github.com/greenaddress/abcore) Android Bitcoin Core wallet
113 | - [bitcoin_ndk](https://github.com/greenaddress/bitcoin_ndk) ndk build of bitcoin core and knots
114 | - [clightning_ndk](https://github.com/clightning4j/lightning_ndk) android cross-compilation of c-lightning for Android >= 24 Api
115 | - [c-lightning](https://github.com/ElementsProject/lightning) Lightning Network implementation in C
116 | - [esplora plugin](https://github.com/clightning4j/esplora_clnd_plugin) C-Lightning plugin for esplora
117 |
118 | ## License
119 |
120 |
121 |
122 |
123 |
124 | c-lightning Android Mobile Porting
125 |
126 | Copyright (c) 2019-2021 Luca Vaccaro
127 |
128 | This program is free software; you can redistribute it and/or modify
129 | it under the terms of the GNU General Public License as published by
130 | the Free Software Foundation; either version 2 of the License.
131 |
132 | This program is distributed in the hope that it will be useful,
133 | but WITHOUT ANY WARRANTY; without even the implied warranty of
134 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
135 | GNU General Public License for more details.
136 |
137 | You should have received a copy of the GNU General Public License along
138 | with this program; if not, write to the Free Software Foundation, Inc.,
139 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
140 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: "org.jmailen.kotlinter"
5 |
6 | android {
7 | compileSdkVersion 28
8 | buildToolsVersion "29.0.3"
9 | defaultConfig {
10 | applicationId "com.lvaccaro.lamp"
11 | minSdkVersion 24
12 | targetSdkVersion 28
13 | versionCode 37
14 | versionName "3.7"
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | vectorDrawables.useSupportLibrary = true
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | testOptions {
26 | unitTests.returnDefaultValues = true
27 | }
28 |
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 |
34 | kotlinOptions {
35 | jvmTarget = JavaVersion.VERSION_1_8.toString()
36 | }
37 |
38 | lintOptions {
39 | abortOnError false
40 | }
41 | }
42 |
43 | dependencies {
44 | implementation fileTree(dir: 'libs', include: ['*.jar'])
45 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
46 | implementation 'androidx.appcompat:appcompat:1.3.0'
47 | implementation 'androidx.core:core-ktx:1.5.0'
48 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
49 | implementation 'androidx.preference:preference-ktx:1.1.1'
50 | implementation 'org.tukaani:xz:1.8'
51 | implementation 'org.apache.commons:commons-compress:1.20'
52 | implementation 'com.google.zxing:core:3.4.0'
53 | implementation 'me.dm7.barcodescanner:zxing:1.9.13'
54 | implementation 'com.google.android.material:material:1.3.0'
55 | implementation "org.jetbrains.anko:anko-commons:0.10.4"
56 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
57 |
58 | testImplementation 'junit:junit:4.13.2'
59 | androidTestImplementation 'androidx.test:runner:1.3.0'
60 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
61 | }
62 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/lvaccaro/lamp/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.runner.AndroidJUnit4
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.lvaccaro.lamp", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
25 |
28 |
29 |
30 |
35 |
38 |
39 |
44 |
47 |
48 |
52 |
55 |
56 |
60 |
63 |
64 |
68 |
71 |
72 |
76 |
79 |
80 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
108 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/LightningCli.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import androidx.preference.PreferenceManager
6 | import com.lvaccaro.lamp.services.LightningService
7 | import org.json.JSONObject
8 | import java.io.File
9 | import java.io.InputStream
10 | import java.util.logging.Logger
11 |
12 | class LightningCli {
13 |
14 | val command = "lightning-cli"
15 | val log = Logger.getLogger(LightningService::class.java.name)
16 |
17 | @Throws(Exception::class)
18 | fun exec(c: Context, options: Array, json: Boolean = true): InputStream {
19 | val binaryDir = c.rootDir()
20 | val lightningDir = File(c.rootDir(), ".lightning")
21 | val sharedPref = PreferenceManager.getDefaultSharedPreferences(c)
22 | val network = sharedPref.getString("network", "testnet").toString()
23 |
24 | val args = arrayOf(
25 | String.format("%s/cli/%s", binaryDir.canonicalPath, command),
26 | String.format("--network=%s", network),
27 | String.format("--lightning-dir=%s", lightningDir.path),
28 | String.format("--%s", if (json == true) "json" else "raw")
29 | )
30 |
31 | val pb = ProcessBuilder((args + options).asList())
32 | pb.directory(binaryDir)
33 | // pb.redirectErrorStream(true)
34 |
35 | val process = pb.start()
36 | val code = process.waitFor()
37 | if (code != 0) {
38 | val error = process.errorStream.toText()
39 | val input = process.inputStream.toText()
40 | log.info(error)
41 | log.info(input)
42 | throw Exception(if (!error.isEmpty()) error else input)
43 | }
44 | return process.inputStream
45 | }
46 | }
47 |
48 | // extension to convert inputStream in text
49 | fun InputStream.toText(): String {
50 | val reader = bufferedReader()
51 | val builder = StringBuilder()
52 | var line = reader.readLine()
53 | while (line != null) {
54 | if (!line.startsWith("**")) {
55 | builder.append(line + "\r\n")
56 | }
57 | line = reader.readLine()
58 | }
59 | return builder.toString()
60 | }
61 |
62 | // extension to convert inputStream in json object
63 | fun InputStream.toJSONObject(): JSONObject {
64 | val text = toText()
65 | val json = JSONObject(text)
66 | return json
67 | }
68 |
69 | // extension to provide the rootDir
70 | fun Context.rootDir(): File {
71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
72 | return noBackupFilesDir
73 | }
74 | return filesDir
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/activities/BuildInvoiceActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.activities
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.view.View
6 | import android.view.inputmethod.InputMethodManager
7 | import android.widget.Toast
8 | import androidx.appcompat.app.AppCompatActivity
9 | import com.lvaccaro.lamp.LightningCli
10 | import com.lvaccaro.lamp.R
11 | import com.lvaccaro.lamp.toJSONObject
12 | import com.lvaccaro.lamp.utils.UI
13 | import kotlinx.android.synthetic.main.activity_build_invoice.balanceText
14 | import kotlinx.android.synthetic.main.activity_build_invoice.btcButton
15 | import kotlinx.android.synthetic.main.activity_build_invoice.btclnLayout
16 | import kotlinx.android.synthetic.main.activity_build_invoice.copyButton
17 | import kotlinx.android.synthetic.main.activity_build_invoice.copyShareLayout
18 | import kotlinx.android.synthetic.main.activity_build_invoice.descriptionText
19 | import kotlinx.android.synthetic.main.activity_build_invoice.expiredText
20 | import kotlinx.android.synthetic.main.activity_build_invoice.expiredTitle
21 | import kotlinx.android.synthetic.main.activity_build_invoice.labelText
22 | import kotlinx.android.synthetic.main.activity_build_invoice.lightningButton
23 | import kotlinx.android.synthetic.main.activity_build_invoice.qrImage
24 | import kotlinx.android.synthetic.main.activity_build_invoice.shareButton
25 | import org.jetbrains.anko.contentView
26 | import org.jetbrains.anko.doAsync
27 | import org.json.JSONObject
28 | import java.lang.Exception
29 | import java.text.SimpleDateFormat
30 | import java.util.Date
31 |
32 | class BuildInvoiceActivity : AppCompatActivity() {
33 |
34 | private val cli = LightningCli()
35 | private var decoded: JSONObject? = null
36 |
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 | super.onCreate(savedInstanceState)
39 | setContentView(R.layout.activity_build_invoice)
40 | balanceText.requestFocus()
41 |
42 | lightningButton.setOnClickListener {
43 | val amount = balanceText.text.toString()
44 | var sat = if (amount.isEmpty()) "any" else (amount.toDouble() * 1000).toLong().toString()
45 | doAsync { invoice(sat, labelText.text.toString(), descriptionText.text.toString()) }
46 | }
47 |
48 | btcButton.setOnClickListener {
49 | doAsync { generate() }
50 | }
51 | }
52 |
53 | fun generate() {
54 | try {
55 | val res = cli.exec(this, arrayOf("newaddr"), true).toJSONObject()
56 | runOnUiThread { showAddress(res["address"] as String) }
57 | } catch (e: Exception) {
58 | runOnUiThread {
59 | Toast.makeText(
60 | this,
61 | e.localizedMessage,
62 | Toast.LENGTH_LONG
63 | ).show()
64 | }
65 | }
66 | }
67 |
68 | fun invoice(amount: String, label: String, description: String) {
69 | try {
70 | val res = cli.exec(
71 | this,
72 | arrayOf(
73 | "invoice",
74 | amount,
75 | label,
76 | description
77 | ),
78 | true
79 | ).toJSONObject()
80 | runOnUiThread { showInvoice(res["bolt11"] as String) }
81 | } catch (e: Exception) {
82 | runOnUiThread {
83 | Toast.makeText(
84 | this,
85 | e.localizedMessage,
86 | Toast.LENGTH_LONG
87 | ).show()
88 | }
89 | }
90 | }
91 |
92 | fun showAddress(address: String) {
93 | labelText.visibility = View.GONE
94 | descriptionText.visibility = View.GONE
95 | btclnLayout.visibility = View.GONE
96 | copyShareLayout.visibility = View.VISIBLE
97 |
98 | copyButton.setOnClickListener { UI.copyToClipboard(this, "address", address) }
99 | shareButton.setOnClickListener { UI.share(this, "address", address) }
100 |
101 | // hide keyboard
102 | val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
103 | inputMethodManager.hideSoftInputFromWindow(contentView?.windowToken, 0)
104 |
105 | // show bolt11
106 | val qr = UI.getQrCode(address)
107 | qrImage.setImageBitmap(qr)
108 |
109 | expiredTitle.visibility = View.VISIBLE
110 | expiredText.visibility = View.VISIBLE
111 | expiredTitle.text = "Address"
112 | expiredText.text = address
113 | }
114 |
115 | fun showInvoice(bolt11: String) {
116 | balanceText.isEnabled = false
117 | labelText.isEnabled = false
118 | descriptionText.isEnabled = false
119 | labelText.visibility = View.GONE
120 | btclnLayout.visibility = View.GONE
121 | copyShareLayout.visibility = View.VISIBLE
122 |
123 | copyButton.setOnClickListener { UI.copyToClipboard(this, "bolt11", bolt11) }
124 | shareButton.setOnClickListener { UI.share(this, "bolt11", bolt11) }
125 |
126 | // hide keyboard
127 | val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
128 | inputMethodManager.hideSoftInputFromWindow(contentView?.windowToken, 0)
129 |
130 | // show bolt11
131 | val qr = UI.getQrCode(bolt11)
132 | qrImage.setImageBitmap(qr)
133 |
134 | // get expired time
135 | doAsync { decodeInvoice(bolt11) }
136 | }
137 |
138 | private fun decodeInvoice(bolt11: String) {
139 | val res = cli.exec(this, arrayOf("decodepay", bolt11), true)
140 | .toJSONObject()
141 | decoded = res
142 | val created_at = res["created_at"] as Int
143 | val expiry = res["expiry"] as Int
144 | val date = Date(created_at * 1000L + expiry)
145 | runOnUiThread {
146 | expiredTitle.visibility = View.VISIBLE
147 | expiredText.visibility = View.VISIBLE
148 | expiredText.text = SimpleDateFormat("HH:mm:ss, dd MMM yyyy").format(date)
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/activities/ChannelsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.activities
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.Menu
6 | import android.view.MenuItem
7 | import android.view.ViewGroup
8 | import android.widget.ProgressBar
9 | import android.widget.TextView
10 | import android.widget.Toast
11 | import androidx.appcompat.app.AppCompatActivity
12 | import androidx.recyclerview.widget.LinearLayoutManager
13 | import androidx.recyclerview.widget.RecyclerView
14 | import com.lvaccaro.lamp.LightningCli
15 | import com.lvaccaro.lamp.R
16 | import com.lvaccaro.lamp.fragments.ChannelFragment
17 | import com.lvaccaro.lamp.fragments.FundChannelFragment
18 | import com.lvaccaro.lamp.toJSONObject
19 | import kotlinx.android.synthetic.main.activity_channels.toolbar
20 | import org.jetbrains.anko.doAsync
21 | import org.json.JSONArray
22 | import org.json.JSONObject
23 | import java.lang.Exception
24 | import kotlin.collections.ArrayList
25 |
26 | typealias ChannelClickListener = (JSONObject) -> Unit
27 |
28 | class ChannelAdapter(
29 | val list: ArrayList,
30 | private val onClickListener: ChannelClickListener
31 | ) :
32 | RecyclerView.Adapter() {
33 |
34 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelViewHolder {
35 | val inflater = LayoutInflater.from(parent.context)
36 | return ChannelViewHolder(inflater, parent)
37 | }
38 |
39 | override fun onBindViewHolder(holder: ChannelViewHolder, position: Int) {
40 | val item: JSONObject = list[position]
41 | holder.bind(item)
42 | holder.itemView.setOnClickListener { onClickListener(item) }
43 | }
44 |
45 | override fun getItemCount(): Int = list.size
46 | }
47 |
48 | class ChannelViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
49 | RecyclerView.ViewHolder(inflater.inflate(R.layout.list_channel, parent, false)) {
50 |
51 | fun bind(channel: JSONObject) {
52 | val cid = channel.getString("channel_id")
53 | val msatoshi_to_us = channel.getDouble("msatoshi_to_us") / 1000
54 | val msatoshi_total = channel.getDouble("msatoshi_total") / 1000
55 | itemView.findViewById(R.id.cid).text = "CID: ${cid.subSequence(0,8)}..."
56 | itemView.findViewById(R.id.status).text = channel.getString("state")
57 | itemView.findViewById(R.id.mysats).text = "My balance: $msatoshi_to_us sat"
58 | itemView.findViewById(R.id.availablesats).text = "Available to receive: $msatoshi_total sat"
59 | itemView.findViewById(R.id.progressBar).apply {
60 | max = msatoshi_total.toInt()
61 | progress = msatoshi_to_us.toInt()
62 | }
63 | }
64 | }
65 |
66 | class ChannelsActivity : AppCompatActivity() {
67 |
68 | lateinit var recyclerView: RecyclerView
69 |
70 | override fun onCreate(savedInstanceState: Bundle?) {
71 | super.onCreate(savedInstanceState)
72 | setContentView(R.layout.activity_channels)
73 | setSupportActionBar(toolbar)
74 |
75 | recyclerView = findViewById(R.id.recycler_view)
76 | recyclerView.layoutManager = LinearLayoutManager(this)
77 | recyclerView.adapter = ChannelAdapter(
78 | ArrayList(),
79 | this::showChannel
80 | )
81 |
82 | doAsync { refresh() }
83 | }
84 |
85 | private fun showChannel(channel: JSONObject) {
86 | val bundle = Bundle()
87 | bundle.putString("channel", channel.toString())
88 | val fragment = ChannelFragment()
89 | fragment.arguments = bundle
90 | fragment.show(supportFragmentManager, "ChannelFragment")
91 | }
92 |
93 | private fun refresh() {
94 | try {
95 | val res = LightningCli().exec(
96 | this@ChannelsActivity,
97 | arrayOf("listpeers"),
98 | true
99 | ).toJSONObject()
100 |
101 | val channels = ArrayList()
102 | val peers = res["peers"] as JSONArray
103 | for (i in 0 until peers.length()) {
104 | val peer = peers.get(i) as? JSONObject
105 | val peerChannels = peer?.get("channels") as JSONArray
106 | for (j in 0 until peerChannels.length()) {
107 | val channel = peerChannels.get(j) as JSONObject
108 | channel.put("peer_id", peer.getString("id"))
109 | channels.add(channel)
110 | }
111 | }
112 |
113 | runOnUiThread {
114 | val total = channels.sumBy { it.getInt("msatoshi_to_us") / 1000 }
115 | findViewById(R.id.total_text).text = "$total sat in channels"
116 | val recyclerView = findViewById(R.id.recycler_view)
117 | val adapter = recyclerView.adapter as ChannelAdapter
118 | adapter.apply {
119 | list.clear()
120 | list.addAll(channels)
121 | notifyDataSetChanged()
122 | }
123 | }
124 | } catch (e: Exception) {
125 | runOnUiThread {
126 | Toast.makeText(
127 | this@ChannelsActivity,
128 | "Channel funded",
129 | Toast.LENGTH_LONG
130 | ).show()
131 | }
132 | }
133 | }
134 |
135 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
136 | // Inflate the menu; this adds items to the action bar if it is present.
137 | menuInflater.inflate(R.menu.menu_channels, menu)
138 | return true
139 | }
140 |
141 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
142 | return when (item.itemId) {
143 | R.id.action_add -> {
144 | val bottomSheetDialog =
145 | FundChannelFragment()
146 | bottomSheetDialog.show(supportFragmentManager, "Fund channel")
147 | true
148 | }
149 | else -> super.onOptionsItemSelected(item)
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/activities/ConsoleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.activities
2 |
3 | import android.os.AsyncTask
4 | import android.os.Bundle
5 | import android.widget.EditText
6 | import android.widget.ImageButton
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.lvaccaro.lamp.LightningCli
9 | import com.lvaccaro.lamp.R
10 | import com.lvaccaro.lamp.toText
11 | import java.lang.Exception
12 |
13 | class ConsoleActivity : AppCompatActivity() {
14 |
15 | private lateinit var editTextResult: EditText
16 | private lateinit var editTextCmd: EditText
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContentView(R.layout.activity_console)
21 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
22 |
23 | editTextCmd = findViewById(R.id.edit_text_console_message)
24 | editTextResult = findViewById(R.id.edit_text_result_command)
25 |
26 | findViewById(R.id.send).setOnClickListener {
27 | val textContent = editTextCmd.text.toString()
28 | if (textContent != "") {
29 | if (textContent.equals("clean", true)) {
30 | // Command to clean console
31 | editTextResult.setText("")
32 | editTextCmd.setText("")
33 | } else {
34 | CommandTask().execute(textContent)
35 | }
36 | }
37 | }
38 | }
39 |
40 | inner class CommandTask : AsyncTask() {
41 |
42 | lateinit var params: String
43 | override fun onPreExecute() {
44 | super.onPreExecute()
45 | editTextCmd.setText("")
46 | }
47 |
48 | override fun doInBackground(vararg params: String): String {
49 | this.params = params[0]
50 | val args = params[0].split(" ").toTypedArray()
51 | try {
52 | return LightningCli()
53 | .exec(this@ConsoleActivity, args, true).toText()
54 | } catch (e: Exception) {
55 | e.printStackTrace()
56 | return e.localizedMessage ?: "Error, params: $args"
57 | }
58 | }
59 |
60 | override fun onPostExecute(result: String?) {
61 | super.onPostExecute(result)
62 | editTextResult.append("$ lightning-cli $params\n")
63 | editTextResult.append(result ?: "")
64 | editTextResult.append("\n")
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/activities/LogActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.activities
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.text.method.ScrollingMovementMethod
6 | import android.util.Log
7 | import android.view.Menu
8 | import android.view.MenuItem
9 | import android.view.View
10 | import android.widget.EditText
11 | import android.widget.ProgressBar
12 | import android.widget.Toast
13 | import androidx.appcompat.app.AppCompatActivity
14 | import com.lvaccaro.lamp.R
15 | import com.lvaccaro.lamp.rootDir
16 | import com.lvaccaro.lamp.utils.UI
17 | import kotlinx.android.synthetic.main.activity_log.*
18 | import org.jetbrains.anko.doAsync
19 | import java.io.File
20 | import java.io.RandomAccessFile
21 |
22 | class LogActivity : AppCompatActivity() {
23 |
24 | companion object {
25 | val TAG = LogActivity::class.java.canonicalName
26 | }
27 |
28 | private var daemon = "lightningd"
29 | private val maxBufferToLoad = 200
30 | private var sizeBuffer = 0
31 |
32 | // UI component
33 | private lateinit var editText: EditText
34 | private lateinit var progressBar: ProgressBar
35 |
36 | override fun onCreate(savedInstanceState: Bundle?) {
37 | super.onCreate(savedInstanceState)
38 | setContentView(R.layout.activity_log)
39 | setSupportActionBar(toolbar)
40 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
41 | editText = findViewById(R.id.edit_text_container_log)
42 | editText.apply {
43 | movementMethod = ScrollingMovementMethod()
44 | isVerticalScrollBarEnabled = true
45 | }
46 | progressBar = findViewById(R.id.loading_status)
47 | progressBar.max = maxBufferToLoad
48 | readLog()
49 | }
50 |
51 | override fun onResume() {
52 | super.onResume()
53 | readLog()
54 | }
55 |
56 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
57 | menuInflater.inflate(R.menu.menu_log, menu)
58 | return true
59 | }
60 |
61 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
62 | return when (item.itemId) {
63 | R.id.action_lightning -> {
64 | daemon = "lightningd"
65 | readLog()
66 | true
67 | }
68 | R.id.action_tor -> {
69 | daemon = "tor"
70 | readLog()
71 | true
72 | }
73 | R.id.action_share_log -> {
74 | shareLogByIntent()
75 | true
76 | }
77 | else -> super.onOptionsItemSelected(item)
78 | }
79 | }
80 |
81 | private fun shareLogByIntent() {
82 | doAsync {
83 | val shareIntent = Intent(Intent.ACTION_SEND).apply {
84 | type = "text/plain"
85 | val logFile = File(rootDir(), "$daemon.log")
86 | if (!logFile.exists()) {
87 | runOnUiThread {
88 | UI.showMessageOnToast(applicationContext, "No log file found")
89 | }
90 | return@doAsync
91 | }
92 | val body = StringBuilder()
93 | body.append("------- LOG $daemon.log CONTENT ----------").append("\n")
94 | val lines = logFile.readLines()
95 | val sizeNow = lines.size
96 | var difference = 0
97 | if (sizeNow > 450) sizeNow - 200
98 | for (at in difference until sizeNow) {
99 | val line = lines[at]
100 | body.append(line).append("\n")
101 | }
102 | putExtra(Intent.EXTRA_TEXT, body.toString())
103 | }
104 | if (shareIntent.resolveActivity(packageManager) != null) {
105 | startActivity(Intent.createChooser(shareIntent, null))
106 | return@doAsync
107 | }
108 | runOnUiThread {
109 | UI.showMessageOnToast(applicationContext, "Intent resolving error")
110 | }
111 | }
112 | }
113 |
114 | private fun readLog() {
115 | title = "Log $daemon"
116 | val logFile = File(rootDir(), "$daemon.log")
117 | if (!logFile.exists()) {
118 | UI.showMessageOnToast(this, "No log file found")
119 | return
120 | }
121 | editText.setText("")
122 | doAsync {
123 | runOnUiThread {
124 | Toast.makeText(this@LogActivity, "Loading", Toast.LENGTH_SHORT).show()
125 | progressBar.visibility = View.VISIBLE
126 | }
127 | val randomAccessFile = RandomAccessFile(logFile, "r")
128 | read(randomAccessFile, editText)
129 | }
130 | }
131 |
132 | private fun read(randomAccessFile: RandomAccessFile, et: EditText) {
133 | Log.d(TAG, "Start to read the file with RandomAccessFile")
134 | // Set the position at the end of the file
135 | val fileSize = randomAccessFile.length() - 1
136 | randomAccessFile.seek(fileSize)
137 | // The maximum dimension of this object is one line
138 | val lineBuilder = StringBuilder()
139 | // This contains the each line of the logger, the line of the logger are fixed
140 | // to the propriety *maxBufferToLoad*
141 | val logBuilder = StringBuilder()
142 | for (pointer in fileSize downTo 1) {
143 | randomAccessFile.seek(pointer)
144 | val character = randomAccessFile.read().toChar()
145 | lineBuilder.append(character)
146 | if (character.equals('\n', false)) {
147 | sizeBuffer++
148 | logBuilder.append(lineBuilder.reverse().toString())
149 | lineBuilder.clear()
150 | runOnUiThread {
151 | this.progressBar.progress = sizeBuffer
152 | }
153 | if (sizeBuffer == maxBufferToLoad) break
154 | }
155 | }
156 | Log.d(TAG, "Print lines to EditText")
157 | val lines = logBuilder.toString().split("\n").reversed()
158 | runOnUiThread {
159 | lines.forEach {
160 | if (it.trim().isNotEmpty() && it.length < 400)
161 | et.append(it.plus("\n"))
162 | }
163 | progressBar.visibility = View.GONE
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/activities/ScanActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.activities
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.ClipboardManager
6 | import android.content.Context
7 | import android.content.pm.PackageManager
8 | import android.os.Build
9 | import android.os.Bundle
10 | import android.util.Log
11 | import android.view.Menu
12 | import android.view.MenuItem
13 | import androidx.appcompat.app.AppCompatActivity
14 | import com.google.zxing.Result
15 | import com.lvaccaro.lamp.R
16 | import me.dm7.barcodescanner.zxing.ZXingScannerView
17 |
18 | class ScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
19 | private val TAG = "ScanActivity"
20 | lateinit var mScannerView: ZXingScannerView
21 |
22 | public override fun onCreate(state: Bundle?) {
23 | super.onCreate(state)
24 | supportActionBar?.setDisplayShowHomeEnabled(true)
25 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
26 |
27 | mScannerView = ZXingScannerView(this)
28 | setContentView(mScannerView)
29 | mScannerView.setAutoFocus(true)
30 | mScannerView.setAspectTolerance(0.5f)
31 |
32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
33 | checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
34 | ) {
35 | requestPermissions((arrayOf(Manifest.permission.CAMERA)), 101)
36 | return
37 | }
38 | }
39 |
40 | public override fun onResume() {
41 | super.onResume()
42 | mScannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
43 | mScannerView.startCamera()
44 | }
45 |
46 | public override fun onPause() {
47 | super.onPause()
48 | mScannerView.stopCamera()
49 | }
50 |
51 | override fun onStop() {
52 | super.onStop()
53 | mScannerView.stopCamera()
54 | }
55 |
56 | override fun onRequestPermissionsResult(
57 | requestCode: Int,
58 | permissions: Array,
59 | grantResults: IntArray
60 | ) {
61 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
62 | if (requestCode == 101 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
63 | mScannerView?.startCamera()
64 | }
65 | }
66 |
67 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
68 | menuInflater.inflate(R.menu.menu_scan, menu)
69 | return true
70 | }
71 |
72 | override fun handleResult(rawResult: Result?) {
73 | Log.d(TAG, rawResult?.text)
74 | val result = rawResult?.text ?: ""
75 | if (result.isEmpty()) {
76 | mScannerView.resumeCameraPreview(this)
77 | return
78 | }
79 |
80 | runOnUiThread {
81 | val intent = intent
82 | intent.putExtra("text", result)
83 | setResult(Activity.RESULT_OK, intent)
84 | finish()
85 | }
86 | }
87 |
88 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
89 | return when (item.itemId) {
90 | android.R.id.home -> {
91 | onBackPressed()
92 | return true
93 | }
94 | R.id.action_paste -> {
95 | val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
96 | val clip = clipboard.primaryClip
97 | val item = clip?.getItemAt(0)
98 | val text = item?.text.toString()
99 | val intent = intent
100 | intent.putExtra("text", text)
101 | setResult(Activity.RESULT_OK, intent)
102 | finish()
103 | true
104 | }
105 | else -> super.onOptionsItemSelected(item)
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/activities/UriResultActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.activities
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AlertDialog
8 | import androidx.appcompat.app.AppCompatActivity
9 | import com.lvaccaro.lamp.LightningCli
10 | import com.lvaccaro.lamp.fragments.FundChannelFragment
11 | import com.lvaccaro.lamp.fragments.WithdrawFragment
12 | import com.lvaccaro.lamp.services.CLightningException
13 | import com.lvaccaro.lamp.toJSONObject
14 | import com.lvaccaro.lamp.utils.LampKeys
15 | import com.lvaccaro.lamp.utils.Validator
16 | import org.json.JSONObject
17 | import java.lang.Exception
18 |
19 | open class UriResultActivity() : AppCompatActivity() {
20 |
21 | val cli = LightningCli()
22 | val TAG = "UriResultActivity"
23 |
24 | fun parse(text: String) {
25 | // Check is if a Bitcoin payment
26 | val isBitcoinAddress = Validator.isBitcoinAddress(text)
27 | val isBitcoinURI = Validator.isBitcoinURL(text)
28 | val isBoltPayment = Validator.isBolt11(text)
29 | val isURINodeConnect = Validator.isLightningNodURI(text)
30 | var resultCommand: JSONObject? = null
31 | try {
32 | if (isBitcoinAddress) {
33 | val parm = HashMap()
34 | Log.d(TAG, "*** Bitcoin address")
35 | parm.put(LampKeys.ADDRESS_KEY, text)
36 | runOnUiThread {
37 | showWithdraw(parm)
38 | }
39 | } else if (isBitcoinURI) {
40 | Log.d(TAG, "*** Bitcoin URI")
41 | val result = Validator.doParseBitcoinURL(text)
42 | runOnUiThread { showWithdraw(result) }
43 | } else if (isBoltPayment) {
44 | Log.d(TAG, "*** Bolt payment")
45 | val bolt11 = Validator.getBolt11(text)
46 | runOnUiThread { showDecodePay(bolt11) }
47 | } else if (isURINodeConnect) {
48 | Log.d(TAG, "*** Node URI connect $text")
49 | resultCommand = runCommandCLightning(LampKeys.CONNECT_COMMAND, arrayOf(text))
50 | runOnUiThread { showConnect(resultCommand!!["id"].toString()) }
51 | } else {
52 | resultCommand = JSONObject()
53 | resultCommand.put("message", "No action found")
54 | }
55 | } catch (ex: CLightningException) {
56 | // FIXME: This have sense?
57 | Log.e(TAG, ex.localizedMessage)
58 | resultCommand = JSONObject(ex.localizedMessage)
59 | ex.printStackTrace()
60 | } finally {
61 | if (resultCommand == null) {
62 | return
63 | }
64 | var message = ""
65 | if (resultCommand.has(LampKeys.MESSAGE_JSON_KEY)) {
66 | message = resultCommand.get(LampKeys.MESSAGE_JSON_KEY).toString()
67 | } else if (resultCommand.has("id")) {
68 | message = "Connected to node"
69 | }
70 | runOnUiThread {
71 | showMessageOnToast(
72 | message,
73 | Toast.LENGTH_LONG
74 | )
75 | }
76 | }
77 | }
78 |
79 | fun runCommandCLightning(command: String, parameter: Array): JSONObject {
80 | try {
81 | val payload = ArrayList()
82 | payload.add(command)
83 | payload.addAll(parameter)
84 | payload.forEach { Log.d(TAG, "***** $it") }
85 | val rpcResult =
86 | cli.exec(this, payload.toTypedArray()).toJSONObject()
87 | Log.d(TAG, rpcResult.toString())
88 | return rpcResult
89 | } catch (ex: Exception) {
90 | // FIXME: This have sense?
91 | val answer = JSONObject(ex.localizedMessage)
92 | showMessageOnToast(answer[LampKeys.MESSAGE_JSON_KEY].toString(), Toast.LENGTH_LONG)
93 | throw CLightningException(ex.cause)
94 | }
95 | }
96 |
97 | private fun showDecodePay(bolt11: String) {
98 | val intent = Intent(this, SendActivity::class.java)
99 | intent.putExtra("bolt11", bolt11)
100 | startActivity(intent)
101 | }
102 |
103 | private fun showConnect(id: String) {
104 | AlertDialog.Builder(this)
105 | .setTitle("connect")
106 | .setMessage(id)
107 | .setPositiveButton("fund channel") { _, _ ->
108 | // Open fund channel fragment
109 | val bottomSheetDialog =
110 | FundChannelFragment()
111 | val args = Bundle()
112 | args.putString("uri", id)
113 | bottomSheetDialog.arguments = args
114 | bottomSheetDialog.show(supportFragmentManager, "Fund channel")
115 | }
116 | .setNegativeButton("cancel") { _, _ -> }
117 | .show()
118 | }
119 |
120 | private fun showWithdraw(param: HashMap?) {
121 | val bottomSheetDialog = WithdrawFragment()
122 | val bundle = Bundle()
123 | val address = param?.get(LampKeys.ADDRESS_KEY) ?: ""
124 | val networkCheck = Validator.isCorrectNetwork(cli, this.applicationContext, address)
125 | if (networkCheck != null) {
126 | showMessageOnToast(networkCheck, Toast.LENGTH_LONG)
127 | return
128 | }
129 | var amount = ""
130 | if (param!!.contains(LampKeys.AMOUNT_KEY)) {
131 | // FIXME(vincenzopalazzo): create a converted class to set the set the correct ammounet.
132 | // For instance, Validator.toMilliSatoshi()
133 | amount = (param!![LampKeys.AMOUNT_KEY]!!.toDouble() * 100000000).toLong().toString()
134 | }
135 | bundle.putString(LampKeys.ADDRESS_KEY, address)
136 | bundle.putString(LampKeys.AMOUNT_KEY, amount)
137 | bottomSheetDialog.arguments = bundle
138 | bottomSheetDialog.show(supportFragmentManager, "WithdrawFragment")
139 | }
140 |
141 | protected fun showMessageOnToast(message: String, duration: Int = Toast.LENGTH_LONG) {
142 | if (message.isEmpty()) return
143 | runOnUiThread {
144 | Toast.makeText(
145 | this, message,
146 | duration
147 | ).show()
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/adapters/BalanceAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import android.widget.TextView
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.lvaccaro.lamp.R
8 |
9 | data class Balance(val title: String, val subtitle: String, val value: String)
10 |
11 | typealias BalanceClickListener = (Int) -> Unit
12 |
13 | class BalanceAdapter(
14 | val list: ArrayList,
15 | private val onClickListener: BalanceClickListener?
16 | ) :
17 | RecyclerView.Adapter() {
18 |
19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalanceViewHolder {
20 | val inflater = LayoutInflater.from(parent.context)
21 | return BalanceViewHolder(inflater, parent)
22 | }
23 |
24 | override fun onBindViewHolder(holder: BalanceViewHolder, position: Int) {
25 | val item: Balance = list[position]
26 | holder.bind(item.title, item.subtitle, item.value)
27 | holder.itemView.setOnClickListener { onClickListener?.invoke(position) }
28 | }
29 |
30 | override fun getItemCount(): Int = list.size
31 | }
32 |
33 | class BalanceViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
34 | RecyclerView.ViewHolder(inflater.inflate(R.layout.list_balance, parent, false)) {
35 | fun bind(title: String, subtitle: String, value: String) {
36 | itemView.findViewById(R.id.title).text = title
37 | itemView.findViewById(R.id.subtitle).text = subtitle
38 | itemView.findViewById(R.id.value).text = value
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/adapters/HashMapAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import android.widget.TextView
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.lvaccaro.lamp.utils.UI
8 | import org.json.JSONObject
9 |
10 | class HashMapAdapter(val map: LinkedHashMap) :
11 | RecyclerView.Adapter() {
12 |
13 | class ViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
14 | RecyclerView.ViewHolder(inflater.inflate(android.R.layout.two_line_list_item, parent, false)) {
15 |
16 | fun bind(key: String, value: String) {
17 | itemView.findViewById(android.R.id.text1).text = key
18 | itemView.findViewById(android.R.id.text2).text = value
19 | }
20 | }
21 |
22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
23 | val inflater = LayoutInflater.from(parent.context)
24 | return ViewHolder(
25 | inflater,
26 | parent
27 | )
28 | }
29 |
30 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
31 | val key = map.keys.toList()[position]
32 | val value = map.values.toList()[position]
33 | holder.bind(key, value)
34 | holder.itemView.setOnClickListener {
35 | UI.copyToClipboard(
36 | holder.itemView.context,
37 | key,
38 | value
39 | )
40 | }
41 | }
42 |
43 | override fun getItemCount(): Int = map.count()
44 |
45 | companion object {
46 | fun from(json: JSONObject): LinkedHashMap {
47 | val temp = json.keys()
48 | val hashMap = LinkedHashMap()
49 | while (temp.hasNext()) {
50 | val key = temp.next()
51 | val value = json[key].toString()
52 | hashMap.put(key, value)
53 | }
54 | return hashMap
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/fragments/ChannelFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.Toast
9 | import androidx.recyclerview.widget.LinearLayoutManager
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
12 | import com.lvaccaro.lamp.LightningCli
13 | import com.lvaccaro.lamp.R
14 | import com.lvaccaro.lamp.adapters.HashMapAdapter
15 | import com.lvaccaro.lamp.toJSONObject
16 | import com.lvaccaro.lamp.utils.UI
17 | import org.jetbrains.anko.doAsync
18 | import org.json.JSONObject
19 | import java.lang.Exception
20 |
21 | class ChannelFragment : BottomSheetDialogFragment() {
22 |
23 | override fun onCreateView(
24 | inflater: LayoutInflater,
25 | container: ViewGroup?,
26 | savedInstanceState: Bundle?
27 | ): View? {
28 | val view = inflater.inflate(R.layout.fragment_channel, container, false)
29 | val data = arguments?.getString("channel") ?: ""
30 | val channel = JSONObject(data)
31 |
32 | val recyclerView = view.findViewById(R.id.recycler_view)
33 | recyclerView.apply {
34 | layoutManager = LinearLayoutManager(context)
35 | adapter = HashMapAdapter(
36 | HashMapAdapter.from(channel)
37 | )
38 | }
39 |
40 | val closeButton = view.findViewById(R.id.close_button)
41 | closeButton.apply {
42 | isEnabled = channel.getString("state") == "CHANNELD_NORMAL"
43 | setOnClickListener { doAsync { close(channel.getString("channel_id")) } }
44 | }
45 | return view
46 | }
47 |
48 | private fun close(cid: String) {
49 | val context = activity!!
50 | try {
51 | LightningCli().exec(
52 | context,
53 | arrayOf("close", cid),
54 | true
55 | ).toJSONObject()
56 | context.runOnUiThread {
57 | Toast.makeText(context, "Channel closing...", Toast.LENGTH_LONG).show()
58 | dismiss()
59 | }
60 | } catch (e: Exception) {
61 | context.runOnUiThread {
62 | UI.textAlertDialog(context, "Error", e.localizedMessage)
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/fragments/DecodedInvoiceFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.Toast
9 | import androidx.recyclerview.widget.LinearLayoutManager
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
12 | import com.lvaccaro.lamp.LightningCli
13 | import com.lvaccaro.lamp.R
14 | import com.lvaccaro.lamp.adapters.HashMapAdapter
15 | import com.lvaccaro.lamp.toJSONObject
16 | import com.lvaccaro.lamp.utils.UI
17 | import org.jetbrains.anko.doAsync
18 | import java.lang.Exception
19 |
20 | class DecodedInvoiceFragment : BottomSheetDialogFragment() {
21 |
22 | private val cli = LightningCli()
23 |
24 | override fun onCreateView(
25 | inflater: LayoutInflater,
26 | container: ViewGroup?,
27 | savedInstanceState: Bundle?
28 | ): View? {
29 | val view = inflater.inflate(R.layout.fragment_decoded_invoice, container, false)
30 | val bolt11 = arguments?.getString("bolt11") ?: ""
31 |
32 | val recyclerView = view.findViewById(R.id.recycler_view)
33 | recyclerView.layoutManager = LinearLayoutManager(context)
34 |
35 | val payButton = view.findViewById(R.id.pay_button)
36 | payButton.setOnClickListener { doAsync { pay(bolt11) } }
37 |
38 | val context = activity!!
39 | doAsync {
40 | try {
41 | val res = cli.exec(context, arrayOf("decodepay", bolt11), true)
42 | .toJSONObject()
43 | context.runOnUiThread {
44 | recyclerView.adapter =
45 | HashMapAdapter(
46 | HashMapAdapter.from(res)
47 | )
48 | }
49 | } catch (e: Exception) {
50 | context.runOnUiThread {
51 | UI.textAlertDialog(context, "Error", e.localizedMessage)
52 | }
53 | }
54 | }
55 | return view
56 | }
57 |
58 | private fun pay(bolt11: String) {
59 | // Pay invoice
60 | val context = activity!!
61 | try {
62 | cli.exec(context, arrayOf("pay", bolt11), true)
63 | .toJSONObject()
64 | context.runOnUiThread {
65 | Toast.makeText(context, "Invoice paid", Toast.LENGTH_LONG).show()
66 | dismiss()
67 | }
68 | } catch (e: Exception) {
69 | context.runOnUiThread {
70 | UI.textAlertDialog(context, "Error", e.localizedMessage)
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/fragments/FundChannelFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.Toast
9 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
10 | import com.google.android.material.switchmaterial.SwitchMaterial
11 | import com.google.android.material.textfield.TextInputEditText
12 | import com.lvaccaro.lamp.LightningCli
13 | import com.lvaccaro.lamp.R
14 | import com.lvaccaro.lamp.toJSONObject
15 | import com.lvaccaro.lamp.utils.UI
16 | import org.jetbrains.anko.doAsync
17 | import java.lang.Exception
18 |
19 | class FundChannelFragment : BottomSheetDialogFragment() {
20 |
21 | override fun onCreateView(
22 | inflater: LayoutInflater,
23 | container: ViewGroup?,
24 | savedInstanceState: Bundle?
25 | ): View? {
26 | val view = inflater.inflate(R.layout.fragment_fundchannel, container, false)
27 | val uri = arguments?.getString("uri")
28 | view.findViewById(R.id.node_text).setText(uri ?: "")
29 | view.findViewById(R.id.button).setOnClickListener {
30 | val uri = view.findViewById(R.id.node_text).text.toString()
31 | val isMax = view.findViewById(R.id.fundmax_switch).isChecked
32 | var isPrivate = view.findViewById(R.id.private_switch).isChecked
33 | val satoshi = view.findViewById(R.id.satoshi_text).text.toString()
34 | doAsync {
35 | fund(
36 | uri,
37 | if (isMax) "all" else satoshi,
38 | "normal",
39 | if (isPrivate) "false" else "true"
40 | )
41 | }
42 | }
43 | return view
44 | }
45 |
46 | fun fund(id: String, amount: String, feerate: String, announce: String) {
47 | try {
48 | LightningCli().exec(
49 | context!!,
50 | arrayOf("fundchannel", id, amount, feerate, announce),
51 | true
52 | ).toJSONObject()
53 | activity?.runOnUiThread {
54 | Toast.makeText(
55 | context,
56 | "Channel funded",
57 | Toast.LENGTH_LONG
58 | ).show()
59 | dismiss()
60 | }
61 | } catch (e: Exception) {
62 | activity?.runOnUiThread {
63 | UI.textAlertDialog(context!!, "Warning", e.localizedMessage)
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/fragments/PeerInfoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.ImageView
9 | import android.widget.TextView
10 | import android.widget.Toast
11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
12 | import com.lvaccaro.lamp.LightningCli
13 | import com.lvaccaro.lamp.R
14 | import com.lvaccaro.lamp.toJSONObject
15 | import com.lvaccaro.lamp.utils.UI
16 | import org.jetbrains.anko.doAsync
17 | import org.json.JSONArray
18 | import org.json.JSONObject
19 | import java.lang.Exception
20 |
21 | class PeerInfoFragment : BottomSheetDialogFragment() {
22 |
23 | var address = ""
24 |
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | val view = inflater.inflate(R.layout.fragment_peer_info, container, false)
31 | view.findViewById(R.id.copyButton).setOnClickListener {
32 | UI.copyToClipboard(context!!, "peer_node", address)
33 | }
34 | view.findViewById(R.id.shareButton).setOnClickListener {
35 | UI.share(context!!, "Peer node", address)
36 | }
37 | doAsync { getInfo(view) }
38 | return view
39 | }
40 |
41 | fun getInfo(view: View) {
42 | try {
43 | val res =
44 | LightningCli().exec(context!!, arrayOf("getinfo"), true).toJSONObject()
45 | val id = res["id"] as String
46 | val addresses = res["address"] as JSONArray
47 | // the node has an address? if not hide the UI node info
48 | address = id
49 | if (addresses.length() != 0) {
50 | val addressText = addresses[0] as JSONObject
51 | address = id + "@" + addressText.getString("address")
52 | }
53 |
54 | activity?.runOnUiThread {
55 | view.findViewById(R.id.peerText).text = address
56 | }
57 |
58 | val qrcode = UI.getQrCode(address)
59 | activity?.runOnUiThread {
60 | view.findViewById(R.id.peerImage).setImageBitmap(qrcode)
61 | }
62 | } catch (e: Exception) {
63 | activity?.runOnUiThread {
64 | Toast.makeText(
65 | activity,
66 | e.localizedMessage,
67 | Toast.LENGTH_LONG
68 | ).show()
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/fragments/RecyclerViewFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import androidx.recyclerview.widget.RecyclerView
10 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
11 | import com.lvaccaro.lamp.R
12 | import com.lvaccaro.lamp.adapters.HashMapAdapter
13 | import org.json.JSONObject
14 |
15 | class RecyclerViewFragment : BottomSheetDialogFragment() {
16 |
17 | override fun onCreateView(
18 | inflater: LayoutInflater,
19 | container: ViewGroup?,
20 | savedInstanceState: Bundle?
21 | ): View? {
22 | val view = inflater.inflate(R.layout.fragment_recyclerview, container, false)
23 | val title = arguments?.getString("title")
24 | val data = arguments?.getString("data")
25 | val recyclerView = view.findViewById(R.id.recycler_view)
26 | recyclerView.apply {
27 | layoutManager = LinearLayoutManager(context)
28 | adapter = HashMapAdapter(
29 | HashMapAdapter.from(JSONObject(data))
30 | )
31 | }
32 | view.findViewById(R.id.title).text = title
33 | return view
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/fragments/WithdrawFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.fragments
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import android.widget.TextView
11 | import android.widget.Toast
12 | import androidx.appcompat.app.AlertDialog
13 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
14 | import com.google.android.material.switchmaterial.SwitchMaterial
15 | import com.lvaccaro.lamp.LightningCli
16 | import com.lvaccaro.lamp.R
17 | import com.lvaccaro.lamp.toJSONObject
18 | import com.lvaccaro.lamp.utils.LampKeys
19 | import com.lvaccaro.lamp.utils.UI
20 | import org.jetbrains.anko.doAsync
21 | import java.lang.Exception
22 |
23 | class WithdrawFragment : BottomSheetDialogFragment() {
24 |
25 | val cli = LightningCli()
26 |
27 | override fun onCreateView(
28 | inflater: LayoutInflater,
29 | container: ViewGroup?,
30 | savedInstanceState: Bundle?
31 | ): View? {
32 | val view = inflater.inflate(R.layout.fragment_withdraw, container, false)
33 | val address = arguments?.getString(LampKeys.ADDRESS_KEY) ?: ""
34 | val satoshi = arguments?.getString(LampKeys.AMOUNT_KEY) ?: ""
35 | view.findViewById(R.id.addressText).text = address
36 | view.findViewById(R.id.satoshiText).text = satoshi
37 | view.findViewById(R.id.satoshiText).addTextChangedListener(object : TextWatcher {
38 | override fun afterTextChanged(s: Editable?) { }
39 |
40 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
41 | }
42 |
43 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
44 | view.findViewById(R.id.switch_send_all).isChecked = s?.length == 0
45 | }
46 | })
47 | view.findViewById(R.id.switch_send_all).isChecked = satoshi.trim().isEmpty()
48 | view.findViewById(R.id.confirmButton).setOnClickListener {
49 | var satoshi = view.findViewById(R.id.satoshiText).text.toString()
50 | val address = view.findViewById(R.id.addressText).text.toString()
51 |
52 | val sendAll = view.findViewById(R.id.switch_send_all).isChecked
53 | if (sendAll) satoshi = "all"
54 |
55 | doAsync { withdraw(address, satoshi) }
56 | }
57 | return view
58 | }
59 |
60 | fun withdraw(address: String, satoshi: String) {
61 | try {
62 | val res = cli.exec(
63 | context!!,
64 | arrayOf(
65 | "withdraw",
66 | address,
67 | satoshi
68 | ),
69 | true
70 | ).toJSONObject()
71 | val txid = res["txid"].toString()
72 | activity?.runOnUiThread {
73 | AlertDialog.Builder(context!!)
74 | .setTitle("Transaction Sent")
75 | .setMessage("Tx ID: $txid")
76 | .setPositiveButton("clipboard") { dialog, which ->
77 | UI.copyToClipboard(context!!, "address", address)
78 | }.setNegativeButton("continue") { dialog, which -> }
79 | .setCancelable(false)
80 | .show()
81 | }
82 | } catch (e: Exception) {
83 | activity?.runOnUiThread {
84 | Toast.makeText(
85 | activity,
86 | e.localizedMessage,
87 | Toast.LENGTH_LONG
88 | ).show()
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/BrokenStatus.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.util.Log
6 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
7 |
8 | class BrokenStatus : IEventHandler {
9 |
10 | companion object {
11 | val TAG = BrokenStatus::class.java.canonicalName
12 | const val NOTIFICATION: String = "NODE_NOTIFICATION_BROKEN"
13 | const val PATTERN = "**BROKEN**"
14 | }
15 |
16 | override fun doReceive(context: Context, information: String) {
17 | if (information.contains(PATTERN) && !information.contains("gossip_store_compact")) {
18 | Log.d(TAG, "****** Action received $NOTIFICATION ******")
19 | val intent = Intent()
20 | intent.action = NOTIFICATION
21 | intent.putExtra("message", "Node crash, check logs")
22 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/IEventHandler.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 |
5 | interface IEventHandler {
6 |
7 | fun doReceive(context: Context, information: String)
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/NewBlockHandler.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.util.Log
6 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
7 |
8 | class NewBlockHandler : IEventHandler {
9 |
10 | companion object {
11 | val TAG = NewChannelPayment::class.java.canonicalName
12 | val NOTIFICATION: String = "NODE_NOTIFICATION_NEW_BLOCK"
13 | val PATTERN = "lightningd: Adding block"
14 | }
15 |
16 | override fun doReceive(context: Context, information: String) {
17 | if (information.contains(PATTERN)) {
18 | Log.d(TAG, "****** Action received $NOTIFICATION ******")
19 | val regex = "Adding block [\\w|[^:]]+".toRegex()
20 | val height = regex.find(information)?.value?.split(" ")?.get(2)
21 | val intent = Intent()
22 | intent.action = NOTIFICATION
23 | intent.putExtra("message", "new block")
24 | intent.putExtra("height", height?.toInt() ?: 0)
25 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/NewChannelPayment.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
6 | import com.lvaccaro.lamp.utils.UI
7 | import org.jetbrains.anko.runOnUiThread
8 |
9 | class NewChannelPayment : IEventHandler {
10 |
11 | companion object {
12 | val TAG = NewChannelPayment::class.java.canonicalName
13 | val NOTIFICATION: String = "NODE_NOTIFICATION_FUNDCHANNEL"
14 | val PATTERN = "peer_out WIRE_FUNDING_LOCKED"
15 | }
16 |
17 | override fun doReceive(context: Context, information: String) {
18 | if (information.contains(PATTERN)) {
19 | val intent = Intent()
20 | intent.action = NOTIFICATION
21 | intent.putExtra("message", "New channel founded")
22 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
23 | context.runOnUiThread {
24 | UI.notification(context, "New channel founded", "")
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/NewTransaction.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
6 | import com.lvaccaro.lamp.utils.UI
7 | import org.jetbrains.anko.runOnUiThread
8 |
9 | class NewTransaction : IEventHandler {
10 |
11 | companion object {
12 | val TAG = NewChannelPayment::class.java.canonicalName
13 | val NOTIFICATION: String = "NODE_NOTIFICATION_NEW_TRANSACTION"
14 | val PATTERN = "wallet: Owning output"
15 | }
16 |
17 | override fun doReceive(context: Context, information: String) {
18 | if (information.contains(PATTERN)) {
19 | val regex = "wallet: Owning output \\d [\\w|[^:]]+".toRegex()
20 | val amount = regex.find(information)?.value?.split(" ")?.get(4)
21 | val intent = Intent()
22 | intent.action = NOTIFICATION
23 | intent.putExtra("message", "new transaction")
24 | intent.putExtra("amount", amount)
25 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
26 | context.runOnUiThread {
27 | UI.notification(context, "New onchain transaction", "$amount")
28 | UI.showMessageOnToast(context, "New onchain transaction $amount")
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/NodeUpHandler.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.util.Log
6 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
7 |
8 | /**
9 | * @author https://github.com/vincenzopalazzo
10 | */
11 | class NodeUpHandler : IEventHandler {
12 |
13 | companion object {
14 | val TAG = NodeUpHandler::class.java.canonicalName
15 | val NOTIFICATION: String = "NODE_UP_NOTIFICATION"
16 | val PATTERN_ONE = "lightningd: Server started with public key"
17 | }
18 |
19 | override fun doReceive(context: Context, information: String) {
20 | if (information.contains(PATTERN_ONE)) {
21 | Log.d(TAG, "****** Action received $NOTIFICATION ******")
22 | val intent = Intent()
23 | intent.action = NOTIFICATION
24 | intent.putExtra("message", "Node running")
25 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/PaidInvoice.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
6 | import com.lvaccaro.lamp.utils.UI
7 | import org.jetbrains.anko.runOnUiThread
8 |
9 | class PaidInvoice : IEventHandler {
10 |
11 | companion object {
12 | val TAG = NewChannelPayment::class.java.canonicalName
13 | val NOTIFICATION: String = "NODE_NOTIFICATION_PAID_INVOICE"
14 | val PATTERN = "lightningd: Resolved invoice"
15 | }
16 |
17 | override fun doReceive(context: Context, information: String) {
18 | if (information.contains(PATTERN)) {
19 | val intent = Intent()
20 | intent.action = NOTIFICATION
21 | intent.putExtra("message", "Paid invoice")
22 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
23 | context.runOnUiThread {
24 | UI.notification(context, "Paid invoice", "")
25 | UI.showMessageOnToast(context, "Paid invoice")
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/handlers/ShutdownNode.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.handlers
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.util.Log
6 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
7 |
8 | class ShutdownNode : IEventHandler {
9 |
10 | companion object {
11 | val TAG = ShutdownNode::class.java.canonicalName
12 | val NOTIFICATION: String = "SHUTDOWN_NODE_NOTIFICATION"
13 | val PATTERN = "lightningd: JSON-RPC shutdown"
14 | }
15 |
16 | override fun doReceive(context: Context, information: String) {
17 | if (information.contains(PATTERN)) {
18 | Log.d(NewChannelPayment.TAG, "****** Action received $NOTIFICATION ******")
19 | val intent = Intent()
20 | intent.action = NOTIFICATION
21 | intent.putExtra("message", "Shutdown")
22 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/services/CLightningException.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.services
2 |
3 | class CLightningException : Exception {
4 | constructor() : super() {}
5 | constructor(message: String?) : super(message) {}
6 | constructor(message: String?, cause: Throwable?) : super(message, cause) {}
7 | constructor(cause: Throwable?) : super(cause) {}
8 | protected constructor(
9 | message: String?,
10 | cause: Throwable?,
11 | enableSuppression: Boolean,
12 | writableStackTrace: Boolean
13 | ) : super(message, cause, enableSuppression, writableStackTrace) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/services/Globber.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.services
2 |
3 | import java.io.BufferedReader
4 | import java.io.File
5 | import java.io.InputStream
6 | import java.io.InputStreamReader
7 | import java.util.logging.Logger
8 |
9 | class Globber(val stream: InputStream, val file: File) : Thread() {
10 | val log = Logger.getLogger(LightningService::class.java.name)
11 |
12 | override fun run() {
13 | try {
14 | val isr = InputStreamReader(stream)
15 | val br = BufferedReader(isr)
16 | while (!currentThread().isInterrupted()) {
17 | val line = br.readLine()
18 | if (line != null && line.length > 0) {
19 | file.appendText("${line}\n")
20 | log.info(line)
21 | }
22 | }
23 | } catch (ioe: Exception) {
24 | ioe.printStackTrace()
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/services/TorService.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.services
2 |
3 | import android.app.IntentService
4 | import android.app.Notification
5 | import android.app.NotificationChannel
6 | import android.app.NotificationManager
7 | import android.app.PendingIntent
8 | import android.app.Service
9 | import android.content.Context
10 | import android.content.Intent
11 | import android.os.Build
12 | import android.util.Log
13 | import com.lvaccaro.lamp.MainActivity
14 | import com.lvaccaro.lamp.R
15 | import com.lvaccaro.lamp.rootDir
16 | import java.io.File
17 | import java.lang.reflect.Field
18 | import java.util.logging.Logger
19 |
20 | class TorService : IntentService("TorService") {
21 |
22 | val log = Logger.getLogger(TorService::class.java.name)
23 | companion object {
24 | val NOTIFICATION_ID = 432432
25 | }
26 | val daemon = "tor"
27 | var process: Process? = null
28 | var globber: Globber? = null
29 |
30 | override fun onHandleIntent(workIntent: Intent?) {
31 | val dataString = workIntent!!.dataString
32 | }
33 |
34 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
35 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND)
36 | log.info("start $daemon service")
37 | val torDir = File(rootDir(), ".tor")
38 | val torHiddenServiceDir = File(rootDir(), ".torHiddenService")
39 | val binaryDir = rootDir()
40 | if (!torDir.exists()) {
41 | torDir.mkdir()
42 | }
43 | if (!torHiddenServiceDir.exists()) {
44 | torHiddenServiceDir.mkdir()
45 | }
46 |
47 | val options = arrayListOf(
48 | String.format("%s/%s", binaryDir.canonicalPath, daemon),
49 | "SafeSocks", "1",
50 | "SocksPort", "9050",
51 | "NoExec", "1",
52 | "ControlPort", "9051",
53 | "DataDirectory", torDir.path,
54 | "HiddenServiceDir", torHiddenServiceDir.path,
55 | "HiddenServicePort", String.format("%d %s:%d", 9735, "127.0.0.1", 9735)
56 | )
57 |
58 | val pb = ProcessBuilder(options)
59 | pb.directory(binaryDir)
60 | pb.redirectErrorStream(true)
61 | val logFile = File(rootDir(), "$daemon.log")
62 | logFile.delete()
63 | logFile.createNewFile()
64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
65 | pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile))
66 | }
67 | process = pb.start()
68 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
69 | // Redirection output to file
70 | globber = Globber(
71 | process!!.inputStream,
72 | logFile
73 | )
74 | globber?.start()
75 | }
76 |
77 | // return super.onStartCommand(intent, flags, startId)
78 | log.info("exit $daemon service")
79 |
80 | startForeground()
81 | return Service.START_STICKY
82 | }
83 |
84 | fun startForeground() {
85 | val intent = Intent(this, MainActivity::class.java)
86 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
87 | val pendingIntent = PendingIntent.getActivity(
88 | this,
89 | NOTIFICATION_ID, intent, PendingIntent.FLAG_ONE_SHOT
90 | )
91 |
92 | val notification = Notification.Builder(this)
93 | .setContentTitle("$daemon is running")
94 | .setContentIntent(pendingIntent)
95 | .setSmallIcon(R.drawable.ic_tor)
96 | .setOngoing(true)
97 |
98 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
99 | val importance = NotificationManager.IMPORTANCE_LOW
100 | val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
101 | val channelName = "channel_01"
102 | val channel = NotificationChannel(channelName, "$daemon", importance)
103 | channel.enableLights(true)
104 | channel.enableVibration(true)
105 | notificationManager.createNotificationChannel(channel)
106 | notification.setChannelId(channelName)
107 | }
108 |
109 | startForeground(NOTIFICATION_ID, notification.build())
110 | }
111 |
112 | private fun cancelNotification() {
113 | Log.d(this::class.java.canonicalName, "******** Cancel notification called ********")
114 | val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
115 | nm.cancel(NOTIFICATION_ID)
116 | }
117 |
118 | override fun onDestroy() {
119 | super.onDestroy()
120 | log.info("destroying $daemon service")
121 | if (process != null) {
122 | android.os.Process.sendSignal(getPid(process!!), 15)
123 | }
124 | process?.destroy()
125 | globber?.interrupt()
126 | process = null
127 | globber = null
128 | cancelNotification()
129 | }
130 |
131 | fun getPid(p: Process): Int {
132 | var pid = -1
133 | try {
134 | val f: Field = p::class.java.getDeclaredField("pid")
135 | f.setAccessible(true)
136 | pid = f.getInt(p)
137 | f.setAccessible(false)
138 | } catch (e: Throwable) {
139 | }
140 | return pid
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/utils/Archive.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.utils
2 |
3 | import android.os.Build
4 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry
5 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
6 | import org.apache.commons.compress.compressors.xz.XZCompressorInputStream
7 | import org.apache.commons.compress.utils.IOUtils
8 | import java.io.BufferedInputStream
9 | import java.io.File
10 | import java.io.FileInputStream
11 | import java.io.FileOutputStream
12 |
13 | class Archive {
14 |
15 | class Release(val name: String, val version: String) {
16 | override fun toString(): String {
17 | return "%s %s".format(name, version).trim()
18 | }
19 | }
20 |
21 | companion object {
22 | val RELEASE = Release("Miami", "v0.10.0")
23 |
24 | fun arch(): String {
25 | var abi: String?
26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
27 | abi = Build.SUPPORTED_ABIS[0]
28 | } else {
29 | abi = Build.CPU_ABI
30 | }
31 | when (abi) {
32 | "armeabi-v7a" -> return "arm-linux-androideabi"
33 | "arm64-v8a" -> return "aarch64-linux-android"
34 | "x86" -> return "i686-linux-android"
35 | "x86_64" -> return "x86_64-linux-android"
36 | }
37 | throw Error("No arch found")
38 | }
39 |
40 | fun tarFilename(): String {
41 | val ARCH = arch()
42 | val PACKAGE = "lightning_ndk"
43 | return "${ARCH}_$PACKAGE.tar.xz"
44 | }
45 |
46 | fun url(): String {
47 | val TAR_FILENAME = tarFilename()
48 | return "https://github.com/clightning4j/lightning_ndk/releases/download/${RELEASE.version}/$TAR_FILENAME"
49 | }
50 |
51 | fun delete(downloadDir: File): Boolean {
52 | return File(downloadDir, tarFilename()).delete()
53 | }
54 |
55 | fun deleteUncompressed(dir: File): Boolean {
56 | return File(dir, "cli").deleteRecursively() &&
57 | File(dir, "lightningd").deleteRecursively() &&
58 | File(dir, "plugins").deleteRecursively() &&
59 | File(dir, "bitcoin-cli").delete() &&
60 | File(dir, "bitcoind").delete() &&
61 | File(dir, "tor").delete()
62 | }
63 |
64 | fun uncompressXZ(inputFile: File, outputDir: File) {
65 | mkdir(outputDir)
66 | mkdir(File(outputDir, "plugins"))
67 | mkdir(File(outputDir, "lightningd"))
68 | mkdir(File(outputDir, "cli"))
69 | val input = TarArchiveInputStream(
70 | BufferedInputStream(
71 | XZCompressorInputStream(
72 | BufferedInputStream(FileInputStream(inputFile))
73 | )
74 | )
75 | )
76 | var counter = 0
77 | var entry = input.nextEntry
78 | while (entry != null) {
79 | val name = entry.name
80 | val file: File?
81 | file = File(outputDir, name)
82 | val out = FileOutputStream(file)
83 | try {
84 | IOUtils.copy(input, out)
85 | } finally {
86 | IOUtils.closeQuietly(out)
87 | }
88 | val mode = (entry as TarArchiveEntry).mode
89 | //noinspection ResultOfMethodCallIgnored
90 | file.setExecutable(true, mode and 1 == 0)
91 | entry = input.nextEntry
92 | counter++
93 | }
94 | IOUtils.closeQuietly(input)
95 | inputFile.delete()
96 | }
97 |
98 | private fun mkdir(dir: File) {
99 | if (!dir.exists()) {
100 | dir.mkdir()
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/utils/LampKeys.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.utils
2 |
3 | class LampKeys {
4 | companion object {
5 | val ADDRESS_KEY = "ADDRESS_KEY"
6 | val AMOUNT_KEY = "amount"
7 | val LABEL_KEY = "label"
8 | val MESSAGE_KEY = "message"
9 | val WITHDRAW_COMMAND = "withdraw"
10 | val DECODEPAY_COMMAND = "decodepay"
11 | val CONNECT_COMMAND = "connect"
12 | val MESSAGE_JSON_KEY = "message"
13 |
14 | // UI save state
15 | const val OFF_CHAIN_BALANCE = "OFF_CHAIN_BALANCE"
16 | const val ON_CHAIN_BALANCE = "ON_CHAIN_BALANCE"
17 | const val OUR_CHAIN_BALANCE = "OUR_CHAIN_BALANCE"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/utils/LogObserver.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.utils
2 |
3 | import android.content.Context
4 | import android.os.FileObserver
5 | import android.util.Log
6 | import com.lvaccaro.lamp.handlers.BrokenStatus
7 | import com.lvaccaro.lamp.handlers.IEventHandler
8 | import com.lvaccaro.lamp.handlers.NewBlockHandler
9 | import com.lvaccaro.lamp.handlers.NewChannelPayment
10 | import com.lvaccaro.lamp.handlers.NewTransaction
11 | import com.lvaccaro.lamp.handlers.NodeUpHandler
12 | import com.lvaccaro.lamp.handlers.PaidInvoice
13 | import com.lvaccaro.lamp.handlers.ShutdownNode
14 | import java.io.File
15 | import java.io.LineNumberReader
16 |
17 | /**
18 | * This class is an implementation of FileObserver discussed inside the PRs XX
19 | * The ideas is from @lvaccaro
20 | *
21 | * Info about the clightning node
22 | *
23 | * ---- Node create a transaction (with fundchannel or when I receive the onchain tx)----
24 | * Pattern log: DEBUG wallet: Owning output 1 89846sat (SEGWIT) txid 33c1f5d2df4f425898dc6eb49dae51aaab1d430ee7c0da2cab18123d5c1192f0
25 | * ---- Node make a withdraw action
26 | * Patter log: DEBUG lightningd: sendrawtransaction
27 | *
28 | * --- Node adding block
29 | * Pattern log: DEBUG lightningd: Adding block
30 | *
31 | * --- Node sendrawtransaction
32 | * Pattern log: DEBUG plugin-esplora: sendrawtx exit 0
33 | *
34 | * ---- Shutdown node with command close----
35 | * Pattern log: 2020-08-03T15:38:38.812Z UNUSUAL lightningd: JSON-RPC shutdown
36 | *
37 | * ------ Node receive transaction to blockchain ----
38 | * Pattern log: No debug log
39 | *
40 | * ---- Node receive a lightning payment (keysend, pay)
41 | * Pattern log:
42 | *
43 | * ---- Node crash
44 | * Pattern log: **BROKEN**
45 | *
46 | * -- Node up
47 | * Patter log: "lightningd: Server started with public key"
48 | *
49 | *
50 | * @author https://github.com/vincenzopalazzo
51 | */
52 | class LogObserver(val context: Context, val path: String, val nameFile: String) : FileObserver(path) {
53 |
54 | init {
55 | initHandler()
56 | }
57 |
58 | companion object {
59 | val TAG = LogObserver::class.java.canonicalName
60 | }
61 |
62 | private lateinit var actionHandler: ArrayList
63 | private lateinit var logFile: File
64 | private var actualLine = 0
65 | private var lineNumberReader: LineNumberReader? = null
66 |
67 | private fun initHandler() {
68 | actionHandler = ArrayList()
69 | actionHandler.addAll(
70 | arrayOf(
71 | NewChannelPayment(),
72 | ShutdownNode(),
73 | NewBlockHandler(),
74 | BrokenStatus(),
75 | NewTransaction(),
76 | PaidInvoice(),
77 | NodeUpHandler()
78 | )
79 | )
80 | }
81 |
82 | override fun onEvent(event: Int, file: String?) {
83 | if (file == null) return
84 | if (file == nameFile) {
85 | when (event) {
86 | MODIFY -> readNewLines()
87 | }
88 | }
89 | }
90 |
91 | private fun readNewLines() {
92 | if (lineNumberReader == null)
93 | initFileLog()
94 |
95 | // FIXME(vicenzopalazzo): This is real util?
96 | if (lineNumberReader == null) return
97 | lineNumberReader?.lineNumber = actualLine
98 | var line: String? = lineNumberReader?.readLine()
99 | while (line != null) {
100 | readLogLine(line)
101 | Log.d(TAG, line)
102 | line = lineNumberReader?.readLine()
103 | }
104 | }
105 |
106 | private fun readLogLine(line: String) {
107 | actionHandler.forEach { it.doReceive(context, line) }
108 | actualLine++
109 | }
110 |
111 | private fun initFileLog() {
112 | logFile = File(path, nameFile)
113 | lineNumberReader = LineNumberReader(logFile.reader())
114 | // FIXME: level api that are enable this line about is Android nougat
115 | // for the actual version of lightning_ndk I don't need to insert the check of the version
116 | actualLine = lineNumberReader!!.lines().count().toInt()
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/utils/SimulatorPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.utils
2 |
3 | import org.json.JSONArray
4 | import org.json.JSONObject
5 |
6 | object SimulatorPlugin {
7 |
8 | fun onchain(jsonObject: JSONObject): Int {
9 | var onChainFunds = 0
10 | if (jsonObject.has("outputs")) {
11 | val outputs: JSONArray = jsonObject["outputs"] as JSONArray
12 | for (i in 0 until outputs.length()) {
13 | val output = outputs.getJSONObject(i)
14 | onChainFunds += output["value"] as Int
15 | }
16 | }
17 | return onChainFunds
18 | }
19 |
20 | fun offchain(jsonObject: JSONObject): Int {
21 | var offChainFunds = 0
22 | if (jsonObject.has("channels")) {
23 | val channels: JSONArray = jsonObject["channels"] as JSONArray
24 | for (i in 0 until channels.length()) {
25 | val channel = channels.getJSONObject(i)
26 | offChainFunds = channel["channel_sat"] as Int
27 | }
28 | }
29 | return offChainFunds
30 | }
31 |
32 | fun funds(listFunds: JSONObject): Int {
33 | var fundsToUs = 0
34 | val peers = listFunds["peers"] as JSONArray
35 | for (i in 0 until peers.length()) {
36 | val peer = peers.get(i) as? JSONObject
37 | val peerChannels = peer?.get("channels") as JSONArray
38 | for (j in 0 until peerChannels.length()) {
39 | val channel = peerChannels.get(j) as JSONObject
40 | fundsToUs += channel["msatoshi_to_us"] as Int
41 | }
42 | }
43 | return fundsToUs
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/utils/UI.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.utils
2 |
3 | import android.app.NotificationChannel
4 | import android.app.NotificationManager
5 | import android.content.ClipData
6 | import android.content.ClipboardManager
7 | import android.content.Context
8 | import android.content.Intent
9 | import android.graphics.Bitmap
10 | import android.graphics.Color
11 | import android.os.Build
12 | import android.widget.Toast
13 | import androidx.appcompat.app.AlertDialog
14 | import androidx.appcompat.app.AppCompatActivity
15 | import androidx.core.app.NotificationCompat
16 | import com.google.android.material.snackbar.Snackbar
17 | import com.google.zxing.WriterException
18 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
19 | import com.google.zxing.qrcode.encoder.Encoder
20 |
21 | class UI {
22 |
23 | companion object {
24 |
25 | fun copyToClipboard(context: Context, key: String, text: String) {
26 | val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
27 | val clip: ClipData = ClipData.newPlainText(key, text)
28 | clipboard.setPrimaryClip(clip)
29 | Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_LONG).show()
30 | }
31 |
32 | fun snackBar(
33 | context: AppCompatActivity,
34 | message: String,
35 | duration: Int = Snackbar.LENGTH_LONG
36 | ) {
37 | Snackbar.make(context.findViewById(android.R.id.content), message, duration).show()
38 | }
39 |
40 | fun showMessageOnToast(
41 | context: Context,
42 | message: String,
43 | duration: Int = Toast.LENGTH_SHORT
44 | ) {
45 | Toast.makeText(context, message, duration).show()
46 | }
47 |
48 | fun textAlertDialog(context: Context, title: String, message: String) {
49 | AlertDialog.Builder(context)
50 | .setTitle(title)
51 | .setMessage(message)
52 | .setPositiveButton(android.R.string.ok) { _, _ -> }
53 | .show()
54 | }
55 |
56 | fun share(context: Context, title: String, text: String) {
57 | val sendIntent = Intent()
58 | sendIntent.action = Intent.ACTION_SEND
59 | sendIntent.putExtra(Intent.EXTRA_TEXT, text)
60 | sendIntent.type = "text/plain"
61 | val shareIntent = Intent.createChooser(sendIntent, title)
62 | context.startActivity(shareIntent)
63 | }
64 |
65 | fun getQrCode(text: String): Bitmap {
66 | val SCALE = 16
67 | try {
68 | val matrix = Encoder.encode(text, ErrorCorrectionLevel.M).matrix
69 | val height = matrix.height * SCALE
70 | val width = matrix.width * SCALE
71 | val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
72 | for (x in 0 until width)
73 | for (y in 0 until height) {
74 | val point = matrix.get(x / SCALE, y / SCALE).toInt()
75 | bitmap.setPixel(x, y, if (point == 0x01) Color.BLACK else 0)
76 | }
77 | return bitmap
78 | } catch (e: WriterException) {
79 | throw RuntimeException(e)
80 | }
81 | }
82 |
83 | fun notification(context: Context, title: String, body: String) {
84 | val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
85 | val channelId = "basic_notification"
86 | val channelName = "basic_channel_notification"
87 |
88 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
89 | val importance = NotificationManager.IMPORTANCE_DEFAULT
90 | val notificationChannel = NotificationChannel(
91 | channelId,
92 | channelName,
93 | importance
94 | )
95 | notificationChannel.enableLights(true)
96 | notificationChannel.lightColor = Color.YELLOW
97 | notificationChannel.enableVibration(true)
98 | notificationChannel.vibrationPattern =
99 | longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
100 | manager.createNotificationChannel(notificationChannel)
101 | }
102 | val notification = NotificationCompat.Builder(context, channelId)
103 | .setContentTitle(title)
104 | .setContentText(body)
105 | .setSmallIcon(com.lvaccaro.lamp.R.drawable.ic_notification)
106 | .setAutoCancel(true)
107 | .setChannelId(channelId)
108 | .build()
109 | manager.notify(101, notification)
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/views/HistoryBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.views
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ImageView
9 | import android.widget.LinearLayout
10 | import android.widget.TextView
11 | import androidx.appcompat.app.AppCompatActivity
12 | import androidx.recyclerview.widget.LinearLayoutManager
13 | import androidx.recyclerview.widget.RecyclerView
14 | import com.google.android.material.bottomsheet.BottomSheetBehavior
15 | import com.lvaccaro.lamp.LightningCli
16 | import com.lvaccaro.lamp.R
17 | import com.lvaccaro.lamp.fragments.RecyclerViewFragment
18 | import com.lvaccaro.lamp.toJSONObject
19 | import org.jetbrains.anko.doAsync
20 | import org.jetbrains.anko.runOnUiThread
21 | import org.json.JSONArray
22 | import org.json.JSONObject
23 | import java.text.SimpleDateFormat
24 | import java.util.Date
25 |
26 | class ListAdapter(
27 | val list: JSONArray,
28 | private val onItemClick: ((JSONObject) -> Unit)?
29 | ) :
30 | RecyclerView.Adapter() {
31 |
32 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
33 | val inflater = LayoutInflater.from(parent.context)
34 | return ItemViewHolder(inflater, parent)
35 | }
36 |
37 | override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
38 | val json = list.getJSONObject(position)
39 | val type = json["type"] as String
40 | if (type == "payment")
41 | holder.payment(json)
42 | else
43 | holder.invoice(json)
44 | holder.itemView.setOnClickListener {
45 | onItemClick?.invoke(json)
46 | }
47 | }
48 |
49 | override fun getItemCount(): Int = list.length()
50 | }
51 |
52 | class ItemViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
53 | RecyclerView.ViewHolder(inflater.inflate(R.layout.list_tx, parent, false)) {
54 | private var mDateView: TextView? = null
55 | private var mAmountView: TextView? = null
56 | private var mLabelView: TextView? = null
57 |
58 | init {
59 | mDateView = itemView.findViewById(R.id.date)
60 | mAmountView = itemView.findViewById(R.id.amount)
61 | mLabelView = itemView.findViewById(R.id.label)
62 | }
63 |
64 | fun invoice(invoice: JSONObject) {
65 | val paidAt = invoice["paid_at"] as Int
66 | val msatoshi = invoice["msatoshi"] as Int
67 | val label = invoice["label"] as String
68 | bind(msatoshi, paidAt, label, true)
69 | }
70 |
71 | fun payment(payment: JSONObject) {
72 | val createdAt = payment["created_at"] as Int
73 | val msatoshi = payment["msatoshi"] as Int
74 | val id = payment["id"] as Int
75 | bind(msatoshi, createdAt, "Payment ID $id", true)
76 | }
77 |
78 | fun bind(msatoshi: Int, paidAt: Int, label: String, incoming: Boolean) {
79 | val date = Date(paidAt * 1000L)
80 | mAmountView?.text = String.format("%s %dmsat", if (incoming) "+" else "-", msatoshi)
81 | mDateView?.text = SimpleDateFormat("dd MMM yyyy, HH:mm:ss").format(date)
82 | mLabelView?.text = label
83 | }
84 | }
85 |
86 | class HistoryBottomSheet(val context: Context, val view: View?) :
87 | BottomSheetBehavior.BottomSheetCallback() {
88 |
89 | private val bottomSheetBehavior: BottomSheetBehavior
90 | private val recyclerView: RecyclerView
91 |
92 | init {
93 | bottomSheetBehavior = BottomSheetBehavior.from(view as LinearLayout)
94 | bottomSheetBehavior.addBottomSheetCallback(this)
95 | recyclerView = view.findViewById(R.id.recycler_view)
96 | recyclerView.layoutManager = LinearLayoutManager(context)
97 | }
98 |
99 | override fun onStateChanged(bottomSheet: View, newState: Int) {
100 | val imageView = view?.findViewById(R.id.arrow_image)
101 | when (newState) {
102 | BottomSheetBehavior.STATE_COLLAPSED -> {
103 | imageView?.setImageDrawable(context.getDrawable(R.drawable.ic_arrow_up))
104 | }
105 | BottomSheetBehavior.STATE_HIDDEN -> {}
106 | BottomSheetBehavior.STATE_EXPANDED -> {
107 | imageView?.setImageDrawable(context.getDrawable(R.drawable.ic_arrow_down))
108 | doAsync { reload() }
109 | }
110 | BottomSheetBehavior.STATE_DRAGGING -> {}
111 | BottomSheetBehavior.STATE_SETTLING -> { }
112 | }
113 | }
114 |
115 | override fun onSlide(bottomSheet: View, slideOffset: Float) {
116 | }
117 |
118 | private fun slideUpDown() {
119 | if (bottomSheetBehavior.state != BottomSheetBehavior.STATE_EXPANDED) {
120 | bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
121 | } else {
122 | bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
123 | }
124 | }
125 |
126 | fun reload() {
127 | val list = getData()
128 | context.runOnUiThread {
129 | recyclerView.adapter = ListAdapter(list) {
130 | val bundle = Bundle()
131 | bundle.putString("title", it.getString("type"))
132 | bundle.putString("data", it.toString())
133 | val fragment = RecyclerViewFragment()
134 | fragment.arguments = bundle
135 | fragment.show((context as AppCompatActivity).supportFragmentManager, "RecyclerViewFragment")
136 | }
137 | view?.apply {
138 | findViewById(R.id.loading_text)?.visibility = View.GONE
139 | findViewById(R.id.no_transactions_text)?.visibility = if (list.length() == 0) View.VISIBLE else View.GONE
140 | }
141 | }
142 | }
143 |
144 | private fun getData(): JSONArray {
145 | val listinvoices = LightningCli()
146 | .exec(context, arrayOf("listinvoices"), true).toJSONObject()
147 | val listsendpays = LightningCli()
148 | .exec(context, arrayOf("listsendpays"), true).toJSONObject()
149 |
150 | val invoices = listinvoices["invoices"] as JSONArray
151 | val payments = listsendpays["payments"] as JSONArray
152 |
153 | val output = JSONArray()
154 | for (i in 0 until invoices.length()) {
155 | val invoice = invoices.getJSONObject(i)
156 | if (invoice.getString("status") == "paid")
157 | output.put(invoice.put("type", "invoice"))
158 | }
159 | for (i in 0 until payments.length()) {
160 | val payment = payments.getJSONObject(i)
161 | if (payment.getString("status") == "complete")
162 | output.put(payment.put("type", "payment"))
163 | }
164 | return output
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lvaccaro/lamp/views/PowerImageView.kt:
--------------------------------------------------------------------------------
1 | package com.lvaccaro.lamp.views
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.AnimationDrawable
5 | import android.util.AttributeSet
6 | import androidx.appcompat.widget.AppCompatImageView
7 | import com.lvaccaro.lamp.R
8 |
9 | class PowerImageView : AppCompatImageView {
10 |
11 | private var light = false
12 |
13 | constructor(context: Context?) : super(context!!)
14 | constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs)
15 | constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context!!, attrs, defStyleAttr)
16 |
17 | fun on() {
18 | light = true
19 | isClickable = true
20 | setBackgroundResource(R.drawable.ic_lamp_on)
21 | }
22 |
23 | fun off() {
24 | light = false
25 | isClickable = true
26 | setBackgroundResource(R.drawable.ic_lamp_off)
27 | }
28 |
29 | fun animating() {
30 | isClickable = false
31 | setBackgroundResource(R.drawable.ic_lamp)
32 | val animation: AnimationDrawable = background as AnimationDrawable
33 | animation.start()
34 | }
35 |
36 | fun isAnimating(): Boolean {
37 | return !isClickable
38 | }
39 |
40 | fun isOn(): Boolean {
41 | return light
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_channels.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_paste.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-hdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_channels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-hdpi/ic_channels.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-hdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-hdpi/ic_paste.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_sats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-hdpi/ic_sats.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_tor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-hdpi/ic_tor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-mdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_channels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-mdpi/ic_channels.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-mdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-mdpi/ic_paste.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_sats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-mdpi/ic_sats.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_tor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-mdpi/ic_tor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xhdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_channels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xhdpi/ic_channels.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xhdpi/ic_paste.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_sats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xhdpi/ic_sats.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_tor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xhdpi/ic_tor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxhdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_channels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxhdpi/ic_channels.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxhdpi/ic_paste.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_sats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxhdpi/ic_sats.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_tor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxhdpi/ic_tor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_sats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxxhdpi/ic_sats.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_tor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable-xxxhdpi/ic_tor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/divider.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_down.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_up.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_account_balance_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_account_box_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_call_received_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_send_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_share_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_blockchain.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable/ic_lamp1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable/ic_lamp2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable/ic_lamp3.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable/ic_lamp4.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable/ic_lamp_off.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lamp_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/drawable/ic_lamp_on.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lightning.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_build_invoice.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
29 |
30 |
38 |
39 |
47 |
48 |
54 |
55 |
64 |
65 |
73 |
74 |
75 |
82 |
83 |
92 |
93 |
101 |
102 |
103 |
104 |
111 |
112 |
119 |
120 |
121 |
122 |
129 |
130 |
137 |
138 |
139 |
140 |
147 |
148 |
157 |
158 |
166 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_channels.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
20 |
21 |
22 |
23 |
28 |
29 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_console.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
22 |
23 |
27 |
28 |
37 |
38 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_log.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
13 |
14 |
19 |
20 |
25 |
26 |
27 |
28 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_scan.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_send.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
29 |
30 |
38 |
39 |
47 |
48 |
54 |
55 |
65 |
66 |
75 |
76 |
77 |
78 |
86 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main_off.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
22 |
23 |
31 |
32 |
33 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main_on.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
24 |
25 |
38 |
39 |
47 |
48 |
53 |
54 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_channel.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
19 |
20 |
26 |
27 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_decoded_invoice.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
19 |
20 |
26 |
27 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_fundchannel.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
20 |
21 |
25 |
26 |
31 |
32 |
33 |
34 |
39 |
40 |
45 |
46 |
50 |
51 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
23 |
24 |
31 |
32 |
38 |
39 |
46 |
47 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_peer_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
20 |
21 |
25 |
26 |
33 |
34 |
35 |
36 |
39 |
40 |
45 |
46 |
50 |
51 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_recyclerview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_withdraw.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
18 |
19 |
24 |
25 |
26 |
27 |
31 |
32 |
37 |
38 |
39 |
40 |
46 |
47 |
51 |
52 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_balance.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
31 |
32 |
33 |
34 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_channel.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
27 |
28 |
35 |
36 |
37 |
38 |
42 |
43 |
49 |
50 |
54 |
55 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_tx.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
14 |
18 |
22 |
26 |
30 |
34 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_channels.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_invoice.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_log.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
13 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_scan.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | - bitcoin
4 | - testnet
5 | - regtest
6 |
7 |
8 |
9 | - io
10 | - debug
11 | - info
12 | - unusual
13 | - broken
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFC107
4 | #FFC107
5 | #FFC107
6 | #FFEBAE
7 | #263238
8 | #cfd8dc
9 | #37474f
10 | @android:color/background_light
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 180dp
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Lamp
3 | Downloading
4 | Settings
5 | GetInfo
6 | New Address
7 | Connect
8 | Start
9 | Stop
10 | Lightning daemon options
11 | Select the network parameters
12 | Log level
13 | Bitcoind RPC options
14 | Bitcoind RPC username
15 | Bitcoind RPC password
16 | Bitcoind RPC host to connect to
17 | Bitcoind RPC port
18 | Clear memory
19 | Erase logs file
20 | Erase data dir
21 | Erase binary dir
22 | Log
23 | Download
24 | Console
25 | Scan
26 | Paste from clipboard
27 | Set a socks v5 proxy IP address and port
28 | Activate a proxy
29 | Set an IP address (v4 or v6) or .onion v2/v3 to announce, but not listen on
30 | Announce address
31 | Proxy
32 | Network options
33 | Invoice
34 | Bind address
35 | Set an IP address (v4 or v6) to listen on, but not announce
36 | Alias
37 | Up to 32-byte alias for node
38 | Enable Tor deamon
39 | If Tor is enabled, the following network configuration will be overwritten
40 | Bolt11
41 | Expired in
42 | MilliSatoshi
43 | Label
44 | Description
45 | confirm
46 | Transactions
47 | Enable Esplora plugin
48 | Using Esplora plugin as bitcoind backend
49 | Settings
50 | Channels
51 | History
52 | Withdraw
53 | Destination address
54 | Satoshi
55 |
56 | Export data
57 | Import data
58 | Esplora api endoint uri
59 | Fund Channel
60 | Node URI
61 | Warning
62 | MainActivity
63 | PayViewActivity
64 | Autostart
65 | Lamp settings
66 | Run lightning on Lamp startup
67 | Update
68 | Receive
69 | Send
70 | Balance
71 | share
72 | copy
73 | Node info
74 | Route
75 | pay
76 | loading..
77 | No transactions
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
21 |
22 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
18 |
19 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
54 |
55 |
60 |
61 |
66 |
67 |
71 |
72 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
97 |
98 |
102 |
103 |
107 |
108 |
113 |
114 |
119 |
120 |
121 |
122 |
123 |
124 |
127 |
128 |
131 |
132 |
133 |
134 |
135 |
136 |
139 |
140 |
143 |
144 |
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.4.31'
5 | repositories {
6 | google()
7 | jcenter()
8 | maven {
9 | url "https://plugins.gradle.org/m2/"
10 | }
11 | }
12 | dependencies {
13 | classpath 'com.android.tools.build:gradle:7.0.3'
14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
15 | classpath "org.jmailen.gradle:kotlinter-gradle:3.4.0"
16 | // NOTE: Do not place your application dependencies here; they belong
17 | // in the individual module build.gradle files
18 | }
19 | }
20 |
21 | allprojects {
22 | repositories {
23 | google()
24 | jcenter()
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/doc/cmdline-tools-setup.md:
--------------------------------------------------------------------------------
1 | # How to setup cmdline-tools to build and test this project in Ubuntu
2 |
3 | Download https://developer.android.com/studio/#command-tools
4 |
5 | Run following commands:
6 |
7 | ```
8 | ## to get rid of openjdk11 and replace it with 8
9 | ## run following commented lines as root
10 | # apt-get autoremove openjdk*
11 | # apt-get install openjdk-8-jdk
12 | cd $HOME
13 | mkdir -p android_sdk/cmdline-tools
14 | cd android_sdk/cmdline-tools
15 | unzip ~/commandlinetools-linux-*_latest.zip
16 | export ANDROID_SDK_ROOT=$HOME/android_sdk ###
17 | cmdline-tools/bin/sdkmanager --install "cmdline-tools;latest"
18 | cmdline-tools/bin/sdkmanager --install "build-tools;29.0.3"
19 | cmdline-tools/bin/sdkmanager --install "platforms;android-28"
20 | rm -rf cmdline-tools
21 | export PATH=$HOME/android_sdk/cmdline-tools/latest/bin:$PATH ###
22 | yes | sdkmanager --licenses
23 | cd ~/src/lamp
24 | ./gradlew tasks
25 | ./gradlew build
26 | # connect your phone/emulator (check: adb devices, adb shell)
27 | ./gradlew installDebug
28 | ```
29 |
30 | Consider adding lines marked with `###` to your `~/.profile` or equivalent.
31 |
32 | See https://stackoverflow.com/questions/60440509/android-command-line-tools-sdkmanager-always-shows-warning-could-not-create-se
33 |
--------------------------------------------------------------------------------
/doc/img/Screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/doc/img/Screen1.png
--------------------------------------------------------------------------------
/doc/img/Screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/doc/img/Screen2.png
--------------------------------------------------------------------------------
/doc/img/Screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/doc/img/Screen3.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clightning4j/lamp/e3fe170cd3dd314222f228127fc0b9d6ca97c7ed/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Mar 27 00:40:00 CET 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='Lamp'
3 |
--------------------------------------------------------------------------------