From 78dd5879011955148cdbad28fdabf77ae19b71a5 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 3 May 2020 13:46:31 +0100 Subject: [PATCH] Send tip dialog. Channel page share and follow/unfollow. --- .../io/lbry/browser/FileViewActivity.java | 70 +- .../java/io/lbry/browser/MainActivity.java | 16 +- .../browser/dialog/SendTipDialogFragment.java | 186 ++++ .../java/io/lbry/browser/model/Claim.java | 4 + .../browser/model/lbryinc/Subscription.java | 8 + .../tasks/wallet/SupportCreateTask.java | 63 ++ .../browser/ui/channel/ChannelFragment.java | 142 ++- .../ui/following/FollowingFragment.java | 5 +- .../browser/ui/wallet/WalletFragment.java | 8 +- .../java/io/lbry/browser/utils/Helper.java | 1 + .../main/java/io/lbry/browser/utils/Lbry.java | 9 +- .../java/io/lbry/browser/utils/Lbryio.java | 19 + .../main/res/layout/activity_file_view.xml | 904 +++++++++--------- app/src/main/res/layout/content_main.xml | 1 + app/src/main/res/layout/dialog_send_tip.xml | 125 +++ app/src/main/res/layout/fragment_channel.xml | 93 +- .../res/layout/fragment_channel_about.xml | 7 +- .../res/layout/fragment_channel_content.xml | 2 +- app/src/main/res/layout/fragment_wallet.xml | 2 +- app/src/main/res/values/strings.xml | 15 +- 20 files changed, 1201 insertions(+), 479 deletions(-) create mode 100644 app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java create mode 100644 app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java create mode 100644 app/src/main/res/layout/dialog_send_tip.xml diff --git a/app/src/main/java/io/lbry/browser/FileViewActivity.java b/app/src/main/java/io/lbry/browser/FileViewActivity.java index 9537722c..e2cb8a3b 100644 --- a/app/src/main/java/io/lbry/browser/FileViewActivity.java +++ b/app/src/main/java/io/lbry/browser/FileViewActivity.java @@ -18,7 +18,6 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.widget.NestedScrollView; import androidx.preference.PreferenceManager; @@ -35,12 +34,16 @@ import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.snackbar.Snackbar; +import java.math.BigDecimal; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import io.lbry.browser.adapter.ClaimListAdapter; import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.dialog.SendTipDialogFragment; import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.ClaimCacheKey; @@ -62,12 +65,13 @@ public class FileViewActivity extends AppCompatActivity { private static boolean startingShareActivity; private SimpleExoPlayer player; + private boolean hasLoadedFirstBalance; private boolean loadFilePending; private boolean resolving; private Claim claim; private ClaimListAdapter relatedContentAdapter; private File file; - private BroadcastReceiver sdkReadyReceiver; + private BroadcastReceiver sdkReceiver; private Player.EventListener fileViewPlayerListener; private View buttonShareAction; @@ -117,7 +121,7 @@ public class FileViewActivity extends AppCompatActivity { resolveUrl(url); } - registerSdkReadyReceiver(); + registerSdkReceiver(); fileViewPlayerListener = new Player.EventListener() { @Override @@ -129,6 +133,7 @@ public class FileViewActivity extends AppCompatActivity { }; initUi(); + onWalletBalanceUpdated(); renderClaim(); } @@ -181,19 +186,38 @@ public class FileViewActivity extends AppCompatActivity { } } - private void registerSdkReadyReceiver() { + private void registerSdkReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(MainActivity.ACTION_SDK_READY); - sdkReadyReceiver = new BroadcastReceiver() { + filter.addAction(MainActivity.ACTION_WALLET_BALANCE_UPDATED); + sdkReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - // authenticate after we receive the sdk ready event - if (loadFilePending) { - loadFile(); + String action = intent.getAction(); + if (action.equalsIgnoreCase(MainActivity.ACTION_SDK_READY)) { + // authenticate after we receive the sdk ready event + if (loadFilePending) { + loadFile(); + } + } else if (action.equalsIgnoreCase(MainActivity.ACTION_WALLET_BALANCE_UPDATED)) { + onWalletBalanceUpdated(); } } }; - registerReceiver(sdkReadyReceiver, filter); + registerReceiver(sdkReceiver, filter); + } + + private void onWalletBalanceUpdated() { + if (Lbry.SDK_READY) { + if (!hasLoadedFirstBalance) { + findViewById(R.id.floating_balance_loading).setVisibility(View.GONE); + findViewById(R.id.floating_balance_value).setVisibility(View.VISIBLE); + hasLoadedFirstBalance = true; + } + + ((TextView) findViewById(R.id.floating_balance_value)).setText( + Helper.shortCurrencyFormat(Lbry.walletBalance.getAvailable().doubleValue())); + } } private String getStreamingUrl() { @@ -308,6 +332,32 @@ public class FileViewActivity extends AppCompatActivity { } }); + findViewById(R.id.file_view_action_tip).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + SendTipDialogFragment dialog = SendTipDialogFragment.newInstance(); + dialog.setClaim(claim); + dialog.setListener(new SendTipDialogFragment.SendTipListener() { + @Override + public void onTipSent(BigDecimal amount) { + double sentAmount = amount.doubleValue(); + String message = getResources().getQuantityString( + R.plurals.you_sent_a_tip, sentAmount == 1.0 ? 1 : 2, + new DecimalFormat("#,###.##").format(sentAmount)); + Snackbar.make(findViewById(R.id.file_view_claim_display_area), message, Snackbar.LENGTH_LONG).show(); + } + }); + dialog.show(getSupportFragmentManager(), SendTipDialogFragment.TAG); + } + } + }); + RecyclerView relatedContentList = findViewById(R.id.file_view_related_content_list); relatedContentList.setNestedScrollingEnabled(false); LinearLayoutManager llm = new LinearLayoutManager(this); @@ -506,7 +556,7 @@ public class FileViewActivity extends AppCompatActivity { } protected void onDestroy() { - Helper.unregisterReceiver(sdkReadyReceiver, this); + Helper.unregisterReceiver(sdkReceiver, this); if (MainActivity.appPlayer != null && fileViewPlayerListener != null) { MainActivity.appPlayer.removeListener(fileViewPlayerListener); } diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index 066ee069..e1fda658 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -118,6 +118,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static SimpleExoPlayer appPlayer; public static Claim nowPlayingClaim; + public static boolean startingShareActivity = false; public static boolean startingFileViewActivity = false; public static boolean mainActive = false; private boolean enteringPIPMode = false; @@ -147,6 +148,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static final String ACTION_NOW_PLAYING_CLAIM_UPDATED = "io.lbry.browser.Broadcast.NowPlayingClaimUpdated"; public static final String ACTION_NOW_PLAYING_CLAIM_CLEARED = "io.lbry.browser.Broadcast.NowPlayingClaimCleared"; public static final String ACTION_OPEN_ALL_CONTENT_TAG = "io.lbry.browser.Broadcast.OpenAllContentTag"; + public static final String ACTION_WALLET_BALANCE_UPDATED = "io.lbry.browser.Broadcast.WalletBalanceUpdated"; // preference keys public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode"; @@ -385,7 +387,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener params.put("url", url); params.put("claim", getCachedClaimForUrl(url)); openFragment(ChannelFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params); - setWunderbarValue(url); + setWunderbarValue(url); // TODO: Move this to fragment onResume } private Claim getCachedClaimForUrl(String url) { @@ -502,6 +504,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } Lbry.walletBalance = walletBalance; updateFloatingWalletBalance(); + + sendBroadcast(new Intent(ACTION_WALLET_BALANCE_UPDATED)); } @Override @@ -1354,6 +1358,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener @Override protected void onUserLeaveHint() { + if (startingShareActivity) { + // share activity triggered this, so reset the flag at this point + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + startingShareActivity = false; + } + }, 1000); + return; + } enterPIPMode(); } diff --git a/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java new file mode 100644 index 00000000..1f97d496 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java @@ -0,0 +1,186 @@ +package io.lbry.browser.dialog; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import androidx.core.text.HtmlCompat; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import java.math.BigDecimal; +import java.text.DecimalFormat; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.wallet.SupportCreateTask; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import lombok.Setter; + +public class SendTipDialogFragment extends BottomSheetDialogFragment implements WalletBalanceListener { + public static final String TAG = "SendTipDialog"; + + private MaterialButton sendButton; + private View cancelLink; + private TextInputEditText inputAmount; + private View inlineBalanceContainer; + private TextView inlineBalanceValue; + private ProgressBar sendProgress; + + @Setter + private SendTipListener listener; + @Setter + private Claim claim; + + public static SendTipDialogFragment newInstance() { + return new SendTipDialogFragment(); + } + + private void disableControls() { + getDialog().setCanceledOnTouchOutside(false); + sendButton.setEnabled(false); + cancelLink.setEnabled(false); + } + private void enableControls() { + getDialog().setCanceledOnTouchOutside(true); + sendButton.setEnabled(true); + cancelLink.setEnabled(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_send_tip, container, false); + + inputAmount = view.findViewById(R.id.tip_input_amount); + inlineBalanceContainer = view.findViewById(R.id.tip_inline_balance_container); + inlineBalanceValue = view.findViewById(R.id.tip_inline_balance_value); + sendProgress = view.findViewById(R.id.tip_send_progress); + cancelLink = view.findViewById(R.id.tip_cancel_link); + sendButton = view.findViewById(R.id.tip_send); + + inputAmount.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + inputAmount.setHint(hasFocus ? getString(R.string.zero) : ""); + inlineBalanceContainer.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + TextView infoText = view.findViewById(R.id.tip_info); + infoText.setMovementMethod(LinkMovementMethod.getInstance()); + infoText.setText(HtmlCompat.fromHtml( + Claim.TYPE_CHANNEL.equalsIgnoreCase(claim.getValueType()) ? + getString(R.string.send_tip_info_channel, claim.getTitleOrName()) : + getString(R.string.send_tip_info_content, claim.getTitleOrName()), + HtmlCompat.FROM_HTML_MODE_LEGACY)); + + sendButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String amountString = Helper.getValue(inputAmount.getText()); + if (Helper.isNullOrEmpty(amountString)) { + showError(getString(R.string.invalid_amount)); + return; + } + + BigDecimal amount = new BigDecimal(amountString); + if (amount.doubleValue() > Lbry.walletBalance.getAvailable().doubleValue()) { + showError(getString(R.string.insufficient_balance)); + return; + } + + SupportCreateTask task = new SupportCreateTask(claim.getClaimId(), amount, true, sendProgress, new GenericTaskHandler() { + @Override + public void beforeStart() { + disableControls(); + } + + @Override + public void onSuccess() { + enableControls(); + if (listener != null) { + listener.onTipSent(amount); + } + + dismiss(); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + enableControls(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + cancelLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + String channel = null; + if (Claim.TYPE_CHANNEL.equalsIgnoreCase(claim.getValueType())) { + channel = claim.getTitleOrName(); + } else if (claim.getSigningChannel() != null) { + channel = claim.getPublisherTitle(); + } + ((TextView) view.findViewById(R.id.tip_send_title)).setText( + Helper.isNullOrEmpty(channel) ? getString(R.string.send_a_tip) : getString(R.string.send_a_tip_to, channel) + ); + + onWalletBalanceUpdated(Lbry.walletBalance); + + return view; + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).addWalletBalanceListener(this); + } + } + + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).removeWalletBalanceListener(this); + } + super.onPause(); + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineBalanceValue != null) { + inlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void showError(String message) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint( + ContextCompat.getColor(getContext(), R.color.red)).show(); + } + + public interface SendTipListener { + void onTipSent(BigDecimal amount); + } +} 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 85b879d2..88e988d5 100644 --- a/app/src/main/java/io/lbry/browser/model/Claim.java +++ b/app/src/main/java/io/lbry/browser/model/Claim.java @@ -125,6 +125,7 @@ public class Claim { return "Anonymous"; } + public List getTags() { return (value != null && value.getTags() != null) ? new ArrayList<>(value.getTags()) : new ArrayList<>(); } @@ -142,6 +143,9 @@ public class Claim { public String getTitle() { return (value != null) ? value.getTitle() : null; } + public String getTitleOrName() { + return (value != null) ? value.getTitle() : getName(); + } public long getDuration() { if (value instanceof StreamMetadata) { diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java b/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java index 59901634..d15e262c 100644 --- a/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java @@ -1,5 +1,6 @@ package io.lbry.browser.model.lbryinc; +import io.lbry.browser.model.Claim; import lombok.Getter; import lombok.Setter; @@ -19,6 +20,13 @@ public class Subscription { this.url = url; } + public static Subscription fromClaim(Claim claim) { + return new Subscription(claim.getName(), claim.getPermanentUrl()); + } + public String toString() { + return url; + } + public boolean equals(Object o) { return (o instanceof Subscription) && url.equalsIgnoreCase(((Subscription) o).getUrl()); } diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java new file mode 100644 index 00000000..55d28eeb --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java @@ -0,0 +1,63 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; +import android.view.View; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class SupportCreateTask extends AsyncTask { + private String claimId; + private BigDecimal amount; + private boolean tip; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public SupportCreateTask(String claimId, BigDecimal amount, boolean tip, View progressView, GenericTaskHandler handler) { + this.claimId = claimId; + this.amount = amount; + this.tip = tip; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + if (handler != null) { + handler.beforeStart(); + } + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("amount", new DecimalFormat(Helper.SDK_AMOUNT_FORMAT).format(amount.doubleValue())); + options.put("tip", tip); + Lbry.genericApiCall(Lbry.METHOD_SUPPORT_CREATE, options); + } catch (ApiCallException 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/ChannelFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java index be9ffb58..d08be4fc 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 @@ -1,6 +1,7 @@ package io.lbry.browser.ui.channel; import android.content.Context; +import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; @@ -10,6 +11,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; @@ -19,24 +21,35 @@ import androidx.viewpager2.widget.ViewPager2; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; +import java.math.BigDecimal; +import java.text.DecimalFormat; import java.util.List; import java.util.Map; 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.model.Claim; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.ChannelSubscribeTask; import io.lbry.browser.tasks.ResolveTask; import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.ui.following.FollowingFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; import lombok.SneakyThrows; public class ChannelFragment extends BaseFragment { private Claim claim; - private boolean resolving; + private boolean subscribing; private String url; private View layoutResolving; @@ -50,6 +63,11 @@ public class ChannelFragment extends BaseFragment { private TabLayout tabLayout; private ViewPager2 tabPager; + private View buttonShare; + private View buttonTip; + private View buttonFollowUnfollow; + private SolidIconView iconFollowUnfollow; + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_channel, container, false); @@ -64,13 +82,125 @@ public class ChannelFragment extends BaseFragment { textTitle = root.findViewById(R.id.channel_view_title); textFollowerCount = root.findViewById(R.id.channel_view_follower_count); + 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); + iconFollowUnfollow = root.findViewById(R.id.channel_view_icon_follow_unfollow); + tabPager = root.findViewById(R.id.channel_view_pager); tabLayout = root.findViewById(R.id.channel_view_tabs); tabPager.setSaveEnabled(false); + buttonShare.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + try { + String shareUrl = LbryUri.parse( + !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()).toTvString(); + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_TEXT, shareUrl); + + MainActivity.startingShareActivity = true; + Intent shareUrlIntent = Intent.createChooser(shareIntent, getString(R.string.share_lbry_content)); + shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(shareUrlIntent); + } catch (LbryUriException ex) { + // pass + } + } + } + }); + + buttonTip.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + SendTipDialogFragment dialog = SendTipDialogFragment.newInstance(); + dialog.setClaim(claim); + dialog.setListener(new SendTipDialogFragment.SendTipListener() { + @Override + public void onTipSent(BigDecimal amount) { + double sentAmount = amount.doubleValue(); + String message = getResources().getQuantityString( + R.plurals.you_sent_a_tip, sentAmount == 1.0 ? 1 : 2, + new DecimalFormat("#,###.##").format(sentAmount)); + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + dialog.show(((MainActivity) context).getSupportFragmentManager(), SendTipDialogFragment.TAG); + } + } + } + }); + + buttonFollowUnfollow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + if (subscribing) { + return; + } + + subscribing = true; + boolean isFollowing = Lbryio.isFollowing(claim); + Subscription subscription = Subscription.fromClaim(claim); + buttonFollowUnfollow.setEnabled(false); + new ChannelSubscribeTask(getContext(), claim.getClaimId(), subscription, isFollowing, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + if (isFollowing) { + Lbryio.removeSubscription(subscription); + Lbryio.addCachedResolvedSubscription(claim); + } else { + Lbryio.addSubscription(subscription); + Lbryio.addCachedResolvedSubscription(claim); + } + buttonFollowUnfollow.setEnabled(true); + subscribing = false; + checkIsFollowing(); + FollowingFragment.resetClaimSearchContent = true; + + if (Lbry.SDK_READY) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).saveSharedUserState(); + } + } + } + + @Override + public void onError(Exception exception) { + buttonFollowUnfollow.setEnabled(true); + subscribing = false; + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + }); + return root; } + private void checkIsFollowing() { + if (claim != null) { + boolean isFollowing = Lbryio.isFollowing(claim); + if (iconFollowUnfollow != null) { + iconFollowUnfollow.setText(isFollowing ? R.string.fa_heart_broken : R.string.fa_heart); + iconFollowUnfollow.setTextColor(ContextCompat.getColor(getContext(), isFollowing ? R.color.foreground : R.color.red)); + } + } + } + public void onResume() { super.onResume(); checkParams(); @@ -148,6 +278,7 @@ public class ChannelFragment extends BaseFragment { return; } + checkIsFollowing(); layoutDisplayArea.setVisibility(View.VISIBLE); String thumbnailUrl = claim.getThumbnailUrl(); @@ -169,7 +300,14 @@ public class ChannelFragment extends BaseFragment { textAlpha.setText(claim.getName().substring(1, 2)); } - tabPager.setAdapter(new ChannelPagerAdapter(claim, (MainActivity) getContext())); + try { + if (tabPager.getAdapter() == null) { + tabPager.setAdapter(new ChannelPagerAdapter(claim, (MainActivity) getContext())); + } + } catch (IllegalStateException ex) { + // TODO: Fix why this is happening + // pass + } new TabLayoutMediator(tabLayout, tabPager, new TabLayoutMediator.TabConfigurationStrategy() { @Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) { 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 88971ca0..756657c7 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 @@ -53,6 +53,7 @@ public class FollowingFragment extends BaseFragment implements FetchSubscriptionsTask.FetchSubscriptionsHandler, ChannelItemSelectionListener, SharedPreferences.OnSharedPreferenceChangeListener { + public static boolean resetClaimSearchContent; private static final int SUGGESTED_PAGE_SIZE = 45; private static final int MIN_SUGGESTED_SUBSCRIBE_COUNT = 5; @@ -353,7 +354,9 @@ public class FollowingFragment extends BaseFragment implements } else { fetchAndResolveChannelList(); } - fetchClaimSearchContent(); + + fetchClaimSearchContent(resetClaimSearchContent); + resetClaimSearchContent = false; showSubscribedContent(); } 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 5b169fb5..f631dfbc 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 @@ -28,6 +28,7 @@ import com.google.android.material.snackbar.Snackbar; import com.google.android.material.switchmaterial.SwitchMaterial; import com.google.android.material.textfield.TextInputEditText; +import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.List; @@ -341,13 +342,16 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W // wallet_send task String recipientAddress = Helper.getValue(inputSendAddress.getText()); String amountString = Helper.getValue(inputSendAmount.getText()); - String amount = String.valueOf(Double.valueOf(amountString)); + String amount = new DecimalFormat(Helper.SDK_AMOUNT_FORMAT).format(new BigDecimal(amountString).doubleValue()); disableSendControls(); WalletSendTask task = new WalletSendTask(recipientAddress, amount, walletSendProgress, new WalletSendTask.WalletSendHandler() { @Override public void onSuccess() { - String message = getResources().getQuantityString(R.plurals.you_sent_credits, Double.valueOf(amount) == 1.0 ? 1 : 2, amountString); + double sentAmount = Double.valueOf(amount); + String message = getResources().getQuantityString( + R.plurals.you_sent_credits, sentAmount == 1.0 ? 1 : 2, + new DecimalFormat("#,###.##").format(sentAmount)); Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); inputSendAddress.setText(null); inputSendAmount.setText(null); 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 782ef5d3..58926185 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -39,6 +39,7 @@ public final class Helper { public static final String METHOD_GET = "GET"; public static final String METHOD_POST = "POST"; public static final String ISO_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + public static final String SDK_AMOUNT_FORMAT = "0.0#######"; public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded"); public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); public static final int CONTENT_PAGE_SIZE = 25; 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 6f53fa30..bd697b96 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbry.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -80,6 +80,7 @@ public final class Lbry { public static final String METHOD_ADDRESS_LIST = "address_list"; public static final String METHOD_TRANSACTION_LIST = "transaction_list"; public static final String METHOD_UTXO_RELEASE = "utxo_release"; + public static final String METHOD_SUPPORT_CREATE = "support_create"; public static final String METHOD_SUPPORT_ABANDON = "support_abandon"; public static final String METHOD_SYNC_HASH = "sync_hash"; public static final String METHOD_SYNC_APPLY = "sync_apply"; @@ -148,7 +149,11 @@ public final class Lbry { if (value instanceof List) { value = Helper.jsonArrayFromList((List) value); } - jsonParams.put(param.getKey(), value); + if (value instanceof Double) { + jsonParams.put(param.getKey(), (double) value); + } else { + jsonParams.put(param.getKey(), value); + } } } catch (JSONException ex) { // pass @@ -412,7 +417,7 @@ public final class Lbry { try { response = parseResponse(apiCall(method, params)); } catch (LbryRequestException | LbryResponseException ex) { - throw new ApiCallException(String.format("Could not execute %s call", method), ex); + throw new ApiCallException(String.format("Could not execute %s call: %s", method, ex.getMessage()), ex); } return response; } 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 103d30cb..7995cb97 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbryio.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java @@ -293,4 +293,23 @@ public final class Lbryio { subscriptions.remove(subscription); } } + public static void addCachedResolvedSubscription(Claim claim) { + synchronized (lock) { + if (!cacheResolvedSubscriptions.contains(claim)) { + cacheResolvedSubscriptions.add(claim); + } + } + } + public static void removeCachedResolvedSubscription(Claim claim) { + synchronized (lock) { + cacheResolvedSubscriptions.remove(claim); + } + } + + public static boolean isFollowing(Subscription subscription) { + return subscriptions.contains(subscription); + } + public static boolean isFollowing(Claim claim) { + return subscriptions.contains(Subscription.fromClaim(claim)); + } } diff --git a/app/src/main/res/layout/activity_file_view.xml b/app/src/main/res/layout/activity_file_view.xml index 307a25b1..f2f7109c 100644 --- a/app/src/main/res/layout/activity_file_view.xml +++ b/app/src/main/res/layout/activity_file_view.xml @@ -6,452 +6,366 @@ android:layout_height="match_parent" android:background="@color/pageBackground" android:fitsSystemWindows="true"> - - - - + - - - - - - - - - - + + - - - - - - - - - - + android:layout_gravity="center_vertical" + android:fontFamily="@font/inter" + android:text="@string/loading_decentralized_data" + android:textSize="16sp" + android:textFontWeight="300" /> + - - + - + + + + + - - - - - - + android:layout_alignParentBottom="true" /> + + - - - + android:layout_height="wrap_content" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" + android:layout_marginLeft="24dp" + android:layout_centerVertical="true"> + android:textColor="@color/white" + android:textSize="18sp" + android:text="@string/unsupported_content" /> + - - + + - - + - + + + + + + + + + + + + + - + + + + + + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + android:src="@drawable/ic_following" + android:tint="@color/red" /> - + + + + + + + + + + + + - + android:layout_marginBottom="8dp"> + + + + + + + - - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index a9d18a88..f87abc38 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -19,6 +19,7 @@ diff --git a/app/src/main/res/layout/dialog_send_tip.xml b/app/src/main/res/layout/dialog_send_tip.xml new file mode 100644 index 00000000..c5a034b7 --- /dev/null +++ b/app/src/main/res/layout/dialog_send_tip.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml index 3bd2b4a2..36979755 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -123,14 +123,95 @@ + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_about.xml b/app/src/main/res/layout/fragment_channel_about.xml index d8e7e04d..a572155e 100644 --- a/app/src/main/res/layout/fragment_channel_about.xml +++ b/app/src/main/res/layout/fragment_channel_about.xml @@ -32,7 +32,8 @@ + android:layout_height="match_parent" + android:layout_marginTop="48dp"> diff --git a/app/src/main/res/layout/fragment_channel_content.xml b/app/src/main/res/layout/fragment_channel_content.xml index a376679b..9c30d04f 100644 --- a/app/src/main/res/layout/fragment_channel_content.xml +++ b/app/src/main/res/layout/fragment_channel_content.xml @@ -95,7 +95,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_centerVertical="true" - android:layout_alignParentRight="true" /> + android:layout_centerHorizontal="true" /> in your publishes in your supports Earn more tips by uploading cool videos - The background service is still initializing. You can explore and watch content in the mean time. + The background service is initializing... + The background service is still initializing. You can explore and watch content in the mean time. + You cannot do this right now because the background service is still initializing. A backup of your wallet is synced with lbry.tv Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet. @@ -193,6 +195,16 @@ You have not followed any tags yet. Get started by adding tags that you are interested in! We could not find new tags that you\'re not following. The \'%1$s\' tag has already been added. + Send a tip + Send a tip to %1$s + This will appear as a tip for %1$s, which will boost its ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. + This will appear as a tip for %1$s, which will boost the channel\'s ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. + Cancel + + + You sent %1$s credit as a tip, Mahalo! + You sent %1$s credits as a tip, Mahalo! + Please provide an email address. @@ -223,6 +235,7 @@ +