You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Wonerujo/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java

465 lines
16 KiB

/*
* 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.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.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
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.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.layout.NodeInfoAdapter;
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.NodePinger;
import com.m2049r.xmrwallet.util.Notice;
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<WalletManager.WalletInfo> walletList = new ArrayList<>();
private View tvGuntherSays;
private ImageView ivGunther;
private TextView tvNodeName;
private TextView tvNodeAddress;
private View pbNode;
private View llNode;
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<NodeInfo> getFavouriteNodes();
Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger();
}
@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()");
super.onPause();
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume() %s", activityCallback.getFavouriteNodes().size());
activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet();
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);
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);
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);
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");
pbNode = view.findViewById(R.id.pbNode);
llNode = view.findViewById(R.id.llNode);
llNode.setOnClickListener(v -> startNodePrefs());
tvNodeName = view.findViewById(R.id.tvNodeName);
tvNodeAddress = view.findViewById(R.id.tvNodeAddress);
view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode());
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<String> 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, fabLedger;
private FrameLayout fabScreen;
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, 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);
}
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);
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);
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);
}
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.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<NodeInfo> nodes) {
if (nodes.isEmpty()) return null;
NodePinger.execute(nodes, null);
List<NodeInfo> nodeList = new ArrayList<>(nodes);
Collections.sort(nodeList, NodeInfo.BestNodeComparator);
return nodeList.get(0);
}
private class AsyncFindBestNode extends AsyncTask<Integer, Void, NodeInfo> {
final static int PING_SELECTED = 0;
final static int FIND_BEST = 1;
@Override
protected void onPreExecute() {
super.onPreExecute();
pbNode.setVisibility(View.VISIBLE);
llNode.setVisibility(View.INVISIBLE);
}
@Override
protected NodeInfo doInBackground(Integer... params) {
Set<NodeInfo> 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;
pbNode.setVisibility(View.INVISIBLE);
llNode.setVisibility(View.VISIBLE);
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);
tvNodeAddress.setText(null);
tvNodeAddress.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());
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp());
tvNodeAddress.setVisibility(View.VISIBLE);
}
private void startNodePrefs() {
activityCallback.onNodePrefs();
}
}