Comments: remember last position

This commit is contained in:
Yuriy Liskov
2024-01-18 19:36:48 +02:00
parent 4cf11bdebf
commit 65cf138b3f
7 changed files with 166 additions and 48 deletions

View File

@ -1,5 +1,6 @@
package com.stfalcon.chatkit.messages;
import android.annotation.SuppressLint;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.util.SparseArray;
@ -575,6 +576,7 @@ public class MessageHolders {
final ImageLoader imageLoader,
final View.OnClickListener onMessageClickListener,
final View.OnLongClickListener onMessageLongClickListener,
final View.OnFocusChangeListener onMessageFocusChangeListener,
final DateFormatter.Formatter dateHeadersFormatter,
final SparseArray<MessagesListAdapter.OnMessageViewClickListener> clickListenersArray) {
@ -583,6 +585,7 @@ public class MessageHolders {
((MessageHolders.BaseMessageViewHolder) holder).imageLoader = imageLoader;
holder.itemView.setOnLongClickListener(onMessageLongClickListener);
holder.itemView.setOnClickListener(onMessageClickListener);
holder.itemView.setOnFocusChangeListener(onMessageFocusChangeListener);
for (int i = 0; i < clickListenersArray.size(); i++) {
final int key = clickListenersArray.keyAt(i);
@ -778,6 +781,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public void applyStyle(MessagesListStyle style) {
super.applyStyle(style);
@ -812,10 +816,11 @@ public class MessageHolders {
//wrapper.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
//wrapper.requestFocus();
//wrapper.setBackgroundResource(R.drawable.bgchange);
wrapper.setOnFocusChangeListener((v, hasFocus) -> {
//text.setBackgroundResource(hasFocus ? R.color.tg_selected_bg : R.color.transparent);
bubble.setBackgroundResource(hasFocus ? R.drawable.shape_incoming_message_focused : R.drawable.shape_incoming_message);
});
//wrapper.setOnFocusChangeListener((v, hasFocus) -> {
// //text.setBackgroundResource(hasFocus ? R.color.tg_selected_bg : R.color.transparent);
// bubble.setBackgroundResource(hasFocus ? R.drawable.shape_incoming_message_focused : R.drawable.shape_incoming_message);
//});
}
}
}
@ -861,6 +866,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public final void applyStyle(MessagesListStyle style) {
super.applyStyle(style);
@ -930,6 +936,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public final void applyStyle(MessagesListStyle style) {
super.applyStyle(style);
@ -1000,6 +1007,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public final void applyStyle(MessagesListStyle style) {
super.applyStyle(style);
@ -1062,6 +1070,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public void applyStyle(MessagesListStyle style) {
if (text != null) {
@ -1093,6 +1102,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public void applyStyle(MessagesListStyle style) {
if (text != null) {
@ -1143,6 +1153,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public void applyStyle(MessagesListStyle style) {
if (time != null) {
@ -1191,6 +1202,7 @@ public class MessageHolders {
}
}
@SuppressLint("WrongConstant")
@Override
public void applyStyle(MessagesListStyle style) {
if (time != null) {

View File

@ -16,6 +16,7 @@
package com.stfalcon.chatkit.messages;
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@ -63,6 +64,7 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
private OnLoadMoreListener loadMoreListener;
private OnMessageClickListener<MESSAGE> onMessageClickListener;
private OnMessageViewClickListener<MESSAGE> onMessageViewClickListener;
private OnMessageViewFocusListener<MESSAGE> onMessageViewFocusListener;
private OnMessageLongClickListener<MESSAGE> onMessageLongClickListener;
private OnMessageViewLongClickListener<MESSAGE> onMessageViewLongClickListener;
private ImageLoader imageLoader;
@ -71,7 +73,6 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
private DateFormatter.Formatter dateHeadersFormatter;
private SparseArray<OnMessageViewClickListener> viewClickListenersArray = new SparseArray<>();
private boolean isDateHeaderEnabled;
private int currentPosition = -1;
private MESSAGE focusedMessage;
/**
@ -111,6 +112,7 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
holders.bind(holder, wrapper.item, wrapper.isSelected, imageLoader,
getMessageClickListener(wrapper),
getMessageLongClickListener(wrapper),
getMessageFocusChangeListener(wrapper),
dateHeadersFormatter,
viewClickListenersArray);
@ -380,6 +382,12 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
}
}
public void scrollToPosition(int position) {
if (layoutManager != null && !items.isEmpty()) {
layoutManager.scrollToPosition(position);
}
}
public void scrollToTop() {
if (layoutManager != null && !items.isEmpty()) {
layoutManager.scrollToPosition(items.size() - 1);
@ -567,6 +575,10 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
this.onMessageViewLongClickListener = onMessageViewLongClickListener;
}
public void setOnMessageViewFocusListener(OnMessageViewFocusListener<MESSAGE> onMessageViewFocusListener) {
this.onMessageViewFocusListener = onMessageViewFocusListener;
}
/**
* Set callback to be invoked when list scrolled to top.
*
@ -629,6 +641,14 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
}
}
public int getMessagePosition(MESSAGE message) {
if (message == null) {
return -1;
}
return getMessagePositionById(message.getId());
}
@SuppressWarnings("unchecked")
private int getMessagePositionById(String id) {
for (int i = 0; i < items.size(); i++) {
@ -702,6 +722,12 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
}
}
private void notifyMessageViewFocused(View view, MESSAGE message) {
if (onMessageViewFocusListener != null) {
onMessageViewFocusListener.onMessageViewFocus(view, message);
}
}
private View.OnClickListener getMessageClickListener(final Wrapper<MESSAGE> wrapper) {
return new DebouncedOnClickListener(3_000) {
@Override
@ -737,9 +763,13 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
private View.OnFocusChangeListener getMessageFocusChangeListener(final Wrapper<MESSAGE> wrapper) {
return (v, hasFocus) -> {
// Change background of the focused message
// NOTE: you can have only one focus listener
View bubble = v.findViewById(R.id.bubble);
bubble.setBackgroundResource(hasFocus ? R.drawable.shape_incoming_message_focused : R.drawable.shape_incoming_message);
if (hasFocus) {
MESSAGE message = (wrapper.item);
currentPosition = getMessagePositionById(message.getId());
notifyMessageViewFocused(v, wrapper.item);
}
};
}
@ -860,6 +890,10 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
void onMessageViewClick(View view, MESSAGE message);
}
public interface OnMessageViewFocusListener<MESSAGE extends IMessage> {
void onMessageViewFocus(View view, MESSAGE message);
}
/**
* Interface definition for a callback to be invoked when message item is long clicked.
*/
@ -1067,6 +1101,7 @@ public class MessagesListAdapter<MESSAGE extends IMessage>
}
}
@SuppressLint("WrongConstant")
@Override
public void applyStyle(MessagesListStyle style) {
if (text != null) {

View File

@ -1,13 +1,19 @@
package com.liskovsoft.smartyoutubetv2.common.app.models.playback.controllers;
import android.content.Context;
import android.util.Pair;
import com.liskovsoft.mediaserviceinterfaces.CommentsService;
import com.liskovsoft.mediaserviceinterfaces.data.CommentGroup;
import com.liskovsoft.mediaserviceinterfaces.data.CommentItem;
import com.liskovsoft.mediaserviceinterfaces.data.MediaItemMetadata;
import com.liskovsoft.sharedutils.helpers.Helpers;
import com.liskovsoft.sharedutils.mylogger.Log;
import com.liskovsoft.sharedutils.rx.RxHelper;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.PlayerEventListenerHelper;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.controllers.SuggestionsController.MetadataListener;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.CommentsReceiver;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.CommentsReceiver.Backup;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.CommentsReceiverImpl;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.UiOptionItem;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.AppDialogPresenter;
@ -21,6 +27,7 @@ public class CommentsController extends PlayerEventListenerHelper implements Met
private String mLiveChatKey;
private String mCommentsKey;
private String mTitle;
private Pair<String, Backup> mBackup;
public CommentsController() {
}
@ -41,6 +48,9 @@ public class CommentsController extends PlayerEventListenerHelper implements Met
mLiveChatKey = metadata != null && metadata.getLiveChatKey() != null ? metadata.getLiveChatKey() : null;
mCommentsKey = metadata != null && metadata.getCommentsKey() != null ? metadata.getCommentsKey() : null;
mTitle = metadata != null ? metadata.getTitle() : null;
if (mBackup != null && !Helpers.equals(mBackup.first, mCommentsKey)) {
mBackup = null;
}
}
private void openCommentsDialog() {
@ -58,35 +68,45 @@ public class CommentsController extends PlayerEventListenerHelper implements Met
CommentsReceiver commentsReceiver = new CommentsReceiverImpl(getContext()) {
@Override
public void onLoadMore(String nextCommentsKey) {
loadComments(this, nextCommentsKey);
public void onLoadMore(CommentGroup commentGroup) {
loadComments(this, commentGroup.getNextCommentsKey());
}
@Override
public void onStart() {
if (mBackup != null) {
loadBackup(mBackup.second);
return;
}
loadComments(this, mCommentsKey);
}
@Override
public void onCommentClicked(String nestedCommentsKey) {
if (nestedCommentsKey == null) {
public void onCommentClicked(CommentItem commentItem) {
if (commentItem.getNestedCommentsKey() == null) {
return;
}
CommentsReceiver nestedReceiver = new CommentsReceiverImpl(getContext()) {
@Override
public void onLoadMore(String nextCommentsKey) {
loadComments(this, nextCommentsKey);
public void onLoadMore(CommentGroup commentGroup) {
loadComments(this, commentGroup.getNextCommentsKey());
}
@Override
public void onStart() {
loadComments(this, nestedCommentsKey);
loadComments(this, commentItem.getNestedCommentsKey());
}
};
showDialog(nestedReceiver, title);
}
@Override
public void onFinish(Backup backup) {
mBackup = new Pair<>(mCommentsKey, backup);
}
};
showDialog(commentsReceiver, title);
@ -102,6 +122,7 @@ public class CommentsController extends PlayerEventListenerHelper implements Met
@Override
public void onEngineReleased() {
disposeActions();
mBackup = null;
}
@Override

View File

@ -1,16 +1,21 @@
package com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui;
import com.liskovsoft.mediaserviceinterfaces.data.CommentGroup;
import com.liskovsoft.mediaserviceinterfaces.data.CommentItem;
public interface CommentsReceiver {
interface Callback {
void onCommentGroup(CommentGroup commentGroup);
void onBackup(Backup backup);
}
interface Backup {}
void addCommentGroup(CommentGroup commentGroup);
void loadBackup(Backup backup);
void setCallback(Callback callback);
void onLoadMore(String nextCommentsKey);
void onLoadMore(CommentGroup commentGroup);
void onStart();
void onCommentClicked(String nestedCommentsKey);
void onCommentClicked(CommentItem commentItem);
void onFinish(Backup backup);
String getLoadingMessage();
String getErrorMessage();
}

View File

@ -2,6 +2,7 @@ package com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui;
import android.content.Context;
import com.liskovsoft.mediaserviceinterfaces.data.CommentGroup;
import com.liskovsoft.mediaserviceinterfaces.data.CommentItem;
import com.liskovsoft.smartyoutubetv2.common.R;
public abstract class CommentsReceiverImpl implements CommentsReceiver {
@ -19,21 +20,33 @@ public abstract class CommentsReceiverImpl implements CommentsReceiver {
}
}
@Override
public void loadBackup(Backup backup) {
if (mCallback != null) {
mCallback.onBackup(backup);
}
}
@Override
public void setCallback(Callback callback) {
mCallback = callback;
}
@Override
public void onLoadMore(String nextCommentsKey) {
public void onLoadMore(CommentGroup commentGroup) {
}
@Override
public void onCommentClicked(String nestedCommentsKey) {
public void onCommentClicked(CommentItem commentItem) {
}
@Override
public void onFinish(Backup backup) {
}
@Override
public String getLoadingMessage() {
return mContext.getString(R.string.loading);

View File

@ -9,8 +9,11 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.preference.DialogPreference;
import com.bumptech.glide.Glide;
import com.liskovsoft.mediaserviceinterfaces.data.CommentGroup;
import com.liskovsoft.mediaserviceinterfaces.data.CommentItem;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.CommentsReceiver;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.CommentsReceiver.Backup;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.CommentsReceiver.Callback;
import com.liskovsoft.smartyoutubetv2.common.prefs.PlayerTweaksData;
import com.liskovsoft.smartyoutubetv2.tv.R;
import com.liskovsoft.smartyoutubetv2.tv.ui.mod.leanback.preference.LeanbackPreferenceDialogFragment;
@ -27,10 +30,22 @@ public class CommentsPreferenceDialogFragment extends LeanbackPreferenceDialogFr
private boolean mIsTransparent;
private CommentsReceiver mCommentsReceiver;
private CharSequence mDialogTitle;
private String mNextCommentsKey;
private CommentGroup mCurrentGroup;
private List<ChatItemMessage> mBackupMessages;
private ChatItemMessage mFocusedMessage;
private static class CommentsBackup implements Backup {
public CommentsBackup(List<ChatItemMessage> backupMessages, ChatItemMessage focusedMessage, CommentGroup currentGroup) {
this.backupMessages = backupMessages;
this.focusedMessage = focusedMessage;
this.currentGroup = currentGroup;
}
public final List<ChatItemMessage> backupMessages;
public final ChatItemMessage focusedMessage;
public final CommentGroup currentGroup;
}
public static CommentsPreferenceDialogFragment newInstance(CommentsReceiver commentsReceiver, String key) {
final Bundle args = new Bundle(1);
args.putString(ARG_KEY, key);
@ -76,39 +91,50 @@ public class CommentsPreferenceDialogFragment extends LeanbackPreferenceDialogFr
.apply(ViewUtil.glideOptions())
.circleCrop() // resize image
.into(imageView));
adapter.setLoadMoreListener((page, totalItemsCount) -> mCommentsReceiver.onLoadMore(mNextCommentsKey));
adapter.setOnMessageViewClickListener((v, message) -> {
mFocusedMessage = message;
mCommentsReceiver.onCommentClicked(message.getNestedCommentsKey());
});
adapter.setLoadMoreListener((page, totalItemsCount) -> mCommentsReceiver.onLoadMore(mCurrentGroup));
adapter.setOnMessageViewClickListener((v, message) -> mCommentsReceiver.onCommentClicked(message.getCommentItem()));
adapter.setOnMessageViewFocusListener((view1, message) -> mFocusedMessage = message);
messagesList.setAdapter(adapter);
messagesList.requestFocus(); // hold focus even when there's no messages
adapter.enableStackFromEnd(true);
adapter.setLoadingMessage(mCommentsReceiver.getLoadingMessage());
mCommentsReceiver.setCallback(commentGroup -> {
if (commentGroup == null || commentGroup.getComments() == null) {
adapter.setLoadingMessage(mCommentsReceiver.getErrorMessage());
return;
}
for (CommentItem commentItem : commentGroup.getComments()) {
ChatItemMessage message = ChatItemMessage.from(commentItem);
adapter.addToStart(message, false);
if (mFocusedMessage == null && IMessage.checkMessage(message)) {
mFocusedMessage = message;
adapter.setFocusedMessage(message);
mCommentsReceiver.setCallback(new Callback() {
@Override
public void onCommentGroup(CommentGroup commentGroup) {
if (commentGroup == null || commentGroup.getComments() == null) {
adapter.setLoadingMessage(mCommentsReceiver.getErrorMessage());
return;
}
for (CommentItem commentItem : commentGroup.getComments()) {
ChatItemMessage message = ChatItemMessage.from(commentItem);
adapter.addToStart(message, false);
if (mFocusedMessage == null && IMessage.checkMessage(message)) {
mFocusedMessage = message;
adapter.setFocusedMessage(message);
}
}
if (adapter.getMessagesCount() == 0) { // No comments under the video
adapter.setLoadingMessage(mCommentsReceiver.getErrorMessage());
}
if (mCurrentGroup == null || mCurrentGroup.getNextCommentsKey() == null) {
adapter.scrollToTop();
}
mCurrentGroup = commentGroup;
}
if (adapter.getMessagesCount() == 0) { // No comments under the video
adapter.setLoadingMessage(mCommentsReceiver.getErrorMessage());
@Override
public void onBackup(Backup backup) {
mBackupMessages = ((CommentsBackup) backup).backupMessages;
mFocusedMessage = ((CommentsBackup) backup).focusedMessage;
mCurrentGroup = ((CommentsBackup) backup).currentGroup;
adapter.addToEnd(mBackupMessages, false);
adapter.setFocusedMessage(mFocusedMessage);
adapter.scrollToPosition(adapter.getMessagePosition(mFocusedMessage));
}
if (mNextCommentsKey == null) {
adapter.scrollToTop();
}
mNextCommentsKey = commentGroup.getNextCommentsKey();
});
if (mBackupMessages == null) {
@ -139,6 +165,12 @@ public class CommentsPreferenceDialogFragment extends LeanbackPreferenceDialogFr
backupMessages();
}
@Override
public void onDestroy() {
super.onDestroy();
mCommentsReceiver.onFinish(new CommentsBackup(mBackupMessages, mFocusedMessage, mCurrentGroup));
}
private void backupMessages() {
MessagesList messagesList = getView().findViewById(R.id.messagesList);

View File

@ -15,7 +15,7 @@ public class ChatItemMessage implements IMessage {
private CharSequence mText;
private ChatItemAuthor mAuthor;
private Date mCreatedAt;
private String mNestedCommentsKey;
private CommentItem mCommentItem;
public static ChatItemMessage from(ChatItem chatItem) {
ChatItemMessage message = new ChatItemMessage();
@ -43,7 +43,7 @@ public class ChatItemMessage implements IMessage {
}
message.mAuthor = ChatItemAuthor.from(commentItem);
message.mCreatedAt = new Date();
message.mNestedCommentsKey = commentItem.getNestedCommentsKey();
message.mCommentItem = commentItem;
return message;
}
@ -68,7 +68,7 @@ public class ChatItemMessage implements IMessage {
return mCreatedAt;
}
public String getNestedCommentsKey() {
return mNestedCommentsKey;
public CommentItem getCommentItem() {
return mCommentItem;
}
}