add option to unlock all tips
This commit is contained in:
parent
540a841255
commit
4bb9f5cb43
7 changed files with 228 additions and 17 deletions
|
@ -66,7 +66,6 @@ import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.core.content.res.ResourcesCompat;
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
import androidx.core.graphics.TypefaceCompat;
|
|
||||||
import androidx.core.graphics.drawable.DrawableCompat;
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
import androidx.core.view.GravityCompat;
|
import androidx.core.view.GravityCompat;
|
||||||
import androidx.core.view.OnApplyWindowInsetsListener;
|
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.WalletSync;
|
||||||
import io.lbry.browser.model.lbryinc.Reward;
|
import io.lbry.browser.model.lbryinc.Reward;
|
||||||
import io.lbry.browser.model.lbryinc.Subscription;
|
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.ClaimListResultHandler;
|
||||||
import io.lbry.browser.tasks.claim.ClaimListTask;
|
import io.lbry.browser.tasks.claim.ClaimListTask;
|
||||||
import io.lbry.browser.tasks.lbryinc.ClaimRewardTask;
|
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.SyncApplyTask;
|
||||||
import io.lbry.browser.tasks.wallet.SyncGetTask;
|
import io.lbry.browser.tasks.wallet.SyncGetTask;
|
||||||
import io.lbry.browser.tasks.wallet.SyncSetTask;
|
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.tasks.wallet.WalletBalanceTask;
|
||||||
import io.lbry.browser.ui.BaseFragment;
|
import io.lbry.browser.ui.BaseFragment;
|
||||||
import io.lbry.browser.ui.channel.ChannelFormFragment;
|
import io.lbry.browser.ui.channel.ChannelFormFragment;
|
||||||
|
@ -182,6 +183,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
||||||
private boolean inPictureInPictureMode;
|
private boolean inPictureInPictureMode;
|
||||||
@Getter
|
@Getter
|
||||||
private boolean inFullscreenMode;
|
private boolean inFullscreenMode;
|
||||||
|
// make tip unlock a global operation
|
||||||
|
@Getter
|
||||||
|
private boolean unlockingTips;
|
||||||
|
|
||||||
public static SimpleExoPlayer appPlayer;
|
public static SimpleExoPlayer appPlayer;
|
||||||
public static Cache playerCache;
|
public static Cache playerCache;
|
||||||
public static boolean playerReassigned;
|
public static boolean playerReassigned;
|
||||||
|
@ -2812,6 +2817,41 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
|
||||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
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() {
|
private void checkSyncedWallet() {
|
||||||
String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE);
|
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
|
// Just check if the current user has a synced wallet, no need to do anything else here
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package io.lbry.browser.model;
|
package io.lbry.browser.model;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@ -21,4 +24,18 @@ public class WalletBalance {
|
||||||
tips = new BigDecimal(0);
|
tips = new BigDecimal(0);
|
||||||
total = 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,16 +23,7 @@ public class WalletBalanceTask extends AsyncTask<Void, Void, WalletBalance> {
|
||||||
WalletBalance balance = new WalletBalance();
|
WalletBalance balance = new WalletBalance();
|
||||||
try {
|
try {
|
||||||
JSONObject json = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_BALANCE);
|
JSONObject json = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_BALANCE);
|
||||||
JSONObject reservedSubtotals = Helper.getJSONObject("reserved_subtotals", json);
|
balance = WalletBalance.fromJSONObject(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)));
|
|
||||||
}
|
|
||||||
} catch (ApiCallException | ClassCastException ex) {
|
} catch (ApiCallException | ClassCastException ex) {
|
||||||
error = ex;
|
error = ex;
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.lbry.browser.ui.wallet;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
@ -17,6 +18,7 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
@ -66,6 +68,9 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
|
||||||
private TextView textSupportsBalance;
|
private TextView textSupportsBalance;
|
||||||
private ProgressBar walletSendProgress;
|
private ProgressBar walletSendProgress;
|
||||||
|
|
||||||
|
private TextView linkUnlockTips;
|
||||||
|
private ProgressBar progressUnlockTips;
|
||||||
|
|
||||||
private View loadingRecentContainer;
|
private View loadingRecentContainer;
|
||||||
private View inlineBalanceContainer;
|
private View inlineBalanceContainer;
|
||||||
private TextView textWalletInlineBalance;
|
private TextView textWalletInlineBalance;
|
||||||
|
@ -114,6 +119,9 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
|
||||||
textSupportsBalance = root.findViewById(R.id.wallet_balance_staked_supports);
|
textSupportsBalance = root.findViewById(R.id.wallet_balance_staked_supports);
|
||||||
textWalletHintSyncStatus = root.findViewById(R.id.wallet_hint_sync_status);
|
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);
|
recentTransactionsList = root.findViewById(R.id.wallet_recent_transactions_list);
|
||||||
linkViewAll = root.findViewById(R.id.wallet_link_view_all);
|
linkViewAll = root.findViewById(R.id.wallet_link_view_all);
|
||||||
textNoRecentTransactions = root.findViewById(R.id.wallet_no_recent_transactions);
|
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));
|
itemDecoration.setDrawable(ContextCompat.getDrawable(context, R.drawable.thin_divider));
|
||||||
recentTransactionsList.addItemDecoration(itemDecoration);
|
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() {
|
textEarnMoreTips.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
@ -492,6 +518,7 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
|
||||||
|
|
||||||
checkReceiveAddress();
|
checkReceiveAddress();
|
||||||
checkRewardsDriver();
|
checkRewardsDriver();
|
||||||
|
checkTips();
|
||||||
fetchRecentTransactions();
|
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)));
|
Helper.setViewText(textTipsBalanceUSD, String.format("≈$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(tipsUsdBalance)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkTips();
|
||||||
checkRewardsDriver();
|
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() {
|
private void checkRewardsDriver() {
|
||||||
// check rewards driver
|
// check rewards driver
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
|
|
@ -88,6 +88,9 @@ public final class Lbry {
|
||||||
public static final String METHOD_PREFERENCE_GET = "preference_get";
|
public static final String METHOD_PREFERENCE_GET = "preference_get";
|
||||||
public static final String METHOD_PREFERENCE_SET = "preference_set";
|
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_ABANDON = "channel_abandon";
|
||||||
public static final String METHOD_CHANNEL_CREATE = "channel_create";
|
public static final String METHOD_CHANNEL_CREATE = "channel_create";
|
||||||
public static final String METHOD_CHANNEL_UPDATE = "channel_update";
|
public static final String METHOD_CHANNEL_UPDATE = "channel_update";
|
||||||
|
|
|
@ -160,12 +160,41 @@
|
||||||
android:textFontWeight="300"
|
android:textFontWeight="300"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<TextView
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="16dp">
|
||||||
android:fontFamily="@font/inter"
|
<TextView
|
||||||
android:text="@string/in_tips"
|
android:id="@+id/wallet_in_tips_label"
|
||||||
android:textSize="14sp" />
|
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
|
<TextView
|
||||||
android:id="@+id/wallet_hint_earn_more_tips"
|
android:id="@+id/wallet_hint_earn_more_tips"
|
||||||
|
|
Loading…
Reference in a new issue