From b4599ec7c5e571d52228430f16fe9876d6e508b1 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Mon, 11 May 2020 19:38:21 +0100 Subject: [PATCH] add Invites page --- .../java/io/lbry/browser/MainActivity.java | 32 +- .../io/lbry/browser/VerificationActivity.java | 13 +- .../adapter/InlineChannelSpinnerAdapter.java | 70 +++ .../browser/adapter/InviteeListAdapter.java | 92 +++ .../browser/adapter/RewardListAdapter.java | 5 +- .../java/io/lbry/browser/model/Claim.java | 1 + .../io/lbry/browser/model/NavMenuItem.java | 1 + .../lbry/browser/model/lbryinc/Invitee.java | 11 + .../{ => lbryinc}/ChannelSubscribeTask.java | 2 +- .../tasks/{ => lbryinc}/ClaimRewardTask.java | 2 +- .../{ => lbryinc}/FetchCurrentUserTask.java | 2 +- .../tasks/lbryinc/FetchInviteStatusTask.java | 72 +++ .../tasks/lbryinc/FetchReferralCodeTask.java | 57 ++ .../tasks/{ => lbryinc}/FetchRewardsTask.java | 2 +- .../{ => lbryinc}/FetchSubscriptionsTask.java | 2 +- .../tasks/lbryinc/InviteByEmailTask.java | 55 ++ .../ui/channel/ChannelFormFragment.java | 6 +- .../browser/ui/channel/ChannelFragment.java | 3 +- .../ui/following/FollowingFragment.java | 4 +- .../browser/ui/wallet/InvitesFragment.java | 553 ++++++++++++++++++ .../browser/ui/wallet/RewardsFragment.java | 9 +- .../java/io/lbry/browser/utils/Lbryio.java | 2 +- ...allet_address.xml => bg_copyable_text.xml} | 0 .../main/res/layout/card_invites_by_email.xml | 62 ++ .../main/res/layout/card_invites_by_link.xml | 101 ++++ .../main/res/layout/card_invites_history.xml | 45 ++ .../layout/card_wallet_receive_credits.xml | 2 +- .../layout/container_inline_channel_form.xml | 119 ++++ app/src/main/res/layout/fragment_invites.xml | 85 +++ app/src/main/res/layout/fragment_rewards.xml | 1 - app/src/main/res/layout/list_item_invitee.xml | 37 ++ app/src/main/res/layout/list_item_reward.xml | 8 +- .../main/res/layout/spinner_item_channel.xml | 14 + app/src/main/res/values/strings.xml | 23 + 34 files changed, 1451 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java create mode 100644 app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java create mode 100644 app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java rename app/src/main/java/io/lbry/browser/tasks/{ => lbryinc}/ChannelSubscribeTask.java (98%) rename app/src/main/java/io/lbry/browser/tasks/{ => lbryinc}/ClaimRewardTask.java (98%) rename app/src/main/java/io/lbry/browser/tasks/{ => lbryinc}/FetchCurrentUserTask.java (96%) create mode 100644 app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java create mode 100644 app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java rename app/src/main/java/io/lbry/browser/tasks/{ => lbryinc}/FetchRewardsTask.java (98%) rename app/src/main/java/io/lbry/browser/tasks/{ => lbryinc}/FetchSubscriptionsTask.java (98%) create mode 100644 app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java create mode 100644 app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java rename app/src/main/res/drawable/{bg_wallet_address.xml => bg_copyable_text.xml} (100%) create mode 100644 app/src/main/res/layout/card_invites_by_email.xml create mode 100644 app/src/main/res/layout/card_invites_by_link.xml create mode 100644 app/src/main/res/layout/card_invites_history.xml create mode 100644 app/src/main/res/layout/container_inline_channel_form.xml create mode 100644 app/src/main/res/layout/fragment_invites.xml create mode 100644 app/src/main/res/layout/list_item_invitee.xml create mode 100644 app/src/main/res/layout/spinner_item_channel.xml diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index 9cd4530c..2e40a17a 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -65,7 +65,6 @@ import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.net.ConnectException; -import java.nio.channels.Channel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -95,7 +94,7 @@ import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.ClaimListResultHandler; import io.lbry.browser.tasks.ClaimListTask; -import io.lbry.browser.tasks.FetchRewardsTask; +import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.MergeSubscriptionsTask; import io.lbry.browser.tasks.ResolveTask; @@ -115,6 +114,7 @@ import io.lbry.browser.ui.following.FollowingFragment; import io.lbry.browser.ui.search.SearchFragment; import io.lbry.browser.ui.settings.SettingsFragment; import io.lbry.browser.ui.allcontent.AllContentFragment; +import io.lbry.browser.ui.wallet.InvitesFragment; import io.lbry.browser.ui.wallet.RewardsFragment; import io.lbry.browser.ui.wallet.WalletFragment; import io.lbry.browser.utils.Helper; @@ -229,6 +229,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private String pendingChannelUrl; private boolean pendingFollowingReload; + // startup stages (to be able to determine how far a user made it if startup fails) + // and display a more useful message for troubleshooting + private static final int STARTUP_STAGE_INSTALL_ID_LOADED = 1; + private static final int STARTUP_STAGE_KNOWN_TAGS_LOADED = 2; + private static final int STARTUP_STAGE_EXCHANGE_RATE_LOADED = 3; + private static final int STARTUP_STAGE_USER_AUTHENTICATED = 4; + private static final int STARTUP_STAGE_NEW_INSTALL_DONE = 5; + private static final int STARTUP_STAGE_SUBSCRIPTIONS_LOADED = 6; + private static final int STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED = 7; + private final List supportedMenuItemIds = Arrays.asList( // find content NavMenuItem.ID_ITEM_FOLLOWING, @@ -241,7 +251,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener // wallet NavMenuItem.ID_ITEM_WALLET, NavMenuItem.ID_ITEM_REWARDS, - + NavMenuItem.ID_ITEM_INVITES, NavMenuItem.ID_ITEM_SETTINGS ); @@ -432,6 +442,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener case NavMenuItem.ID_ITEM_REWARDS: openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); break; + case NavMenuItem.ID_ITEM_INVITES: + openFragment(InvitesFragment.class, true, NavMenuItem.ID_ITEM_INVITES); + break; case NavMenuItem.ID_ITEM_SETTINGS: openFragment(SettingsFragment.class, true, NavMenuItem.ID_ITEM_SETTINGS); @@ -1471,6 +1484,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener if (Lbryio.currentUser == null) { Lbryio.authenticate(context); } + if (Lbryio.currentUser == null) { + throw new Exception("Did not retrieve authenticated user."); + } + + Lbryio.newInstall(context); // (light) fetch subscriptions @@ -1546,8 +1564,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } if (Lbryio.totalUnclaimedRewardAmount > 0) { - ((TextView) findViewById(R.id.floating_reward_value)).setText(Helper.shortCurrencyFormat(Lbryio.totalUnclaimedRewardAmount)); - findViewById(R.id.floating_reward_container).setVisibility(View.VISIBLE); + showFloatingUnclaimedRewards(); } } @@ -1558,6 +1575,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + public void showFloatingUnclaimedRewards() { + ((TextView) findViewById(R.id.floating_reward_value)).setText(Helper.shortCurrencyFormat(Lbryio.totalUnclaimedRewardAmount)); + findViewById(R.id.floating_reward_container).setVisibility(View.VISIBLE); + } + private void checkUrlIntent(Intent intent) { if (intent != null) { Uri data = intent.getData(); diff --git a/app/src/main/java/io/lbry/browser/VerificationActivity.java b/app/src/main/java/io/lbry/browser/VerificationActivity.java index 65e0b9db..af6013aa 100644 --- a/app/src/main/java/io/lbry/browser/VerificationActivity.java +++ b/app/src/main/java/io/lbry/browser/VerificationActivity.java @@ -1,6 +1,5 @@ package io.lbry.browser; -import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.AsyncTask; @@ -8,27 +7,17 @@ import android.os.Bundle; import android.view.View; import androidx.fragment.app.FragmentActivity; -import androidx.viewpager.widget.ViewPager; import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.snackbar.Snackbar; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import io.lbry.browser.adapter.ClaimListAdapter; import io.lbry.browser.adapter.VerificationPagerAdapter; import io.lbry.browser.listener.SignInListener; import io.lbry.browser.listener.WalletSyncListener; -import io.lbry.browser.model.Claim; import io.lbry.browser.model.lbryinc.User; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimListTask; -import io.lbry.browser.tasks.FetchCurrentUserTask; -import io.lbry.browser.ui.channel.ChannelManagerFragment; -import io.lbry.browser.utils.Helper; -import io.lbry.browser.utils.Lbry; +import io.lbry.browser.tasks.lbryinc.FetchCurrentUserTask; import io.lbry.browser.utils.Lbryio; public class VerificationActivity extends FragmentActivity implements SignInListener, WalletSyncListener { diff --git a/app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java b/app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java new file mode 100644 index 00000000..2a2da905 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java @@ -0,0 +1,70 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.database.DataSetObserver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.Claim; + +public class InlineChannelSpinnerAdapter extends ArrayAdapter { + + private List channels; + private int layoutResourceId; + private LayoutInflater inflater; + + public InlineChannelSpinnerAdapter(Context context, int resource, List channels) { + super(context, resource, 0, channels); + inflater = LayoutInflater.from(context); + layoutResourceId = resource; + this.channels = new ArrayList<>(channels); + } + public void addPlaceholder(boolean includeAnonymous) { + Claim placeholder = new Claim(); + placeholder.setPlaceholder(true); + insert(placeholder, 0); + channels.add(0, placeholder); + + if (includeAnonymous) { + Claim anonymous = new Claim(); + anonymous.setPlaceholderAnonymous(true); + insert(anonymous, 1); + channels.add(1, anonymous); + } + } + + @Override + public View getDropDownView(int position, View view, ViewGroup parent) { + return createView(position, view, parent); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + return createView(position, view, parent); + } + private View createView(int position, View convertView, ViewGroup parent){ + View view = inflater.inflate(layoutResourceId, parent, false); + + Context context = getContext(); + Claim channel = getItem(position); + String name = channel.getName(); + if (channel.isPlaceholder()) { + name = context.getString(R.string.create_a_channel); + } else if (channel.isPlaceholderAnonymous()) { + name = context.getString(R.string.anonymous); + } + + TextView label = view.findViewById(R.id.channel_item_name); + label.setText(name); + + return view; + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java new file mode 100644 index 00000000..7ff066b0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java @@ -0,0 +1,92 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Typeface; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.lbryinc.Invitee; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import lombok.Setter; + +public class InviteeListAdapter extends RecyclerView.Adapter { + + private Context context; + private List items; + + public InviteeListAdapter(List invitees, Context context) { + this.context = context; + this.items = new ArrayList<>(invitees); + } + + public void clear() { + items.clear(); + notifyDataSetChanged(); + } + + public List getItems() { + return new ArrayList<>(items); + } + + public void addHeader() { + Invitee header = new Invitee(); + header.setHeader(true); + items.add(0, header); + } + + public void addInvitees(List Invitees) { + for (Invitee tx : Invitees) { + if (!items.contains(tx)) { + items.add(tx); + } + } + notifyDataSetChanged(); + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public InviteeListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_invitee, root, false); + return new InviteeListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(InviteeListAdapter.ViewHolder vh, int position) { + Invitee item = items.get(position); + vh.emailView.setText(item.isHeader() ? context.getString(R.string.email) : item.getEmail()); + vh.emailView.setTypeface(null, item.isHeader() ? Typeface.BOLD : Typeface.NORMAL); + + String rewardText = context.getString( + item.isInviteRewardClaimed() ? R.string.claimed : + (item.isInviteRewardClaimable() ? R.string.claimable : R.string.unclaimable)); + vh.rewardView.setText(item.isHeader() ? context.getString(R.string.reward) : rewardText); + vh.rewardView.setTypeface(null, item.isHeader() ? Typeface.BOLD : Typeface.NORMAL); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected TextView emailView; + protected TextView rewardView; + + public ViewHolder(View v) { + super(v); + emailView = v.findViewById(R.id.invitee_email); + rewardView = v.findViewById(R.id.invitee_reward); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java index 278631d2..e5ea6c4c 100644 --- a/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java @@ -138,14 +138,13 @@ public class RewardListAdapter extends RecyclerView.Adapter 7; vh.iconClaimed.setVisibility(reward.isClaimed() ? View.VISIBLE : View.INVISIBLE); vh.inputCustomCode.setVisibility(reward.isCustom() ? View.VISIBLE : View.GONE); vh.buttonClaimCustom.setVisibility(reward.isCustom() ? View.VISIBLE : View.GONE); vh.textTitle.setText(reward.getRewardTitle()); vh.textDescription.setText(reward.getRewardDescription()); - vh.upTo.setVisibility(reward.shouldDisplayRange() ? View.VISIBLE : View.INVISIBLE); + vh.upTo.setVisibility(reward.shouldDisplayRange() ? View.VISIBLE : View.GONE); vh.textLbcValue.setText(reward.isCustom() ? "?" : Helper.LBC_CURRENCY_FORMAT.format(Helper.parseDouble(reward.getDisplayAmount(), 0))); vh.textLinkTransaction.setVisibility(hasTransaction ? View.VISIBLE : View.GONE); vh.textLinkTransaction.setText(hasTransaction ? reward.getTransactionId().substring(0, 7) : null); diff --git a/app/src/main/java/io/lbry/browser/model/Claim.java b/app/src/main/java/io/lbry/browser/model/Claim.java index 444958b4..cab80fdc 100644 --- a/app/src/main/java/io/lbry/browser/model/Claim.java +++ b/app/src/main/java/io/lbry/browser/model/Claim.java @@ -51,6 +51,7 @@ public class Claim { @EqualsAndHashCode.Include private boolean placeholder; + private boolean placeholderAnonymous; private boolean featured; private boolean unresolved; // used for featured private String address; diff --git a/app/src/main/java/io/lbry/browser/model/NavMenuItem.java b/app/src/main/java/io/lbry/browser/model/NavMenuItem.java index 67e1b8a7..53468b09 100644 --- a/app/src/main/java/io/lbry/browser/model/NavMenuItem.java +++ b/app/src/main/java/io/lbry/browser/model/NavMenuItem.java @@ -41,6 +41,7 @@ public class NavMenuItem { private boolean group; private int icon; private String title; + private String extraLabel; private String name; // same as title, but only as en lang for events private List items; diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java b/app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java new file mode 100644 index 00000000..2d84072d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java @@ -0,0 +1,11 @@ +package io.lbry.browser.model.lbryinc; + +import lombok.Data; + +@Data +public class Invitee { + private boolean header; + private String email; + private boolean inviteRewardClaimed; + private boolean inviteRewardClaimable; +} diff --git a/app/src/main/java/io/lbry/browser/tasks/ChannelSubscribeTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ChannelSubscribeTask.java similarity index 98% rename from app/src/main/java/io/lbry/browser/tasks/ChannelSubscribeTask.java rename to app/src/main/java/io/lbry/browser/tasks/lbryinc/ChannelSubscribeTask.java index e0f7b783..d58e7565 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ChannelSubscribeTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ChannelSubscribeTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.lbryinc; import android.content.Context; import android.database.sqlite.SQLiteDatabase; diff --git a/app/src/main/java/io/lbry/browser/tasks/ClaimRewardTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ClaimRewardTask.java similarity index 98% rename from app/src/main/java/io/lbry/browser/tasks/ClaimRewardTask.java rename to app/src/main/java/io/lbry/browser/tasks/lbryinc/ClaimRewardTask.java index 96bb76ec..3a48c8e8 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ClaimRewardTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ClaimRewardTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.lbryinc; import android.content.Context; import android.os.AsyncTask; diff --git a/app/src/main/java/io/lbry/browser/tasks/FetchCurrentUserTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchCurrentUserTask.java similarity index 96% rename from app/src/main/java/io/lbry/browser/tasks/FetchCurrentUserTask.java rename to app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchCurrentUserTask.java index 567734d0..3be8e03e 100644 --- a/app/src/main/java/io/lbry/browser/tasks/FetchCurrentUserTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchCurrentUserTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.lbryinc; import android.os.AsyncTask; diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java new file mode 100644 index 00000000..d56f7dbd --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java @@ -0,0 +1,72 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Invitee; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchInviteStatusTask extends AsyncTask> { + private FetchInviteStatusHandler handler; + private View progressView; + private Exception error; + + public FetchInviteStatusTask(View progressView, FetchInviteStatusHandler handler) { + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected List doInBackground(Void... params) { + List invitees = null; + try { + JSONObject status = (JSONObject) Lbryio.parseResponse(Lbryio.call("user", "invite_status", null, null)); + JSONArray inviteesArray = status.getJSONArray("invitees"); + invitees = new ArrayList<>(); + for (int i = 0; i < inviteesArray.length(); i++) { + JSONObject inviteeObject = inviteesArray.getJSONObject(i); + Invitee invitee = new Invitee(); + invitee.setEmail(Helper.getJSONString("email", null, inviteeObject)); + invitee.setInviteRewardClaimable(Helper.getJSONBoolean("invite_reward_claimable", false, inviteeObject)); + invitee.setInviteRewardClaimed(Helper.getJSONBoolean("invite_reward_claimed", false, inviteeObject)); + + if (!Helper.isNullOrEmpty(invitee.getEmail())) { + invitees.add(invitee); + } + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return invitees; + } + + protected void onPostExecute(List invitees) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (invitees != null) { + handler.onSuccess(invitees); + } else { + handler.onError(error); + } + } + } + + public interface FetchInviteStatusHandler { + void onSuccess(List invitees); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java new file mode 100644 index 00000000..af15d83a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java @@ -0,0 +1,57 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchReferralCodeTask extends AsyncTask { + private FetchReferralCodeHandler handler; + private View progressView; + private Exception error; + + public FetchReferralCodeTask(View progressView, FetchReferralCodeHandler handler) { + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected String doInBackground(Void... params) { + String referralCode = null; + try { + JSONArray results = (JSONArray) Lbryio.parseResponse(Lbryio.call("user_referral_code", "list", null, null)); + if (results.length() > 0) { + referralCode = results.getString(0); + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return referralCode; + } + + protected void onPostExecute(String referralCode) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (!Helper.isNullOrEmpty(referralCode)) { + handler.onSuccess(referralCode); + } else { + handler.onError(error); + } + } + } + + public interface FetchReferralCodeHandler { + void onSuccess(String referralCode); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/FetchRewardsTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchRewardsTask.java similarity index 98% rename from app/src/main/java/io/lbry/browser/tasks/FetchRewardsTask.java rename to app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchRewardsTask.java index ef353303..b9f18067 100644 --- a/app/src/main/java/io/lbry/browser/tasks/FetchRewardsTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchRewardsTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.lbryinc; import android.os.AsyncTask; import android.view.View; diff --git a/app/src/main/java/io/lbry/browser/tasks/FetchSubscriptionsTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchSubscriptionsTask.java similarity index 98% rename from app/src/main/java/io/lbry/browser/tasks/FetchSubscriptionsTask.java rename to app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchSubscriptionsTask.java index 2dce437c..d70aece3 100644 --- a/app/src/main/java/io/lbry/browser/tasks/FetchSubscriptionsTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchSubscriptionsTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.lbryinc; import android.content.Context; import android.database.sqlite.SQLiteDatabase; diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java new file mode 100644 index 00000000..e2709862 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java @@ -0,0 +1,55 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class InviteByEmailTask extends AsyncTask { + private String email; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public InviteByEmailTask(String email, View progressView, GenericTaskHandler handler) { + this.email = email; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("email", email); + Lbryio.parseResponse(Lbryio.call("user", "invite", options, Helper.METHOD_POST, null)); + } catch (LbryioRequestException | LbryioResponseException ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java index ca763edb..6999fdc0 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java @@ -3,6 +3,7 @@ package io.lbry.browser.ui.channel; import android.Manifest; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -342,13 +343,10 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - - private void showError(String message) { Context context = getContext(); if (context != null) { - Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint( - ContextCompat.getColor(context, R.color.red)).show(); + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); } } diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java index 934eb96b..7c8e4023 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java @@ -33,10 +33,9 @@ import io.lbry.browser.R; import io.lbry.browser.dialog.SendTipDialogFragment; import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.listener.FetchChannelsListener; -import io.lbry.browser.listener.SdkStatusListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.lbryinc.Subscription; -import io.lbry.browser.tasks.ChannelSubscribeTask; +import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; import io.lbry.browser.tasks.ClaimListResultHandler; import io.lbry.browser.tasks.ResolveTask; import io.lbry.browser.ui.BaseFragment; diff --git a/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java b/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java index 80c1142e..98e262f3 100644 --- a/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java @@ -37,10 +37,10 @@ import io.lbry.browser.dialog.DiscoverDialogFragment; import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.lbryinc.Subscription; -import io.lbry.browser.tasks.ChannelSubscribeTask; +import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; import io.lbry.browser.tasks.ClaimListResultHandler; import io.lbry.browser.tasks.ClaimSearchTask; -import io.lbry.browser.tasks.FetchSubscriptionsTask; +import io.lbry.browser.tasks.lbryinc.FetchSubscriptionsTask; import io.lbry.browser.tasks.ResolveTask; import io.lbry.browser.listener.ChannelItemSelectionListener; import io.lbry.browser.ui.BaseFragment; diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java new file mode 100644 index 00000000..6746964f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java @@ -0,0 +1,553 @@ +package io.lbry.browser.ui.wallet; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatSpinner; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.BuildConfig; +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.InlineChannelSpinnerAdapter; +import io.lbry.browser.adapter.InviteeListAdapter; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.model.lbryinc.Invitee; +import io.lbry.browser.tasks.ClaimListResultHandler; +import io.lbry.browser.tasks.ClaimListTask; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.content.ChannelCreateUpdateTask; +import io.lbry.browser.tasks.content.ClaimResultHandler; +import io.lbry.browser.tasks.content.LogPublishTask; +import io.lbry.browser.tasks.lbryinc.FetchInviteStatusTask; +import io.lbry.browser.tasks.lbryinc.FetchReferralCodeTask; +import io.lbry.browser.tasks.lbryinc.InviteByEmailTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; + +public class InvitesFragment extends BaseFragment implements SdkStatusListener, WalletBalanceListener { + + private static final String INVITE_LINK_FORMAT = "https://lbry.tv/$/invite/%s:%s"; + + private boolean fetchingChannels; + private View layoutAccountDriver; + private View layoutSdkInitializing; + private TextView textLearnMoreLink; + private MaterialButton buttonGetStarted; + + private View buttonCopyInviteLink; + private TextView textInviteLink; + private TextInputLayout layoutInputEmail; + private TextInputEditText inputEmail; + private MaterialButton buttonInviteByEmail; + + private RecyclerView inviteHistoryList; + private InviteeListAdapter inviteHistoryAdapter; + private InlineChannelSpinnerAdapter channelSpinnerAdapter; + private AppCompatSpinner channelSpinner; + private View progressLoadingChannels; + private View progressLoadingInviteByEmail; + private View progressLoadingStatus; + + private View inlineChannelCreator; + private TextInputEditText inlineChannelCreatorInputName; + private TextInputEditText inlineChannelCreatorInputDeposit; + private View inlineChannelCreatorInlineBalance; + private TextView inlineChannelCreatorInlineBalanceValue; + private View inlineChannelCreatorCancelLink; + private View inlineChannelCreatorProgress; + private MaterialButton inlineChannelCreatorCreateButton; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_invites, container, false); + + layoutAccountDriver = root.findViewById(R.id.invites_account_driver_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + textLearnMoreLink = root.findViewById(R.id.invites_account_driver_learn_more); + buttonGetStarted = root.findViewById(R.id.invites_get_started_button); + + textInviteLink = root.findViewById(R.id.invites_invite_link); + buttonCopyInviteLink = root.findViewById(R.id.invites_copy_invite_link); + layoutInputEmail = root.findViewById(R.id.invites_email_input_layout); + inputEmail = root.findViewById(R.id.invites_email_input); + buttonInviteByEmail = root.findViewById(R.id.invites_email_button); + + progressLoadingChannels = root.findViewById(R.id.invites_loading_channels_progress); + progressLoadingInviteByEmail = root.findViewById(R.id.invites_loading_invite_by_email_progress); + progressLoadingStatus = root.findViewById(R.id.invites_loading_status_progress); + + inviteHistoryList = root.findViewById(R.id.invite_history_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + inviteHistoryList.setLayoutManager(llm); + + channelSpinner = root.findViewById(R.id.invites_channel_spinner); + + inlineChannelCreator = root.findViewById(R.id.container_inline_channel_form_create); + inlineChannelCreatorInputName = root.findViewById(R.id.inline_channel_form_input_name); + inlineChannelCreatorInputDeposit = root.findViewById(R.id.inline_channel_form_input_deposit); + inlineChannelCreatorInlineBalance = root.findViewById(R.id.inline_channel_form_inline_balance_container); + inlineChannelCreatorInlineBalanceValue = root.findViewById(R.id.inline_channel_form_inline_balance_value); + inlineChannelCreatorProgress = root.findViewById(R.id.inline_channel_form_create_progress); + inlineChannelCreatorCancelLink = root.findViewById(R.id.inline_channel_form_cancel_link); + inlineChannelCreatorCreateButton = root.findViewById(R.id.inline_channel_form_create_button); + + initUi(); + + return root; + } + + private void initUi() { + layoutAccountDriver.setVisibility(Lbryio.isSignedIn() ? View.GONE : View.VISIBLE); + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + Helper.applyHtmlForTextView(textLearnMoreLink); + + inputEmail.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + layoutInputEmail.setHint(hasFocus ? getString(R.string.email) : + Helper.getValue(inputEmail.getText()).length() > 0 ? + getString(R.string.email) : getString(R.string.invite_email_placeholder)); + } + }); + inputEmail.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + Helper.setViewEnabled(buttonInviteByEmail, charSequence.length() > 0); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + buttonInviteByEmail.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String email = Helper.getValue(inputEmail.getText()); + if (email.indexOf("@") == -1) { + showError(getString(R.string.provide_valid_email)); + return; + } + + InviteByEmailTask task = new InviteByEmailTask(email, progressLoadingInviteByEmail, new GenericTaskHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(buttonInviteByEmail, false); + } + + @Override + public void onSuccess() { + Snackbar.make(getView(), getString(R.string.invite_sent_to, email), Snackbar.LENGTH_LONG).show(); + Helper.setViewText(inputEmail, null); + Helper.setViewEnabled(buttonInviteByEmail, true); + fetchInviteStatus(); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + Helper.setViewEnabled(buttonInviteByEmail, true); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + buttonGetStarted.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).simpleSignIn(); + } + } + }); + + if (Lbry.ownChannels != null) { + updateChannelList(Lbry.ownChannels); + } + + channelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + Object item = adapterView.getItemAtPosition(position); + if (item instanceof Claim) { + Claim claim = (Claim) item; + if (claim.isPlaceholder()) { + if (!fetchingChannels) { + showInlineChannelCreator(); + } + } else { + hideInlineChannelCreator(); + // build invite link + updateInviteLink(claim); + } + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + textInviteLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + copyInviteLink(); + } + }); + buttonCopyInviteLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + copyInviteLink(); + } + }); + + setupInlineChannelCreator( + inlineChannelCreator, + inlineChannelCreatorInputName, + inlineChannelCreatorInputDeposit, + inlineChannelCreatorInlineBalance, + inlineChannelCreatorInlineBalanceValue, + inlineChannelCreatorCancelLink, + inlineChannelCreatorCreateButton, + inlineChannelCreatorProgress + ); + } + + private void updateInviteLink(Claim claim) { + String link = String.format(INVITE_LINK_FORMAT, claim.getName(), claim.getClaimId()); + textInviteLink.setText(link); + } + private void copyInviteLink() { + Context context = getContext(); + if (context != null && textInviteLink != null) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData data = ClipData.newPlainText("inviteLink", textInviteLink.getText()); + clipboard.setPrimaryClip(data); + } + Snackbar.make(getView(), R.string.invite_link_copied, Snackbar.LENGTH_SHORT).show(); + } + + private void updateChannelList(List channels) { + if (channelSpinnerAdapter == null) { + Context context = getContext(); + channelSpinnerAdapter = new InlineChannelSpinnerAdapter(context, R.layout.spinner_item_channel, channels); + channelSpinnerAdapter.addPlaceholder(false); + channelSpinner.setAdapter(channelSpinnerAdapter); + channelSpinnerAdapter.notifyDataSetChanged(); + } else { + channelSpinnerAdapter.clear(); + channelSpinnerAdapter.addAll(channels); + channelSpinnerAdapter.addPlaceholder(false); + channelSpinnerAdapter.notifyDataSetChanged(); + } + + if (channelSpinnerAdapter.getCount() > 1) { + channelSpinner.setSelection(1); + } + } + + public void onResume() { + super.onResume(); + layoutAccountDriver.setVisibility(Lbryio.isSignedIn() ? View.GONE : View.VISIBLE); + + fetchInviteStatus(); + if (!Lbry.SDK_READY) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + activity.addWalletBalanceListener(this); + } + } else { + onSdkReady(); + } + } + + public void onSdkReady() { + Helper.setViewVisibility(layoutSdkInitializing, View.GONE); + fetchChannels(); + } + + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + activity.hideFloatingWalletBalance(); + } + } + + public void clearInputFocus() { + inputEmail.clearFocus(); + inlineChannelCreatorInputName.clearFocus(); + inlineChannelCreatorInputDeposit.clearFocus(); + } + + public void onStop() { + clearInputFocus(); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeSdkStatusListener(this); + activity.removeWalletBalanceListener(this); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + private void showInlineChannelCreator() { + Helper.setViewVisibility(inlineChannelCreator, View.VISIBLE); + } + private void hideInlineChannelCreator() { + Helper.setViewVisibility(inlineChannelCreator, View.GONE); + } + + private void fetchDefaultInviteLink() { + FetchReferralCodeTask task = new FetchReferralCodeTask(null, new FetchReferralCodeTask.FetchReferralCodeHandler() { + @Override + public void onSuccess(String referralCode) { + String previousLink = Helper.getValue(textInviteLink.getText()); + if (Helper.isNullOrEmpty(previousLink)) { + Helper.setViewText(textInviteLink, String.format("https://lbry.tv/$/invite/%s", referralCode)); + } + } + + @Override + public void onError(Exception error) { + // pass + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void disableChannelSpinner() { + Helper.setViewEnabled(channelSpinner, false); + hideInlineChannelCreator(); + } + private void enableChannelSpinner() { + Helper.setViewEnabled(channelSpinner, true); + Claim selectedClaim = (Claim) channelSpinner.getSelectedItem(); + if (selectedClaim != null) { + if (selectedClaim.isPlaceholder()) { + showInlineChannelCreator(); + } else { + hideInlineChannelCreator(); + } + } + } + + private void fetchChannels() { + fetchingChannels = true; + disableChannelSpinner(); + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, progressLoadingChannels, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = new ArrayList<>(claims); + updateChannelList(Lbry.ownChannels); + if (Lbry.ownChannels == null || Lbry.ownChannels.size() == 0) { + fetchDefaultInviteLink(); + } + enableChannelSpinner(); + fetchingChannels = false; + } + + @Override + public void onError(Exception error) { + fetchDefaultInviteLink(); + enableChannelSpinner(); + fetchingChannels = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void fetchInviteStatus() { + FetchInviteStatusTask task = new FetchInviteStatusTask(progressLoadingStatus, new FetchInviteStatusTask.FetchInviteStatusHandler() { + @Override + public void onSuccess(List invitees) { + if (inviteHistoryAdapter == null) { + inviteHistoryAdapter = new InviteeListAdapter(invitees, getContext()); + inviteHistoryAdapter.addHeader(); + inviteHistoryList.setAdapter(inviteHistoryAdapter); + } else { + inviteHistoryAdapter.addInvitees(invitees); + } + Helper.setViewVisibility(inviteHistoryList, + inviteHistoryAdapter == null || inviteHistoryAdapter.getItemCount() == 0 ? View.GONE : View.VISIBLE + ); + } + + @Override + public void onError(Exception error) { + Helper.setViewVisibility(inviteHistoryList, + inviteHistoryAdapter == null || inviteHistoryAdapter.getItemCount() == 0 ? View.GONE : View.VISIBLE + ); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void setupInlineChannelCreator( + View container, + TextInputEditText inputChannelName, + TextInputEditText inputDeposit, + View inlineBalanceView, + TextView inlineBalanceValue, + View linkCancel, + MaterialButton buttonCreate, + View progressView) { + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + Helper.setViewVisibility(inlineBalanceView, hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + linkCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Helper.setViewText(inputChannelName, null); + Helper.setViewText(inputDeposit, null); + Helper.setViewVisibility(container, View.GONE); + } + }); + + buttonCreate.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // validate deposit and channel name + String channelNameString = Helper.getValue(inputChannelName.getText()); + if (!channelNameString.startsWith("@")) { + channelNameString = String.format("@%s", channelNameString); + } + Claim claimToSave = new Claim(); + claimToSave.setName(channelNameString); + String channelName = claimToSave.getName().startsWith("@") ? claimToSave.getName().substring(1) : claimToSave.getName(); + String depositString = Helper.getValue(inputDeposit.getText()); + if (Helper.isNullOrEmpty(channelName)) { + showError(getString(R.string.please_enter_channel_name)); + return; + } + if (!LbryUri.isNameValid(channelName)) { + showError(getString(R.string.channel_name_invalid_characters)); + return; + } + if (Helper.channelExists(channelName)) { + showError(getString(R.string.channel_name_already_created)); + return; + } + + double depositAmount = 0; + try { + depositAmount = Double.valueOf(depositString); + } catch (NumberFormatException ex) { + // pass + showError(getString(R.string.please_enter_valid_deposit)); + return; + } + if (depositAmount == 0) { + String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT)); + showError(error); + return; + } + if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) { + showError(getString(R.string.deposit_more_than_balance)); + return; + } + + ChannelCreateUpdateTask task = new ChannelCreateUpdateTask( + claimToSave, new BigDecimal(depositString), false, progressView, new ClaimResultHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(inputChannelName, false); + Helper.setViewEnabled(inputDeposit, false); + Helper.setViewEnabled(buttonCreate, false); + Helper.setViewEnabled(linkCancel, false); + } + + @Override + public void onSuccess(Claim claimResult) { + if (!BuildConfig.DEBUG) { + LogPublishTask logPublishTask = new LogPublishTask(claimResult); + logPublishTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + // add the claim to the channel list and set it as the selected item + channelSpinnerAdapter.add(claimResult); + channelSpinner.setSelection(channelSpinnerAdapter.getCount() - 1); + + Helper.setViewEnabled(inputChannelName, true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(buttonCreate, true); + Helper.setViewEnabled(linkCancel, true); + } + + @Override + public void onError(Exception error) { + Helper.setViewEnabled(inputChannelName, true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(buttonCreate, true); + Helper.setViewEnabled(linkCancel, true); + showError(error.getMessage()); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + Helper.setViewText(inlineBalanceValue, Helper.shortCurrencyFormat(Lbry.walletBalance.getAvailable().doubleValue())); + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineChannelCreatorInlineBalanceValue != null) { + inlineChannelCreatorInlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void showError(String message) { + Context context = getContext(); + if (context != null) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java index a9bda4fd..b3aa0ec5 100644 --- a/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java @@ -27,8 +27,8 @@ import io.lbry.browser.R; import io.lbry.browser.adapter.RewardListAdapter; import io.lbry.browser.listener.SdkStatusListener; import io.lbry.browser.model.lbryinc.Reward; -import io.lbry.browser.tasks.ClaimRewardTask; -import io.lbry.browser.tasks.FetchRewardsTask; +import io.lbry.browser.tasks.lbryinc.ClaimRewardTask; +import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; @@ -130,6 +130,11 @@ public class RewardsFragment extends BaseFragment implements RewardListAdapter.R Lbryio.updateRewardsLists(rewards); updateUnclaimedRewardsValue(); + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).showFloatingUnclaimedRewards(); + } + if (adapter == null) { adapter = new RewardListAdapter(rewards, getContext()); adapter.setClickListener(RewardsFragment.this); diff --git a/app/src/main/java/io/lbry/browser/utils/Lbryio.java b/app/src/main/java/io/lbry/browser/utils/Lbryio.java index b6f0a595..6262d865 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbryio.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java @@ -250,7 +250,7 @@ public final class Lbryio { JSONObject response = (JSONObject) parseResponse(Lbryio.call("lbc", "exchange_rate", null)); LBCUSDRate = Helper.getJSONDouble("lbc_usd", 0, response); } catch (LbryioResponseException | LbryioRequestException | ClassCastException ex) { - + // pass } } diff --git a/app/src/main/res/drawable/bg_wallet_address.xml b/app/src/main/res/drawable/bg_copyable_text.xml similarity index 100% rename from app/src/main/res/drawable/bg_wallet_address.xml rename to app/src/main/res/drawable/bg_copyable_text.xml diff --git a/app/src/main/res/layout/card_invites_by_email.xml b/app/src/main/res/layout/card_invites_by_email.xml new file mode 100644 index 00000000..e11fe386 --- /dev/null +++ b/app/src/main/res/layout/card_invites_by_email.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_invites_by_link.xml b/app/src/main/res/layout/card_invites_by_link.xml new file mode 100644 index 00000000..82853634 --- /dev/null +++ b/app/src/main/res/layout/card_invites_by_link.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_invites_history.xml b/app/src/main/res/layout/card_invites_history.xml new file mode 100644 index 00000000..7965d4e6 --- /dev/null +++ b/app/src/main/res/layout/card_invites_history.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_wallet_receive_credits.xml b/app/src/main/res/layout/card_wallet_receive_credits.xml index 880a55e7..dc340eaf 100644 --- a/app/src/main/res/layout/card_wallet_receive_credits.xml +++ b/app/src/main/res/layout/card_wallet_receive_credits.xml @@ -40,7 +40,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:fontFamily="@font/inter" - android:background="@drawable/bg_wallet_address" + android:background="@drawable/bg_copyable_text" android:textSize="14sp" android:letterSpacing="0.05" android:paddingTop="8dp" diff --git a/app/src/main/res/layout/container_inline_channel_form.xml b/app/src/main/res/layout/container_inline_channel_form.xml new file mode 100644 index 00000000..bdacd126 --- /dev/null +++ b/app/src/main/res/layout/container_inline_channel_form.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_invites.xml b/app/src/main/res/layout/fragment_invites.xml new file mode 100644 index 00000000..67efda37 --- /dev/null +++ b/app/src/main/res/layout/fragment_invites.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rewards.xml b/app/src/main/res/layout/fragment_rewards.xml index f3790d60..46838086 100644 --- a/app/src/main/res/layout/fragment_rewards.xml +++ b/app/src/main/res/layout/fragment_rewards.xml @@ -4,7 +4,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/pageBackground"> - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_reward.xml b/app/src/main/res/layout/list_item_reward.xml index b6e6c4d4..fec233be 100644 --- a/app/src/main/res/layout/list_item_reward.xml +++ b/app/src/main/res/layout/list_item_reward.xml @@ -9,8 +9,8 @@ + android:visibility="gone" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 455e8b2a..f0406d18 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -247,6 +247,7 @@ You have not created a channel.\nStart now by creating a new channel! Create a channel + Create a channel... Edit channel Delete selection? Description @@ -278,6 +279,7 @@ The channel was successfully saved. The claim is pending publish on the blockchain. You will be able to access or edit the claim in a few moments. Pending + Create A minimum deposit of %1$s credit is required. A minimum deposit of %1$s credits is required. @@ -308,6 +310,27 @@ %1$s available credits + + LBRY Invite Program + You can earn extra credits for each person you invite to use LBRY. + <a href="https://lbry.com/faq/invites">Learn more</a>. + Invite Link + Share this link with friends (or enemies) and get credits when they join lbry.tv. + Your invite link + Customize invite link + Invite by Email + Invite someone you know by email and earn credits when they join lbry.tv. + imaginary@friend.com + Invite + Invite History + Earn credits for invite a friend, an enemy, a frenemy, or an enefriend. Everyone needs content freedom. + Reward + Claimed + Claimable + Unclaimable + Invite link copied. + Invite sent to %1$s +