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 15651a8d..74f9df1b 100644 --- a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java @@ -297,17 +297,13 @@ public class ClaimListAdapter extends RecyclerView.Adapter { private List items; private Context context; + private boolean nested; + private float scale; @Setter private ClaimListAdapter.ClaimListItemListener listener; + @Setter + private ReplyClickListener replyListener; public CommentListAdapter(List items, Context context) { + this(items, context, false); + } + + public CommentListAdapter(List 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 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 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 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 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 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 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 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 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())); + } +} 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 a389e273..76b9f2fd 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 @@ -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; } } } diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java index 09948944..90ce3dfc 100644 --- a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java @@ -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,19 +1419,22 @@ 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); - } + loadComments(); } } } + 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() { View root = getView(); if (root != null) { @@ -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 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,9 +2859,15 @@ public class FileViewFragment extends BaseFragment implements @Override public void onSuccess(Comment createdComment) { inputComment.setText(null); + clearReplyToComment(); + if (commentListAdapter != null) { createdComment.setPoster(comment.getPoster()); - commentListAdapter.insert(0, createdComment); + if (!Helper.isNullOrEmpty(createdComment.getParentId())) { + commentListAdapter.addReply(createdComment); + } else { + commentListAdapter.insert(0, createdComment); + } } afterPostComment(); checkNoComments(); diff --git a/app/src/main/res/layout/container_comment_form.xml b/app/src/main/res/layout/container_comment_form.xml index 41b16047..1948ef6d 100644 --- a/app/src/main/res/layout/container_comment_form.xml +++ b/app/src/main/res/layout/container_comment_form.xml @@ -1,118 +1,187 @@ - - - - - - - + - - - - + android:orientation="vertical" + android:padding="16dp"> - - - - + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + - - + + + + + + + - - + android:layout_marginLeft="8dp"> + + - + + - - + + + + + + + + + + + + + + + + + + + - - - \ No newline at end of file + android:layout_gravity="right" + android:layout_marginTop="2dp" + android:fontFamily="@font/inter" + android:textColor="@color/lightGrey" + android:textFontWeight="300" + android:textSize="12sp" /> + + + + + + + \ 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 bfac6090..af46977f 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -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"/> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_comment.xml b/app/src/main/res/layout/list_item_comment.xml index 882ab739..bf068177 100644 --- a/app/src/main/res/layout/list_item_comment.xml +++ b/app/src/main/res/layout/list_item_comment.xml @@ -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" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ea3f5a1..5805f84f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,6 +88,8 @@ Post comment with tip? Your comment was successfully posted. Please select a channel to repost on. + Reply + Replying to %1$s Post for %1$s credit Post for %1$s credits