diff --git a/app/build.gradle b/app/build.gradle index 9123227..5a77de5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,15 +2,15 @@ apply plugin: 'com.android.application' apply plugin: "androidx.navigation.safeargs" android { - compileSdkVersion 33 + compileSdkVersion 34 buildToolsVersion '30.0.3' ndkVersion '17.2.4988734' defaultConfig { applicationId "net.mynero.wallet" minSdkVersion 21 - targetSdkVersion 33 - versionCode 40302 - versionName "0.4.3.2 'Fluorine Fermi'" + targetSdkVersion 34 + versionCode 40400 + versionName "0.4.4 'Fluorine Fermi'" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 7407801..440106a 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -1135,7 +1135,7 @@ Java_net_mynero_wallet_model_Wallet_getCoinsJ(JNIEnv *env, jobject instance) { jobject newCoinsInfo(JNIEnv *env, Monero::CoinsInfo *info) { jmethodID c = env->GetMethodID(class_CoinsInfo, "", - "(JZLjava/lang/String;JLjava/lang/String;Ljava/lang/String;ZJ)V"); + "(JZLjava/lang/String;JLjava/lang/String;Ljava/lang/String;ZJZ)V"); jstring _key_image = env->NewStringUTF(info->keyImage().c_str()); jstring _pub_key = env->NewStringUTF(info->pubKey().c_str()); jstring _hash = env->NewStringUTF(info->hash().c_str()); @@ -1147,7 +1147,8 @@ jobject newCoinsInfo(JNIEnv *env, Monero::CoinsInfo *info) { _hash, _pub_key, info->unlocked(), - static_cast (info->internalOutputIndex())); + static_cast (info->internalOutputIndex()), + info->frozen()); env->DeleteLocalRef(_key_image); env->DeleteLocalRef(_hash); env->DeleteLocalRef(_pub_key); @@ -1183,6 +1184,18 @@ Java_net_mynero_wallet_model_Coins_refreshJ(JNIEnv *env, jobject instance) { return coins_cpp2java(env, coins->getAll()); } +JNIEXPORT void JNICALL +Java_net_mynero_wallet_model_Coins_setFrozen(JNIEnv *env, jobject instance, jstring publicKey, jboolean frozen) { + Monero::Coins *coins = getHandle(env, instance); + const char *_publicKey = env->GetStringUTFChars(publicKey, nullptr); + if (frozen) { + coins->setFrozen(_publicKey); + } else { + coins->thaw(_publicKey); + } + env->ReleaseStringUTFChars(publicKey, _publicKey); +} + //virtual TransactionHistory * history() const = 0; JNIEXPORT jlong JNICALL Java_net_mynero_wallet_model_Wallet_getHistoryJ(JNIEnv *env, jobject instance) { diff --git a/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java b/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java index 2f1f832..cd6546d 100644 --- a/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java +++ b/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java @@ -32,11 +32,12 @@ import net.mynero.wallet.util.Constants; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class CoinsInfoAdapter extends RecyclerView.Adapter { private List localDataSet; - private List selectedUtxos; + private List selectedUtxos; private CoinsInfoAdapterListener listener = null; /** @@ -48,13 +49,13 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter(); } - public void submitList(List dataSet, List selectedUtxos) { + public void submitList(List dataSet, List selectedUtxos) { this.localDataSet = dataSet; this.selectedUtxos = selectedUtxos; notifyDataSetChanged(); } - public void updateSelectedUtxos(List selectedUtxos) { + public void updateSelectedUtxos(List selectedUtxos) { this.selectedUtxos = selectedUtxos; notifyDataSetChanged(); } @@ -98,8 +99,14 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter selectedUtxos) { - boolean selected = selectedUtxos.contains(coinsInfo.getKeyImage()); + public void bind(CoinsInfo coinsInfo, List selectedUtxos) { + boolean selected = false; + for(CoinsInfo selectedUtxo : selectedUtxos) { + if (Objects.equals(selectedUtxo.getKeyImage(), coinsInfo.getKeyImage())) { + selected = true; + break; + } + } TextView pubKeyTextView = itemView.findViewById(R.id.utxo_pub_key_textview); TextView amountTextView = itemView.findViewById(R.id.utxo_amount_textview); TextView globalIdxTextView = itemView.findViewById(R.id.utxo_global_index_textview); @@ -119,10 +126,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter selectedUtxos = new ArrayList<>(); + private final ArrayList selectedUtxos = new ArrayList<>(); private final CoinsInfoAdapter adapter = new CoinsInfoAdapter(this); private Button sendUtxosButton; private Button churnUtxosButton; + private Button freezeUtxosButton; + enum FreezeActionType { + FREEZE, + UNFREEZE, + TOGGLE_FREEZE + } + private FreezeActionType freezeActionType = FreezeActionType.FREEZE; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -47,22 +57,45 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf } private void bindListeners(View view) { + freezeUtxosButton = view.findViewById(R.id.freeze_utxos_button); sendUtxosButton = view.findViewById(R.id.send_utxos_button); churnUtxosButton = view.findViewById(R.id.churn_utxos_button); sendUtxosButton.setVisibility(View.GONE); churnUtxosButton.setVisibility(View.GONE); + freezeUtxosButton.setVisibility(View.GONE); + freezeUtxosButton.setOnClickListener(view1 -> { + Toast.makeText(getContext(), "Toggling freeze status, please wait.", Toast.LENGTH_SHORT).show(); + MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR.execute(() -> { + UTXOService.getInstance().toggleFrozen(selectedUtxos); + getActivity().runOnUiThread(() -> { + selectedUtxos.clear(); + adapter.updateSelectedUtxos(new ArrayList<>()); + sendUtxosButton.setVisibility(View.GONE); + churnUtxosButton.setVisibility(View.GONE); + freezeUtxosButton.setVisibility(View.GONE); + }); + }); + }); sendUtxosButton.setOnClickListener(view1 -> { + ArrayList selectedKeyImages = new ArrayList<>(); + for(CoinsInfo coinsInfo : selectedUtxos) { + selectedKeyImages.add(coinsInfo.getKeyImage()); + } SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); sendDialog.listener = this; - sendDialog.selectedUtxos = selectedUtxos; + sendDialog.selectedUtxos = selectedKeyImages; sendDialog.show(getActivity().getSupportFragmentManager(), null); }); churnUtxosButton.setOnClickListener(view1 -> { + ArrayList selectedKeyImages = new ArrayList<>(); + for(CoinsInfo coinsInfo : selectedUtxos) { + selectedKeyImages.add(coinsInfo.getKeyImage()); + } SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); sendDialog.listener = this; sendDialog.isChurning = true; sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().getAddress()); - sendDialog.selectedUtxos = selectedUtxos; + sendDialog.selectedUtxos = selectedKeyImages; sendDialog.show(getActivity().getSupportFragmentManager(), null); }); } @@ -93,22 +126,43 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf @Override public void onUtxoSelected(CoinsInfo coinsInfo) { - boolean selected = selectedUtxos.contains(coinsInfo.getKeyImage()); + boolean selected = selectedUtxos.contains(coinsInfo); if (selected) { - selectedUtxos.remove(coinsInfo.getKeyImage()); + selectedUtxos.remove(coinsInfo); } else { - selectedUtxos.add(coinsInfo.getKeyImage()); + selectedUtxos.add(coinsInfo); } + boolean frozenExists = false, unfrozenExists = false, bothExist = false; + for(CoinsInfo selectedUtxo : selectedUtxos) { + if(selectedUtxo.isFrozen()) + frozenExists = true; + else { + unfrozenExists = true; + } + } + bothExist = frozenExists && unfrozenExists; + if (selectedUtxos.isEmpty()) { sendUtxosButton.setVisibility(View.GONE); churnUtxosButton.setVisibility(View.GONE); + freezeUtxosButton.setVisibility(View.GONE); + freezeActionType = FreezeActionType.FREEZE; } else { sendUtxosButton.setVisibility(View.VISIBLE); churnUtxosButton.setVisibility(View.VISIBLE); + freezeUtxosButton.setVisibility(View.VISIBLE); } adapter.updateSelectedUtxos(selectedUtxos); + + if(bothExist) { + freezeUtxosButton.setText(R.string.toggle_freeze); + } else if(frozenExists) { + freezeUtxosButton.setText(R.string.unfreeze); + } else if(unfrozenExists) { + freezeUtxosButton.setText(R.string.freeze); + } } @Override diff --git a/app/src/main/java/net/mynero/wallet/model/Coins.java b/app/src/main/java/net/mynero/wallet/model/Coins.java index f6fb33f..a9854ca 100644 --- a/app/src/main/java/net/mynero/wallet/model/Coins.java +++ b/app/src/main/java/net/mynero/wallet/model/Coins.java @@ -46,5 +46,7 @@ public class Coins { coins = transactionInfos; } + public native void setFrozen(String publicKey, boolean frozen); + private native List refreshJ(); } diff --git a/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java b/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java index c572120..962c418 100644 --- a/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java +++ b/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java @@ -46,8 +46,9 @@ public class CoinsInfo implements Parcelable, Comparable { String pubKey; boolean unlocked; long localOutputIndex; + boolean frozen; - public CoinsInfo(long globalOutputIndex, boolean spent, String keyImage, long amount, String hash, String pubKey, boolean unlocked, long localOutputIndex) { + public CoinsInfo(long globalOutputIndex, boolean spent, String keyImage, long amount, String hash, String pubKey, boolean unlocked, long localOutputIndex, boolean frozen) { this.globalOutputIndex = globalOutputIndex; this.spent = spent; this.keyImage = keyImage; @@ -56,6 +57,7 @@ public class CoinsInfo implements Parcelable, Comparable { this.pubKey = pubKey; this.unlocked = unlocked; this.localOutputIndex = localOutputIndex; + this.frozen = frozen; } protected CoinsInfo(Parcel in) { @@ -94,6 +96,10 @@ public class CoinsInfo implements Parcelable, Comparable { return localOutputIndex; } + public boolean isFrozen() { + return frozen; + } + @Override public int describeContents() { return 0; diff --git a/app/src/main/java/net/mynero/wallet/service/UTXOService.java b/app/src/main/java/net/mynero/wallet/service/UTXOService.java index 057a412..a19fe9f 100644 --- a/app/src/main/java/net/mynero/wallet/service/UTXOService.java +++ b/app/src/main/java/net/mynero/wallet/service/UTXOService.java @@ -5,6 +5,7 @@ import android.util.Pair; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import net.mynero.wallet.model.Coins; import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.PendingTransaction; import net.mynero.wallet.model.Wallet; @@ -36,6 +37,14 @@ public class UTXOService extends ServiceBase { return WalletManager.getInstance().getWallet().getCoins().getAll(); } + public void toggleFrozen(List selectedCoins) { + Coins coins = WalletManager.getInstance().getWallet().getCoins(); + for(CoinsInfo coin : selectedCoins) { + coins.setFrozen(coin.getPubKey(), !coin.isFrozen()); + } + refreshUtxos(); + } + public ArrayList selectUtxos(long amount, boolean sendAll) throws Exception { final long basicFeeEstimate = calculateBasicFee(amount); final long amountWithBasicFee = amount + basicFeeEstimate; @@ -46,7 +55,7 @@ public class UTXOService extends ServiceBase { Collections.sort(utxos); //loop through each utxo for (CoinsInfo coinsInfo : utxos) { - if (!coinsInfo.isSpent() && coinsInfo.isUnlocked()) { //filter out spent and locked outputs + if (!coinsInfo.isSpent() && coinsInfo.isUnlocked() && !coinsInfo.isFrozen()) { //filter out spent, locked, and frozen outputs if (sendAll) { // if send all, add all utxos and set amount to send all selectedUtxos.add(coinsInfo.getKeyImage()); diff --git a/app/src/main/res/drawable/button_bg_center.xml b/app/src/main/res/drawable/button_bg_center.xml new file mode 100644 index 0000000..10f01bd --- /dev/null +++ b/app/src/main/res/drawable/button_bg_center.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_bg_disabled_center.xml b/app/src/main/res/drawable/button_bg_disabled_center.xml new file mode 100644 index 0000000..83846bc --- /dev/null +++ b/app/src/main/res/drawable/button_bg_disabled_center.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/button_bg_enabled_center.xml b/app/src/main/res/drawable/button_bg_enabled_center.xml new file mode 100644 index 0000000..3a0b828 --- /dev/null +++ b/app/src/main/res/drawable/button_bg_enabled_center.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_utxos.xml b/app/src/main/res/layout/fragment_utxos.xml index ea8cd54..24bf6a1 100644 --- a/app/src/main/res/layout/fragment_utxos.xml +++ b/app/src/main/res/layout/fragment_utxos.xml @@ -47,15 +47,25 @@ app:layout_constraintStart_toStartOf="parent">