diff --git a/.gitignore b/.gitignore index 8080314..532b316 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /captures .externalNativeBuild .DS_Store +/app/release diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..8047f23 Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index d067860..36bf8c7 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -33,6 +33,7 @@ import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.TextView; +import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; @@ -366,7 +367,25 @@ public class GenerateFragment extends Fragment { public void onResume() { super.onResume(); Log.d(TAG, "onResume()"); - activityCallback.setTitle(getString(R.string.generate_title)); + 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_VIEWONLY: + return getString(R.string.generate_wallet_type_view); + default: + Log.e(TAG, "unknown type " + type); + return "?"; + } } GenerateFragment.Listener activityCallback; @@ -379,6 +398,9 @@ public class GenerateFragment extends Fragment { void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); void setTitle(String title); + + void setToolbarButton(int type); + } @Override diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index eabbd60..cb97f34 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -47,8 +47,10 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import com.m2049r.xmrwallet.dialog.DonationFragment; import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.LicensesFragment; +import com.m2049r.xmrwallet.dialog.PrivacyFragment; import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; @@ -120,7 +122,8 @@ public class LoginActivity extends AppCompatActivity finish(); break; case Toolbar.BUTTON_DONATE: - Toast.makeText(LoginActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show(); + DonationFragment.display(getSupportFragmentManager()); + break; case Toolbar.BUTTON_NONE: default: Log.e(TAG, "Button " + type + "pressed - how can this be?"); @@ -1056,9 +1059,12 @@ public class LoginActivity extends AppCompatActivity case R.id.action_details_help: HelpFragment.displayHelp(getSupportFragmentManager(), R.raw.help_details); return true; - case R.id.action_lincense_info: + case R.id.action_license_info: LicensesFragment.displayLicensesFragment(getSupportFragmentManager()); return true; + case R.id.action_privacy_policy: + PrivacyFragment.display(getSupportFragmentManager()); + return true; case R.id.action_testnet: try { LoginFragment loginFragment = (LoginFragment) diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index 45bdcf0..5ae7692 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -143,6 +143,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL); fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL); + 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); @@ -208,6 +209,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter }); loadPrefs(); + return view; } @@ -267,6 +269,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter filterList(); adapter.setInfos(displayedList); adapter.notifyDataSetChanged(); + if (displayedList.isEmpty()) { + fab.startAnimation(fab_pulse); + } else { + fab.clearAnimation(); + } } private void showInfo(@NonNull String name) { @@ -381,6 +388,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter private FrameLayout fabScreen; private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL; 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; diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java index c0f9eee..845d090 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java @@ -57,11 +57,8 @@ public class SendFragment extends Fragment { private TextInputLayout etAddress; private TextInputLayout etPaymentId; - //TextInputLayout etAmount; + private ExchangeView evAmount; - private TextView tvAmountB; - private Spinner sCurrencyA; - private Spinner sCurrencyB; private Button bScan; private Spinner sMixin; @@ -102,9 +99,6 @@ public class SendFragment extends Fragment { etAddress = (TextInputLayout) view.findViewById(R.id.etAddress); etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); evAmount = (ExchangeView) view.findViewById(R.id.evAmount); - tvAmountB = (TextView) view.findViewById(R.id.tvAmountB); - sCurrencyA = (Spinner) view.findViewById(R.id.sCurrencyA); - sCurrencyB = (Spinner) view.findViewById(R.id.sCurrencyB); bScan = (Button) view.findViewById(R.id.bScan); bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend); @@ -140,18 +134,6 @@ public class SendFragment extends Fragment { } }); - evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() { - @Override - public void onNewAmount(String xmr) { - if ((xmr != null)) { - // stupid workaround to not show error on open of screen - if ((checkAddressNoError() && checkAmountWithError()) || checkAmount()) { - etPaymentId.requestFocus(); - } - } - } - }); - etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { @@ -406,9 +388,6 @@ public class SendFragment extends Fragment { } if ((data != null) && (data.amount <= 0)) { evAmount.focus(); - } else { - etDummy.requestFocus(); - Helper.hideKeyboard(getActivity()); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index eef4039..51ed193 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -36,6 +36,7 @@ import android.util.Log; import android.view.MenuItem; import android.widget.Toast; +import com.m2049r.xmrwallet.dialog.DonationFragment; import com.m2049r.xmrwallet.layout.Toolbar; import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.TransactionInfo; @@ -154,7 +155,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. onWalletDetails(); return true; case R.id.action_donate: - onWalletDetails(); + DonationFragment.display(getSupportFragmentManager()); return true; case R.id.action_share: onShareTxInfo(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java index a323bad..d72b3c3 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java @@ -305,17 +305,17 @@ public class WalletFragment extends Fragment long daemonHeight = activityCallback.getDaemonHeight(); if (!wallet.isSynchronized()) { long n = daemonHeight - wallet.getBlockChainHeight(); - sync = formatter.format(n) + " " + getString(R.string.status_remaining); + sync = getString(R.string.status_syncing) + " " + formatter.format(n) + " " + getString(R.string.status_remaining); if (firstBlock == 0) { firstBlock = wallet.getBlockChainHeight(); } int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock)); - onProgress(getString(R.string.status_syncing) + " " + sync); + //onProgress(getString(R.string.status_syncing) + " " + sync); if (x == 0) x = -1; onProgress(x); ivSynced.setVisibility(View.GONE); } else { - sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight()); + sync = getString(R.string.status_synced) + formatter.format(wallet.getBlockChainHeight()); ivSynced.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/DonationFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/DonationFragment.java new file mode 100644 index 0000000..db9e25d --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/DonationFragment.java @@ -0,0 +1,114 @@ +/* + * 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 work by Adam Speakman http://speakman.net.nz + */ + +package com.m2049r.xmrwallet.dialog; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.text.Html; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebView; +import android.widget.ImageButton; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.m2049r.xmrwallet.BuildConfig; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.util.Helper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class DonationFragment extends DialogFragment { + static final String TAG = "DonationFragment"; + private static final String FRAGMENT_TAG = "com.m2049r.xmrwalelt.dialog.DonationFragment"; + + /** + * Creates a new instance of LicensesFragment with no Close button. + * + * @return A new licenses fragment. + */ + public static DonationFragment newInstance() { + return new DonationFragment(); + } + + /** + * Builds and displays a licenses fragment with no Close button. Requires + * "/res/raw/licenses.html" and "/res/layout/licenses_fragment.xml" to be + * present. + * + * @param fm A fragment manager instance used to display this LicensesFragment. + */ + public static void display(FragmentManager fm) { + FragmentTransaction ft = fm.beginTransaction(); + Fragment prev = fm.findFragmentByTag(FRAGMENT_TAG); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + // Create and show the dialog. + DialogFragment newFragment = DonationFragment.newInstance(); + newFragment.show(ft, FRAGMENT_TAG); + } + + @SuppressLint("InflateParams") + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_donation, null); + + ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.donation_credits))); + + ((ImageButton) view.findViewById(R.id.bCopyAddress)). + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), + ((TextView) view.findViewById(R.id.tvWalletAddress)).getText().toString()); + Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); + } + }); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setView(view); + builder.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/PrivacyFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/PrivacyFragment.java new file mode 100644 index 0000000..3c21c57 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/PrivacyFragment.java @@ -0,0 +1,93 @@ +/* + * 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 work by Adam Speakman http://speakman.net.nz + */ + +package com.m2049r.xmrwallet.dialog; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.util.Helper; + +public class PrivacyFragment extends DialogFragment { + static final String TAG = "PrivacyFragment"; + private static final String FRAGMENT_TAG = "com.m2049r.xmrwalelt.dialog.PrivacyFragment"; + + /** + * Creates a new instance of LicensesFragment with no Close button. + * + * @return A new licenses fragment. + */ + public static PrivacyFragment newInstance() { + return new PrivacyFragment(); + } + + /** + * Builds and displays a licenses fragment with no Close button. Requires + * "/res/raw/licenses.html" and "/res/layout/licenses_fragment.xml" to be + * present. + * + * @param fm A fragment manager instance used to display this LicensesFragment. + */ + public static void display(FragmentManager fm) { + FragmentTransaction ft = fm.beginTransaction(); + Fragment prev = fm.findFragmentByTag(FRAGMENT_TAG); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + // Create and show the dialog. + DialogFragment newFragment = PrivacyFragment.newInstance(); + newFragment.show(ft, FRAGMENT_TAG); + } + + @SuppressLint("InflateParams") + @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))); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setView(view); + builder.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/layout/ExchangeView.java b/app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java index 39b4c02..db4a720 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/ExchangeView.java @@ -67,6 +67,7 @@ public class ExchangeView extends LinearLayout implements AsyncExchangeRate.List public void setAmount(String xmrAmount) { if (xmrAmount != null) { setCurrencyA(0); + etAmount.getEditText().setText(xmrAmount); setXmr(xmrAmount); this.notXmrAmount = null; doExchange(); @@ -191,6 +192,15 @@ public class ExchangeView extends LinearLayout implements AsyncExchangeRate.List } }); + 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)) || (actionId == EditorInfo.IME_ACTION_DONE)) { diff --git a/app/src/main/res/anim/fab_pulse.xml b/app/src/main/res/anim/fab_pulse.xml new file mode 100644 index 0000000..fb4e3ac --- /dev/null +++ b/app/src/main/res/anim/fab_pulse.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gunther_donate.png b/app/src/main/res/drawable/gunther_donate.png new file mode 100644 index 0000000..bc6508c Binary files /dev/null and b/app/src/main/res/drawable/gunther_donate.png differ diff --git a/app/src/main/res/drawable/ic_eye_black_24dp.xml b/app/src/main/res/drawable/ic_eye_black_24dp.xml new file mode 100644 index 0000000..e02f1d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_eye_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_donation.xml b/app/src/main/res/layout/fragment_donation.xml new file mode 100644 index 0000000..c615dd4 --- /dev/null +++ b/app/src/main/res/layout/fragment_donation.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_generate.xml b/app/src/main/res/layout/fragment_generate.xml index b4573db..aed4258 100644 --- a/app/src/main/res/layout/fragment_generate.xml +++ b/app/src/main/res/layout/fragment_generate.xml @@ -69,7 +69,7 @@ android:hint="@string/generate_mnemonic_hint" android:imeOptions="actionNext" android:inputType="textMultiLine" - android:textAlignment="center" /> + android:textAlignment="textStart" /> + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index 6c78ab8..6572261 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -47,10 +47,10 @@