From 4bb9f5cb4355a422cb1bd795a8e4c392b36ef3e4 Mon Sep 17 00:00:00 2001
From: Akinwale Ariwodola <akinwale@gmail.com>
Date: Sat, 23 May 2020 23:37:57 +0100
Subject: [PATCH] add option to unlock all tips

---
 .../java/io/lbry/browser/MainActivity.java    | 42 +++++++++-
 .../io/lbry/browser/model/WalletBalance.java  | 17 +++++
 .../browser/tasks/wallet/UnlockTipsTask.java  | 76 +++++++++++++++++++
 .../tasks/wallet/WalletBalanceTask.java       | 11 +--
 .../browser/ui/wallet/WalletFragment.java     | 55 ++++++++++++++
 .../main/java/io/lbry/browser/utils/Lbry.java |  3 +
 .../main/res/layout/card_wallet_balance.xml   | 41 ++++++++--
 7 files changed, 228 insertions(+), 17 deletions(-)
 create mode 100644 app/src/main/java/io/lbry/browser/tasks/wallet/UnlockTipsTask.java

diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java
index ae1b023c..411174bc 100644
--- a/app/src/main/java/io/lbry/browser/MainActivity.java
+++ b/app/src/main/java/io/lbry/browser/MainActivity.java
@@ -66,7 +66,6 @@ import androidx.core.app.NotificationManagerCompat;
 import androidx.core.content.ContextCompat;
 import androidx.core.content.FileProvider;
 import androidx.core.content.res.ResourcesCompat;
-import androidx.core.graphics.TypefaceCompat;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.core.view.GravityCompat;
 import androidx.core.view.OnApplyWindowInsetsListener;
@@ -129,6 +128,7 @@ import io.lbry.browser.model.WalletBalance;
 import io.lbry.browser.model.WalletSync;
 import io.lbry.browser.model.lbryinc.Reward;
 import io.lbry.browser.model.lbryinc.Subscription;
+import io.lbry.browser.tasks.GenericTaskHandler;
 import io.lbry.browser.tasks.claim.ClaimListResultHandler;
 import io.lbry.browser.tasks.claim.ClaimListTask;
 import io.lbry.browser.tasks.lbryinc.ClaimRewardTask;
@@ -143,6 +143,7 @@ import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask;
 import io.lbry.browser.tasks.wallet.SyncApplyTask;
 import io.lbry.browser.tasks.wallet.SyncGetTask;
 import io.lbry.browser.tasks.wallet.SyncSetTask;
+import io.lbry.browser.tasks.wallet.UnlockTipsTask;
 import io.lbry.browser.tasks.wallet.WalletBalanceTask;
 import io.lbry.browser.ui.BaseFragment;
 import io.lbry.browser.ui.channel.ChannelFormFragment;
@@ -182,6 +183,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
     private boolean inPictureInPictureMode;
     @Getter
     private boolean inFullscreenMode;
+    // make tip unlock a global operation
+    @Getter
+    private boolean unlockingTips;
+
     public static SimpleExoPlayer appPlayer;
     public static Cache playerCache;
     public static boolean playerReassigned;
@@ -2812,6 +2817,41 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
         task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
+    public void unlockTips() {
+        if (unlockingTips) {
+            return;
+        }
+        UnlockTipsTask task = new UnlockTipsTask(new GenericTaskHandler() {
+            @Override
+            public void beforeStart() {
+                unlockingTips = true;
+            }
+
+            @Override
+            public void onSuccess() {
+                unlockingTips = false;
+                for (Fragment fragment : openNavFragments.values()) {
+                    if (fragment instanceof WalletFragment) {
+                        ((WalletFragment) fragment).checkTips(true);
+                    }
+                }
+            }
+
+            @Override
+            public void onError(Exception error) {
+                unlockingTips = false;
+                for (Fragment fragment : openNavFragments.values()) {
+                    if (fragment instanceof WalletFragment) {
+                        ((WalletFragment) fragment).checkTips();
+                    }
+                }
+                // fail silently?
+                //showError(error.getMessage());
+            }
+        });
+        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
+
     private void checkSyncedWallet() {
         String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE);
         // Just check if the current user has a synced wallet, no need to do anything else here
diff --git a/app/src/main/java/io/lbry/browser/model/WalletBalance.java b/app/src/main/java/io/lbry/browser/model/WalletBalance.java
index 993a0c31..c2102b70 100644
--- a/app/src/main/java/io/lbry/browser/model/WalletBalance.java
+++ b/app/src/main/java/io/lbry/browser/model/WalletBalance.java
@@ -1,7 +1,10 @@
 package io.lbry.browser.model;
 
+import org.json.JSONObject;
+
 import java.math.BigDecimal;
 
+import io.lbry.browser.utils.Helper;
 import lombok.Data;
 
 @Data
@@ -21,4 +24,18 @@ public class WalletBalance {
         tips = new BigDecimal(0);
         total = new BigDecimal(0);
     }
+
+    public static WalletBalance fromJSONObject(JSONObject json) {
+        WalletBalance balance = new WalletBalance();
+        JSONObject reservedSubtotals = Helper.getJSONObject("reserved_subtotals", json);
+        balance.setAvailable(new BigDecimal(Helper.getJSONString("available", "0", json)));
+        balance.setReserved(new BigDecimal(Helper.getJSONString("reserved", "0", json)));
+        balance.setTotal(new BigDecimal(Helper.getJSONString("total", "0", json)));
+        if (reservedSubtotals != null) {
+            balance.setClaims(new BigDecimal(Helper.getJSONString("claims", "0", reservedSubtotals)));
+            balance.setSupports(new BigDecimal(Helper.getJSONString("supports", "0", reservedSubtotals)));
+            balance.setTips(new BigDecimal(Helper.getJSONString("tips", "0", reservedSubtotals)));
+        }
+        return balance;
+    }
 }
diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/UnlockTipsTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/UnlockTipsTask.java
new file mode 100644
index 00000000..a3b53298
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/wallet/UnlockTipsTask.java
@@ -0,0 +1,76 @@
+package io.lbry.browser.tasks.wallet;
+
+import android.os.AsyncTask;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.lbry.browser.exceptions.ApiCallException;
+import io.lbry.browser.model.WalletBalance;
+import io.lbry.browser.tasks.GenericTaskHandler;
+import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.Lbry;
+
+public class UnlockTipsTask extends AsyncTask<Void, Void, Boolean> {
+
+    private GenericTaskHandler handler;
+    private Exception error;
+
+    public UnlockTipsTask(GenericTaskHandler handler) {
+        this.handler = handler;
+    }
+
+    public Boolean doInBackground(Void... params) {
+
+        List<String> txids = new ArrayList<>();
+        List<String> claimIds = new ArrayList<>();
+
+        try {
+            Map<String, Object> options = new HashMap<>();
+            options.put("type", "support");
+            options.put("is_not_my_input", true);
+            options.put("is_my_output", true);
+            JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_TXO_LIST, options);
+            if (result.has("items") && !result.isNull("items")) {
+                JSONArray items = result.getJSONArray("items");
+                for (int i = 0; i < items.length(); i++) {
+                    JSONObject item = items.getJSONObject(i);
+                    String txid = Helper.getJSONString("txid", null, item);
+                    String claimId = Helper.getJSONString("claim_id", null, item);
+                    if (!Helper.isNullOrEmpty(txid) && !Helper.isNullOrEmpty(claimId)) {
+                        txids.add(txid);
+                        claimIds.add(claimId);
+                    }
+                }
+            }
+
+            if (txids.size() > 0 && txids.size() == claimIds.size()) {
+                options = new HashMap<>();
+                options.put("txid", txids);
+                options.put("claim_id", claimIds);
+                Lbry.genericApiCall(Lbry.METHOD_TXO_SPEND, options);
+            }
+
+            return true;
+        } catch (ApiCallException | ClassCastException | JSONException ex) {
+            error = ex;
+            return false;
+        }
+    }
+
+    protected void onPostExecute(Boolean result) {
+        if (handler != null) {
+            if (result) {
+                handler.onSuccess();
+            } else {
+                handler.onError(error);
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java
index 09d26c42..53eb94e8 100644
--- a/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java
+++ b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java
@@ -23,16 +23,7 @@ public class WalletBalanceTask extends AsyncTask<Void, Void, WalletBalance> {
        WalletBalance balance = new WalletBalance();
         try {
             JSONObject json = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_BALANCE);
-            JSONObject reservedSubtotals = Helper.getJSONObject("reserved_subtotals", json);
-
-            balance.setAvailable(new BigDecimal(Helper.getJSONString("available", "0", json)));
-            balance.setReserved(new BigDecimal(Helper.getJSONString("reserved", "0", json)));
-            balance.setTotal(new BigDecimal(Helper.getJSONString("total", "0", json)));
-            if (reservedSubtotals != null) {
-                balance.setClaims(new BigDecimal(Helper.getJSONString("claims", "0", reservedSubtotals)));
-                balance.setSupports(new BigDecimal(Helper.getJSONString("supports", "0", reservedSubtotals)));
-                balance.setTips(new BigDecimal(Helper.getJSONString("tips", "0", reservedSubtotals)));
-            }
+            balance = WalletBalance.fromJSONObject(json);
         } catch (ApiCallException | ClassCastException ex) {
             error = ex;
             return null;
diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java
index 24a3e2b9..95a84eb6 100644
--- a/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java
+++ b/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java
@@ -3,6 +3,7 @@ package io.lbry.browser.ui.wallet;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.SharedPreferences;
 import android.graphics.Color;
 import android.os.AsyncTask;
@@ -17,6 +18,7 @@ import android.widget.ProgressBar;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
 import androidx.core.content.ContextCompat;
 import androidx.preference.PreferenceManager;
 import androidx.recyclerview.widget.DividerItemDecoration;
@@ -66,6 +68,9 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
     private TextView textSupportsBalance;
     private ProgressBar walletSendProgress;
 
+    private TextView linkUnlockTips;
+    private ProgressBar progressUnlockTips;
+
     private View loadingRecentContainer;
     private View inlineBalanceContainer;
     private TextView textWalletInlineBalance;
@@ -114,6 +119,9 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
         textSupportsBalance = root.findViewById(R.id.wallet_balance_staked_supports);
         textWalletHintSyncStatus = root.findViewById(R.id.wallet_hint_sync_status);
 
+        linkUnlockTips = root.findViewById(R.id.wallet_unlock_tips_link);
+        progressUnlockTips = root.findViewById(R.id.wallet_unlock_tips_progress);
+
         recentTransactionsList = root.findViewById(R.id.wallet_recent_transactions_list);
         linkViewAll = root.findViewById(R.id.wallet_link_view_all);
         textNoRecentTransactions = root.findViewById(R.id.wallet_no_recent_transactions);
@@ -242,6 +250,24 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
         itemDecoration.setDrawable(ContextCompat.getDrawable(context, R.drawable.thin_divider));
         recentTransactionsList.addItemDecoration(itemDecoration);
 
+        linkUnlockTips.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (context != null) {
+                    AlertDialog.Builder builder = new AlertDialog.Builder(context).
+                            setTitle(R.string.unlock_tips).
+                            setMessage(R.string.confirm_unlock_tips)
+                            .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialogInterface, int i) {
+                                    unlockTips();
+                                }
+                            }).setNegativeButton(R.string.no, null);
+                    builder.show();
+                }
+            }
+        });
+
         textEarnMoreTips.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
@@ -492,6 +518,7 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
 
         checkReceiveAddress();
         checkRewardsDriver();
+        checkTips();
         fetchRecentTransactions();
     }
 
@@ -535,9 +562,37 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
             Helper.setViewText(textTipsBalanceUSD, String.format("≈$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(tipsUsdBalance)));
         }
 
+        checkTips();
         checkRewardsDriver();
     }
 
+    private void unlockTips() {
+        Context context = getContext();
+        if (context instanceof MainActivity) {
+            linkUnlockTips.setVisibility(View.GONE);
+            progressUnlockTips.setVisibility(View.VISIBLE);
+            ((MainActivity) context).unlockTips();
+        }
+    }
+
+    public void checkTips() {
+        checkTips(false);
+    }
+
+    public void checkTips(boolean forceHideLink) {
+        WalletBalance walletBalance = Lbry.walletBalance;
+        double tipBalance = walletBalance == null ? 0 : walletBalance.getTips().doubleValue();
+        boolean unlocking = false;
+        Context context = getContext();
+        if (context instanceof MainActivity) {
+            MainActivity activity = (MainActivity) context;
+            unlocking = activity.isUnlockingTips();
+        }
+
+        Helper.setViewVisibility(linkUnlockTips, !forceHideLink && tipBalance > 0 && !unlocking ? View.VISIBLE : View.GONE);
+        Helper.setViewVisibility(progressUnlockTips, unlocking ? View.VISIBLE : View.GONE);
+    }
+
     private void checkRewardsDriver() {
         // check rewards driver
         Context ctx = getContext();
diff --git a/app/src/main/java/io/lbry/browser/utils/Lbry.java b/app/src/main/java/io/lbry/browser/utils/Lbry.java
index ec2ed59d..7f6df825 100644
--- a/app/src/main/java/io/lbry/browser/utils/Lbry.java
+++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java
@@ -88,6 +88,9 @@ public final class Lbry {
     public static final String METHOD_PREFERENCE_GET = "preference_get";
     public static final String METHOD_PREFERENCE_SET = "preference_set";
 
+    public static final String METHOD_TXO_LIST = "txo_list";
+    public static final String METHOD_TXO_SPEND = "txo_spend";
+
     public static final String METHOD_CHANNEL_ABANDON = "channel_abandon";
     public static final String METHOD_CHANNEL_CREATE = "channel_create";
     public static final String METHOD_CHANNEL_UPDATE = "channel_update";
diff --git a/app/src/main/res/layout/card_wallet_balance.xml b/app/src/main/res/layout/card_wallet_balance.xml
index 3468bdad..8e07d85f 100644
--- a/app/src/main/res/layout/card_wallet_balance.xml
+++ b/app/src/main/res/layout/card_wallet_balance.xml
@@ -160,12 +160,41 @@
                         android:textFontWeight="300"
                         android:textSize="14sp" />
 
-                    <TextView
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:fontFamily="@font/inter"
-                        android:text="@string/in_tips"
-                        android:textSize="14sp" />
+                    <RelativeLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="16dp">
+                        <TextView
+                            android:id="@+id/wallet_in_tips_label"
+                            android:layout_centerVertical="true"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:fontFamily="@font/inter"
+                            android:text="@string/in_tips"
+                            android:textSize="14sp" />
+                        <TextView
+                            android:id="@+id/wallet_unlock_tips_link"
+                            android:background="?attr/selectableItemBackground"
+                            android:clickable="true"
+                            android:layout_centerVertical="true"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_toRightOf="@id/wallet_in_tips_label"
+                            android:fontFamily="@font/inter"
+                            android:layout_marginLeft="24dp"
+                            android:text="@string/unlock"
+                            android:textColor="@color/lbryGreen"
+                            android:textSize="14sp"
+                            android:visibility="gone" />
+                        <ProgressBar
+                            android:id="@+id/wallet_unlock_tips_progress"
+                            android:layout_centerVertical="true"
+                            android:layout_width="16dp"
+                            android:layout_height="16dp"
+                            android:layout_marginLeft="24dp"
+                            android:layout_toRightOf="@id/wallet_in_tips_label"
+                            android:visibility="gone" />
+
+                    </RelativeLayout>
 
                     <TextView
                         android:id="@+id/wallet_hint_earn_more_tips"