diff --git a/app/build.gradle b/app/build.gradle index f7abd538..b2abe1a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,6 +76,8 @@ dependencies { implementation 'com.google.android:flexbox:2.0.1' + implementation 'com.hbb20:ccp:2.3.8' + compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index a587a4ad..9cd4530c 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -81,6 +81,7 @@ import io.lbry.browser.adapter.UrlSuggestionListAdapter; import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.dialog.ContentScopeDialogFragment; import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.listener.FetchChannelsListener; import io.lbry.browser.listener.SdkStatusListener; import io.lbry.browser.listener.WalletBalanceListener; import io.lbry.browser.model.Claim; @@ -90,8 +91,11 @@ import io.lbry.browser.model.Tag; import io.lbry.browser.model.UrlSuggestion; 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.ClaimListResultHandler; +import io.lbry.browser.tasks.ClaimListTask; +import io.lbry.browser.tasks.FetchRewardsTask; import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.MergeSubscriptionsTask; import io.lbry.browser.tasks.ResolveTask; @@ -111,6 +115,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.RewardsFragment; import io.lbry.browser.ui.wallet.WalletFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; @@ -125,8 +130,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static SimpleExoPlayer appPlayer; public static Claim nowPlayingClaim; + public static boolean startingFilePickerActivity = false; public static boolean startingShareActivity = false; public static boolean startingFileViewActivity = false; + public static boolean startingSignInFlowActivity = false; public static boolean mainActive = false; private boolean enteringPIPMode = false; @@ -152,6 +159,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static final int REQUEST_STORAGE_PERMISSION = 1001; public static final int REQUEST_SIMPLE_SIGN_IN = 2001; public static final int REQUEST_WALLET_SYNC_SIGN_IN = 2002; + public static final int REQUEST_REWARDS_VERIFY_SIGN_IN = 2003; public static final int REQUEST_FILE_PICKER = 5001; @@ -180,6 +188,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static final String PREFERENCE_KEY_INTERNAL_SKIP_WALLET_ACCOUNT = "io.lbry.browser.preference.internal.WalletSkipAccount"; public static final String PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED = "io.lbry.browser.preference.internal.WalletSyncEnabled"; public static final String PREFERENCE_KEY_INTERNAL_WALLET_RECEIVE_ADDRESS = "io.lbry.browser.preference.internal.WalletReceiveAddress"; + public static final String PREFERENCE_KEY_INTERNAL_REWARDS_NOT_INTERESTED = "io.lbry.browser.preference.internal.RewardsNotInterested"; private final int CHECK_SDK_READY_INTERVAL = 1000; @@ -210,6 +219,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private int selectedMenuItemId = -1; private List sdkStatusListeners; private List walletBalanceListeners; + private List fetchChannelsListeners; @Getter private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private boolean walletBalanceUpdateScheduled; @@ -230,6 +240,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener // wallet NavMenuItem.ID_ITEM_WALLET, + NavMenuItem.ID_ITEM_REWARDS, + + NavMenuItem.ID_ITEM_SETTINGS ); @@ -267,6 +280,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener openNavFragments = new HashMap<>(); sdkStatusListeners = new ArrayList<>(); walletBalanceListeners = new ArrayList<>(); + fetchChannelsListeners = new ArrayList<>(); sdkStatusListeners.add(this); @@ -379,6 +393,22 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener walletBalanceListeners.remove(listener); } + public void removeNavFragment(Class fragmentClass, int navItemId) { + String key = buildNavFragmentKey(fragmentClass, navItemId); + if (openNavFragments.containsKey(key)) { + openNavFragments.remove(key); + } + } + + public void addFetchChannelsListener(FetchChannelsListener listener) { + if (!fetchChannelsListeners.contains(listener)) { + fetchChannelsListeners.add(listener); + } + } + public void removeFetchChannelsListener(FetchChannelsListener listener) { + fetchChannelsListeners.remove(listener); + } + private void openSelectedMenuItem() { switch (selectedMenuItemId) { // TODO: reverse map lookup for class? @@ -399,6 +429,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener case NavMenuItem.ID_ITEM_WALLET: openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET); break; + case NavMenuItem.ID_ITEM_REWARDS: + openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); + break; case NavMenuItem.ID_ITEM_SETTINGS: openFragment(SettingsFragment.class, true, NavMenuItem.ID_ITEM_SETTINGS); @@ -567,6 +600,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener // check (and start) the LBRY SDK service serviceRunning = isServiceRunning(this, LbrynetService.class); if (!serviceRunning) { + Lbry.SDK_READY = false; ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); } checkSdkReady(); @@ -682,11 +716,14 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener urlSuggestionList.setAdapter(urlSuggestionListAdapter); } - private void clearWunderbarFocus(View view) { + public void clearWunderbarFocus(View view) { findViewById(R.id.wunderbar).clearFocus(); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } + public View getWunderbar() { + return findViewById(R.id.wunderbar); + } private void launchSearch(String text) { Fragment currentFragment = getCurrentFragment(); @@ -944,6 +981,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener scheduleWalletBalanceUpdate(); scheduleWalletSyncTask(); + fetchChannels(); initFloatingWalletBalance(); } @@ -955,13 +993,18 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } private void initFloatingWalletBalance() { - findViewById(R.id.floating_balance_container).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET); } }); + findViewById(R.id.floating_reward_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); + } + }); } private void updateFloatingWalletBalance() { @@ -1278,6 +1321,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener startActivityForResult(intent, REQUEST_WALLET_SYNC_SIGN_IN); } + public void rewardsSignIn() { + Intent intent = new Intent(this, VerificationActivity.class); + intent.putExtra("flow", VerificationActivity.VERIFICATION_FLOW_REWARDS); + startActivityForResult(intent, REQUEST_REWARDS_VERIFY_SIGN_IN); + } + @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { @@ -1309,6 +1358,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_FILE_PICKER) { + startingFilePickerActivity = false; ChannelFormFragment channelFormFragment = null; //PublishFormFragment publishFormFragment = null; for (Fragment fragment : openNavFragments.values()) { @@ -1326,7 +1376,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } } else { if (channelFormFragment != null) { - channelFormFragment.onFilePicked(null); + channelFormFragment.onFilePickerCancelled(); } } } else if (requestCode == REQUEST_SIMPLE_SIGN_IN || requestCode == REQUEST_WALLET_SYNC_SIGN_IN) { @@ -1476,6 +1526,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener loadLastFragment(); showSignedInUser(); + fetchRewards(); checkUrlIntent(getIntent()); appStarted = true; @@ -1483,6 +1534,30 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + private void fetchRewards() { + FetchRewardsTask task = new FetchRewardsTask(null, new FetchRewardsTask.FetchRewardsHandler() { + @Override + public void onSuccess(List rewards) { + Lbryio.updateRewardsLists(rewards); + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof RewardsFragment) { + ((RewardsFragment) fragment).updateUnclaimedRewardsValue(); + } + } + + 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); + } + } + + @Override + public void onError(Exception error) { + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + private void checkUrlIntent(Intent intent) { if (intent != null) { Uri data = intent.getData(); @@ -1555,7 +1630,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPlayer != null && FileViewActivity.instance == null && - !startingFileViewActivity) { + !startingFileViewActivity && + !startingFilePickerActivity && + !startingSignInFlowActivity) { enteringPIPMode = true; getSupportActionBar().hide(); @@ -1784,6 +1861,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public void showSearchBar() { findViewById(R.id.wunderbar_container).setVisibility(View.VISIBLE); + clearWunderbarFocus(findViewById(R.id.wunderbar)); } @Override @@ -1861,6 +1939,24 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } } + public void fetchChannels() { + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, null, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = new ArrayList<>(claims); + for (FetchChannelsListener listener : fetchChannelsListeners) { + listener.onChannelsFetched(claims); + } + } + + @Override + public void onError(Exception error) { + // pass + } + }); + 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/VerificationActivity.java b/app/src/main/java/io/lbry/browser/VerificationActivity.java index 29342786..65e0b9db 100644 --- a/app/src/main/java/io/lbry/browser/VerificationActivity.java +++ b/app/src/main/java/io/lbry/browser/VerificationActivity.java @@ -1,6 +1,8 @@ package io.lbry.browser; +import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; @@ -11,13 +13,22 @@ 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.utils.Lbryio; public class VerificationActivity extends FragmentActivity implements SignInListener, WalletSyncListener { @@ -59,10 +70,6 @@ public class VerificationActivity extends FragmentActivity implements SignInList viewPager.setSaveEnabled(false); viewPager.setAdapter(new VerificationPagerAdapter(this)); - if (Lbryio.isSignedIn() && flow == VERIFICATION_FLOW_WALLET) { - viewPager.setCurrentItem(1); - } - findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); findViewById(R.id.verification_close_button).setOnClickListener(new View.OnClickListener() { @Override @@ -73,6 +80,51 @@ public class VerificationActivity extends FragmentActivity implements SignInList }); } + public void onResume() { + super.onResume(); + checkFlow(); + } + + public void checkFlow() { + ViewPager2 viewPager = findViewById(R.id.verification_pager); + if (Lbryio.isSignedIn()) { + boolean flowHandled = false; + if (flow == VERIFICATION_FLOW_WALLET) { + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_WALLET, false); + flowHandled = true; + } else if (flow == VERIFICATION_FLOW_REWARDS) { + User user = Lbryio.currentUser; + if (!user.isIdentityVerified()) { + // phone number verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_PHONE, false); + flowHandled = true; + } else if (!user.isRewardApproved()) { + // manual verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false); + flowHandled = true; + } + } + + if (!flowHandled) { + // user has already been verified and or reward approved + setResult(RESULT_CANCELED); + finish(); + return; + } + } + } + + public void showLoading() { + findViewById(R.id.verification_loading_progress).setVisibility(View.VISIBLE); + findViewById(R.id.verification_pager).setVisibility(View.INVISIBLE); + findViewById(R.id.verification_close_button).setVisibility(View.GONE); + } + + public void hideLoading() { + findViewById(R.id.verification_loading_progress).setVisibility(View.GONE); + findViewById(R.id.verification_pager).setVisibility(View.VISIBLE); + } + @Override public void onBackPressed() { // ignore back press @@ -96,6 +148,7 @@ public class VerificationActivity extends FragmentActivity implements SignInList resultIntent.putExtra("email", email); // only sign in required, don't do anything else + showLoading(); FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() { @Override public void onSuccess(User user) { @@ -106,29 +159,94 @@ public class VerificationActivity extends FragmentActivity implements SignInList @Override public void onError(Exception error) { - setResult(RESULT_CANCELED); - finish(); + showFetchUserError(error.getMessage()); + hideLoading(); } }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { // change pager view depending on flow + showLoading(); FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() { @Override - public void onSuccess(User user) { Lbryio.currentUser = user; } + public void onSuccess(User user) { + hideLoading(); + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + + Lbryio.currentUser = user; + ViewPager2 viewPager = findViewById(R.id.verification_pager); + // for rewards, (show phone verification if not done, or manual verification if required) + if (flow == VERIFICATION_FLOW_REWARDS) { + if (!user.isIdentityVerified()) { + // phone number verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_PHONE, false); + } else if (!user.isRewardApproved()) { + // manual verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false); + } else { + // fully verified + setResult(RESULT_OK); + finish(); + } + } else if (flow == VERIFICATION_FLOW_WALLET) { + // for wallet sync, if password unlock is required, show password entry page + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_WALLET, false); + } + } @Override - public void onError(Exception error) { } + public void onError(Exception error) { + showFetchUserError(error.getMessage()); + hideLoading(); + } }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - ViewPager2 viewPager = findViewById(R.id.verification_pager); - // for rewards, (show phone verification if not done, or manual verification if required) - - // for wallet sync, if password unlock is required, show password entry page - viewPager.setCurrentItem(1); } } + @Override + public void onPhoneAdded(String countryCode, String phoneNumber) { + + } + + @Override + public void onPhoneVerified() { + showLoading(); + FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() { + @Override + public void onSuccess(User user) { + Lbryio.currentUser = user; + if (user.isIdentityVerified() && user.isRewardApproved()) { + // verified for rewards + setResult(RESULT_OK); + finish(); + return; + } + + // show manual verification page if the user is still not reward approved + ViewPager2 viewPager = findViewById(R.id.verification_pager); + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false); + hideLoading(); + } + + @Override + public void onError(Exception error) { + showFetchUserError(error.getMessage()); + hideLoading(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showFetchUserError(String message) { + Snackbar.make(findViewById(R.id.verification_pager), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + } + + @Override + public void onManualVerifyContinue() { + setResult(RESULT_OK); + finish(); + } + @Override public void onWalletSyncProcessing() { findViewById(R.id.verification_close_button).setVisibility(View.GONE); diff --git a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java index d9125c39..0640818b 100644 --- a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java @@ -7,11 +7,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.snackbar.Snackbar; import java.math.BigDecimal; import java.util.ArrayList; @@ -30,6 +32,8 @@ public class ClaimListAdapter extends RecyclerView.Adapter items; private List selectedItems; @@ -110,6 +114,7 @@ public class ClaimListAdapter extends RecyclerView.Adapter { + + public static final int DISPLAY_MODE_ALL = 1; + public static final int DISPLAY_MODE_UNCLAIMED = 2; + + private Context context; + @Setter + private List all; + private List items; + @Setter + private RewardClickListener clickListener; + @Getter + private int displayMode; + + public RewardListAdapter(List all, Context context) { + this.all = new ArrayList<>(all); + this.items = new ArrayList<>(all); + this.context = context; + this.displayMode = DISPLAY_MODE_ALL; + + addCustomReward(); + } + + public void setRewards(List rewards) { + this.all = new ArrayList<>(rewards); + updateItemsForDisplayMode(); + notifyDataSetChanged(); + } + + public void setDisplayMode(int displayMode) { + this.displayMode = displayMode; + updateItemsForDisplayMode(); + notifyDataSetChanged(); + } + + private void updateItemsForDisplayMode() { + if (displayMode == DISPLAY_MODE_ALL) { + items = new ArrayList<>(all); + } else if (displayMode == DISPLAY_MODE_UNCLAIMED) { + items = new ArrayList<>(); + for (Reward reward : all) { + if (!reward.isClaimed()) { + items.add(reward); + } + } + } + addCustomReward(); + } + + private void addCustomReward() { + Reward custom = new Reward(); + custom.setCustom(true); + custom.setRewardTitle(context.getString(R.string.custom_reward_title)); + custom.setRewardDescription(context.getString(R.string.custom_reward_description)); + items.add(custom); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected View iconClaimed; + protected View loading; + protected View upTo; + protected TextView textTitle; + protected TextView textDescription; + protected TextView textLbcValue; + protected TextView textUsdValue; + protected TextView textLinkTransaction; + protected EditText inputCustomCode; + protected MaterialButton buttonClaimCustom; + public ViewHolder(View v) { + super(v); + iconClaimed = v.findViewById(R.id.reward_item_claimed_icon); + upTo = v.findViewById(R.id.reward_item_up_to); + loading = v.findViewById(R.id.reward_item_loading); + textTitle = v.findViewById(R.id.reward_item_title); + textDescription = v.findViewById(R.id.reward_item_description); + textLbcValue = v.findViewById(R.id.reward_item_lbc_value); + textLinkTransaction = v.findViewById(R.id.reward_item_tx_link); + textUsdValue = v.findViewById(R.id.reward_item_usd_value); + inputCustomCode = v.findViewById(R.id.reward_item_custom_code_input); + buttonClaimCustom = v.findViewById(R.id.reward_item_custom_claim_button); + } + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + public void addReward(Reward reward) { + if (!items.contains(reward)) { + items.add(reward); + } + notifyDataSetChanged(); + } + public List getRewards() { + return new ArrayList<>(items); + } + + @Override + public RewardListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_reward, root, false); + return new RewardListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(RewardListAdapter.ViewHolder vh, int position) { + Reward reward = items.get(position); + String displayAmount = reward.getDisplayAmount(); + double rewardAmount = 0; + if (!"?".equals(displayAmount)) { + rewardAmount = Double.valueOf(displayAmount); + } + boolean hasTransaction = !Helper.isNullOrEmpty(reward.getTransactionId()); + + 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.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); + vh.textLinkTransaction.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (context != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("%s/%s", Helper.EXPLORER_TX_PREFIX, reward.getTransactionId()))); + context.startActivity(intent); + } + } + }); + + vh.textUsdValue.setText(reward.isCustom() || Lbryio.LBCUSDRate == 0 ? null : + String.format("≈$%s", Helper.USD_CURRENCY_FORMAT.format(rewardAmount * Lbryio.LBCUSDRate))); + + vh.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (reward.isClaimed()) { + return; + } + + if (clickListener != null) { + clickListener.onRewardClicked(reward, vh.loading); + } + } + }); + + vh.inputCustomCode.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) { + String value = charSequence.toString().trim(); + vh.buttonClaimCustom.setEnabled(value.length() > 0); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + vh.buttonClaimCustom.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String claimCode = Helper.getValue(vh.inputCustomCode.getText()); + if (Helper.isNullOrEmpty(claimCode)) { + Snackbar.make(view, R.string.please_enter_claim_code, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + return; + } + + if (clickListener != null) { + clickListener.onCustomClaimButtonClicked(claimCode, vh.inputCustomCode, vh.buttonClaimCustom, vh.loading); + } + } + }); + } + + public interface RewardClickListener { + void onRewardClicked(Reward reward, View loadingView); + void onCustomClaimButtonClicked(String code, EditText inputCustomCode, MaterialButton buttonClaim, View loadingView); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java index 98de0618..95b4a139 100644 --- a/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java @@ -23,7 +23,6 @@ import lombok.Setter; public class TransactionListAdapter extends RecyclerView.Adapter { - private static final String EXPLORER_TX_PREFIX = "https://explorer.lbry.com/tx"; private static final DecimalFormat TX_LIST_AMOUNT_FORMAT = new DecimalFormat("#,##0.0000"); private static final SimpleDateFormat TX_LIST_DATE_FORMAT = new SimpleDateFormat("MMM d"); @@ -92,7 +91,7 @@ public class TransactionListAdapter extends RecyclerView.Adapter channels); +} diff --git a/app/src/main/java/io/lbry/browser/listener/SignInListener.java b/app/src/main/java/io/lbry/browser/listener/SignInListener.java index 0217a91f..866e2774 100644 --- a/app/src/main/java/io/lbry/browser/listener/SignInListener.java +++ b/app/src/main/java/io/lbry/browser/listener/SignInListener.java @@ -4,4 +4,7 @@ public interface SignInListener { void onEmailAdded(String email); void onEmailEdit(); void onEmailVerified(); + void onPhoneAdded(String countryCode, String phoneNumber); + void onPhoneVerified(); + void onManualVerifyContinue(); } diff --git a/app/src/main/java/io/lbry/browser/model/Transaction.java b/app/src/main/java/io/lbry/browser/model/Transaction.java index b230c480..e48f3913 100644 --- a/app/src/main/java/io/lbry/browser/model/Transaction.java +++ b/app/src/main/java/io/lbry/browser/model/Transaction.java @@ -28,6 +28,7 @@ public class Transaction { private TransactionInfo abandonInfo; private TransactionInfo claimInfo; private TransactionInfo supportInfo; + private TransactionInfo updateInfo; public LbryUri getClaimUrl() { if (!Helper.isNullOrEmpty(claim) && !Helper.isNullOrEmpty(claimId)) { @@ -77,6 +78,14 @@ public class Transaction { transaction.setSupportInfo(info); } } + if (info == null && jsonObject.has("update_info")) { + JSONArray array = jsonObject.getJSONArray("update_info"); + if (array.length() > 0) { + info = TransactionInfo.fromJSONObject(array.getJSONObject(0)); + descStringId = info.getClaimName().startsWith("@") ? R.string.channel_update : R.string.publish_update; + transaction.setUpdateInfo(info); + } + } if (info != null) { transaction.setClaim(info.getClaimName()); transaction.setClaimId(info.getClaimId()); diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/Reward.java b/app/src/main/java/io/lbry/browser/model/lbryinc/Reward.java new file mode 100644 index 00000000..67f46db6 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Reward.java @@ -0,0 +1,69 @@ +package io.lbry.browser.model.lbryinc; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.json.JSONObject; + +import java.lang.reflect.Type; + +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import lombok.Data; + +@Data +public class Reward { + public static final String TYPE_NEW_DEVELOPER = "new_developer"; + public static final String TYPE_NEW_USER = "new_user"; + public static final String TYPE_CONFIRM_EMAIL = "email_provided"; + public static final String TYPE_FIRST_CHANNEL = "new_channel"; + public static final String TYPE_FIRST_STREAM = "first_stream"; + public static final String TYPE_MANY_DOWNLOADS = "many_downloads"; + public static final String TYPE_FIRST_PUBLISH = "first_publish"; + public static final String TYPE_REFERRAL = "referrer"; + public static final String TYPE_REFEREE = "referee"; + public static final String TYPE_REWARD_CODE = "reward_code"; + public static final String TYPE_SUBSCRIPTION = "subscription"; + public static final String YOUTUBE_CREATOR = "youtube_creator"; + public static final String TYPE_DAILY_VIEW = "daily_view"; + public static final String TYPE_NEW_ANDROID = "new_android"; + + private boolean custom; + private long id; + private String rewardType; + private double rewardAmount; + private String transactionId; + private String createdAt; + private String updatedAt; + private String rewardTitle; + private String rewardDescription; + private String rewardNotification; + private String rewardRange; + + public String getDisplayAmount() { + if (shouldDisplayRange()) { + return rewardRange.split("-")[1]; + } + if (rewardAmount > 0) { + return String.valueOf(rewardAmount); + } + return "?"; + } + + public boolean isClaimed() { + return !Helper.isNullOrEmpty(transactionId); + } + + public boolean shouldDisplayRange() { + return (!isClaimed() && !Helper.isNullOrEmpty(rewardRange) && rewardRange.indexOf('-') > -1); + } + + public static Reward fromJSONObject(JSONObject rewardObject) { + String rewardJson = rewardObject.toString(); + Type type = new TypeToken(){}.getType(); + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + return gson.fromJson(rewardJson, type); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/ClaimRewardTask.java b/app/src/main/java/io/lbry/browser/tasks/ClaimRewardTask.java new file mode 100644 index 00000000..96bb76ec --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/ClaimRewardTask.java @@ -0,0 +1,86 @@ +package io.lbry.browser.tasks; + +import android.content.Context; +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.R; +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Lbryio; + +public class ClaimRewardTask extends AsyncTask { + + private Context context; + private String type; + private String claimCode; + private View progressView; + private double amountClaimed; + private ClaimRewardHandler handler; + private Exception error; + + public ClaimRewardTask(String type, String claimCode, View progressView, Context context, ClaimRewardHandler handler) { + this.type = type; + this.claimCode = claimCode; + this.progressView = progressView; + this.context = context; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + public String doInBackground(Void... params) { + String message = null; + try { + // Get a new wallet address for the reward + String address = (String) Lbry.genericApiCall(Lbry.METHOD_ADDRESS_UNUSED); + Map options = new HashMap<>(); + options.put("reward_type", type); + options.put("wallet_address", address); + if (!Helper.isNullOrEmpty(claimCode)) { + options.put("claim_code", claimCode); + + } + JSONObject reward = (JSONObject) Lbryio.parseResponse( + Lbryio.call("reward", "claim", options, Helper.METHOD_POST, null)); + amountClaimed = Helper.getJSONDouble("reward_amount", 0, reward); + String defaultMessage = context != null ? + context.getResources().getQuantityString( + R.plurals.claim_reward_message, + amountClaimed == 1 ? 1 : 2, + new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed)) : ""; + message = Helper.getJSONString("reward_notification", defaultMessage, reward); + } catch (ApiCallException | LbryioRequestException | LbryioResponseException ex) { + error = ex; + } + + return message; + } + + protected void onPostExecute(String message) { + Helper.setViewVisibility(progressView, View.INVISIBLE); + if (handler != null) { + if (message != null) { + handler.onSuccess(amountClaimed, message); + } else { + handler.onError(error); + } + } + } + + public interface ClaimRewardHandler { + void onSuccess(double amountClaimed, String message); + 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/FetchRewardsTask.java new file mode 100644 index 00000000..ef353303 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/FetchRewardsTask.java @@ -0,0 +1,66 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchRewardsTask extends AsyncTask> { + private FetchRewardsHandler handler; + private View progressView; + private Exception error; + + public FetchRewardsTask(View progressView, FetchRewardsHandler handler) { + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected List doInBackground(Void... params) { + List rewards = null; + try { + Map options = new HashMap<>(); + options.put("multiple_rewards_per_type", "true"); + JSONArray results = (JSONArray) Lbryio.parseResponse(Lbryio.call("reward", "list", null, null)); + rewards = new ArrayList<>(); + for (int i = 0; i < results.length(); i++) { + rewards.add(Reward.fromJSONObject(results.getJSONObject(i))); + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return rewards; + } + + protected void onPostExecute(List rewards) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (rewards != null) { + handler.onSuccess(rewards); + } else { + handler.onError(error); + } + } + } + + public interface FetchRewardsHandler { + void onSuccess(List rewards); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/content/ChannelCreateUpdateTask.java b/app/src/main/java/io/lbry/browser/tasks/content/ChannelCreateUpdateTask.java index 88cca734..3bbd4340 100644 --- a/app/src/main/java/io/lbry/browser/tasks/content/ChannelCreateUpdateTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/content/ChannelCreateUpdateTask.java @@ -3,6 +3,8 @@ package io.lbry.browser.tasks.content; import android.os.AsyncTask; import android.view.View; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.math.BigDecimal; @@ -16,15 +18,15 @@ import io.lbry.browser.tasks.GenericTaskHandler; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; -public class ChannelCreateUpdateTask extends AsyncTask { +public class ChannelCreateUpdateTask extends AsyncTask { private Claim claim; private BigDecimal deposit; private boolean update; private Exception error; - private GenericTaskHandler handler; + private ClaimResultHandler handler; private View progressView; - public ChannelCreateUpdateTask(Claim claim, BigDecimal deposit, boolean update, View progressView, GenericTaskHandler handler) { + public ChannelCreateUpdateTask(Claim claim, BigDecimal deposit, boolean update, View progressView, ClaimResultHandler handler) { this.claim = claim; this.deposit = deposit; this.update = update; @@ -38,7 +40,7 @@ public class ChannelCreateUpdateTask extends AsyncTask { handler.beforeStart(); } } - protected Boolean doInBackground(Void... params) { + protected Claim doInBackground(Void... params) { Map options = new HashMap<>(); if (!update) { options.put("name", claim.getName()); @@ -55,21 +57,43 @@ public class ChannelCreateUpdateTask extends AsyncTask { options.put("tags", claim.getTags()); options.put("blocking", true); + Claim claimResult = null; String method = !update ? Lbry.METHOD_CHANNEL_CREATE : Lbry.METHOD_CHANNEL_UPDATE; try { - Lbry.genericApiCall(method, options); - } catch (ApiCallException | ClassCastException ex) { + JSONObject result = (JSONObject) Lbry.genericApiCall(method, options); + if (result.has("outputs")) { + JSONArray outputs = result.getJSONArray("outputs"); + for (int i = 0; i < outputs.length(); i++) { + JSONObject output = outputs.getJSONObject(i); + if (output.has("claim_id") && output.has("claim_op")) { + claimResult = claimFromResult(output); + break; + } + } + } + } catch (ApiCallException | ClassCastException | JSONException ex) { error = ex; - return false; } - return true; + return claimResult; } - protected void onPostExecute(Boolean result) { + + private static Claim claimFromResult(JSONObject item) { + // we only need name, permanent_url, txid and nout + Claim claim = new Claim(); + claim.setClaimId(Helper.getJSONString("claim_id", null, item)); + claim.setName(Helper.getJSONString("name", null, item)); + claim.setPermanentUrl(Helper.getJSONString("permanent_url", null, item)); + claim.setTxid(Helper.getJSONString("txid", null, item)); + claim.setNout(Helper.getJSONInt("nout", -1, item)); + return claim; + } + + protected void onPostExecute(Claim result) { Helper.setViewVisibility(progressView, View.GONE); if (handler != null) { - if (result) { - handler.onSuccess(); + if (result != null) { + handler.onSuccess(result); } else { handler.onError(error); } diff --git a/app/src/main/java/io/lbry/browser/tasks/content/ClaimResultHandler.java b/app/src/main/java/io/lbry/browser/tasks/content/ClaimResultHandler.java new file mode 100644 index 00000000..9716686f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/content/ClaimResultHandler.java @@ -0,0 +1,9 @@ +package io.lbry.browser.tasks.content; + +import io.lbry.browser.model.Claim; + +public interface ClaimResultHandler { + void beforeStart(); + void onSuccess(Claim claimResult); + void onError(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/content/LogPublishTask.java b/app/src/main/java/io/lbry/browser/tasks/content/LogPublishTask.java index e1b1911f..31bc6915 100644 --- a/app/src/main/java/io/lbry/browser/tasks/content/LogPublishTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/content/LogPublishTask.java @@ -1,4 +1,35 @@ package io.lbry.browser.tasks.content; -public class LogPublishTask { +import android.os.AsyncTask; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Lbryio; +import okhttp3.Response; + +public class LogPublishTask extends AsyncTask { + private Claim claimResult; + public LogPublishTask(Claim claimResult) { + this.claimResult = claimResult; + } + protected Void doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("uri", claimResult.getPermanentUrl()); + options.put("claim_id", claimResult.getClaimId()); + options.put("outpoint", String.format("%s:%d", claimResult.getTxid(), claimResult.getNout())); + if (claimResult.getSigningChannel() != null) { + options.put("channel_claim_id", claimResult.getSigningChannel().getClaimId()); + } + Lbryio.call("event", "publish", options, null).close(); + } catch (LbryioRequestException | LbryioResponseException ex) { + // pass + } + return null; + } } diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/PhoneNewVerifyTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneNewVerifyTask.java new file mode 100644 index 00000000..15730464 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneNewVerifyTask.java @@ -0,0 +1,65 @@ +package io.lbry.browser.tasks.verification; + +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 PhoneNewVerifyTask extends AsyncTask { + private String countryCode; + private String phoneNumber; + private String verificationCode; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public PhoneNewVerifyTask(String countryCode, String phoneNumber, String verificationCode, View progressView, GenericTaskHandler handler) { + this.countryCode = countryCode; + this.phoneNumber = phoneNumber; + this.verificationCode = verificationCode; + 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 { + boolean isVerify = !Helper.isNullOrEmpty(verificationCode); + Map options = new HashMap<>(); + options.put("country_code", countryCode); + options.put("phone_number", phoneNumber.replace(" ", "").replace("-", "")); + if (isVerify) { + options.put("verification_code", verificationCode); + } + + String action = isVerify ? "phone_number_confirm" : "phone_number_new"; + Lbryio.parseResponse(Lbryio.call("user", action, options, Helper.METHOD_POST, null)); + } catch (LbryioResponseException | LbryioRequestException 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/tasks/verification/PhoneResendTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneResendTask.java new file mode 100644 index 00000000..247646b0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneResendTask.java @@ -0,0 +1,4 @@ +package io.lbry.browser.tasks.verification; + +public class PhoneResendTask { +} 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 0f89752f..ca763edb 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 @@ -32,17 +32,21 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import io.lbry.browser.BuildConfig; import io.lbry.browser.MainActivity; import io.lbry.browser.R; import io.lbry.browser.adapter.TagListAdapter; import io.lbry.browser.listener.WalletBalanceListener; import io.lbry.browser.model.Claim; +import io.lbry.browser.model.NavMenuItem; import io.lbry.browser.model.Tag; import io.lbry.browser.model.WalletBalance; import io.lbry.browser.tasks.GenericTaskHandler; import io.lbry.browser.tasks.UpdateSuggestedTagsTask; import io.lbry.browser.tasks.UploadImageTask; 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.ui.BaseFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; @@ -63,6 +67,7 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi private TextView linkShowOptional; private MaterialButton buttonSave; + private View inlineBalanceContainer; private TextView inlineBalanceValue; private View uploadProgress; private View containerOptionalFields; @@ -120,6 +125,7 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi iconContainer = root.findViewById(R.id.channel_form_icon_container); imageCover = root.findViewById(R.id.channel_form_cover_image); imageThumbnail = root.findViewById(R.id.channel_form_thumbnail); + inlineBalanceContainer = root.findViewById(R.id.channel_form_inline_balance_container); inlineBalanceValue = root.findViewById(R.id.channel_form_inline_balance_value); uploadProgress = root.findViewById(R.id.channel_form_upload_progress); channelSaveProgress = root.findViewById(R.id.channel_form_save_progress); @@ -146,6 +152,12 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi buttonSave = root.findViewById(R.id.channel_form_save_button); + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + Helper.setViewVisibility(inlineBalanceContainer, hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); linkShowOptional.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -161,6 +173,8 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi linkCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { + clearInputFocus(); + Context context = getContext(); if (context instanceof MainActivity) { ((MainActivity) context).onBackPressed(); @@ -295,15 +309,22 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi return; } - ChannelCreateUpdateTask task = new ChannelCreateUpdateTask(claim, new BigDecimal(depositString), editMode, channelSaveProgress, new GenericTaskHandler() { + ChannelCreateUpdateTask task = new ChannelCreateUpdateTask(claim, new BigDecimal(depositString), editMode, channelSaveProgress, new ClaimResultHandler() { @Override public void beforeStart() { preSave(); } @Override - public void onSuccess() { + public void onSuccess(Claim claimResult) { postSave(); + + // Run the logPublish task + if (!BuildConfig.DEBUG) { + LogPublishTask logPublish = new LogPublishTask(claimResult); + logPublish.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + Context context = getContext(); if (context instanceof MainActivity) { MainActivity activity = (MainActivity) context; @@ -354,13 +375,18 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi private void launchFilePicker() { Context context = getContext(); if (context instanceof MainActivity) { + MainActivity.startingFilePickerActivity = true; Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.setType("image/*"); ((MainActivity) context).startActivityForResult( Intent.createChooser(intent, getString(coverFilePickerActive ? R.string.select_cover : R.string.select_thumbnail)), MainActivity.REQUEST_FILE_PICKER); } + } + public void onFilePickerCancelled() { + coverFilePickerActive = false; + thumbnailFilePickerActive = false; } public void onFilePicked(String filePath) { @@ -455,6 +481,12 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi } } + @Override + public void onPause() { + clearInputFocus(); + super.onPause(); + } + @Override public void onStop() { Context context = getContext(); @@ -463,6 +495,9 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi activity.removeWalletBalanceListener(this); activity.restoreToggle(); activity.showFloatingWalletBalance(); + if (!MainActivity.startingFilePickerActivity) { + activity.removeNavFragment(ChannelFormFragment.class, NavMenuItem.ID_ITEM_CHANNELS); + } } super.onStop(); } @@ -472,6 +507,16 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi checkParams(); updateFieldsFromCurrentClaim(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (editMode) { + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.edit_channel); + } + } + } String filterText = Helper.getValue(inputTagFilter.getText()); updateSuggestedTags(filterText, SUGGESTED_LIMIT, true); } @@ -583,9 +628,22 @@ public class ChannelFormFragment extends BaseFragment implements WalletBalanceLi Helper.setViewVisibility(linkShowOptional, View.VISIBLE); Helper.setViewEnabled(linkCancel, true); Helper.setViewEnabled(buttonSave, true); + + clearInputFocus(); + saveInProgress = false; } + public void clearInputFocus() { + inputChannelName.clearFocus(); + inputDeposit.clearFocus(); + inputWebsite.clearFocus(); + inputEmail.clearFocus(); + inputDescription.clearFocus(); + inputTitle.clearFocus(); + inputTagFilter.clearFocus(); + } + @Override public void onTagClicked(Tag tag, int customizeMode) { if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_ADD) { 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 92d39450..934eb96b 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 @@ -32,6 +32,8 @@ import io.lbry.browser.MainActivity; 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; @@ -46,7 +48,7 @@ import io.lbry.browser.utils.LbryUri; import io.lbry.browser.utils.Lbryio; import lombok.SneakyThrows; -public class ChannelFragment extends BaseFragment { +public class ChannelFragment extends BaseFragment implements FetchChannelsListener { private Claim claim; private boolean subscribing; private String url; @@ -62,6 +64,8 @@ public class ChannelFragment extends BaseFragment { private TabLayout tabLayout; private ViewPager2 tabPager; + private View buttonEdit; + private View buttonDelete; private View buttonShare; private View buttonTip; private View buttonFollowUnfollow; @@ -81,6 +85,8 @@ public class ChannelFragment extends BaseFragment { textTitle = root.findViewById(R.id.channel_view_title); textFollowerCount = root.findViewById(R.id.channel_view_follower_count); + buttonEdit = root.findViewById(R.id.channel_view_edit); + buttonDelete = root.findViewById(R.id.channel_view_delete); buttonShare = root.findViewById(R.id.channel_view_share); buttonTip = root.findViewById(R.id.channel_view_tip); buttonFollowUnfollow = root.findViewById(R.id.channel_view_follow_unfollow); @@ -90,6 +96,27 @@ public class ChannelFragment extends BaseFragment { tabLayout = root.findViewById(R.id.channel_view_tabs); tabPager.setSaveEnabled(false); + buttonEdit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openChannelForm(claim); + } + } + } + }); + buttonDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + // show confirmation? + // delete claim task and redirect + } + } + }); + buttonShare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -203,12 +230,39 @@ public class ChannelFragment extends BaseFragment { } } + public void onChannelsFetched(List channels) { + checkOwnChannel(); + } + + private void checkOwnChannel() { + if (claim != null) { + boolean isOwnChannel = Lbry.ownChannels.contains(claim); + Helper.setViewVisibility(buttonEdit, isOwnChannel ? View.VISIBLE : View.GONE); + Helper.setViewVisibility(buttonDelete, isOwnChannel ? View.VISIBLE : View.GONE); + } + } + public void onResume() { super.onResume(); + Context context = getContext(); + Map params = getParams(); String url = params != null && params.containsKey("url") ? (String) params.get("url") : null; - Helper.setWunderbarValue(url, getContext()); + Helper.setWunderbarValue(url, context); + if (context instanceof MainActivity) { + ((MainActivity) context).addFetchChannelsListener(this); + } + checkParams(); + checkOwnChannel(); + } + + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).removeFetchChannelsListener(this); + } + super.onPause(); } private void checkParams() { @@ -252,7 +306,7 @@ public class ChannelFragment extends BaseFragment { if (claims.size() > 0) { claim = claims.get(0); renderClaim(); - // TODO: Load follower count + checkOwnChannel(); } else { renderNothingAtLocation(); } diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java index a36ddad0..fb4e173f 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java @@ -57,7 +57,6 @@ public class ChannelManagerFragment extends BaseFragment implements ActionMode.C ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_channel_manager, container, false); - buttonNewChannel = root.findViewById(R.id.channel_manager_create_button); fabNewChannel = root.findViewById(R.id.channel_manager_fab_new_channel); buttonNewChannel.setOnClickListener(newChannelClickListener); @@ -149,6 +148,7 @@ public class ChannelManagerFragment extends BaseFragment implements ActionMode.C Context context = getContext(); if (adapter == null) { adapter = new ClaimListAdapter(claims, context); + adapter.setCanEnterSelectionMode(true); adapter.setSelectionModeListener(ChannelManagerFragment.this); adapter.setListener(new ClaimListAdapter.ClaimListItemListener() { @Override diff --git a/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java new file mode 100644 index 00000000..e901cb13 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java @@ -0,0 +1,36 @@ +package io.lbry.browser.ui.verification; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import io.lbry.browser.R; +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class ManualVerificationFragment extends Fragment { + @Setter + private SignInListener listener; + + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_verification_manual, container, false); + + Helper.applyHtmlForTextView((TextView) root.findViewById(R.id.verification_manual_discord_verify)); + root.findViewById(R.id.verification_manual_continue_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onManualVerifyContinue(); + } + } + }); + + return root; + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/verification/PhoneVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/PhoneVerificationFragment.java new file mode 100644 index 00000000..77547269 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/verification/PhoneVerificationFragment.java @@ -0,0 +1,165 @@ +package io.lbry.browser.ui.verification; + +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; +import androidx.fragment.app.Fragment; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.hbb20.CountryCodePicker; + +import io.lbry.browser.R; +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.verification.PhoneNewVerifyTask; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class PhoneVerificationFragment extends Fragment { + @Setter + private SignInListener listener; + + private View layoutCollect; + private View layoutVerify; + private MaterialButton continueButton; + private MaterialButton verifyButton; + private View editButton; + private TextView textVerifyParagraph; + private CountryCodePicker countryCodePicker; + private EditText inputPhoneNumber; + private EditText inputVerificationCode; + private ProgressBar newLoading; + private ProgressBar verifyLoading; + + private String currentCountryCode; + private String currentPhoneNumber; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_verification_phone, container, false); + + layoutCollect = root.findViewById(R.id.verification_phone_collect_container); + layoutVerify = root.findViewById(R.id.verification_phone_verify_container); + continueButton = root.findViewById(R.id.verification_phone_continue_button); + verifyButton = root.findViewById(R.id.verification_phone_verify_button); + editButton = root.findViewById(R.id.verification_phone_edit_button); + textVerifyParagraph = root.findViewById(R.id.verification_phone_verify_paragraph); + + countryCodePicker = root.findViewById(R.id.verification_phone_country_code); + inputPhoneNumber = root.findViewById(R.id.verification_phone_input); + inputVerificationCode = root.findViewById(R.id.verification_phone_code_input); + + newLoading = root.findViewById(R.id.verification_phone_new_progress); + verifyLoading = root.findViewById(R.id.verification_phone_verify_progress); + + Context context = getContext(); + countryCodePicker.setTypeFace(ResourcesCompat.getFont(context, R.font.inter_light)); + countryCodePicker.registerCarrierNumberEditText(inputPhoneNumber); + + continueButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + currentCountryCode = countryCodePicker.getSelectedCountryCode(); + currentPhoneNumber = Helper.getValue(inputPhoneNumber.getText()); + + if (Helper.isNullOrEmpty(currentPhoneNumber) || !countryCodePicker.isValidFullNumber()) { + Snackbar.make(getView(), R.string.please_enter_valid_phone, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + return; + } + + addPhoneNumber(); + } + }); + + verifyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String code = Helper.getValue(inputVerificationCode.getText()); + if (Helper.isNullOrEmpty(code)) { + Snackbar.make(getView(), R.string.please_enter_verification_code, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + return; + } + verifyPhoneNumber(code); + } + }); + + editButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + layoutVerify.setVisibility(View.GONE); + layoutCollect.setVisibility(View.VISIBLE); + } + }); + + return root; + } + + private void addPhoneNumber() { + PhoneNewVerifyTask task = new PhoneNewVerifyTask(currentCountryCode, currentPhoneNumber, null, newLoading, new GenericTaskHandler() { + @Override + public void beforeStart() { + continueButton.setEnabled(false); + continueButton.setVisibility(View.GONE); + } + + @Override + public void onSuccess() { + if (listener != null) { + listener.onPhoneAdded(currentCountryCode, currentPhoneNumber); + } + + textVerifyParagraph.setText(getString(R.string.enter_phone_verify_code, countryCodePicker.getFullNumberWithPlus())); + layoutCollect.setVisibility(View.GONE); + layoutVerify.setVisibility(View.VISIBLE); + continueButton.setEnabled(true); + continueButton.setVisibility(View.VISIBLE); + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + continueButton.setEnabled(true); + continueButton.setVisibility(View.VISIBLE); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void verifyPhoneNumber(String verificationCode) { + PhoneNewVerifyTask task = new PhoneNewVerifyTask(currentCountryCode, currentPhoneNumber, verificationCode, verifyLoading, new GenericTaskHandler() { + @Override + public void beforeStart() { + verifyButton.setEnabled(false); + editButton.setEnabled(false); + } + + @Override + public void onSuccess() { + if (listener != null) { + listener.onPhoneVerified(); + } + verifyButton.setEnabled(true); + editButton.setEnabled(true); + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + verifyButton.setEnabled(true); + editButton.setEnabled(true); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} 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 new file mode 100644 index 00000000..a9bda4fd --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java @@ -0,0 +1,264 @@ +package io.lbry.browser.ui.wallet; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +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 java.text.DecimalFormat; +import java.util.List; + +import io.lbry.browser.MainActivity; +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.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Lbryio; + +public class RewardsFragment extends BaseFragment implements RewardListAdapter.RewardClickListener, SdkStatusListener { + + private boolean rewardClaimInProgress; + private View layoutAccountDriver; + private View layoutSdkInitializing; + private View linkNotInterested; + private TextView textAccountDriverTitle; + private TextView textFreeCreditsWorth; + private TextView textLearnMoreLink; + private MaterialButton buttonGetStarted; + + private ProgressBar rewardsLoading; + private RewardListAdapter adapter; + private RecyclerView rewardList; + private TextView linkFilterUnclaimed; + private TextView linkFilterAll; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_rewards, container, false); + + layoutAccountDriver = root.findViewById(R.id.rewards_account_driver_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + linkNotInterested = root.findViewById(R.id.rewards_not_interested_link); + textAccountDriverTitle = root.findViewById(R.id.rewards_account_driver_title); + textFreeCreditsWorth = root.findViewById(R.id.rewards_account_driver_credits_worth); + textLearnMoreLink = root.findViewById(R.id.rewards_account_driver_learn_more); + buttonGetStarted = root.findViewById(R.id.rewards_get_started_button); + + linkFilterUnclaimed = root.findViewById(R.id.rewards_filter_link_unclaimed); + linkFilterAll = root.findViewById(R.id.rewards_filter_link_all); + rewardList = root.findViewById(R.id.rewards_list); + rewardsLoading = root.findViewById(R.id.rewards_list_loading); + + Context context = getContext(); + LinearLayoutManager llm = new LinearLayoutManager(context); + rewardList.setLayoutManager(llm); + adapter = new RewardListAdapter(Lbryio.allRewards, context); + adapter.setClickListener(this); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_UNCLAIMED); + rewardList.setAdapter(adapter); + + initUi(); + + return root; + } + + + public void onResume() { + super.onResume(); + checkRewardsStatus(); + fetchRewards(); + + if (!Lbry.SDK_READY) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + } + + public void onSdkReady() { + Helper.setViewVisibility(layoutSdkInitializing, View.GONE); + } + + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + activity.hideFloatingWalletBalance(); + } + } + + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeSdkStatusListener(this); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + private void fetchRewards() { + Helper.setViewVisibility(rewardList, View.INVISIBLE); + FetchRewardsTask task = new FetchRewardsTask(rewardsLoading, new FetchRewardsTask.FetchRewardsHandler() { + @Override + public void onSuccess(List rewards) { + Lbryio.updateRewardsLists(rewards); + updateUnclaimedRewardsValue(); + + if (adapter == null) { + adapter = new RewardListAdapter(rewards, getContext()); + adapter.setClickListener(RewardsFragment.this); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_UNCLAIMED); + rewardList.setAdapter(adapter); + } else { + adapter.setRewards(rewards); + } + Helper.setViewVisibility(rewardList, View.VISIBLE); + } + + @Override + public void onError(Exception error) { + // pass + Helper.setViewVisibility(rewardList, View.VISIBLE); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void initUi() { + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + + linkNotInterested.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).onBackPressed(); + } + } + }); + buttonGetStarted.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).rewardsSignIn(); + } + } + }); + + linkFilterAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + linkFilterUnclaimed.setTypeface(null, Typeface.NORMAL); + linkFilterAll.setTypeface(null, Typeface.BOLD); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_ALL); + if (adapter.getItemCount() == 1) { + fetchRewards(); + } + } + }); + linkFilterUnclaimed.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + linkFilterUnclaimed.setTypeface(null, Typeface.BOLD); + linkFilterAll.setTypeface(null, Typeface.NORMAL); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_UNCLAIMED); + if (adapter.getItemCount() == 1) { + fetchRewards(); + } + } + }); + + updateUnclaimedRewardsValue(); + layoutAccountDriver.setVisibility(Lbryio.currentUser != null && Lbryio.currentUser.isRewardApproved() ? View.GONE : View.VISIBLE); + Helper.applyHtmlForTextView(textLearnMoreLink); + } + + private void checkRewardsStatus() { + Helper.setViewVisibility(layoutAccountDriver, Lbryio.currentUser != null && Lbryio.currentUser.isRewardApproved() ? View.GONE : View.VISIBLE); + } + + public void updateUnclaimedRewardsValue() { + String accountDriverTitle = getResources().getQuantityString( + R.plurals.available_credits, + Lbryio.totalUnclaimedRewardAmount == 1 ? 1 : 2, + Helper.shortCurrencyFormat(Lbryio.totalUnclaimedRewardAmount)); + double unclaimedRewardAmountUsd = Lbryio.totalUnclaimedRewardAmount * Lbryio.LBCUSDRate; + Helper.setViewText(textAccountDriverTitle, accountDriverTitle); + Helper.setViewText(textFreeCreditsWorth, getString(R.string.free_credits_worth, Helper.USD_CURRENCY_FORMAT.format(unclaimedRewardAmountUsd))); + } + + @Override + public void onRewardClicked(Reward reward, View loadingView) { + if (rewardClaimInProgress || reward.isCustom()) { + return; + } + claimReward(reward.getRewardType(), null, null, null, loadingView); + } + + @Override + public void onCustomClaimButtonClicked(String code, EditText inputCustomCode, MaterialButton buttonClaim, View loadingView) { + if (rewardClaimInProgress) { + return; + } + claimReward(Reward.TYPE_REWARD_CODE, code, inputCustomCode, buttonClaim, loadingView); + } + + private void claimReward(String type, String code, EditText inputClaimCode, MaterialButton buttonClaim, View loadingView) { + rewardClaimInProgress = true; + Helper.setViewEnabled(buttonClaim, false); + Helper.setViewEnabled(inputClaimCode, false); + ClaimRewardTask task = new ClaimRewardTask(type, code, loadingView, getContext(), new ClaimRewardTask.ClaimRewardHandler() { + @Override + public void onSuccess(double amountClaimed, String message) { + if (Helper.isNullOrEmpty(message)) { + message = getResources().getQuantityString( + R.plurals.claim_reward_message, + amountClaimed == 1 ? 1 : 2, + new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed)); + } + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); + Helper.setViewEnabled(buttonClaim, true); + Helper.setViewEnabled(inputClaimCode, true); + rewardClaimInProgress = false; + + fetchRewards(); + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + Helper.setViewEnabled(buttonClaim, true); + Helper.setViewEnabled(inputClaimCode, true); + rewardClaimInProgress = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} 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 0a9df179..79e00c30 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 @@ -4,9 +4,9 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; -import android.text.method.LinkMovementMethod; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -17,7 +17,6 @@ import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.core.text.HtmlCompat; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; @@ -178,7 +177,7 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W String amountString = Helper.getValue(inputSendAmount.getText()); if (!recipientAddress.matches(LbryUri.REGEX_ADDRESS)) { Snackbar.make(getView(), R.string.invalid_recipient_address, Snackbar.LENGTH_LONG). - setBackgroundTint(getResources().getColor(R.color.red)).show(); + setBackgroundTint(Color.RED).show(); return false; } @@ -206,11 +205,11 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W private void initUi() { onWalletBalanceUpdated(Lbry.walletBalance); - applyHtmlForTextView(textConvertCredits); - applyHtmlForTextView(textConvertCreditsBittrex); - applyHtmlForTextView(textWhatSyncMeans); - applyHtmlForTextView(linkManualBackup); - applyHtmlForTextView(linkSyncFAQ); + Helper.applyHtmlForTextView(textConvertCredits); + Helper.applyHtmlForTextView(textConvertCreditsBittrex); + Helper.applyHtmlForTextView(textWhatSyncMeans); + Helper.applyHtmlForTextView(linkManualBackup); + Helper.applyHtmlForTextView(linkSyncFAQ); Context context = getContext(); LinearLayoutManager llm = new LinearLayoutManager(context); @@ -387,11 +386,6 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W } } - private static void applyHtmlForTextView(TextView textView) { - textView.setMovementMethod(LinkMovementMethod.getInstance()); - textView.setText(HtmlCompat.fromHtml(textView.getText().toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)); - } - private boolean hasSkippedAccount() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); return sp.getBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_SKIP_WALLET_ACCOUNT, false); @@ -475,24 +469,21 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private static final DecimalFormat LBC_CURRENCY_FORMAT = new DecimalFormat("#,###.##"); - private static final DecimalFormat USD_CURRENCY_FORMAT = new DecimalFormat("#,##0.00"); - public void onWalletBalanceUpdated(WalletBalance walletBalance) { double balance = walletBalance.getAvailable().doubleValue(); double usdBalance = balance * Lbryio.LBCUSDRate; double tipsBalance = walletBalance.getTips().doubleValue(); double tipsUsdBalance = tipsBalance * Lbryio.LBCUSDRate; - Helper.setViewText(textWalletBalance, LBC_CURRENCY_FORMAT.format(balance)); + Helper.setViewText(textWalletBalance, Helper.LBC_CURRENCY_FORMAT.format(balance)); Helper.setViewText(textTipsBalance, Helper.shortCurrencyFormat(tipsBalance)); Helper.setViewText(textClaimsBalance, Helper.shortCurrencyFormat(walletBalance.getClaims().doubleValue())); Helper.setViewText(textSupportsBalance, Helper.shortCurrencyFormat(walletBalance.getSupports().doubleValue())); Helper.setViewText(textWalletInlineBalance, Helper.shortCurrencyFormat(balance)); if (Lbryio.LBCUSDRate > 0) { // only update display usd values if the rate is loaded - Helper.setViewText(textWalletBalanceUSD, String.format("≈$%s", USD_CURRENCY_FORMAT.format(usdBalance))); - Helper.setViewText(textTipsBalanceUSD, String.format("≈$%s", USD_CURRENCY_FORMAT.format(tipsUsdBalance))); + Helper.setViewText(textWalletBalanceUSD, String.format("≈$%s", Helper.USD_CURRENCY_FORMAT.format(usdBalance))); + Helper.setViewText(textTipsBalanceUSD, String.format("≈$%s", Helper.USD_CURRENCY_FORMAT.format(tipsUsdBalance))); } } } diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java index 84baa684..18673b39 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -16,10 +16,12 @@ import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; +import android.text.method.LinkMovementMethod; import android.view.View; import android.widget.TextView; import androidx.core.content.ContextCompat; +import androidx.core.text.HtmlCompat; import org.json.JSONArray; import org.json.JSONException; @@ -54,6 +56,10 @@ public final class Helper { public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); public static final int CONTENT_PAGE_SIZE = 25; public static final double MIN_DEPOSIT = 0.05; + public static final String LBC_CURRENCY_FORMAT_PATTERN = "#,###.##"; + public static final DecimalFormat LBC_CURRENCY_FORMAT = new DecimalFormat(LBC_CURRENCY_FORMAT_PATTERN); + public static final DecimalFormat USD_CURRENCY_FORMAT = new DecimalFormat("#,##0.00"); + public static final String EXPLORER_TX_PREFIX = "https://explorer.lbry.com/tx"; public static boolean isNull(String value) { return value == null; @@ -153,6 +159,14 @@ public final class Helper { } } + public static Double parseDouble(Object value, double defaultValue) { + try { + return Double.parseDouble(String.valueOf(value)); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + public static String formatDuration(long duration) { long seconds = duration; long hours = Double.valueOf(Math.floor(seconds / 3600.0)).longValue(); @@ -536,4 +550,9 @@ public final class Helper { public static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } + + public static void applyHtmlForTextView(TextView textView) { + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setText(HtmlCompat.fromHtml(textView.getText().toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)); + } } 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 0cb95e66..680f6956 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbry.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -33,6 +33,7 @@ import io.lbry.browser.model.File; import io.lbry.browser.model.Tag; import io.lbry.browser.model.Transaction; import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.User; import io.lbry.lbrysdk.Utils; import okhttp3.OkHttpClient; 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 7995cb97..b6f0a595 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbryio.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java @@ -30,6 +30,7 @@ import io.lbry.browser.exceptions.LbryioRequestException; import io.lbry.browser.exceptions.LbryioResponseException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.WalletSync; +import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.model.lbryinc.User; import io.lbry.lbrysdk.Utils; @@ -57,6 +58,10 @@ public final class Lbryio { public static String AUTH_TOKEN; private static boolean generatingAuthToken = false; + public static List allRewards = new ArrayList<>(); + public static List unclaimedRewards = new ArrayList<>(); + public static double totalUnclaimedRewardAmount = 0; + public static Response call(String resource, String action, Context context) throws LbryioRequestException, LbryioResponseException { return call(resource, action, null, Helper.METHOD_GET, context); } @@ -312,4 +317,20 @@ public final class Lbryio { public static boolean isFollowing(Claim claim) { return subscriptions.contains(Subscription.fromClaim(claim)); } + + public static void updateRewardsLists(List rewards) { + synchronized (lock) { + allRewards.clear(); + unclaimedRewards.clear(); + totalUnclaimedRewardAmount = 0; + for (int i = 0; i < rewards.size(); i++) { + Reward reward = rewards.get(i); + allRewards.add(reward); + if (!reward.isClaimed()) { + unclaimedRewards.add(reward); + totalUnclaimedRewardAmount += reward.getRewardAmount(); + } + } + } + } } diff --git a/app/src/main/res/drawable-anydpi/ic_check_circle.xml b/app/src/main/res/drawable-anydpi/ic_check_circle.xml new file mode 100644 index 00000000..bbdc3744 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_check_circle.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_check_circle.png b/app/src/main/res/drawable-hdpi/ic_check_circle.png new file mode 100644 index 00000000..ff8c711e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_check_circle.png b/app/src/main/res/drawable-mdpi/ic_check_circle.png new file mode 100644 index 00000000..9545c7f0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check_circle.png b/app/src/main/res/drawable-xhdpi/ic_check_circle.png new file mode 100644 index 00000000..1b7f7109 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_circle.png b/app/src/main/res/drawable-xxhdpi/ic_check_circle.png new file mode 100644 index 00000000..eaf7a66e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_check_circle.png differ diff --git a/app/src/main/res/layout/activity_verification.xml b/app/src/main/res/layout/activity_verification.xml index 9714897a..04fddc26 100644 --- a/app/src/main/res/layout/activity_verification.xml +++ b/app/src/main/res/layout/activity_verification.xml @@ -33,5 +33,14 @@ android:id="@+id/verification_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_manager.xml b/app/src/main/res/layout/fragment_channel_manager.xml index 1f3fcf06..f53e3429 100644 --- a/app/src/main/res/layout/fragment_channel_manager.xml +++ b/app/src/main/res/layout/fragment_channel_manager.xml @@ -1,7 +1,10 @@ + android:layout_height="match_parent" + android:focusable="true" + android:focusableInTouchMode="true" + android:focusedByDefault="true"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_verification_manual.xml b/app/src/main/res/layout/fragment_verification_manual.xml new file mode 100644 index 00000000..5e43ceae --- /dev/null +++ b/app/src/main/res/layout/fragment_verification_manual.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_verification_phone.xml b/app/src/main/res/layout/fragment_verification_phone.xml new file mode 100644 index 00000000..2010f9c1 --- /dev/null +++ b/app/src/main/res/layout/fragment_verification_phone.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_channel.xml b/app/src/main/res/layout/list_item_channel.xml index 98e830e1..0469c640 100644 --- a/app/src/main/res/layout/list_item_channel.xml +++ b/app/src/main/res/layout/list_item_channel.xml @@ -158,6 +158,16 @@ android:fontFamily="@font/inter" android:textSize="11sp" android:textFontWeight="300" /> + diff --git a/app/src/main/res/layout/list_item_channel_filter.xml b/app/src/main/res/layout/list_item_channel_filter.xml index eff97a43..63970ca2 100644 --- a/app/src/main/res/layout/list_item_channel_filter.xml +++ b/app/src/main/res/layout/list_item_channel_filter.xml @@ -38,6 +38,7 @@ android:layout_height="wrap_content" android:layout_centerInParent="true" android:fontFamily="@font/inter" + android:textAllCaps="true" android:textFontWeight="300" android:text="@string/all" /> diff --git a/app/src/main/res/layout/list_item_featured_search_result.xml b/app/src/main/res/layout/list_item_featured_search_result.xml index aba7ff6b..6c450388 100644 --- a/app/src/main/res/layout/list_item_featured_search_result.xml +++ b/app/src/main/res/layout/list_item_featured_search_result.xml @@ -174,6 +174,17 @@ android:textColor="@android:color/white" android:textSize="11sp" android:textFontWeight="300" /> + diff --git a/app/src/main/res/layout/list_item_reward.xml b/app/src/main/res/layout/list_item_reward.xml new file mode 100644 index 00000000..b6e6c4d4 --- /dev/null +++ b/app/src/main/res/layout/list_item_reward.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_stream.xml b/app/src/main/res/layout/list_item_stream.xml index 3507a289..635ac178 100644 --- a/app/src/main/res/layout/list_item_stream.xml +++ b/app/src/main/res/layout/list_item_stream.xml @@ -173,7 +173,16 @@ android:fontFamily="@font/inter" android:textSize="11sp" android:textFontWeight="300" /> - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27e148a4..455e8b2a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,7 +41,7 @@ Please select up to 5 creators to continue. %1$d remaining... Done - ALL + All Discover new channels @@ -134,6 +134,8 @@ Support Abandon Channel + Channel Update + Publish Update Wallet Sync Sync status @@ -224,6 +226,19 @@ Password Enable sync The wallet sync operation could not be completed at this time. Please try again later. If this problem persists, please send an email to hello@lbry.com. + Phone Number + Please enter your phone number. + Not interested + Manual Reward Verification + This account must undergo review before you can participate in the rewards program. This can take anywhere from several minutes to several days. + If you continue to see this message, please request to be verified on the <a href="https://discordapp.com/invite/Z3bERWA">LBRY Discord server</a>. + Please enjoy free content in the meantime! + Verify Phone Number + Please enter the verification code sent to %1$s + 0000 + Verify + Please enter a valid phone number. + Please enter the verification code sent to your phone number. You have not added any tags yet. Add tags to improve discovery. @@ -261,7 +276,8 @@ Deposit cannot be higher than your balance. The channel save request failed. Please try again. The channel was successfully saved. - The channel is pending publish on the blockchain. You will be able to access or edit the channel in a few moments. + The claim is pending publish on the blockchain. You will be able to access or edit the claim in a few moments. + Pending A minimum deposit of %1$s credit is required. A minimum deposit of %1$s credits is required. @@ -271,6 +287,27 @@ Are you sure you want to delete the selected channels? + + LBRY credits allow you to publish or purchase content. + You can obtain free credits worth $%1$s after you provide an email address. + <a href="https://lbry.com/faq/earn-credits">Learn more</a>. + Get started + abc123 + Claim + Please enter a custom reward code to claim. + Unclaimed + Custom Code + up to + Are you a supermodel or rockstar that received a custom reward code? Claim it here. + + You have claimed %1$s credit as a reward. + You have claimed %1$s credits as a reward. + + + %1$s available credit + %1$s available credits + + diff --git a/build.gradle b/build.gradle index 2e8e3867..ad32875c 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.2.0' // NOTE: Do not place your application dependencies here; they belong