diff --git a/LICENSE b/LICENSE index f29b709..8dada3e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2022 pokkst + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + 1. Definitions. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 87adf83..284959c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ -# monero-wallet -WIP Monero Wallet +# Monerujo +Another Android Monero Wallet for Monero + **(not + Monero Classic, + Monero-Classic, + Monero Zero, + Monero Original, + Monero C, + Monero V)** + +### QUICKSTART +- Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it +- Alternatively add our F-Droid repo https://f-droid.monerujo.io/fdroid/repo with fingerpint ```A8 2C 68 E1 4A F0 AA 6A 2E C2 0E 6B 27 2E FF 25 E5 A0 38 F3 F6 58 84 31 6E 0F 5E 0D 91 E7 B7 13``` to your F-Droid client +- Run the App and select "Generate Wallet" to create a new wallet or recover a wallet +- Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started) +- See the [FAQ](doc/FAQ.md) + +## Translations +Help us translate Monerujo! You can find instructions for adding a new translation or updating an existent one in [this guide](https://github.com/monero-ecosystem/monero-translations/blob/master/translate-monerujo.md), but is suggested to contact the Monero Localization Workgroup first if you have any doubt or question. You can do so in many ways. For example by email: translate@getmonero.org or chatting in `#monero-translations` (chatroom on Freenode, matrix and MatterMost). To see the complete list of contacts, take a look at the [official repository of the workgroup on GitHub](https://github.com/monero-ecosystem/monero-translations/blob/master/README.md#contacts). + +### Disclaimer +You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet. + +### Random Notes +- works on the mainnet & stagenet +- use your own daemon - it's easy +- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/ + +### TODO +- see taiga.getmonero.org & issues on github + +### Issues / Pitfalls +- Users of Zenfone MAX & Zenfone 2 Laser (possibly others) **MUST** use the armeabi-v7a APK as the arm64-v8a build uses hardware AES +functionality these models don't have. +- You should backup your wallet files in the "monerujo" folder periodically. +- Also note, that on some devices the backups will only be visible on a PC over USB after a reboot of the device (it's an Android bug/feature) +- Created wallets on a private testnet are unusable because the restore height is set to that +of the "real" testnet. After creating a new wallet, make a **new** one by recovering from the seed. +The official monero client shows the same behaviour. + +### HOW TO BUILD + +See [the instructions](doc/BUILDING-external-libs.md) + +Then, fire up Android Studio and build the APK. + +### Donations +- Address: 4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk +- Viewkey: b1aff2a12191723da0afbe75516f94dd8b068215f6e847d8da57aca5f1f98e0c diff --git a/app/build.gradle b/app/build.gradle index 40e250f..0e61e27 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -112,6 +112,9 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } namespace 'com.m2049r.xmrwallet' + buildFeatures { + viewBinding true + } } static def getId(name) { @@ -146,6 +149,10 @@ dependencies { implementation 'org.jitsi:dnssecjava:1.2.0' implementation 'org.slf4j:slf4j-nop:1.7.36' implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.navigation:navigation-fragment:2.5.1' + implementation 'androidx.navigation:navigation-ui:2.5.1' //noinspection GradleDependency testImplementation "junit:junit:4.13.2" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e9ab3e..a43e517 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + @@ -28,7 +29,6 @@ + android:exported="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0) { - Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - public void run() { - if (progressDialog != null) progressDialog.show(); - } - }, delayMillis); - } else { - progressDialog.show(); - } - } - - @Override - public void showLedgerProgressDialog(int mode) { - dismissProgressDialog(); // just in case - progressDialog = new LedgerProgressDialog(BaseActivity.this, mode); - Ledger.setListener((Ledger.Listener) progressDialog); - progressDialog.show(); - } - - @Override - public void dismissProgressDialog() { - if (progressDialog == null) return; // nothing to do - if (progressDialog instanceof Ledger.Listener) { - Ledger.unsetListener((Ledger.Listener) progressDialog); - } - if (progressDialog.isShowing()) { - progressDialog.dismiss(); - } - progressDialog = null; - } - - static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds - - private PowerManager.WakeLock wl = null; - - void acquireWakeLock() { - if ((wl != null) && wl.isHeld()) return; - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name)); - try { - wl.acquire(); - Timber.d("WakeLock acquired"); - } catch (SecurityException ex) { - Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage()); - wl = null; - } - } - - void releaseWakeLock(int delayMillis) { - Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - @Override - public void run() { - releaseWakeLock(); - } - }, delayMillis); - } - - void releaseWakeLock() { - if ((wl == null) || !wl.isHeld()) return; - wl.release(); - wl = null; - Timber.d("WakeLock released"); - } - - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - initNfc(); - } - - @Override - protected void onPostResume() { - super.onPostResume(); - if (nfcAdapter != null) { - nfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, null, null); - // intercept all techs so we can tell the user their tag is no good - } - } - - @Override - protected void onPause() { - Timber.d("onPause()"); - if (nfcAdapter != null) - nfcAdapter.disableForegroundDispatch(this); - super.onPause(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - processNfcIntent(intent); - } - - // NFC stuff - private NfcAdapter nfcAdapter; - private PendingIntent nfcPendingIntent; - - public void initNfc() { - nfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (nfcAdapter == null) // no NFC support - return; - nfcPendingIntent = PendingIntent.getActivity(this, 0, - new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0); - } - - private void processNfcIntent(Intent intent) { - String action = intent.getAction(); - Timber.d("ACTION=%s", action); - if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) - || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) - || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) { - Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - Ndef ndef = Ndef.get(tag); - if (ndef == null) { - Toast.makeText(this, getString(R.string.nfc_tag_unsupported), Toast.LENGTH_LONG).show(); - return; - } - - Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (f instanceof ReceiveFragment) { - // We want to write a Tag from the ReceiveFragment - BarcodeData bc = ((ReceiveFragment) f).getBarcodeData(); - if (bc != null) { - new AsyncWriteTag(ndef, bc.getUri()).execute(); - } // else wallet is not loaded yet or receive is otherwise not ready - ignore - } else if (f instanceof SendFragment) { - // We want to read a Tag for the SendFragment - NdefMessage ndefMessage = ndef.getCachedNdefMessage(); - if (ndefMessage == null) { - Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show(); - return; - } - NdefRecord firstRecord = ndefMessage.getRecords()[0]; - Uri uri = firstRecord.toUri(); // we insist on the first record - if (uri == null) { - Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show(); - } else { - BarcodeData bc = BarcodeData.fromString(uri.toString()); - if (bc == null) - Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show(); - else - onUriScanned(bc); - } - } - } - } - - // this gets called only if we get data - @CallSuper - void onUriScanned(BarcodeData barcodeData) { - // do nothing by default yet - } - - private BarcodeData barcodeData = null; - - private BarcodeData popBarcodeData() { - BarcodeData popped = barcodeData; - barcodeData = null; - return popped; - } - - private class AsyncWriteTag extends AsyncTask { - - Ndef ndef; - Uri uri; - String errorMessage = null; - - AsyncWriteTag(Ndef ndef, Uri uri) { - this.ndef = ndef; - this.uri = uri; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - showProgressDialog(R.string.progress_nfc_write); - } - - @Override - protected Boolean doInBackground(Void... params) { - if (params.length != 0) return false; - try { - writeNdef(ndef, uri); - return true; - } catch (IOException | FormatException ex) { - Timber.e(ex); - } catch (IllegalArgumentException ex) { - errorMessage = ex.getMessage(); - Timber.d(errorMessage); - } finally { - try { - ndef.close(); - } catch (IOException ex) { - Timber.e(ex); - } - } - return false; - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if (isDestroyed()) { - return; - } - dismissProgressDialog(); - if (!result) { - if (errorMessage != null) - Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show(); - else - Toast.makeText(getApplicationContext(), getString(R.string.nfc_write_failed), Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(getApplicationContext(), getString(R.string.nfc_write_successful), Toast.LENGTH_SHORT).show(); - } - } - } - - void writeNdef(Ndef ndef, Uri uri) throws IOException, FormatException { - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (nfcAdapter == null) return; // no NFC support here - - NdefRecord recordNFC = NdefRecord.createUri(uri); - NdefMessage message = new NdefMessage(recordNFC); - ndef.connect(); - int tagSize = ndef.getMaxSize(); - int msgSize = message.getByteArrayLength(); - Timber.d("tagSize=%d, msgSIze=%d, uriSize=%d", tagSize, msgSize, uri.toString().length()); - if (tagSize < msgSize) - throw new IllegalArgumentException(getString(R.string.nfc_tag_size, tagSize, msgSize)); - ndef.writeNdefMessage(message); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java deleted file mode 100644 index 6370e99..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ /dev/null @@ -1,615 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import androidx.annotation.NonNull; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.Editable; -import android.text.Html; -import android.text.InputType; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.LinearLayout; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.switchmaterial.SwitchMaterial; -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.FingerprintHelper; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.KeyStoreHelper; -import com.m2049r.xmrwallet.util.RestoreHeight; -import com.m2049r.xmrwallet.util.ledger.Monero; -import com.m2049r.xmrwallet.widget.PasswordEntryView; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.io.File; -import java.text.ParseException; -import java.text.SimpleDateFormat; - -import timber.log.Timber; - -public class GenerateFragment extends Fragment { - - static final String TYPE = "type"; - static final String TYPE_NEW = "new"; - static final String TYPE_KEY = "key"; - static final String TYPE_SEED = "seed"; - static final String TYPE_LEDGER = "ledger"; - static final String TYPE_VIEWONLY = "view"; - - private TextInputLayout etWalletName; - private PasswordEntryView etWalletPassword; - private LinearLayout llFingerprintAuth; - private TextInputLayout etWalletAddress; - private TextInputLayout etWalletMnemonic; - private TextInputLayout etWalletViewKey; - private TextInputLayout etWalletSpendKey; - private TextInputLayout etWalletRestoreHeight; - private Button bGenerate; - - private Button bSeedOffset; - private TextInputLayout etSeedOffset; - - private String type = null; - - private void clearErrorOnTextEntry(final TextInputLayout textInputLayout) { - textInputLayout.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - textInputLayout.setError(null); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Bundle args = getArguments(); - this.type = args.getString(TYPE); - - View view = inflater.inflate(R.layout.fragment_generate, container, false); - - etWalletName = view.findViewById(R.id.etWalletName); - etWalletPassword = view.findViewById(R.id.etWalletPassword); - llFingerprintAuth = view.findViewById(R.id.llFingerprintAuth); - etWalletMnemonic = view.findViewById(R.id.etWalletMnemonic); - etWalletAddress = view.findViewById(R.id.etWalletAddress); - etWalletViewKey = view.findViewById(R.id.etWalletViewKey); - etWalletSpendKey = view.findViewById(R.id.etWalletSpendKey); - etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight); - bGenerate = view.findViewById(R.id.bGenerate); - bSeedOffset = view.findViewById(R.id.bSeedOffset); - etSeedOffset = view.findViewById(R.id.etSeedOffset); - - etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - - etWalletName.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - checkName(); - } - }); - clearErrorOnTextEntry(etWalletName); - - etWalletMnemonic.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - checkMnemonic(); - } - }); - clearErrorOnTextEntry(etWalletMnemonic); - - etWalletAddress.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - checkAddress(); - } - }); - clearErrorOnTextEntry(etWalletAddress); - - etWalletViewKey.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - checkViewKey(); - } - }); - clearErrorOnTextEntry(etWalletViewKey); - - etWalletSpendKey.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - checkSpendKey(); - } - }); - clearErrorOnTextEntry(etWalletSpendKey); - - Helper.showKeyboard(requireActivity()); - - etWalletName.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - if (checkName()) { - etWalletPassword.requestFocus(); - } // otherwise ignore - return true; - } - return false; - }); - - if (FingerprintHelper.isDeviceSupported(getContext())) { - llFingerprintAuth.setVisibility(View.VISIBLE); - - final SwitchMaterial swFingerprintAllowed = (SwitchMaterial) llFingerprintAuth.getChildAt(0); - swFingerprintAllowed.setOnClickListener(view1 -> { - if (!swFingerprintAllowed.isChecked()) return; - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity()); - builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn))) - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), null) - .setNegativeButton(getString(R.string.label_cancel), (dialogInterface, i) -> swFingerprintAllowed.setChecked(false)) - .show(); - }); - } - - switch (type) { - case TYPE_NEW: - etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED); - break; - case TYPE_LEDGER: - etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); - etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - etWalletRestoreHeight.requestFocus(); - return true; - } - return false; - }); - break; - case TYPE_SEED: - etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - etWalletMnemonic.requestFocus(); - return true; - } - return false; - }); - etWalletMnemonic.setVisibility(View.VISIBLE); - etWalletMnemonic.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - if (checkMnemonic()) { - etWalletRestoreHeight.requestFocus(); - } - return true; - } - return false; - }); - bSeedOffset.setVisibility(View.VISIBLE); - bSeedOffset.setOnClickListener(v -> toggleSeedOffset()); - break; - case TYPE_KEY: - case TYPE_VIEWONLY: - etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - etWalletAddress.requestFocus(); - return true; - } - return false; - }); - etWalletAddress.setVisibility(View.VISIBLE); - etWalletAddress.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - if (checkAddress()) { - etWalletViewKey.requestFocus(); - } - return true; - } - return false; - }); - etWalletViewKey.setVisibility(View.VISIBLE); - etWalletViewKey.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - if (checkViewKey()) { - if (type.equals(TYPE_KEY)) { - etWalletSpendKey.requestFocus(); - } else { - etWalletRestoreHeight.requestFocus(); - } - } - return true; - } - return false; - }); - break; - } - if (type.equals(TYPE_KEY)) { - etWalletSpendKey.setVisibility(View.VISIBLE); - etWalletSpendKey.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_NEXT)) { - if (checkSpendKey()) { - etWalletRestoreHeight.requestFocus(); - } - return true; - } - return false; - }); - } - if (!type.equals(TYPE_NEW)) { - etWalletRestoreHeight.setVisibility(View.VISIBLE); - etWalletRestoreHeight.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED); - } - bGenerate.setOnClickListener(v -> { - Helper.hideKeyboard(getActivity()); - generateWallet(); - }); - - etWalletName.requestFocus(); - - return view; - } - - void toggleSeedOffset() { - if (etSeedOffset.getVisibility() == View.VISIBLE) { - etSeedOffset.getEditText().getText().clear(); - etSeedOffset.setVisibility(View.GONE); - bSeedOffset.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_keyboard_arrow_down, 0, 0, 0); - } else { - etSeedOffset.setVisibility(View.VISIBLE); - bSeedOffset.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_keyboard_arrow_up, 0, 0, 0); - etSeedOffset.requestFocusFromTouch(); - } - } - - private boolean checkName() { - String name = etWalletName.getEditText().getText().toString(); - boolean ok = true; - if (name.length() == 0) { - etWalletName.setError(getString(R.string.generate_wallet_name)); - ok = false; - } else if (name.charAt(0) == '.') { - etWalletName.setError(getString(R.string.generate_wallet_dot)); - ok = false; - } else { - File walletFile = Helper.getWalletFile(getActivity(), name); - if (WalletManager.getInstance().walletExists(walletFile)) { - etWalletName.setError(getString(R.string.generate_wallet_exists)); - ok = false; - } - } - if (ok) { - etWalletName.setError(null); - } - return ok; - } - - private boolean checkHeight() { - long height = !type.equals(TYPE_NEW) ? getHeight() : 0; - boolean ok = true; - if (height < 0) { - etWalletRestoreHeight.setError(getString(R.string.generate_restoreheight_error)); - ok = false; - } - if (ok) { - etWalletRestoreHeight.setError(null); - } - return ok; - } - - private long getHeight() { - long height = -1; - - String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim(); - if (restoreHeight.isEmpty()) return -1; - try { - // is it a date? - SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd"); - parser.setLenient(false); - height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight)); - } catch (ParseException ignored) { - } - if ((height < 0) && (restoreHeight.length() == 8)) - try { - // is it a date without dashes? - SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd"); - parser.setLenient(false); - height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight)); - } catch (ParseException ignored) { - } - if (height < 0) - try { - // or is it a height? - height = Long.parseLong(restoreHeight); - } catch (NumberFormatException ex) { - return -1; - } - Timber.d("Using Restore Height = %d", height); - return height; - } - - private boolean checkMnemonic() { - String seed = etWalletMnemonic.getEditText().getText().toString(); - boolean ok = (seed.split("\\s").length == 25); // 25 words - if (!ok) { - etWalletMnemonic.setError(getString(R.string.generate_check_mnemonic)); - } else { - etWalletMnemonic.setError(null); - } - return ok; - } - - private boolean checkAddress() { - String address = etWalletAddress.getEditText().getText().toString(); - boolean ok = Wallet.isAddressValid(address); - if (!ok) { - etWalletAddress.setError(getString(R.string.generate_check_address)); - } else { - etWalletAddress.setError(null); - } - return ok; - } - - private boolean checkViewKey() { - String viewKey = etWalletViewKey.getEditText().getText().toString(); - boolean ok = (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$")); - if (!ok) { - etWalletViewKey.setError(getString(R.string.generate_check_key)); - } else { - etWalletViewKey.setError(null); - } - return ok; - } - - private boolean checkSpendKey() { - String spendKey = etWalletSpendKey.getEditText().getText().toString(); - boolean ok = ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$")))); - if (!ok) { - etWalletSpendKey.setError(getString(R.string.generate_check_key)); - } else { - etWalletSpendKey.setError(null); - } - return ok; - } - - private void generateWallet() { - if (!checkName()) return; - if (!checkHeight()) return; - - String name = etWalletName.getEditText().getText().toString(); - String password = etWalletPassword.getEditText().getText().toString(); - boolean fingerprintAuthAllowed = ((SwitchMaterial) llFingerprintAuth.getChildAt(0)).isChecked(); - - // create the real wallet password - String crazyPass = KeyStoreHelper.getCrazyPass(getActivity(), password); - - long height = getHeight(); - if (height < 0) height = 0; - - switch (type) { - case TYPE_NEW: - bGenerate.setEnabled(false); - if (fingerprintAuthAllowed) { - KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password); - } - activityCallback.onGenerate(name, crazyPass); - break; - case TYPE_SEED: - if (!checkMnemonic()) return; - final String seed = etWalletMnemonic.getEditText().getText().toString(); - bGenerate.setEnabled(false); - if (fingerprintAuthAllowed) { - KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password); - } - final String offset = etSeedOffset.getEditText().getText().toString(); - activityCallback.onGenerate(name, crazyPass, seed, offset, height); - break; - case TYPE_LEDGER: - bGenerate.setEnabled(false); - if (fingerprintAuthAllowed) { - KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password); - } - activityCallback.onGenerateLedger(name, crazyPass, height); - break; - case TYPE_KEY: - case TYPE_VIEWONLY: - if (checkAddress() && checkViewKey() && checkSpendKey()) { - bGenerate.setEnabled(false); - String address = etWalletAddress.getEditText().getText().toString(); - String viewKey = etWalletViewKey.getEditText().getText().toString(); - String spendKey = ""; - if (type.equals(TYPE_KEY)) { - spendKey = etWalletSpendKey.getEditText().getText().toString(); - } - if (fingerprintAuthAllowed) { - KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password); - } - activityCallback.onGenerate(name, crazyPass, address, viewKey, spendKey, height); - } - break; - } - } - - public void walletGenerateError() { - bGenerate.setEnabled(true); - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activityCallback.setTitle(getString(R.string.generate_title) + " - " + getType()); - activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); - - } - - String getType() { - switch (type) { - case TYPE_KEY: - return getString(R.string.generate_wallet_type_key); - case TYPE_NEW: - return getString(R.string.generate_wallet_type_new); - case TYPE_SEED: - return getString(R.string.generate_wallet_type_seed); - case TYPE_LEDGER: - return getString(R.string.generate_wallet_type_ledger); - case TYPE_VIEWONLY: - return getString(R.string.generate_wallet_type_view); - default: - Timber.e("unknown type %s", type); - return "?"; - } - } - - GenerateFragment.Listener activityCallback; - - public interface Listener { - void onGenerate(String name, String password); - - void onGenerate(String name, String password, String seed, String offset, long height); - - void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); - - void onGenerateLedger(String name, String password, long height); - - void setTitle(String title); - - void setToolbarButton(int type); - - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof GenerateFragment.Listener) { - this.activityCallback = (GenerateFragment.Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - switch (type) { - case TYPE_KEY: - inflater.inflate(R.menu.create_wallet_keys, menu); - break; - case TYPE_NEW: - inflater.inflate(R.menu.create_wallet_new, menu); - break; - case TYPE_SEED: - inflater.inflate(R.menu.create_wallet_seed, menu); - break; - case TYPE_LEDGER: - inflater.inflate(R.menu.create_wallet_ledger, menu); - break; - case TYPE_VIEWONLY: - inflater.inflate(R.menu.create_wallet_view, menu); - break; - default: - } - super.onCreateOptionsMenu(menu, inflater); - } - - AlertDialog ledgerDialog = null; - - public void convertLedgerSeed() { - if (ledgerDialog != null) return; - final Activity activity = requireActivity(); - View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null); - MaterialAlertDialogBuilder alertDialogBuilder = new MaterialAlertDialogBuilder(activity); - alertDialogBuilder.setView(promptsView); - - final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed); - final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase); - - clearErrorOnTextEntry(etSeed); - - alertDialogBuilder - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), null) - .setNegativeButton(getString(R.string.label_cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Helper.hideKeyboardAlways(activity); - etWalletMnemonic.getEditText().getText().clear(); - dialog.cancel(); - ledgerDialog = null; - } - }); - - ledgerDialog = alertDialogBuilder.create(); - - ledgerDialog.setOnShowListener(dialog -> { - Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(view -> { - String ledgerSeed = etSeed.getEditText().getText().toString(); - String ledgerPassphrase = etPassphrase.getEditText().getText().toString(); - String moneroSeed = Monero.convert(ledgerSeed, ledgerPassphrase); - if (moneroSeed != null) { - etWalletMnemonic.getEditText().setText(moneroSeed); - ledgerDialog.dismiss(); - ledgerDialog = null; - } else { - etSeed.setError(getString(R.string.bad_ledger_seed)); - } - }); - }); - - if (Helper.preventScreenshot()) { - ledgerDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - } - - ledgerDialog.show(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java deleted file mode 100644 index cb40b84..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Editable; -import android.text.Html; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.switchmaterial.SwitchMaterial; -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.ledger.Ledger; -import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; -import com.m2049r.xmrwallet.model.NetworkType; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.FingerprintHelper; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.KeyStoreHelper; -import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; -import com.m2049r.xmrwallet.widget.PasswordEntryView; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.text.NumberFormat; - -import timber.log.Timber; - -public class GenerateReviewFragment extends Fragment { - static final public String VIEW_TYPE_DETAILS = "details"; - static final public String VIEW_TYPE_ACCEPT = "accept"; - static final public String VIEW_TYPE_WALLET = "wallet"; - - public static final String REQUEST_TYPE = "type"; - public static final String REQUEST_PATH = "path"; - public static final String REQUEST_PASSWORD = "password"; - - private ScrollView scrollview; - - private ProgressBar pbProgress; - private TextView tvWalletPassword; - private TextView tvWalletAddress; - private FrameLayout flWalletMnemonic; - private TextView tvWalletMnemonic; - private TextView tvWalletHeight; - private TextView tvWalletViewKey; - private TextView tvWalletSpendKey; - private ImageButton bCopyAddress; - private LinearLayout llAdvancedInfo; - private LinearLayout llPassword; - private LinearLayout llMnemonic; - private LinearLayout llSpendKey; - private LinearLayout llViewKey; - private Button bAdvancedInfo; - private Button bAccept; - - private Button bSeedOffset; - private TextInputLayout etSeedOffset; - - private String walletPath; - private String walletName; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View view = inflater.inflate(R.layout.fragment_review, container, false); - - scrollview = view.findViewById(R.id.scrollview); - pbProgress = view.findViewById(R.id.pbProgress); - tvWalletPassword = view.findViewById(R.id.tvWalletPassword); - tvWalletAddress = view.findViewById(R.id.tvWalletAddress); - tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey); - tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey); - tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic); - flWalletMnemonic = view.findViewById(R.id.flWalletMnemonic); - tvWalletHeight = view.findViewById(R.id.tvWalletHeight); - bCopyAddress = view.findViewById(R.id.bCopyAddress); - bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo); - llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo); - llPassword = view.findViewById(R.id.llPassword); - llMnemonic = view.findViewById(R.id.llMnemonic); - llSpendKey = view.findViewById(R.id.llSpendKey); - llViewKey = view.findViewById(R.id.llViewKey); - - etSeedOffset = view.findViewById(R.id.etSeedOffset); - bSeedOffset = view.findViewById(R.id.bSeedOffset); - - bAccept = view.findViewById(R.id.bAccept); - - boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet; - tvWalletMnemonic.setTextIsSelectable(allowCopy); - tvWalletSpendKey.setTextIsSelectable(allowCopy); - tvWalletPassword.setTextIsSelectable(allowCopy); - - bAccept.setOnClickListener(v -> acceptWallet()); - view.findViewById(R.id.bCopyViewKey).setOnClickListener(v -> copyViewKey()); - bCopyAddress.setEnabled(false); - bCopyAddress.setOnClickListener(v -> copyAddress()); - bAdvancedInfo.setOnClickListener(v -> toggleAdvancedInfo()); - - bSeedOffset.setOnClickListener(v -> toggleSeedOffset()); - etSeedOffset.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - showSeed(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - } - }); - - Bundle args = getArguments(); - type = args.getString(REQUEST_TYPE); - walletPath = args.getString(REQUEST_PATH); - localPassword = args.getString(REQUEST_PASSWORD); - showDetails(); - return view; - } - - String getSeedOffset() { - return etSeedOffset.getEditText().getText().toString(); - } - - boolean seedOffsetInProgress = false; - - void showSeed() { - synchronized (this) { - if (seedOffsetInProgress) return; - seedOffsetInProgress = true; - } - new AsyncShowSeed().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath); - } - - void showDetails() { - tvWalletPassword.setText(null); - new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath); - } - - void copyViewKey() { - Helper.clipBoardCopy(requireActivity(), getString(R.string.label_copy_viewkey), tvWalletViewKey.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_viewkey), Toast.LENGTH_SHORT).show(); - } - - void copyAddress() { - Helper.clipBoardCopy(requireActivity(), getString(R.string.label_copy_address), tvWalletAddress.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); - } - - void nocopy() { - Toast.makeText(getActivity(), getString(R.string.message_nocopy), Toast.LENGTH_SHORT).show(); - } - - void toggleAdvancedInfo() { - if (llAdvancedInfo.getVisibility() == View.VISIBLE) { - llAdvancedInfo.setVisibility(View.GONE); - bAdvancedInfo.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_keyboard_arrow_down, 0, 0, 0); - } else { - llAdvancedInfo.setVisibility(View.VISIBLE); - bAdvancedInfo.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_keyboard_arrow_up, 0, 0, 0); - scrollview.post(() -> scrollview.fullScroll(ScrollView.FOCUS_DOWN)); - } - } - - void toggleSeedOffset() { - if (etSeedOffset.getVisibility() == View.VISIBLE) { - etSeedOffset.getEditText().getText().clear(); - etSeedOffset.setVisibility(View.GONE); - bSeedOffset.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_keyboard_arrow_down, 0, 0, 0); - } else { - etSeedOffset.setVisibility(View.VISIBLE); - bSeedOffset.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_keyboard_arrow_up, 0, 0, 0); - etSeedOffset.requestFocusFromTouch(); - } - } - - String type; - - private void acceptWallet() { - bAccept.setEnabled(false); - acceptCallback.onAccept(walletName, getPassword()); - } - - private class AsyncShow extends AsyncTask { - String name; - String address; - long height; - String seed; - String viewKey; - String spendKey; - Wallet.Status walletStatus; - - boolean dialogOpened = false; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - showProgress(); - if ((walletPath != null) - && (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword()) - == Wallet.Device.Device_Ledger) - && (progressCallback != null)) { - progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); - dialogOpened = true; - } - } - - @Override - protected Boolean doInBackground(String... params) { - if (params.length != 1) return false; - String walletPath = params[0]; - - Wallet wallet; - boolean closeWallet; - if (type.equals(GenerateReviewFragment.VIEW_TYPE_WALLET)) { - wallet = GenerateReviewFragment.this.walletCallback.getWallet(); - closeWallet = false; - } else { - wallet = WalletManager.getInstance().openWallet(walletPath, getPassword()); - closeWallet = true; - } - name = wallet.getName(); - walletStatus = wallet.getStatus(); - if (!walletStatus.isOk()) { - if (closeWallet) wallet.close(); - return false; - } - - address = wallet.getAddress(); - height = wallet.getRestoreHeight(); - seed = wallet.getSeed(getSeedOffset()); - switch (wallet.getDeviceType()) { - case Device_Ledger: - viewKey = Ledger.Key(); - break; - case Device_Software: - viewKey = wallet.getSecretViewKey(); - break; - default: - throw new IllegalStateException("Hardware backing not supported. At all!"); - } - spendKey = wallet.isWatchOnly() ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey(); - if (closeWallet) wallet.close(); - return true; - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if (dialogOpened) - progressCallback.dismissProgressDialog(); - if (!isAdded()) return; // never mind - walletName = name; - if (result) { - if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) { - bAccept.setVisibility(View.VISIBLE); - bAccept.setEnabled(true); - } - llPassword.setVisibility(View.VISIBLE); - tvWalletPassword.setText(getPassword()); - tvWalletAddress.setText(address); - tvWalletHeight.setText(NumberFormat.getInstance().format(height)); - if (!seed.isEmpty()) { - llMnemonic.setVisibility(View.VISIBLE); - tvWalletMnemonic.setText(seed); - } - boolean showAdvanced = false; - if (isKeyValid(viewKey)) { - llViewKey.setVisibility(View.VISIBLE); - tvWalletViewKey.setText(viewKey); - showAdvanced = true; - } - if (isKeyValid(spendKey)) { - llSpendKey.setVisibility(View.VISIBLE); - tvWalletSpendKey.setText(spendKey); - showAdvanced = true; - } - if (showAdvanced) bAdvancedInfo.setVisibility(View.VISIBLE); - bCopyAddress.setEnabled(true); - activityCallback.setTitle(name, getString(R.string.details_title)); - activityCallback.setToolbarButton( - GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK); - } else { - // TODO show proper error message and/or end the fragment? - tvWalletAddress.setText(walletStatus.toString()); - tvWalletHeight.setText(walletStatus.toString()); - tvWalletMnemonic.setText(walletStatus.toString()); - tvWalletViewKey.setText(walletStatus.toString()); - tvWalletSpendKey.setText(walletStatus.toString()); - } - hideProgress(); - } - } - - Listener activityCallback = null; - ProgressListener progressCallback = null; - AcceptListener acceptCallback = null; - ListenerWithWallet walletCallback = null; - PasswordChangedListener passwordCallback = null; - - public interface Listener { - void setTitle(String title, String subtitle); - - void setToolbarButton(int type); - } - - public interface ProgressListener { - void showProgressDialog(int msgId); - - void showLedgerProgressDialog(int mode); - - void dismissProgressDialog(); - } - - public interface AcceptListener { - void onAccept(String name, String password); - } - - public interface ListenerWithWallet { - Wallet getWallet(); - } - - public interface PasswordChangedListener { - void onPasswordChanged(String newPassword); - - String getPassword(); - } - - private String localPassword = null; - - private String getPassword() { - if (passwordCallback != null) return passwordCallback.getPassword(); - return localPassword; - } - - private void setPassword(String password) { - if (passwordCallback != null) passwordCallback.onPasswordChanged(password); - else localPassword = password; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof Listener) { - this.activityCallback = (Listener) context; - } - if (context instanceof ProgressListener) { - this.progressCallback = (ProgressListener) context; - } - if (context instanceof AcceptListener) { - this.acceptCallback = (AcceptListener) context; - } - if (context instanceof ListenerWithWallet) { - this.walletCallback = (ListenerWithWallet) context; - } - if (context instanceof PasswordChangedListener) { - this.passwordCallback = (PasswordChangedListener) context; - } - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activityCallback.setTitle(walletName, getString(R.string.details_title)); - activityCallback.setToolbarButton( - GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK); - } - - public void showProgress() { - pbProgress.setVisibility(View.VISIBLE); - } - - public void hideProgress() { - pbProgress.setVisibility(View.INVISIBLE); - } - - boolean backOk() { - return !type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - String type = getArguments().getString(REQUEST_TYPE); // intance variable not set yet - if (GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type)) { - inflater.inflate(R.menu.wallet_details_help_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } else { - inflater.inflate(R.menu.wallet_details_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - } - - boolean changeWalletPassword(String newPassword) { - Wallet wallet; - boolean closeWallet; - if (type.equals(GenerateReviewFragment.VIEW_TYPE_WALLET)) { - wallet = GenerateReviewFragment.this.walletCallback.getWallet(); - closeWallet = false; - } else { - wallet = WalletManager.getInstance().openWallet(walletPath, getPassword()); - closeWallet = true; - } - - boolean ok = false; - Wallet.Status walletStatus = wallet.getStatus(); - if (walletStatus.isOk()) { - wallet.setPassword(newPassword); - ok = true; - } - if (closeWallet) wallet.close(); - return ok; - } - - private class AsyncChangePassword extends AsyncTask { - String newPassword; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (progressCallback != null) - progressCallback.showProgressDialog(R.string.changepw_progress); - } - - @Override - protected Boolean doInBackground(String... params) { - if (params.length != 2) return false; - final String userPassword = params[0]; - final boolean fingerPassValid = Boolean.parseBoolean(params[1]); - newPassword = KeyStoreHelper.getCrazyPass(getActivity(), userPassword); - final boolean success = changeWalletPassword(newPassword); - if (success) { - Context ctx = getActivity(); - if (ctx != null) - if (fingerPassValid) { - KeyStoreHelper.saveWalletUserPass(ctx, walletName, userPassword); - } else { - KeyStoreHelper.removeWalletUserPass(ctx, walletName); - } - } - return success; - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if ((getActivity() == null) || getActivity().isDestroyed()) { - return; - } - if (progressCallback != null) - progressCallback.dismissProgressDialog(); - if (result) { - Toast.makeText(getActivity(), getString(R.string.changepw_success), Toast.LENGTH_SHORT).show(); - setPassword(newPassword); - showDetails(); - } else { - Toast.makeText(getActivity(), getString(R.string.changepw_failed), Toast.LENGTH_LONG).show(); - } - } - } - - AlertDialog openDialog = null; // for preventing opening of multiple dialogs - - public AlertDialog createChangePasswordDialog() { - if (openDialog != null) return null; // we are already open - LayoutInflater li = LayoutInflater.from(getActivity()); - View promptsView = li.inflate(R.layout.prompt_changepw, null); - - AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(requireActivity()); - alertDialogBuilder.setView(promptsView); - - final PasswordEntryView etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA); - etPasswordA.setHint(getString(R.string.prompt_changepw, walletName)); - - final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB); - etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName)); - - LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth); - final SwitchMaterial swFingerprintAllowed = (SwitchMaterial) llFingerprintAuth.getChildAt(0); - if (FingerprintHelper.isDeviceSupported(getActivity())) { - llFingerprintAuth.setVisibility(View.VISIBLE); - - swFingerprintAllowed.setOnClickListener(view -> { - if (!swFingerprintAllowed.isChecked()) return; - - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireActivity()); - builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn))) - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), null) - .setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - swFingerprintAllowed.setChecked(false); - } - }) - .show(); - }); - - swFingerprintAllowed.setChecked(FingerprintHelper.isFingerPassValid(getActivity(), walletName)); - } - - etPasswordA.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - if (etPasswordB.getError() != null) { - etPasswordB.setError(null); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - } - }); - - etPasswordB.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - if (etPasswordB.getError() != null) { - etPasswordB.setError(null); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - } - }); - - // set dialog message - alertDialogBuilder - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), null) - .setNegativeButton(getString(R.string.label_cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Helper.hideKeyboardAlways(requireActivity()); - dialog.cancel(); - openDialog = null; - } - }); - - openDialog = alertDialogBuilder.create(); - openDialog.setOnShowListener(dialog -> { - Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(view -> { - String newPasswordA = etPasswordA.getEditText().getText().toString(); - String newPasswordB = etPasswordB.getEditText().getText().toString(); - if (!newPasswordA.equals(newPasswordB)) { - etPasswordB.setError(getString(R.string.generate_bad_passwordB)); - } else { - new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); - Helper.hideKeyboardAlways(requireActivity()); - openDialog.dismiss(); - openDialog = null; - } - }); - }); - - // accept keyboard "ok" - etPasswordB.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - String newPasswordA = etPasswordA.getEditText().getText().toString(); - String newPasswordB = etPasswordB.getEditText().getText().toString(); - // disallow empty passwords - if (!newPasswordA.equals(newPasswordB)) { - etPasswordB.setError(getString(R.string.generate_bad_passwordB)); - } else { - new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); - Helper.hideKeyboardAlways(requireActivity()); - openDialog.dismiss(); - openDialog = null; - } - return true; - } - return false; - }); - - if (Helper.preventScreenshot()) { - openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - } - - return openDialog; - } - - private boolean isKeyValid(String key) { - return (key != null) && (key.length() == 64) - && !key.equals("0000000000000000000000000000000000000000000000000000000000000000") - && !key.toLowerCase().equals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - // ledger implmenetation returns the spend key as f's - } - - private class AsyncShowSeed extends AsyncTask { - String seed; - String offset; - Wallet.Status walletStatus; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - offset = getSeedOffset(); - flWalletMnemonic.setAlpha(0.1f); - } - - @Override - protected Boolean doInBackground(String... params) { - if (params.length != 1) return false; - String walletPath = params[0]; - - Wallet wallet; - boolean closeWallet; - if (type.equals(GenerateReviewFragment.VIEW_TYPE_WALLET)) { - wallet = GenerateReviewFragment.this.walletCallback.getWallet(); - closeWallet = false; - } else { - wallet = WalletManager.getInstance().openWallet(walletPath, getPassword()); - closeWallet = true; - } - walletStatus = wallet.getStatus(); - if (!walletStatus.isOk()) { - if (closeWallet) wallet.close(); - return false; - } - - seed = wallet.getSeed(offset); - if (closeWallet) wallet.close(); - return true; - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if (!isAdded()) return; // never mind - if (result) { - if (!seed.isEmpty()) { - llMnemonic.setVisibility(View.VISIBLE); - tvWalletMnemonic.setText(seed); - } - } else { - tvWalletMnemonic.setText(walletStatus.toString()); - } - seedOffsetInProgress = false; - if (!getSeedOffset().equals(offset)) { // make sure we have encrypted with the correct offset - showSeed(); // seed has changed in the meantime - recalc - } else - flWalletMnemonic.setAlpha(1); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java deleted file mode 100644 index 0cb2991..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ /dev/null @@ -1,1469 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - -import com.google.android.material.checkbox.MaterialCheckBox; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.m2049r.xmrwallet.data.DefaultNodes; -import com.m2049r.xmrwallet.data.Node; -import com.m2049r.xmrwallet.data.NodeInfo; -import com.m2049r.xmrwallet.dialog.CreditsFragment; -import com.m2049r.xmrwallet.dialog.HelpFragment; -import com.m2049r.xmrwallet.ledger.Ledger; -import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; -import com.m2049r.xmrwallet.model.NetworkType; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.WalletService; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.KeyStoreHelper; -import com.m2049r.xmrwallet.util.LegacyStorageHelper; -import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; -import com.m2049r.xmrwallet.util.NetCipherHelper; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.util.ZipBackup; -import com.m2049r.xmrwallet.util.ZipRestore; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import timber.log.Timber; - -public class LoginActivity extends BaseActivity - implements LoginFragment.Listener, GenerateFragment.Listener, - GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener, - NodeFragment.Listener, SettingsFragment.Listener { - private static final String GENERATE_STACK = "gen"; - - private static final String NODES_PREFS_NAME = "nodes"; - private static final String SELECTED_NODE_PREFS_NAME = "selected_node"; - private static final String PREF_DAEMON_TESTNET = "daemon_testnet"; - private static final String PREF_DAEMON_STAGENET = "daemon_stagenet"; - private static final String PREF_DAEMON_MAINNET = "daemon_mainnet"; - - private NodeInfo node = null; - - Set favouriteNodes = new HashSet<>(); - - @Override - public NodeInfo getNode() { - return node; - } - - @Override - public void setNode(NodeInfo node) { - setNode(node, true); - } - - private void setNode(NodeInfo node, boolean save) { - if (node != this.node) { - if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType())) - throw new IllegalArgumentException("network type does not match"); - this.node = node; - for (NodeInfo nodeInfo : favouriteNodes) { - nodeInfo.setSelected(nodeInfo == node); - } - WalletManager.getInstance().setDaemon(node); - if (save) - saveSelectedNode(); - } - } - - @Override - public Set getFavouriteNodes() { - return favouriteNodes; - } - - @Override - public Set getOrPopulateFavourites() { - if (favouriteNodes.isEmpty()) { - for (DefaultNodes node : DefaultNodes.values()) { - NodeInfo nodeInfo = NodeInfo.fromString(node.getUri()); - if (nodeInfo != null) { - nodeInfo.setFavourite(true); - favouriteNodes.add(nodeInfo); - } - } - saveFavourites(); - } - return favouriteNodes; - } - - @Override - public void setFavouriteNodes(Collection nodes) { - Timber.d("adding %d nodes", nodes.size()); - favouriteNodes.clear(); - for (NodeInfo node : nodes) { - Timber.d("adding %s %b", node, node.isFavourite()); - if (node.isFavourite()) - favouriteNodes.add(node); - } - saveFavourites(); - } - - private void loadFavouritesWithNetwork() { - Helper.runWithNetwork(() -> { - loadFavourites(); - return true; - }); - } - - private void loadFavourites() { - Timber.d("loadFavourites"); - favouriteNodes.clear(); - final String selectedNodeId = getSelectedNodeId(); - Map storedNodes = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).getAll(); - for (Map.Entry nodeEntry : storedNodes.entrySet()) { - if (nodeEntry != null) { // just in case, ignore possible future errors - final String nodeId = (String) nodeEntry.getValue(); - final NodeInfo addedNode = addFavourite(nodeId); - if (addedNode != null) { - if (nodeId.equals(selectedNodeId)) { - addedNode.setSelected(true); - } - } - } - } - if (storedNodes.isEmpty()) { // try to load legacy list & remove it (i.e. migrate the data once) - SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE); - switch (WalletManager.getInstance().getNetworkType()) { - case NetworkType_Mainnet: - loadLegacyList(sharedPref.getString(PREF_DAEMON_MAINNET, null)); - sharedPref.edit().remove(PREF_DAEMON_MAINNET).apply(); - break; - case NetworkType_Stagenet: - loadLegacyList(sharedPref.getString(PREF_DAEMON_STAGENET, null)); - sharedPref.edit().remove(PREF_DAEMON_STAGENET).apply(); - break; - case NetworkType_Testnet: - loadLegacyList(sharedPref.getString(PREF_DAEMON_TESTNET, null)); - sharedPref.edit().remove(PREF_DAEMON_TESTNET).apply(); - break; - default: - throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType()); - } - } - } - - private void saveFavourites() { - Timber.d("SAVE"); - SharedPreferences.Editor editor = getSharedPreferences(NODES_PREFS_NAME, Context.MODE_PRIVATE).edit(); - editor.clear(); - int i = 1; - for (Node info : favouriteNodes) { - final String nodeString = info.toNodeString(); - editor.putString(Integer.toString(i), nodeString); - Timber.d("saved %d:%s", i, nodeString); - i++; - } - editor.apply(); - } - - private NodeInfo addFavourite(String nodeString) { - final NodeInfo nodeInfo = NodeInfo.fromString(nodeString); - if (nodeInfo != null) { - nodeInfo.setFavourite(true); - favouriteNodes.add(nodeInfo); - } else - Timber.w("nodeString invalid: %s", nodeString); - return nodeInfo; - } - - private void loadLegacyList(final String legacyListString) { - if (legacyListString == null) return; - final String[] nodeStrings = legacyListString.split(";"); - for (final String nodeString : nodeStrings) { - addFavourite(nodeString); - } - } - - private void saveSelectedNode() { // save only if changed - final NodeInfo nodeInfo = getNode(); - final String selectedNodeId = getSelectedNodeId(); - if (nodeInfo != null) { - if (!nodeInfo.toNodeString().equals(selectedNodeId)) - saveSelectedNode(nodeInfo); - } else { - if (selectedNodeId != null) - saveSelectedNode(null); - } - } - - private void saveSelectedNode(NodeInfo nodeInfo) { - SharedPreferences.Editor editor = getSharedPreferences(SELECTED_NODE_PREFS_NAME, Context.MODE_PRIVATE).edit(); - if (nodeInfo == null) { - editor.clear(); - } else { - editor.putString("0", getNode().toNodeString()); - } - editor.apply(); - } - - private String getSelectedNodeId() { - return getSharedPreferences(SELECTED_NODE_PREFS_NAME, Context.MODE_PRIVATE) - .getString("0", null); - } - - - private Toolbar toolbar; - - @Override - public void setToolbarButton(int type) { - toolbar.setButton(type); - } - - @Override - public void setTitle(String title) { - toolbar.setTitle(title); - } - - @Override - public void setSubtitle(String subtitle) { - toolbar.setSubtitle(subtitle); - } - - @Override - public void setTitle(String title, String subtitle) { - toolbar.setTitle(title, subtitle); - } - - @Override - public boolean hasLedger() { - return Ledger.isConnected(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - Timber.d("onCreate()"); - ThemeHelper.setPreferred(this); - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_login); - toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayShowTitleEnabled(false); - - toolbar.setOnButtonListener(type -> { - switch (type) { - case Toolbar.BUTTON_BACK: - onBackPressed(); - break; - case Toolbar.BUTTON_CLOSE: - finish(); - break; - case Toolbar.BUTTON_SETTINGS: - startSettingsFragment(); - break; - case Toolbar.BUTTON_NONE: - break; - default: - Timber.e("Button " + type + "pressed - how can this be?"); - } - }); - - loadFavouritesWithNetwork(); - - LegacyStorageHelper.migrateWallets(this); - - if (savedInstanceState == null) startLoginFragment(); - - // try intents - Intent intent = getIntent(); - if (!processUsbIntent(intent)) - processUriIntent(intent); - } - - boolean checkServiceRunning() { - if (WalletService.Running) { - Toast.makeText(this, getString(R.string.service_busy), Toast.LENGTH_SHORT).show(); - return true; - } else { - return false; - } - } - - @Override - public boolean onWalletSelected(String walletName, boolean streetmode) { - if (node == null) { - Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show(); - return false; - } - if (checkServiceRunning()) return false; - try { - new AsyncOpenWallet(walletName, node, streetmode).execute(); - } catch (IllegalArgumentException ex) { - Timber.e(ex.getLocalizedMessage()); - Toast.makeText(this, ex.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); - return false; - } - return true; - } - - @Override - public void onWalletDetails(final String walletName) { - Timber.d("details for wallet .%s.", walletName); - if (checkServiceRunning()) return; - DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final File walletFile = Helper.getWalletFile(LoginActivity.this, walletName); - if (WalletManager.getInstance().walletExists(walletFile)) { - Helper.promptPassword(LoginActivity.this, walletName, true, new Helper.PasswordAction() { - @Override - public void act(String walletName1, String password, boolean fingerprintUsed) { - if (checkDevice(walletName1, password)) - startDetails(walletFile, password, GenerateReviewFragment.VIEW_TYPE_DETAILS); - } - - @Override - public void fail(String walletName) { - } - }); - } else { // this cannot really happen as we prefilter choices - Timber.e("Wallet missing: %s", walletName); - Toast.makeText(LoginActivity.this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); - } - break; - - case DialogInterface.BUTTON_NEGATIVE: - // do nothing - break; - } - }; - - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setMessage(getString(R.string.details_alert_message)) - .setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener) - .setNegativeButton(getString(R.string.details_alert_no), dialogClickListener) - .show(); - } - - private void renameWallet(String oldName, String newName) { - File walletFile = Helper.getWalletFile(this, oldName); - boolean success = renameWallet(walletFile, newName); - if (success) { - reloadWalletList(); - } else { - Toast.makeText(LoginActivity.this, getString(R.string.rename_failed), Toast.LENGTH_LONG).show(); - } - } - - // copy + delete seems safer than rename because we can rollback easily - boolean renameWallet(File walletFile, String newName) { - if (copyWallet(walletFile, new File(walletFile.getParentFile(), newName), false, true)) { - try { - KeyStoreHelper.copyWalletUserPass(this, walletFile.getName(), newName); - } catch (KeyStoreHelper.BrokenPasswordStoreException ex) { - Timber.w(ex); - } - deleteWallet(walletFile); // also deletes the keystore entry - return true; - } else { - return false; - } - } - - @Override - public void onWalletRename(final String walletName) { - Timber.d("rename for wallet ." + walletName + "."); - if (checkServiceRunning()) return; - LayoutInflater li = LayoutInflater.from(this); - View promptsView = li.inflate(R.layout.prompt_rename, null); - - AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(this); - alertDialogBuilder.setView(promptsView); - - final EditText etRename = promptsView.findViewById(R.id.etRename); - final TextView tvRenameLabel = promptsView.findViewById(R.id.tvRenameLabel); - - tvRenameLabel.setText(getString(R.string.prompt_rename, walletName)); - - // set dialog message - alertDialogBuilder - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), - (dialog, id) -> { - Helper.hideKeyboardAlways(LoginActivity.this); - String newName = etRename.getText().toString(); - renameWallet(walletName, newName); - }) - .setNegativeButton(getString(R.string.label_cancel), - (dialog, id) -> { - Helper.hideKeyboardAlways(LoginActivity.this); - dialog.cancel(); - }); - - final AlertDialog dialog = alertDialogBuilder.create(); - Helper.showKeyboard(dialog); - - // accept keyboard "ok" - etRename.setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - Helper.hideKeyboardAlways(LoginActivity.this); - String newName = etRename.getText().toString(); - dialog.cancel(); - renameWallet(walletName, newName); - return false; - } - return false; - }); - - dialog.show(); - } - - private static final int CREATE_BACKUP_INTENT = 4711; - private static final int RESTORE_BACKUP_INTENT = 4712; - private ZipBackup zipBackup; - - @Override - public void onWalletBackup(String walletName) { - Timber.d("backup for wallet ." + walletName + "."); - // overwrite any pending backup request - zipBackup = new ZipBackup(this, walletName); - - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_TITLE, zipBackup.getBackupName()); - startActivityForResult(intent, CREATE_BACKUP_INTENT); - } - - @Override - public void onWalletRestore() { - Timber.d("restore wallet"); - - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/zip"); - startActivityForResult(intent, RESTORE_BACKUP_INTENT); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == CREATE_BACKUP_INTENT) { - if (data == null) { - // nothing selected - Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show(); - zipBackup = null; - return; - } - try { - if (zipBackup == null) return; // ignore unsolicited request - zipBackup.writeTo(data.getData()); - Toast.makeText(this, getString(R.string.backup_success), Toast.LENGTH_SHORT).show(); - } catch (IOException ex) { - Timber.e(ex); - Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show(); - } finally { - zipBackup = null; - } - } else if (requestCode == RESTORE_BACKUP_INTENT) { - if (data == null) { - // nothing selected - Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show(); - return; - } - try { - ZipRestore zipRestore = new ZipRestore(this, data.getData()); - Toast.makeText(this, getString(R.string.menu_restore), Toast.LENGTH_SHORT).show(); - if (zipRestore.restore()) { - reloadWalletList(); - } else { - Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show(); - } - } catch (IOException ex) { - Timber.e(ex); - Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show(); - } - } - } - - @Override - public void onWalletDelete(final String walletName) { - Timber.d("delete for wallet ." + walletName + "."); - if (checkServiceRunning()) return; - DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - if (deleteWallet(Helper.getWalletFile(LoginActivity.this, walletName))) { - reloadWalletList(); - } else { - Toast.makeText(LoginActivity.this, getString(R.string.delete_failed), Toast.LENGTH_LONG).show(); - } - break; - case DialogInterface.BUTTON_NEGATIVE: - // do nothing - break; - } - }; - - final AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - final AlertDialog confirm = builder.setMessage(getString(R.string.delete_alert_message)) - .setTitle(walletName) - .setPositiveButton(getString(R.string.delete_alert_yes), dialogClickListener) - .setNegativeButton(getString(R.string.delete_alert_no), dialogClickListener) - .setView(View.inflate(builder.getContext(), R.layout.checkbox_confirm, null)) - .show(); - confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); - final MaterialCheckBox checkBox = confirm.findViewById(R.id.checkbox); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(isChecked); - }); - } - - @Override - public void onWalletDeleteCache(final String walletName) { - Timber.d("delete cache for wallet ." + walletName + "."); - if (checkServiceRunning()) return; - DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - if (!deleteWalletCache(Helper.getWalletFile(LoginActivity.this, walletName))) { - Toast.makeText(LoginActivity.this, getString(R.string.delete_failed), Toast.LENGTH_LONG).show(); - } - break; - case DialogInterface.BUTTON_NEGATIVE: - // do nothing - break; - } - }; - - final AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - final AlertDialog confirm = builder.setMessage(getString(R.string.deletecache_alert_message)) - .setTitle(walletName) - .setPositiveButton(getString(R.string.delete_alert_yes), dialogClickListener) - .setNegativeButton(getString(R.string.delete_alert_no), dialogClickListener) - .setView(View.inflate(builder.getContext(), R.layout.checkbox_confirm, null)) - .show(); - confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); - final MaterialCheckBox checkBox = confirm.findViewById(R.id.checkbox); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - confirm.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(isChecked); - }); - } - - void reloadWalletList() { - Timber.d("reloadWalletList()"); - try { - LoginFragment loginFragment = (LoginFragment) - getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (loginFragment != null) { - loginFragment.loadList(); - } - } catch (ClassCastException ex) { - Timber.w(ex); - } - } - - public void onWalletChangePassword() {//final String walletName, final String walletPassword) { - try { - GenerateReviewFragment detailsFragment = (GenerateReviewFragment) - getSupportFragmentManager().findFragmentById(R.id.fragment_container); - AlertDialog dialog = detailsFragment.createChangePasswordDialog(); - if (dialog != null) { - Helper.showKeyboard(dialog); - dialog.show(); - } - } catch (ClassCastException ex) { - Timber.w("onWalletChangePassword() called, but no GenerateReviewFragment active"); - } - } - - @Override - public void onAddWallet(String type) { - if (checkServiceRunning()) return; - startGenerateFragment(type); - } - - @Override - public void onNodePrefs() { - Timber.d("node prefs"); - if (checkServiceRunning()) return; - startNodeFragment(); - } - - //////////////////////////////////////// - // LoginFragment.Listener - //////////////////////////////////////// - - @Override - public File getStorageRoot() { - return Helper.getWalletRoot(getApplicationContext()); - } - - //////////////////////////////////////// - //////////////////////////////////////// - - @Override - public void showNet() { - showNet(WalletManager.getInstance().getNetworkType()); - } - - private void showNet(NetworkType net) { - switch (net) { - case NetworkType_Mainnet: - toolbar.setSubtitle(null); - toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet); - break; - case NetworkType_Testnet: - toolbar.setSubtitle(getString(R.string.connect_testnet)); - toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); - break; - case NetworkType_Stagenet: - toolbar.setSubtitle(getString(R.string.connect_stagenet)); - toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); - break; - default: - throw new IllegalStateException("NetworkType unknown: " + net); - } - } - - @Override - protected void onPause() { - Timber.d("onPause()"); - super.onPause(); - } - - @Override - protected void onDestroy() { - Timber.d("onDestroy"); - dismissProgressDialog(); - unregisterDetachReceiver(); - Ledger.disconnect(); - super.onDestroy(); - } - - @Override - protected void onResume() { - super.onResume(); - Timber.d("onResume()"); - // wait for WalletService to finish - if (WalletService.Running && (progressDialog == null)) { - // and show a progress dialog, but only if there isn't one already - new AsyncWaitForService().execute(); - } - if (!Ledger.isConnected()) attachLedger(); - registerTor(); - } - - private class AsyncWaitForService extends AsyncTask { - @Override - protected void onPreExecute() { - super.onPreExecute(); - showProgressDialog(R.string.service_progress); - } - - @Override - protected Void doInBackground(Void... params) { - try { - while (WalletService.Running & !isCancelled()) { - Thread.sleep(250); - } - } catch (InterruptedException ex) { - // oh well ... - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (isDestroyed()) { - return; - } - dismissProgressDialog(); - } - } - - void startWallet(String walletName, String walletPassword, - boolean fingerprintUsed, boolean streetmode) { - Timber.d("startWallet()"); - Intent intent = new Intent(getApplicationContext(), WalletActivity.class); - intent.putExtra(WalletActivity.REQUEST_ID, walletName); - intent.putExtra(WalletActivity.REQUEST_PW, walletPassword); - intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed); - intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode); - if (uri != null) { - intent.putExtra(WalletActivity.REQUEST_URI, uri); - uri = null; // use only once - } - startActivity(intent); - } - - void startDetails(File walletFile, String password, String type) { - Timber.d("startDetails()"); - Bundle b = new Bundle(); - b.putString("path", walletFile.getAbsolutePath()); - b.putString("password", password); - b.putString("type", type); - startReviewFragment(b); - } - - void startLoginFragment() { - // we set these here because we cannot be ceratin we have permissions for storage before - Helper.setMoneroHome(this); - Helper.initLogger(this); - Fragment fragment = new LoginFragment(); - getSupportFragmentManager().beginTransaction() - .add(R.id.fragment_container, fragment).commit(); - Timber.d("LoginFragment added"); - } - - void startGenerateFragment(String type) { - Bundle extras = new Bundle(); - extras.putString(GenerateFragment.TYPE, type); - replaceFragment(new GenerateFragment(), GENERATE_STACK, extras); - Timber.d("GenerateFragment placed"); - } - - void startReviewFragment(Bundle extras) { - replaceFragment(new GenerateReviewFragment(), null, extras); - Timber.d("GenerateReviewFragment placed"); - } - - void startNodeFragment() { - replaceFragment(new NodeFragment(), null, null); - Timber.d("NodeFragment placed"); - } - - void startSettingsFragment() { - replaceFragment(new SettingsFragment(), null, null); - Timber.d("SettingsFragment placed"); - } - - void replaceFragment(Fragment newFragment, String stackName, Bundle extras) { - if (extras != null) { - newFragment.setArguments(extras); - } - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragment_container, newFragment); - transaction.addToBackStack(stackName); - transaction.commit(); - } - - void popFragmentStack(String name) { - getSupportFragmentManager().popBackStack(name, FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - - ////////////////////////////////////////// - // GenerateFragment.Listener - ////////////////////////////////////////// - static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more - - private class AsyncCreateWallet extends AsyncTask { - final String walletName; - final String walletPassword; - final WalletCreator walletCreator; - - File newWalletFile; - - AsyncCreateWallet(final String name, final String password, - final WalletCreator walletCreator) { - super(); - this.walletName = name; - this.walletPassword = password; - this.walletCreator = walletCreator; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - acquireWakeLock(); - if (walletCreator.isLedger()) { - showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); - } else { - showProgressDialog(R.string.generate_wallet_creating); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - // check if the wallet we want to create already exists - File walletFolder = getStorageRoot(); - if (!walletFolder.isDirectory()) { - Timber.e("Wallet dir " + walletFolder.getAbsolutePath() + "is not a directory"); - return false; - } - File cacheFile = new File(walletFolder, walletName); - File keysFile = new File(walletFolder, walletName + ".keys"); - File addressFile = new File(walletFolder, walletName + ".address.txt"); - - if (cacheFile.exists() || keysFile.exists() || addressFile.exists()) { - Timber.e("Some wallet files already exist for %s", cacheFile.getAbsolutePath()); - return false; - } - - newWalletFile = new File(walletFolder, walletName); - boolean success = walletCreator.createWallet(newWalletFile, walletPassword); - if (success) { - return true; - } else { - Timber.e("Could not create new wallet in %s", newWalletFile.getAbsolutePath()); - return false; - } - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); - if (isDestroyed()) { - return; - } - dismissProgressDialog(); - if (result) { - startDetails(newWalletFile, walletPassword, GenerateReviewFragment.VIEW_TYPE_ACCEPT); - } else { - walletGenerateError(); - } - } - } - - public void createWallet(final String name, final String password, - final WalletCreator walletCreator) { - new AsyncCreateWallet(name, password, walletCreator) - .executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR); - } - - void walletGenerateError() { - try { - GenerateFragment genFragment = (GenerateFragment) - getSupportFragmentManager().findFragmentById(R.id.fragment_container); - genFragment.walletGenerateError(); - } catch (ClassCastException ex) { - Timber.e("walletGenerateError() but not in GenerateFragment"); - } - } - - interface WalletCreator { - boolean createWallet(File aFile, String password); - - boolean isLedger(); - - } - - boolean checkAndCloseWallet(Wallet aWallet) { - Wallet.Status walletStatus = aWallet.getStatus(); - if (!walletStatus.isOk()) { - Timber.e(walletStatus.getErrorString()); - toast(walletStatus.getErrorString()); - } - aWallet.close(); - return walletStatus.isOk(); - } - - @Override - public void onGenerate(final String name, final String password) { - createWallet(name, password, - new WalletCreator() { - @Override - public boolean isLedger() { - return false; - } - - @Override - public boolean createWallet(File aFile, String password) { - NodeInfo currentNode = getNode(); - // get it from the connected node if we have one - final long restoreHeight = - (currentNode != null) ? currentNode.getHeight() : -1; - Wallet newWallet = WalletManager.getInstance() - .createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight); - return checkAndCloseWallet(newWallet); - } - }); - } - - @Override - public void onGenerate(final String name, final String password, - final String seed, final String offset, - final long restoreHeight) { - createWallet(name, password, - new WalletCreator() { - @Override - public boolean isLedger() { - return false; - } - - @Override - public boolean createWallet(File aFile, String password) { - Wallet newWallet = WalletManager.getInstance() - .recoveryWallet(aFile, password, seed, offset, restoreHeight); - return checkAndCloseWallet(newWallet); - } - }); - } - - @Override - public void onGenerateLedger(final String name, final String password, - final long restoreHeight) { - createWallet(name, password, - new WalletCreator() { - @Override - public boolean isLedger() { - return true; - } - - @Override - public boolean createWallet(File aFile, String password) { - Wallet newWallet = WalletManager.getInstance() - .createWalletFromDevice(aFile, password, - restoreHeight, "Ledger"); - return checkAndCloseWallet(newWallet); - } - }); - } - - @Override - public void onGenerate(final String name, final String password, - final String address, final String viewKey, final String spendKey, - final long restoreHeight) { - createWallet(name, password, - new WalletCreator() { - @Override - public boolean isLedger() { - return false; - } - - @Override - public boolean createWallet(File aFile, String password) { - Wallet newWallet = WalletManager.getInstance() - .createWalletWithKeys(aFile, password, MNEMONIC_LANGUAGE, restoreHeight, - address, viewKey, spendKey); - return checkAndCloseWallet(newWallet); - } - }); - } - - private void toast(final String msg) { - runOnUiThread(() -> Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show()); - } - - private void toast(final int msgId) { - runOnUiThread(() -> Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show()); - } - - @Override - public void onAccept(final String name, final String password) { - File walletFolder = getStorageRoot(); - File walletFile = new File(walletFolder, name); - Timber.d("New Wallet %s", walletFile.getAbsolutePath()); - walletFile.delete(); // when recovering wallets, the cache seems corrupt - so remove it - - popFragmentStack(GENERATE_STACK); - Toast.makeText(LoginActivity.this, - getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show(); - } - - boolean walletExists(File walletFile, boolean any) { - File dir = walletFile.getParentFile(); - String name = walletFile.getName(); - if (any) { - return new File(dir, name).exists() - || new File(dir, name + ".keys").exists() - || new File(dir, name + ".address.txt").exists(); - } else { - return new File(dir, name).exists() - && new File(dir, name + ".keys").exists() - && new File(dir, name + ".address.txt").exists(); - } - } - - boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite, boolean ignoreCacheError) { - if (walletExists(dstWallet, true) && !overwrite) return false; - boolean success = false; - File srcDir = srcWallet.getParentFile(); - String srcName = srcWallet.getName(); - File dstDir = dstWallet.getParentFile(); - String dstName = dstWallet.getName(); - try { - copyFile(new File(srcDir, srcName + ".keys"), new File(dstDir, dstName + ".keys")); - try { // cache & address.txt are optional files - copyFile(new File(srcDir, srcName), new File(dstDir, dstName)); - copyFile(new File(srcDir, srcName + ".address.txt"), new File(dstDir, dstName + ".address.txt")); - } catch (IOException ex) { - Timber.d("CACHE %s", ignoreCacheError); - if (!ignoreCacheError) { // ignore cache backup error if backing up (can be resynced) - throw ex; - } - } - success = true; - } catch (IOException ex) { - Timber.e("wallet copy failed: %s", ex.getMessage()); - // try to rollback - deleteWallet(dstWallet); - } - return success; - } - - // do our best to delete as much as possible of the wallet files - boolean deleteWallet(File walletFile) { - Timber.d("deleteWallet %s", walletFile.getAbsolutePath()); - File dir = walletFile.getParentFile(); - String name = walletFile.getName(); - boolean success = true; - File cacheFile = new File(dir, name); - if (cacheFile.exists()) { - success = cacheFile.delete(); - } - success = new File(dir, name + ".keys").delete() && success; - File addressFile = new File(dir, name + ".address.txt"); - if (addressFile.exists()) { - success = addressFile.delete() && success; - } - Timber.d("deleteWallet is %s", success); - KeyStoreHelper.removeWalletUserPass(this, walletFile.getName()); - return success; - } - - boolean deleteWalletCache(File walletFile) { - Timber.d("deleteWalletCache %s", walletFile.getAbsolutePath()); - File dir = walletFile.getParentFile(); - String name = walletFile.getName(); - boolean success = true; - File cacheFile = new File(dir, name); - if (cacheFile.exists()) { - success = cacheFile.delete(); - } - return success; - } - - void copyFile(File src, File dst) throws IOException { - try (FileChannel inChannel = new FileInputStream(src).getChannel(); - FileChannel outChannel = new FileOutputStream(dst).getChannel()) { - inChannel.transferTo(0, inChannel.size(), outChannel); - } - } - - @Override - public void onBackPressed() { - Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (f instanceof GenerateReviewFragment) { - if (((GenerateReviewFragment) f).backOk()) { - super.onBackPressed(); - } - } else if (f instanceof NodeFragment) { - if (!((NodeFragment) f).isRefreshing()) { - super.onBackPressed(); - } else { - Toast.makeText(LoginActivity.this, getString(R.string.node_refresh_wait), Toast.LENGTH_LONG).show(); - } - } else if (f instanceof LoginFragment) { - if (((LoginFragment) f).isFabOpen()) { - ((LoginFragment) f).animateFAB(); - } else { - super.onBackPressed(); - } - } else { - super.onBackPressed(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final int id = item.getItemId(); - if (id == R.id.action_create_help_new) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_create_new); - return true; - } else if (id == R.id.action_create_help_keys) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_create_keys); - return true; - } else if (id == R.id.action_create_help_view) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_create_view); - return true; - } else if (id == R.id.action_create_help_seed) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed); - return true; - } else if (id == R.id.action_create_help_ledger) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger); - return true; - } else if (id == R.id.action_details_help) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_details); - return true; - } else if (id == R.id.action_details_changepw) { - onWalletChangePassword(); - return true; - } else if (id == R.id.action_help_list) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_list); - return true; - } else if (id == R.id.action_help_node) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_node); - return true; - } else if (id == R.id.action_default_nodes) { - Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) && - (f instanceof NodeFragment)) { - ((NodeFragment) f).restoreDefaultNodes(); - } - return true; - } else if (id == R.id.action_ledger_seed) { - Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (f instanceof GenerateFragment) { - ((GenerateFragment) f).convertLedgerSeed(); - } - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - // an AsyncTask which tests the node before trying to open the wallet - private class AsyncOpenWallet extends AsyncTask { - final static int OK = 0; - final static int TIMEOUT = 1; - final static int INVALID = 2; - final static int IOEX = 3; - - private final String walletName; - private final NodeInfo node; - private final boolean streetmode; - - AsyncOpenWallet(String walletName, NodeInfo node, boolean streetmode) { - this.walletName = walletName; - this.node = node; - this.streetmode = streetmode; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected Boolean doInBackground(Void... params) { - Timber.d("checking %s", node.getAddress()); - return node.testRpcService(); - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if (isDestroyed()) { - return; - } - if (result) { - Timber.d("selected wallet is .%s.", node.getName()); - // now it's getting real, onValidateFields if wallet exists - promptAndStart(walletName, streetmode); - } else { - if (node.getResponseCode() == 0) { // IOException - Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_node_invalid), Toast.LENGTH_LONG).show(); - } else { // connected but broken - Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_connect_ioex), Toast.LENGTH_LONG).show(); - } - } - } - - } - - boolean checkDevice(String walletName, String password) { - String keyPath = new File(Helper.getWalletRoot(LoginActivity.this), - walletName + ".keys").getAbsolutePath(); - // check if we need connected hardware - Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password); - if (device == Wallet.Device.Device_Ledger) { - if (!hasLedger()) { - toast(R.string.open_wallet_ledger_missing); - } else { - return true; - } - } else {// device could be undefined meaning the password is wrong - // this gets dealt with later - return true; - } - return false; - } - - void promptAndStart(String walletName, final boolean streetmode) { - File walletFile = Helper.getWalletFile(this, walletName); - if (WalletManager.getInstance().walletExists(walletFile)) { - Helper.promptPassword(LoginActivity.this, walletName, false, - new Helper.PasswordAction() { - @Override - public void act(String walletName, String password, boolean fingerprintUsed) { - if (checkDevice(walletName, password)) - startWallet(walletName, password, fingerprintUsed, streetmode); - } - - @Override - public void fail(String walletName) { - } - - }); - } else { // this cannot really happen as we prefilter choices - Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); - } - } - - // USB Stuff - (Ledger) - - private static final String ACTION_USB_PERMISSION = "com.m2049r.xmrwallet.USB_PERMISSION"; - - void attachLedger() { - final UsbManager usbManager = getUsbManager(); - UsbDevice device = Ledger.findDevice(usbManager); - if (device != null) { - if (usbManager.hasPermission(device)) { - connectLedger(usbManager, device); - } else { - registerReceiver(usbPermissionReceiver, new IntentFilter(ACTION_USB_PERMISSION)); - usbManager.requestPermission(device, - PendingIntent.getBroadcast(this, 0, - new Intent(ACTION_USB_PERMISSION), - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0)); - } - } else { - Timber.d("no ledger device found"); - } - } - - private final BroadcastReceiver usbPermissionReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (ACTION_USB_PERMISSION.equals(action)) { - unregisterReceiver(usbPermissionReceiver); - synchronized (this) { - UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { - if (device != null) { - connectLedger(getUsbManager(), device); - } - } else { - Timber.w("User denied permission for device %s", device.getProductName()); - } - } - } - } - }; - - private void connectLedger(UsbManager usbManager, final UsbDevice usbDevice) { - if (Ledger.ENABLED) - try { - Ledger.connect(usbManager, usbDevice); - if (!Ledger.check()) { - Ledger.disconnect(); - runOnUiThread(() -> Toast.makeText(LoginActivity.this, - getString(R.string.toast_ledger_start_app, usbDevice.getProductName()), - Toast.LENGTH_SHORT) - .show()); - } else { - registerDetachReceiver(); - onLedgerAction(); - runOnUiThread(() -> Toast.makeText(LoginActivity.this, - getString(R.string.toast_ledger_attached, usbDevice.getProductName()), - Toast.LENGTH_SHORT) - .show()); - } - } catch (IOException ex) { - runOnUiThread(() -> Toast.makeText(LoginActivity.this, - getString(R.string.open_wallet_ledger_missing), - Toast.LENGTH_SHORT) - .show()); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - processUsbIntent(intent); - } - - private boolean processUsbIntent(Intent intent) { - String action = intent.getAction(); - if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { - synchronized (this) { - final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - if (device != null) { - final UsbManager usbManager = getUsbManager(); - if (usbManager.hasPermission(device)) { - Timber.d("Ledger attached by intent"); - connectLedger(usbManager, device); - } - } - } - return true; - } - return false; - } - - private String uri = null; - - private void processUriIntent(Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_VIEW.equals(action)) { - synchronized (this) { - uri = intent.getDataString(); - Timber.d("URI Intent %s", uri); - HelpFragment.display(getSupportFragmentManager(), R.string.help_uri); - } - } - } - - BroadcastReceiver detachReceiver; - - private void unregisterDetachReceiver() { - if (detachReceiver != null) { - unregisterReceiver(detachReceiver); - detachReceiver = null; - } - } - - private void registerDetachReceiver() { - detachReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { - unregisterDetachReceiver(); - final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - Timber.i("Ledger detached!"); - if (device != null) - runOnUiThread(() -> Toast.makeText(LoginActivity.this, - getString(R.string.toast_ledger_detached, device.getProductName()), - Toast.LENGTH_SHORT) - .show()); - Ledger.disconnect(); - onLedgerAction(); - } - } - }; - - registerReceiver(detachReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED)); - } - - public void onLedgerAction() { - Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (f instanceof GenerateFragment) { - onBackPressed(); - } else if (f instanceof LoginFragment) { - if (((LoginFragment) f).isFabOpen()) { - ((LoginFragment) f).animateFAB(); - } - } - } - - // get UsbManager or die trying - @NonNull - private UsbManager getUsbManager() { - final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - if (usbManager == null) { - throw new IllegalStateException("no USB_SERVICE"); - } - return usbManager; - } - - // - // Tor (Orbot) stuff - // - - void torNotify() { - final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); - if (fragment == null) return; - - if (fragment instanceof LoginFragment) { - runOnUiThread(((LoginFragment) fragment)::showNetwork); - } - } - - private void deregisterTor() { - NetCipherHelper.deregister(); - } - - private void registerTor() { - NetCipherHelper.register(new NetCipherHelper.OnStatusChangedListener() { - @Override - public void connected() { - Timber.d("CONNECTED"); - WalletManager.getInstance().setProxy(NetCipherHelper.getProxy()); - torNotify(); - if (waitingUiTask != null) { - Timber.d("RUN"); - runOnUiThread(waitingUiTask); - waitingUiTask = null; - } - } - - @Override - public void disconnected() { - Timber.d("DISCONNECTED"); - WalletManager.getInstance().setProxy(""); - torNotify(); - } - - @Override - public void notInstalled() { - Timber.d("NOT INSTALLED"); - WalletManager.getInstance().setProxy(""); - torNotify(); - } - - @Override - public void notEnabled() { - Timber.d("NOT ENABLED"); - notInstalled(); - } - }); - } - - private Runnable waitingUiTask; - - @Override - public void runOnNetCipher(Runnable uiTask) { - if (waitingUiTask != null) throw new IllegalStateException("only one tor task at a time"); - if (NetCipherHelper.hasClient()) { - runOnUiThread(uiTask); - } else { - waitingUiTask = uiTask; - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java deleted file mode 100644 index 21b85be..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.Html; -import android.text.Spanned; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.progressindicator.CircularProgressIndicator; -import com.m2049r.xmrwallet.data.NodeInfo; -import com.m2049r.xmrwallet.dialog.HelpFragment; -import com.m2049r.xmrwallet.layout.WalletInfoAdapter; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.KeyStoreHelper; -import com.m2049r.xmrwallet.util.NetCipherHelper; -import com.m2049r.xmrwallet.util.NodePinger; -import com.m2049r.xmrwallet.util.Notice; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import timber.log.Timber; - -public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener, - View.OnClickListener { - - private WalletInfoAdapter adapter; - - private final List walletList = new ArrayList<>(); - - private View tvGuntherSays; - private ImageView ivGunther; - private TextView tvNodeName; - private TextView tvNodeInfo; - private ImageButton ibNetwork; - private CircularProgressIndicator pbNetwork; - - private Listener activityCallback; - - // Container Activity must implement this interface - public interface Listener { - File getStorageRoot(); - - boolean onWalletSelected(String wallet, boolean streetmode); - - void onWalletDetails(String wallet); - - void onWalletRename(String name); - - void onWalletBackup(String name); - - void onWalletRestore(); - - void onWalletDelete(String walletName); - - void onWalletDeleteCache(String walletName); - - void onAddWallet(String type); - - void onNodePrefs(); - - void showNet(); - - void setToolbarButton(int type); - - void setTitle(String title); - - void setNode(NodeInfo node); - - NodeInfo getNode(); - - Set getFavouriteNodes(); - - Set getOrPopulateFavourites(); - - boolean hasLedger(); - - void runOnNetCipher(Runnable runnable); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof Listener) { - this.activityCallback = (Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onPause() { - Timber.d("onPause()"); - torStatus = null; - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume() %s", activityCallback.getFavouriteNodes().size()); - activityCallback.setTitle(null); - activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS); - activityCallback.showNet(); - showNetwork(); - //activityCallback.runOnNetCipher(this::pingSelectedNode); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Timber.d("onCreateView"); - View view = inflater.inflate(R.layout.fragment_login, container, false); - - tvGuntherSays = view.findViewById(R.id.tvGuntherSays); - ivGunther = view.findViewById(R.id.ivGunther); - fabScreen = view.findViewById(R.id.fabScreen); - fab = view.findViewById(R.id.fab); - fabNew = view.findViewById(R.id.fabNew); - fabView = view.findViewById(R.id.fabView); - fabKey = view.findViewById(R.id.fabKey); - fabSeed = view.findViewById(R.id.fabSeed); - fabImport = view.findViewById(R.id.fabImport); - fabLedger = view.findViewById(R.id.fabLedger); - - fabNewL = view.findViewById(R.id.fabNewL); - fabViewL = view.findViewById(R.id.fabViewL); - fabKeyL = view.findViewById(R.id.fabKeyL); - fabSeedL = view.findViewById(R.id.fabSeedL); - fabImportL = view.findViewById(R.id.fabImportL); - fabLedgerL = view.findViewById(R.id.fabLedgerL); - - fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse); - fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen); - fab_close_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_close_screen); - fab_open = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open); - fab_close = AnimationUtils.loadAnimation(getContext(), R.anim.fab_close); - rotate_forward = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_forward); - rotate_backward = AnimationUtils.loadAnimation(getContext(), R.anim.rotate_backward); - fab.setOnClickListener(this); - fabNew.setOnClickListener(this); - fabView.setOnClickListener(this); - fabKey.setOnClickListener(this); - fabSeed.setOnClickListener(this); - fabImport.setOnClickListener(this); - fabLedger.setOnClickListener(this); - fabScreen.setOnClickListener(this); - - RecyclerView recyclerView = view.findViewById(R.id.list); - registerForContextMenu(recyclerView); - this.adapter = new WalletInfoAdapter(getActivity(), this); - recyclerView.setAdapter(adapter); - - ViewGroup llNotice = view.findViewById(R.id.llNotice); - Notice.showAll(llNotice, ".*_login"); - - view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs()); - tvNodeName = view.findViewById(R.id.tvNodeName); - tvNodeInfo = view.findViewById(R.id.tvInfo); - view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode()); - ibNetwork = view.findViewById(R.id.ibNetwork); - ibNetwork.setOnClickListener(v -> changeNetwork()); - ibNetwork.setEnabled(false); - pbNetwork = view.findViewById(R.id.pbNetwork); - - Helper.hideKeyboard(getActivity()); - - loadList(); - - return view; - } - - // Callbacks from WalletInfoAdapter - - // Wallet touched - @Override - public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) { - openWallet(infoItem.getName(), false); - } - - private void openWallet(String name, boolean streetmode) { - activityCallback.onWalletSelected(name, streetmode); - } - - @Override - public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) { - final int id = item.getItemId(); - if (id == R.id.action_streetmode) { - openWallet(listItem.getName(), true); - } else if (id == R.id.action_info) { - showInfo(listItem.getName()); - } else if (id == R.id.action_rename) { - activityCallback.onWalletRename(listItem.getName()); - } else if (id == R.id.action_backup) { - activityCallback.onWalletBackup(listItem.getName()); - } else if (id == R.id.action_archive) { - activityCallback.onWalletDelete(listItem.getName()); - } else if (id == R.id.action_deletecache) { - activityCallback.onWalletDeleteCache(listItem.getName()); - } else { - return super.onContextItemSelected(item); - } - return true; - } - - public void loadList() { - Timber.d("loadList()"); - WalletManager mgr = WalletManager.getInstance(); - walletList.clear(); - walletList.addAll(mgr.findWallets(activityCallback.getStorageRoot())); - adapter.setInfos(walletList); - - // deal with Gunther & FAB animation - if (walletList.isEmpty()) { - fab.startAnimation(fab_pulse); - if (ivGunther.getDrawable() == null) { - ivGunther.setImageResource(R.drawable.ic_emptygunther); - tvGuntherSays.setVisibility(View.VISIBLE); - } - } else { - fab.clearAnimation(); - if (ivGunther.getDrawable() != null) { - ivGunther.setImageDrawable(null); - } - tvGuntherSays.setVisibility(View.GONE); - } - - // remove information of non-existent wallet - Set removedWallets = getActivity() - .getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE) - .getAll().keySet(); - for (WalletManager.WalletInfo s : walletList) { - removedWallets.remove(s.getName()); - } - for (String name : removedWallets) { - KeyStoreHelper.removeWalletUserPass(getActivity(), name); - } - } - - private void showInfo(@NonNull String name) { - activityCallback.onWalletDetails(name); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.list_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - private boolean isFabOpen = false; - private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger; - private RelativeLayout fabScreen; - private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL; - private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen; - private Animation fab_pulse; - - public boolean isFabOpen() { - return isFabOpen; - } - - public void animateFAB() { - if (isFabOpen) { // close the fab - fabScreen.setClickable(false); - fabScreen.startAnimation(fab_close_screen); - fab.startAnimation(rotate_backward); - if (fabLedgerL.getVisibility() == View.VISIBLE) { - fabLedgerL.startAnimation(fab_close); - fabLedger.setClickable(false); - } else { - fabNewL.startAnimation(fab_close); - fabNew.setClickable(false); - fabViewL.startAnimation(fab_close); - fabView.setClickable(false); - fabKeyL.startAnimation(fab_close); - fabKey.setClickable(false); - fabSeedL.startAnimation(fab_close); - fabSeed.setClickable(false); - fabImportL.startAnimation(fab_close); - fabImport.setClickable(false); - } - isFabOpen = false; - } else { // open the fab - fabScreen.setClickable(true); - fabScreen.startAnimation(fab_open_screen); - fab.startAnimation(rotate_forward); - if (activityCallback.hasLedger()) { - fabLedgerL.setVisibility(View.VISIBLE); - fabNewL.setVisibility(View.GONE); - fabViewL.setVisibility(View.GONE); - fabKeyL.setVisibility(View.GONE); - fabSeedL.setVisibility(View.GONE); - fabImportL.setVisibility(View.GONE); - - fabLedgerL.startAnimation(fab_open); - fabLedger.setClickable(true); - } else { - fabLedgerL.setVisibility(View.GONE); - fabNewL.setVisibility(View.VISIBLE); - fabViewL.setVisibility(View.VISIBLE); - fabKeyL.setVisibility(View.VISIBLE); - fabSeedL.setVisibility(View.VISIBLE); - fabImportL.setVisibility(View.VISIBLE); - - fabNewL.startAnimation(fab_open); - fabNew.setClickable(true); - fabViewL.startAnimation(fab_open); - fabView.setClickable(true); - fabKeyL.startAnimation(fab_open); - fabKey.setClickable(true); - fabSeedL.startAnimation(fab_open); - fabSeed.setClickable(true); - fabImportL.startAnimation(fab_open); - fabImport.setClickable(true); - } - isFabOpen = true; - } - } - - @Override - public void onClick(View v) { - final int id = v.getId(); - Timber.d("onClick %d/%d", id, R.id.fabLedger); - if (id == R.id.fab) { - animateFAB(); - } else if (id == R.id.fabNew) { - fabScreen.setVisibility(View.INVISIBLE); - isFabOpen = false; - activityCallback.onAddWallet(GenerateFragment.TYPE_NEW); - } else if (id == R.id.fabView) { - animateFAB(); - activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY); - } else if (id == R.id.fabKey) { - animateFAB(); - activityCallback.onAddWallet(GenerateFragment.TYPE_KEY); - } else if (id == R.id.fabSeed) { - animateFAB(); - activityCallback.onAddWallet(GenerateFragment.TYPE_SEED); - } else if (id == R.id.fabImport) { - animateFAB(); - activityCallback.onWalletRestore(); - } else if (id == R.id.fabLedger) { - Timber.d("FAB_LEDGER"); - animateFAB(); - activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER); - } else if (id == R.id.fabScreen) { - animateFAB(); - } - } - - public void findBestNode() { - new AsyncFindBestNode().execute(AsyncFindBestNode.FIND_BEST); - } - - public void pingSelectedNode() { - new AsyncFindBestNode().execute(AsyncFindBestNode.PING_SELECTED); - } - - private NodeInfo autoselect(Set nodes) { - if (nodes.isEmpty()) return null; - NodePinger.execute(nodes, null); - List nodeList = new ArrayList<>(nodes); - Collections.sort(nodeList, NodeInfo.BestNodeComparator); - return nodeList.get(0); - } - - private void setSubtext(String status) { - final Context ctx = getContext(); - final Spanned text = Html.fromHtml(ctx.getString(R.string.status, - Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF), - Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF), - status, "")); - tvNodeInfo.setText(text); - } - - private class AsyncFindBestNode extends AsyncTask { - final static int PING_SELECTED = 0; - final static int FIND_BEST = 1; - - private boolean netState; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - tvNodeName.setVisibility(View.GONE); - pbNetwork.setVisibility(View.VISIBLE); - netState = ibNetwork.isClickable(); - ibNetwork.setClickable(false); - setSubtext(getString(R.string.node_waiting)); - } - - @Override - protected NodeInfo doInBackground(Integer... params) { - Set favourites = activityCallback.getOrPopulateFavourites(); - NodeInfo selectedNode; - if (params[0] == FIND_BEST) { - selectedNode = autoselect(favourites); - } else if (params[0] == PING_SELECTED) { - selectedNode = activityCallback.getNode(); - if (!activityCallback.getFavouriteNodes().contains(selectedNode)) - selectedNode = null; // it's not in the favourites (any longer) - if (selectedNode == null) - for (NodeInfo node : favourites) { - if (node.isSelected()) { - selectedNode = node; - break; - } - } - if (selectedNode == null) { // autoselect - selectedNode = autoselect(favourites); - } else { - selectedNode.testRpcService(); - } - } else throw new IllegalStateException(); - if ((selectedNode != null) && selectedNode.isValid()) { - activityCallback.setNode(selectedNode); - return selectedNode; - } else { - activityCallback.setNode(null); - return null; - } - } - - @Override - protected void onPostExecute(NodeInfo result) { - if (!isAdded()) return; - tvNodeName.setVisibility(View.VISIBLE); - pbNetwork.setVisibility(View.INVISIBLE); - ibNetwork.setClickable(netState); - if (result != null) { - Timber.d("found a good node %s", result.toString()); - showNode(result); - } else { - tvNodeName.setText(getResources().getText(R.string.node_create_hint)); - tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - tvNodeInfo.setText(null); - tvNodeInfo.setVisibility(View.GONE); - } - } - - @Override - protected void onCancelled(NodeInfo result) { //TODO: cancel this on exit from fragment - Timber.d("cancelled with %s", result); - } - } - - private void showNode(NodeInfo nodeInfo) { - tvNodeName.setText(nodeInfo.getName()); - nodeInfo.showInfo(tvNodeInfo); - tvNodeInfo.setVisibility(View.VISIBLE); - } - - private void startNodePrefs() { - activityCallback.onNodePrefs(); - } - - // Network (Tor) stuff - - private void changeNetwork() { - Timber.d("S: %s", NetCipherHelper.getStatus()); - final NetCipherHelper.Status status = NetCipherHelper.getStatus(); - if (status == NetCipherHelper.Status.NOT_INSTALLED) { - HelpFragment.display(requireActivity().getSupportFragmentManager(), R.string.help_tor); - } else if (status == NetCipherHelper.Status.NOT_ENABLED) { - Toast.makeText(getActivity(), getString(R.string.tor_enable_background), Toast.LENGTH_LONG).show(); - } else { - pbNetwork.setVisibility(View.VISIBLE); - ibNetwork.setEnabled(false); - NetCipherHelper.getInstance().toggle(); - } - } - - private NetCipherHelper.Status torStatus = null; - - void showNetwork() { - final NetCipherHelper.Status status = NetCipherHelper.getStatus(); - Timber.d("SHOW %s", status); - if (status == torStatus) return; - torStatus = status; - switch (status) { - case ENABLED: - ibNetwork.setImageResource(R.drawable.ic_network_tor_on); - ibNetwork.setEnabled(true); - ibNetwork.setClickable(true); - pbNetwork.setVisibility(View.INVISIBLE); - break; - case NOT_ENABLED: - case DISABLED: - ibNetwork.setImageResource(R.drawable.ic_network_clearnet); - ibNetwork.setEnabled(true); - ibNetwork.setClickable(true); - pbNetwork.setVisibility(View.INVISIBLE); - break; - case STARTING: - ibNetwork.setImageResource(R.drawable.ic_network_clearnet); - ibNetwork.setEnabled(false); - pbNetwork.setVisibility(View.VISIBLE); - break; - case STOPPING: - ibNetwork.setImageResource(R.drawable.ic_network_clearnet); - ibNetwork.setEnabled(false); - pbNetwork.setVisibility(View.VISIBLE); - break; - case NOT_INSTALLED: - ibNetwork.setEnabled(true); - ibNetwork.setClickable(true); - pbNetwork.setVisibility(View.INVISIBLE); - ibNetwork.setImageResource(R.drawable.ic_network_clearnet); - break; - default: - return; - } - activityCallback.runOnNetCipher(this::pingSelectedNode); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java b/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java index 5c7cc75..86601f5 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java @@ -1,39 +1,62 @@ -/* - * Copyright (c) 2018-2020 EarlOfEgo, m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.m2049r.xmrwallet; -import android.content.Intent; import android.os.Bundle; - -import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import com.m2049r.xmrwallet.model.TransactionInfo; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.MoneroHandlerThread; +import com.m2049r.xmrwallet.service.TxService; +import java.io.File; +import java.util.List; -import com.m2049r.xmrwallet.onboarding.OnBoardingActivity; -import com.m2049r.xmrwallet.onboarding.OnBoardingManager; +public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener { + private final MutableLiveData _address = new MutableLiveData<>(""); + public LiveData address = _address; + private final MutableLiveData _balance = new MutableLiveData<>(0L); + public LiveData balance = _balance; + private final MutableLiveData> _history = new MutableLiveData<>(); + public LiveData> history = _history; + + private MoneroHandlerThread thread = null; + private TxService txService = null; -public class MainActivity extends BaseActivity { @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (OnBoardingManager.shouldShowOnBoarding(getApplicationContext())) { - startActivity(new Intent(this, OnBoardingActivity.class)); - } else { - startActivity(new Intent(this, LoginActivity.class)); - } - finish(); + setContentView(R.layout.activity_main); + init(); } -} + + public MoneroHandlerThread getThread() { + return thread; + } + + private void init() { + File walletFile = new File(getApplicationInfo().dataDir, "xmr_wallet"); + Wallet wallet = null; + if(walletFile.exists()) { + wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), ""); + } else { + wallet = WalletManager.getInstance().createWallet(walletFile, "", "English", 0); + } + WalletManager.getInstance().setProxy("127.0.0.1:9050"); + thread = new MoneroHandlerThread("WalletService", wallet, this); + thread.start(); + this.txService = new TxService(this, thread); + } + + @Override + public void onRefresh() { + WalletManager walletManager = WalletManager.getInstance(); + Wallet wallet = walletManager.getWallet(); + if(wallet != null) { + String address = wallet.getLastSubaddress(0); + _history.postValue(wallet.getHistory().getAll()); + _balance.postValue(wallet.getBalance()); + _address.postValue(address); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java deleted file mode 100644 index 0bbc5a4..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.levin.scanner.Dispatcher; -import com.m2049r.xmrwallet.data.DefaultNodes; -import com.m2049r.xmrwallet.data.Node; -import com.m2049r.xmrwallet.data.NodeInfo; -import com.m2049r.xmrwallet.layout.NodeInfoAdapter; -import com.m2049r.xmrwallet.model.NetworkType; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.NodePinger; -import com.m2049r.xmrwallet.util.Notice; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.io.File; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.text.NumberFormat; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import timber.log.Timber; - -public class NodeFragment extends Fragment - implements NodeInfoAdapter.OnInteractionListener, View.OnClickListener { - - static private int NODES_TO_FIND = 10; - - static private NumberFormat FORMATTER = NumberFormat.getInstance(); - - private SwipeRefreshLayout pullToRefresh; - private TextView tvPull; - private View fab; - - private Set nodeList = new HashSet<>(); - - private NodeInfoAdapter nodesAdapter; - - private Listener activityCallback; - - public interface Listener { - File getStorageRoot(); - - void setToolbarButton(int type); - - void setSubtitle(String title); - - Set getFavouriteNodes(); - - Set getOrPopulateFavourites(); - - void setFavouriteNodes(Collection favouriteNodes); - - void setNode(NodeInfo node); - } - - void filterFavourites() { - for (Iterator iter = nodeList.iterator(); iter.hasNext(); ) { - Node node = iter.next(); - if (!node.isFavourite()) iter.remove(); - } - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof Listener) { - this.activityCallback = (Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onPause() { - Timber.d("onPause() %d", nodeList.size()); - if (asyncFindNodes != null) - asyncFindNodes.cancel(true); - if (activityCallback != null) - activityCallback.setFavouriteNodes(nodeList); - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activityCallback.setSubtitle(getString(R.string.label_nodes)); - updateRefreshElements(); - } - - boolean isRefreshing() { - return asyncFindNodes != null; - } - - void updateRefreshElements() { - if (isRefreshing()) { - activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); - fab.setVisibility(View.GONE); - } else { - activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); - fab.setVisibility(View.VISIBLE); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Timber.d("onCreateView"); - View view = inflater.inflate(R.layout.fragment_node, container, false); - - fab = view.findViewById(R.id.fab); - fab.setOnClickListener(this); - - RecyclerView recyclerView = view.findViewById(R.id.list); - nodesAdapter = new NodeInfoAdapter(getActivity(), this); - recyclerView.setAdapter(nodesAdapter); - - tvPull = view.findViewById(R.id.tvPull); - - pullToRefresh = view.findViewById(R.id.pullToRefresh); - pullToRefresh.setOnRefreshListener(() -> { - if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) { - refresh(AsyncFindNodes.SCAN); - } else { - Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show(); - pullToRefresh.setRefreshing(false); - } - }); - - Helper.hideKeyboard(getActivity()); - - nodeList = new HashSet<>(activityCallback.getFavouriteNodes()); - nodesAdapter.setNodes(nodeList); - - ViewGroup llNotice = view.findViewById(R.id.llNotice); - Notice.showAll(llNotice, ".*_nodes"); - - refresh(AsyncFindNodes.PING); // start connection tests - - return view; - } - - private AsyncFindNodes asyncFindNodes = null; - - private boolean refresh(int type) { - if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing - asyncFindNodes = new AsyncFindNodes(); - updateRefreshElements(); - asyncFindNodes.execute(type); - return true; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.node_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - // Callbacks from NodeInfoAdapter - @Override - public void onInteraction(final View view, final NodeInfo nodeItem) { - Timber.d("onInteraction"); - if (!nodeItem.isFavourite()) { - nodeItem.setFavourite(true); - activityCallback.setFavouriteNodes(nodeList); - } - AsyncTask.execute(() -> { - activityCallback.setNode(nodeItem); // this marks it as selected & saves it as well - nodeItem.setSelecting(false); - try { - requireActivity().runOnUiThread(() -> nodesAdapter.allowClick(true)); - } catch (IllegalStateException ex) { - // it's ok - } - }); - } - - // open up edit dialog - @Override - public boolean onLongInteraction(final View view, final NodeInfo nodeItem) { - Timber.d("onLongInteraction"); - EditDialog diag = createEditDialog(nodeItem); - if (diag != null) { - diag.show(); - } - return true; - } - - @Override - public void onClick(View v) { - int id = v.getId(); - if (id == R.id.fab) { - EditDialog diag = createEditDialog(null); - if (diag != null) { - diag.show(); - } - } - } - - private class AsyncFindNodes extends AsyncTask - implements NodePinger.Listener { - final static int SCAN = 0; - final static int RESTORE_DEFAULTS = 1; - final static int PING = 2; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - filterFavourites(); - nodesAdapter.setNodes(null); - nodesAdapter.allowClick(false); - tvPull.setText(getString(R.string.node_scanning)); - } - - @Override - protected Boolean doInBackground(Integer... params) { - if (params[0] == RESTORE_DEFAULTS) { // true = restore defaults - for (DefaultNodes node : DefaultNodes.values()) { - NodeInfo nodeInfo = NodeInfo.fromString(node.getUri()); - if (nodeInfo != null) { - nodeInfo.setFavourite(true); - nodeList.add(nodeInfo); - } - } - NodePinger.execute(nodeList, this); - return true; - } else if (params[0] == PING) { - NodePinger.execute(nodeList, this); - return true; - } else if (params[0] == SCAN) { - // otherwise scan the network - Timber.d("scanning"); - Set seedList = new HashSet<>(); - seedList.addAll(nodeList); - nodeList.clear(); - Timber.d("seed %d", seedList.size()); - Dispatcher d = new Dispatcher(info -> publishProgress(info)); - d.seedPeers(seedList); - d.awaitTermination(NODES_TO_FIND); - - // we didn't find enough because we didn't ask around enough? ask more! - if ((d.getRpcNodes().size() < NODES_TO_FIND) && - (d.getPeerCount() < NODES_TO_FIND + seedList.size())) { - // try again - publishProgress((NodeInfo[]) null); - d = new Dispatcher(new Dispatcher.Listener() { - @Override - public void onGet(NodeInfo info) { - publishProgress(info); - } - }); - // also seed with monero seed nodes (see p2p/net_node.inl:410 in monero src) - seedList.add(new NodeInfo(new InetSocketAddress("107.152.130.98", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("212.83.175.67", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("5.9.100.248", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("163.172.182.165", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("161.67.132.39", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080))); - seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080))); - d.seedPeers(seedList); - d.awaitTermination(NODES_TO_FIND); - } - // final (filtered) result - nodeList.addAll(d.getRpcNodes()); - return true; - } - return false; - } - - @Override - protected void onProgressUpdate(NodeInfo... values) { - Timber.d("onProgressUpdate"); - if (!isCancelled()) - if (values != null) - nodesAdapter.addNode(values[0]); - else - nodesAdapter.setNodes(null); - } - - @Override - protected void onPostExecute(Boolean result) { - Timber.d("done scanning"); - complete(); - } - - @Override - protected void onCancelled(Boolean result) { - Timber.d("cancelled scanning"); - complete(); - } - - private void complete() { - asyncFindNodes = null; - if (!isAdded()) return; - //if (isCancelled()) return; - tvPull.setText(getString(R.string.node_pull_hint)); - pullToRefresh.setRefreshing(false); - nodesAdapter.setNodes(nodeList); - nodesAdapter.allowClick(true); - updateRefreshElements(); - } - - public void publish(NodeInfo nodeInfo) { - publishProgress(nodeInfo); - } - } - - @Override - public void onDetach() { - Timber.d("detached"); - super.onDetach(); - } - - private EditDialog editDialog = null; // for preventing opening of multiple dialogs - - private EditDialog createEditDialog(final NodeInfo nodeInfo) { - if (editDialog != null) return null; // we are already open - editDialog = new EditDialog(nodeInfo); - return editDialog; - } - - class EditDialog { - final NodeInfo nodeInfo; - final NodeInfo nodeBackup; - - private boolean applyChanges() { - nodeInfo.clear(); - showTestResult(); - - final String portString = etNodePort.getEditText().getText().toString().trim(); - int port; - if (portString.isEmpty()) { - port = Node.getDefaultRpcPort(); - } else { - try { - port = Integer.parseInt(portString); - } catch (NumberFormatException ex) { - etNodePort.setError(getString(R.string.node_port_numeric)); - return false; - } - } - etNodePort.setError(null); - if ((port <= 0) || (port > 65535)) { - etNodePort.setError(getString(R.string.node_port_range)); - return false; - } - - final String host = etNodeHost.getEditText().getText().toString().trim(); - if (host.isEmpty()) { - etNodeHost.setError(getString(R.string.node_host_empty)); - return false; - } - final boolean setHostSuccess = Helper.runWithNetwork(() -> { - try { - nodeInfo.setHost(host); - return true; - } catch (UnknownHostException ex) { - return false; - } - }); - if (!setHostSuccess) { - etNodeHost.setError(getString(R.string.node_host_unresolved)); - return false; - } - etNodeHost.setError(null); - nodeInfo.setRpcPort(port); - // setName() may trigger reverse DNS - Helper.runWithNetwork(new Helper.Action() { - @Override - public boolean run() { - nodeInfo.setName(etNodeName.getEditText().getText().toString().trim()); - return true; - } - }); - nodeInfo.setUsername(etNodeUser.getEditText().getText().toString().trim()); - nodeInfo.setPassword(etNodePass.getEditText().getText().toString()); // no trim for pw - return true; - } - - private boolean shutdown = false; - - private void apply() { - if (applyChanges()) { - closeDialog(); - if (nodeBackup == null) { // this is a (FAB) new node - nodeInfo.setFavourite(true); - nodeList.add(nodeInfo); - } - shutdown = true; - new AsyncTestNode().execute(); - } - } - - private void closeDialog() { - if (editDialog == null) throw new IllegalStateException(); - Helper.hideKeyboardAlways(getActivity()); - editDialog.dismiss(); - editDialog = null; - NodeFragment.this.editDialog = null; - } - - private void show() { - editDialog.show(); - } - - private void test() { - if (applyChanges()) - new AsyncTestNode().execute(); - } - - private void showKeyboard() { - Helper.showKeyboard(editDialog); - } - - AlertDialog editDialog = null; - - TextInputLayout etNodeName; - TextInputLayout etNodeHost; - TextInputLayout etNodePort; - TextInputLayout etNodeUser; - TextInputLayout etNodePass; - TextView tvResult; - - void showTestResult() { - if (nodeInfo.isSuccessful()) { - tvResult.setText(getString(R.string.node_result, - FORMATTER.format(nodeInfo.getHeight()), nodeInfo.getMajorVersion(), - nodeInfo.getResponseTime(), nodeInfo.getHostAddress())); - } else { - tvResult.setText(NodeInfoAdapter.getResponseErrorText(getActivity(), nodeInfo.getResponseCode())); - } - } - - EditDialog(final NodeInfo nodeInfo) { - AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity()); - LayoutInflater li = LayoutInflater.from(alertDialogBuilder.getContext()); - View promptsView = li.inflate(R.layout.prompt_editnode, null); - alertDialogBuilder.setView(promptsView); - - etNodeName = promptsView.findViewById(R.id.etNodeName); - etNodeHost = promptsView.findViewById(R.id.etNodeHost); - etNodePort = promptsView.findViewById(R.id.etNodePort); - etNodeUser = promptsView.findViewById(R.id.etNodeUser); - etNodePass = promptsView.findViewById(R.id.etNodePass); - tvResult = promptsView.findViewById(R.id.tvResult); - - if (nodeInfo != null) { - this.nodeInfo = nodeInfo; - nodeBackup = new NodeInfo(nodeInfo); - etNodeName.getEditText().setText(nodeInfo.getName()); - etNodeHost.getEditText().setText(nodeInfo.getHost()); - etNodePort.getEditText().setText(Integer.toString(nodeInfo.getRpcPort())); - etNodeUser.getEditText().setText(nodeInfo.getUsername()); - etNodePass.getEditText().setText(nodeInfo.getPassword()); - showTestResult(); - } else { - this.nodeInfo = new NodeInfo(); - nodeBackup = null; - } - - // set dialog message - alertDialogBuilder - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), null) - .setNeutralButton(getString(R.string.label_test), null) - .setNegativeButton(getString(R.string.label_cancel), - (dialog, id) -> { - closeDialog(); - nodesAdapter.setNodes(); // to refresh test results - }); - - editDialog = alertDialogBuilder.create(); - // these need to be here, since we don't always close the dialog - editDialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(final DialogInterface dialog) { - Button testButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL); - testButton.setOnClickListener(view -> test()); - - Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(view -> apply()); - } - }); - - if (Helper.preventScreenshot()) { - editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - } - - etNodePass.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_DONE) { - editDialog.getButton(DialogInterface.BUTTON_NEUTRAL).requestFocus(); - test(); - return true; - } - return false; - }); - } - - private class AsyncTestNode extends AsyncTask { - @Override - protected void onPreExecute() { - super.onPreExecute(); - tvResult.setText(getString(R.string.node_testing, nodeInfo.getHostAddress())); - } - - @Override - protected Boolean doInBackground(Void... params) { - nodeInfo.testRpcService(); - return true; - } - - @Override - protected void onPostExecute(Boolean result) { - if (editDialog != null) { - showTestResult(); - } - if (shutdown) { - if (nodeBackup == null) { - nodesAdapter.addNode(nodeInfo); - } else { - nodesAdapter.setNodes(); - } - nodesAdapter.notifyItemChanged(nodeInfo); - } - } - } - } - - void restoreDefaultNodes() { - if (WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) { - if (!refresh(AsyncFindNodes.RESTORE_DEFAULTS)) { - Toast.makeText(getActivity(), getString(R.string.toast_default_nodes), Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(getActivity(), getString(R.string.node_wrong_net), Toast.LENGTH_LONG).show(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java b/app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java deleted file mode 100644 index eb09125..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/OnBackPressedListener.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -public interface OnBackPressedListener { - boolean onBackPressed(); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/OnBlockUpdateListener.java b/app/src/main/java/com/m2049r/xmrwallet/OnBlockUpdateListener.java deleted file mode 100644 index 242bea0..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/OnBlockUpdateListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import com.m2049r.xmrwallet.model.Wallet; - -public interface OnBlockUpdateListener { - void onBlockUpdate(final Wallet wallet); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/OnUriScannedListener.java b/app/src/main/java/com/m2049r/xmrwallet/OnUriScannedListener.java deleted file mode 100644 index 34fa1c5..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/OnUriScannedListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import com.m2049r.xmrwallet.data.BarcodeData; - -public interface OnUriScannedListener { - boolean onUriScanned(BarcodeData barcodeData); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java deleted file mode 100644 index 06621ff..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; -import android.net.Uri; -import android.nfc.NfcManager; -import android.os.Bundle; -import android.text.Editable; -import android.text.Html; -import android.text.InputType; -import android.text.Spanned; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.FileProvider; -import androidx.fragment.app.Fragment; - -import com.google.android.material.textfield.TextInputLayout; -import com.google.android.material.transition.MaterialContainerTransform; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.Crypto; -import com.m2049r.xmrwallet.data.Subaddress; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.ExchangeView; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import timber.log.Timber; - -public class ReceiveFragment extends Fragment { - - private ProgressBar pbProgress; - private TextView tvAddress; - private TextInputLayout etNotes; - private ExchangeView evAmount; - private TextView tvQrCode; - private ImageView ivQrCode; - private ImageView ivQrCodeFull; - private EditText etDummy; - private ImageButton bCopyAddress; - private MenuItem shareItem; - - private Wallet wallet = null; - private boolean isMyWallet = false; - - public interface Listener { - void setToolbarButton(int type); - - void setTitle(String title); - - void setSubtitle(String subtitle); - - void showSubaddresses(boolean managerMode); - - Subaddress getSelectedSubaddress(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View view = inflater.inflate(R.layout.fragment_receive, container, false); - - pbProgress = view.findViewById(R.id.pbProgress); - tvAddress = view.findViewById(R.id.tvAddress); - etNotes = view.findViewById(R.id.etNotes); - evAmount = view.findViewById(R.id.evAmount); - ivQrCode = view.findViewById(R.id.qrCode); - tvQrCode = view.findViewById(R.id.tvQrCode); - ivQrCodeFull = view.findViewById(R.id.qrCodeFull); - etDummy = view.findViewById(R.id.etDummy); - bCopyAddress = view.findViewById(R.id.bCopyAddress); - - etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - - bCopyAddress.setOnClickListener(v -> copyAddress()); - - evAmount.setOnNewAmountListener(xmr -> { - Timber.d("new amount = %s", xmr); - generateQr(); - if (shareRequested && (xmr != null)) share(); - }); - - evAmount.setOnFailedExchangeListener(() -> { - if (isAdded()) { - clearQR(); - Toast.makeText(getActivity(), getString(R.string.message_exchange_failed), Toast.LENGTH_LONG).show(); - } - }); - - final EditText notesEdit = etNotes.getEditText(); - notesEdit.setRawInputType(InputType.TYPE_CLASS_TEXT); - notesEdit.setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - generateQr(); - return true; - } - return false; - }); - notesEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - clearQR(); - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - - tvAddress.setOnClickListener(v -> { - listenerCallback.showSubaddresses(false); - }); - - view.findViewById(R.id.cvQrCode).setOnClickListener(v -> { - Helper.hideKeyboard(getActivity()); - etDummy.requestFocus(); - if (qrValid) { - ivQrCodeFull.setImageBitmap(((BitmapDrawable) ivQrCode.getDrawable()).getBitmap()); - ivQrCodeFull.setVisibility(View.VISIBLE); - } else { - evAmount.doExchange(); - } - }); - - ivQrCodeFull.setOnClickListener(v -> { - ivQrCodeFull.setImageBitmap(null); - ivQrCodeFull.setVisibility(View.GONE); - }); - - showProgress(); - clearQR(); - - if (getActivity() instanceof GenerateReviewFragment.ListenerWithWallet) { - wallet = ((GenerateReviewFragment.ListenerWithWallet) getActivity()).getWallet(); - show(); - } else { - throw new IllegalStateException("no wallet info"); - } - - View tvNfc = view.findViewById(R.id.tvNfc); - NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE); - if ((manager != null) && (manager.getDefaultAdapter() != null)) - tvNfc.setVisibility(View.VISIBLE); - - return view; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - final MaterialContainerTransform transform = new MaterialContainerTransform(); - transform.setDrawingViewId(R.id.fragment_container); - transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration)); - transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground)); - setSharedElementEnterTransition(transform); - } - - private boolean shareRequested = false; - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, final MenuInflater inflater) { - inflater.inflate(R.menu.receive_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - - shareItem = menu.findItem(R.id.menu_item_share); - shareItem.setOnMenuItemClickListener(item -> { - if (shareRequested) return true; - shareRequested = true; - if (!qrValid) { - evAmount.doExchange(); - } else { - share(); - } - return true; - }); - } - - private void share() { - shareRequested = false; - if (saveQrCode()) { - final Intent sendIntent = getSendIntent(); - if (sendIntent != null) - startActivity(Intent.createChooser(sendIntent, null)); - } else { - Toast.makeText(getActivity(), getString(R.string.message_qr_failed), Toast.LENGTH_SHORT).show(); - } - } - - private boolean saveQrCode() { - if (!qrValid) throw new IllegalStateException("trying to save null qr code!"); - - File cachePath = new File(getActivity().getCacheDir(), "images"); - if (!cachePath.exists()) - if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder"); - File png = new File(cachePath, "QR.png"); - try { - FileOutputStream stream = new FileOutputStream(png); - Bitmap qrBitmap = ((BitmapDrawable) ivQrCode.getDrawable()).getBitmap(); - qrBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - stream.close(); - return true; - } catch (IOException ex) { - Timber.e(ex); - // make sure we don't share an old qr code - if (!png.delete()) throw new IllegalStateException("cannot delete old qr code"); - // if we manage to delete it, the URI points to nothing and the user gets a toast with the error - } - return false; - } - - private Intent getSendIntent() { - File imagePath = new File(requireActivity().getCacheDir(), "images"); - File png = new File(imagePath, "QR.png"); - Uri contentUri = FileProvider.getUriForFile(requireActivity(), BuildConfig.APPLICATION_ID + ".fileprovider", png); - if (contentUri != null) { - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file - shareIntent.setTypeAndNormalize("image/png"); - shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); - if (bcData != null) - shareIntent.putExtra(Intent.EXTRA_TEXT, bcData.getUriString()); - return shareIntent; - } - return null; - } - - void copyAddress() { - Helper.clipBoardCopy(requireActivity(), getString(R.string.label_copy_address), subaddress.getAddress()); - Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); - } - - private boolean qrValid = false; - - void clearQR() { - if (qrValid) { - ivQrCode.setImageBitmap(null); - qrValid = false; - if (isLoaded) - tvQrCode.setVisibility(View.VISIBLE); - } - } - - void setQR(Bitmap qr) { - ivQrCode.setImageBitmap(qr); - qrValid = true; - tvQrCode.setVisibility(View.GONE); - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - listenerCallback.setToolbarButton(Toolbar.BUTTON_BACK); - if (wallet != null) { - listenerCallback.setTitle(wallet.getName()); - listenerCallback.setSubtitle(wallet.getAccountLabel()); - setNewSubaddress(); - } else { - listenerCallback.setSubtitle(getString(R.string.status_wallet_loading)); - clearQR(); - } - } - - private boolean isLoaded = false; - - private void show() { - Timber.d("name=%s", wallet.getName()); - isLoaded = true; - hideProgress(); - } - - public BarcodeData getBarcodeData() { - if (qrValid) - return bcData; - else - return null; - } - - private BarcodeData bcData = null; - - private void generateQr() { - Timber.d("GENQR"); - String address = subaddress.getAddress(); - String notes = etNotes.getEditText().getText().toString(); - String xmrAmount = evAmount.getAmount(); - Timber.d("%s/%s/%s", xmrAmount, notes, address); - if ((xmrAmount == null) || !Wallet.isAddressValid(address)) { - clearQR(); - Timber.d("CLEARQR"); - return; - } - bcData = new BarcodeData(Crypto.XMR, address, notes, xmrAmount); - int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight()); - Bitmap qr = generate(bcData.getUriString(), size, size); - if (qr != null) { - setQR(qr); - Timber.d("SETQR"); - etDummy.requestFocus(); - Helper.hideKeyboard(getActivity()); - } - } - - public Bitmap generate(String text, int width, int height) { - if ((width <= 0) || (height <= 0)) return null; - Map hints = new HashMap<>(); - hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); - try { - BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints); - int[] pixels = new int[width * height]; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (bitMatrix.get(j, i)) { - pixels[i * width + j] = 0x00000000; - } else { - pixels[i * height + j] = 0xffffffff; - } - } - } - Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565); - bitmap = addLogo(bitmap); - return bitmap; - } catch (WriterException ex) { - Timber.e(ex); - } - return null; - } - - private Bitmap addLogo(Bitmap qrBitmap) { - // addume logo & qrcode are both square - Bitmap logo = getMoneroLogo(); - final int qrSize = qrBitmap.getWidth(); - final int logoSize = logo.getWidth(); - - Bitmap logoBitmap = Bitmap.createBitmap(qrSize, qrSize, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(logoBitmap); - canvas.drawBitmap(qrBitmap, 0, 0, null); - canvas.save(); - final float sx = 0.2f * qrSize / logoSize; - canvas.scale(sx, sx, qrSize / 2f, qrSize / 2f); - canvas.drawBitmap(logo, (qrSize - logoSize) / 2f, (qrSize - logoSize) / 2f, null); - canvas.restore(); - return logoBitmap; - } - - private Bitmap logo = null; - - private Bitmap getMoneroLogo() { - if (logo == null) { - logo = Helper.getBitmap(getContext(), R.drawable.ic_monero_logo_b); - } - return logo; - } - - public void showProgress() { - pbProgress.setVisibility(View.VISIBLE); - } - - public void hideProgress() { - pbProgress.setVisibility(View.GONE); - } - - Listener listenerCallback = null; - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof Listener) { - this.listenerCallback = (Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onPause() { - Timber.d("onPause()"); - Helper.hideKeyboard(getActivity()); - super.onPause(); - } - - @Override - public void onDetach() { - Timber.d("onDetach()"); - if ((wallet != null) && (isMyWallet)) { - wallet.close(); - wallet = null; - isMyWallet = false; - } - super.onDetach(); - } - - private Subaddress subaddress = null; - - void setNewSubaddress() { - final Subaddress newSubaddress = listenerCallback.getSelectedSubaddress(); - if (!Objects.equals(subaddress, newSubaddress)) { - final Runnable resetSize = () -> tvAddress.animate().setDuration(125).scaleX(1).scaleY(1).start(); - tvAddress.animate().alpha(1).setDuration(125) - .scaleX(1.2f).scaleY(1.2f) - .withEndAction(resetSize).start(); - } - subaddress = newSubaddress; - final Context context = getContext(); - Spanned label = Html.fromHtml(context.getString(R.string.receive_subaddress, - Integer.toHexString(ThemeHelper.getThemedColor(context, R.attr.positiveColor) & 0xFFFFFF), - Integer.toHexString(ThemeHelper.getThemedColor(context, android.R.attr.colorBackground) & 0xFFFFFF), - subaddress.getDisplayLabel(), subaddress.getAddress())); - tvAddress.setText(label); - generateQr(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java deleted file mode 100644 index 32512fd..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2017 dm77, m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.Result; - -import me.dm7.barcodescanner.zxing.ZXingScannerView; -import timber.log.Timber; - -public class ScannerFragment extends Fragment implements ZXingScannerView.ResultHandler { - - private OnScannedListener onScannedListener; - - public interface OnScannedListener { - boolean onScanned(String qrCode); - } - - private ZXingScannerView mScannerView; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Timber.d("onCreateView"); - mScannerView = new ZXingScannerView(getActivity()); - return mScannerView; - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume"); - mScannerView.setResultHandler(this); - mScannerView.startCamera(); - } - - @Override - public void handleResult(Result rawResult) { - if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE)) { - if (onScannedListener.onScanned(rawResult.getText())) { - return; - } else { - Toast.makeText(getActivity(), getString(R.string.send_qr_address_invalid), Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(getActivity(), getString(R.string.send_qr_invalid), Toast.LENGTH_SHORT).show(); - } - - // Note from dm77: - // * Wait 2 seconds to resume the preview. - // * On older devices continuously stopping and resuming camera preview can result in freezing the app. - // * I don't know why this is the case but I don't have the time to figure out. - Handler handler = new Handler(); - handler.postDelayed(() -> mScannerView.resumeCameraPreview(ScannerFragment.this), 2000); - } - - @Override - public void onPause() { - Timber.d("onPause"); - mScannerView.stopCamera(); - super.onPause(); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof OnScannedListener) { - this.onScannedListener = (OnScannedListener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java b/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java deleted file mode 100644 index 238aeea..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/SecureActivity.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.content.res.Configuration; -import android.os.Build; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; - -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.LocaleHelper; - -import java.util.Locale; - -import static android.view.WindowManager.LayoutParams; - -public abstract class SecureActivity extends AppCompatActivity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (Helper.preventScreenshot()) { - getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE); - } - } - - @Override - protected void attachBaseContext(Context newBase) { - super.attachBaseContext(newBase); - applyOverrideConfiguration(new Configuration()); - } - - @Override - public void applyOverrideConfiguration(Configuration newConfig) { - super.applyOverrideConfiguration(updateConfigurationIfSupported(newConfig)); - } - - private Configuration updateConfigurationIfSupported(Configuration config) { - // Configuration.getLocales is added after 24 and Configuration.locale is deprecated in 24 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!config.getLocales().isEmpty()) { - return config; - } - } else { - if (config.locale != null) { - return config; - } - } - - Locale locale = LocaleHelper.getPreferredLocale(this); - if (locale != null) { - config.setLocale(locale); - } - return config; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SettingsFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SettingsFragment.java deleted file mode 100644 index 6a4bb5a..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/SettingsFragment.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.m2049r.xmrwallet; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; - -import androidx.annotation.StyleRes; -import androidx.preference.ListPreference; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; - -import com.m2049r.xmrwallet.dialog.AboutFragment; -import com.m2049r.xmrwallet.dialog.CreditsFragment; -import com.m2049r.xmrwallet.dialog.PrivacyFragment; -import com.m2049r.xmrwallet.util.DayNightMode; -import com.m2049r.xmrwallet.util.LocaleHelper; -import com.m2049r.xmrwallet.util.NightmodeHelper; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Locale; - -import timber.log.Timber; - -public class SettingsFragment extends PreferenceFragmentCompat - implements SharedPreferences.OnSharedPreferenceChangeListener { - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.root_preferences, rootKey); - - findPreference(getString(R.string.about_info)).setOnPreferenceClickListener(preference -> { - AboutFragment.display(getParentFragmentManager()); - return true; - }); - findPreference(getString(R.string.privacy_info)).setOnPreferenceClickListener(preference -> { - PrivacyFragment.display(getParentFragmentManager()); - return true; - }); - findPreference(getString(R.string.credits_info)).setOnPreferenceClickListener(preference -> { - CreditsFragment.display(getParentFragmentManager()); - return true; - }); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(getString(R.string.preferred_locale))) { - activity.recreate(); - } else if (key.equals(getString(R.string.preferred_nightmode))) { - NightmodeHelper.setNightMode(DayNightMode.valueOf(sharedPreferences.getString(key, "AUTO"))); - } else if (key.equals(getString(R.string.preferred_theme))) { - ThemeHelper.setTheme((Activity) activity, sharedPreferences.getString(key, "Classic")); - activity.recreate(); - } - } - - private SettingsFragment.Listener activity; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof SettingsFragment.Listener) { - activity = (SettingsFragment.Listener) context; - } else { - throw new ClassCastException(context + " must implement Listener"); - } - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activity.setSubtitle(getString(R.string.menu_settings)); - activity.setToolbarButton(Toolbar.BUTTON_BACK); - populateLanguages(); - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onPause() { - super.onPause(); - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .unregisterOnSharedPreferenceChangeListener(this); - } - - public interface Listener { - void setToolbarButton(int type); - - void setSubtitle(String title); - - void recreate(); - - void setTheme(@StyleRes final int resId); - } - - public void populateLanguages() { - ListPreference language = findPreference(getString(R.string.preferred_locale)); - assert language != null; - - final ArrayList availableLocales = LocaleHelper.getAvailableLocales(requireContext()); - Collections.sort(availableLocales, (locale1, locale2) -> { - String localeString1 = LocaleHelper.getDisplayName(locale1, true); - String localeString2 = LocaleHelper.getDisplayName(locale2, true); - return localeString1.compareTo(localeString2); - }); - - String[] localeDisplayNames = new String[1 + availableLocales.size()]; - localeDisplayNames[0] = getString(R.string.language_system_default); - for (int i = 1; i < localeDisplayNames.length; i++) { - localeDisplayNames[i] = LocaleHelper.getDisplayName(availableLocales.get(i - 1), true); - } - language.setEntries(localeDisplayNames); - - String[] languageTags = new String[1 + availableLocales.size()]; - languageTags[0] = ""; - for (int i = 1; i < languageTags.length; i++) { - languageTags[i] = availableLocales.get(i - 1).toLanguageTag(); - } - language.setEntryValues(languageTags); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java deleted file mode 100644 index c42ae7a..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; - -import com.m2049r.xmrwallet.data.Subaddress; -import com.m2049r.xmrwallet.layout.SubaddressInfoAdapter; -import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; -import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.util.ArrayList; -import java.util.List; - -import lombok.RequiredArgsConstructor; -import timber.log.Timber; - -public class SubaddressFragment extends Fragment implements SubaddressInfoAdapter.OnInteractionListener, - View.OnClickListener, OnBlockUpdateListener { - static public final String KEY_MODE = "mode"; - static public final String MODE_MANAGER = "manager"; - - private SubaddressInfoAdapter adapter; - - private Listener activityCallback; - - private Wallet wallet; - - // Container Activity must implement this interface - public interface Listener { - void onSubaddressSelected(Subaddress subaddress); - - void setSubtitle(String title); - - void setToolbarButton(int type); - - void showSubaddress(View view, final int subaddressIndex); - - void saveWallet(); - } - - public interface ProgressListener { - void showProgressDialog(int msgId); - - void showLedgerProgressDialog(int mode); - - void dismissProgressDialog(); - } - - private ProgressListener progressCallback = null; - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof ProgressListener) { - progressCallback = (ProgressListener) context; - } - if (context instanceof Listener) { - activityCallback = (Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onPause() { - Timber.d("onPause()"); - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - activityCallback.setSubtitle(getString(R.string.subbaddress_title)); - activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Timber.d("onCreateView"); - - final Bundle b = getArguments(); - managerMode = ((b != null) && (MODE_MANAGER.equals(b.getString(KEY_MODE)))); - - View view = inflater.inflate(R.layout.fragment_subaddress, container, false); - view.findViewById(R.id.fab).setOnClickListener(this); - - if (managerMode) { - view.findViewById(R.id.tvInstruction).setVisibility(View.GONE); - view.findViewById(R.id.tvHint).setVisibility(View.GONE); - } - - final RecyclerView list = view.findViewById(R.id.list); - adapter = new SubaddressInfoAdapter(getActivity(), this); - list.setAdapter(adapter); - adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - list.scrollToPosition(positionStart); - } - }); - - Helper.hideKeyboard(getActivity()); - - wallet = WalletManager.getInstance().getWallet(); - - loadList(); - - return view; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - } - - public void loadList() { - Timber.d("loadList()"); - final int numSubaddresses = wallet.getNumSubaddresses(); - final List list = new ArrayList<>(); - for (int i = 0; i < numSubaddresses; i++) { - list.add(wallet.getSubaddressObject(i)); - } - adapter.setInfos(list); - } - - @Override - public void onBlockUpdate(Wallet wallet) { - loadList(); - } - - @Override - public void onClick(View v) { - int id = v.getId(); - if (id == R.id.fab) { - getNewSubaddress(); - } - } - - private int lastUsedSubaddress() { - int lastUsedSubaddress = 0; - for (TransactionInfo info : wallet.getHistory().getAll()) { - if (info.addressIndex > lastUsedSubaddress) - lastUsedSubaddress = info.addressIndex; - } - return lastUsedSubaddress; - } - - private void getNewSubaddress() { - final int maxSubaddresses = lastUsedSubaddress() + wallet.getDeviceType().getSubaddressLookahead(); - if (wallet.getNumSubaddresses() < maxSubaddresses) - new AsyncSubaddress().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR); - else - Toast.makeText(getActivity(), getString(R.string.max_subaddress_warning), Toast.LENGTH_LONG).show(); - } - - @SuppressLint("StaticFieldLeak") - @RequiredArgsConstructor - private class AsyncSubaddress extends AsyncTask { - boolean dialogOpened = false; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) { - progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS); - dialogOpened = true; - } - } - - @Override - protected Boolean doInBackground(Void... params) { - if (params.length != 0) return false; - wallet.getNewSubaddress(); - if (activityCallback != null) { - activityCallback.saveWallet(); - } - return true; - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if (dialogOpened) - progressCallback.dismissProgressDialog(); - if (!isAdded()) // never mind then - return; - loadList(); - } - } - - boolean managerMode = false; - - // Callbacks from SubaddressInfoAdapter - @Override - public void onInteraction(final View view, final Subaddress subaddress) { - if (managerMode) - activityCallback.showSubaddress(view, subaddress.getAddressIndex()); - else - activityCallback.onSubaddressSelected(subaddress); // also closes the fragment with onBackpressed() - } - - @Override - public boolean onLongInteraction(View view, Subaddress subaddress) { - activityCallback.showSubaddress(view, subaddress.getAddressIndex()); - return false; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/SubaddressInfoFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SubaddressInfoFragment.java deleted file mode 100644 index 3b0941a..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/SubaddressInfoFragment.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; -import androidx.transition.Transition; -import androidx.transition.TransitionInflater; - -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.data.Subaddress; -import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; -import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.util.ArrayList; -import java.util.List; - -import timber.log.Timber; - -public class SubaddressInfoFragment extends Fragment - implements TransactionInfoAdapter.OnInteractionListener, OnBlockUpdateListener { - private TransactionInfoAdapter adapter; - - private Subaddress subaddress; - - private TextInputLayout etName; - private TextView tvAddress; - private TextView tvTxLabel; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false); - - etName = view.findViewById(R.id.etName); - tvAddress = view.findViewById(R.id.tvAddress); - tvTxLabel = view.findViewById(R.id.tvTxLabel); - - final RecyclerView list = view.findViewById(R.id.list); - adapter = new TransactionInfoAdapter(getActivity(), this); - list.setAdapter(adapter); - - final Wallet wallet = activityCallback.getWallet(); - - Bundle b = getArguments(); - final int subaddressIndex = b.getInt("subaddressIndex"); - subaddress = wallet.getSubaddressObject(subaddressIndex); - - etName.getEditText().setText(subaddress.getDisplayLabel()); - tvAddress.setText(getContext().getString(R.string.subbaddress_info_subtitle, - subaddress.getAddressIndex(), subaddress.getSquashedAddress())); - - etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString()); - } - }); - etName.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - Helper.hideKeyboard(getActivity()); - wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString()); - onRefreshed(wallet); - return true; - } - return false; - }); - - onRefreshed(wallet); - - return view; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Transition transform = TransitionInflater.from(requireContext()) - .inflateTransition(R.transition.details); - setSharedElementEnterTransition(transform); - } - - public void onRefreshed(final Wallet wallet) { - Timber.d("onRefreshed"); - List list = new ArrayList<>(); - for (TransactionInfo info : wallet.getHistory().getAll()) { - if (info.addressIndex == subaddress.getAddressIndex()) - list.add(info); - } - adapter.setInfos(list); - if (list.isEmpty()) - tvTxLabel.setText(R.string.subaddress_notx_label); - else - tvTxLabel.setText(R.string.subaddress_tx_label); - } - - @Override - public void onBlockUpdate(Wallet wallet) { - onRefreshed(wallet); - } - - // Callbacks from TransactionInfoAdapter - @Override - public void onInteraction(final View view, final TransactionInfo infoItem) { - activityCallback.onTxDetailsRequest(view, infoItem); - } - - Listener activityCallback; - - // Container Activity must implement this interface - public interface Listener { - void onTxDetailsRequest(View view, TransactionInfo info); - - Wallet getWallet(); - - void setToolbarButton(int type); - - void setTitle(String title, String subtitle); - - void setSubtitle(String subtitle); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof Listener) { - this.activityCallback = (Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activityCallback.setSubtitle(getString(R.string.subbaddress_title)); - activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); - } - - @Override - public void onPause() { - super.onPause(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java deleted file mode 100644 index 82be82b..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.graphics.Paint; -import android.net.Uri; -import android.os.Bundle; -import android.text.Html; -import android.text.InputType; -import android.text.Spanned; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.transition.Transition; -import androidx.transition.TransitionInflater; - -import com.m2049r.xmrwallet.data.Subaddress; -import com.m2049r.xmrwallet.data.UserNotes; -import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Transfer; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; -import java.util.TimeZone; - -import timber.log.Timber; - -public class TxFragment extends Fragment { - - static public final String ARG_INFO = "info"; - - private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); - - public TxFragment() { - super(); - Calendar cal = Calendar.getInstance(); - TimeZone tz = cal.getTimeZone(); //get the local time zone. - TS_FORMATTER.setTimeZone(tz); - } - - private TextView tvAccount; - private TextView tvAddress; - private TextView tvTxTimestamp; - private TextView tvTxId; - private TextView tvTxKey; - private TextView tvDestination; - private TextView tvTxPaymentId; - private TextView tvTxBlockheight; - private TextView tvTxAmount; - private TextView tvTxFee; - private TextView tvTxTransfers; - private TextView etTxNotes; - - // XMRTO stuff - private View cvXmrTo; - private TextView tvTxXmrToKey; - private TextView tvDestinationBtc; - private TextView tvTxAmountBtc; - private TextView tvXmrToSupport; - private TextView tvXmrToKeyLabel; - private ImageView tvXmrToLogo; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_tx_info, container, false); - - cvXmrTo = view.findViewById(R.id.cvXmrTo); - tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); - tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc); - tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc); - tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport); - tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel); - tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo); - - tvAccount = view.findViewById(R.id.tvAccount); - tvAddress = view.findViewById(R.id.tvAddress); - tvTxTimestamp = view.findViewById(R.id.tvTxTimestamp); - tvTxId = view.findViewById(R.id.tvTxId); - tvTxKey = view.findViewById(R.id.tvTxKey); - tvDestination = view.findViewById(R.id.tvDestination); - tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId); - tvTxBlockheight = view.findViewById(R.id.tvTxBlockheight); - tvTxAmount = view.findViewById(R.id.tvTxAmount); - tvTxFee = view.findViewById(R.id.tvTxFee); - tvTxTransfers = view.findViewById(R.id.tvTxTransfers); - etTxNotes = view.findViewById(R.id.etTxNotes); - - etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); - - tvTxXmrToKey.setOnClickListener(v -> { - Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show(); - }); - - info = getArguments().getParcelable(ARG_INFO); - show(); - return view; - } - - void shareTxInfo() { - if (this.info == null) return; - StringBuffer sb = new StringBuffer(); - - sb.append(getString(R.string.tx_timestamp)).append(":\n"); - sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n"); - - sb.append(getString(R.string.tx_amount)).append(":\n"); - sb.append((info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-")); - sb.append(Wallet.getDisplayAmount(info.amount)).append("\n"); - sb.append(getString(R.string.tx_fee)).append(":\n"); - sb.append(Wallet.getDisplayAmount(info.fee)).append("\n\n"); - - sb.append(getString(R.string.tx_notes)).append(":\n"); - String oneLineNotes = info.notes.replace("\n", " ; "); - sb.append(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n\n"); - - sb.append(getString(R.string.tx_destination)).append(":\n"); - sb.append(tvDestination.getText()).append("\n\n"); - - sb.append(getString(R.string.tx_paymentId)).append(":\n"); - sb.append(info.paymentId).append("\n\n"); - - sb.append(getString(R.string.tx_id)).append(":\n"); - sb.append(info.hash).append("\n"); - sb.append(getString(R.string.tx_key)).append(":\n"); - sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n\n"); - - sb.append(getString(R.string.tx_blockheight)).append(":\n"); - if (info.isFailed) { - sb.append(getString(R.string.tx_failed)).append("\n"); - } else if (info.isPending) { - sb.append(getString(R.string.tx_pending)).append("\n"); - } else { - sb.append(info.blockheight).append("\n"); - } - sb.append("\n"); - - sb.append(getString(R.string.tx_transfers)).append(":\n"); - if (info.transfers != null) { - boolean comma = false; - for (Transfer transfer : info.transfers) { - if (comma) { - sb.append(", "); - } else { - comma = true; - } - sb.append(transfer.address).append(": "); - sb.append(Wallet.getDisplayAmount(transfer.amount)); - } - } else { - sb.append("-"); - } - sb.append("\n\n"); - - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString()); - sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, null)); - } - - TransactionInfo info = null; - UserNotes userNotes = null; - - void loadNotes() { - if ((userNotes == null) || (info.notes == null)) { - info.notes = activityCallback.getTxNotes(info.hash); - } - userNotes = new UserNotes(info.notes); - etTxNotes.setText(userNotes.note); - } - - private void setTxColour(int clr) { - tvTxAmount.setTextColor(clr); - tvTxFee.setTextColor(clr); - } - - private void showSubaddressLabel() { - final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex); - final Context ctx = getContext(); - Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted, - info.accountIndex, info.addressIndex, - Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF), - Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF), - subaddress.getDisplayLabel())); - tvAccount.setText(label); - tvAccount.setOnClickListener(v -> activityCallback.showSubaddress(v, info.addressIndex)); - } - - private void show() { - if (info.txKey == null) { - info.txKey = activityCallback.getTxKey(info.hash); - } - if (info.address == null) { - info.address = activityCallback.getTxAddress(info.accountIndex, info.addressIndex); - } - loadNotes(); - - showSubaddressLabel(); - tvAddress.setText(info.address); - - tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000))); - tvTxId.setText(info.hash); - tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey); - tvTxPaymentId.setText(info.paymentId); - if (info.isFailed) { - tvTxBlockheight.setText(getString(R.string.tx_failed)); - } else if (info.isPending) { - tvTxBlockheight.setText(getString(R.string.tx_pending)); - } else { - tvTxBlockheight.setText("" + info.blockheight); - } - String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-"); - - long realAmount = info.amount; - tvTxAmount.setText(sign + Wallet.getDisplayAmount(realAmount)); - - if ((info.fee > 0)) { - String fee = Wallet.getDisplayAmount(info.fee); - tvTxFee.setText(getString(R.string.tx_list_fee, fee)); - } else { - tvTxFee.setText(null); - tvTxFee.setVisibility(View.GONE); - } - - if (info.isFailed) { - tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount))); - tvTxFee.setText(getString(R.string.tx_list_failed_text)); - setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor)); - } else if (info.isPending) { - setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor)); - } else if (info.direction == TransactionInfo.Direction.Direction_In) { - setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.positiveColor)); - } else { - setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.negativeColor)); - } - Set destinations = new HashSet<>(); - StringBuilder sb = new StringBuilder(); - StringBuilder dstSb = new StringBuilder(); - if (info.transfers != null) { - boolean newline = false; - for (Transfer transfer : info.transfers) { - destinations.add(transfer.address); - if (newline) { - sb.append("\n"); - } else { - newline = true; - } - sb.append("[").append(transfer.address.substring(0, 6)).append("] "); - sb.append(Wallet.getDisplayAmount(transfer.amount)); - } - newline = false; - for (String dst : destinations) { - if (newline) { - dstSb.append("\n"); - } else { - newline = true; - } - dstSb.append(dst); - } - } else { - sb.append("-"); - dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ? - activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex).getAddress() : - "-"); - } - tvTxTransfers.setText(sb.toString()); - tvDestination.setText(dstSb.toString()); - showBtcInfo(); - } - - @SuppressLint("SetTextI18n") - void showBtcInfo() { - if (userNotes.xmrtoKey != null) { - cvXmrTo.setVisibility(View.VISIBLE); - String key = userNotes.xmrtoKey; - if ("xmrto".equals(userNotes.xmrtoTag)) { // legacy xmr.to service :( - key = "xmrto-" + key; - } - tvTxXmrToKey.setText(key); - tvDestinationBtc.setText(userNotes.xmrtoDestination); - tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency); - switch (userNotes.xmrtoTag) { - case "xmrto": - tvXmrToSupport.setVisibility(View.GONE); - tvXmrToKeyLabel.setVisibility(View.INVISIBLE); - tvXmrToLogo.setImageResource(R.drawable.ic_xmrto_logo); - break; - case "side": // defaults in layout - just add underline - tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - tvXmrToSupport.setOnClickListener(v -> { - Uri uri = Uri.parse("https://sideshift.ai/orders/" + userNotes.xmrtoKey); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); - }); - break; - default: - tvXmrToSupport.setVisibility(View.GONE); - tvXmrToKeyLabel.setVisibility(View.INVISIBLE); - tvXmrToLogo.setVisibility(View.GONE); - } - } else { - cvXmrTo.setVisibility(View.GONE); - } - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - Transition transform = TransitionInflater.from(requireContext()) - .inflateTransition(R.transition.details); - setSharedElementEnterTransition(transform); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.tx_info_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - Listener activityCallback; - - public interface Listener { - Subaddress getWalletSubaddress(int accountIndex, int subaddressIndex); - - String getTxKey(String hash); - - String getTxNotes(String hash); - - boolean setTxNotes(String txId, String txNotes); - - String getTxAddress(int major, int minor); - - void setToolbarButton(int type); - - void setSubtitle(String subtitle); - - void showSubaddress(View view, final int subaddressIndex); - - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof TxFragment.Listener) { - this.activityCallback = (TxFragment.Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onPause() { - if (!etTxNotes.getText().toString().equals(userNotes.note)) { // notes have changed - // save them - userNotes.setNote(etTxNotes.getText().toString()); - info.notes = userNotes.txNotes; - activityCallback.setTxNotes(info.hash, info.notes); - } - Helper.hideKeyboard(getActivity()); - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activityCallback.setSubtitle(getString(R.string.tx_title)); - activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); - showSubaddressLabel(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java deleted file mode 100644 index 5ab6ad7..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ /dev/null @@ -1,1220 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.annotation.SuppressLint; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.IBinder; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AlertDialog; -import androidx.core.view.GravityCompat; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.navigation.NavigationView; -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.Subaddress; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.data.UserNotes; -import com.m2049r.xmrwallet.dialog.CreditsFragment; -import com.m2049r.xmrwallet.dialog.HelpFragment; -import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment; -import com.m2049r.xmrwallet.fragment.send.SendFragment; -import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.WalletService; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.util.ArrayList; -import java.util.List; - -import timber.log.Timber; - -public class WalletActivity extends BaseActivity implements WalletFragment.Listener, - WalletService.Observer, SendFragment.Listener, TxFragment.Listener, - GenerateReviewFragment.ListenerWithWallet, - GenerateReviewFragment.Listener, - GenerateReviewFragment.PasswordChangedListener, - ScannerFragment.OnScannedListener, ReceiveFragment.Listener, - SendAddressWizardFragment.OnScanListener, - WalletFragment.DrawerLocker, - NavigationView.OnNavigationItemSelectedListener, - SubaddressFragment.Listener, - SubaddressInfoFragment.Listener { - - public static final String REQUEST_ID = "id"; - public static final String REQUEST_PW = "pw"; - public static final String REQUEST_FINGERPRINT_USED = "fingerprint"; - public static final String REQUEST_STREETMODE = "streetmode"; - public static final String REQUEST_URI = "uri"; - - private NavigationView accountsView; - private DrawerLayout drawer; - private ActionBarDrawerToggle drawerToggle; - - private Toolbar toolbar; - private boolean requestStreetMode = false; - - private String password; - - private String uri = null; - - private long streetMode = 0; - - @Override - public void onPasswordChanged(String newPassword) { - password = newPassword; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public void setToolbarButton(int type) { - toolbar.setButton(type); - } - - @Override - public void setTitle(String title, String subtitle) { - toolbar.setTitle(title, subtitle); - } - - @Override - public void setTitle(String title) { - Timber.d("setTitle:%s.", title); - toolbar.setTitle(title); - } - - @Override - public void setSubtitle(String subtitle) { - toolbar.setSubtitle(subtitle); - } - - private boolean synced = false; - - @Override - public boolean isSynced() { - return synced; - } - - private WalletFragment getWalletFragment() { - return (WalletFragment) getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName()); - } - - private Fragment getCurrentFragment() { - return getSupportFragmentManager().findFragmentById(R.id.fragment_container); - } - - @Override - public boolean isStreetMode() { - return streetMode > 0; - } - - private void enableStreetMode(boolean enable) { - if (enable) { - streetMode = getWallet().getDaemonBlockChainHeight(); - } else { - streetMode = 0; - } - final WalletFragment walletFragment = getWalletFragment(); - if (walletFragment != null) walletFragment.resetDismissedTransactions(); - forceUpdate(); - runOnUiThread(() -> { - if (getWallet() != null) - updateAccountsBalance(); - }); - } - - @Override - public long getStreetModeHeight() { - return streetMode; - } - - @Override - public boolean isWatchOnly() { - return getWallet().isWatchOnly(); - } - - @Override - public String getTxKey(String txId) { - return getWallet().getTxKey(txId); - } - - @Override - public String getTxNotes(String txId) { - return getWallet().getUserNote(txId); - } - - @Override - public boolean setTxNotes(String txId, String txNotes) { - return getWallet().setUserNote(txId, txNotes); - } - - @Override - public String getTxAddress(int major, int minor) { - return getWallet().getSubaddress(major, minor); - } - - @Override - protected void onStart() { - super.onStart(); - Timber.d("onStart()"); - } - - private void startWalletService() { - Bundle extras = getIntent().getExtras(); - if (extras != null) { - acquireWakeLock(); - String walletId = extras.getString(REQUEST_ID); - // we can set the streetmode height AFTER opening the wallet - requestStreetMode = extras.getBoolean(REQUEST_STREETMODE); - password = extras.getString(REQUEST_PW); - uri = extras.getString(REQUEST_URI); - connectWalletService(walletId, password); - } else { - finish(); - } - } - - private void stopWalletService() { - disconnectWalletService(); - releaseWakeLock(); - } - - private void onWalletRescan() { - try { - final WalletFragment walletFragment = getWalletFragment(); - getWallet().rescanBlockchainAsync(); - synced = false; - walletFragment.unsync(); - invalidateOptionsMenu(); - } catch (ClassCastException ex) { - Timber.d(ex.getLocalizedMessage()); - // keep calm and carry on - } - } - - @Override - protected void onStop() { - Timber.d("onStop()"); - super.onStop(); - } - - @Override - protected void onDestroy() { - Timber.d("onDestroy()"); - if ((mBoundService != null) && (getWallet() != null)) { - saveWallet(); - } - stopWalletService(); - if (drawer != null) drawer.removeDrawerListener(drawerToggle); - super.onDestroy(); - } - - @Override - public boolean hasWallet() { - return haveWallet; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem renameItem = menu.findItem(R.id.action_rename); - if (renameItem != null) - renameItem.setEnabled(hasWallet() && getWallet().isSynchronized()); - MenuItem streetmodeItem = menu.findItem(R.id.action_streetmode); - if (streetmodeItem != null) - if (isStreetMode()) { - streetmodeItem.setIcon(R.drawable.gunther_csi_24dp); - } else { - streetmodeItem.setIcon(R.drawable.gunther_24dp); - } - final MenuItem rescanItem = menu.findItem(R.id.action_rescan); - if (rescanItem != null) - rescanItem.setEnabled(isSynced()); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == R.id.action_rescan) { - onWalletRescan(); - } else if (itemId == R.id.action_info) { - onWalletDetails(); - } else if (itemId == R.id.action_credits) { - CreditsFragment.display(getSupportFragmentManager()); - } else if (itemId == R.id.action_share) { - onShareTxInfo(); - } else if (itemId == R.id.action_help_tx_info) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_tx_details); - } else if (itemId == R.id.action_help_wallet) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_wallet); - } else if (itemId == R.id.action_details_help) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_details); - } else if (itemId == R.id.action_details_changepw) { - onWalletChangePassword(); - } else if (itemId == R.id.action_help_send) { - HelpFragment.display(getSupportFragmentManager(), R.string.help_send); - } else if (itemId == R.id.action_rename) { - onAccountRename(); - } else if (itemId == R.id.action_subaddresses) { - showSubaddresses(true); - } else if (itemId == R.id.action_streetmode) { - if (isStreetMode()) { // disable streetmode - onDisableStreetMode(); - } else { - onEnableStreetMode(); - } - } else - return super.onOptionsItemSelected(item); - return true; - } - - private void updateStreetMode() { - invalidateOptionsMenu(); - } - - private void onEnableStreetMode() { - enableStreetMode(true); - updateStreetMode(); - } - - private void onDisableStreetMode() { - Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() { - @Override - public void act(String walletName, String password, boolean fingerprintUsed) { - runOnUiThread(() -> { - enableStreetMode(false); - updateStreetMode(); - }); - } - - @Override - public void fail(String walletName) { - } - }); - } - - - public void onWalletChangePassword() { - try { - GenerateReviewFragment detailsFragment = (GenerateReviewFragment) getCurrentFragment(); - AlertDialog dialog = detailsFragment.createChangePasswordDialog(); - if (dialog != null) { - Helper.showKeyboard(dialog); - dialog.show(); - } - } catch (ClassCastException ex) { - Timber.w("onWalletChangePassword() called, but no GenerateReviewFragment active"); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - Timber.d("onCreate()"); - ThemeHelper.setPreferred(this); - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - // activity restarted - // we don't want that - finish it and fall back to previous activity - finish(); - return; - } - - setContentView(R.layout.activity_wallet); - toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayShowTitleEnabled(false); - - toolbar.setOnButtonListener(new Toolbar.OnButtonListener() { - @Override - public void onButton(int type) { - switch (type) { - case Toolbar.BUTTON_BACK: - onDisposeRequest(); - onBackPressed(); - break; - case Toolbar.BUTTON_CANCEL: - onDisposeRequest(); - Helper.hideKeyboard(WalletActivity.this); - WalletActivity.super.onBackPressed(); - break; - case Toolbar.BUTTON_CLOSE: - finish(); - break; - case Toolbar.BUTTON_SETTINGS: - Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show(); - case Toolbar.BUTTON_NONE: - default: - Timber.e("Button " + type + "pressed - how can this be?"); - } - } - }); - - drawer = findViewById(R.id.drawer_layout); - drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, 0, 0); - drawer.addDrawerListener(drawerToggle); - drawerToggle.syncState(); - setDrawerEnabled(false); // disable until synced - - accountsView = findViewById(R.id.accounts_nav); - accountsView.setNavigationItemSelectedListener(this); - - showNet(); - - Fragment walletFragment = new WalletFragment(); - getSupportFragmentManager().beginTransaction() - .add(R.id.fragment_container, walletFragment, WalletFragment.class.getName()).commit(); - Timber.d("fragment added"); - - startWalletService(); - Timber.d("onCreate() done."); - } - - public void showNet() { - switch (WalletManager.getInstance().getNetworkType()) { - case NetworkType_Mainnet: - toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet); - break; - case NetworkType_Stagenet: - case NetworkType_Testnet: - toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); - break; - default: - throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType()); - } - } - - @Override - public Wallet getWallet() { - if (mBoundService == null) throw new IllegalStateException("WalletService not bound."); - return mBoundService.getWallet(); - } - - private WalletService mBoundService = null; - private boolean mIsBound = false; - - private final ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - // This is called when the connection with the service has been - // established, giving us the service object we can use to - // interact with the service. Because we have bound to a explicit - // service that we know is running in our own process, we can - // cast its IBinder to a concrete class and directly access it. - mBoundService = ((WalletService.WalletServiceBinder) service).getService(); - mBoundService.setObserver(WalletActivity.this); - Bundle extras = getIntent().getExtras(); - if (extras != null) { - String walletId = extras.getString(REQUEST_ID); - if (walletId != null) { - setTitle(walletId, getString(R.string.status_wallet_connecting)); - } - } - updateProgress(); - Timber.d("CONNECTED"); - } - - public void onServiceDisconnected(ComponentName className) { - // This is called when the connection with the service has been - // unexpectedly disconnected -- that is, its process crashed. - // Because it is running in our same process, we should never - // see this happen. - mBoundService = null; - setTitle(getString(R.string.wallet_activity_name), getString(R.string.status_wallet_disconnected)); - Timber.d("DISCONNECTED"); - } - }; - - void connectWalletService(String walletName, String walletPassword) { - // Establish a connection with the service. We use an explicit - // class name because we want a specific service implementation that - // we know will be running in our own process (and thus won't be - // supporting component replacement by other applications). - Intent intent = new Intent(getApplicationContext(), WalletService.class); - intent.putExtra(WalletService.REQUEST_WALLET, walletName); - intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_LOAD); - intent.putExtra(WalletService.REQUEST_CMD_LOAD_PW, walletPassword); - startService(intent); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - mIsBound = true; - Timber.d("BOUND"); - } - - void disconnectWalletService() { - if (mIsBound) { - // Detach our existing connection. - mBoundService.setObserver(null); - unbindService(mConnection); - mIsBound = false; - Timber.d("UNBOUND"); - } - } - - @Override - protected void onPause() { - Timber.d("onPause()"); - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - Timber.d("onResume()"); - } - - @Override - public void saveWallet() { - if (mIsBound) { // no point in talking to unbound service - Intent intent = new Intent(getApplicationContext(), WalletService.class); - intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_STORE); - startService(intent); - Timber.d("STORE request sent"); - } else { - Timber.e("Service not bound"); - } - } - -////////////////////////////////////////// -// WalletFragment.Listener -////////////////////////////////////////// - - @Override - public boolean hasBoundService() { - return mBoundService != null; - } - - @Override - public Wallet.ConnectionStatus getConnectionStatus() { - return mBoundService.getConnectionStatus(); - } - - @Override - public long getDaemonHeight() { - return mBoundService.getDaemonHeight(); - } - - @Override - public void onSendRequest(View view) { - replaceFragment(SendFragment.newInstance(uri), null, null); - uri = null; // only use uri once - } - - @Override - public void onTxDetailsRequest(View view, TransactionInfo info) { - Bundle args = new Bundle(); - args.putParcelable(TxFragment.ARG_INFO, info); - replaceFragmentWithTransition(view, new TxFragment(), null, args); - } - - @Override - public void forceUpdate() { - try { - onRefreshed(getWallet(), true); - } catch (IllegalStateException ex) { - Timber.e(ex.getLocalizedMessage()); - } - } - -/////////////////////////// -// WalletService.Observer -/////////////////////////// - - private int numAccounts = -1; - - // refresh and return true if successful - @Override - public boolean onRefreshed(final Wallet wallet, final boolean full) { - Timber.d("onRefreshed()"); - runOnUiThread(() -> { - if (getWallet() != null) - updateAccountsBalance(); - }); - if (numAccounts != wallet.getNumAccounts()) { - numAccounts = wallet.getNumAccounts(); - runOnUiThread(this::updateAccountsList); - } - try { - final WalletFragment walletFragment = getWalletFragment(); - if (wallet.isSynchronized()) { - releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced - if (!synced) { // first sync - onProgress(-1); - saveWallet(); // save on first sync - synced = true; - runOnUiThread(walletFragment::onSynced); - } - } - runOnUiThread(() -> { - walletFragment.onRefreshed(wallet, full); - updateCurrentFragment(wallet); - }); - return true; - } catch (ClassCastException ex) { - // not in wallet fragment (probably send monero) - Timber.d(ex.getLocalizedMessage()); - // keep calm and carry on - } - return false; - } - - private void updateCurrentFragment(final Wallet wallet) { - final Fragment fragment = getCurrentFragment(); - if (fragment instanceof OnBlockUpdateListener) { - ((OnBlockUpdateListener) fragment).onBlockUpdate(wallet); - } - } - - @Override - public void onWalletStored(final boolean success) { - runOnUiThread(() -> { - if (!success) { - Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unload_failed), Toast.LENGTH_LONG).show(); - } - }); - } - - boolean haveWallet = false; - - @Override - public void onWalletOpen(final Wallet.Device device) { - if (device == Wallet.Device.Device_Ledger) { - runOnUiThread(() -> showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE)); - } - } - - @Override - public void onWalletStarted(final Wallet.Status walletStatus) { - runOnUiThread(() -> { - dismissProgressDialog(); - if (walletStatus == null) { - // guess what went wrong - Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show(); - } else { - if (Wallet.ConnectionStatus.ConnectionStatus_WrongVersion == walletStatus.getConnectionStatus()) - Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_wrongversion), Toast.LENGTH_LONG).show(); - else if (!walletStatus.isOk()) - Toast.makeText(WalletActivity.this, walletStatus.getErrorString(), Toast.LENGTH_LONG).show(); - } - }); - if ((walletStatus == null) || (Wallet.ConnectionStatus.ConnectionStatus_Connected != walletStatus.getConnectionStatus())) { - finish(); - } else { - haveWallet = true; - invalidateOptionsMenu(); - - if (requestStreetMode) onEnableStreetMode(); - - final WalletFragment walletFragment = getWalletFragment(); - runOnUiThread(() -> { - updateAccountsHeader(); - if (walletFragment != null) { - walletFragment.onLoaded(); - } - }); - } - } - - @Override - public void onTransactionCreated(final String txTag, final PendingTransaction pendingTransaction) { - try { - final SendFragment sendFragment = (SendFragment) getCurrentFragment(); - runOnUiThread(() -> { - dismissProgressDialog(); - PendingTransaction.Status status = pendingTransaction.getStatus(); - if (status != PendingTransaction.Status.Status_Ok) { - String errorText = pendingTransaction.getErrorString(); - getWallet().disposePendingTransaction(); - sendFragment.onCreateTransactionFailed(errorText); - } else { - sendFragment.onTransactionCreated(txTag, pendingTransaction); - } - }); - } catch (ClassCastException ex) { - // not in spend fragment - Timber.d(ex.getLocalizedMessage()); - // don't need the transaction any more - getWallet().disposePendingTransaction(); - } - } - - @Override - public void onSendTransactionFailed(final String error) { - try { - final SendFragment sendFragment = (SendFragment) getCurrentFragment(); - runOnUiThread(() -> sendFragment.onSendTransactionFailed(error)); - } catch (ClassCastException ex) { - // not in spend fragment - Timber.d(ex.getLocalizedMessage()); - } - } - - @Override - public void onTransactionSent(final String txId) { - try { - final SendFragment sendFragment = (SendFragment) getCurrentFragment(); - runOnUiThread(() -> sendFragment.onTransactionSent(txId)); - } catch (ClassCastException ex) { - // not in spend fragment - Timber.d(ex.getLocalizedMessage()); - } - } - - @Override - public void onProgress(final String text) { - try { - final WalletFragment walletFragment = getWalletFragment(); - runOnUiThread(new Runnable() { - public void run() { - walletFragment.setProgress(text); - } - }); - } catch (ClassCastException ex) { - // not in wallet fragment (probably send monero) - Timber.d(ex.getLocalizedMessage()); - // keep calm and carry on - } - } - - @Override - public void onProgress(final int n) { - runOnUiThread(() -> { - try { - WalletFragment walletFragment = getWalletFragment(); - if (walletFragment != null) - walletFragment.setProgress(n); - } catch (ClassCastException ex) { - // not in wallet fragment (probably send monero) - Timber.d(ex.getLocalizedMessage()); - // keep calm and carry on - } - }); - } - - private void updateProgress() { - // TODO maybe show real state of WalletService (like "still closing previous wallet") - if (hasBoundService()) { - onProgress(mBoundService.getProgressText()); - onProgress(mBoundService.getProgressValue()); - } - } - -/////////////////////////// -// SendFragment.Listener -/////////////////////////// - - @Override - public void onSend(UserNotes notes) { - if (mIsBound) { // no point in talking to unbound service - Intent intent = new Intent(getApplicationContext(), WalletService.class); - intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SEND); - intent.putExtra(WalletService.REQUEST_CMD_SEND_NOTES, notes.txNotes); - startService(intent); - Timber.d("SEND TX request sent"); - } else { - Timber.e("Service not bound"); - } - - } - - @Override - public void onPrepareSend(final String tag, final TxData txData) { - if (mIsBound) { // no point in talking to unbound service - Intent intent = new Intent(getApplicationContext(), WalletService.class); - intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_TX); - intent.putExtra(WalletService.REQUEST_CMD_TX_DATA, txData); - intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag); - startService(intent); - Timber.d("CREATE TX request sent"); - if (getWallet().getDeviceType() == Wallet.Device.Device_Ledger) - showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND); - } else { - Timber.e("Service not bound"); - } - } - - @Override - public Subaddress getWalletSubaddress(int accountIndex, int subaddressIndex) { - return getWallet().getSubaddressObject(accountIndex, subaddressIndex); - } - - public String getWalletName() { - return getWallet().getName(); - } - - void popFragmentStack(String name) { - if (name == null) { - getSupportFragmentManager().popBackStack(); - } else { - getSupportFragmentManager().popBackStack(name, FragmentManager.POP_BACK_STACK_INCLUSIVE); - } - } - - void replaceFragmentWithTransition(View view, Fragment newFragment, String stackName, Bundle extras) { - if (extras != null) { - newFragment.setArguments(extras); - } - int transition; - if (newFragment instanceof TxFragment) - transition = R.string.tx_details_transition_name; - else if (newFragment instanceof SubaddressInfoFragment) - transition = R.string.subaddress_info_transition_name; - else - throw new IllegalStateException("expecting known transition"); - - getSupportFragmentManager().beginTransaction() - .addSharedElement(view, getString(transition)) - .replace(R.id.fragment_container, newFragment) - .addToBackStack(stackName) - .commit(); - } - - void replaceFragment(Fragment newFragment, String stackName, Bundle extras) { - if (extras != null) { - newFragment.setArguments(extras); - } - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.fragment_container, newFragment) - .addToBackStack(stackName) - .commit(); - } - - private void onWalletDetails() { - DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final Bundle extras = new Bundle(); - extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET); - - Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() { - @Override - public void act(String walletName, String password, boolean fingerprintUsed) { - replaceFragment(new GenerateReviewFragment(), null, extras); - } - - @Override - public void fail(String walletName) { - } - }); - - break; - case DialogInterface.BUTTON_NEGATIVE: - // do nothing - break; - } - } - }; - - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setMessage(getString(R.string.details_alert_message)) - .setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener) - .setNegativeButton(getString(R.string.details_alert_no), dialogClickListener) - .show(); - } - - void onShareTxInfo() { - try { - TxFragment fragment = (TxFragment) getCurrentFragment(); - fragment.shareTxInfo(); - } catch (ClassCastException ex) { - // not in wallet fragment - Timber.e(ex.getLocalizedMessage()); - // keep calm and carry on - } - } - - @Override - public void onDisposeRequest() { - //TODO consider doing this through the WalletService to avoid concurrency issues - getWallet().disposePendingTransaction(); - } - - private boolean startScanFragment = false; - - @Override - protected void onResumeFragments() { - super.onResumeFragments(); - if (startScanFragment) { - startScanFragment(); - startScanFragment = false; - } - } - - private void startScanFragment() { - Bundle extras = new Bundle(); - replaceFragment(new ScannerFragment(), null, extras); - } - - /// QR scanner callbacks - @Override - public void onScan() { - if (Helper.getCameraPermission(this)) { - startScanFragment(); - } else { - Timber.i("Waiting for permissions"); - } - } - - @Override - public boolean onScanned(String qrCode) { - // #gurke - BarcodeData bcData = BarcodeData.fromString(qrCode); - if (bcData != null) { - popFragmentStack(null); - Timber.d("AAA"); - onUriScanned(bcData); - return true; - } else { - return false; - } - } - - OnUriScannedListener onUriScannedListener = null; - - @Override - public void setOnUriScannedListener(OnUriScannedListener onUriScannedListener) { - this.onUriScannedListener = onUriScannedListener; - } - - @Override - void onUriScanned(BarcodeData barcodeData) { - super.onUriScanned(barcodeData); - boolean processed = false; - if (onUriScannedListener != null) { - processed = onUriScannedListener.onUriScanned(barcodeData); - } - if (!processed || (onUriScannedListener == null)) { - Toast.makeText(this, getString(R.string.nfc_tag_read_what), Toast.LENGTH_LONG).show(); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - Timber.d("onRequestPermissionsResult()"); - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == Helper.PERMISSIONS_REQUEST_CAMERA) { // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startScanFragment = true; - } else { - String msg = getString(R.string.message_camera_not_permitted); - Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); - } - } - } - - @Override - public void onWalletReceive(View view) { - final String address = getWallet().getAddress(); - Timber.d("startReceive()"); - Bundle b = new Bundle(); - b.putString("address", address); - b.putString("name", getWalletName()); - replaceFragment(new ReceiveFragment(), null, b); - Timber.d("ReceiveFragment placed"); - } - - @Override - public long getTotalFunds() { - return getWallet().getUnlockedBalance(); - } - - @Override - public void onBackPressed() { - if (drawer.isDrawerOpen(GravityCompat.START)) { - drawer.closeDrawer(GravityCompat.START); - return; - } - final Fragment fragment = getCurrentFragment(); - if (fragment instanceof OnBackPressedListener) { - if (!((OnBackPressedListener) fragment).onBackPressed()) { - super.onBackPressed(); - } - } else { - super.onBackPressed(); - } - Helper.hideKeyboard(this); - } - - @Override - public void onFragmentDone() { - popFragmentStack(null); - } - - @Override - public SharedPreferences getPrefs() { - return getPreferences(Context.MODE_PRIVATE); - } - - private final List accountIds = new ArrayList<>(); - - // generate and cache unique ids for use in accounts list - private int getAccountId(int accountIndex) { - final int n = accountIds.size(); - for (int i = n; i <= accountIndex; i++) { - accountIds.add(View.generateViewId()); - } - return accountIds.get(accountIndex); - } - - // drawer stuff - - void updateAccountsBalance() { - final TextView tvBalance = accountsView.getHeaderView(0).findViewById(R.id.tvBalance); - if (!isStreetMode()) { - tvBalance.setText(getString(R.string.accounts_balance, - Helper.getDisplayAmount(getWallet().getBalanceAll(), 5))); - } else { - tvBalance.setText(null); - } - updateAccountsList(); - } - - void updateAccountsHeader() { - final Wallet wallet = getWallet(); - final TextView tvName = accountsView.getHeaderView(0).findViewById(R.id.tvName); - tvName.setText(wallet.getName()); - } - - void updateAccountsList() { - Menu menu = accountsView.getMenu(); - menu.removeGroup(R.id.accounts_list); - final Wallet wallet = getWallet(); - if (wallet != null) { - final int n = wallet.getNumAccounts(); - final boolean showBalances = (n > 1) && !isStreetMode(); - for (int i = 0; i < n; i++) { - final String label = (showBalances ? - getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2)) - : wallet.getAccountLabel(i)); - final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label); - item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp); - if (i == wallet.getAccountIndex()) - item.setChecked(true); - } - menu.setGroupCheckable(R.id.accounts_list, true, true); - } - } - - @Override - public void setDrawerEnabled(boolean enabled) { - Timber.d("setDrawerEnabled %b", enabled); - final int lockMode = enabled ? DrawerLayout.LOCK_MODE_UNLOCKED : - DrawerLayout.LOCK_MODE_LOCKED_CLOSED; - drawer.setDrawerLockMode(lockMode); - drawerToggle.setDrawerIndicatorEnabled(enabled); - invalidateOptionsMenu(); // menu may need to be changed - } - - void updateAccountName() { - setSubtitle(getWallet().getAccountLabel()); - updateAccountsList(); - } - - public void onAccountRename() { - final LayoutInflater li = LayoutInflater.from(this); - final View promptsView = li.inflate(R.layout.prompt_rename, null); - - final AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(this); - alertDialogBuilder.setView(promptsView); - - final EditText etRename = promptsView.findViewById(R.id.etRename); - final TextView tvRenameLabel = promptsView.findViewById(R.id.tvRenameLabel); - final Wallet wallet = getWallet(); - tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel())); - - // set dialog message - alertDialogBuilder - .setCancelable(false) - .setPositiveButton(getString(R.string.label_ok), - (dialog, id) -> { - Helper.hideKeyboardAlways(WalletActivity.this); - String newName = etRename.getText().toString(); - wallet.setAccountLabel(newName); - updateAccountName(); - }) - .setNegativeButton(getString(R.string.label_cancel), - (dialog, id) -> { - Helper.hideKeyboardAlways(WalletActivity.this); - dialog.cancel(); - }); - - final AlertDialog dialog = alertDialogBuilder.create(); - Helper.showKeyboard(dialog); - - // accept keyboard "ok" - etRename.setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - Helper.hideKeyboardAlways(WalletActivity.this); - String newName = etRename.getText().toString(); - dialog.cancel(); - wallet.setAccountLabel(newName); - updateAccountName(); - return false; - } - return false; - }); - - dialog.show(); - } - - public void setAccountIndex(int accountIndex) { - getWallet().setAccountIndex(accountIndex); - selectedSubaddressIndex = 0; - } - - @Override - public boolean onNavigationItemSelected(MenuItem item) { - final int id = item.getItemId(); - if (id == R.id.account_new) { - addAccount(); - } else { - Timber.d("NavigationDrawer ID=%d", id); - int accountIdx = accountIds.indexOf(id); - if (accountIdx >= 0) { - Timber.d("found @%d", accountIdx); - setAccountIndex(accountIdx); - } - forceUpdate(); - drawer.closeDrawer(GravityCompat.START); - } - return true; - } - - private int lastUsedAccount() { - int lastUsedAccount = 0; - for (TransactionInfo info : getWallet().getHistory().getAll()) { - if (info.accountIndex > lastUsedAccount) - lastUsedAccount = info.accountIndex; - } - return lastUsedAccount; - } - - private void addAccount() { - final Wallet wallet = getWallet(); - final int maxAccounts = lastUsedAccount() + wallet.getDeviceType().getAccountLookahead(); - if (wallet.getNumAccounts() < maxAccounts) - new AsyncAddAccount().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR); - else - Toast.makeText(this, getString(R.string.max_account_warning), Toast.LENGTH_LONG).show(); - } - - @SuppressLint("StaticFieldLeak") - private class AsyncAddAccount extends AsyncTask { - boolean dialogOpened = false; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - switch (getWallet().getDeviceType()) { - case Device_Ledger: - showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT); - dialogOpened = true; - break; - case Device_Software: - showProgressDialog(R.string.accounts_progress_new); - dialogOpened = true; - break; - default: - throw new IllegalStateException("Hardware backing not supported. At all!"); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - if (params.length != 0) return false; - getWallet().addAccount(); - setAccountIndex(getWallet().getNumAccounts() - 1); - return true; - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - forceUpdate(); - drawer.closeDrawer(GravityCompat.START); - if (dialogOpened) - dismissProgressDialog(); - Toast.makeText(WalletActivity.this, - getString(R.string.accounts_new, getWallet().getNumAccounts() - 1), - Toast.LENGTH_SHORT).show(); - } - } - - // we store the index only and always retrieve a new Subaddress object - // to ensure we get the current label - private int selectedSubaddressIndex = 0; - - @Override - public Subaddress getSelectedSubaddress() { - return getWallet().getSubaddressObject(selectedSubaddressIndex); - } - - @Override - public void onSubaddressSelected(@Nullable final Subaddress subaddress) { - selectedSubaddressIndex = subaddress.getAddressIndex(); - onBackPressed(); - } - - @Override - public void showSubaddresses(boolean managerMode) { - final Bundle b = new Bundle(); - if (managerMode) - b.putString(SubaddressFragment.KEY_MODE, SubaddressFragment.MODE_MANAGER); - replaceFragment(new SubaddressFragment(), null, b); - } - - @Override - public void showSubaddress(View view, final int subaddressIndex) { - final Bundle b = new Bundle(); - b.putInt("subaddressIndex", subaddressIndex); - replaceFragmentWithTransition(view, new SubaddressInfoFragment(), null, b); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java deleted file mode 100644 index 515d2d8..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.Spinner; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.RecyclerView; - -import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener; -import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; -import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ServiceHelper; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import timber.log.Timber; - -public class WalletFragment extends Fragment - implements TransactionInfoAdapter.OnInteractionListener { - private TransactionInfoAdapter adapter; - private final NumberFormat formatter = NumberFormat.getInstance(); - - private TextView tvStreetView; - private LinearLayout llBalance; - private FrameLayout flExchange; - private TextView tvBalance; - private TextView tvUnconfirmedAmount; - private TextView tvProgress; - private ImageView ivSynced; - private ProgressBar pbProgress; - private Button bReceive; - private Button bSend; - private ImageView ivStreetGunther; - private Drawable streetGunther = null; - RecyclerView txlist; - - private Spinner sCurrency; - - private final List dismissedTransactions = new ArrayList<>(); - - public void resetDismissedTransactions() { - dismissedTransactions.clear(); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - if (activityCallback.hasWallet()) - inflater.inflate(R.menu.wallet_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_wallet, container, false); - - ivStreetGunther = view.findViewById(R.id.ivStreetGunther); - tvStreetView = view.findViewById(R.id.tvStreetView); - llBalance = view.findViewById(R.id.llBalance); - flExchange = view.findViewById(R.id.flExchange); - ((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable(). - setColorFilter( - ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant), - android.graphics.PorterDuff.Mode.MULTIPLY); - - tvProgress = view.findViewById(R.id.tvProgress); - pbProgress = view.findViewById(R.id.pbProgress); - tvBalance = view.findViewById(R.id.tvBalance); - showBalance(Helper.getDisplayAmount(0)); - tvUnconfirmedAmount = view.findViewById(R.id.tvUnconfirmedAmount); - showUnconfirmed(0); - ivSynced = view.findViewById(R.id.ivSynced); - - sCurrency = view.findViewById(R.id.sCurrency); - List currencies = new ArrayList<>(); - currencies.add(Helper.BASE_CRYPTO); - if (Helper.SHOW_EXCHANGERATES) - currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency))); - ArrayAdapter spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies); - spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - sCurrency.setAdapter(spinnerAdapter); - - bSend = view.findViewById(R.id.bSend); - bReceive = view.findViewById(R.id.bReceive); - - txlist = view.findViewById(R.id.list); - adapter = new TransactionInfoAdapter(getActivity(), this); - txlist.setAdapter(adapter); - adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - if ((positionStart == 0) && (txlist.computeVerticalScrollOffset() == 0)) - txlist.scrollToPosition(positionStart); - } - }); - - txlist.addOnItemTouchListener( - new SwipeableRecyclerViewTouchListener(txlist, - new SwipeableRecyclerViewTouchListener.SwipeListener() { - @Override - public boolean canSwipeLeft(int position) { - return activityCallback.isStreetMode(); - } - - @Override - public boolean canSwipeRight(int position) { - return activityCallback.isStreetMode(); - } - - @Override - public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions) { - for (int position : reverseSortedPositions) { - dismissedTransactions.add(adapter.getItem(position).hash); - adapter.removeItem(position); - } - } - - @Override - public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) { - for (int position : reverseSortedPositions) { - dismissedTransactions.add(adapter.getItem(position).hash); - adapter.removeItem(position); - } - } - })); - - bSend.setOnClickListener(v -> activityCallback.onSendRequest(v)); - bReceive.setOnClickListener(v -> activityCallback.onWalletReceive(v)); - - sCurrency.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { - refreshBalance(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) - } - }); - - if (activityCallback.isSynced()) { - onSynced(); - } - - activityCallback.forceUpdate(); - - return view; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - } - - void showBalance(String balance) { - tvBalance.setText(balance); - final boolean streetMode = activityCallback.isStreetMode(); - if (!streetMode) { - llBalance.setVisibility(View.VISIBLE); - tvStreetView.setVisibility(View.INVISIBLE); - } else { - llBalance.setVisibility(View.INVISIBLE); - tvStreetView.setVisibility(View.VISIBLE); - } - setStreetModeBackground(streetMode); - } - - void showUnconfirmed(double unconfirmedAmount) { - if (activityCallback.isStreetMode() || unconfirmedAmount == 0) { - tvUnconfirmedAmount.setText(null); - tvUnconfirmedAmount.setVisibility(View.GONE); - } else { - String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true); - tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed)); - tvUnconfirmedAmount.setVisibility(View.VISIBLE); - } - } - - void updateBalance() { - if (isExchanging) return; // wait for exchange to finish - it will fire this itself then. - // at this point selection is XMR in case of error - String displayB; - double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue(); - if (!Helper.BASE_CRYPTO.equals(balanceCurrency)) { // not XMR - double amountB = amountA * balanceRate; - displayB = Helper.getFormattedAmount(amountB, false); - } else { // XMR - displayB = Helper.getFormattedAmount(amountA, true); - } - showBalance(displayB); - } - - String balanceCurrency = Helper.BASE_CRYPTO; - double balanceRate = 1.0; - - private final ExchangeApi exchangeApi = ServiceHelper.getExchangeApi(); - - void refreshBalance() { - double unconfirmedXmr = Helper.getDecimalAmount(balance - unlockedBalance).doubleValue(); - showUnconfirmed(unconfirmedXmr); - if (sCurrency.getSelectedItemPosition() == 0) { // XMR - double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue(); - showBalance(Helper.getFormattedAmount(amountXmr, true)); - } else { // not XMR - String currency = (String) sCurrency.getSelectedItem(); - Timber.d(currency); - if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) { - showExchanging(); - exchangeApi.queryExchangeRate(Helper.BASE_CRYPTO, currency, - new ExchangeCallback() { - @Override - public void onSuccess(final ExchangeRate exchangeRate) { - if (isAdded()) - new Handler(Looper.getMainLooper()).post(() -> exchange(exchangeRate)); - } - - @Override - public void onError(final Exception e) { - Timber.e(e.getLocalizedMessage()); - if (isAdded()) - new Handler(Looper.getMainLooper()).post(() -> exchangeFailed()); - } - }); - } else { - updateBalance(); - } - } - } - - boolean isExchanging = false; - - void showExchanging() { - isExchanging = true; - tvBalance.setVisibility(View.GONE); - flExchange.setVisibility(View.VISIBLE); - sCurrency.setEnabled(false); - } - - void hideExchanging() { - isExchanging = false; - tvBalance.setVisibility(View.VISIBLE); - flExchange.setVisibility(View.GONE); - sCurrency.setEnabled(true); - } - - public void exchangeFailed() { - sCurrency.setSelection(0, true); // default to XMR - double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue(); - showBalance(Helper.getFormattedAmount(amountXmr, true)); - hideExchanging(); - } - - public void exchange(final ExchangeRate exchangeRate) { - hideExchanging(); - if (!Helper.BASE_CRYPTO.equals(exchangeRate.getBaseCurrency())) { - Timber.e("Not XMR"); - sCurrency.setSelection(0, true); - balanceCurrency = Helper.BASE_CRYPTO; - balanceRate = 1.0; - } else { - int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency()); - if (spinnerPosition < 0) { // requested currency not in list - Timber.e("Requested currency not in list %s", exchangeRate.getQuoteCurrency()); - sCurrency.setSelection(0, true); - } else { - sCurrency.setSelection(spinnerPosition, true); - } - balanceCurrency = exchangeRate.getQuoteCurrency(); - balanceRate = exchangeRate.getRate(); - } - updateBalance(); - } - - // Callbacks from TransactionInfoAdapter - @Override - public void onInteraction(final View view, final TransactionInfo infoItem) { - activityCallback.onTxDetailsRequest(view, infoItem); - } - - // if account index has changed scroll to top? - private int accountIndex = 0; - - public void onRefreshed(final Wallet wallet, boolean full) { - Timber.d("onRefreshed(%b)", full); - - if (adapter.needsTransactionUpdateOnNewBlock()) { - wallet.refreshHistory(); - full = true; - } - if (full) { - List list = new ArrayList<>(); - final long streetHeight = activityCallback.getStreetModeHeight(); - Timber.d("StreetHeight=%d", streetHeight); - wallet.refreshHistory(); - for (TransactionInfo info : wallet.getHistory().getAll()) { - Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel); - if ((info.isPending || (info.blockheight >= streetHeight)) - && !dismissedTransactions.contains(info.hash)) - list.add(info); - } - adapter.setInfos(list); - if (accountIndex != wallet.getAccountIndex()) { - accountIndex = wallet.getAccountIndex(); - txlist.scrollToPosition(0); - } - } - updateStatus(wallet); - } - - public void onSynced() { - if (!activityCallback.isWatchOnly()) { - bSend.setVisibility(View.VISIBLE); - bSend.setEnabled(true); - } - if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume() - } - - public void unsync() { - if (!activityCallback.isWatchOnly()) { - bSend.setVisibility(View.INVISIBLE); - bSend.setEnabled(false); - } - if (isVisible()) enableAccountsList(false); //otherwise it is enabled in onResume() - firstBlock = 0; - } - - boolean walletLoaded = false; - - public void onLoaded() { - walletLoaded = true; - showReceive(); - } - - private void showReceive() { - if (walletLoaded) { - bReceive.setVisibility(View.VISIBLE); - bReceive.setEnabled(true); - } - } - - private String syncText = null; - - public void setProgress(final String text) { - syncText = text; - tvProgress.setText(text); - } - - private int syncProgress = -1; - - public void setProgress(final int n) { - syncProgress = n; - if (n > 100) { - pbProgress.setIndeterminate(true); - pbProgress.setVisibility(View.VISIBLE); - } else if (n >= 0) { - pbProgress.setIndeterminate(false); - pbProgress.setProgress(n); - pbProgress.setVisibility(View.VISIBLE); - } else { // <0 - pbProgress.setVisibility(View.INVISIBLE); - } - } - - void setActivityTitle(Wallet wallet) { - if (wallet == null) return; - walletTitle = wallet.getName(); - walletSubtitle = wallet.getAccountLabel(); - activityCallback.setTitle(walletTitle, walletSubtitle); - Timber.d("wallet title is %s", walletTitle); - } - - private long firstBlock = 0; - private String walletTitle = null; - private String walletSubtitle = null; - private long unlockedBalance = 0; - private long balance = 0; - - private int accountIdx = -1; - - private void updateStatus(Wallet wallet) { - if (!isAdded()) return; - Timber.d("updateStatus()"); - if ((walletTitle == null) || (accountIdx != wallet.getAccountIndex())) { - accountIdx = wallet.getAccountIndex(); - setActivityTitle(wallet); - } - balance = wallet.getBalance(); - unlockedBalance = wallet.getUnlockedBalance(); - refreshBalance(); - String sync; - if (!activityCallback.hasBoundService()) - throw new IllegalStateException("WalletService not bound."); - Wallet.ConnectionStatus daemonConnected = activityCallback.getConnectionStatus(); - if (daemonConnected == Wallet.ConnectionStatus.ConnectionStatus_Connected) { - if (!wallet.isSynchronized()) { - long daemonHeight = activityCallback.getDaemonHeight(); - long walletHeight = wallet.getBlockChainHeight(); - long n = daemonHeight - walletHeight; - sync = getString(R.string.status_syncing) + " " + formatter.format(n) + " " + getString(R.string.status_remaining); - if (firstBlock == 0) { - firstBlock = walletHeight; - } - int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock)); - if (x == 0) x = 101; // indeterminate - setProgress(x); - ivSynced.setVisibility(View.GONE); - } else { - sync = getString(R.string.status_synced) + " " + formatter.format(wallet.getBlockChainHeight()); - ivSynced.setVisibility(View.VISIBLE); - } - } else { - sync = getString(R.string.status_wallet_connecting); - setProgress(101); - } - setProgress(sync); - // TODO show connected status somewhere - } - - Listener activityCallback; - - // Container Activity must implement this interface - public interface Listener { - boolean hasBoundService(); - - void forceUpdate(); - - Wallet.ConnectionStatus getConnectionStatus(); - - long getDaemonHeight(); //mBoundService.getDaemonHeight(); - - void onSendRequest(View view); - - void onTxDetailsRequest(View view, TransactionInfo info); - - boolean isSynced(); - - boolean isStreetMode(); - - long getStreetModeHeight(); - - boolean isWatchOnly(); - - String getTxKey(String txId); - - void onWalletReceive(View view); - - boolean hasWallet(); - - Wallet getWallet(); - - void setToolbarButton(int type); - - void setTitle(String title, String subtitle); - - void setSubtitle(String subtitle); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof Listener) { - this.activityCallback = (Listener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume()"); - activityCallback.setTitle(walletTitle, walletSubtitle); - activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); - setProgress(syncProgress); - setProgress(syncText); - showReceive(); - if (activityCallback.isSynced()) enableAccountsList(true); - } - - @Override - public void onPause() { - enableAccountsList(false); - super.onPause(); - } - - public interface DrawerLocker { - void setDrawerEnabled(boolean enabled); - } - - private void enableAccountsList(boolean enable) { - if (activityCallback instanceof DrawerLocker) { - ((DrawerLocker) activityCallback).setDrawerEnabled(enable); - } - } - - public void setStreetModeBackground(boolean enable) { - //TODO figure out why gunther disappears on return from send although he is still set - if (enable) { - if (streetGunther == null) - streetGunther = ContextCompat.getDrawable(requireContext(), R.drawable.ic_gunther_streetmode); - ivStreetGunther.setImageDrawable(streetGunther); - } else - ivStreetGunther.setImageDrawable(null); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java deleted file mode 100644 index 3762fb1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet; - -import android.app.Application; -import android.content.Context; -import android.content.res.Configuration; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentManager; - -import com.m2049r.xmrwallet.model.NetworkType; -import com.m2049r.xmrwallet.util.LocaleHelper; -import com.m2049r.xmrwallet.util.NetCipherHelper; -import com.m2049r.xmrwallet.util.NightmodeHelper; - -import timber.log.Timber; - -public class XmrWalletApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - FragmentManager.enableNewStateManager(false); - if (BuildConfig.DEBUG) { - Timber.plant(new Timber.DebugTree()); - } - - NightmodeHelper.setPreferredNightmode(this); - - NetCipherHelper.createInstance(this); - } - - @Override - protected void attachBaseContext(Context context) { - super.attachBaseContext(LocaleHelper.setPreferredLocale(context)); - } - - @Override - public void onConfigurationChanged(@NonNull Configuration configuration) { - super.onConfigurationChanged(configuration); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - LocaleHelper.updateSystemDefaultLocale(configuration.getLocales().get(0)); - } else { - LocaleHelper.updateSystemDefaultLocale(configuration.locale); - } - LocaleHelper.setPreferredLocale(this); - } - - static public NetworkType getNetworkType() { - switch (BuildConfig.FLAVOR_net) { - case "mainnet": - return NetworkType.NetworkType_Mainnet; - case "stagenet": - return NetworkType.NetworkType_Stagenet; - case "devnet": // flavors cannot start with "test" - return NetworkType.NetworkType_Testnet; - default: - throw new IllegalStateException("unknown net flavor " + BuildConfig.FLAVOR_net); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java b/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java index e9e66f1..45b49e6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java @@ -14,18 +14,18 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum Crypto { - XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", R.id.ibXMR, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid), - BTC("BTC", true, "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> { + XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", 0, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid), + BTC("BTC", true, "bitcoin:amount:label:message", 0, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> { return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC); }), - DASH("DASH", true, "dash:amount:label:message", R.id.ibDASH, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> { + DASH("DASH", true, "dash:amount:label:message", 0, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> { return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH); }), - DOGE("DOGE", true, "dogecoin:amount:label:message", R.id.ibDOGE, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> { + DOGE("DOGE", true, "dogecoin:amount:label:message", 0, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> { return BitcoinAddressValidator.validate(address, BitcoinAddressType.DOGE); }), - ETH("ETH", false, "ethereum:amount:label:message", R.id.ibETH, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate), - LTC("LTC", true, "litecoin:amount:label:message", R.id.ibLTC, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> { + ETH("ETH", false, "ethereum:amount:label:message", 0, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate), + LTC("LTC", true, "litecoin:amount:label:message", 0, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> { return BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC); }); diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/UserNotes.java b/app/src/main/java/com/m2049r/xmrwallet/data/UserNotes.java index f5eb14b..646af50 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/UserNotes.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/UserNotes.java @@ -16,9 +16,6 @@ package com.m2049r.xmrwallet.data; -import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder; -import com.m2049r.xmrwallet.util.Helper; - import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -59,22 +56,6 @@ public class UserNotes { txNotes = buildTxNote(); } - public void setXmrtoOrder(CreateOrder order) { - if (order != null) { - xmrtoTag = order.TAG; - xmrtoKey = order.getOrderId(); - xmrtoAmount = Helper.getDisplayAmount(order.getBtcAmount()); - xmrtoCurrency = order.getBtcCurrency(); - xmrtoDestination = order.getBtcAddress(); - } else { - xmrtoTag = null; - xmrtoKey = null; - xmrtoAmount = null; - xmrtoDestination = null; - } - txNotes = buildTxNote(); - } - private String buildTxNote() { StringBuilder sb = new StringBuilder(); if (xmrtoKey != null) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/AboutFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/AboutFragment.java deleted file mode 100644 index dc1047e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/AboutFragment.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.m2049r.xmrwallet.BuildConfig; -import com.m2049r.xmrwallet.R; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -import timber.log.Timber; - -public class AboutFragment extends DialogFragment { - static final String TAG = "AboutFragment"; - - public static AboutFragment newInstance() { - return new AboutFragment(); - } - - public static void display(FragmentManager fm) { - FragmentTransaction ft = fm.beginTransaction(); - Fragment prev = fm.findFragmentByTag(TAG); - if (prev != null) { - ft.remove(prev); - } - - AboutFragment.newInstance().show(ft, TAG); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_about, null); - ((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getLicencesHtml())); - ((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()) - .setView(view) - .setNegativeButton(R.string.about_close, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.dismiss(); - } - }); - return builder.create(); - } - - private String getLicencesHtml() { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(getContext().getAssets().open("licenses.html"), StandardCharsets.UTF_8))) { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) - sb.append(line); - return sb.toString(); - } catch (IOException ex) { - Timber.e(ex); - return ex.getLocalizedMessage(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/CreditsFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/CreditsFragment.java deleted file mode 100644 index d33921e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/CreditsFragment.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.m2049r.xmrwallet.R; - -public class CreditsFragment extends DialogFragment { - static final String TAG = "DonationFragment"; - - public static CreditsFragment newInstance() { - return new CreditsFragment(); - } - - public static void display(FragmentManager fm) { - FragmentTransaction ft = fm.beginTransaction(); - Fragment prev = fm.findFragmentByTag(TAG); - if (prev != null) { - ft.remove(prev); - } - - CreditsFragment.newInstance().show(ft, TAG); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_credits, null); - - ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.credits_text))); - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()) - .setView(view) - .setNegativeButton(R.string.about_close, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.dismiss(); - } - }); - return builder.create(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java deleted file mode 100644 index 6928937..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/HelpFragment.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.dialog; - -import android.app.Dialog; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.text.Html; -import android.text.Spanned; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.util.NetCipherHelper; - -public class HelpFragment extends DialogFragment { - static final String TAG = "HelpFragment"; - private static final String HELP_ID = "HELP_ID"; - private static final String TOR_BUTTON = "TOR"; - - public static HelpFragment newInstance(int helpResourceId) { - HelpFragment fragment = new HelpFragment(); - Bundle bundle = new Bundle(); - bundle.putInt(HELP_ID, helpResourceId); - // a hack for the tor button - if (helpResourceId == R.string.help_tor) - bundle.putInt(TOR_BUTTON, 7); - fragment.setArguments(bundle); - return fragment; - } - - public static void display(FragmentManager fm, int helpResourceId) { - FragmentTransaction ft = fm.beginTransaction(); - Fragment prev = fm.findFragmentByTag(TAG); - if (prev != null) { - ft.remove(prev); - } - - HelpFragment.newInstance(helpResourceId).show(ft, TAG); - } - - private Spanned getHtml(String html, double textSize) { - final Html.ImageGetter imageGetter = source -> { - final int imageId = getResources().getIdentifier(source.replace("/", ""), "drawable", requireActivity().getPackageName()); - // Don't die if we don't find the image - use a heart instead - final Drawable drawable = ContextCompat.getDrawable(requireActivity(), imageId > 0 ? imageId : R.drawable.ic_favorite_24dp); - final double f = textSize / drawable.getIntrinsicHeight(); - drawable.setBounds(0, 0, (int) (f * drawable.getIntrinsicWidth()), (int) textSize); - return drawable; - }; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY, imageGetter, null); - } else { - return Html.fromHtml(html, imageGetter, null); - } - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null); - - int helpId = 0; - boolean torButton = false; - Bundle arguments = getArguments(); - if (arguments != null) { - helpId = arguments.getInt(HELP_ID); - torButton = arguments.getInt(TOR_BUTTON) > 0; - } - final TextView helpTv = view.findViewById(R.id.tvHelp); - if (helpId > 0) - helpTv.setText(getHtml(getString(helpId), helpTv.getTextSize())); - - MaterialAlertDialogBuilder builder = - new MaterialAlertDialogBuilder(requireActivity()) - .setView(view); - if (torButton) { - builder.setNegativeButton(R.string.help_nok, - (dialog, id) -> dialog.dismiss()) - .setPositiveButton(R.string.help_getorbot, - (dialog, id) -> { - dialog.dismiss(); - NetCipherHelper.getInstance().installOrbot(requireActivity()); - }); - } else { - builder.setNegativeButton(R.string.help_ok, - (dialog, id) -> dialog.dismiss()); - } - return builder.create(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/PrivacyFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/PrivacyFragment.java deleted file mode 100644 index 6ddb2e4..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/PrivacyFragment.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.m2049r.xmrwallet.R; - -public class PrivacyFragment extends DialogFragment { - static final String TAG = "PrivacyFragment"; - - public static PrivacyFragment newInstance() { - return new PrivacyFragment(); - } - - public static void display(FragmentManager fm) { - FragmentTransaction ft = fm.beginTransaction(); - Fragment prev = fm.findFragmentByTag(TAG); - if (prev != null) { - ft.remove(prev); - } - - PrivacyFragment.newInstance().show(ft, TAG); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_privacy_policy, null); - - ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.privacy_policy))); - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()) - .setView(view) - .setNegativeButton(R.string.about_close, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - dialog.dismiss(); - } - }); - return builder.create(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java deleted file mode 100644 index a8bb780..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.m2049r.xmrwallet.dialog; - -/* - * Copyright (C) 2007 The Android Open Source Project - * Copyright (C) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.util.Helper; - -import java.util.Locale; - -import timber.log.Timber; - -public class ProgressDialog extends AlertDialog { - - private ProgressBar pbBar; - - private TextView tvMessage; - - private TextView tvProgress; - - private View rlProgressBar, pbCircle; - - static private final String PROGRESS_FORMAT = "%1d/%2d"; - - private CharSequence message; - private int maxValue, progressValue; - private boolean indeterminate = true; - - public ProgressDialog(Context context) { - super(context); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null); - pbCircle = view.findViewById(R.id.pbCircle); - tvMessage = view.findViewById(R.id.tvMessage); - rlProgressBar = view.findViewById(R.id.rlProgressBar); - pbBar = view.findViewById(R.id.pbBar); - tvProgress = view.findViewById(R.id.tvProgress); - setView(view); - setIndeterminate(indeterminate); - if (maxValue > 0) { - setMax(maxValue); - } - if (progressValue > 0) { - setProgress(progressValue); - } - if (message != null) { - Timber.d("msg=%s", message); - setMessage(message); - } - - super.onCreate(savedInstanceState); - - if (Helper.preventScreenshot()) { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - } - } - - public void setProgress(int value, int max) { - progressValue = value; - maxValue = max; - if (pbBar != null) { - pbBar.setProgress(value); - pbBar.setMax(max); - tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue)); - } - } - - public void setProgress(int value) { - progressValue = value; - if (pbBar != null) { - pbBar.setProgress(value); - tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue)); - } - } - - public void setMax(int max) { - maxValue = max; - if (pbBar != null) { - pbBar.setMax(max); - } - } - - public void setIndeterminate(boolean indeterminate) { - if (this.indeterminate != indeterminate) { - if (rlProgressBar != null) { - if (indeterminate) { - pbCircle.setVisibility(View.VISIBLE); - rlProgressBar.setVisibility(View.GONE); - } else { - pbCircle.setVisibility(View.GONE); - rlProgressBar.setVisibility(View.VISIBLE); - } - } - this.indeterminate = indeterminate; - } - } - - @Override - public void setMessage(CharSequence message) { - this.message = message; - if (tvMessage != null) { - tvMessage.setText(message); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java new file mode 100644 index 0000000..71cb2f3 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java @@ -0,0 +1,118 @@ +package com.m2049r.xmrwallet.fragment.home; + +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.m2049r.xmrwallet.MainActivity; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.data.BarcodeData; +import com.m2049r.xmrwallet.data.Crypto; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.service.TxService; +import com.m2049r.xmrwallet.util.Helper; + +import java.util.HashMap; +import java.util.Map; + +import timber.log.Timber; + +public class HomeFragment extends Fragment { + + private HomeViewModel mViewModel; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_home, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mViewModel = new ViewModelProvider(this).get(HomeViewModel.class); + MainActivity mainActivity = (MainActivity) getActivity(); + if(mainActivity == null) return; + + ImageView settingsImageView = view.findViewById(R.id.settings_imageview); + ImageView addressImageView = view.findViewById(R.id.monero_qr_imageview); + TextView addressTextView = view.findViewById(R.id.address_textview); + TextView balanceTextView = view.findViewById(R.id.balance_textview); + EditText addressEditText = view.findViewById(R.id.address_edittext); + EditText amountEditText = view.findViewById(R.id.amount_edittext); + Button sendButton = view.findViewById(R.id.send_button); + + mainActivity.address.observe(getViewLifecycleOwner(), addr -> { + if(!addr.isEmpty()) { + addressTextView.setText(addr); + addressImageView.setImageBitmap(mViewModel.generate(addr, 256, 256)); + } + }); + + mainActivity.balance.observe(getViewLifecycleOwner(), balance -> { + balanceTextView.setText(getString(R.string.wallet_balance_text, Wallet.getDisplayAmount(balance))); + }); + + TxService.getInstance().clearSendEvent.observe(getViewLifecycleOwner(), o -> { + addressEditText.setText(null); + amountEditText.setText(null); + sendButton.setEnabled(true); + }); + + settingsImageView.setOnClickListener(view12 -> { + navigate(R.id.settings_fragment); + }); + + sendButton.setOnClickListener(view1 -> { + String address = addressEditText.getText().toString().trim(); + String amount = amountEditText.getText().toString().trim(); + boolean validAddress = Wallet.isAddressValid(address); + if(validAddress && !amount.isEmpty()) { + sendButton.setEnabled(false); + TxService.getInstance().sendTx(address, amount); + } else if(!validAddress) { + Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show(); + } else if(amount.isEmpty()) { + Toast.makeText(getActivity(), getString(R.string.send_amount_empty), Toast.LENGTH_SHORT).show(); + } + }); + } + + private void navigate(int destination) { + FragmentActivity activity = getActivity(); + if(activity != null) { + FragmentManager fm = activity.getSupportFragmentManager(); + NavHostFragment navHostFragment = + (NavHostFragment) fm.findFragmentById(R.id.nav_host_fragment); + if(navHostFragment != null) { + navHostFragment.getNavController().navigate(destination); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java new file mode 100644 index 0000000..214fb22 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java @@ -0,0 +1,41 @@ +package com.m2049r.xmrwallet.fragment.home; + +import android.graphics.Bitmap; + +import androidx.lifecycle.ViewModel; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import java.util.HashMap; +import java.util.Map; + +import timber.log.Timber; + +public class HomeViewModel extends ViewModel { + public Bitmap generate(String text, int width, int height) { + if ((width <= 0) || (height <= 0)) return null; + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); + try { + BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints); + int[] pixels = new int[width * height]; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (bitMatrix.get(j, i)) { + pixels[i * width + j] = 0x00000000; + } else { + pixels[i * height + j] = 0xffffffff; + } + } + } + return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565); + } catch (WriterException ex) { + Timber.e(ex); + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java deleted file mode 100644 index f25ad19..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.content.Context; -import android.nfc.NfcManager; -import android.os.Bundle; -import android.text.Editable; -import android.text.Html; -import android.text.InputType; -import android.text.Spanned; -import android.text.TextWatcher; -import android.util.Patterns; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; - -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.Crypto; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.data.TxDataBtc; -import com.m2049r.xmrwallet.data.UserNotes; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.OpenAliasHelper; -import com.m2049r.xmrwallet.util.ServiceHelper; -import com.m2049r.xmrwallet.util.validator.BitcoinAddressType; -import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator; -import com.m2049r.xmrwallet.util.validator.EthAddressValidator; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import timber.log.Timber; - -public class SendAddressWizardFragment extends SendWizardFragment { - - static final int INTEGRATED_ADDRESS_LENGTH = 106; - - public static SendAddressWizardFragment newInstance(Listener listener) { - SendAddressWizardFragment instance = new SendAddressWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - Listener sendListener; - - public void setSendListener(Listener listener) { - this.sendListener = listener; - } - - public interface Listener { - void setBarcodeData(BarcodeData data); - - BarcodeData getBarcodeData(); - - BarcodeData popBarcodeData(); - - void setMode(SendFragment.Mode mode); - - TxData getTxData(); - } - - private EditText etDummy; - private TextInputLayout etAddress; - private TextInputLayout etNotes; - private TextView tvXmrTo; - private TextView tvTor; - private Map ibCrypto; - final private Set possibleCryptos = new HashSet<>(); - private Crypto selectedCrypto = null; - - private boolean resolvingOA = false; - - OnScanListener onScanListener; - - public interface OnScanListener { - void onScan(); - } - - private Crypto getCryptoForButton(ImageButton button) { - for (Map.Entry entry : ibCrypto.entrySet()) { - if (entry.getValue() == button) return entry.getKey(); - } - return null; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); - - View view = inflater.inflate(R.layout.fragment_send_address, container, false); - - tvTor = view.findViewById(R.id.tvTor); - tvXmrTo = view.findViewById(R.id.tvXmrTo); - ibCrypto = new HashMap<>(); - for (Crypto crypto : Crypto.values()) { - final ImageButton button = view.findViewById(crypto.getButtonId()); - if (Helper.ALLOW_SHIFT || (crypto == Crypto.XMR)) { - ibCrypto.put(crypto, button); - button.setOnClickListener(v -> { - if (possibleCryptos.contains(crypto)) { - selectedCrypto = crypto; - updateCryptoButtons(false); - } else { - // show help what to do: - if (button.getId() != R.id.ibXMR) { - final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()]; - final String symbol = getCryptoForButton(button).getSymbol(); - tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol))); - tvXmrTo.setVisibility(View.VISIBLE); - } else { - tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr))); - tvXmrTo.setVisibility(View.VISIBLE); - tvTor.setVisibility(View.INVISIBLE); - } - } - }); - } else { - button.setImageResource(crypto.getIconDisabledId()); - button.setImageAlpha(128); - button.setEnabled(false); - } - } - if (!Helper.ALLOW_SHIFT) { - tvTor.setVisibility(View.VISIBLE); - } - updateCryptoButtons(true); - - etAddress = view.findViewById(R.id.etAddress); - etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - // ignore ENTER - return ((event != null) && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)); - } - }); - etAddress.getEditText().setOnFocusChangeListener((v, hasFocus) -> { - if (!hasFocus) { - String enteredAddress = etAddress.getEditText().getText().toString().trim(); - String dnsOA = dnsFromOpenAlias(enteredAddress); - Timber.d("OpenAlias is %s", dnsOA); - if (dnsOA != null) { - processOpenAlias(dnsOA); - } - } - }); - etAddress.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - Timber.d("AFTER: %s", editable.toString()); - etAddress.setError(null); - possibleCryptos.clear(); - selectedCrypto = null; - final String address = etAddress.getEditText().getText().toString(); - if (isIntegratedAddress(address)) { - Timber.d("isIntegratedAddress"); - possibleCryptos.add(Crypto.XMR); - selectedCrypto = Crypto.XMR; - etAddress.setError(getString(R.string.info_paymentid_integrated)); - sendListener.setMode(SendFragment.Mode.XMR); - } else if (isStandardAddress(address)) { - Timber.d("isStandardAddress"); - possibleCryptos.add(Crypto.XMR); - selectedCrypto = Crypto.XMR; - sendListener.setMode(SendFragment.Mode.XMR); - } - if (!Helper.ALLOW_SHIFT) return; - if ((selectedCrypto == null) && isEthAddress(address)) { - Timber.d("isEthAddress"); - possibleCryptos.add(Crypto.ETH); - selectedCrypto = Crypto.ETH; - tvXmrTo.setVisibility(View.VISIBLE); - sendListener.setMode(SendFragment.Mode.BTC); - } - if (possibleCryptos.isEmpty()) { - Timber.d("isBitcoinAddress"); - for (BitcoinAddressType type : BitcoinAddressType.values()) { - if (BitcoinAddressValidator.validate(address, type)) { - possibleCryptos.add(Crypto.valueOf(type.name())); - } - } - if (!possibleCryptos.isEmpty()) // found something in need of shifting! - sendListener.setMode(SendFragment.Mode.BTC); - if (possibleCryptos.size() == 1) { - selectedCrypto = (Crypto) possibleCryptos.toArray()[0]; - } - } - if (possibleCryptos.isEmpty()) { - Timber.d("other"); - tvXmrTo.setVisibility(View.INVISIBLE); - sendListener.setMode(SendFragment.Mode.XMR); - } - updateCryptoButtons(address.isEmpty()); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - final ImageButton bPasteAddress = view.findViewById(R.id.bPasteAddress); - bPasteAddress.setOnClickListener(v -> { - final String clip = Helper.getClipBoardText(getActivity()); - if (clip == null) return; - // clean it up - final String address = clip.replaceAll("( +)|(\\r?\\n?)", ""); - BarcodeData bc = BarcodeData.fromString(address); - if (bc != null) { - processScannedData(bc); - final EditText et = etAddress.getEditText(); - et.setSelection(et.getText().length()); - etAddress.requestFocus(); - } else { - Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show(); - } - }); - - etNotes = view.findViewById(R.id.etNotes); - etNotes.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT); - etNotes.getEditText(). - - setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - etDummy.requestFocus(); - return true; - } - return false; - }); - - final View cvScan = view.findViewById(R.id.bScan); - cvScan.setOnClickListener(v -> onScanListener.onScan()); - - etDummy = view.findViewById(R.id.etDummy); - etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etDummy.requestFocus(); - - View tvNfc = view.findViewById(R.id.tvNfc); - NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE); - if ((manager != null) && (manager.getDefaultAdapter() != null)) - tvNfc.setVisibility(View.VISIBLE); - - return view; - } - - private void selectedCrypto(Crypto crypto) { - final ImageButton button = ibCrypto.get(crypto); - button.setImageResource(crypto.getIconEnabledId()); - button.setImageAlpha(255); - button.setEnabled(true); - } - - private void possibleCrypto(Crypto crypto) { - final ImageButton button = ibCrypto.get(crypto); - button.setImageResource(crypto.getIconDisabledId()); - button.setImageAlpha(255); - button.setEnabled(true); - } - - private void impossibleCrypto(Crypto crypto) { - final ImageButton button = ibCrypto.get(crypto); - button.setImageResource(crypto.getIconDisabledId()); - button.setImageAlpha(128); - button.setEnabled(true); - } - - private void updateCryptoButtons(boolean noAddress) { - if (!Helper.ALLOW_SHIFT) return; - for (Crypto crypto : Crypto.values()) { - if (crypto == selectedCrypto) { - selectedCrypto(crypto); - } else if (possibleCryptos.contains(crypto)) { - possibleCrypto(crypto); - } else { - impossibleCrypto(crypto); - } - } - if ((selectedCrypto != null) && (selectedCrypto != Crypto.XMR)) { - tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto, selectedCrypto.getSymbol()))); - tvXmrTo.setVisibility(View.VISIBLE); - } else if ((selectedCrypto == null) && (possibleCryptos.size() > 1)) { - tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_ambiguous))); - tvXmrTo.setVisibility(View.VISIBLE); - } else { - tvXmrTo.setVisibility(View.INVISIBLE); - } - if (noAddress) { - selectedCrypto(Crypto.XMR); - } - } - - private void processOpenAlias(String dnsOA) { - if (resolvingOA) return; // already resolving - just wait - sendListener.popBarcodeData(); - if (dnsOA != null) { - resolvingOA = true; - etAddress.setError(getString(R.string.send_address_resolve_openalias)); - OpenAliasHelper.resolve(dnsOA, new OpenAliasHelper.OnResolvedListener() { - @Override - public void onResolved(Map dataMap) { - resolvingOA = false; - BarcodeData barcodeData = dataMap.get(Crypto.XMR); - if (barcodeData == null) barcodeData = dataMap.get(Crypto.BTC); - if (barcodeData != null) { - Timber.d("Security=%s, %s", barcodeData.security.toString(), barcodeData.address); - processScannedData(barcodeData); - } else { - etAddress.setError(getString(R.string.send_address_not_openalias)); - Timber.d("NO XMR OPENALIAS TXT FOUND"); - } - } - - @Override - public void onFailure() { - resolvingOA = false; - etAddress.setError(getString(R.string.send_address_not_openalias)); - Timber.e("OA FAILED"); - } - }); - } // else ignore - } - - private boolean checkAddressNoError() { - return selectedCrypto != null; - } - - private boolean checkAddress() { - boolean ok = checkAddressNoError(); - if (possibleCryptos.isEmpty()) { - etAddress.setError(getString(R.string.send_address_invalid)); - } else { - etAddress.setError(null); - } - return ok; - } - - private boolean isStandardAddress(String address) { - return Wallet.isAddressValid(address); - } - - private boolean isIntegratedAddress(String address) { - return (address.length() == INTEGRATED_ADDRESS_LENGTH) - && Wallet.isAddressValid(address); - } - - private boolean isBitcoinishAddress(String address) { - return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC) - || - BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC) - || - BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH); - } - - private boolean isEthAddress(String address) { - return EthAddressValidator.validate(address); - } - - private void shakeAddress() { - if (possibleCryptos.size() > 1) { // address ambiguous - for (Crypto crypto : Crypto.values()) { - if (possibleCryptos.contains(crypto)) { - ibCrypto.get(crypto).startAnimation(Helper.getShakeAnimation(getContext())); - } - } - } else { - etAddress.startAnimation(Helper.getShakeAnimation(getContext())); - } - } - - @Override - public boolean onValidateFields() { - if (!checkAddressNoError()) { - shakeAddress(); - String enteredAddress = etAddress.getEditText().getText().toString().trim(); - String dnsOA = dnsFromOpenAlias(enteredAddress); - Timber.d("OpenAlias is %s", dnsOA); - if (dnsOA != null) { - processOpenAlias(dnsOA); - } - return false; - } - - if (sendListener != null) { - TxData txData = sendListener.getTxData(); - if (txData instanceof TxDataBtc) { - ((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString()); - ((TxDataBtc) txData).setBtcSymbol(selectedCrypto.getSymbol()); - txData.setDestinationAddress(null); - ServiceHelper.ASSET = selectedCrypto.getSymbol().toLowerCase(); - } else { - txData.setDestinationAddress(etAddress.getEditText().getText().toString()); - ServiceHelper.ASSET = null; - } - txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString())); - txData.setPriority(PendingTransaction.Priority.Priority_Default); - txData.setMixin(SendFragment.MIXIN); - } - return true; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - if (context instanceof OnScanListener) { - onScanListener = (OnScanListener) context; - } else { - throw new ClassCastException(context.toString() - + " must implement ScanListener"); - } - } - - // QR Scan Stuff - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume"); - processScannedData(); - } - - public void processScannedData(BarcodeData barcodeData) { - sendListener.setBarcodeData(barcodeData); - if (isResumed()) - processScannedData(); - } - - public void processScannedData() { - BarcodeData barcodeData = sendListener.getBarcodeData(); - if (barcodeData != null) { - Timber.d("GOT DATA"); - if (!Helper.ALLOW_SHIFT && (barcodeData.asset != Crypto.XMR)) { - Timber.d("BUT ONLY XMR SUPPORTED"); - barcodeData = null; - sendListener.setBarcodeData(barcodeData); - return; - } - if (barcodeData.address != null) { - etAddress.getEditText().setText(barcodeData.address); - possibleCryptos.clear(); - selectedCrypto = null; - if (barcodeData.isAmbiguous()) { - possibleCryptos.addAll(barcodeData.ambiguousAssets); - } else { - possibleCryptos.add(barcodeData.asset); - selectedCrypto = barcodeData.asset; - } - if (Helper.ALLOW_SHIFT) - updateCryptoButtons(false); - if (checkAddress()) { - if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC) - etAddress.setError(getString(R.string.send_address_no_dnssec)); - else if (barcodeData.security == BarcodeData.Security.OA_DNSSEC) - etAddress.setError(getString(R.string.send_address_openalias)); - } - } else { - etAddress.getEditText().getText().clear(); - etAddress.setError(null); - } - - String scannedNotes = barcodeData.addressName; - if (scannedNotes == null) { - scannedNotes = barcodeData.description; - } else if (barcodeData.description != null) { - scannedNotes = scannedNotes + ": " + barcodeData.description; - } - if (scannedNotes != null) { - etNotes.getEditText().setText(scannedNotes); - } else { - etNotes.getEditText().getText().clear(); - etNotes.setError(null); - } - } else - Timber.d("barcodeData=null"); - } - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - etDummy.requestFocus(); - } - - String dnsFromOpenAlias(String openalias) { - Timber.d("checking openalias candidate %s", openalias); - if (Patterns.DOMAIN_NAME.matcher(openalias).matches()) return openalias; - if (Patterns.EMAIL_ADDRESS.matcher(openalias).matches()) { - openalias = openalias.replaceFirst("@", "."); - if (Patterns.DOMAIN_NAME.matcher(openalias).matches()) return openalias; - } - return null; // not an openalias - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java deleted file mode 100644 index 12edaf6..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAmountWizardFragment.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.TextView; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.widget.ExchangeEditText; - -import timber.log.Timber; - -public class SendAmountWizardFragment extends SendWizardFragment { - - public static SendAmountWizardFragment newInstance(Listener listener) { - SendAmountWizardFragment instance = new SendAmountWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - Listener sendListener; - - public void setSendListener(Listener listener) { - this.sendListener = listener; - } - - interface Listener { - SendFragment.Listener getActivityCallback(); - - TxData getTxData(); - - BarcodeData popBarcodeData(); - } - - private TextView tvFunds; - private ExchangeEditText etAmount; - private View rlSweep; - private ImageButton ibSweep; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); - - sendListener = (Listener) getParentFragment(); - - View view = inflater.inflate(R.layout.fragment_send_amount, container, false); - - tvFunds = view.findViewById(R.id.tvFunds); - etAmount = view.findViewById(R.id.etAmount); - rlSweep = view.findViewById(R.id.rlSweep); - - view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - sweepAll(false); - } - }); - - ibSweep = view.findViewById(R.id.ibSweep); - - ibSweep.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - sweepAll(true); - } - }); - - etAmount.requestFocus(); - return view; - } - - private boolean spendAllMode = false; - - private void sweepAll(boolean spendAllMode) { - if (spendAllMode) { - ibSweep.setVisibility(View.INVISIBLE); - etAmount.setVisibility(View.GONE); - rlSweep.setVisibility(View.VISIBLE); - } else { - ibSweep.setVisibility(View.VISIBLE); - etAmount.setVisibility(View.VISIBLE); - rlSweep.setVisibility(View.GONE); - } - this.spendAllMode = spendAllMode; - } - - @Override - public boolean onValidateFields() { - if (spendAllMode) { - if (sendListener != null) { - sendListener.getTxData().setAmount(Wallet.SWEEP_ALL); - } - } else { - if (!etAmount.validate(maxFunds, 0)) { - return false; - } - - if (sendListener != null) { - String xmr = etAmount.getNativeAmount(); - if (xmr != null) { - sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr)); - } else { - sendListener.getTxData().setAmount(0L); - } - } - } - return true; - } - - double maxFunds = 0; - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - Helper.showKeyboard(getActivity()); - final long funds = getTotalFunds(); - maxFunds = 1.0 * funds / Helper.ONE_XMR; - if (!sendListener.getActivityCallback().isStreetMode()) { - tvFunds.setText(getString(R.string.send_available, - Wallet.getDisplayAmount(funds))); - } else { - tvFunds.setText(getString(R.string.send_available, - getString(R.string.unknown_amount))); - } - final BarcodeData data = sendListener.popBarcodeData(); - if ((data != null) && (data.amount != null)) { - etAmount.setAmount(data.amount); - } - } - - long getTotalFunds() { - return sendListener.getActivityCallback().getTotalFunds(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java deleted file mode 100644 index 72b2e98..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcAmountWizardFragment.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.os.Bundle; -import android.text.Html; -import android.text.Spanned; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.TxDataBtc; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.service.shift.ShiftError; -import com.m2049r.xmrwallet.service.shift.ShiftException; -import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters; -import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; -import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ServiceHelper; -import com.m2049r.xmrwallet.widget.ExchangeOtherEditText; -import com.m2049r.xmrwallet.widget.SendProgressView; - -import java.text.NumberFormat; -import java.util.Locale; - -import timber.log.Timber; - -public class SendBtcAmountWizardFragment extends SendWizardFragment { - - public static SendBtcAmountWizardFragment newInstance(SendAmountWizardFragment.Listener listener) { - SendBtcAmountWizardFragment instance = new SendBtcAmountWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - SendAmountWizardFragment.Listener sendListener; - - public SendBtcAmountWizardFragment setSendListener(SendAmountWizardFragment.Listener listener) { - this.sendListener = listener; - return this; - } - - private TextView tvFunds; - private ExchangeOtherEditText etAmount; - - private TextView tvXmrToParms; - private SendProgressView evParams; - private View llXmrToParms; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); - - sendListener = (SendAmountWizardFragment.Listener) getParentFragment(); - - View view = inflater.inflate(R.layout.fragment_send_btc_amount, container, false); - - tvFunds = view.findViewById(R.id.tvFunds); - - evParams = view.findViewById(R.id.evXmrToParms); - llXmrToParms = view.findViewById(R.id.llXmrToParms); - - tvXmrToParms = view.findViewById(R.id.tvXmrToParms); - - etAmount = view.findViewById(R.id.etAmount); - etAmount.requestFocus(); - - return view; - } - - @Override - public boolean onValidateFields() { - Timber.i(maxBtc + "/" + minBtc); - if (!etAmount.validate(maxBtc, minBtc)) { - return false; - } - if (orderParameters == null) { - return false; // this should never happen - } - if (sendListener != null) { - TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); - String btcString = etAmount.getNativeAmount(); - if (btcString != null) { - try { - double btc = Double.parseDouble(btcString); - Timber.d("setBtcAmount %f", btc); - txDataBtc.setBtcAmount(btc); - txDataBtc.setAmount(btc / orderParameters.getPrice()); - } catch (NumberFormatException ex) { - Timber.d(ex.getLocalizedMessage()); - txDataBtc.setBtcAmount(0); - } - } else { - txDataBtc.setBtcAmount(0); - } - } - return true; - } - - double maxBtc = 0; - double minBtc = 0; - - @Override - public void onPauseFragment() { - llXmrToParms.setVisibility(View.INVISIBLE); - } - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - final String btcSymbol = ((TxDataBtc) sendListener.getTxData()).getBtcSymbol(); - if (!btcSymbol.toLowerCase().equals(ServiceHelper.ASSET)) - throw new IllegalStateException("Asset Symbol is wrong!"); - final long funds = getTotalFunds(); - if (!sendListener.getActivityCallback().isStreetMode()) { - tvFunds.setText(getString(R.string.send_available, - Wallet.getDisplayAmount(funds))); - //TODO - } else { - tvFunds.setText(getString(R.string.send_available, - getString(R.string.unknown_amount))); - } - etAmount.setAmount(""); - final BarcodeData data = sendListener.popBarcodeData(); - if (data != null) { - if (data.amount != null) { - etAmount.setAmount(data.amount); - } - } - etAmount.setBaseCurrency(btcSymbol); - callXmrTo(); - } - - long getTotalFunds() { - return sendListener.getActivityCallback().getTotalFunds(); - } - - private QueryOrderParameters orderParameters = null; - - private void processOrderParms(final QueryOrderParameters orderParameters) { - this.orderParameters = orderParameters; - getView().post(() -> { - final double price = orderParameters.getPrice(); - etAmount.setExchangeRate(1 / price); - maxBtc = price * orderParameters.getUpperLimit(); - minBtc = price * orderParameters.getLowerLimit(); - Timber.d("minBtc=%f / maxBtc=%f", minBtc, maxBtc); - NumberFormat df = NumberFormat.getInstance(Locale.US); - df.setMaximumFractionDigits(6); - String min = df.format(minBtc); - String max = df.format(maxBtc); - String rate = df.format(price); - final TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); - Spanned xmrParmText = Html.fromHtml(getString(R.string.info_send_xmrto_parms, - min, max, rate, txDataBtc.getBtcSymbol())); - tvXmrToParms.setText(xmrParmText); - - final long funds = getTotalFunds(); - double availableXmr = 1.0 * funds / Helper.ONE_XMR; - - String availBtcString; - String availXmrString; - if (!sendListener.getActivityCallback().isStreetMode()) { - availBtcString = df.format(availableXmr * price); - availXmrString = df.format(availableXmr); - } else { - availBtcString = getString(R.string.unknown_amount); - availXmrString = availBtcString; - } - tvFunds.setText(getString(R.string.send_available_btc, - availXmrString, - availBtcString, - ((TxDataBtc) sendListener.getTxData()).getBtcSymbol())); - llXmrToParms.setVisibility(View.VISIBLE); - evParams.hideProgress(); - }); - } - - private void processOrderParmsError(final Exception ex) { - etAmount.setExchangeRate(0); - orderParameters = null; - maxBtc = 0; - minBtc = 0; - Timber.e(ex); - getView().post(() -> { - if (ex instanceof ShiftException) { - ShiftException xmrEx = (ShiftException) ex; - ShiftError xmrErr = xmrEx.getError(); - if (xmrErr != null) { - if (xmrErr.isRetryable()) { - evParams.showMessage(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(), - getString(R.string.text_retry)); - evParams.setOnClickListener(v -> { - evParams.setOnClickListener(null); - callXmrTo(); - }); - } else { - evParams.showMessage(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(), - getString(R.string.text_noretry)); - } - } else { - evParams.showMessage(getString(R.string.label_generic_xmrto_error), - getString(R.string.text_generic_xmrto_error, xmrEx.getCode()), - getString(R.string.text_noretry)); - } - } else { - evParams.showMessage(getString(R.string.label_generic_xmrto_error), - ex.getLocalizedMessage(), - getString(R.string.text_noretry)); - } - }); - } - - private void callXmrTo() { - evParams.showProgress(getString(R.string.label_send_progress_queryparms)); - getXmrToApi().queryOrderParameters(new ShiftCallback() { - @Override - public void onSuccess(final QueryOrderParameters orderParameters) { - processOrderParms(orderParameters); - } - - @Override - public void onError(final Exception e) { - processOrderParmsError(e); - } - }); - } - - private SideShiftApi xmrToApi = null; - - private SideShiftApi getXmrToApi() { - if (xmrToApi == null) { - synchronized (this) { - if (xmrToApi == null) { - xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl()); - } - } - } - return xmrToApi; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java deleted file mode 100644 index 66a66c2..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.data.TxDataBtc; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.service.shift.ShiftError; -import com.m2049r.xmrwallet.service.shift.ShiftException; -import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder; -import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote; -import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; -import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ServiceHelper; -import com.m2049r.xmrwallet.widget.SendProgressView; - -import java.text.NumberFormat; -import java.util.Locale; - -import timber.log.Timber; - -public class SendBtcConfirmWizardFragment extends SendWizardFragment implements SendConfirm { - public static SendBtcConfirmWizardFragment newInstance(SendConfirmWizardFragment.Listener listener) { - SendBtcConfirmWizardFragment instance = new SendBtcConfirmWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - SendConfirmWizardFragment.Listener sendListener; - - public void setSendListener(SendConfirmWizardFragment.Listener listener) { - this.sendListener = listener; - } - - private View llStageA; - private SendProgressView evStageA; - private View llStageB; - private SendProgressView evStageB; - private View llStageC; - private SendProgressView evStageC; - private TextView tvTxBtcAmount; - private TextView tvTxBtcRate; - private TextView tvTxBtcAddress; - private TextView tvTxBtcAddressLabel; - private TextView tvTxXmrToKey; - private TextView tvTxFee; - private TextView tvTxTotal; - private View llConfirmSend; - private Button bSend; - private View pbProgressSend; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Timber.d("onCreateView(%s)", (String.valueOf(savedInstanceState))); - - View view = inflater.inflate( - R.layout.fragment_send_btc_confirm, container, false); - - tvTxBtcAddress = view.findViewById(R.id.tvTxBtcAddress); - tvTxBtcAddressLabel = view.findViewById(R.id.tvTxBtcAddressLabel); - tvTxBtcAmount = view.findViewById(R.id.tvTxBtcAmount); - tvTxBtcRate = view.findViewById(R.id.tvTxBtcRate); - tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); - - tvTxFee = view.findViewById(R.id.tvTxFee); - tvTxTotal = view.findViewById(R.id.tvTxTotal); - - llStageA = view.findViewById(R.id.llStageA); - evStageA = view.findViewById(R.id.evStageA); - llStageB = view.findViewById(R.id.llStageB); - evStageB = view.findViewById(R.id.evStageB); - llStageC = view.findViewById(R.id.llStageC); - evStageC = view.findViewById(R.id.evStageC); - - tvTxXmrToKey.setOnClickListener(v -> { - Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show(); - }); - - llConfirmSend = view.findViewById(R.id.llConfirmSend); - pbProgressSend = view.findViewById(R.id.pbProgressSend); - - bSend = view.findViewById(R.id.bSend); - bSend.setEnabled(false); - - bSend.setOnClickListener(v -> { - Timber.d("bSend.setOnClickListener"); - bSend.setEnabled(false); - preSend(); - }); - - return view; - } - - int inProgress = 0; - final static int STAGE_X = 0; - final static int STAGE_A = 1; - final static int STAGE_B = 2; - final static int STAGE_C = 3; - - private void showProgress(int stage, String progressText) { - Timber.d("showProgress(%d)", stage); - inProgress = stage; - switch (stage) { - case STAGE_A: - evStageA.showProgress(progressText); - break; - case STAGE_B: - evStageB.showProgress(progressText); - break; - case STAGE_C: - evStageC.showProgress(progressText); - break; - default: - throw new IllegalStateException("unknown stage " + stage); - } - } - - public void hideProgress() { - Timber.d("hideProgress(%d)", inProgress); - switch (inProgress) { - case STAGE_A: - evStageA.hideProgress(); - llStageA.setVisibility(View.VISIBLE); - break; - case STAGE_B: - evStageB.hideProgress(); - llStageB.setVisibility(View.VISIBLE); - break; - case STAGE_C: - evStageC.hideProgress(); - llStageC.setVisibility(View.VISIBLE); - break; - default: - throw new IllegalStateException("unknown stage " + inProgress); - } - inProgress = STAGE_X; - } - - public void showStageError(String code, String message, String solution) { - switch (inProgress) { - case STAGE_A: - evStageA.showMessage(code, message, solution); - break; - case STAGE_B: - evStageB.showMessage(code, message, solution); - break; - case STAGE_C: - evStageC.showMessage(code, message, solution); - break; - default: - throw new IllegalStateException("unknown stage"); - } - inProgress = STAGE_X; - } - - PendingTransaction pendingTransaction = null; - - void send() { - Timber.d("SEND @%d", sendCountdown); - if (sendCountdown <= 0) { - Timber.i("User waited too long in password dialog."); - Toast.makeText(getContext(), getString(R.string.send_xmrto_timeout), Toast.LENGTH_SHORT).show(); - return; - } - sendListener.getTxData().getUserNotes().setXmrtoOrder(xmrtoOrder); // note the transaction in the TX notes - ((TxDataBtc) sendListener.getTxData()).setXmrtoOrderId(xmrtoOrder.getOrderId()); // remember the order id for later - // TODO make method in TxDataBtc to set both of the above in one go - sendListener.commitTransaction(); - getActivity().runOnUiThread(() -> pbProgressSend.setVisibility(View.VISIBLE)); - } - - @Override - public void sendFailed(String error) { - pbProgressSend.setVisibility(View.INVISIBLE); - Toast.makeText(getContext(), getString(R.string.status_transaction_failed, error), Toast.LENGTH_LONG).show(); - } - - @Override - // callback from wallet when PendingTransaction created (started by prepareSend() here - public void transactionCreated(final String txTag, final PendingTransaction pendingTransaction) { - if (isResumed - && (inProgress == STAGE_C) - && (xmrtoOrder != null) - && (xmrtoOrder.getOrderId().equals(txTag))) { - this.pendingTransaction = pendingTransaction; - getView().post(() -> { - hideProgress(); - tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee())); - tvTxTotal.setText(Wallet.getDisplayAmount( - pendingTransaction.getFee() + pendingTransaction.getAmount())); - updateSendButton(); - }); - } else { - this.pendingTransaction = null; - sendListener.disposeTransaction(); - } - } - - @Override - public void createTransactionFailed(String errorText) { - Timber.e("CREATE TX FAILED"); - if (pendingTransaction != null) { - throw new IllegalStateException("pendingTransaction is not null"); - } - showStageError(getString(R.string.send_create_tx_error_title), - errorText, - getString(R.string.text_noretry_monero)); - } - - @Override - public boolean onValidateFields() { - return true; - } - - private boolean isResumed = false; - - @Override - public void onPauseFragment() { - isResumed = false; - stopSendTimer(); - sendListener.disposeTransaction(); - pendingTransaction = null; - inProgress = STAGE_X; - updateSendButton(); - super.onPauseFragment(); - } - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - if (sendListener.getMode() != SendFragment.Mode.BTC) { - throw new IllegalStateException("Mode is not BTC!"); - } - if (!((TxDataBtc) sendListener.getTxData()).getBtcSymbol().toLowerCase().equals(ServiceHelper.ASSET)) - throw new IllegalStateException("Asset Symbol is wrong!"); - Helper.hideKeyboard(getActivity()); - llStageA.setVisibility(View.INVISIBLE); - evStageA.hideProgress(); - llStageB.setVisibility(View.INVISIBLE); - evStageB.hideProgress(); - llStageC.setVisibility(View.INVISIBLE); - evStageC.hideProgress(); - isResumed = true; - if ((pendingTransaction == null) && (inProgress == STAGE_X)) { - stageA(); - } // otherwise just sit there blank - // TODO: don't sit there blank - can this happen? should we just die? - } - - private int sendCountdown = 0; - private static final int XMRTO_COUNTDOWN_STEP = 1; // 1 second - - Runnable updateRunnable = null; - - void startSendTimer(int timeout) { - Timber.d("startSendTimer()"); - sendCountdown = timeout; - updateRunnable = new Runnable() { - @Override - public void run() { - if (!isAdded()) - return; - Timber.d("updateTimer()"); - if (sendCountdown <= 0) { - bSend.setEnabled(false); - sendCountdown = 0; - Toast.makeText(getContext(), getString(R.string.send_xmrto_timeout), Toast.LENGTH_SHORT).show(); - } - int minutes = sendCountdown / 60; - int seconds = sendCountdown % 60; - String t = String.format("%d:%02d", minutes, seconds); - bSend.setText(getString(R.string.send_send_timed_label, t)); - if (sendCountdown > 0) { - sendCountdown -= XMRTO_COUNTDOWN_STEP; - getView().postDelayed(this, XMRTO_COUNTDOWN_STEP * 1000); - } - } - }; - getView().post(updateRunnable); - } - - void stopSendTimer() { - getView().removeCallbacks(updateRunnable); - } - - void updateSendButton() { - Timber.d("updateSendButton()"); - if (pendingTransaction != null) { - llConfirmSend.setVisibility(View.VISIBLE); - bSend.setEnabled(sendCountdown > 0); - } else { - llConfirmSend.setVisibility(View.GONE); - bSend.setEnabled(false); - } - } - - public void preSend() { - Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() { - @Override - public void act(String walletName, String password, boolean fingerprintUsed) { - send(); - } - - public void fail(String walletName) { - getActivity().runOnUiThread(() -> { - bSend.setEnabled(sendCountdown > 0); // allow to try again - }); - } - }); - } - - // creates a pending transaction and calls us back with transactionCreated() - // or createTransactionFailed() - void prepareSend() { - if (!isResumed) return; - if ((xmrtoOrder == null)) { - throw new IllegalStateException("xmrtoOrder is null"); - } - showProgress(3, getString(R.string.label_send_progress_create_tx)); - final TxData txData = sendListener.getTxData(); - txData.setDestinationAddress(xmrtoOrder.getXmrAddress()); - txData.setAmount(xmrtoOrder.getXmrAmount()); - getActivityCallback().onPrepareSend(xmrtoOrder.getOrderId(), txData); - } - - SendFragment.Listener getActivityCallback() { - return sendListener.getActivityCallback(); - } - - private RequestQuote xmrtoQuote = null; - - private void processStageA(final RequestQuote requestQuote) { - Timber.d("processCreateOrder %s", requestQuote.getId()); - TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); - // verify the BTC amount is correct - if (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) { - Timber.d("Failed to get quote"); - getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(), - getString(R.string.shift_noquote), - getString(R.string.shift_checkamount))); - return; // just stop for now - } - xmrtoQuote = requestQuote; - txDataBtc.setAmount(xmrtoQuote.getXmrAmount()); - getView().post(() -> { - // show data from the actual quote as that is what is used to - NumberFormat df = NumberFormat.getInstance(Locale.US); - df.setMaximumFractionDigits(12); - final String btcAmount = df.format(xmrtoQuote.getBtcAmount()); - final String xmrAmountTotal = df.format(xmrtoQuote.getXmrAmount()); - tvTxBtcAmount.setText(getString(R.string.text_send_btc_amount, - btcAmount, xmrAmountTotal, txDataBtc.getBtcSymbol())); - final String xmrPriceBtc = df.format(xmrtoQuote.getPrice()); - tvTxBtcRate.setText(getString(R.string.text_send_btc_rate, xmrPriceBtc, txDataBtc.getBtcSymbol())); - hideProgress(); - }); - stageB(requestQuote.getId()); - } - - private void processStageAError(final Exception ex) { - Timber.e("processStageAError %s", ex.getLocalizedMessage()); - getView().post(() -> { - if (ex instanceof ShiftException) { - ShiftException xmrEx = (ShiftException) ex; - ShiftError xmrErr = xmrEx.getError(); - if (xmrErr != null) { - if (xmrErr.isRetryable()) { - showStageError(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(), - getString(R.string.text_retry)); - evStageA.setOnClickListener(v -> { - evStageA.setOnClickListener(null); - stageA(); - }); - } else { - showStageError(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(), - getString(R.string.text_noretry)); - } - } else { - showStageError(getString(R.string.label_generic_xmrto_error), - getString(R.string.text_generic_xmrto_error, xmrEx.getCode()), - getString(R.string.text_noretry)); - } - } else { - evStageA.showMessage(getString(R.string.label_generic_xmrto_error), - ex.getLocalizedMessage(), - getString(R.string.text_noretry)); - } - }); - } - - private void stageA() { - if (!isResumed) return; - Timber.d("Request Quote"); - xmrtoQuote = null; - xmrtoOrder = null; - showProgress(1, getString(R.string.label_send_progress_xmrto_create)); - TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); - - ShiftCallback callback = new ShiftCallback() { - @Override - public void onSuccess(RequestQuote requestQuote) { - if (!isResumed) return; - if (xmrtoQuote != null) { - Timber.w("another ongoing request quote request"); - return; - } - processStageA(requestQuote); - } - - @Override - public void onError(Exception ex) { - if (!isResumed) return; - if (xmrtoQuote != null) { - Timber.w("another ongoing request quote request"); - return; - } - processStageAError(ex); - } - }; - - getXmrToApi().requestQuote(txDataBtc.getBtcAmount(), callback); - } - - private CreateOrder xmrtoOrder = null; - - private void processStageB(final CreateOrder order) { - Timber.d("processCreateOrder %s for %s", order.getOrderId(), order.getQuoteId()); - TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); - // verify amount & destination - if ((order.getBtcAmount() != txDataBtc.getBtcAmount()) - || (!txDataBtc.validateAddress(order.getBtcAddress()))) { - throw new IllegalStateException("Order does not fulfill quote!"); // something is terribly wrong - die - } - xmrtoOrder = order; - getView().post(() -> { - tvTxXmrToKey.setText(order.getOrderId()); - tvTxBtcAddress.setText(order.getBtcAddress()); - tvTxBtcAddressLabel.setText(getString(R.string.label_send_btc_address, txDataBtc.getBtcSymbol())); - hideProgress(); - Timber.d("Expires @ %s", order.getExpiresAt().toString()); - final int timeout = (int) (order.getExpiresAt().getTime() - order.getCreatedAt().getTime()) / 1000 - 60; // -1 minute buffer - startSendTimer(timeout); - prepareSend(); - }); - } - - private void processStageBError(final Exception ex) { - Timber.e("processCreateOrderError %s", ex.getLocalizedMessage()); - getView().post(() -> { - if (ex instanceof ShiftException) { - ShiftException xmrEx = (ShiftException) ex; - ShiftError xmrErr = xmrEx.getError(); - if (xmrErr != null) { - if (xmrErr.isRetryable()) { - showStageError(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(), - getString(R.string.text_retry)); - evStageB.setOnClickListener(v -> { - evStageB.setOnClickListener(null); - stageB(xmrtoOrder.getOrderId()); - }); - } else { - showStageError(xmrErr.getErrorType().toString(), xmrErr.getErrorMsg(), - getString(R.string.text_noretry)); - } - } else { - showStageError(getString(R.string.label_generic_xmrto_error), - getString(R.string.text_generic_xmrto_error, xmrEx.getCode()), - getString(R.string.text_noretry)); - } - } else { - evStageB.showMessage(getString(R.string.label_generic_xmrto_error), - ex.getLocalizedMessage(), - getString(R.string.text_noretry)); - } - }); - } - - private void stageB(final String quoteId) { - Timber.d("createOrder(%s)", quoteId); - if (!isResumed) return; - final String btcAddress = ((TxDataBtc) sendListener.getTxData()).getBtcAddress(); - getView().post(() -> { - xmrtoOrder = null; - showProgress(2, getString(R.string.label_send_progress_xmrto_query)); - getXmrToApi().createOrder(quoteId, btcAddress, new ShiftCallback() { - @Override - public void onSuccess(CreateOrder order) { - if (!isResumed) return; - if (xmrtoQuote == null) return; - if (!order.getQuoteId().equals(xmrtoQuote.getId())) { - Timber.d("Quote ID does not match"); - // ignore (we got a response to a stale request) - return; - } - if (xmrtoOrder != null) - throw new IllegalStateException("xmrtoOrder must be null here!"); - processStageB(order); - } - - @Override - public void onError(Exception ex) { - if (!isResumed) return; - processStageBError(ex); - } - }); - }); - } - - private SideShiftApi xmrToApi = null; - - private SideShiftApi getXmrToApi() { - if (xmrToApi == null) { - synchronized (this) { - if (xmrToApi == null) { - xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl()); - } - } - } - return xmrToApi; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java deleted file mode 100644 index 41c13db..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcSuccessWizardFragment.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.content.Intent; -import android.graphics.Paint; -import android.net.Uri; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.Crypto; -import com.m2049r.xmrwallet.data.PendingTx; -import com.m2049r.xmrwallet.data.TxDataBtc; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.service.shift.ShiftException; -import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus; -import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; -import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ServiceHelper; -import com.m2049r.xmrwallet.util.ThemeHelper; - -import java.text.NumberFormat; -import java.util.Locale; - -import timber.log.Timber; - -public class SendBtcSuccessWizardFragment extends SendWizardFragment { - - public static SendBtcSuccessWizardFragment newInstance(SendSuccessWizardFragment.Listener listener) { - SendBtcSuccessWizardFragment instance = new SendBtcSuccessWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - SendSuccessWizardFragment.Listener sendListener; - - public void setSendListener(SendSuccessWizardFragment.Listener listener) { - this.sendListener = listener; - } - - ImageButton bCopyTxId; - private TextView tvTxId; - private TextView tvTxAddress; - private TextView tvTxAmount; - private TextView tvTxFee; - private TextView tvXmrToAmount; - private ImageView ivXmrToIcon; - private TextView tvXmrToStatus; - private ImageView ivXmrToStatus; - private ImageView ivXmrToStatusBig; - private ProgressBar pbXmrto; - private TextView tvTxXmrToKey; - private TextView tvXmrToSupport; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); - - View view = inflater.inflate( - R.layout.fragment_send_btc_success, container, false); - - bCopyTxId = view.findViewById(R.id.bCopyTxId); - bCopyTxId.setEnabled(false); - bCopyTxId.setOnClickListener(v -> { - Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_txid), Toast.LENGTH_SHORT).show(); - }); - - tvXmrToAmount = view.findViewById(R.id.tvXmrToAmount); - ivXmrToIcon = view.findViewById(R.id.ivXmrToIcon); - tvXmrToStatus = view.findViewById(R.id.tvXmrToStatus); - ivXmrToStatus = view.findViewById(R.id.ivXmrToStatus); - ivXmrToStatusBig = view.findViewById(R.id.ivXmrToStatusBig); - - tvTxId = view.findViewById(R.id.tvTxId); - tvTxAddress = view.findViewById(R.id.tvTxAddress); - tvTxAmount = view.findViewById(R.id.tvTxAmount); - tvTxFee = view.findViewById(R.id.tvTxFee); - - pbXmrto = view.findViewById(R.id.pbXmrto); - pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY); - - tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); - tvTxXmrToKey.setOnClickListener(v -> { - Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show(); - }); - - tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport); - tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - - return view; - } - - @Override - public boolean onValidateFields() { - return true; - } - - private boolean isResumed = false; - - @Override - public void onPauseFragment() { - isResumed = false; - super.onPauseFragment(); - } - - TxDataBtc btcData = null; - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - Helper.hideKeyboard(getActivity()); - isResumed = true; - - btcData = (TxDataBtc) sendListener.getTxData(); - tvTxAddress.setText(btcData.getDestinationAddress()); - - final PendingTx committedTx = sendListener.getCommittedTx(); - if (committedTx != null) { - tvTxId.setText(committedTx.txId); - bCopyTxId.setEnabled(true); - tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount))); - tvTxFee.setText(getString(R.string.send_fee, Helper.getDisplayAmount(committedTx.fee))); - if (btcData != null) { - NumberFormat df = NumberFormat.getInstance(Locale.US); - df.setMaximumFractionDigits(12); - String btcAmount = df.format(btcData.getBtcAmount()); - tvXmrToAmount.setText(getString(R.string.info_send_xmrto_success_btc, btcAmount, btcData.getBtcSymbol())); - //TODO btcData.getBtcAddress(); - tvTxXmrToKey.setText(btcData.getXmrtoOrderId()); - final Crypto crypto = Crypto.withSymbol(btcData.getBtcSymbol()); - ivXmrToIcon.setImageResource(crypto.getIconEnabledId()); - tvXmrToSupport.setOnClickListener(v -> { - Uri orderUri = getXmrToApi().getQueryOrderUri(btcData.getXmrtoOrderId()); - Intent intent = new Intent(Intent.ACTION_VIEW, orderUri); - startActivity(intent); - }); - queryOrder(); - } else { - throw new IllegalStateException("btcData is null"); - } - } - sendListener.enableDone(); - } - - private void processQueryOrder(final QueryOrderStatus status) { - Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getOrderId()); - if (!btcData.getXmrtoOrderId().equals(status.getOrderId())) - throw new IllegalStateException("UUIDs do not match!"); - if (isResumed && (getView() != null)) - getView().post(() -> { - showXmrToStatus(status); - if (!status.isTerminal()) { - getView().postDelayed(this::queryOrder, SideShiftApi.QUERY_INTERVAL); - } - }); - } - - private void queryOrder() { - Timber.d("queryOrder(%s)", btcData.getXmrtoOrderId()); - if (!isResumed) return; - getXmrToApi().queryOrderStatus(btcData.getXmrtoOrderId(), new ShiftCallback() { - @Override - public void onSuccess(QueryOrderStatus status) { - if (!isAdded()) return; - processQueryOrder(status); - } - - @Override - public void onError(final Exception ex) { - if (!isResumed) return; - Timber.w(ex); - getActivity().runOnUiThread(() -> { - if (ex instanceof ShiftException) { - Toast.makeText(getActivity(), ((ShiftException) ex).getError().getErrorMsg(), Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(getActivity(), ex.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - }); - } - }); - } - - void showXmrToStatus(final QueryOrderStatus status) { - int statusResource = 0; - if (status.isError()) { - tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString())); - statusResource = R.drawable.ic_error_red_24dp; - pbXmrto.getIndeterminateDrawable().setColorFilter( - ThemeHelper.getThemedColor(getContext(), android.R.attr.colorError), - android.graphics.PorterDuff.Mode.MULTIPLY); - } else if (status.isSent() || status.isPaid()) { - tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent, btcData.getBtcSymbol())); - statusResource = R.drawable.ic_success; - pbXmrto.getIndeterminateDrawable().setColorFilter( - ThemeHelper.getThemedColor(getContext(), R.attr.positiveColor), - android.graphics.PorterDuff.Mode.MULTIPLY); - } else if (status.isWaiting()) { - tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid)); - statusResource = R.drawable.ic_pending; - pbXmrto.getIndeterminateDrawable().setColorFilter( - ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor), - android.graphics.PorterDuff.Mode.MULTIPLY); - } else if (status.isPending()) { - tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid)); - statusResource = R.drawable.ic_pending; - pbXmrto.getIndeterminateDrawable().setColorFilter( - ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor), - android.graphics.PorterDuff.Mode.MULTIPLY); - } else { - throw new IllegalStateException("status is broken: " + status.toString()); - } - ivXmrToStatus.setImageResource(statusResource); - if (status.isTerminal()) { - pbXmrto.setVisibility(View.INVISIBLE); - ivXmrToIcon.setVisibility(View.GONE); - ivXmrToStatus.setVisibility(View.GONE); - ivXmrToStatusBig.setImageResource(statusResource); - ivXmrToStatusBig.setVisibility(View.VISIBLE); - } - } - - private SideShiftApi xmrToApi = null; - - private SideShiftApi getXmrToApi() { - if (xmrToApi == null) { - synchronized (this) { - if (xmrToApi == null) { - xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl()); - } - } - } - return xmrToApi; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirm.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirm.java deleted file mode 100644 index 69e97c8..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirm.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import com.m2049r.xmrwallet.model.PendingTransaction; - -interface SendConfirm { - void sendFailed(String errorText); - - void createTransactionFailed(String errorText); - - void transactionCreated(String txTag, PendingTransaction pendingTransaction); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java deleted file mode 100644 index f4d334e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.data.UserNotes; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; - -import timber.log.Timber; - -public class SendConfirmWizardFragment extends SendWizardFragment implements SendConfirm { - - public static SendConfirmWizardFragment newInstance(Listener listener) { - SendConfirmWizardFragment instance = new SendConfirmWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - Listener sendListener; - - public SendConfirmWizardFragment setSendListener(Listener listener) { - this.sendListener = listener; - return this; - } - - interface Listener { - SendFragment.Listener getActivityCallback(); - - TxData getTxData(); - - void commitTransaction(); - - void disposeTransaction(); - - SendFragment.Mode getMode(); - } - - private TextView tvTxAddress; - private TextView tvTxNotes; - private TextView tvTxAmount; - private TextView tvTxFee; - private TextView tvTxTotal; - private View llProgress; - private View bSend; - private View llConfirmSend; - private View pbProgressSend; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); - - View view = inflater.inflate( - R.layout.fragment_send_confirm, container, false); - - tvTxAddress = view.findViewById(R.id.tvTxAddress); - tvTxNotes = view.findViewById(R.id.tvTxNotes); - tvTxAmount = view.findViewById(R.id.tvTxAmount); - tvTxFee = view.findViewById(R.id.tvTxFee); - tvTxTotal = view.findViewById(R.id.tvTxTotal); - - llProgress = view.findViewById(R.id.llProgress); - pbProgressSend = view.findViewById(R.id.pbProgressSend); - llConfirmSend = view.findViewById(R.id.llConfirmSend); - - bSend = view.findViewById(R.id.bSend); - bSend.setEnabled(false); - bSend.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Timber.d("bSend.setOnClickListener"); - bSend.setEnabled(false); - preSend(); - } - }); - return view; - } - - boolean inProgress = false; - - public void hideProgress() { - llProgress.setVisibility(View.INVISIBLE); - inProgress = false; - } - - public void showProgress() { - llProgress.setVisibility(View.VISIBLE); - inProgress = true; - } - - PendingTransaction pendingTransaction = null; - - @Override - // callback from wallet when PendingTransaction created - public void transactionCreated(String txTag, PendingTransaction pendingTransaction) { - // ignore txTag - the app flow ensures this is the correct tx - // TODO: use the txTag - hideProgress(); - if (isResumed) { - this.pendingTransaction = pendingTransaction; - refreshTransactionDetails(); - } else { - sendListener.disposeTransaction(); - } - } - - void send() { - sendListener.commitTransaction(); - getActivity().runOnUiThread(() -> pbProgressSend.setVisibility(View.VISIBLE)); - } - - @Override - public void sendFailed(String errorText) { - pbProgressSend.setVisibility(View.INVISIBLE); - showAlert(getString(R.string.send_create_tx_error_title), errorText); - } - - @Override - public void createTransactionFailed(String errorText) { - hideProgress(); - showAlert(getString(R.string.send_create_tx_error_title), errorText); - } - - private void showAlert(String title, String message) { - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(getActivity()); - builder.setCancelable(true). - setTitle(title). - setMessage(message). - create(). - show(); - } - - @Override - public boolean onValidateFields() { - return true; - } - - private boolean isResumed = false; - - @Override - public void onPauseFragment() { - isResumed = false; - pendingTransaction = null; - sendListener.disposeTransaction(); - refreshTransactionDetails(); - super.onPauseFragment(); - } - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - Helper.hideKeyboard(getActivity()); - isResumed = true; - - final TxData txData = sendListener.getTxData(); - tvTxAddress.setText(txData.getDestinationAddress()); - UserNotes notes = sendListener.getTxData().getUserNotes(); - if ((notes != null) && (!notes.note.isEmpty())) { - tvTxNotes.setText(notes.note); - } else { - tvTxNotes.setText("-"); - } - refreshTransactionDetails(); - if ((pendingTransaction == null) && (!inProgress)) { - showProgress(); - prepareSend(txData); - } - } - - void refreshTransactionDetails() { - Timber.d("refreshTransactionDetails()"); - if (pendingTransaction != null) { - llConfirmSend.setVisibility(View.VISIBLE); - bSend.setEnabled(true); - tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee())); - if (getActivityCallback().isStreetMode() - && (sendListener.getTxData().getAmount() == Wallet.SWEEP_ALL)) { - tvTxAmount.setText(getString(R.string.street_sweep_amount)); - tvTxTotal.setText(getString(R.string.street_sweep_amount)); - } else { - tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount())); - tvTxTotal.setText(Wallet.getDisplayAmount( - pendingTransaction.getFee() + pendingTransaction.getAmount())); - } - } else { - llConfirmSend.setVisibility(View.GONE); - bSend.setEnabled(false); - } - } - - public void preSend() { - Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() { - @Override - public void act(String walletName, String password, boolean fingerprintUsed) { - send(); - } - - public void fail(String walletName) { - getActivity().runOnUiThread(() -> { - bSend.setEnabled(true); // allow to try again - }); - } - }); - } - - // creates a pending transaction and calls us back with transactionCreated() - // or createTransactionFailed() - void prepareSend(TxData txData) { - getActivityCallback().onPrepareSend(null, txData); - } - - SendFragment.Listener getActivityCallback() { - return sendListener.getActivityCallback(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java deleted file mode 100644 index ce82795..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendFragment.java +++ /dev/null @@ -1,558 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.text.InputType; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.transition.MaterialContainerTransform; -import com.m2049r.xmrwallet.OnBackPressedListener; -import com.m2049r.xmrwallet.OnUriScannedListener; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.WalletActivity; -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.PendingTx; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.data.TxDataBtc; -import com.m2049r.xmrwallet.data.UserNotes; -import com.m2049r.xmrwallet.layout.SpendViewPager; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.Notice; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.widget.DotBar; -import com.m2049r.xmrwallet.widget.Toolbar; - -import java.lang.ref.WeakReference; - -import timber.log.Timber; - -public class SendFragment extends Fragment - implements SendAddressWizardFragment.Listener, - SendAmountWizardFragment.Listener, - SendConfirmWizardFragment.Listener, - SendSuccessWizardFragment.Listener, - OnBackPressedListener, OnUriScannedListener { - - final static public int MIXIN = 0; - - private Listener activityCallback; - - public interface Listener { - SharedPreferences getPrefs(); - - long getTotalFunds(); - - boolean isStreetMode(); - - void onPrepareSend(String tag, TxData data); - - String getWalletName(); - - void onSend(UserNotes notes); - - void onDisposeRequest(); - - void onFragmentDone(); - - void setToolbarButton(int type); - - void setTitle(String title); - - void setSubtitle(String subtitle); - - void setOnUriScannedListener(OnUriScannedListener onUriScannedListener); - } - - private View llNavBar; - private DotBar dotBar; - private Button bPrev; - private Button bNext; - - private Button bDone; - - static private final int MAX_FALLBACK = Integer.MAX_VALUE; - - public static SendFragment newInstance(String uri) { - SendFragment f = new SendFragment(); - Bundle args = new Bundle(); - args.putString(WalletActivity.REQUEST_URI, uri); - f.setArguments(args); - return f; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - final View view = inflater.inflate(R.layout.fragment_send, container, false); - - llNavBar = view.findViewById(R.id.llNavBar); - bDone = view.findViewById(R.id.bDone); - - dotBar = view.findViewById(R.id.dotBar); - bPrev = view.findViewById(R.id.bPrev); - bNext = view.findViewById(R.id.bNext); - - ViewGroup llNotice = view.findViewById(R.id.llNotice); - Notice.showAll(llNotice, ".*_send"); - - spendViewPager = view.findViewById(R.id.pager); - pagerAdapter = new SpendPagerAdapter(getChildFragmentManager()); - spendViewPager.setOffscreenPageLimit(pagerAdapter.getCount()); // load & keep all pages in cache - spendViewPager.setAdapter(pagerAdapter); - - spendViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - private int fallbackPosition = MAX_FALLBACK; - private int currentPosition = 0; - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int newPosition) { - Timber.d("onPageSelected=%d/%d", newPosition, fallbackPosition); - if (fallbackPosition < newPosition) { - spendViewPager.setCurrentItem(fallbackPosition); - } else { - pagerAdapter.getFragment(currentPosition).onPauseFragment(); - pagerAdapter.getFragment(newPosition).onResumeFragment(); - updatePosition(newPosition); - currentPosition = newPosition; - fallbackPosition = MAX_FALLBACK; - } - } - - @Override - public void onPageScrollStateChanged(int state) { - if (state == ViewPager.SCROLL_STATE_DRAGGING) { - if (!spendViewPager.validateFields(spendViewPager.getCurrentItem())) { - fallbackPosition = spendViewPager.getCurrentItem(); - } else { - fallbackPosition = spendViewPager.getCurrentItem() + 1; - } - } - } - }); - - bPrev.setOnClickListener(v -> spendViewPager.previous()); - - bNext.setOnClickListener(v -> spendViewPager.next()); - - bDone.setOnClickListener(v -> { - Timber.d("bDone.onClick"); - activityCallback.onFragmentDone(); - }); - - updatePosition(0); - - final EditText etDummy = view.findViewById(R.id.etDummy); - etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - etDummy.requestFocus(); - Helper.hideKeyboard(getActivity()); - - Bundle args = getArguments(); - if (args != null) { - String uri = args.getString(WalletActivity.REQUEST_URI); - Timber.d("URI: %s", uri); - if (uri != null) { - barcodeData = BarcodeData.fromString(uri); - Timber.d("barcodeData: %s", barcodeData != null ? barcodeData.toString() : "null"); - } - } - - return view; - } - - void updatePosition(int position) { - dotBar.setActiveDot(position); - CharSequence nextLabel = pagerAdapter.getPageTitle(position + 1); - bNext.setText(nextLabel); - if (nextLabel != null) { - bNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_navigate_next, 0); - } else { - bNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - CharSequence prevLabel = pagerAdapter.getPageTitle(position - 1); - bPrev.setText(prevLabel); - if (prevLabel != null) { - bPrev.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_navigate_prev, 0, 0, 0); - } else { - bPrev.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - } - - @Override - public void onResume() { - super.onResume(); - Timber.d("onResume"); - activityCallback.setSubtitle(getString(R.string.send_title)); - if (spendViewPager.getCurrentItem() == SpendPagerAdapter.POS_SUCCESS) { - activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); - } else { - activityCallback.setToolbarButton(Toolbar.BUTTON_CANCEL); - } - } - - @Override - public void onAttach(@NonNull Context context) { - Timber.d("onAttach %s", context); - super.onAttach(context); - if (context instanceof Listener) { - activityCallback = (Listener) context; - activityCallback.setOnUriScannedListener(this); - } else { - throw new ClassCastException(context.toString() - + " must implement Listener"); - } - } - - @Override - public void onDetach() { - activityCallback.setOnUriScannedListener(null); - super.onDetach(); - } - - private SpendViewPager spendViewPager; - private SpendPagerAdapter pagerAdapter; - - @Override - public boolean onBackPressed() { - if (isComitted()) return true; // no going back - if (spendViewPager.getCurrentItem() == 0) { - return false; - } else { - spendViewPager.previous(); - return true; - } - } - - @Override - public boolean onUriScanned(BarcodeData barcodeData) { - if (spendViewPager.getCurrentItem() == SpendPagerAdapter.POS_ADDRESS) { - final SendWizardFragment fragment = pagerAdapter.getFragment(SpendPagerAdapter.POS_ADDRESS); - if (fragment instanceof SendAddressWizardFragment) { - ((SendAddressWizardFragment) fragment).processScannedData(barcodeData); - return true; - } - } - return false; - } - - enum Mode { - XMR, BTC - } - - Mode mode = Mode.XMR; - - @Override - public void setMode(Mode aMode) { - if (mode != aMode) { - mode = aMode; - switch (aMode) { - case XMR: - txData = new TxData(); - break; - case BTC: - txData = new TxDataBtc(); - break; - default: - throw new IllegalArgumentException("Mode " + String.valueOf(aMode) + " unknown!"); - } - getView().post(() -> pagerAdapter.notifyDataSetChanged()); - Timber.d("New Mode = %s", mode.toString()); - } - } - - @Override - public Mode getMode() { - return mode; - } - - public class SpendPagerAdapter extends FragmentStatePagerAdapter { - private static final int POS_ADDRESS = 0; - private static final int POS_AMOUNT = 1; - private static final int POS_CONFIRM = 2; - private static final int POS_SUCCESS = 3; - private int numPages = 3; - - SparseArray> myFragments = new SparseArray<>(); - - public SpendPagerAdapter(FragmentManager fm) { - super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - } - - public void addSuccess() { - numPages++; - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return numPages; - } - - @NonNull - @Override - public Object instantiateItem(@NonNull ViewGroup container, int position) { - Timber.d("instantiateItem %d", position); - SendWizardFragment fragment = (SendWizardFragment) super.instantiateItem(container, position); - myFragments.put(position, new WeakReference<>(fragment)); - return fragment; - } - - @Override - public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { - Timber.d("destroyItem %d", position); - myFragments.remove(position); - super.destroyItem(container, position, object); - } - - public SendWizardFragment getFragment(int position) { - WeakReference ref = myFragments.get(position); - if (ref != null) - return myFragments.get(position).get(); - else - return null; - } - - @NonNull - @Override - public SendWizardFragment getItem(int position) { - Timber.d("getItem(%d) CREATE", position); - Timber.d("Mode=%s", mode.toString()); - if (mode == Mode.XMR) { - switch (position) { - case POS_ADDRESS: - return SendAddressWizardFragment.newInstance(SendFragment.this); - case POS_AMOUNT: - return SendAmountWizardFragment.newInstance(SendFragment.this); - case POS_CONFIRM: - return SendConfirmWizardFragment.newInstance(SendFragment.this); - case POS_SUCCESS: - return SendSuccessWizardFragment.newInstance(SendFragment.this); - default: - throw new IllegalArgumentException("no such send position(" + position + ")"); - } - } else if (mode == Mode.BTC) { - switch (position) { - case POS_ADDRESS: - return SendAddressWizardFragment.newInstance(SendFragment.this); - case POS_AMOUNT: - return SendBtcAmountWizardFragment.newInstance(SendFragment.this); - case POS_CONFIRM: - return SendBtcConfirmWizardFragment.newInstance(SendFragment.this); - case POS_SUCCESS: - return SendBtcSuccessWizardFragment.newInstance(SendFragment.this); - default: - throw new IllegalArgumentException("no such send position(" + position + ")"); - } - } else { - throw new IllegalStateException("Unknown mode!"); - } - } - - @Override - public CharSequence getPageTitle(int position) { - Timber.d("getPageTitle(%d)", position); - if (position >= numPages) return null; - switch (position) { - case POS_ADDRESS: - return getString(R.string.send_address_title); - case POS_AMOUNT: - return getString(R.string.send_amount_title); - case POS_CONFIRM: - return getString(R.string.send_confirm_title); - case POS_SUCCESS: - return getString(R.string.send_success_title); - default: - return null; - } - } - - @Override - public int getItemPosition(@NonNull Object object) { - Timber.d("getItemPosition %s", String.valueOf(object)); - if (object instanceof SendAddressWizardFragment) { - // keep these pages - return POSITION_UNCHANGED; - } else { - return POSITION_NONE; - } - } - } - - @Override - public TxData getTxData() { - return txData; - } - - private TxData txData = new TxData(); - - private BarcodeData barcodeData; - - // Listeners - @Override - public void setBarcodeData(BarcodeData data) { - barcodeData = data; - } - - @Override - public BarcodeData getBarcodeData() { - return barcodeData; - } - - @Override - public BarcodeData popBarcodeData() { - Timber.d("POPPED"); - BarcodeData data = barcodeData; - barcodeData = null; - return data; - } - - boolean isComitted() { - return committedTx != null; - } - - PendingTx committedTx; - - @Override - public PendingTx getCommittedTx() { - return committedTx; - } - - - @Override - public void commitTransaction() { - Timber.d("REALLY SEND"); - disableNavigation(); // committed - disable all navigation - activityCallback.onSend(txData.getUserNotes()); - committedTx = pendingTx; - } - - void disableNavigation() { - spendViewPager.allowSwipe(false); - } - - void enableNavigation() { - spendViewPager.allowSwipe(true); - } - - @Override - public void enableDone() { - llNavBar.setVisibility(View.INVISIBLE); - bDone.setVisibility(View.VISIBLE); - } - - public Listener getActivityCallback() { - return activityCallback; - } - - - // callbacks from send service - - public void onTransactionCreated(final String txTag, final PendingTransaction pendingTransaction) { - final SendConfirm confirm = getSendConfirm(); - if (confirm != null) { - pendingTx = new PendingTx(pendingTransaction); - confirm.transactionCreated(txTag, pendingTransaction); - } else { - // not in confirm fragment => dispose & move on - disposeTransaction(); - } - } - - @Override - public void disposeTransaction() { - pendingTx = null; - activityCallback.onDisposeRequest(); - } - - PendingTx pendingTx; - - public PendingTx getPendingTx() { - return pendingTx; - } - - public void onCreateTransactionFailed(String errorText) { - final SendConfirm confirm = getSendConfirm(); - if (confirm != null) { - confirm.createTransactionFailed(errorText); - } - } - - SendConfirm getSendConfirm() { - final SendWizardFragment fragment = pagerAdapter.getFragment(SpendPagerAdapter.POS_CONFIRM); - if (fragment instanceof SendConfirm) { - return (SendConfirm) fragment; - } else { - return null; - } - } - - public void onTransactionSent(final String txId) { - Timber.d("txid=%s", txId); - pagerAdapter.addSuccess(); - Timber.d("numPages=%d", spendViewPager.getAdapter().getCount()); - activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); - spendViewPager.setCurrentItem(SpendPagerAdapter.POS_SUCCESS); - } - - public void onSendTransactionFailed(final String error) { - Timber.d("error=%s", error); - committedTx = null; - final SendConfirm confirm = getSendConfirm(); - if (confirm != null) { - confirm.sendFailed(getString(R.string.status_transaction_failed, error)); - } - enableNavigation(); - } - - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - final MaterialContainerTransform transform = new MaterialContainerTransform(); - transform.setDrawingViewId(R.id.fragment_container); - transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration)); - transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground)); - setSharedElementEnterTransition(transform); - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.send_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java deleted file mode 100644 index 34f3339..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendSuccessWizardFragment.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.TextView; -import android.widget.Toast; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.PendingTx; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.Helper; - -import timber.log.Timber; - -public class SendSuccessWizardFragment extends SendWizardFragment { - - public static SendSuccessWizardFragment newInstance(Listener listener) { - SendSuccessWizardFragment instance = new SendSuccessWizardFragment(); - instance.setSendListener(listener); - return instance; - } - - Listener sendListener; - - public SendSuccessWizardFragment setSendListener(Listener listener) { - this.sendListener = listener; - return this; - } - - interface Listener { - TxData getTxData(); - - PendingTx getCommittedTx(); - - void enableDone(); - - SendFragment.Mode getMode(); - - SendFragment.Listener getActivityCallback(); - } - - ImageButton bCopyTxId; - private TextView tvTxId; - private TextView tvTxAddress; - private TextView tvTxPaymentId; - private TextView tvTxAmount; - private TextView tvTxFee; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - Timber.d("onCreateView() %s", (String.valueOf(savedInstanceState))); - - View view = inflater.inflate( - R.layout.fragment_send_success, container, false); - - bCopyTxId = view.findViewById(R.id.bCopyTxId); - bCopyTxId.setEnabled(false); - bCopyTxId.setOnClickListener(v -> { - Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString()); - Toast.makeText(getActivity(), getString(R.string.message_copy_txid), Toast.LENGTH_SHORT).show(); - }); - - tvTxId = view.findViewById(R.id.tvTxId); - tvTxAddress = view.findViewById(R.id.tvTxAddress); - tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId); - tvTxAmount = view.findViewById(R.id.tvTxAmount); - tvTxFee = view.findViewById(R.id.tvTxFee); - - return view; - } - - @Override - public boolean onValidateFields() { - return true; - } - - @Override - public void onPauseFragment() { - super.onPauseFragment(); - } - - @Override - public void onResumeFragment() { - super.onResumeFragment(); - Timber.d("onResumeFragment()"); - Helper.hideKeyboard(getActivity()); - - final TxData txData = sendListener.getTxData(); - tvTxAddress.setText(txData.getDestinationAddress()); - - final PendingTx committedTx = sendListener.getCommittedTx(); - if (committedTx != null) { - tvTxId.setText(committedTx.txId); - bCopyTxId.setEnabled(true); - - if (sendListener.getActivityCallback().isStreetMode() - && (sendListener.getTxData().getAmount() == Wallet.SWEEP_ALL)) { - tvTxAmount.setText(getString(R.string.street_sweep_amount)); - } else { - tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount))); - } - tvTxFee.setText(getString(R.string.send_fee, Helper.getDisplayAmount(committedTx.fee))); - } - sendListener.enableDone(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendWizardFragment.java deleted file mode 100644 index 5848ad8..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendWizardFragment.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.fragment.send; - -import androidx.fragment.app.Fragment; - -import com.m2049r.xmrwallet.layout.SpendViewPager; - -abstract public class SendWizardFragment extends Fragment - implements SpendViewPager.OnValidateFieldsListener { - - @Override - public boolean onValidateFields() { - return true; - } - - public void onPauseFragment() { - } - - public void onResumeFragment() { - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java new file mode 100644 index 0000000..bd70220 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java @@ -0,0 +1,39 @@ +package com.m2049r.xmrwallet.fragment.settings; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.fragment.NavHostFragment; + +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.Wallet; + +public class SettingsFragment extends Fragment { + + private SettingsViewModel mViewModel; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_settings, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mViewModel = new ViewModelProvider(this).get(SettingsViewModel.class); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java new file mode 100644 index 0000000..58d27c6 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsViewModel.java @@ -0,0 +1,7 @@ +package com.m2049r.xmrwallet.fragment.settings; + +import androidx.lifecycle.ViewModel; + +public class SettingsViewModel extends ViewModel{ + +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/DiffCallback.java b/app/src/main/java/com/m2049r/xmrwallet/layout/DiffCallback.java deleted file mode 100644 index 231f7c1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/DiffCallback.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 yorha-0x - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.layout; - -import androidx.recyclerview.widget.DiffUtil; - -import java.util.List; - -public abstract class DiffCallback extends DiffUtil.Callback { - - protected final List mOldList; - protected final List mNewList; - - public DiffCallback(List oldList, List newList) { - this.mOldList = oldList; - this.mNewList = newList; - } - - @Override - public int getOldListSize() { - return mOldList.size(); - } - - @Override - public int getNewListSize() { - return mNewList.size(); - } - - public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition); - - public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java deleted file mode 100644 index baae49e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/NodeInfoAdapter.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.layout; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.NodeInfo; -import com.m2049r.xmrwallet.dialog.HelpFragment; -import com.m2049r.xmrwallet.util.NetCipherHelper; - -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public class NodeInfoAdapter extends RecyclerView.Adapter { - public interface OnInteractionListener { - void onInteraction(View view, NodeInfo item); - - boolean onLongInteraction(View view, NodeInfo item); - } - - private final List nodeItems = new ArrayList<>(); - private final OnInteractionListener listener; - - private final FragmentActivity activity; - - public NodeInfoAdapter(FragmentActivity activity, OnInteractionListener listener) { - this.activity = activity; - this.listener = listener; - } - - public void notifyItemChanged(NodeInfo nodeInfo) { - final int pos = nodeItems.indexOf(nodeInfo); - if (pos >= 0) notifyItemChanged(pos); - } - - private static class NodeDiff extends DiffCallback { - - public NodeDiff(List oldList, List newList) { - super(oldList, newList); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition)); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - final NodeInfo oldItem = mOldList.get(oldItemPosition); - final NodeInfo newItem = mNewList.get(newItemPosition); - return (oldItem.getTimestamp() == newItem.getTimestamp()) - && (oldItem.isTested() == newItem.isTested()) - && (oldItem.isValid() == newItem.isValid()) - && (oldItem.getResponseTime() == newItem.getResponseTime()) - && (oldItem.isSelected() == newItem.isSelected()) - && (oldItem.getName().equals(newItem.getName())); - } - } - - @Override - public @NonNull - ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_node, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(final @NonNull ViewHolder holder, int position) { - holder.bind(position); - } - - @Override - public int getItemCount() { - return nodeItems.size(); - } - - public void addNode(NodeInfo node) { - List newItems = new ArrayList<>(nodeItems); - if (!nodeItems.contains(node)) - newItems.add(node); - setNodes(newItems); // in case the nodeinfo has changed - } - - public void setNodes(Collection newItemsCollection) { - List newItems; - if (newItemsCollection != null) { - newItems = new ArrayList<>(newItemsCollection); - Collections.sort(newItems, NodeInfo.BestNodeComparator); - } else { - newItems = new ArrayList<>(); - } - final NodeInfoAdapter.NodeDiff diffCallback = new NodeInfoAdapter.NodeDiff(nodeItems, newItems); - final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); - nodeItems.clear(); - nodeItems.addAll(newItems); - diffResult.dispatchUpdatesTo(this); - } - - public void setNodes() { - setNodes(nodeItems); - } - - private boolean itemsClickable = true; - - public void allowClick(boolean clickable) { - itemsClickable = clickable; - notifyDataSetChanged(); - } - - class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - final ImageButton ibBookmark; - final View pbBookmark; - final TextView tvName; - final TextView tvInfo; - final ImageView ivPing; - NodeInfo nodeItem; - - ViewHolder(View itemView) { - super(itemView); - ibBookmark = itemView.findViewById(R.id.ibBookmark); - pbBookmark = itemView.findViewById(R.id.pbBookmark); - tvName = itemView.findViewById(R.id.tvName); - tvInfo = itemView.findViewById(R.id.tvInfo); - ivPing = itemView.findViewById(R.id.ivPing); - ibBookmark.setOnClickListener(v -> { - nodeItem.toggleFavourite(); - showStar(); - if (!nodeItem.isFavourite()) { - nodeItem.setSelected(false); - setNodes(nodeItems); - } - }); - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); - } - - private void showStar() { - if (nodeItem.isFavourite()) { - ibBookmark.setImageResource(R.drawable.ic_favorite_24dp); - } else { - ibBookmark.setImageResource(R.drawable.ic_favorite_border_24dp); - } - } - - void bind(int position) { - nodeItem = nodeItems.get(position); - tvName.setText(nodeItem.getName()); - ivPing.setImageResource(getPingIcon(nodeItem)); - if (nodeItem.isTested()) { - if (nodeItem.isValid()) { - nodeItem.showInfo(tvInfo); - } else { - nodeItem.showInfo(tvInfo, getResponseErrorText(activity, nodeItem.getResponseCode()), true); - } - } else { - nodeItem.showInfo(tvInfo); - } - itemView.setSelected(nodeItem.isSelected()); - itemView.setClickable(itemsClickable); - itemView.setEnabled(itemsClickable); - ibBookmark.setClickable(itemsClickable); - pbBookmark.setVisibility(nodeItem.isSelecting() ? View.VISIBLE : View.INVISIBLE); - showStar(); - } - - @Override - public void onClick(View view) { - if (listener != null) { - int position = getAdapterPosition(); // gets item position - if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it - final NodeInfo node = nodeItems.get(position); - if (node.isOnion()) { - switch (NetCipherHelper.getStatus()) { - case NOT_INSTALLED: - HelpFragment.display(activity.getSupportFragmentManager(), R.string.help_tor); - return; - case DISABLED: - HelpFragment.display(activity.getSupportFragmentManager(), R.string.help_tor_enable); - return; - } - } - node.setSelecting(true); - allowClick(false); - listener.onInteraction(view, node); - } - } - } - - @Override - public boolean onLongClick(View view) { - if (listener != null) { - int position = getAdapterPosition(); // gets item position - if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it - return listener.onLongInteraction(view, nodeItems.get(position)); - } - } - return false; - } - } - - static public int getPingIcon(NodeInfo nodeInfo) { - if (nodeInfo.isUnauthorized()) { - return R.drawable.ic_wifi_lock; - } - if (nodeInfo.isValid()) { - final double ping = nodeInfo.getResponseTime(); - if (ping < NodeInfo.PING_GOOD) { - return R.drawable.ic_wifi_4_bar; - } else if (ping < NodeInfo.PING_MEDIUM) { - return R.drawable.ic_wifi_3_bar; - } else if (ping < NodeInfo.PING_BAD) { - return R.drawable.ic_wifi_2_bar; - } else { - return R.drawable.ic_wifi_1_bar; - } - } else { - return R.drawable.ic_wifi_off; - } - } - - static public String getResponseErrorText(Context ctx, int responseCode) { - if (responseCode == 0) { - return ctx.getResources().getString(R.string.node_general_error); - } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { - return ctx.getResources().getString(R.string.node_auth_error); - } else if (responseCode == 418) { - return ctx.getResources().getString(R.string.node_tor_error); - } else { - return ctx.getResources().getString(R.string.node_test_error, responseCode); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java b/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java deleted file mode 100644 index 71b7e0c..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/SpendViewPager.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.layout; - -import android.content.Context; -import androidx.viewpager.widget.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import com.m2049r.xmrwallet.fragment.send.SendFragment; - -public class SpendViewPager extends ViewPager { - - public interface OnValidateFieldsListener { - boolean onValidateFields(); - } - - public SpendViewPager(Context context) { - super(context); - } - - public SpendViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void next() { - int pos = getCurrentItem(); - if (validateFields(pos)) { - setCurrentItem(pos + 1); - } - } - - public void previous() { - setCurrentItem(getCurrentItem() - 1); - } - - private boolean allowSwipe = true; - - public void allowSwipe(boolean allow) { - allowSwipe = allow; - } - - public boolean validateFields(int position) { - OnValidateFieldsListener c = ((SendFragment.SpendPagerAdapter) getAdapter()).getFragment(position); - return c.onValidateFields(); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - if (allowSwipe) return super.onInterceptTouchEvent(event); - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (allowSwipe) return super.onTouchEvent(event); - return false; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/SubaddressInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/SubaddressInfoAdapter.java deleted file mode 100644 index 6dab349..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/SubaddressInfoAdapter.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.layout; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.Subaddress; -import com.m2049r.xmrwallet.util.Helper; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import timber.log.Timber; - -public class SubaddressInfoAdapter extends RecyclerView.Adapter { - public interface OnInteractionListener { - void onInteraction(View view, Subaddress item); - - boolean onLongInteraction(View view, Subaddress item); - } - - private final List items; - private final OnInteractionListener listener; - - Context context; - - public SubaddressInfoAdapter(Context context, OnInteractionListener listener) { - this.context = context; - this.items = new ArrayList<>(); - this.listener = listener; - } - - private static class SubaddressInfoDiff extends DiffCallback { - - public SubaddressInfoDiff(List oldList, List newList) { - super(oldList, newList); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return mOldList.get(oldItemPosition).getAddress().equals(mNewList.get(newItemPosition).getAddress()); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition)); - } - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_subaddress, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - holder.bind(position); - } - - @Override - public int getItemCount() { - return items.size(); - } - - public Subaddress getItem(int position) { - return items.get(position); - } - - public void setInfos(List newItems) { - if (newItems == null) { - newItems = new ArrayList<>(); - Timber.d("setInfos null"); - } else { - Timber.d("setInfos %s", newItems.size()); - } - Collections.sort(newItems); - final DiffCallback diffCallback = new SubaddressInfoDiff(items, newItems); - final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); - items.clear(); - items.addAll(newItems); - diffResult.dispatchUpdatesTo(this); - } - - class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { - final TextView tvName; - final TextView tvAddress; - final TextView tvAmount; - Subaddress item; - - ViewHolder(View itemView) { - super(itemView); - tvName = itemView.findViewById(R.id.tvName); - tvAddress = itemView.findViewById(R.id.tvAddress); - tvAmount = itemView.findViewById(R.id.tx_amount); - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); - } - - void bind(int position) { - item = getItem(position); - itemView.setTransitionName(context.getString(R.string.subaddress_item_transition_name, item.getAddressIndex())); - - final String label = item.getDisplayLabel(); - final String address = context.getString(R.string.subbaddress_info_subtitle, - item.getAddressIndex(), item.getSquashedAddress()); - tvName.setText(label.isEmpty() ? address : label); - tvAddress.setText(address); - final long amount = item.getAmount(); - if (amount > 0) - tvAmount.setText(context.getString(R.string.tx_list_amount_positive, - Helper.getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO))); - else - tvAmount.setText(""); - } - - @Override - public void onClick(View view) { - if (listener != null) { - int position = getAdapterPosition(); // gets item position - if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it - listener.onInteraction(view, getItem(position)); - } - } - } - - @Override - public boolean onLongClick(View view) { - if (listener != null) { - int position = getAdapterPosition(); // gets item position - if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it - return listener.onLongInteraction(view, getItem(position)); - } - } - return true; - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java deleted file mode 100644 index c4ce06e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.layout; - -import android.content.Context; -import android.text.Html; -import android.text.Spanned; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.progressindicator.CircularProgressIndicator; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.Crypto; -import com.m2049r.xmrwallet.data.UserNotes; -import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ThemeHelper; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.TimeZone; - -import timber.log.Timber; - -public class TransactionInfoAdapter extends RecyclerView.Adapter { - private final static SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - - private final int outboundColour; - private final int inboundColour; - private final int pendingColour; - private final int failedColour; - - public interface OnInteractionListener { - void onInteraction(View view, TransactionInfo item); - } - - private final List infoItems; - private final OnInteractionListener listener; - - private final Context context; - - public TransactionInfoAdapter(Context context, OnInteractionListener listener) { - this.context = context; - inboundColour = ThemeHelper.getThemedColor(context, R.attr.positiveColor); - outboundColour = ThemeHelper.getThemedColor(context, R.attr.negativeColor); - pendingColour = ThemeHelper.getThemedColor(context, R.attr.neutralColor); - failedColour = ThemeHelper.getThemedColor(context, R.attr.neutralColor); - infoItems = new ArrayList<>(); - this.listener = listener; - Calendar cal = Calendar.getInstance(); - TimeZone tz = cal.getTimeZone(); //get the local time zone. - DATETIME_FORMATTER.setTimeZone(tz); - } - - public boolean needsTransactionUpdateOnNewBlock() { - return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed(); - } - - private static class TransactionInfoDiff extends DiffCallback { - - public TransactionInfoDiff(List oldList, List newList) { - super(oldList, newList); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return mOldList.get(oldItemPosition).hash.equals(mNewList.get(newItemPosition).hash); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - final TransactionInfo oldItem = mOldList.get(oldItemPosition); - final TransactionInfo newItem = mNewList.get(newItemPosition); - return (oldItem.direction == newItem.direction) - && (oldItem.isPending == newItem.isPending) - && (oldItem.isFailed == newItem.isFailed) - && ((oldItem.confirmations == newItem.confirmations) || (oldItem.isConfirmed())) - && (oldItem.subaddressLabel.equals(newItem.subaddressLabel)) - && (Objects.equals(oldItem.notes, newItem.notes)); - } - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - holder.bind(position); - } - - @Override - public int getItemCount() { - return infoItems.size(); - } - - public void setInfos(List newItems) { - if (newItems == null) { - newItems = new ArrayList<>(); - Timber.d("setInfos null"); - } else { - Timber.d("setInfos %s", newItems.size()); - } - Collections.sort(newItems); - final DiffCallback diffCallback = new TransactionInfoAdapter.TransactionInfoDiff(infoItems, newItems); - final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); - infoItems.clear(); - infoItems.addAll(newItems); - diffResult.dispatchUpdatesTo(this); - } - - public void removeItem(int position) { - List newItems = new ArrayList<>(infoItems); - if (newItems.size() > position) - newItems.remove(position); - setInfos(newItems); // in case the nodeinfo has changed - } - - public TransactionInfo getItem(int position) { - return infoItems.get(position); - } - - class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - final ImageView ivTxType; - final TextView tvAmount; - final TextView tvFailed; - final TextView tvPaymentId; - final TextView tvDateTime; - final CircularProgressIndicator pbConfirmations; - final TextView tvConfirmations; - TransactionInfo infoItem; - - ViewHolder(View itemView) { - super(itemView); - ivTxType = itemView.findViewById(R.id.ivTxType); - tvAmount = itemView.findViewById(R.id.tx_amount); - tvFailed = itemView.findViewById(R.id.tx_failed); - tvPaymentId = itemView.findViewById(R.id.tx_paymentid); - tvDateTime = itemView.findViewById(R.id.tx_datetime); - pbConfirmations = itemView.findViewById(R.id.pbConfirmations); - pbConfirmations.setMax(TransactionInfo.CONFIRMATION); - tvConfirmations = itemView.findViewById(R.id.tvConfirmations); - } - - private String getDateTime(long time) { - return DATETIME_FORMATTER.format(new Date(time * 1000)); - } - - private void setTxColour(int clr) { - tvAmount.setTextColor(clr); - } - - void bind(int position) { - infoItem = infoItems.get(position); - itemView.setTransitionName(context.getString(R.string.tx_item_transition_name, infoItem.hash)); - - UserNotes userNotes = new UserNotes(infoItem.notes); - if (userNotes.xmrtoKey != null) { - final Crypto crypto = Crypto.withSymbol(userNotes.xmrtoCurrency); - if (crypto != null) { - ivTxType.setImageResource(crypto.getIconEnabledId()); - ivTxType.setVisibility(View.VISIBLE); - } else {// otherwirse pretend we don't know it's a shift - ivTxType.setVisibility(View.GONE); - } - } else { - ivTxType.setVisibility(View.GONE); - } - - String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO); - if (infoItem.direction == TransactionInfo.Direction.Direction_Out) { - tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount)); - } else { - tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount)); - } - - tvFailed.setVisibility(View.GONE); - if (infoItem.isFailed) { - this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount)); - tvFailed.setVisibility(View.VISIBLE); - setTxColour(failedColour); - pbConfirmations.setVisibility(View.GONE); - tvConfirmations.setVisibility(View.GONE); - } else if (infoItem.isPending) { - setTxColour(pendingColour); - pbConfirmations.setVisibility(View.GONE); - pbConfirmations.setIndeterminate(true); - pbConfirmations.setVisibility(View.VISIBLE); - tvConfirmations.setVisibility(View.GONE); - } else if (infoItem.direction == TransactionInfo.Direction.Direction_In) { - setTxColour(inboundColour); - if (!infoItem.isConfirmed()) { - pbConfirmations.setVisibility(View.VISIBLE); - final int confirmations = (int) infoItem.confirmations; - pbConfirmations.setProgressCompat(confirmations, true); - final String confCount = Integer.toString(confirmations); - tvConfirmations.setText(confCount); - if (confCount.length() == 1) // we only have space for character in the progress circle - tvConfirmations.setVisibility(View.VISIBLE); - else - tvConfirmations.setVisibility(View.GONE); - } else { - pbConfirmations.setVisibility(View.GONE); - tvConfirmations.setVisibility(View.GONE); - } - } else { - setTxColour(outboundColour); - pbConfirmations.setVisibility(View.GONE); - tvConfirmations.setVisibility(View.GONE); - } - - String tag = null; - String info = ""; - if ((infoItem.addressIndex != 0) && (infoItem.direction == TransactionInfo.Direction.Direction_In)) - tag = infoItem.getDisplayLabel(); - if ((userNotes.note.isEmpty())) { - if (!infoItem.paymentId.equals("0000000000000000")) { - info = infoItem.paymentId; - } - } else { - info = userNotes.note; - } - if (tag == null) { - tvPaymentId.setText(info); - } else { - Spanned label = Html.fromHtml(context.getString(R.string.tx_details_notes, - Integer.toHexString(ThemeHelper.getThemedColor(context, R.attr.positiveColor) & 0xFFFFFF), - Integer.toHexString(ThemeHelper.getThemedColor(context, android.R.attr.colorBackground) & 0xFFFFFF), - tag, info.isEmpty() ? "" : ("  " + info))); - tvPaymentId.setText(label); - } - - this.tvDateTime.setText(getDateTime(infoItem.timestamp)); - - itemView.setOnClickListener(this); - } - - @Override - public void onClick(View view) { - if (listener != null) { - int position = getAdapterPosition(); // gets item position - if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it - listener.onInteraction(view, infoItems.get(position)); - } - } - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java deleted file mode 100644 index ad885e7..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/WalletInfoAdapter.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.layout; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.PopupMenu; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.WalletManager; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.TimeZone; - -import timber.log.Timber; - -public class WalletInfoAdapter extends RecyclerView.Adapter { - - private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - - public interface OnInteractionListener { - void onInteraction(View view, WalletManager.WalletInfo item); - - boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo infoItem); - } - - private final List infoItems; - private final OnInteractionListener listener; - - Context context; - - public WalletInfoAdapter(Context context, OnInteractionListener listener) { - this.context = context; - this.infoItems = new ArrayList<>(); - this.listener = listener; - Calendar cal = Calendar.getInstance(); - TimeZone tz = cal.getTimeZone(); //get the local time zone. - DATETIME_FORMATTER.setTimeZone(tz); - } - - private static class WalletInfoDiff extends DiffCallback { - - public WalletInfoDiff(List oldList, List newList) { - super(oldList, newList); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return mOldList.get(oldItemPosition).getName().equals(mNewList.get(newItemPosition).getName()); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return mOldList.get(oldItemPosition).compareTo(mNewList.get(newItemPosition)) == 0; - } - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new ViewHolder( - LayoutInflater.from(parent.getContext()).inflate(R.layout.item_wallet, parent, false) - ); - } - - @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - holder.bind(position); - } - - @Override - public int getItemCount() { - return infoItems.size(); - } - - public WalletManager.WalletInfo getItem(int position) { - return infoItems.get(position); - } - - public void setInfos(List newItems) { - if (newItems == null) { - newItems = new ArrayList<>(); - Timber.d("setInfos null"); - } else { - Timber.d("setInfos %s", newItems.size()); - } - Collections.sort(newItems); - final DiffCallback diffCallback = new WalletInfoAdapter.WalletInfoDiff(infoItems, newItems); - final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); - infoItems.clear(); - infoItems.addAll(newItems); - diffResult.dispatchUpdatesTo(this); - } - - class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - final TextView tvName; - final ImageButton ibOptions; - WalletManager.WalletInfo infoItem; - boolean popupOpen = false; - - ViewHolder(View itemView) { - super(itemView); - tvName = itemView.findViewById(R.id.tvName); - ibOptions = itemView.findViewById(R.id.ibOptions); - ibOptions.setOnClickListener(view -> { - if (popupOpen) return; - //creating a popup menu - PopupMenu popup = new PopupMenu(context, ibOptions); - //inflating menu from xml resource - popup.inflate(R.menu.list_context_menu); - popupOpen = true; - //adding click listener - popup.setOnMenuItemClickListener(item -> { - if (listener != null) { - return listener.onContextInteraction(item, infoItem); - } - return false; - }); - //displaying the popup - popup.show(); - popup.setOnDismissListener(menu -> popupOpen = false); - - }); - itemView.setOnClickListener(this); - } - - private String getDateTime(long time) { - return DATETIME_FORMATTER.format(new Date(time * 1000)); - } - - void bind(int position) { - infoItem = infoItems.get(position); - tvName.setText(infoItem.getName()); - } - - @Override - public void onClick(View view) { - if (listener != null) { - int position = getAdapterPosition(); // gets item position - if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it - listener.onInteraction(view, infoItems.get(position)); - } - } - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/ledger/LedgerProgressDialog.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/LedgerProgressDialog.java deleted file mode 100644 index 0c81cb2..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/ledger/LedgerProgressDialog.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.ledger; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.dialog.ProgressDialog; - -import timber.log.Timber; - -public class LedgerProgressDialog extends ProgressDialog implements Ledger.Listener { - - static public final int TYPE_DEBUG = 0; - static public final int TYPE_RESTORE = 1; - static public final int TYPE_SUBADDRESS = 2; - static public final int TYPE_ACCOUNT = 3; - static public final int TYPE_SEND = 4; - - private final int type; - private Handler uiHandler = new Handler(Looper.getMainLooper()); - - public LedgerProgressDialog(Context context, int type) { - super(context); - this.type = type; - setCancelable(false); - if (type == TYPE_SEND) - setMessage(context.getString(R.string.info_prepare_tx)); - else - setMessage(context.getString(R.string.progress_ledger_progress)); - } - - @Override - public void onBackPressed() { - // prevent back button - } - - private int firstSubaddress = Integer.MAX_VALUE; - - private boolean validate = false; - private boolean validated = false; - - @Override - public void onInstructionSend(final Instruction ins, final byte[] apdu) { - Timber.d("LedgerProgressDialog SEND %s", ins); - uiHandler.post(new Runnable() { - @Override - public void run() { - if (type > TYPE_DEBUG) { - validate = false; - switch (ins) { - case INS_RESET: // ledger may ask for confirmation - maybe a bug? - case INS_GET_KEY: // ledger asks for confirmation to send keys - case INS_DISPLAY_ADDRESS: - setIndeterminate(true); - setMessage(getContext().getString(R.string.progress_ledger_confirm)); - break; - case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: // lookahead - //00 4a 00 00 09 00 01000000 30000000 - // 0 1 2 3 4 5 6 7 8 9 a b c d - int account = bytesToInteger(apdu, 6); - int subaddress = bytesToInteger(apdu, 10); - Timber.d("fetching subaddress (%d, %d)", account, subaddress); - switch (type) { - case TYPE_RESTORE: - setProgress(account * Ledger.LOOKAHEAD_SUBADDRESSES + subaddress + 1, - Ledger.LOOKAHEAD_ACCOUNTS * Ledger.LOOKAHEAD_SUBADDRESSES); - setIndeterminate(false); - break; - case TYPE_ACCOUNT: - final int requestedSubaddress = account * Ledger.LOOKAHEAD_SUBADDRESSES + subaddress; - if (firstSubaddress > requestedSubaddress) { - firstSubaddress = requestedSubaddress; - } - setProgress(requestedSubaddress - firstSubaddress + 1, - Ledger.LOOKAHEAD_ACCOUNTS * Ledger.LOOKAHEAD_SUBADDRESSES); - setIndeterminate(false); - break; - case TYPE_SUBADDRESS: - if (firstSubaddress > subaddress) { - firstSubaddress = subaddress; - } - setProgress(subaddress - firstSubaddress + 1, Ledger.LOOKAHEAD_SUBADDRESSES); - setIndeterminate(false); - break; - default: - setIndeterminate(true); - break; - } - setMessage(getContext().getString(R.string.progress_ledger_lookahead)); - break; - case INS_VERIFY_KEY: - setIndeterminate(true); - setMessage(getContext().getString(R.string.progress_ledger_verify)); - break; - case INS_OPEN_TX: - setIndeterminate(true); - setMessage(getContext().getString(R.string.progress_ledger_opentx)); - break; - case INS_MLSAG: - if (validated) { - setIndeterminate(true); - setMessage(getContext().getString(R.string.progress_ledger_mlsag)); - } - break; - case INS_PREFIX_HASH: - if ((apdu[2] != 1) || (apdu[3] != 0)) break; - setIndeterminate(true); - setMessage(getContext().getString(R.string.progress_ledger_confirm)); - break; - case INS_VALIDATE: - if ((apdu[2] != 1) || (apdu[3] != 1)) break; - validate = true; - uiHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (validate) { - setIndeterminate(true); - setMessage(getContext().getString(R.string.progress_ledger_confirm)); - validated = true; - } - } - }, 250); - break; - default: - // ignore others and maintain state - } - } else { - setMessage(ins.name()); - } - } - }); - } - - @Override - public void onInstructionReceive(final Instruction ins, final byte[] data) { - Timber.d("LedgerProgressDialog RECV %s", ins); - uiHandler.post(new Runnable() { - @Override - public void run() { - if (type > TYPE_DEBUG) { - switch (ins) { - case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: // lookahead - case INS_VERIFY_KEY: - case INS_GET_CHACHA8_PREKEY: - break; - default: - if (type != TYPE_SEND) - setMessage(getContext().getString(R.string.progress_ledger_progress)); - } - } else { - setMessage("Returned from " + ins.name()); - } - } - }); - } - - // TODO: we use ints in Java but the are signed; accounts & subaddresses are unsigned ... - private int bytesToInteger(byte[] bytes, int offset) { - int result = 0; - for (int i = 3; i >= 0; i--) { - result <<= 8; - result |= (bytes[offset + i] & 0xFF); - } - return result; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/livedata/SingleLiveEvent.java b/app/src/main/java/com/m2049r/xmrwallet/livedata/SingleLiveEvent.java new file mode 100644 index 0000000..8ecbcea --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/livedata/SingleLiveEvent.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.livedata; + +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + *

+ * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + *

+ * Note that only one observer is going to be notified of changes. + */ +public class SingleLiveEvent extends MutableLiveData { + + private static final String TAG = "SingleLiveEvent"; + + private final AtomicBoolean mPending = new AtomicBoolean(false); + + @MainThread + @Override + public void observe(LifecycleOwner owner, final Observer observer) { + + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); + } + + // Observe the internal MutableLiveData + super.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t); + } + } + }); + } + + @MainThread + public void setValue(@Nullable T t) { + mPending.set(true); + super.setValue(t); + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + public void call() { + setValue(null); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java index f5aa743..2257140 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -16,7 +16,6 @@ package com.m2049r.xmrwallet.model; -import com.m2049r.xmrwallet.XmrWalletApplication; import com.m2049r.xmrwallet.data.Node; import com.m2049r.xmrwallet.ledger.Ledger; import com.m2049r.xmrwallet.util.RestoreHeight; @@ -25,7 +24,6 @@ import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; import java.util.List; import lombok.Getter; @@ -248,7 +246,7 @@ public class WalletManager { //TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; private String daemonAddress = null; - private final NetworkType networkType = XmrWalletApplication.getNetworkType(); + private final NetworkType networkType = NetworkType.NetworkType_Mainnet; public NetworkType getNetworkType() { return networkType; diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java deleted file mode 100644 index c2793a1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingActivity.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2018-2020 EarlOfEgo, m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.onboarding; - -import android.content.Intent; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.viewpager.widget.ViewPager; - -import com.google.android.material.tabs.TabLayout; -import com.m2049r.xmrwallet.LoginActivity; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.util.KeyStoreHelper; - -public class OnBoardingActivity extends AppCompatActivity implements OnBoardingAdapter.Listener { - - private OnBoardingViewPager pager; - private OnBoardingAdapter pagerAdapter; - private Button nextButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_on_boarding); - - nextButton = findViewById(R.id.buttonNext); - - pager = findViewById(R.id.pager); - pagerAdapter = new OnBoardingAdapter(this, this); - pager.setAdapter(pagerAdapter); - int pixels = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()); - pager.setPageMargin(pixels); - pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { - @Override - public void onPageSelected(int position) { - setButtonState(position); - } - }); - - final TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout); - if (pagerAdapter.getCount() > 1) { - tabLayout.setupWithViewPager(pager, true); - LinearLayout tabStrip = ((LinearLayout) tabLayout.getChildAt(0)); - for (int i = 0; i < tabStrip.getChildCount(); i++) { - tabStrip.getChildAt(i).setClickable(false); - } - } else { - tabLayout.setVisibility(View.GONE); - } - - nextButton.setOnClickListener(v -> { - final int item = pager.getCurrentItem(); - if (item + 1 >= pagerAdapter.getCount()) { - finishOnboarding(); - } else { - pager.setCurrentItem(item + 1); - } - }); - - // let old users who have fingerprint wallets already agree for fingerprint sending - OnBoardingScreen.FPSEND.setMustAgree(KeyStoreHelper.hasStoredPasswords(this)); - - for (int i = 0; i < OnBoardingScreen.values().length; i++) { - agreed[i] = !OnBoardingScreen.values()[i].isMustAgree(); - } - - setButtonState(0); - } - - private void finishOnboarding() { - nextButton.setEnabled(false); - OnBoardingManager.setOnBoardingShown(getApplicationContext()); - startActivity(new Intent(this, LoginActivity.class)); - finish(); - } - - boolean[] agreed = new boolean[OnBoardingScreen.values().length]; - - @Override - public void setAgreeClicked(int position, boolean isChecked) { - agreed[position] = isChecked; - setButtonState(position); - } - - @Override - public boolean isAgreeClicked(int position) { - return agreed[position]; - } - - @Override - public void setButtonState(int position) { - nextButton.setEnabled(agreed[position]); - if (nextButton.isEnabled()) - pager.setAllowedSwipeDirection(OnBoardingViewPager.SwipeDirection.ALL); - else - pager.setAllowedSwipeDirection(OnBoardingViewPager.SwipeDirection.LEFT); - if (pager.getCurrentItem() + 1 == pagerAdapter.getCount()) { // last page - nextButton.setText(R.string.onboarding_button_ready); - } else { - nextButton.setText(R.string.onboarding_button_next); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java deleted file mode 100644 index adfc7d9..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingAdapter.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2018-2020 EarlOfEgo, m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.onboarding; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.viewpager.widget.PagerAdapter; - -import com.m2049r.xmrwallet.R; - -import timber.log.Timber; - -public class OnBoardingAdapter extends PagerAdapter { - - interface Listener { - void setAgreeClicked(int position, boolean isChecked); - - boolean isAgreeClicked(int position); - - void setButtonState(int position); - } - - private final Context context; - private Listener listener; - - OnBoardingAdapter(final Context context, final Listener listener) { - this.context = context; - this.listener = listener; - } - - @NonNull - @Override - public Object instantiateItem(@NonNull ViewGroup collection, int position) { - LayoutInflater inflater = LayoutInflater.from(context); - final View view = inflater.inflate(R.layout.view_onboarding, collection, false); - final OnBoardingScreen onBoardingScreen = OnBoardingScreen.values()[position]; - - final Drawable drawable = ContextCompat.getDrawable(context, onBoardingScreen.getDrawable()); - ((ImageView) view.findViewById(R.id.onboardingImage)).setImageDrawable(drawable); - ((TextView) view.findViewById(R.id.onboardingTitle)).setText(onBoardingScreen.getTitle()); - ((TextView) view.findViewById(R.id.onboardingInformation)).setText(onBoardingScreen.getInformation()); - if (onBoardingScreen.isMustAgree()) { - final CheckBox agree = ((CheckBox) view.findViewById(R.id.onboardingAgree)); - agree.setVisibility(View.VISIBLE); - agree.setChecked(listener.isAgreeClicked(position)); - agree.setOnClickListener(v -> { - listener.setAgreeClicked(position, ((CheckBox) v).isChecked()); - }); - } - collection.addView(view); - return view; - } - - @Override - public int getCount() { - return OnBoardingScreen.values().length; - } - - @Override - public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) { - Timber.d("destroy " + position); - collection.removeView((View) view); - } - - @Override - public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) { - return view == object; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java deleted file mode 100644 index eb28331..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2018-2020 EarlOfEgo, m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.onboarding; - -import android.content.Context; -import android.content.SharedPreferences; - -import com.m2049r.xmrwallet.util.KeyStoreHelper; - -import java.util.Date; - -import timber.log.Timber; - -public class OnBoardingManager { - - private static final String PREFS_ONBOARDING = "PREFS_ONBOARDING"; - private static final String ONBOARDING_SHOWN = "ONBOARDING_SHOWN"; - - public static boolean shouldShowOnBoarding(final Context context) { - return !getSharedPreferences(context).contains(ONBOARDING_SHOWN); - } - - public static void setOnBoardingShown(final Context context) { - Timber.d("Set onboarding shown."); - SharedPreferences sharedPreferences = getSharedPreferences(context); - sharedPreferences.edit().putLong(ONBOARDING_SHOWN, new Date().getTime()).apply(); - } - - public static void clearOnBoardingShown(final Context context) { - SharedPreferences sharedPreferences = getSharedPreferences(context); - sharedPreferences.edit().remove(ONBOARDING_SHOWN).apply(); - } - - private static SharedPreferences getSharedPreferences(final Context context) { - return context.getSharedPreferences(PREFS_ONBOARDING, Context.MODE_PRIVATE); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java deleted file mode 100644 index c6227d5..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingScreen.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2018-2020 EarlOfEgo, m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.onboarding; - -import com.m2049r.xmrwallet.R; - -enum OnBoardingScreen { - WELCOME(R.string.onboarding_welcome_title, R.string.onboarding_welcome_information, R.drawable.ic_onboarding_welcome, false), - SEED(R.string.onboarding_seed_title, R.string.onboarding_seed_information, R.drawable.ic_onboarding_seed, true), - FPSEND(R.string.onboarding_fpsend_title, R.string.onboarding_fpsend_information, R.drawable.ic_onboarding_fingerprint, false), - XMRTO(R.string.onboarding_xmrto_title, R.string.onboarding_xmrto_information, R.drawable.ic_onboarding_xmrto, false), - NODES(R.string.onboarding_nodes_title, R.string.onboarding_nodes_information, R.drawable.ic_onboarding_nodes, false); - - private final int title; - private final int information; - private final int drawable; - private boolean mustAgree; - - OnBoardingScreen(final int title, final int information, final int drawable, final boolean mustAgree) { - this.title = title; - this.information = information; - this.drawable = drawable; - this.mustAgree = mustAgree; - } - - public int getTitle() { - return title; - } - - public int getInformation() { - return information; - } - - public int getDrawable() { - return drawable; - } - - public boolean isMustAgree() { - return mustAgree; - } - - public boolean setMustAgree(boolean mustAgree) { - return this.mustAgree = mustAgree; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingViewPager.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingViewPager.java deleted file mode 100644 index 3e26352..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingViewPager.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2020 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// based on https://stackoverflow.com/a/34076649 - -package com.m2049r.xmrwallet.onboarding; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import androidx.viewpager.widget.ViewPager; - -public class OnBoardingViewPager extends ViewPager { - - public enum SwipeDirection { - ALL, LEFT, RIGHT, NONE; - } - - private float initialXValue; - private SwipeDirection direction; - - public OnBoardingViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - this.direction = SwipeDirection.ALL; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (this.IsSwipeAllowed(event)) { - return super.onTouchEvent(event); - } - - return false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - if (this.IsSwipeAllowed(event)) { - return super.onInterceptTouchEvent(event); - } - - return false; - } - - private boolean IsSwipeAllowed(MotionEvent event) { - if (this.direction == SwipeDirection.ALL) return true; - - if (direction == SwipeDirection.NONE)//disable any swipe - return false; - - if (event.getAction() == MotionEvent.ACTION_DOWN) { - initialXValue = event.getX(); - return true; - } - - if (event.getAction() == MotionEvent.ACTION_MOVE) { - float diffX = event.getX() - initialXValue; - if (diffX > 0 && direction == SwipeDirection.RIGHT) { - // swipe from left to right detected - return false; - } else if (diffX < 0 && direction == SwipeDirection.LEFT) { - // swipe from right to left detected - return false; - } - } - - return true; - } - - public void setAllowedSwipeDirection(SwipeDirection direction) { - this.direction = direction; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java index 79ac246..02b386f 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java @@ -22,140 +22,85 @@ import android.os.Looper; import android.os.Message; import android.os.Process; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.m2049r.xmrwallet.data.DefaultNodes; +import com.m2049r.xmrwallet.data.Node; +import com.m2049r.xmrwallet.data.TxData; +import com.m2049r.xmrwallet.fragment.home.HomeViewModel; +import com.m2049r.xmrwallet.model.PendingTransaction; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.model.WalletListener; +import com.m2049r.xmrwallet.model.WalletManager; + /** * Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called. * The started Thread has a stck size of STACK_SIZE (=5MB) */ -public class MoneroHandlerThread extends Thread { +public class MoneroHandlerThread extends Thread implements WalletListener { + private Listener listener = null; + private Wallet wallet = null; // from src/cryptonote_config.h static public final long THREAD_STACK_SIZE = 5 * 1024 * 1024; - private int mPriority; - private int mTid = -1; - private Looper mLooper; - public MoneroHandlerThread(String name) { + public MoneroHandlerThread(String name, Wallet wallet, Listener listener) { super(null, null, name, THREAD_STACK_SIZE); - mPriority = Process.THREAD_PRIORITY_DEFAULT; - } - - /** - * Constructs a MoneroHandlerThread. - * - * @param name - * @param priority The priority to run the thread at. The value supplied must be from - * {@link android.os.Process} and not from java.lang.Thread. - */ - MoneroHandlerThread(String name, int priority) { - super(null, null, name, THREAD_STACK_SIZE); - mPriority = priority; - } - - /** - * Call back method that can be explicitly overridden if needed to execute some - * setup before Looper loops. - */ - - private void onLooperPrepared() { + this.wallet = wallet; + this.listener = listener; + this.listener.onRefresh(); } @Override public void run() { - mTid = Process.myTid(); - Looper.prepare(); - synchronized (this) { - mLooper = Looper.myLooper(); - notifyAll(); - } - Process.setThreadPriority(mPriority); - onLooperPrepared(); - Looper.loop(); - mTid = -1; + WalletManager.getInstance().setDaemon(Node.fromString(DefaultNodes.XMRTW.getUri())); + System.out.println(WalletManager.getInstance().getBlockchainHeight()); + System.out.println(wallet.getSeed("")); + wallet.init(0); + wallet.setListener(this); + wallet.startRefresh(); } - /** - * This method returns the Looper associated with this thread. If this thread not been started - * or for any reason is isAlive() returns false, this method will return null. If this thread - * has been started, this method will block until the looper has been initialized. - * - * @return The looper. - */ - Looper getLooper() { - if (!isAlive()) { - return null; - } + @Override + public void moneySpent(String txId, long amount) {} + @Override + public void moneyReceived(String txId, long amount) {} + @Override + public void unconfirmedMoneyReceived(String txId, long amount) {} - // If the thread has been started, wait until the looper has been created. - synchronized (this) { - while (isAlive() && mLooper == null) { - try { - wait(); - } catch (InterruptedException e) { - } - } + @Override + public void newBlock(long height) { + if(height % 1000 == 0) { + refresh(); } - return mLooper; } - /** - * Quits the handler thread's looper. - *

- * Causes the handler thread's looper to terminate without processing any - * more messages in the message queue. - *

- * Any attempt to post messages to the queue after the looper is asked to quit will fail. - * For example, the {@link Handler#sendMessage(Message)} method will return false. - *

- * Using this method may be unsafe because some messages may not be delivered - * before the looper terminates. Consider using {@link #quitSafely} instead to ensure - * that all pending work is completed in an orderly manner. - *

- * - * @return True if the looper looper has been asked to quit or false if the - * thread had not yet started running. - * @see #quitSafely - */ - public boolean quit() { - Looper looper = getLooper(); - if (looper != null) { - looper.quit(); - return true; - } - return false; + @Override + public void updated() { + refresh(); } - /** - * Quits the handler thread's looper safely. - *

- * Causes the handler thread's looper to terminate as soon as all remaining messages - * in the message queue that are already due to be delivered have been handled. - * Pending delayed messages with due times in the future will not be delivered. - *

- * Any attempt to post messages to the queue after the looper is asked to quit will fail. - * For example, the {@link Handler#sendMessage(Message)} method will return false. - *

- * If the thread has not been started or has finished (that is if - * {@link #getLooper} returns null), then false is returned. - * Otherwise the looper is asked to quit and true is returned. - *

- * - * @return True if the looper looper has been asked to quit or false if the - * thread had not yet started running. - */ - public boolean quitSafely() { - Looper looper = getLooper(); - if (looper != null) { - looper.quitSafely(); - return true; - } - return false; + @Override + public void refreshed() { + wallet.setSynchronized(); + refresh(); } - /** - * Returns the identifier of this thread. See Process.myTid(). - */ - public int getThreadId() { - return mTid; + private void refresh() { + wallet.refreshHistory(); + wallet.store(); + listener.onRefresh(); + } + + public boolean sendTx(String address, String amountStr) { + long amount = Wallet.getAmountFromString(amountStr); + PendingTransaction pendingTx = wallet.createTransaction(new TxData(address, amount, 0, PendingTransaction.Priority.Priority_Default)); + return pendingTx.commit("", true); + } + + public interface Listener { + void onRefresh(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/ServiceBase.java b/app/src/main/java/com/m2049r/xmrwallet/service/ServiceBase.java new file mode 100644 index 0000000..7a663e3 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/ServiceBase.java @@ -0,0 +1,21 @@ +package com.m2049r.xmrwallet.service; + +import com.m2049r.xmrwallet.MainActivity; + +public class ServiceBase { + private MainActivity mainActivity; + private MoneroHandlerThread thread; + + public ServiceBase(MainActivity mainActivity, MoneroHandlerThread thread) { + this.mainActivity = mainActivity; + this.thread = thread; + } + + public MainActivity getMainActivity() { + return mainActivity; + } + + public MoneroHandlerThread getThread() { + return thread; + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java b/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java new file mode 100644 index 0000000..e6e34bc --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java @@ -0,0 +1,26 @@ +package com.m2049r.xmrwallet.service; + +import com.m2049r.xmrwallet.MainActivity; +import com.m2049r.xmrwallet.livedata.SingleLiveEvent; + +public class TxService extends ServiceBase { + public static TxService instance = null; + public static TxService getInstance() { + return instance; + } + + private final SingleLiveEvent _clearSendEvent = new SingleLiveEvent(); + public SingleLiveEvent clearSendEvent = _clearSendEvent; + + public TxService(MainActivity mainActivity, MoneroHandlerThread thread) { + super(mainActivity, thread); + instance = this; + } + + public void sendTx(String address, String amount) { + boolean success = this.getThread().sendTx(address, amount); + if(success) { + _clearSendEvent.call(); + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java deleted file mode 100644 index ece003e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Binder; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Process; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.app.NotificationCompat; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.WalletActivity; -import com.m2049r.xmrwallet.data.TxData; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletListener; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.LocaleHelper; -import com.m2049r.xmrwallet.util.NetCipherHelper; - -import timber.log.Timber; - -public class WalletService extends Service { - public static boolean Running = false; - - final static int NOTIFICATION_ID = 2049; - final static String CHANNEL_ID = "m_service"; - - public static final String REQUEST_WALLET = "wallet"; - public static final String REQUEST = "request"; - - public static final String REQUEST_CMD_LOAD = "load"; - public static final String REQUEST_CMD_LOAD_PW = "walletPassword"; - - public static final String REQUEST_CMD_STORE = "store"; - - public static final String REQUEST_CMD_TX = "createTX"; - public static final String REQUEST_CMD_TX_DATA = "data"; - public static final String REQUEST_CMD_TX_TAG = "tag"; - - public static final String REQUEST_CMD_SWEEP = "sweepTX"; - - public static final String REQUEST_CMD_SEND = "send"; - public static final String REQUEST_CMD_SEND_NOTES = "notes"; - - public static final int START_SERVICE = 1; - public static final int STOP_SERVICE = 2; - - private MyWalletListener listener = null; - - private class MyWalletListener implements WalletListener { - boolean updated = true; - - void start() { - Timber.d("MyWalletListener.start()"); - Wallet wallet = getWallet(); - if (wallet == null) throw new IllegalStateException("No wallet!"); - wallet.setListener(this); - wallet.startRefresh(); - } - - void stop() { - Timber.d("MyWalletListener.stop()"); - Wallet wallet = getWallet(); - if (wallet == null) throw new IllegalStateException("No wallet!"); - wallet.pauseRefresh(); - wallet.setListener(null); - } - - // WalletListener callbacks - public void moneySpent(String txId, long amount) { - Timber.d("moneySpent() %d @ %s", amount, txId); - } - - public void moneyReceived(String txId, long amount) { - Timber.d("moneyReceived() %d @ %s", amount, txId); - } - - public void unconfirmedMoneyReceived(String txId, long amount) { - Timber.d("unconfirmedMoneyReceived() %d @ %s", amount, txId); - } - - private long lastBlockTime = 0; - private int lastTxCount = 0; - - public void newBlock(long height) { - final Wallet wallet = getWallet(); - if (wallet == null) throw new IllegalStateException("No wallet!"); - // don't flood with an update for every block ... - if (lastBlockTime < System.currentTimeMillis() - 2000) { - lastBlockTime = System.currentTimeMillis(); - Timber.d("newBlock() @ %d with observer %s", height, observer); - if (observer != null) { - boolean fullRefresh = false; - updateDaemonState(wallet, wallet.isSynchronized() ? height : 0); - if (!wallet.isSynchronized()) { - updated = true; - // we want to see our transactions as they come in - wallet.refreshHistory(); - int txCount = wallet.getHistory().getCount(); - if (txCount > lastTxCount) { - // update the transaction list only if we have more than before - lastTxCount = txCount; - fullRefresh = true; - } - } - if (observer != null) - observer.onRefreshed(wallet, fullRefresh); - } - } - } - - public void updated() { - Timber.d("updated()"); - Wallet wallet = getWallet(); - if (wallet == null) throw new IllegalStateException("No wallet!"); - updated = true; - } - - public void refreshed() { // this means it's synced - Timber.d("refreshed()"); - final Wallet wallet = getWallet(); - if (wallet == null) throw new IllegalStateException("No wallet!"); - wallet.setSynchronized(); - if (updated) { - updateDaemonState(wallet, wallet.getBlockChainHeight()); - wallet.refreshHistory(); - if (observer != null) { - updated = !observer.onRefreshed(wallet, true); - } - } - } - } - - private long lastDaemonStatusUpdate = 0; - private long daemonHeight = 0; - private Wallet.ConnectionStatus connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected; - private static final long STATUS_UPDATE_INTERVAL = 120000; // 120s (blocktime) - - private void updateDaemonState(Wallet wallet, long height) { - long t = System.currentTimeMillis(); - if (height > 0) { // if we get a height, we are connected - daemonHeight = height; - connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Connected; - lastDaemonStatusUpdate = t; - } else { - if (t - lastDaemonStatusUpdate > STATUS_UPDATE_INTERVAL) { - lastDaemonStatusUpdate = t; - // these calls really connect to the daemon - wasting time - daemonHeight = wallet.getDaemonBlockChainHeight(); - if (daemonHeight > 0) { - // if we get a valid height, then obviously we are connected - connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Connected; - } else { - connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected; - } - } - } - } - - public long getDaemonHeight() { - return this.daemonHeight; - } - - public Wallet.ConnectionStatus getConnectionStatus() { - return this.connectionStatus; - } - - ///////////////////////////////////////////// - // communication back to client (activity) // - ///////////////////////////////////////////// - // NB: This allows for only one observer, i.e. only a single activity bound here - - private Observer observer = null; - - public void setObserver(Observer anObserver) { - observer = anObserver; - Timber.d("setObserver %s", observer); - } - - public interface Observer { - boolean onRefreshed(Wallet wallet, boolean full); - - void onProgress(String text); - - void onProgress(int n); - - void onWalletStored(boolean success); - - void onTransactionCreated(String tag, PendingTransaction pendingTransaction); - - void onTransactionSent(String txid); - - void onSendTransactionFailed(String error); - - void onWalletStarted(Wallet.Status walletStatus); - - void onWalletOpen(Wallet.Device device); - } - - String progressText = null; - int progressValue = -1; - - private void showProgress(String text) { - progressText = text; - if (observer != null) { - observer.onProgress(text); - } - } - - private void showProgress(int n) { - progressValue = n; - if (observer != null) { - observer.onProgress(n); - } - } - - public String getProgressText() { - return progressText; - } - - public int getProgressValue() { - return progressValue; - } - - // - public Wallet getWallet() { - return WalletManager.getInstance().getWallet(); - } - - ///////////////////////////////////////////// - ///////////////////////////////////////////// - - private WalletService.ServiceHandler mServiceHandler; - - private boolean errorState = false; - - // Handler that receives messages from the thread - private final class ServiceHandler extends Handler { - ServiceHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - Timber.d("Handling %s", msg.arg2); - if (errorState) { - Timber.i("In error state."); - // also, we have already stopped ourselves - return; - } - switch (msg.arg2) { - case START_SERVICE: { - Bundle extras = msg.getData(); - String cmd = extras.getString(REQUEST, null); - switch (cmd) { - case REQUEST_CMD_LOAD: - String walletId = extras.getString(REQUEST_WALLET, null); - String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null); - Timber.d("LOAD wallet %s", walletId); - if (walletId != null) { - showProgress(getString(R.string.status_wallet_loading)); - showProgress(10); - Wallet.Status walletStatus = start(walletId, walletPw); - if (observer != null) observer.onWalletStarted(walletStatus); - if ((walletStatus == null) || !walletStatus.isOk()) { - errorState = true; - stop(); - } - } - break; - case REQUEST_CMD_STORE: { - Wallet myWallet = getWallet(); - if (myWallet == null) break; - Timber.d("STORE wallet: %s", myWallet.getName()); - boolean rc = myWallet.store(); - Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc); - if (!rc) { - Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString()); - } - if (observer != null) observer.onWalletStored(rc); - break; - } - case REQUEST_CMD_TX: { - Wallet myWallet = getWallet(); - if (myWallet == null) break; - Timber.d("CREATE TX for wallet: %s", myWallet.getName()); - myWallet.disposePendingTransaction(); // remove any old pending tx - - TxData txData = extras.getParcelable(REQUEST_CMD_TX_DATA); - String txTag = extras.getString(REQUEST_CMD_TX_TAG); - PendingTransaction pendingTransaction = myWallet.createTransaction(txData); - PendingTransaction.Status status = pendingTransaction.getStatus(); - Timber.d("transaction status %s", status); - if (status != PendingTransaction.Status.Status_Ok) { - Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString()); - } - if (observer != null) { - observer.onTransactionCreated(txTag, pendingTransaction); - } else { - myWallet.disposePendingTransaction(); - } - break; - } - case REQUEST_CMD_SWEEP: { - Wallet myWallet = getWallet(); - if (myWallet == null) break; - Timber.d("SWEEP TX for wallet: %s", myWallet.getName()); - myWallet.disposePendingTransaction(); // remove any old pending tx - - String txTag = extras.getString(REQUEST_CMD_TX_TAG); - PendingTransaction pendingTransaction = myWallet.createSweepUnmixableTransaction(); - PendingTransaction.Status status = pendingTransaction.getStatus(); - Timber.d("transaction status %s", status); - if (status != PendingTransaction.Status.Status_Ok) { - Timber.w("Create Transaction failed: %s", pendingTransaction.getErrorString()); - } - if (observer != null) { - observer.onTransactionCreated(txTag, pendingTransaction); - } else { - myWallet.disposePendingTransaction(); - } - break; - } - case REQUEST_CMD_SEND: { - Wallet myWallet = getWallet(); - if (myWallet == null) break; - Timber.d("SEND TX for wallet: %s", myWallet.getName()); - PendingTransaction pendingTransaction = myWallet.getPendingTransaction(); - if (pendingTransaction == null) { - throw new IllegalArgumentException("PendingTransaction is null"); // die - } - if (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok) { - Timber.e("PendingTransaction is %s", pendingTransaction.getStatus()); - final String error = pendingTransaction.getErrorString(); - myWallet.disposePendingTransaction(); // it's broken anyway - if (observer != null) observer.onSendTransactionFailed(error); - return; - } - final String txid = pendingTransaction.getFirstTxId(); // tx ids vanish after commit()! - - boolean success = pendingTransaction.commit("", true); - if (success) { - myWallet.disposePendingTransaction(); - if (observer != null) observer.onTransactionSent(txid); - String notes = extras.getString(REQUEST_CMD_SEND_NOTES); - if ((notes != null) && (!notes.isEmpty())) { - myWallet.setUserNote(txid, notes); - } - boolean rc = myWallet.store(); - Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc); - if (!rc) { - Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString()); - } - if (observer != null) observer.onWalletStored(rc); - listener.updated = true; - } else { - final String error = pendingTransaction.getErrorString(); - myWallet.disposePendingTransaction(); - if (observer != null) observer.onSendTransactionFailed(error); - return; - } - break; - } - } - } - break; - case STOP_SERVICE: - stop(); - break; - default: - Timber.e("UNKNOWN %s", msg.arg2); - } - } - } - - @Override - public void onCreate() { - // We are using a HandlerThread and a Looper to avoid loading and closing - // concurrency - MoneroHandlerThread thread = new MoneroHandlerThread("WalletService", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - - // Get the HandlerThread's Looper and use it for our Handler - final Looper serviceLooper = thread.getLooper(); - mServiceHandler = new WalletService.ServiceHandler(serviceLooper); - - Timber.d("Service created"); - } - - @Override - public void onDestroy() { - Timber.d("onDestroy()"); - if (this.listener != null) { - Timber.w("onDestroy() with active listener"); - // no need to stop() here because the wallet closing should have been triggered - // through onUnbind() already - } - } - - @Override - protected void attachBaseContext(Context context) { - super.attachBaseContext(LocaleHelper.setPreferredLocale(context)); - } - - public class WalletServiceBinder extends Binder { - public WalletService getService() { - return WalletService.this; - } - } - - private final IBinder mBinder = new WalletServiceBinder(); - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Running = true; - // when the activity starts the service, it expects to start it for a new wallet - // the service is possibly still occupied with saving the last opened wallet - // so we queue the open request - // this should not matter since the old activity is not getting updates - // and the new one is not listening yet (although it will be bound) - Timber.d("onStartCommand()"); - // For each start request, send a message to start a job and deliver the - // start ID so we know which request we're stopping when we finish the job - Message msg = mServiceHandler.obtainMessage(); - msg.arg2 = START_SERVICE; - if (intent != null) { - msg.setData(intent.getExtras()); - mServiceHandler.sendMessage(msg); - return START_STICKY; - } else { - // process restart - don't do anything - let system kill it again - stop(); - return START_NOT_STICKY; - } - } - - @Override - public IBinder onBind(Intent intent) { - // Very first client binds - Timber.d("onBind()"); - return mBinder; - } - - @Override - public boolean onUnbind(Intent intent) { - Timber.d("onUnbind()"); - // All clients have unbound with unbindService() - Message msg = mServiceHandler.obtainMessage(); - msg.arg2 = STOP_SERVICE; - mServiceHandler.sendMessage(msg); - Timber.d("onUnbind() message sent"); - return true; // true is important so that onUnbind is also called next time - } - - @Nullable - private Wallet.Status start(String walletName, String walletPassword) { - Timber.d("start()"); - startNotfication(); - showProgress(getString(R.string.status_wallet_loading)); - showProgress(10); - if (listener == null) { - Timber.d("start() loadWallet"); - Wallet aWallet = loadWallet(walletName, walletPassword); - if (aWallet == null) return null; - Wallet.Status walletStatus = aWallet.getFullStatus(); - if (!walletStatus.isOk()) { - aWallet.close(); - return walletStatus; - } - listener = new MyWalletListener(); - listener.start(); - showProgress(100); - } - showProgress(getString(R.string.status_wallet_connecting)); - showProgress(101); - // if we try to refresh the history here we get occasional segfaults! - // doesnt matter since we update as soon as we get a new block anyway - Timber.d("start() done"); - return getWallet().getFullStatus(); - } - - public void stop() { - Timber.d("stop()"); - setObserver(null); // in case it was not reset already - if (listener != null) { - listener.stop(); - Wallet myWallet = getWallet(); - Timber.d("stop() closing"); - myWallet.close(); - Timber.d("stop() closed"); - listener = null; - } - stopForeground(true); - stopSelf(); - Running = false; - } - - private Wallet loadWallet(String walletName, String walletPassword) { - Wallet wallet = openWallet(walletName, walletPassword); - if (wallet != null) { - Timber.d("Using daemon %s", WalletManager.getInstance().getDaemonAddress()); - showProgress(55); - wallet.init(0); - wallet.setProxy(NetCipherHelper.getProxy()); - showProgress(90); - } - return wallet; - } - - private Wallet openWallet(String walletName, String walletPassword) { - String path = Helper.getWalletFile(getApplicationContext(), walletName).getAbsolutePath(); - showProgress(20); - Wallet wallet = null; - WalletManager walletMgr = WalletManager.getInstance(); - Timber.d("WalletManager network=%s", walletMgr.getNetworkType().name()); - showProgress(30); - if (walletMgr.walletExists(path)) { - Timber.d("open wallet %s", path); - Wallet.Device device = WalletManager.getInstance().queryWalletDevice(path + ".keys", walletPassword); - Timber.d("device is %s", device.toString()); - if (observer != null) observer.onWalletOpen(device); - wallet = walletMgr.openWallet(path, walletPassword); - showProgress(60); - Timber.d("wallet opened"); - Wallet.Status walletStatus = wallet.getStatus(); - if (!walletStatus.isOk()) { - Timber.d("wallet status is %s", walletStatus); - WalletManager.getInstance().close(wallet); // TODO close() failed? - wallet = null; - // TODO what do we do with the progress?? - // TODO tell the activity this failed - // this crashes in MyWalletListener(Wallet aWallet) as wallet == null - } - } - return wallet; - } - - private void startNotfication() { - Intent notificationIntent = new Intent(this, WalletActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0); - - String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? createNotificationChannel() : ""; - Notification notification = new NotificationCompat.Builder(this, channelId) - .setContentTitle(getString(R.string.service_description)) - .setOngoing(true) - .setSmallIcon(R.drawable.ic_monerujo) - .setPriority(NotificationCompat.PRIORITY_MIN) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setContentIntent(pendingIntent) - .build(); - startForeground(NOTIFICATION_ID, notification); - } - - @RequiresApi(Build.VERSION_CODES.O) - private String createNotificationChannel() { - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel channel = new NotificationChannel(CHANNEL_ID, getString(R.string.service_description), - NotificationManager.IMPORTANCE_LOW); - channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); - notificationManager.createNotificationChannel(channel); - return CHANNEL_ID; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java deleted file mode 100644 index 7b7972a..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeApi.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.api; - - -import androidx.annotation.NonNull; - - -public interface ExchangeApi { - - /** - * Queries the exchnage rate - * - * @param baseCurrency base currency - * @param quoteCurrency quote currency - * @param callback the callback with the exchange rate - */ - void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, - @NonNull final ExchangeCallback callback); - -} - diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeCallback.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeCallback.java deleted file mode 100644 index c5b939c..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeCallback.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.api; - -public interface ExchangeCallback { - - void onSuccess(ExchangeRate exchangeRate); - - void onError(Exception ex); - -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeException.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeException.java deleted file mode 100644 index 905819d..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.api; - -public class ExchangeException extends Exception { - private final int code; - private final String errorMsg; - - public String getErrorMsg() { - return errorMsg; - } - - public ExchangeException(final int code) { - super(); - this.code = code; - this.errorMsg = null; - } - - public ExchangeException(final String errorMsg) { - super(); - this.code = 0; - this.errorMsg = errorMsg; - } - - public ExchangeException(final int code, final String errorMsg) { - super(); - this.code = code; - this.errorMsg = errorMsg; - } - - public int getCode() { - return code; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeRate.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeRate.java deleted file mode 100644 index 3c0fadf..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/api/ExchangeRate.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.api; - -public interface ExchangeRate { - - String getServiceName(); - - String getBaseCurrency(); - - String getQuoteCurrency(); - - double getRate(); - -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java deleted file mode 100644 index e355ff5..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeApiImpl.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2019 m2049r@monerujo.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// https://developer.android.com/training/basics/network-ops/xml - -package com.m2049r.xmrwallet.service.exchange.ecb; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.NetCipherHelper; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import okhttp3.Call; -import okhttp3.HttpUrl; -import okhttp3.Response; -import timber.log.Timber; - -public class ExchangeApiImpl implements ExchangeApi { - @NonNull - private final HttpUrl baseUrl; - - //so we can inject the mockserver url - @VisibleForTesting - public ExchangeApiImpl(@NonNull final HttpUrl baseUrl) { - this.baseUrl = baseUrl; - } - - public ExchangeApiImpl() { - this(HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")); - // data is daily and is refreshed around 16:00 CET every working day - } - - public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) { - return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) && - (calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR)); - } - - @Override - public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, - @NonNull final ExchangeCallback callback) { - if (!baseCurrency.equals("EUR")) { - callback.onError(new IllegalArgumentException("Only EUR supported as base")); - return; - } - - if (baseCurrency.equals(quoteCurrency)) { - callback.onSuccess(new ExchangeRateImpl(quoteCurrency, 1.0, new Date())); - return; - } - - if (fetchDate != null) { // we have data - boolean useCache = false; - // figure out if we can use the cached values - // data is daily and is refreshed around 16:00 CET every working day - Calendar now = Calendar.getInstance(TimeZone.getTimeZone("CET")); - - int fetchWeekday = fetchDate.get(Calendar.DAY_OF_WEEK); - int fetchDay = fetchDate.get(Calendar.DAY_OF_YEAR); - int fetchHour = fetchDate.get(Calendar.HOUR_OF_DAY); - - int today = now.get(Calendar.DAY_OF_YEAR); - int nowHour = now.get(Calendar.HOUR_OF_DAY); - - if ( - // was it fetched today before 16:00? assume no new data iff now < 16:00 as well - ((today == fetchDay) && (fetchHour < 16) && (nowHour < 16)) - // was it fetched after, 17:00? we can assume there is no newer data - || ((today == fetchDay) && (fetchHour > 17)) - || ((today == fetchDay + 1) && (fetchHour > 17) && (nowHour < 16)) - // is the data itself from today? there can be no newer data - || (fxDate.get(Calendar.DAY_OF_YEAR) == today) - // was it fetched Sat/Sun? we can assume there is no newer data - || ((fetchWeekday == Calendar.SATURDAY) || (fetchWeekday == Calendar.SUNDAY)) - ) { // return cached rate - try { - callback.onSuccess(getRate(quoteCurrency)); - } catch (ExchangeException ex) { - callback.onError(ex); - } - return; - } - } - - final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(baseUrl); - httpRequest.enqueue(new okhttp3.Callback() { - @Override - public void onFailure(final Call call, final IOException ex) { - callback.onError(ex); - } - - @Override - public void onResponse(final Call call, final Response response) throws IOException { - if (response.isSuccessful()) { - try { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - Document doc = dBuilder.parse(response.body().byteStream()); - doc.getDocumentElement().normalize(); - parse(doc); - try { - callback.onSuccess(getRate(quoteCurrency)); - } catch (ExchangeException ex) { - callback.onError(ex); - } - } catch (ParserConfigurationException | SAXException ex) { - Timber.w(ex); - callback.onError(new ExchangeException(ex.getLocalizedMessage())); - } - } else { - callback.onError(new ExchangeException(response.code(), response.message())); - } - } - }); - } - - final private Map fxEntries = new HashMap<>(); - private Calendar fxDate = null; - private Calendar fetchDate = null; - - synchronized private ExchangeRate getRate(String currency) throws ExchangeException { - Timber.d("Getting %s", currency); - final Double rate = fxEntries.get(currency); - if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency); - return new ExchangeRateImpl(currency, rate, fxDate.getTime()); - } - - private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - - { - DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - private void parse(final Document xmlRootDoc) { - final Map entries = new HashMap<>(); - Calendar date = Calendar.getInstance(TimeZone.getTimeZone("CET")); - try { - NodeList cubes = xmlRootDoc.getElementsByTagName("Cube"); - for (int i = 0; i < cubes.getLength(); i++) { - Node node = cubes.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element cube = (Element) node; - if (cube.hasAttribute("time")) { // a time Cube - final Date time = DATE_FORMAT.parse(cube.getAttribute("time")); - date.setTime(time); - } else if (cube.hasAttribute("currency") - && cube.hasAttribute("rate")) { // a rate Cube - String currency = cube.getAttribute("currency"); - double rate = Double.valueOf(cube.getAttribute("rate")); - entries.put(currency, rate); - } // else an empty Cube - ignore - } - } - } catch (ParseException ex) { - Timber.d(ex); - } - synchronized (this) { - if (date != null) { - fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET")); - fxDate = date; - fxEntries.clear(); - fxEntries.putAll(entries); - } - // else don't change what we have - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeRateImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeRateImpl.java deleted file mode 100644 index 4691dfa..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/ecb/ExchangeRateImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2019 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.ecb; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; - -import java.util.Date; - -class ExchangeRateImpl implements ExchangeRate { - private final Date date; - private final String baseCurrency = "EUR"; - private final String quoteCurrency; - private final double rate; - - @Override - public String getServiceName() { - return "ecb.europa.eu"; - } - - @Override - public String getBaseCurrency() { - return baseCurrency; - } - - @Override - public String getQuoteCurrency() { - return quoteCurrency; - } - - @Override - public double getRate() { - return rate; - } - - ExchangeRateImpl(@NonNull final String quoteCurrency, double rate, @NonNull final Date date) { - super(); - this.quoteCurrency = quoteCurrency; - this.rate = rate; - this.date = date; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java deleted file mode 100644 index ab36259..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeApiImpl.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2017-2019 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.kraken; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.NetCipherHelper; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - -import okhttp3.Call; -import okhttp3.HttpUrl; -import okhttp3.Response; -import timber.log.Timber; - -public class ExchangeApiImpl implements ExchangeApi { - - private final HttpUrl baseUrl; - - //so we can inject the mockserver url - @VisibleForTesting - public ExchangeApiImpl(final HttpUrl baseUrl) { - this.baseUrl = baseUrl; - } - - public ExchangeApiImpl() { - this(HttpUrl.parse("https://api.kraken.com/0/public/Ticker")); - } - - @Override - public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, - @NonNull final ExchangeCallback callback) { - - if (baseCurrency.equals(quoteCurrency)) { - callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0)); - return; - } - - boolean invertQuery; - - - if (Helper.BASE_CRYPTO.equals(baseCurrency)) { - invertQuery = false; - } else if (Helper.BASE_CRYPTO.equals(quoteCurrency)) { - invertQuery = true; - } else { - callback.onError(new IllegalArgumentException("no crypto specified")); - return; - } - - Timber.d("queryExchangeRate: i %b, b %s, q %s", invertQuery, baseCurrency, quoteCurrency); - final boolean invert = invertQuery; - final String base = invert ? quoteCurrency : baseCurrency; - final String quote = invert ? baseCurrency : quoteCurrency; - - final HttpUrl url = baseUrl.newBuilder() - .addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote)) - .build(); - - final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url); - httpRequest.enqueue(new okhttp3.Callback() { - @Override - public void onFailure(final Call call, final IOException ex) { - callback.onError(ex); - } - - @Override - public void onResponse(final Call call, final Response response) throws IOException { - if (response.isSuccessful()) { - try { - final JSONObject json = new JSONObject(response.body().string()); - final JSONArray jsonError = json.getJSONArray("error"); - if (jsonError.length() > 0) { - final String errorMsg = jsonError.getString(0); - callback.onError(new ExchangeException(response.code(), errorMsg)); - } else { - final JSONObject jsonResult = json.getJSONObject("result"); - reportSuccess(jsonResult, invert, callback); - } - } catch (JSONException ex) { - callback.onError(new ExchangeException(ex.getLocalizedMessage())); - } - } else { - callback.onError(new ExchangeException(response.code(), response.message())); - } - } - }); - } - - void reportSuccess(JSONObject jsonObject, boolean swapAssets, ExchangeCallback callback) { - try { - final ExchangeRate exchangeRate = new ExchangeRateImpl(jsonObject, swapAssets); - callback.onSuccess(exchangeRate); - } catch (JSONException ex) { - callback.onError(new ExchangeException(ex.getLocalizedMessage())); - } catch (ExchangeException ex) { - callback.onError(ex); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeRateImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeRateImpl.java deleted file mode 100644 index e3afb5f..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/kraken/ExchangeRateImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.kraken; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.NoSuchElementException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class ExchangeRateImpl implements ExchangeRate { - - private final String baseCurrency; - private final String quoteCurrency; - private final double rate; - - @Override - public String getServiceName() { - return "kraken.com"; - } - - @Override - public String getBaseCurrency() { - return baseCurrency; - } - - @Override - public String getQuoteCurrency() { - return quoteCurrency; - } - - @Override - public double getRate() { - return rate; - } - - ExchangeRateImpl(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) { - super(); - this.baseCurrency = baseCurrency; - this.quoteCurrency = quoteCurrency; - this.rate = rate; - } - - ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException { - try { - final String key = jsonObject.keys().next(); // we expect only one - Pattern pattern = Pattern.compile("^X(.*?)Z(.*?)$"); - Matcher matcher = pattern.matcher(key); - if (matcher.find()) { - baseCurrency = swapAssets ? matcher.group(2) : matcher.group(1); - quoteCurrency = swapAssets ? matcher.group(1) : matcher.group(2); - } else { - throw new ExchangeException("no pair returned!"); - } - - JSONObject pair = jsonObject.getJSONObject(key); - JSONArray close = pair.getJSONArray("c"); - String closePrice = close.getString(0); - if (closePrice != null) { - try { - double rate = Double.parseDouble(closePrice); - this.rate = swapAssets ? (1 / rate) : rate; - } catch (NumberFormatException ex) { - throw new ExchangeException(ex.getLocalizedMessage()); - } - } else { - throw new ExchangeException("no close price returned!"); - } - } catch (NoSuchElementException ex) { - throw new ExchangeException(ex.getLocalizedMessage()); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeApiImpl.java deleted file mode 100644 index b8021b9..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeApiImpl.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2019 m2049r@monerujo.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// https://developer.android.com/training/basics/network-ops/xml - -package com.m2049r.xmrwallet.service.exchange.krakenEcb; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.Helper; - -import okhttp3.OkHttpClient; -import timber.log.Timber; - -/* - Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB - */ - -public class ExchangeApiImpl implements ExchangeApi { - static public final String BASE_FIAT = "EUR"; - - @Override - public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, - @NonNull final ExchangeCallback callback) { - Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency); - if (baseCurrency.equals(quoteCurrency)) { - Timber.d("BASE=QUOTE=1"); - callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0)); - return; - } - - if (!Helper.BASE_CRYPTO.equals(baseCurrency) - && !Helper.BASE_CRYPTO.equals(quoteCurrency)) { - callback.onError(new IllegalArgumentException("no " + Helper.BASE_CRYPTO + " specified")); - return; - } - - final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency; - - final ExchangeApi krakenApi = - new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(); - krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() { - @Override - public void onSuccess(final ExchangeRate krakenRate) { - Timber.d("kraken = %f", krakenRate.getRate()); - final ExchangeApi ecbApi = - new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(); - ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() { - @Override - public void onSuccess(final ExchangeRate ecbRate) { - Timber.d("ECB = %f", ecbRate.getRate()); - double rate = ecbRate.getRate() * krakenRate.getRate(); - Timber.d("Q=%s QC=%s", quote, quoteCurrency); - if (!quote.equals(quoteCurrency)) rate = 1.0d / rate; - Timber.d("rate = %f", rate); - final ExchangeRate exchangeRate = - new ExchangeRateImpl(baseCurrency, quoteCurrency, rate); - callback.onSuccess(exchangeRate); - } - - @Override - public void onError(Exception ex) { - Timber.d(ex); - callback.onError(ex); - } - }); - } - - @Override - public void onError(Exception ex) { - Timber.d(ex); - callback.onError(ex); - } - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeRateImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeRateImpl.java deleted file mode 100644 index 48b8ef0..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/exchange/krakenEcb/ExchangeRateImpl.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2019 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.exchange.krakenEcb; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; - -class ExchangeRateImpl implements ExchangeRate { - private final String baseCurrency; - private final String quoteCurrency; - private final double rate; - - @Override - public String getServiceName() { - return "kraken+ecb"; - } - - @Override - public String getBaseCurrency() { - return baseCurrency; - } - - @Override - public String getQuoteCurrency() { - return quoteCurrency; - } - - @Override - public double getRate() { - return rate; - } - - ExchangeRateImpl(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) { - super(); - this.baseCurrency = baseCurrency; - this.quoteCurrency = quoteCurrency; - this.rate = rate; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/NetworkCallback.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/NetworkCallback.java deleted file mode 100644 index f77128c..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/NetworkCallback.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift; - -import org.json.JSONObject; - -public interface NetworkCallback { - - void onSuccess(JSONObject jsonObject); - - void onError(Exception ex); - -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftApiCall.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftApiCall.java deleted file mode 100644 index c4daeb3..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftApiCall.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift; - -import androidx.annotation.NonNull; - -import org.json.JSONObject; - -public interface ShiftApiCall { - - void call(@NonNull final String path, @NonNull final NetworkCallback callback); - - void call(@NonNull final String path, final JSONObject request, @NonNull final NetworkCallback callback); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftCallback.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftCallback.java deleted file mode 100644 index 4dee50d..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftCallback.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift; - -public interface ShiftCallback { - - void onSuccess(T t); - - void onError(Exception ex); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftError.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftError.java deleted file mode 100644 index d789ec5..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftError.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift; - -import androidx.annotation.NonNull; - -import org.json.JSONException; -import org.json.JSONObject; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class ShiftError { - @Getter - private final Error errorType; - @Getter - private final String errorMsg; - - public enum Error { - SERVICE, - INFRASTRUCTURE - } - - public boolean isRetryable() { - return errorType == Error.INFRASTRUCTURE; - } - - public ShiftError(final JSONObject jsonObject) throws JSONException { - final JSONObject errorObject = jsonObject.getJSONObject("error"); - errorType = Error.SERVICE; - errorMsg = errorObject.getString("message"); - } - - @Override - @NonNull - public String toString() { - return getErrorMsg(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftException.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftException.java deleted file mode 100644 index 3a750b1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/ShiftException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class ShiftException extends Exception { - @Getter - private final int code; - @Getter - private final ShiftError error; - - public ShiftException(int code) { - this.code = code; - this.error = null; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/CreateOrder.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/CreateOrder.java deleted file mode 100644 index f738956..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/CreateOrder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.api; - -import java.util.Date; - -public interface CreateOrder { - String TAG = "side"; - - String getBtcCurrency(); - - double getBtcAmount(); - - String getBtcAddress(); - - String getQuoteId(); - - String getOrderId(); - - double getXmrAmount(); - - String getXmrAddress(); - - Date getCreatedAt(); // createdAt - - Date getExpiresAt(); // expiresAt - -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/QueryOrderParameters.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/QueryOrderParameters.java deleted file mode 100644 index ebd2d2f..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/QueryOrderParameters.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.api; - -public interface QueryOrderParameters { - - double getLowerLimit(); - - double getPrice(); - - double getUpperLimit(); - -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/QueryOrderStatus.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/QueryOrderStatus.java deleted file mode 100644 index acb201c..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/QueryOrderStatus.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.api; - -import java.util.Date; - -public interface QueryOrderStatus { - enum State { - WAITING, // Waiting for mempool - PENDING, // Detected (waiting for confirmations) - SETTLING, // Settlement in progress - SETTLED, // Settlement completed - // no refunding in monerujo so theese are ignored: -// REFUND, // Queued for refund -// REFUNDING, // Refund in progress -// REFUNDED // Refund completed - UNDEFINED - } - - boolean isCreated(); - - boolean isTerminal(); - - boolean isWaiting(); - - boolean isPending(); - - boolean isSent(); - - boolean isPaid(); - - boolean isError(); - - QueryOrderStatus.State getState(); - - String getOrderId(); - - Date getCreatedAt(); - - Date getExpiresAt(); - - double getBtcAmount(); - - String getBtcAddress(); - - double getXmrAmount(); - - String getXmrAddress(); - - double getPrice(); -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/RequestQuote.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/RequestQuote.java deleted file mode 100644 index cbbb36f..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/RequestQuote.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.api; - -import java.util.Date; - -public interface RequestQuote { - - double getBtcAmount(); // settleAmount - - String getId(); // id - - Date getCreatedAt(); // createdAt - - Date getExpiresAt(); // expiresAt - - double getXmrAmount(); // depositAmount - - double getPrice(); // rate -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/SideShiftApi.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/SideShiftApi.java deleted file mode 100644 index 6c9331c..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/api/SideShiftApi.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.api; - -import android.net.Uri; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.shift.ShiftCallback; - -public interface SideShiftApi { - int QUERY_INTERVAL = 5000; // ms - - /** - * Queries the order parameter. - * - * @param callback the callback with the OrderParameter object - */ - void queryOrderParameters(@NonNull final ShiftCallback callback); - - /** - * Creates an order - * - * @param xmrAmount the desired XMR amount - */ - void requestQuote(final double xmrAmount, @NonNull final ShiftCallback callback); - - /** - * Creates an order - * - * @param quoteId the desired XMR amount - * @param btcAddress the target bitcoin address - */ - void createOrder(final String quoteId, @NonNull final String btcAddress, @NonNull final ShiftCallback callback); - - /** - * Queries the order status for given current order - * - * @param orderId the order ID - * @param callback the callback with the OrderStatus object - */ - void queryOrderStatus(@NonNull final String orderId, @NonNull final ShiftCallback callback); - - /* - * Returns the URL for manually querying the order status - * - * @param orderId the order ID - */ - Uri getQueryOrderUri(String orderId); -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/CreateOrderImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/CreateOrderImpl.java deleted file mode 100644 index 258cf4d..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/CreateOrderImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.network; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.BuildConfig; -import com.m2049r.xmrwallet.service.shift.NetworkCallback; -import com.m2049r.xmrwallet.service.shift.ShiftApiCall; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder; -import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; -import com.m2049r.xmrwallet.util.DateHelper; -import com.m2049r.xmrwallet.util.ServiceHelper; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.ParseException; -import java.util.Date; - -import lombok.Getter; - -class CreateOrderImpl implements CreateOrder { - @Getter - private final String btcCurrency; - @Getter - private final double btcAmount; - @Getter - private final String btcAddress; - @Getter - private final String quoteId; - @Getter - private final String orderId; - @Getter - private final double xmrAmount; - @Getter - private final String xmrAddress; - @Getter - private final Date createdAt; - @Getter - private final Date expiresAt; - - CreateOrderImpl(final JSONObject jsonObject) throws JSONException { - // sanity checks - final String depositMethod = jsonObject.getString("depositMethodId"); - final String settleMethod = jsonObject.getString("settleMethodId"); - if (!"xmr".equals(depositMethod) || !ServiceHelper.ASSET.equals(settleMethod)) - throw new IllegalStateException(); - - btcCurrency = settleMethod.toUpperCase(); - btcAmount = jsonObject.getDouble("settleAmount"); - JSONObject settleAddress = jsonObject.getJSONObject("settleAddress"); - btcAddress = settleAddress.getString("address"); - - xmrAmount = jsonObject.getDouble("depositAmount"); - JSONObject depositAddress = jsonObject.getJSONObject("depositAddress"); - xmrAddress = depositAddress.getString("address"); - - quoteId = jsonObject.getString("quoteId"); - - orderId = jsonObject.getString("orderId"); - - try { - final String created = jsonObject.getString("createdAtISO"); - createdAt = DateHelper.parse(created); - final String expires = jsonObject.getString("expiresAtISO"); - expiresAt = DateHelper.parse(expires); - } catch (ParseException ex) { - throw new JSONException(ex.getLocalizedMessage()); - } - } - - public static void call(@NonNull final ShiftApiCall api, final String quoteId, @NonNull final String btcAddress, - @NonNull final ShiftCallback callback) { - try { - final JSONObject request = createRequest(quoteId, btcAddress); - api.call("orders", request, new NetworkCallback() { - @Override - public void onSuccess(JSONObject jsonObject) { - try { - callback.onSuccess(new CreateOrderImpl(jsonObject)); - } catch (JSONException ex) { - callback.onError(ex); - } - } - - @Override - public void onError(Exception ex) { - callback.onError(ex); - } - }); - } catch (JSONException ex) { - callback.onError(ex); - } - } - - static JSONObject createRequest(final String quoteId, final String address) throws JSONException { - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("type", "fixed"); - jsonObject.put("quoteId", quoteId); - jsonObject.put("settleAddress", address); - if (!BuildConfig.ID_A.isEmpty() && !"null".equals(BuildConfig.ID_A)) { - jsonObject.put("affiliateId", BuildConfig.ID_A); - } - return jsonObject; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/QueryOrderParametersImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/QueryOrderParametersImpl.java deleted file mode 100644 index afa5527..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/QueryOrderParametersImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.network; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.shift.NetworkCallback; -import com.m2049r.xmrwallet.service.shift.ShiftApiCall; -import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters; -import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.util.ServiceHelper; - -import org.json.JSONException; -import org.json.JSONObject; - -class QueryOrderParametersImpl implements QueryOrderParameters { - - private double lowerLimit; - private double price; - private double upperLimit; - - public double getLowerLimit() { - return lowerLimit; - } - - public double getPrice() { - return price; - } - - public double getUpperLimit() { - return upperLimit; - } - - QueryOrderParametersImpl(final JSONObject jsonObject) throws JSONException { - lowerLimit = jsonObject.getDouble("min"); - price = jsonObject.getDouble("rate"); - upperLimit = jsonObject.getDouble("max"); - } - - public static void call(@NonNull final ShiftApiCall api, - @NonNull final ShiftCallback callback) { - api.call("pairs/xmr/" + ServiceHelper.ASSET, new NetworkCallback() { - @Override - public void onSuccess(JSONObject jsonObject) { - try { - callback.onSuccess(new QueryOrderParametersImpl(jsonObject)); - } catch (JSONException ex) { - callback.onError(ex); - } - } - - @Override - public void onError(Exception ex) { - callback.onError(ex); - } - }); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/QueryOrderStatusImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/QueryOrderStatusImpl.java deleted file mode 100644 index 439cb93..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/QueryOrderStatusImpl.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.network; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.shift.NetworkCallback; -import com.m2049r.xmrwallet.service.shift.ShiftApiCall; -import com.m2049r.xmrwallet.util.DateHelper; -import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.ParseException; -import java.util.Date; - -import lombok.Getter; -import timber.log.Timber; - -class QueryOrderStatusImpl implements QueryOrderStatus { - - @Getter - private QueryOrderStatus.State state; - @Getter - private final String orderId; - @Getter - private final Date createdAt; - @Getter - private final Date expiresAt; - @Getter - private final double btcAmount; - @Getter - private final String btcAddress; - @Getter - private final double xmrAmount; - @Getter - private final String xmrAddress; - - public boolean isCreated() { - return true; - } - - public boolean isTerminal() { - return (state.equals(State.SETTLED) || isError()); - } - - public boolean isError() { - return state.equals(State.UNDEFINED); - } - - public boolean isWaiting() { - return state.equals(State.WAITING); - } - - public boolean isPending() { - return state.equals(State.PENDING); - } - - public boolean isSent() { - return state.equals(State.SETTLING); - } - - public boolean isPaid() { - return state.equals(State.SETTLED); - } - - public double getPrice() { - return btcAmount / xmrAmount; - } - - QueryOrderStatusImpl(final JSONObject jsonObject) throws JSONException { - try { - String created = jsonObject.getString("createdAtISO"); - createdAt = DateHelper.parse(created); - String expires = jsonObject.getString("expiresAtISO"); - expiresAt = DateHelper.parse(expires); - } catch (ParseException ex) { - throw new JSONException(ex.getLocalizedMessage()); - } - orderId = jsonObject.getString("orderId"); - - btcAmount = jsonObject.getDouble("settleAmount"); - JSONObject settleAddress = jsonObject.getJSONObject("settleAddress"); - btcAddress = settleAddress.getString("address"); - - xmrAmount = jsonObject.getDouble("depositAmount"); - JSONObject depositAddress = jsonObject.getJSONObject("depositAddress"); - xmrAddress = settleAddress.getString("address"); - - JSONArray deposits = jsonObject.getJSONArray("deposits"); - // we only create one deposit, so die if there are more than one: - if (deposits.length() > 1) - throw new IllegalStateException("more than one deposits"); - - state = State.UNDEFINED; - if (deposits.length() == 0) { - state = State.WAITING; - } else if (deposits.length() == 1) { - // sanity check - if (!orderId.equals(deposits.getJSONObject(0).getString("orderId"))) - throw new IllegalStateException("deposit has different order id!"); - String stateName = deposits.getJSONObject(0).getString("status"); - try { - state = State.valueOf(stateName.toUpperCase()); - } catch (IllegalArgumentException ex) { - state = State.UNDEFINED; - } - } - } - - public static void call(@NonNull final ShiftApiCall api, @NonNull final String orderId, - @NonNull final ShiftCallback callback) { - api.call("orders/" + orderId, new NetworkCallback() { - @Override - public void onSuccess(JSONObject jsonObject) { - try { - callback.onSuccess(new QueryOrderStatusImpl(jsonObject)); - } catch (JSONException ex) { - callback.onError(ex); - } - } - - @Override - public void onError(Exception ex) { - callback.onError(ex); - } - }); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/RequestQuoteImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/RequestQuoteImpl.java deleted file mode 100644 index 1cbdf24..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/RequestQuoteImpl.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.network; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.shift.NetworkCallback; -import com.m2049r.xmrwallet.service.shift.ShiftApiCall; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote; -import com.m2049r.xmrwallet.util.DateHelper; -import com.m2049r.xmrwallet.util.ServiceHelper; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.ParseException; -import java.util.Date; -import java.util.Locale; - -import lombok.Getter; - -class RequestQuoteImpl implements RequestQuote { - @Getter - private final double btcAmount; - @Getter - private final String id; - @Getter - private final Date createdAt; - @Getter - private final Date expiresAt; - @Getter - private final double xmrAmount; - @Getter - private final double price; - - // TODO do something with errors - they always seem to send us 500 - - RequestQuoteImpl(final JSONObject jsonObject) throws JSONException { - // sanity checks - final String depositMethod = jsonObject.getString("depositMethod"); - final String settleMethod = jsonObject.getString("settleMethod"); - if (!"xmr".equals(depositMethod) || !ServiceHelper.ASSET.equals(settleMethod)) - throw new IllegalStateException(); - - btcAmount = jsonObject.getDouble("settleAmount"); - id = jsonObject.getString("id"); - - try { - final String created = jsonObject.getString("createdAt"); - createdAt = DateHelper.parse(created); - final String expires = jsonObject.getString("expiresAt"); - expiresAt = DateHelper.parse(expires); - } catch (ParseException ex) { - throw new JSONException(ex.getLocalizedMessage()); - } - xmrAmount = jsonObject.getDouble("depositAmount"); - price = jsonObject.getDouble("rate"); - } - - public static void call(@NonNull final ShiftApiCall api, final double btcAmount, - @NonNull final ShiftCallback callback) { - try { - final JSONObject request = createRequest(btcAmount); - api.call("quotes", request, new NetworkCallback() { - @Override - public void onSuccess(JSONObject jsonObject) { - try { - callback.onSuccess(new RequestQuoteImpl(jsonObject)); - } catch (JSONException ex) { - callback.onError(ex); - } - } - - @Override - public void onError(Exception ex) { - callback.onError(ex); - } - }); - } catch (JSONException ex) { - callback.onError(ex); - } - } - - /** - * Create JSON request object - * - * @param btcAmount how much XMR to shift to BTC - */ - - static JSONObject createRequest(final double btcAmount) throws JSONException { - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("depositMethod", "xmr"); - jsonObject.put("settleMethod", ServiceHelper.ASSET); - // #sideshift is silly and likes numbers as strings - String amount = AmountFormatter.format(btcAmount); - jsonObject.put("settleAmount", amount); - return jsonObject; - } - - static final DecimalFormat AmountFormatter; - - static { - AmountFormatter = new DecimalFormat(); - AmountFormatter.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); - AmountFormatter.setMinimumIntegerDigits(1); - AmountFormatter.setMaximumFractionDigits(12); - AmountFormatter.setGroupingUsed(false); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/SideShiftApiImpl.java b/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/SideShiftApiImpl.java deleted file mode 100644 index c22e322..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/service/shift/sideshift/network/SideShiftApiImpl.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2017-2021 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.service.shift.sideshift.network; - -import android.net.Uri; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.service.shift.NetworkCallback; -import com.m2049r.xmrwallet.service.shift.ShiftApiCall; -import com.m2049r.xmrwallet.service.shift.ShiftCallback; -import com.m2049r.xmrwallet.service.shift.ShiftError; -import com.m2049r.xmrwallet.service.shift.ShiftException; -import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder; -import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters; -import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus; -import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote; -import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; -import com.m2049r.xmrwallet.util.NetCipherHelper; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - -import okhttp3.Call; -import okhttp3.HttpUrl; -import okhttp3.Response; -import timber.log.Timber; - -public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall { - - private final HttpUrl baseUrl; - - public SideShiftApiImpl(final HttpUrl baseUrl) { - this.baseUrl = baseUrl; - } - - @Override - public void queryOrderParameters(@NonNull final ShiftCallback callback) { - QueryOrderParametersImpl.call(this, callback); - } - - @Override - public void requestQuote(final double btcAmount, @NonNull final ShiftCallback callback) { - RequestQuoteImpl.call(this, btcAmount, callback); - } - - @Override - public void createOrder(final String quoteId, @NonNull final String btcAddress, - @NonNull final ShiftCallback callback) { - CreateOrderImpl.call(this, quoteId, btcAddress, callback); - } - - @Override - public void queryOrderStatus(@NonNull final String uuid, - @NonNull final ShiftCallback callback) { - QueryOrderStatusImpl.call(this, uuid, callback); - } - - @Override - public Uri getQueryOrderUri(String orderId) { - return Uri.parse("https://sideshift.ai/orders/" + orderId); - } - - @Override - public void call(@NonNull final String path, @NonNull final NetworkCallback callback) { - call(path, null, callback); - } - - @Override - public void call(@NonNull final String path, final JSONObject request, @NonNull final NetworkCallback callback) { - final HttpUrl url = baseUrl.newBuilder() - .addPathSegments(path) - .build(); - - NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url, request); - httpRequest.enqueue(new okhttp3.Callback() { - @Override - public void onFailure(final Call call, final IOException ex) { - callback.onError(ex); - } - - @Override - public void onResponse(@NonNull final Call call, @NonNull final Response response) throws IOException { - Timber.d("onResponse code=%d", response.code()); - if (response.isSuccessful()) { - try { - final JSONObject json = new JSONObject(response.body().string()); - callback.onSuccess(json); - } catch (JSONException ex) { - callback.onError(ex); - } - } else { - try { - final JSONObject json = new JSONObject(response.body().string()); - Timber.d(json.toString(2)); - final ShiftError error = new ShiftError(json); - Timber.w("%s says %d/%s", CreateOrder.TAG, response.code(), error.toString()); - callback.onError(new ShiftException(response.code(), error)); - } catch (JSONException ex) { - callback.onError(new ShiftException(response.code())); - } - } - } - }); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index 4cd90cf..3620338 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -381,196 +381,6 @@ public class Helper { static AlertDialog openDialog = null; // for preventing opening of multiple dialogs static AsyncTask passwordTask = null; - static public void promptPassword(final Context context, final String wallet, boolean fingerprintDisabled, final PasswordAction action) { - if (openDialog != null) return; // we are already asking for password - LayoutInflater li = LayoutInflater.from(context); - final View promptsView = li.inflate(R.layout.prompt_password, null); - - AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(context); - alertDialogBuilder.setView(promptsView); - - final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword); - etPassword.setHint(context.getString(R.string.prompt_password, wallet)); - - final TextView tvOpenPrompt = promptsView.findViewById(R.id.tvOpenPrompt); - final Drawable icFingerprint = context.getDrawable(R.drawable.ic_fingerprint); - final Drawable icError = context.getDrawable(R.drawable.ic_error_red_36dp); - final Drawable icInfo = context.getDrawable(R.drawable.ic_info_white_24dp); - - final boolean fingerprintAuthCheck = FingerprintHelper.isFingerPassValid(context, wallet); - - final boolean fingerprintAuthAllowed = !fingerprintDisabled && fingerprintAuthCheck; - final CancellationSignal cancelSignal = new CancellationSignal(); - - final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false); - - class PasswordTask extends AsyncTask { - private String pass; - private boolean fingerprintUsed; - - PasswordTask(String pass, boolean fingerprintUsed) { - this.pass = pass; - this.fingerprintUsed = fingerprintUsed; - } - - @Override - protected void onPreExecute() { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icInfo, null, null, null); - tvOpenPrompt.setText(context.getText(R.string.prompt_open_wallet)); - tvOpenPrompt.setVisibility(View.VISIBLE); - } - - @Override - protected Boolean doInBackground(Void... unused) { - return processPasswordEntry(context, wallet, pass, fingerprintUsed, action); - } - - @Override - protected void onPostExecute(Boolean result) { - if (result) { - Helper.hideKeyboardAlways((Activity) context); - cancelSignal.cancel(); - openDialog.dismiss(); - openDialog = null; - } else { - if (fingerprintUsed) { - incorrectSavedPass.set(true); - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icError, null, null, null); - tvOpenPrompt.setText(context.getText(R.string.bad_saved_password)); - } else { - if (!fingerprintAuthAllowed) { - tvOpenPrompt.setVisibility(View.GONE); - } else if (incorrectSavedPass.get()) { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icError, null, null, null); - tvOpenPrompt.setText(context.getText(R.string.bad_password)); - } else { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icFingerprint, null, null, null); - tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth)); - } - etPassword.setError(context.getString(R.string.bad_password)); - } - } - passwordTask = null; - } - } - - etPassword.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - if (etPassword.getError() != null) { - etPassword.setError(null); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - } - }); - - // set dialog message - alertDialogBuilder - .setCancelable(false) - .setPositiveButton(context.getString(R.string.label_ok), null) - .setNegativeButton(context.getString(R.string.label_cancel), - (dialog, id) -> { - action.fail(wallet); - Helper.hideKeyboardAlways((Activity) context); - cancelSignal.cancel(); - if (passwordTask != null) { - passwordTask.cancel(true); - passwordTask = null; - } - dialog.cancel(); - openDialog = null; - }); - openDialog = alertDialogBuilder.create(); - - final FingerprintManager.AuthenticationCallback fingerprintAuthCallback; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - fingerprintAuthCallback = null; - } else { - fingerprintAuthCallback = new FingerprintManager.AuthenticationCallback() { - @Override - public void onAuthenticationError(int errMsgId, CharSequence errString) { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icError, null, null, null); - tvOpenPrompt.setText(errString); - } - - @Override - public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icError, null, null, null); - tvOpenPrompt.setText(helpString); - } - - @Override - public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { - try { - String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet); - if (passwordTask == null) { - passwordTask = new PasswordTask(userPass, true); - passwordTask.execute(); - } - } catch (KeyStoreHelper.BrokenPasswordStoreException ex) { - etPassword.setError(context.getString(R.string.bad_password)); - // TODO: better error message here - what would it be? - } - } - - @Override - public void onAuthenticationFailed() { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icError, null, null, null); - tvOpenPrompt.setText(context.getString(R.string.bad_fingerprint)); - } - }; - } - - openDialog.setOnShowListener(dialog -> { - if (fingerprintAuthAllowed && fingerprintAuthCallback != null) { - tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icFingerprint, null, null, null); - tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth)); - tvOpenPrompt.setVisibility(View.VISIBLE); - FingerprintHelper.authenticate(context, cancelSignal, fingerprintAuthCallback); - } else { - etPassword.requestFocus(); - } - Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(view -> { - String pass = etPassword.getEditText().getText().toString(); - if (passwordTask == null) { - passwordTask = new PasswordTask(pass, false); - passwordTask.execute(); - } - }); - }); - - // accept keyboard "ok" - etPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - String pass = etPassword.getEditText().getText().toString(); - if (passwordTask == null) { - passwordTask = new PasswordTask(pass, false); - passwordTask.execute(); - } - return true; - } - return false; - }); - - if (Helper.preventScreenshot()) { - openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - } - - Helper.showKeyboard(openDialog); - openDialog.show(); - } - public interface PasswordAction { void act(String walletName, String password, boolean fingerprintUsed); diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java b/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java deleted file mode 100644 index 727c395..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.dialog.HelpFragment; -import com.m2049r.xmrwallet.ledger.Ledger; - -import java.util.ArrayList; -import java.util.List; - -public class Notice { - private static final String PREFS_NAME = "notice"; - private static List notices = null; - - private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send"; - private static final String NOTICE_SHOW_LEDGER = "notice_ledger_enabled_login"; - - private static void init() { - synchronized (Notice.class) { - if (notices != null) return; - notices = new ArrayList<>(); - if (Helper.ALLOW_SHIFT) - notices.add( - new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND, - R.string.info_xmrto_enabled, - R.string.help_xmrto, - 1) - ); - if (Ledger.ENABLED) - notices.add( - new Notice(NOTICE_SHOW_LEDGER, - R.string.info_ledger_enabled, - R.string.help_create_ledger, - 1) - ); - } - } - - public static void showAll(ViewGroup parent, String selector) { - if (notices == null) init(); - for (Notice notice : notices) { - if (notice.id.matches(selector)) - notice.show(parent); - } - } - - private final String id; - private final int textResId; - private final int helpResId; - private final int defaultCount; - private transient int count = -1; - - private Notice(final String id, final int textResId, final int helpResId, final int defaultCount) { - this.id = id; - this.textResId = textResId; - this.helpResId = helpResId; - this.defaultCount = defaultCount; - } - - // show this notice as a child of the given parent view - // NB: it assumes the parent is in a Fragment - private void show(final ViewGroup parent) { - final Context context = parent.getContext(); - if (getCount(context) <= 0) return; // don't add it - - final LinearLayout ll = - (LinearLayout) LayoutInflater.from(context) - .inflate(R.layout.template_notice, parent, false); - - ((TextView) ll.findViewById(R.id.tvNotice)).setText(textResId); - - final FragmentManager fragmentManager = - ((FragmentActivity) context).getSupportFragmentManager(); - ll.setOnClickListener(v -> HelpFragment.display(fragmentManager, helpResId)); - - ImageButton ib = ll.findViewById(R.id.ibClose); - ib.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ll.setVisibility(View.GONE); - decCount(context); - } - }); - parent.addView(ll); - } - - private int getCount(final Context context) { - count = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - .getInt(id, defaultCount); - return count; - } - - private void decCount(final Context context) { - final SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - if (count < 0) // not initialized yet - count = prefs.getInt(id, defaultCount); - if (count > 0) - prefs.edit().putInt(id, count - 1).apply(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java index 2762291..e629329 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/ServiceHelper.java @@ -2,7 +2,6 @@ package com.m2049r.xmrwallet.util; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; import okhttp3.HttpUrl; @@ -17,8 +16,4 @@ public class ServiceHelper { return HttpUrl.parse("https://sideshift.ai/api/v1/"); } } - - static public ExchangeApi getExchangeApi() { - return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl(); - } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/CTextInputLayout.java b/app/src/main/java/com/m2049r/xmrwallet/widget/CTextInputLayout.java deleted file mode 100644 index 59b884d..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/CTextInputLayout.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// based on from https://stackoverflow.com/a/45325876 (which did not work for me) - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import com.google.android.material.textfield.TextInputLayout; -import android.util.AttributeSet; -import android.widget.EditText; - -public class CTextInputLayout extends TextInputLayout { - public CTextInputLayout(Context context) { - super(context); - } - - public CTextInputLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public int getBaseline() { - EditText editText = getEditText(); - return editText.getBaseline() - (getMeasuredHeight() - editText.getMeasuredHeight()); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java b/app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java deleted file mode 100644 index 0baf2d3..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/DotBar.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2017 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// based on https://github.com/marcokstephen/StepProgressBar - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; - -import com.m2049r.xmrwallet.R; - -import timber.log.Timber; - -public class DotBar extends View { - - final private int inactiveColor; - final private int activeColor; - - final private float dotSize; - private float dotSpacing; - - final private int numDots; - private int activeDot; - - final private Paint paint; - - public DotBar(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DotBar, 0, 0); - try { - inactiveColor = ta.getInt(R.styleable.DotBar_inactiveColor, 0); - activeColor = ta.getInt(R.styleable.DotBar_activeColor, 0); - dotSize = ta.getDimensionPixelSize(R.styleable.DotBar_dotSize, 8); - numDots = ta.getInt(R.styleable.DotBar_numberDots, 5); - activeDot = ta.getInt(R.styleable.DotBar_activeDot, 0); - } finally { - ta.recycle(); - } - - paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setStyle(Paint.Style.FILL); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int desiredWidth = (int) ((numDots * dotSize) + getPaddingLeft() + getPaddingRight()); - int desiredHeight = (int) (dotSize + getPaddingBottom() + getPaddingTop()); - - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - int width; - int height; - - //Measure Width - if (widthMode == MeasureSpec.EXACTLY) { - //Must be this size - width = widthSize; - } else if (widthMode == MeasureSpec.AT_MOST) { - //Can't be bigger than... - width = Math.min(desiredWidth, widthSize); - } else { - //Be whatever you want - width = desiredWidth; - } - - //Measure Height - if (heightMode == MeasureSpec.EXACTLY) { - //Must be this size - height = heightSize; - } else if (heightMode == MeasureSpec.AT_MOST) { - //Can't be bigger than... - height = Math.min(desiredHeight, heightSize); - } else { - //Be whatever you want - height = desiredHeight; - } - - dotSpacing = (int) (((1.0 * width - (getPaddingLeft() + getPaddingRight())) / numDots - dotSize) / (numDots - 1)); - - Timber.d("dotSpacing=%f", dotSpacing); - //MUST CALL THIS - setMeasuredDimension(width, height); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Centering the dots in the middle of the canvas - float singleDotSize = dotSpacing + dotSize; - float combinedDotSize = singleDotSize * numDots - dotSpacing; - int startingX = (int) ((canvas.getWidth() - combinedDotSize) / 2); - int startingY = (int) ((canvas.getHeight() - dotSize) / 2); - - for (int i = 0; i < numDots; i++) { - int x = (int) (startingX + i * singleDotSize); - if (i == activeDot) { - paint.setColor(activeColor); - } else { - paint.setColor(inactiveColor); - } - canvas.drawCircle(x + dotSize / 2, startingY + dotSize / 2, dotSize / 2, paint); - } - } - - public void next() { - if (activeDot < numDots - 2) { - activeDot++; - invalidate(); - } // else no next - stay stuck at end - } - - public void previous() { - if (activeDot >= 0) { - activeDot--; - invalidate(); - } // else no previous - stay stuck at beginning - } - - public void setActiveDot(int i) { - if ((i >= 0) && (i < numDots)) { - activeDot = i; - invalidate(); - } - } - - public int getActiveDot() { - return activeDot; - } - - public int getNumDots() { - return numDots; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/DropDownEditText.java b/app/src/main/java/com/m2049r/xmrwallet/widget/DropDownEditText.java deleted file mode 100644 index 9a767f5..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/DropDownEditText.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// https://stackoverflow.com/questions/2126717/android-autocompletetextview-show-suggestions-when-no-text-entered - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.graphics.Rect; -import androidx.appcompat.widget.AppCompatAutoCompleteTextView; -import android.util.AttributeSet; - -public class DropDownEditText extends AppCompatAutoCompleteTextView { - - public DropDownEditText(Context context) { - super(context); - } - - public DropDownEditText(Context arg0, AttributeSet arg1) { - super(arg0, arg1); - } - - public DropDownEditText(Context arg0, AttributeSet arg1, int arg2) { - super(arg0, arg1, arg2); - } - - @Override - public boolean enoughToFilter() { - return true; - } - - @Override - protected void onFocusChanged(boolean focused, int direction, - Rect previouslyFocusedRect) { - super.onFocusChanged(focused, direction, previouslyFocusedRect); - if (focused && getAdapter() != null) { - performFiltering("", 0); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeEditText.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeEditText.java deleted file mode 100644 index 00b1b19..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeEditText.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (c) 2017-2019 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.Spinner; -import android.widget.TextView; - -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ServiceHelper; -import com.m2049r.xmrwallet.util.ThemeHelper; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -import timber.log.Timber; - -public class ExchangeEditText extends LinearLayout { - - private double getEnteredAmount() { - String enteredAmount = etAmountA.getEditText().getText().toString(); - try { - return Double.parseDouble(enteredAmount); - } catch (NumberFormatException ex) { - Timber.i(ex.getLocalizedMessage()); - } - return 0; - } - - public boolean validate(double max, double min) { - Timber.d("inProgress=%b", isExchangeInProgress()); - if (isExchangeInProgress()) { - shakeExchangeField(); - return false; - } - boolean ok = true; - String nativeAmount = getNativeAmount(); - if (nativeAmount == null) { - ok = false; - } else { - try { - double amount = Double.parseDouble(nativeAmount); - if ((amount < min) || (amount > max)) { - ok = false; - } - } catch (NumberFormatException ex) { - // this cannot be - Timber.e(ex.getLocalizedMessage()); - ok = false; - } - } - if (!ok) { - shakeAmountField(); - } - return ok; - } - - void shakeAmountField() { - etAmountA.startAnimation(Helper.getShakeAnimation(getContext())); - } - - void shakeExchangeField() { - tvAmountB.startAnimation(Helper.getShakeAnimation(getContext())); - } - - public void setAmount(String nativeAmount) { - if (nativeAmount != null) { - etAmountA.getEditText().setText(nativeAmount); - tvAmountB.setText(null); - if (sCurrencyA.getSelectedItemPosition() != 0) - sCurrencyA.setSelection(0, true); // set native currency & trigger exchange - else - doExchange(); - } else { - tvAmountB.setText(null); - } - } - - public void setEditable(boolean editable) { - etAmountA.setEnabled(editable); - } - - public String getNativeAmount() { - if (isExchangeInProgress()) return null; - if (getCurrencyA() == 0) - return getCleanAmountString(etAmountA.getEditText().getText().toString()); - else - return getCleanAmountString(tvAmountB.getText().toString()); - } - - TextInputLayout etAmountA; - TextView tvAmountB; - Spinner sCurrencyA; - Spinner sCurrencyB; - ImageView evExchange; - ProgressBar pbExchange; - - public int getCurrencyA() { - return sCurrencyA.getSelectedItemPosition(); - } - - public int getCurrencyB() { - return sCurrencyB.getSelectedItemPosition(); - } - - public ExchangeEditText(Context context) { - super(context); - initializeViews(context); - } - - public ExchangeEditText(Context context, AttributeSet attrs) { - super(context, attrs); - initializeViews(context); - } - - public ExchangeEditText(Context context, - AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - initializeViews(context); - } - - /** - * Inflates the views in the layout. - * - * @param context the current context for the view. - */ - void initializeViews(Context context) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.view_exchange_edit, this); - } - - void setCurrencyAdapter(Spinner spinner) { - List currencies = new ArrayList<>(); - currencies.add(Helper.BASE_CRYPTO); - setCurrencyAdapter(spinner, currencies); - } - - protected void setCurrencyAdapter(Spinner spinner, List currencies) { - if (Helper.SHOW_EXCHANGERATES) - currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency))); - ArrayAdapter spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, currencies); - spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(spinnerAdapter); - } - - void setInitialSpinnerSelections(Spinner baseSpinner, Spinner quoteSpinner) { - baseSpinner.setSelection(0, true); - quoteSpinner.setSelection(0, true); - } - - private boolean isInitialized = false; - - void postInitialize() { - setInitialSpinnerSelections(sCurrencyA, sCurrencyB); - isInitialized = true; - startExchange(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - etAmountA = findViewById(R.id.etAmountA); - etAmountA.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - doExchange(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - }); - tvAmountB = findViewById(R.id.tvAmountB); - sCurrencyA = findViewById(R.id.sCurrencyA); - sCurrencyB = findViewById(R.id.sCurrencyB); - evExchange = findViewById(R.id.evExchange); - pbExchange = findViewById(R.id.pbExchange); - - setCurrencyAdapter(sCurrencyA); - setCurrencyAdapter(sCurrencyB); - - post(this::postInitialize); - - // make progress circle gray - pbExchange.getIndeterminateDrawable(). - setColorFilter(ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant), - android.graphics.PorterDuff.Mode.MULTIPLY); - - sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { - if (!isInitialized) return; - if (position != 0) { // if not native, select native on other - sCurrencyB.setSelection(0, true); - } - doExchange(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing - } - }); - - sCurrencyB.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(final AdapterView parentView, View selectedItemView, int position, long id) { - if (!isInitialized) return; - if (position != 0) { // if not native, select native on other - sCurrencyA.setSelection(0, true); - } - doExchange(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing - } - }); - } - - private boolean exchangeRateCacheIsUsable() { - return (exchangeRateCache != null) && - ((exchangeRateCache.getBaseCurrency().equals(sCurrencyA.getSelectedItem()) && - exchangeRateCache.getQuoteCurrency().equals(sCurrencyB.getSelectedItem())) || - (exchangeRateCache.getBaseCurrency().equals(sCurrencyB.getSelectedItem()) && - exchangeRateCache.getQuoteCurrency().equals(sCurrencyA.getSelectedItem()))); - } - - private double exchangeRateFromCache() { - if (!exchangeRateCacheIsUsable()) return 0; - if (exchangeRateCache.getBaseCurrency().equals(sCurrencyA.getSelectedItem())) { - return exchangeRateCache.getRate(); - } else { - return 1.0d / exchangeRateCache.getRate(); - } - } - - public void doExchange() { - if (!isInitialized) return; - tvAmountB.setText(null); - if (getCurrencyA() == getCurrencyB()) { - exchange(1); - return; - } - // use cached exchange rate if we have it - if (!isExchangeInProgress()) { - double rate = exchangeRateFromCache(); - if (rate > 0) { - if (prepareExchange()) { - exchange(rate); - } - } else { - startExchange(); - } - } - } - - private final ExchangeApi exchangeApi = ServiceHelper.getExchangeApi(); - - // starts exchange through exchange api - void startExchange() { - String currencyA = (String) sCurrencyA.getSelectedItem(); - String currencyB = (String) sCurrencyB.getSelectedItem(); - if ((currencyA == null) || (currencyB == null)) return; // nothing to do - execExchange(currencyA, currencyB); - } - - void execExchange(String currencyA, String currencyB) { - showProgress(); - queryExchangeRate(currencyA, currencyB, - new ExchangeCallback() { - @Override - public void onSuccess(final ExchangeRate exchangeRate) { - if (isAttachedToWindow()) - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - exchange(exchangeRate); - } - }); - } - - @Override - public void onError(final Exception e) { - Timber.e(e.getLocalizedMessage()); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - exchangeFailed(); - } - }); - } - }); - } - - void queryExchangeRate(final String base, final String quote, ExchangeCallback callback) { - exchangeApi.queryExchangeRate(base, quote, callback); - } - - private void exchange(double rate) { - double amount = getEnteredAmount(); - if (rate > 0) { - tvAmountB.setText(Helper.getFormattedAmount(rate * amount, getCurrencyB() == 0)); - } else { - tvAmountB.setText("--"); - Timber.d("No rate!"); - } - } - - private static final String CLEAN_FORMAT = "%." + Helper.XMR_DECIMALS + "f"; - - private String getCleanAmountString(String enteredAmount) { - try { - double amount = Double.parseDouble(enteredAmount); - if (amount >= 0) { - return String.format(Locale.US, CLEAN_FORMAT, amount); - } else { - return null; - } - } catch (NumberFormatException ex) { - return null; - } - } - - boolean prepareExchange() { - Timber.d("prepareExchange()"); - String enteredAmount = etAmountA.getEditText().getText().toString(); - if (!enteredAmount.isEmpty()) { - String cleanAmount = getCleanAmountString(enteredAmount); - Timber.d("cleanAmount = %s", cleanAmount); - if (cleanAmount == null) { - shakeAmountField(); - return false; - } - } else { - return false; - } - return true; - } - - public void exchangeFailed() { - hideProgress(); - exchange(0); - } - - // cache for exchange rate - ExchangeRate exchangeRateCache = null; - - public void exchange(ExchangeRate exchangeRate) { - hideProgress(); - // make sure this is what we want - if (!exchangeRate.getBaseCurrency().equals(sCurrencyA.getSelectedItem()) || - !exchangeRate.getQuoteCurrency().equals(sCurrencyB.getSelectedItem())) { - // something's wrong - Timber.i("Currencies don't match! A: %s==%s B: %s==%s", - exchangeRate.getBaseCurrency(), sCurrencyA.getSelectedItem(), - exchangeRate.getQuoteCurrency(), sCurrencyB.getSelectedItem()); - return; - } - - exchangeRateCache = exchangeRate; - if (prepareExchange()) { - exchange(exchangeRate.getRate()); - } - } - - void showProgress() { - pbExchange.setVisibility(View.VISIBLE); - } - - private boolean isExchangeInProgress() { - return pbExchange.getVisibility() == View.VISIBLE; - } - - private void hideProgress() { - pbExchange.setVisibility(View.INVISIBLE); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeOtherEditText.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeOtherEditText.java deleted file mode 100644 index 3dd344e..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeOtherEditText.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2017-2019 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Handler; -import android.os.Looper; -import android.util.AttributeSet; -import android.widget.Spinner; - -import androidx.annotation.NonNull; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.Helper; - -import java.util.ArrayList; -import java.util.List; - -import timber.log.Timber; - -public class ExchangeOtherEditText extends ExchangeEditText { - /* - all exchanges are done through XMR - baseCurrency is the native currency - */ - - String baseCurrency = null; // not XMR - private double exchangeRate = 0; // baseCurrency to XMR - - public void setExchangeRate(double rate) { - exchangeRate = rate; - post(this::startExchange); - } - - public void setBaseCurrency(@NonNull String symbol) { - if (symbol.equals(baseCurrency)) return; - baseCurrency = symbol; - setCurrencyAdapter(sCurrencyA); - setCurrencyAdapter(sCurrencyB); - post(this::postInitialize); - } - - private void setBaseCurrency(Context context, AttributeSet attrs) { - TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExchangeEditText, 0, 0); - try { - baseCurrency = ta.getString(R.styleable.ExchangeEditText_baseSymbol); - if (baseCurrency == null) - throw new IllegalArgumentException("base currency must be set"); - } finally { - ta.recycle(); - } - } - - public ExchangeOtherEditText(Context context, AttributeSet attrs) { - super(context, attrs); - setBaseCurrency(context, attrs); - } - - public ExchangeOtherEditText(Context context, - AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - setBaseCurrency(context, attrs); - } - - @Override - void setCurrencyAdapter(Spinner spinner) { - List currencies = new ArrayList<>(); - if (!baseCurrency.equals(Helper.BASE_CRYPTO)) currencies.add(baseCurrency); - currencies.add(Helper.BASE_CRYPTO); - setCurrencyAdapter(spinner, currencies); - } - - @Override - void setInitialSpinnerSelections(Spinner baseSpinner, Spinner quoteSpinner) { - baseSpinner.setSelection(0, true); - quoteSpinner.setSelection(1, true); - } - - private void localExchange(final String base, final String quote, final double rate) { - exchange(new ExchangeRate() { - @Override - public String getServiceName() { - return "Local"; - } - - @Override - public String getBaseCurrency() { - return base; - } - - @Override - public String getQuoteCurrency() { - return quote; - } - - @Override - public double getRate() { - return rate; - } - }); - } - - @Override - void execExchange(String currencyA, String currencyB) { - if (!currencyA.equals(baseCurrency) && !currencyB.equals(baseCurrency)) { - throw new IllegalStateException("I can only exchange " + baseCurrency); - } - - showProgress(); - - Timber.d("execExchange(%s, %s)", currencyA, currencyB); - - // first deal with XMR/baseCurrency & baseCurrency/XMR - - if (currencyA.equals(Helper.BASE_CRYPTO) && (currencyB.equals(baseCurrency))) { - localExchange(currencyA, currencyB, 1.0d / exchangeRate); - return; - } - if (currencyA.equals(baseCurrency) && (currencyB.equals(Helper.BASE_CRYPTO))) { - localExchange(currencyA, currencyB, exchangeRate); - return; - } - - // next, deal with XMR/baseCurrency - - if (currencyA.equals(baseCurrency)) { - queryExchangeRate(Helper.BASE_CRYPTO, currencyB, exchangeRate, true); - } else { - queryExchangeRate(currencyA, Helper.BASE_CRYPTO, 1.0d / exchangeRate, false); - } - } - - private void queryExchangeRate(final String base, final String quote, final double factor, - final boolean baseIsBaseCrypto) { - queryExchangeRate(base, quote, - new ExchangeCallback() { - @Override - public void onSuccess(final ExchangeRate exchangeRate) { - if (isAttachedToWindow()) - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - ExchangeRate xchange = new ExchangeRate() { - @Override - public String getServiceName() { - return exchangeRate.getServiceName() + "+" + baseCurrency; - } - - @Override - public String getBaseCurrency() { - return baseIsBaseCrypto ? baseCurrency : base; - } - - @Override - public String getQuoteCurrency() { - return baseIsBaseCrypto ? quote : baseCurrency; - } - - @Override - public double getRate() { - return exchangeRate.getRate() * factor; - } - }; - exchange(xchange); - } - }); - } - - @Override - public void onError(final Exception e) { - Timber.e(e.getLocalizedMessage()); - new Handler(Looper.getMainLooper()).post(() -> exchangeFailed()); - } - }); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java deleted file mode 100644 index 3208f72..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/ExchangeView.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.Spinner; -import android.widget.TextView; - -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; -import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; -import com.m2049r.xmrwallet.util.ThemeHelper; -import com.m2049r.xmrwallet.util.Helper; -import com.m2049r.xmrwallet.util.ServiceHelper; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -import timber.log.Timber; - -public class ExchangeView extends LinearLayout { - String xmrAmount = null; - String notXmrAmount = null; - - public void enable(boolean enable) { - etAmount.setEnabled(enable); - sCurrencyA.setEnabled(enable); - sCurrencyB.setEnabled(enable); - } - - void setXmr(String xmr) { - xmrAmount = xmr; - if (onNewAmountListener != null) { - onNewAmountListener.onNewAmount(xmr); - } - } - - public void setAmount(String xmrAmount) { - if (xmrAmount != null) { - setCurrencyA(0); - etAmount.getEditText().setText(xmrAmount); - setXmr(xmrAmount); - this.notXmrAmount = null; - doExchange(); - } else { - setXmr(null); - this.notXmrAmount = null; - tvAmountB.setText("--"); - } - } - - public String getAmount() { - return xmrAmount; - } - - public void setError(String msg) { - etAmount.setError(msg); - } - - TextInputLayout etAmount; - TextView tvAmountB; - Spinner sCurrencyA; - Spinner sCurrencyB; - ImageView evExchange; - ProgressBar pbExchange; - - - public void setCurrencyA(int currency) { - if ((currency != 0) && (getCurrencyB() != 0)) { - setCurrencyB(0); - } - sCurrencyA.setSelection(currency, true); - doExchange(); - } - - public void setCurrencyB(int currency) { - if ((currency != 0) && (getCurrencyA() != 0)) { - setCurrencyA(0); - } - sCurrencyB.setSelection(currency, true); - doExchange(); - } - - public int getCurrencyA() { - return sCurrencyA.getSelectedItemPosition(); - } - - public int getCurrencyB() { - return sCurrencyB.getSelectedItemPosition(); - } - - public ExchangeView(Context context) { - super(context); - initializeViews(context); - } - - public ExchangeView(Context context, AttributeSet attrs) { - super(context, attrs); - initializeViews(context); - } - - public ExchangeView(Context context, - AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - initializeViews(context); - } - - /** - * Inflates the views in the layout. - * - * @param context the current context for the view. - */ - private void initializeViews(Context context) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.view_exchange, this); - } - - void setCurrencyAdapter(Spinner spinner) { - List currencies = new ArrayList<>(); - currencies.add(Helper.BASE_CRYPTO); - if (Helper.SHOW_EXCHANGERATES) - currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency))); - ArrayAdapter spinnerAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, currencies); - spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(spinnerAdapter); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - etAmount = findViewById(R.id.etAmount); - tvAmountB = findViewById(R.id.tvAmountB); - sCurrencyA = findViewById(R.id.sCurrencyA); - sCurrencyB = findViewById(R.id.sCurrencyB); - evExchange = findViewById(R.id.evExchange); - pbExchange = findViewById(R.id.pbExchange); - - setCurrencyAdapter(sCurrencyA); - setCurrencyAdapter(sCurrencyB); - - // make progress circle gray - pbExchange.getIndeterminateDrawable(). - setColorFilter(ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant), - android.graphics.PorterDuff.Mode.MULTIPLY); - - sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { - if (position != 0) { // if not XMR, select XMR on other - sCurrencyB.setSelection(0, true); - } - doExchange(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing (yet?) - } - }); - - sCurrencyB.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(final AdapterView parentView, View selectedItemView, int position, long id) { - if (position != 0) { // if not XMR, select XMR on other - sCurrencyA.setSelection(0, true); - } - doExchange(); - } - - @Override - public void onNothingSelected(AdapterView parentView) { - // nothing - } - }); - - etAmount.getEditText().setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus) { - doExchange(); - } - } - }); - - etAmount.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) - || (actionId == EditorInfo.IME_ACTION_DONE)) { - doExchange(); - return true; - } - return false; - } - }); - - - etAmount.getEditText().addTextChangedListener(new TextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - etAmount.setError(null); - clearAmounts(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - }); - - } - - final static double MAX_AMOUNT_XMR = 1000; - final static double MAX_AMOUNT_NOTXMR = 100000; - - public boolean checkEnteredAmount() { - boolean ok = true; - Timber.d("checkEnteredAmount"); - String amountEntry = etAmount.getEditText().getText().toString(); - if (!amountEntry.isEmpty()) { - try { - double a = Double.parseDouble(amountEntry); - double maxAmount = (getCurrencyA() == 0) ? MAX_AMOUNT_XMR : MAX_AMOUNT_NOTXMR; - if (a > (maxAmount)) { - etAmount.setError(getResources(). - getString(R.string.receive_amount_too_big, - String.format(Locale.US, "%,.0f", maxAmount))); - ok = false; - } else if (a < 0) { - etAmount.setError(getResources().getString(R.string.receive_amount_negative)); - ok = false; - } - } catch (NumberFormatException ex) { - etAmount.setError(getResources().getString(R.string.receive_amount_nan)); - ok = false; - } - } - if (ok) { - etAmount.setError(null); - } - return ok; - } - - public void doExchange() { - tvAmountB.setText("--"); - // use cached exchange rate if we have it - if (!isExchangeInProgress()) { - String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); - String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); - if ((enteredCurrencyA + enteredCurrencyB).equals(assetPair)) { - if (prepareExchange()) { - exchange(assetRate); - } else { - clearAmounts(); - } - } else { - clearAmounts(); - startExchange(); - } - } else { - clearAmounts(); - } - } - - private void clearAmounts() { - if ((xmrAmount != null) || (notXmrAmount != null)) { - tvAmountB.setText("--"); - setXmr(null); - notXmrAmount = null; - } - } - - private final ExchangeApi exchangeApi = ServiceHelper.getExchangeApi(); - - void startExchange() { - showProgress(); - String currencyA = (String) sCurrencyA.getSelectedItem(); - String currencyB = (String) sCurrencyB.getSelectedItem(); - - exchangeApi.queryExchangeRate(currencyA, currencyB, - new ExchangeCallback() { - @Override - public void onSuccess(final ExchangeRate exchangeRate) { - if (isAttachedToWindow()) - new Handler(Looper.getMainLooper()).post(() -> exchange(exchangeRate)); - } - - @Override - public void onError(final Exception e) { - Timber.e(e.getLocalizedMessage()); - new Handler(Looper.getMainLooper()).post(() -> exchangeFailed()); - } - }); - } - - public void exchange(double rate) { - if (getCurrencyA() == 0) { - if (xmrAmount == null) return; - if (!xmrAmount.isEmpty() && (rate > 0)) { - double amountB = rate * Double.parseDouble(xmrAmount); - notXmrAmount = Helper.getFormattedAmount(amountB, getCurrencyB() == 0); - } else { - notXmrAmount = ""; - } - tvAmountB.setText(notXmrAmount); - } else if (getCurrencyB() == 0) { - if (notXmrAmount == null) return; - if (!notXmrAmount.isEmpty() && (rate > 0)) { - double amountB = rate * Double.parseDouble(notXmrAmount); - setXmr(Helper.getFormattedAmount(amountB, true)); - } else { - setXmr(""); - } - tvAmountB.setText(xmrAmount); - } else { // no XMR currency - cannot happen! - throw new IllegalStateException("No XMR currency!"); - } - if (rate == 0) - tvAmountB.setText("--"); - } - - boolean prepareExchange() { - Timber.d("prepareExchange()"); - if (checkEnteredAmount()) { - String enteredAmount = etAmount.getEditText().getText().toString(); - if (!enteredAmount.isEmpty()) { - String cleanAmount = ""; - if (getCurrencyA() == 0) { - // sanitize the input - cleanAmount = Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount)); - setXmr(cleanAmount); - notXmrAmount = null; - Timber.d("cleanAmount = %s", cleanAmount); - } else if (getCurrencyB() == 0) { // we use B & 0 here for the else below ... - // sanitize the input - double amountA = Double.parseDouble(enteredAmount); - cleanAmount = String.format(Locale.US, "%.2f", amountA); - setXmr(null); - notXmrAmount = cleanAmount; - } else { // no XMR currency - cannot happen! - Timber.e("No XMR currency!"); - setXmr(null); - notXmrAmount = null; - return false; - } - Timber.d("prepareExchange() %s", cleanAmount); - } else { - setXmr(""); - notXmrAmount = ""; - } - return true; - } else { - setXmr(null); - notXmrAmount = null; - return false; - } - } - - public void exchangeFailed() { - hideProgress(); - exchange(0); - if (onFailedExchangeListener != null) { - onFailedExchangeListener.onFailedExchange(); - } - } - - String assetPair = null; - double assetRate = 0; - - public void exchange(ExchangeRate exchangeRate) { - hideProgress(); - // first, make sure this is what we want - String enteredCurrencyA = (String) sCurrencyA.getSelectedItem(); - String enteredCurrencyB = (String) sCurrencyB.getSelectedItem(); - if (!exchangeRate.getBaseCurrency().equals(enteredCurrencyA) - || !exchangeRate.getQuoteCurrency().equals(enteredCurrencyB)) { - // something's wrong - Timber.e("Currencies don't match!"); - return; - } - assetPair = enteredCurrencyA + enteredCurrencyB; - assetRate = exchangeRate.getRate(); - if (prepareExchange()) { - exchange(exchangeRate.getRate()); - } - } - - private void showProgress() { - pbExchange.setVisibility(View.VISIBLE); - } - - private boolean isExchangeInProgress() { - return pbExchange.getVisibility() == View.VISIBLE; - } - - private void hideProgress() { - pbExchange.setVisibility(View.INVISIBLE); - } - - // Hooks - public interface OnNewAmountListener { - void onNewAmount(String xmr); - } - - OnNewAmountListener onNewAmountListener; - - public void setOnNewAmountListener(OnNewAmountListener listener) { - onNewAmountListener = listener; - } - - public interface OnAmountInvalidatedListener { - void onAmountInvalidated(); - } - - OnAmountInvalidatedListener onAmountInvalidatedListener; - - public void setOnAmountInvalidatedListener(OnAmountInvalidatedListener listener) { - onAmountInvalidatedListener = listener; - } - - public interface OnFailedExchangeListener { - void onFailedExchange(); - } - - OnFailedExchangeListener onFailedExchangeListener; - - public void setOnFailedExchangeListener(OnFailedExchangeListener listener) { - onFailedExchangeListener = listener; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/PasswordEntryView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/PasswordEntryView.java deleted file mode 100644 index 6d5fee1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/PasswordEntryView.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.material.textfield.TextInputLayout; -import com.m2049r.xmrwallet.R; -import com.nulabinc.zxcvbn.Zxcvbn; - -public class PasswordEntryView extends TextInputLayout implements TextWatcher { - final private Zxcvbn zxcvbn = new Zxcvbn(); - - public PasswordEntryView(@NonNull Context context) { - super(context, null); - } - - public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs, R.attr.textInputStyle); - } - - public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public void addView(@NonNull View child, int index, @NonNull final ViewGroup.LayoutParams params) { - super.addView(child, index, params); - final EditText et = getEditText(); - if (et != null) - et.addTextChangedListener(this); - } - - @Override - public void afterTextChanged(Editable s) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - String password = s.toString(); - int icon = 0; - if (!password.isEmpty()) { - final double strength = Math.min(zxcvbn.measure(password).getGuessesLog10(), 15) / 3 * 20; // 0-100% - if (strength < 21) - icon = R.drawable.ic_smiley_sad_filled; - else if (strength < 40) - icon = R.drawable.ic_smiley_meh_filled; - else if (strength < 60) - icon = R.drawable.ic_smiley_neutral_filled; - else if (strength < 80) - icon = R.drawable.ic_smiley_happy_filled; - else if (strength < 99) - icon = R.drawable.ic_smiley_ecstatic_filled; - else - icon = R.drawable.ic_smiley_gunther_filled; - } - setErrorIconDrawable(icon); - if (icon != 0) - setError(" "); - else setError(null); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java b/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java deleted file mode 100644 index 11d5355..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/SendProgressView.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.m2049r.xmrwallet.R; - -public class SendProgressView extends LinearLayout { - - public SendProgressView(Context context, AttributeSet attrs) { - super(context, attrs); - initializeViews(context); - } - - public SendProgressView(Context context, - AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - initializeViews(context); - } - - private void initializeViews(Context context) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.view_send_progress, this); - } - - - View pbProgress; - View llMessage; - TextView tvCode; - TextView tvMessage; - TextView tvSolution; - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - pbProgress = findViewById(R.id.pbProgress); - llMessage = findViewById(R.id.llMessage); - tvCode = findViewById(R.id.tvCode); - tvMessage = findViewById(R.id.tvMessage); - tvSolution = findViewById(R.id.tvSolution); - } - - public void showProgress(String progressText) { - pbProgress.setVisibility(VISIBLE); - tvCode.setVisibility(INVISIBLE); - tvMessage.setText(progressText); - llMessage.setVisibility(VISIBLE); - tvSolution.setVisibility(INVISIBLE); - } - - public void hideProgress() { - pbProgress.setVisibility(INVISIBLE); - llMessage.setVisibility(INVISIBLE); - } - - public void showMessage(String code, String message, String solution) { - tvCode.setText(code); - tvMessage.setText(message); - tvSolution.setText(solution); - tvCode.setVisibility(VISIBLE); - llMessage.setVisibility(VISIBLE); - tvSolution.setVisibility(VISIBLE); - pbProgress.setVisibility(INVISIBLE); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java b/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java deleted file mode 100644 index 768f8a8..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/widget/Toolbar.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889 - -package com.m2049r.xmrwallet.widget; - -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import com.google.android.material.appbar.MaterialToolbar; -import com.m2049r.xmrwallet.R; - -import timber.log.Timber; - -public class Toolbar extends MaterialToolbar { - public interface OnButtonListener { - void onButton(int type); - } - - OnButtonListener onButtonListener; - - public void setOnButtonListener(OnButtonListener listener) { - onButtonListener = listener; - } - - ImageView toolbarImage; - TextView toolbarTitle; - TextView toolbarSubtitle; - ImageButton bSettings; - - public Toolbar(Context context) { - super(context); - initializeViews(context); - } - - public Toolbar(Context context, AttributeSet attrs) { - super(context, attrs); - initializeViews(context); - } - - public Toolbar(Context context, - AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - initializeViews(context); - } - - /** - * Inflates the views in the layout. - * - * @param context the current context for the view. - */ - private void initializeViews(Context context) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.view_toolbar, this); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - toolbarImage = findViewById(R.id.toolbarImage); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - // the vector image does not work well for androis < Nougat - toolbarImage.getLayoutParams().width = (int) getResources().getDimension(R.dimen.logo_width); - toolbarImage.setImageResource(R.drawable.logo_horizontol_xmrujo); - } - - toolbarTitle = findViewById(R.id.toolbarTitle); - toolbarSubtitle = findViewById(R.id.toolbarSubtitle); - bSettings = findViewById(R.id.bSettings); - bSettings.setOnClickListener(v -> { - if (onButtonListener != null) { - onButtonListener.onButton(buttonType); - } - }); - } - - public void setTitle(String title, String subtitle) { - setTitle(title); - setSubtitle(subtitle); - } - - public void setTitle(String title) { - toolbarTitle.setText(title); - if (title != null) { - toolbarImage.setVisibility(View.INVISIBLE); - toolbarTitle.setVisibility(View.VISIBLE); - } else { - toolbarImage.setVisibility(View.VISIBLE); - toolbarTitle.setVisibility(View.INVISIBLE); - } - } - - public final static int BUTTON_NONE = 0; - public final static int BUTTON_BACK = 1; - public final static int BUTTON_CLOSE = 2; - public final static int BUTTON_SETTINGS = 3; - public final static int BUTTON_CANCEL = 4; - - int buttonType = BUTTON_SETTINGS; - - public void setButton(int type) { - switch (type) { - case BUTTON_BACK: - Timber.d("BUTTON_BACK"); - bSettings.setImageResource(R.drawable.ic_arrow_back); - bSettings.setVisibility(View.VISIBLE); - break; - case BUTTON_CLOSE: - Timber.d("BUTTON_CLOSE"); - bSettings.setImageResource(R.drawable.ic_close_white_24dp); - bSettings.setVisibility(View.VISIBLE); - break; - case BUTTON_SETTINGS: - Timber.d("BUTTON_SETTINGS"); - bSettings.setImageResource(R.drawable.ic_settings); - bSettings.setVisibility(View.VISIBLE); - break; - case BUTTON_CANCEL: - Timber.d("BUTTON_CANCEL"); - bSettings.setImageResource(R.drawable.ic_close_white_24dp); - bSettings.setVisibility(View.VISIBLE); - break; - case BUTTON_NONE: - default: - Timber.d("BUTTON_NONE"); - bSettings.setVisibility(View.INVISIBLE); - } - buttonType = type; - } - - public void setSubtitle(String subtitle) { - toolbarSubtitle.setText(subtitle); - if (subtitle != null) { - toolbarSubtitle.setVisibility(View.VISIBLE); - } else { - toolbarSubtitle.setVisibility(View.INVISIBLE); - } - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_monero_qr.xml b/app/src/main/res/drawable/ic_monero_qr.xml new file mode 100644 index 0000000..2456cf8 --- /dev/null +++ b/app/src/main/res/drawable/ic_monero_qr.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml deleted file mode 100644 index c6cdff3..0000000 --- a/app/src/main/res/layout/activity_login.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..432bd65 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_on_boarding.xml b/app/src/main/res/layout/activity_on_boarding.xml deleted file mode 100644 index cfaf918..0000000 --- a/app/src/main/res/layout/activity_on_boarding.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - -