add comments to channel page

This commit is contained in:
Akinwale Ariwodola 2020-05-31 21:03:20 +01:00
parent a7101778d2
commit b1e8371d28
10 changed files with 1148 additions and 124 deletions

View file

@ -297,17 +297,13 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
return new ClaimListAdapter.ViewHolder(v);
}
public int getScaledValue(int value) {
return (int) (value * scale + 0.5f);
}
@Override
public void onBindViewHolder(ClaimListAdapter.ViewHolder vh, int position) {
int type = getItemViewType(position);
int paddingTop = position == 0 ? 16 : 8;
int paddingBottom = position == getItemCount() - 1 ? 16 : 8;
int paddingTopScaled = getScaledValue(paddingTop);
int paddingBottomScaled = getScaledValue(paddingBottom);
int paddingTopScaled = Helper.getScaledValue(paddingTop, scale);
int paddingBottomScaled = Helper.getScaledValue(paddingBottom, scale);
vh.itemView.setPadding(vh.itemView.getPaddingLeft(), paddingTopScaled, vh.itemView.getPaddingRight(), paddingBottomScaled);
Claim original = items.get(position);

View file

@ -11,6 +11,7 @@ import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
@ -28,12 +29,24 @@ import lombok.Setter;
public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.ViewHolder> {
private List<Comment> items;
private Context context;
private boolean nested;
private float scale;
@Setter
private ClaimListAdapter.ClaimListItemListener listener;
@Setter
private ReplyClickListener replyListener;
public CommentListAdapter(List<Comment> items, Context context) {
this(items, context, false);
}
public CommentListAdapter(List<Comment> items, Context context, boolean nested) {
this.items = new ArrayList<>(items);
this.context = context;
this.nested = nested;
if (context != null) {
scale = context.getResources().getDisplayMetrics().density;
}
for (Comment item : this.items) {
ClaimCacheKey key = new ClaimCacheKey();
key.setClaimId(item.getChannelId());
@ -43,6 +56,11 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
}
}
public void clearItems() {
items.clear();
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return items != null ? items.size() : 0;
@ -54,10 +72,21 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
Comment item = items.get(i);
if (item.getPoster() == null) {
LbryUri url = LbryUri.tryParse(String.format("%s#%s", item.getChannelName(), item.getChannelId()));
if (url != null) {
if (url != null && !urls.contains(url.toString())) {
urls.add(url.toString());
}
}
if (item.getReplies().size() > 0) {
for (int j = 0; j < item.getReplies().size(); j++) {
Comment reply = item.getReplies().get(j);
if (reply.getPoster() == null) {
LbryUri url = LbryUri.tryParse(String.format("%s#%s", reply.getChannelName(), reply.getChannelId()));
if (url != null && !urls.contains(url.toString())) {
urls.add(url.toString());
}
}
}
}
}
return urls;
}
@ -69,15 +98,19 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
protected View noThumbnailView;
protected TextView alphaView;
protected TextView commentTimeView;
protected View replyLink;
protected RecyclerView repliesList;
public ViewHolder (View v) {
super(v);
channelName = v.findViewById(R.id.comment_channel_name);
commentTimeView = v.findViewById(R.id.comment_time);
commentText = v.findViewById(R.id.comment_text);
replyLink = v.findViewById(R.id.comment_reply_link);
thumbnailView = v.findViewById(R.id.comment_thumbnail);
noThumbnailView = v.findViewById(R.id.comment_no_thumbnail);
alphaView = v.findViewById(R.id.comment_thumbnail_alpha);
repliesList = v.findViewById(R.id.comment_replies);
}
}
@ -88,9 +121,31 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
}
}
public void addReply(Comment comment) {
for (int i = 0; i < items.size(); i++) {
Comment parent = items.get(i);
if (parent.getId().equalsIgnoreCase(comment.getParentId())) {
parent.addReply(comment);
notifyDataSetChanged();
break;
}
}
}
public void updatePosterForComment(String channelId, Claim channel) {
for (int i = 0 ; i < items.size(); i++) {
Comment item = items.get(i);
List<Comment> replies = item.getReplies();
if (replies != null && replies.size() > 0) {
for (int j = 0; j < replies.size(); j++) {
Comment reply = item.getReplies().get(j);
if (channelId.equalsIgnoreCase(item.getChannelId())) {
reply.setPoster(channel);
break;
}
}
}
if (channelId.equalsIgnoreCase(item.getChannelId())) {
item.setPoster(channel);
break;
@ -107,10 +162,17 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Comment comment = items.get(position);
holder.itemView.setPadding(
nested ? Helper.getScaledValue(56, scale) : holder.itemView.getPaddingLeft(),
holder.itemView.getPaddingTop(),
nested ? 0 : holder.itemView.getPaddingRight(),
holder.itemView.getPaddingBottom());
holder.channelName.setText(comment.getChannelName());
holder.commentTimeView.setText(DateUtils.getRelativeTimeSpanString(
(comment.getTimestamp() * 1000), System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
holder.commentText.setText(comment.getText());
holder.replyLink.setVisibility(!nested ? View.VISIBLE : View.GONE);
boolean hasThumbnail = comment.getPoster() != null && !Helper.isNullOrEmpty(comment.getPoster().getThumbnailUrl());
holder.thumbnailView.setVisibility(hasThumbnail ? View.VISIBLE : View.INVISIBLE);
@ -122,6 +184,14 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
apply(RequestOptions.circleCropTransform()).into(holder.thumbnailView);
}
holder.alphaView.setText(comment.getChannelName() != null ? comment.getChannelName().substring(1, 2).toUpperCase() : null);
List<Comment> replies = comment.getReplies();
boolean hasReplies = replies != null && replies.size() > 0;
if (hasReplies) {
holder.repliesList.setLayoutManager(new LinearLayoutManager(context));
holder.repliesList.setAdapter(new CommentListAdapter(replies, context, true));
} else {
holder.repliesList.setAdapter(null);
}
holder.channelName.setOnClickListener(new View.OnClickListener() {
@Override
@ -131,5 +201,18 @@ public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.
}
}
});
holder.replyLink.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (replyListener != null) {
replyListener.onReplyClicked(comment);
}
}
});
}
public interface ReplyClickListener {
void onReplyClicked(Comment comment);
}
}

View file

@ -0,0 +1,705 @@
package io.lbry.browser.ui.channel;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatSpinner;
import androidx.core.text.HtmlCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
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 java.util.ArrayList;
import java.util.List;
import io.lbry.browser.BuildConfig;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.browser.adapter.ClaimListAdapter;
import io.lbry.browser.adapter.CommentListAdapter;
import io.lbry.browser.adapter.InlineChannelSpinnerAdapter;
import io.lbry.browser.listener.SdkStatusListener;
import io.lbry.browser.listener.WalletBalanceListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.Comment;
import io.lbry.browser.model.WalletBalance;
import io.lbry.browser.tasks.CommentCreateWithTipTask;
import io.lbry.browser.tasks.CommentListHandler;
import io.lbry.browser.tasks.CommentListTask;
import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask;
import io.lbry.browser.tasks.claim.ClaimListResultHandler;
import io.lbry.browser.tasks.claim.ClaimListTask;
import io.lbry.browser.tasks.claim.ClaimResultHandler;
import io.lbry.browser.tasks.claim.ResolveTask;
import io.lbry.browser.tasks.lbryinc.LogPublishTask;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
import io.lbry.browser.utils.LbryAnalytics;
import io.lbry.browser.utils.LbryUri;
import io.lbry.browser.utils.Lbryio;
import lombok.Setter;
public class ChannelCommentsFragment extends Fragment implements SdkStatusListener, WalletBalanceListener {
@Setter
private Claim claim;
private CommentListAdapter commentListAdapter;
private Comment replyToComment;
private View containerReplyToComment;
private TextView textReplyingTo;
private TextView textReplyToBody;
private View buttonClearReplyToComment;
private boolean postingComment;
private boolean fetchingChannels;
private View progressLoadingChannels;
private View progressPostComment;
private InlineChannelSpinnerAdapter commentChannelSpinnerAdapter;
private AppCompatSpinner commentChannelSpinner;
private TextInputEditText inputComment;
private TextView textCommentLimit;
private MaterialButton buttonPostComment;
private ImageView commentPostAsThumbnail;
private View commentPostAsNoThumbnail;
private TextView commentPostAsAlpha;
private View inlineChannelCreator;
private TextInputEditText inlineChannelCreatorInputName;
private TextInputEditText inlineChannelCreatorInputDeposit;
private View inlineChannelCreatorInlineBalance;
private TextView inlineChannelCreatorInlineBalanceValue;
private View inlineChannelCreatorCancelLink;
private View inlineChannelCreatorProgress;
private MaterialButton inlineChannelCreatorCreateButton;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_channel_comments, container, false);
containerReplyToComment = root.findViewById(R.id.comment_form_reply_to_container);
textReplyingTo = root.findViewById(R.id.comment_form_replying_to_text);
textReplyToBody = root.findViewById(R.id.comment_form_reply_to_body);
buttonClearReplyToComment = root.findViewById(R.id.comment_form_clear_reply_to);
commentChannelSpinner = root.findViewById(R.id.comment_form_channel_spinner);
progressLoadingChannels = root.findViewById(R.id.comment_form_channels_loading);
progressPostComment = root.findViewById(R.id.comment_form_post_progress);
inputComment = root.findViewById(R.id.comment_form_body);
textCommentLimit = root.findViewById(R.id.comment_form_text_limit);
buttonPostComment = root.findViewById(R.id.comment_form_post);
commentPostAsThumbnail = root.findViewById(R.id.comment_form_thumbnail);
commentPostAsNoThumbnail = root.findViewById(R.id.comment_form_no_thumbnail);
commentPostAsAlpha = root.findViewById(R.id.comment_form_thumbnail_alpha);
inlineChannelCreator = root.findViewById(R.id.container_inline_channel_form_create);
inlineChannelCreatorInputName = root.findViewById(R.id.inline_channel_form_input_name);
inlineChannelCreatorInputDeposit = root.findViewById(R.id.inline_channel_form_input_deposit);
inlineChannelCreatorInlineBalance = root.findViewById(R.id.inline_channel_form_inline_balance_container);
inlineChannelCreatorInlineBalanceValue = root.findViewById(R.id.inline_channel_form_inline_balance_value);
inlineChannelCreatorProgress = root.findViewById(R.id.inline_channel_form_create_progress);
inlineChannelCreatorCancelLink = root.findViewById(R.id.inline_channel_form_cancel_link);
inlineChannelCreatorCreateButton = root.findViewById(R.id.inline_channel_form_create_button);
RecyclerView commentList = root.findViewById(R.id.channel_comments_list);
commentList.setLayoutManager(new LinearLayoutManager(getContext()));
initCommentForm(root);
setupInlineChannelCreator(
inlineChannelCreator,
inlineChannelCreatorInputName,
inlineChannelCreatorInputDeposit,
inlineChannelCreatorInlineBalance,
inlineChannelCreatorInlineBalanceValue,
inlineChannelCreatorCancelLink,
inlineChannelCreatorCreateButton,
inlineChannelCreatorProgress,
commentChannelSpinner,
commentChannelSpinnerAdapter
);
return root;
}
public void onResume() {
super.onResume();
Context context = getContext();
if (!Lbry.SDK_READY) {
if (context instanceof MainActivity) {
((MainActivity) context).addSdkStatusListener(this);
}
} else {
onSdkReady();
}
if (context instanceof MainActivity) {
((MainActivity) context).addWalletBalanceListener(this);
}
checkCommentSdkInitializing();
}
public void onStop() {
super.onStop();
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.removeSdkStatusListener(this);
activity.removeWalletBalanceListener(this);
}
}
private void checkCommentSdkInitializing() {
View root = getView();
if (root != null) {
TextView commentsSDKInitializing = root.findViewById(R.id.channel_comments_sdk_initializing);
Helper.setViewVisibility(commentsSDKInitializing, Lbry.SDK_READY ? View.GONE : View.VISIBLE);
}
}
@Override
public void onSdkReady() {
fetchChannels();
checkAndLoadComments();
}
private void checkAndLoadComments() {
View root = getView();
if (root != null) {
checkCommentSdkInitializing();
RecyclerView commentsList = root.findViewById(R.id.channel_comments_list);
if (commentsList == null || commentsList.getAdapter() == null || commentsList.getAdapter().getItemCount() == 0) {
loadComments();
}
}
}
private void loadComments() {
View root = getView();
ProgressBar relatedLoading = root.findViewById(R.id.channel_comments_progress);
if (claim != null && root != null) {
CommentListTask task = new CommentListTask(1, 500, claim.getClaimId(), relatedLoading, new CommentListHandler() {
@Override
public void onSuccess(List<Comment> comments, boolean hasReachedEnd) {
Context ctx = getContext();
View root = getView();
if (ctx != null && root != null) {
commentListAdapter = new CommentListAdapter(comments, ctx);
commentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
@Override
public void onClaimClicked(Claim claim) {
if (!Helper.isNullOrEmpty(claim.getName()) &&
claim.getName().startsWith("@") &&
ctx instanceof MainActivity) {
((MainActivity) ctx).openChannelClaim(claim);
}
}
});
commentListAdapter.setReplyListener(new CommentListAdapter.ReplyClickListener() {
@Override
public void onReplyClicked(Comment comment) {
setReplyToComment(comment);
}
});
RecyclerView relatedContentList = root.findViewById(R.id.channel_comments_list);
relatedContentList.setAdapter(commentListAdapter);
commentListAdapter.notifyDataSetChanged();
checkNoComments();
resolveCommentPosters();
}
}
@Override
public void onError(Exception error) {
// pass
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private void resolveCommentPosters() {
if (commentListAdapter != null) {
List<String> urlsToResolve = new ArrayList<>(commentListAdapter.getClaimUrlsToResolve());
if (urlsToResolve.size() > 0) {
ResolveTask task = new ResolveTask(urlsToResolve, Lbry.SDK_CONNECTION_STRING, null, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
if (commentListAdapter != null) {
for (Claim claim : claims) {
if (claim.getClaimId() != null) {
commentListAdapter.updatePosterForComment(claim.getClaimId(), claim);
}
}
commentListAdapter.notifyDataSetChanged();
}
}
@Override
public void onError(Exception error) {
// pass
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
@Override
public void onWalletBalanceUpdated(WalletBalance walletBalance) {
if (walletBalance != null && inlineChannelCreatorInlineBalanceValue != null) {
inlineChannelCreatorInlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue()));
}
}
private void fetchChannels() {
if (Lbry.ownChannels != null && Lbry.ownChannels.size() > 0) {
updateChannelList(Lbry.ownChannels);
return;
}
fetchingChannels = true;
disableChannelSpinner();
ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, progressLoadingChannels, new ClaimListResultHandler() {
@Override
public void onSuccess(List<Claim> claims) {
Lbry.ownChannels = new ArrayList<>(claims);
updateChannelList(Lbry.ownChannels);
enableChannelSpinner();
fetchingChannels = false;
}
@Override
public void onError(Exception error) {
enableChannelSpinner();
fetchingChannels = false;
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void disableChannelSpinner() {
Helper.setViewEnabled(commentChannelSpinner, false);
hideInlineChannelCreator();
}
private void enableChannelSpinner() {
Helper.setViewEnabled(commentChannelSpinner, true);
if (commentChannelSpinner != null) {
Claim selectedClaim = (Claim) commentChannelSpinner.getSelectedItem();
if (selectedClaim != null) {
if (selectedClaim.isPlaceholder()) {
showInlineChannelCreator();
} else {
hideInlineChannelCreator();
}
}
}
}
private void showInlineChannelCreator() {
Helper.setViewVisibility(inlineChannelCreator, View.VISIBLE);
}
private void hideInlineChannelCreator() {
Helper.setViewVisibility(inlineChannelCreator, View.GONE);
}
private void updateChannelList(List<Claim> channels) {
if (commentChannelSpinnerAdapter == null) {
Context context = getContext();
if (context != null) {
commentChannelSpinnerAdapter = new InlineChannelSpinnerAdapter(context, R.layout.spinner_item_channel, new ArrayList<>(channels));
commentChannelSpinnerAdapter.addPlaceholder(false);
commentChannelSpinnerAdapter.notifyDataSetChanged();
}
} else {
commentChannelSpinnerAdapter.clear();
commentChannelSpinnerAdapter.addAll(channels);
commentChannelSpinnerAdapter.addPlaceholder(false);
commentChannelSpinnerAdapter.notifyDataSetChanged();
}
if (commentChannelSpinner != null) {
commentChannelSpinner.setAdapter(commentChannelSpinnerAdapter);
}
if (commentChannelSpinnerAdapter != null && commentChannelSpinner != null) {
if (commentChannelSpinnerAdapter.getCount() > 1) {
commentChannelSpinner.setSelection(1);
}
}
}
private void initCommentForm(View root) {
double amount = Comment.COST / Lbryio.LBCUSDRate;
String buttonText = getResources().getQuantityString(R.plurals.post_for_credits, amount == 1 ? 1 : 2, Helper.LBC_CURRENCY_FORMAT.format(amount));
buttonPostComment.setText(buttonText);
textCommentLimit.setText(String.format("%d / %d", Helper.getValue(inputComment.getText()).length(), Comment.MAX_LENGTH));
buttonClearReplyToComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clearReplyToComment();
}
});
buttonPostComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (!Lbry.SDK_READY) {
Snackbar.make(root.findViewById(R.id.channel_comments_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show();
return;
}
validateAndCheckPostComment(amount);
}
});
inputComment.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) {
int len = charSequence.length();
textCommentLimit.setText(String.format("%d / %d", len, Comment.MAX_LENGTH));
}
@Override
public void afterTextChanged(Editable editable) {
}
});
commentChannelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
Object item = adapterView.getItemAtPosition(position);
if (item instanceof Claim) {
Claim claim = (Claim) item;
if (claim.isPlaceholder()) {
if (!fetchingChannels) {
showInlineChannelCreator();
}
} else {
hideInlineChannelCreator();
updatePostAsChannel(claim);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
private void validateAndCheckPostComment(double amount) {
String comment = Helper.getValue(inputComment.getText());
Claim channel = (Claim) commentChannelSpinner.getSelectedItem();
if (Helper.isNullOrEmpty(comment)) {
showError(getString(R.string.please_enter_comment));
return;
}
if (channel == null || Helper.isNullOrEmpty(channel.getClaimId())) {
showError(getString(R.string.please_select_channel));
return;
}
if (Lbry.walletBalance == null || amount > Lbry.walletBalance.getAvailable().doubleValue()) {
showError(getString(R.string.insufficient_balance));
return;
}
Context context = getContext();
if (context != null) {
String confirmText = getResources().getQuantityString(
R.plurals.confirm_post_comment,
amount == 1 ? 1 : 2,
Helper.LBC_CURRENCY_FORMAT.format(amount),
claim.getTitleOrName());
AlertDialog.Builder builder = new AlertDialog.Builder(context).
setTitle(R.string.post_comment).
setMessage(confirmText)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
postComment(amount);
}
}).setNegativeButton(R.string.no, null);
builder.show();
}
}
private void updatePostAsChannel(Claim channel) {
boolean hasThumbnail = !Helper.isNullOrEmpty(channel.getThumbnailUrl());
Helper.setViewVisibility(commentPostAsThumbnail, hasThumbnail ? View.VISIBLE : View.INVISIBLE);
Helper.setViewVisibility(commentPostAsNoThumbnail, !hasThumbnail ? View.VISIBLE : View.INVISIBLE);
Helper.setViewText(commentPostAsAlpha, channel.getName() != null ? channel.getName().substring(1, 2).toUpperCase() : null);
Context context = getContext();
int bgColor = Helper.generateRandomColorForValue(channel.getClaimId());
Helper.setIconViewBackgroundColor(commentPostAsNoThumbnail, bgColor, false, context);
if (hasThumbnail && context != null) {
Glide.with(context.getApplicationContext()).
asBitmap().
load(channel.getThumbnailUrl()).
apply(RequestOptions.circleCropTransform()).
into(commentPostAsThumbnail);
}
}
private void beforePostComment() {
postingComment = true;
Helper.setViewEnabled(commentChannelSpinner, false);
Helper.setViewEnabled(inputComment, false);
Helper.setViewEnabled(buttonClearReplyToComment, false);
Helper.setViewEnabled(buttonPostComment, false);
}
private void afterPostComment() {
Helper.setViewEnabled(commentChannelSpinner, true);
Helper.setViewEnabled(inputComment, true);
Helper.setViewEnabled(buttonClearReplyToComment, true);
Helper.setViewEnabled(buttonPostComment, true);
postingComment = false;
}
private Comment buildPostComment() {
Comment comment = new Comment();
Claim channel = (Claim) commentChannelSpinner.getSelectedItem();
comment.setClaimId(claim.getClaimId());
comment.setChannelId(channel.getClaimId());
comment.setChannelName(channel.getName());
comment.setText(Helper.getValue(inputComment.getText()));
comment.setPoster(channel);
if (replyToComment != null) {
comment.setParentId(replyToComment.getId());
}
return comment;
}
private void setReplyToComment(Comment comment) {
replyToComment = comment;
Helper.setViewText(textReplyingTo, getString(R.string.replying_to, comment.getChannelName()));
Helper.setViewText(textReplyToBody, comment.getText());
Helper.setViewVisibility(containerReplyToComment, View.VISIBLE);
inputComment.requestFocus();
Context context = getContext();
if (context != null) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(inputComment, InputMethodManager.SHOW_FORCED);
}
}
private void clearReplyToComment() {
Helper.setViewText(textReplyingTo, null);
Helper.setViewText(textReplyToBody, null);
Helper.setViewVisibility(containerReplyToComment, View.GONE);
replyToComment = null;
}
private void postComment(double tipAmount) {
if (postingComment) {
return;
}
Comment comment = buildPostComment();
// only use 2 decimal places
BigDecimal amount = new BigDecimal(new DecimalFormat(Helper.PLAIN_CURRENCY_FORMAT_PATTERN).format(tipAmount));
beforePostComment();
CommentCreateWithTipTask task = new CommentCreateWithTipTask(comment, amount, progressPostComment, new CommentCreateWithTipTask.CommentCreateWithTipHandler() {
@Override
public void onSuccess(Comment createdComment) {
inputComment.setText(null);
clearReplyToComment();
if (commentListAdapter != null) {
createdComment.setPoster(comment.getPoster());
if (!Helper.isNullOrEmpty(createdComment.getParentId())) {
commentListAdapter.addReply(createdComment);
} else {
commentListAdapter.insert(0, createdComment);
}
}
afterPostComment();
checkNoComments();
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).showMessage(R.string.comment_posted);
}
}
@Override
public void onError(Exception error) {
showError(error.getMessage());
afterPostComment();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void showError(String message) {
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).showError(message);
}
}
private void checkNoComments() {
View root = getView();
if (root != null) {
Helper.setViewVisibility(root.findViewById(R.id.channel_no_comments),
commentListAdapter == null || commentListAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
}
private void setupInlineChannelCreator(
View container,
TextInputEditText inputChannelName,
TextInputEditText inputDeposit,
View inlineBalanceView,
TextView inlineBalanceValue,
View linkCancel,
MaterialButton buttonCreate,
View progressView,
AppCompatSpinner channelSpinner,
InlineChannelSpinnerAdapter channelSpinnerAdapter) {
inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
Helper.setViewVisibility(inlineBalanceView, hasFocus ? View.VISIBLE : View.INVISIBLE);
}
});
linkCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Helper.setViewText(inputChannelName, null);
Helper.setViewText(inputDeposit, null);
Helper.setViewVisibility(container, View.GONE);
}
});
buttonCreate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// validate deposit and channel name
String channelNameString = Helper.normalizeChannelName(Helper.getValue(inputChannelName.getText()));
Claim claimToSave = new Claim();
claimToSave.setName(channelNameString);
String channelName = claimToSave.getName().startsWith("@") ? claimToSave.getName().substring(1) : claimToSave.getName();
String depositString = Helper.getValue(inputDeposit.getText());
if ("@".equals(channelName) || Helper.isNullOrEmpty(channelName)) {
showError(getString(R.string.please_enter_channel_name));
return;
}
if (!LbryUri.isNameValid(channelName)) {
showError(getString(R.string.channel_name_invalid_characters));
return;
}
if (Helper.channelExists(channelName)) {
showError(getString(R.string.channel_name_already_created));
return;
}
double depositAmount = 0;
try {
depositAmount = Double.valueOf(depositString);
} catch (NumberFormatException ex) {
// pass
showError(getString(R.string.please_enter_valid_deposit));
return;
}
if (depositAmount == 0) {
String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT));
showError(error);
return;
}
if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) {
showError(getString(R.string.deposit_more_than_balance));
return;
}
ChannelCreateUpdateTask task = new ChannelCreateUpdateTask(
claimToSave, new BigDecimal(depositString), false, progressView, new ClaimResultHandler() {
@Override
public void beforeStart() {
Helper.setViewEnabled(inputChannelName, false);
Helper.setViewEnabled(inputDeposit, false);
Helper.setViewEnabled(buttonCreate, false);
Helper.setViewEnabled(linkCancel, false);
}
@Override
public void onSuccess(Claim claimResult) {
if (!BuildConfig.DEBUG) {
LogPublishTask logPublishTask = new LogPublishTask(claimResult);
logPublishTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
// channel created
Bundle bundle = new Bundle();
bundle.putString("claim_id", claimResult.getClaimId());
bundle.putString("claim_name", claimResult.getName());
LbryAnalytics.logEvent(LbryAnalytics.EVENT_CHANNEL_CREATE, bundle);
// add the claim to the channel list and set it as the selected item
if (channelSpinnerAdapter != null) {
channelSpinnerAdapter.add(claimResult);
}
if (channelSpinner != null && channelSpinnerAdapter != null) {
channelSpinner.setSelection(channelSpinnerAdapter.getCount() - 1);
}
Helper.setViewEnabled(inputChannelName, true);
Helper.setViewEnabled(inputDeposit, true);
Helper.setViewEnabled(buttonCreate, true);
Helper.setViewEnabled(linkCancel, true);
}
@Override
public void onError(Exception error) {
Helper.setViewEnabled(inputChannelName, true);
Helper.setViewEnabled(inputDeposit, true);
Helper.setViewEnabled(buttonCreate, true);
Helper.setViewEnabled(linkCancel, true);
showError(error.getMessage());
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
Helper.setViewText(inlineBalanceValue, Helper.shortCurrencyFormat(Lbry.walletBalance.getAvailable().doubleValue()));
}
}

View file

@ -446,7 +446,11 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
new TabLayoutMediator(tabLayout, tabPager, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setText(position == 0 ? R.string.content : R.string.about);
switch (position) {
case 0: tab.setText(R.string.content); break;
case 1: tab.setText(R.string.about); break;
case 2: tab.setText(R.string.comments); break;
}
}
}).attach();
} catch (IllegalStateException ex) {
@ -516,6 +520,13 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
// pass
}
return aboutFragment;
case 2:
ChannelCommentsFragment commentsFragment = ChannelCommentsFragment.class.newInstance();
if (channelClaim != null) {
commentsFragment.setClaim(channelClaim);
}
return commentsFragment;
}
return null;
@ -527,7 +538,7 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen
@Override
public int getItemCount() {
return 2;
return 3;
}
}
}

View file

@ -12,7 +12,6 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.format.DateUtils;
@ -22,6 +21,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
@ -200,6 +200,12 @@ public class FileViewFragment extends BaseFragment implements
private WebView webView;
private boolean webViewAdded;
private Comment replyToComment;
private View containerReplyToComment;
private TextView textReplyingTo;
private TextView textReplyToBody;
private View buttonClearReplyToComment;
private boolean postingComment;
private boolean fetchingChannels;
private View progressLoadingChannels;
@ -232,6 +238,11 @@ public class FileViewFragment extends BaseFragment implements
layoutDisplayArea = root.findViewById(R.id.file_view_claim_display_area);
buttonPublishSomething = root.findViewById(R.id.nothing_at_location_publish_button);
containerReplyToComment = root.findViewById(R.id.comment_form_reply_to_container);
textReplyingTo = root.findViewById(R.id.comment_form_replying_to_text);
textReplyToBody = root.findViewById(R.id.comment_form_reply_to_body);
buttonClearReplyToComment = root.findViewById(R.id.comment_form_clear_reply_to);
commentChannelSpinner = root.findViewById(R.id.comment_form_channel_spinner);
progressLoadingChannels = root.findViewById(R.id.comment_form_channels_loading);
progressPostComment = root.findViewById(R.id.comment_form_post_progress);
@ -494,7 +505,14 @@ public class FileViewFragment extends BaseFragment implements
View root = getView();
if (root != null) {
if (relatedContentAdapter != null) {
relatedContentAdapter.clearItems();
}
if (commentListAdapter != null) {
commentListAdapter.clearItems();
}
((RecyclerView) root.findViewById(R.id.file_view_related_content_list)).setAdapter(null);
((RecyclerView) root.findViewById(R.id.file_view_comments_list)).setAdapter(null);
}
if (MainActivity.appPlayer != null) {
MainActivity.appPlayer.setPlayWhenReady(false);
@ -641,6 +659,7 @@ public class FileViewFragment extends BaseFragment implements
} else {
onSdkReady();
}
checkCommentSdkInitializing();
}
public void onStop() {
@ -1400,17 +1419,20 @@ public class FileViewFragment extends BaseFragment implements
private void checkAndLoadComments() {
View root = getView();
if (root != null) {
checkCommentSdkInitializing();
RecyclerView commentsList = root.findViewById(R.id.file_view_comments_list);
if (commentsList == null || commentsList.getAdapter() == null || commentsList.getAdapter().getItemCount() == 0) {
TextView commentsSDKInitializing = root.findViewById(R.id.file_view_comments_sdk_initializing);
if (Lbry.SDK_READY) {
Helper.setViewVisibility(commentsSDKInitializing, View.GONE);
loadComments();
} else {
Helper.setViewVisibility(commentsSDKInitializing, View.VISIBLE);
}
}
}
private void checkCommentSdkInitializing() {
View root = getView();
if (root != null) {
TextView commentsSDKInitializing = root.findViewById(R.id.file_view_comments_sdk_initializing);
Helper.setViewVisibility(commentsSDKInitializing, Lbry.SDK_READY ? View.GONE : View.VISIBLE);
}
}
private void showUnsupportedView() {
@ -1956,7 +1978,7 @@ public class FileViewFragment extends BaseFragment implements
View root = getView();
ProgressBar relatedLoading = root.findViewById(R.id.file_view_comments_progress);
if (claim != null && root != null) {
CommentListTask relatedTask = new CommentListTask(1, 999, claim.getClaimId(), relatedLoading, new CommentListHandler() {
CommentListTask task = new CommentListTask(1, 500, claim.getClaimId(), relatedLoading, new CommentListHandler() {
@Override
public void onSuccess(List<Comment> comments, boolean hasReachedEnd) {
Context ctx = getContext();
@ -1973,6 +1995,12 @@ public class FileViewFragment extends BaseFragment implements
}
}
});
commentListAdapter.setReplyListener(new CommentListAdapter.ReplyClickListener() {
@Override
public void onReplyClicked(Comment comment) {
setReplyToComment(comment);
}
});
RecyclerView relatedContentList = root.findViewById(R.id.file_view_comments_list);
relatedContentList.setAdapter(commentListAdapter);
@ -1988,7 +2016,7 @@ public class FileViewFragment extends BaseFragment implements
// pass
}
});
relatedTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@ -2648,6 +2676,13 @@ public class FileViewFragment extends BaseFragment implements
buttonPostComment.setText(buttonText);
textCommentLimit.setText(String.format("%d / %d", Helper.getValue(inputComment.getText()).length(), Comment.MAX_LENGTH));
buttonClearReplyToComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clearReplyToComment();
}
});
buttonPostComment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@ -2762,12 +2797,14 @@ public class FileViewFragment extends BaseFragment implements
postingComment = true;
Helper.setViewEnabled(commentChannelSpinner, false);
Helper.setViewEnabled(inputComment, false);
Helper.setViewEnabled(buttonClearReplyToComment, false);
Helper.setViewEnabled(buttonPostComment, false);
}
private void afterPostComment() {
Helper.setViewEnabled(commentChannelSpinner, true);
Helper.setViewEnabled(inputComment, true);
Helper.setViewEnabled(buttonClearReplyToComment, true);
Helper.setViewEnabled(buttonPostComment, true);
postingComment = false;
}
@ -2780,10 +2817,34 @@ public class FileViewFragment extends BaseFragment implements
comment.setChannelName(channel.getName());
comment.setText(Helper.getValue(inputComment.getText()));
comment.setPoster(channel);
if (replyToComment != null) {
comment.setParentId(replyToComment.getId());
}
return comment;
}
private void setReplyToComment(Comment comment) {
replyToComment = comment;
Helper.setViewText(textReplyingTo, getString(R.string.replying_to, comment.getChannelName()));
Helper.setViewText(textReplyToBody, comment.getText());
Helper.setViewVisibility(containerReplyToComment, View.VISIBLE);
inputComment.requestFocus();
Context context = getContext();
if (context != null) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(inputComment, InputMethodManager.SHOW_FORCED);
}
}
private void clearReplyToComment() {
Helper.setViewText(textReplyingTo, null);
Helper.setViewText(textReplyToBody, null);
Helper.setViewVisibility(containerReplyToComment, View.GONE);
replyToComment = null;
}
private void postComment(double tipAmount) {
if (postingComment) {
return;
@ -2798,10 +2859,16 @@ public class FileViewFragment extends BaseFragment implements
@Override
public void onSuccess(Comment createdComment) {
inputComment.setText(null);
clearReplyToComment();
if (commentListAdapter != null) {
createdComment.setPoster(comment.getPoster());
if (!Helper.isNullOrEmpty(createdComment.getParentId())) {
commentListAdapter.addReply(createdComment);
} else {
commentListAdapter.insert(0, createdComment);
}
}
afterPostComment();
checkNoComments();

View file

@ -1,8 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/comment_form_card"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
@ -32,6 +39,67 @@
<include layout="@layout/container_inline_channel_form" />
<LinearLayout
android:id="@+id/comment_form_reply_to_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="horizontal"
android:visibility="gone">
<View
android:id="@+id/comment_form_reply_to_vertical_bar"
android:layout_width="3dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/nextLbryGreen" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginRight="40dp">
<TextView
android:id="@+id/comment_form_replying_to_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:text="@string/replying_to"
android:textColor="@color/lightGrey"
android:textSize="12sp" />
<TextView
android:id="@+id/comment_form_reply_to_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:lineSpacingMultiplier="1.05"
android:fontFamily="@font/inter"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<RelativeLayout
android:id="@+id/comment_form_clear_reply_to"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_alignParentRight="true"
android:layout_width="36dp"
android:layout_height="36dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_close"
android:tint="@color/foreground" />
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -115,4 +183,5 @@
android:layout_marginLeft="24dp"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View file

@ -95,7 +95,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:tabGravity="center" />
android:paddingLeft="110dp"
app:tabGravity="fill"
app:tabMode="scrollable"/>
<RelativeLayout
android:id="@+id/channel_view_icon_container"
android:layout_width="80dp"

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp">
<ProgressBar
android:id="@+id/channel_comments_progress"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:visibility="gone" />
</RelativeLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/channel_comments_area"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/container_comment_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/channel_no_comments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/inter"
android:text="@string/no_comments"
android:textFontWeight="300"
android:textSize="14sp"
android:visibility="gone" />
<TextView
android:id="@+id/channel_comments_sdk_initializing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/inter"
android:text="@string/sdk_initializing_comments"
android:textFontWeight="300"
android:textSize="14sp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/channel_comments_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View file

@ -78,11 +78,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:lineSpacingMultiplier="1.05"
android:lineSpacingMultiplier="1.1"
android:fontFamily="@font/inter"
android:textFontWeight="300"
android:textIsSelectable="true"
android:textSize="14sp" />
<TextView
android:id="@+id/comment_reply_link"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/inter"
android:text="@string/reply"
android:textFontWeight="300"
android:textColor="@color/lbryGreen"
android:textSize="12sp" />
</LinearLayout>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/comment_replies"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -88,6 +88,8 @@
<string name="post_comment">Post comment with tip?</string>
<string name="comment_posted">Your comment was successfully posted.</string>
<string name="please_select_repost_channel">Please select a channel to repost on.</string>
<string name="reply">Reply</string>
<string name="replying_to">Replying to %1$s</string>
<plurals name="post_for_credits">
<item quantity="one">Post for %1$s credit</item>
<item quantity="other">Post for %1$s credits</item>