comments viewer improvement
1. Viewing parent comments is now paginated, no more long waits
2. Liking a comment will no longer refresh the entire comment list
3. The pink tint on liked comments from v18 is restored
also removed FeedStoriesFetcher which was deprecated by c24fd01
This commit is contained in:
parent
4157c113f8
commit
3899b9adfa
@ -83,8 +83,8 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
|
||||
}
|
||||
};
|
||||
private final CommentCallback commentCallback;
|
||||
private CommentModel selected;
|
||||
private int selectedIndex;
|
||||
private CommentModel selected, toChangeLike;
|
||||
private int selectedIndex, likedIndex;
|
||||
|
||||
public CommentsAdapter(final CommentCallback commentCallback) {
|
||||
super(DIFF_CALLBACK);
|
||||
@ -106,10 +106,12 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
||||
final CommentModel commentModel = getItem(position);
|
||||
CommentModel commentModel = getItem(position);
|
||||
if (commentModel == null) return;
|
||||
final int type = getItemViewType(position);
|
||||
final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId());
|
||||
final boolean toLike = this.toChangeLike != null && this.toChangeLike.getId().equals(commentModel.getId());
|
||||
if (toLike) commentModel = this.toChangeLike;
|
||||
if (type == TYPE_PARENT) {
|
||||
final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder;
|
||||
viewHolder.bind(commentModel, selected, commentCallback);
|
||||
@ -174,6 +176,14 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
|
||||
notifyItemChanged(selectedIndex);
|
||||
}
|
||||
|
||||
public void setLiked(final CommentModel commentModel, final boolean liked) {
|
||||
likedIndex = getCurrentList().indexOf(commentModel);
|
||||
CommentModel newCommentModel = commentModel;
|
||||
newCommentModel.setLiked(liked);
|
||||
this.toChangeLike = newCommentModel;
|
||||
notifyItemChanged(likedIndex);
|
||||
}
|
||||
|
||||
public CommentModel getSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
@ -89,8 +89,7 @@ public final class ChildCommentViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final void setLiked(final boolean liked) {
|
||||
if (liked) {
|
||||
// container.setBackgroundColor(0x40FF69B4);
|
||||
return;
|
||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
|
||||
}
|
||||
}
|
||||
}
|
@ -89,8 +89,7 @@ public final class ParentCommentViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final void setLiked(final boolean liked) {
|
||||
if (liked) {
|
||||
// container.setBackgroundColor(0x40FF69B4);
|
||||
return;
|
||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
|
||||
}
|
||||
}
|
||||
}
|
@ -28,11 +28,12 @@ import static awais.instagrabber.utils.Utils.logCollector;
|
||||
public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
|
||||
private static final String TAG = "CommentsFetcher";
|
||||
|
||||
private final String shortCode;
|
||||
private final String shortCode, endCursor;
|
||||
private final FetchListener<List<CommentModel>> fetchListener;
|
||||
|
||||
public CommentsFetcher(final String shortCode, final FetchListener<List<CommentModel>> fetchListener) {
|
||||
public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener<List<CommentModel>> fetchListener) {
|
||||
this.shortCode = shortCode;
|
||||
this.endCursor = endCursor;
|
||||
this.fetchListener = fetchListener;
|
||||
}
|
||||
|
||||
@ -48,15 +49,17 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""}
|
||||
*/
|
||||
final List<CommentModel> commentModels = getParentComments();
|
||||
for (final CommentModel commentModel : commentModels) {
|
||||
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
|
||||
if (childCommentModels != null) {
|
||||
final int childCommentsLen = childCommentModels.size();
|
||||
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
|
||||
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
|
||||
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
|
||||
commentModel.setChildCommentModels(remoteChildComments);
|
||||
lastChild.setPageCursor(false, null);
|
||||
if (commentModels != null) {
|
||||
for (final CommentModel commentModel : commentModels) {
|
||||
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
|
||||
if (childCommentModels != null) {
|
||||
final int childCommentsLen = childCommentModels.size();
|
||||
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
|
||||
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
|
||||
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
|
||||
commentModel.setChildCommentModels(remoteChildComments);
|
||||
lastChild.setPageCursor(false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,11 +79,10 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
@NonNull
|
||||
private synchronized List<CommentModel> getChildComments(final String commentId) {
|
||||
final List<CommentModel> commentModels = new ArrayList<>();
|
||||
String endCursor = "";
|
||||
while (endCursor != null) {
|
||||
String childEndCursor = "";
|
||||
while (childEndCursor != null) {
|
||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
|
||||
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
|
||||
|
||||
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}";
|
||||
try {
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setUseCaches(false);
|
||||
@ -93,8 +95,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
.getJSONObject("edge_threaded_comments");
|
||||
|
||||
final JSONObject pageInfo = data.getJSONObject("page_info");
|
||||
endCursor = pageInfo.getString("end_cursor");
|
||||
if (TextUtils.isEmpty(endCursor)) endCursor = null;
|
||||
childEndCursor = pageInfo.getString("end_cursor");
|
||||
if (TextUtils.isEmpty(childEndCursor)) childEndCursor = null;
|
||||
|
||||
final JSONArray childComments = data.optJSONArray("edges");
|
||||
if (childComments != null) {
|
||||
@ -152,17 +154,14 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
@NonNull
|
||||
private synchronized List<CommentModel> getParentComments() {
|
||||
final List<CommentModel> commentModels = new ArrayList<>();
|
||||
String endCursor = "";
|
||||
while (endCursor != null) {
|
||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" +
|
||||
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
|
||||
|
||||
try {
|
||||
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}";
|
||||
try {
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setUseCaches(false);
|
||||
conn.connect();
|
||||
|
||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
|
||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null;
|
||||
else {
|
||||
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
|
||||
.getJSONObject("shortcode_media")
|
||||
@ -170,8 +169,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
"edge_media_to_parent_comment");
|
||||
|
||||
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
|
||||
endCursor = pageInfo.optString("end_cursor");
|
||||
if (TextUtils.isEmpty(endCursor)) endCursor = null;
|
||||
final String foundEndCursor = pageInfo.optString("end_cursor");
|
||||
|
||||
// final boolean containsToken = endCursor.contains("bifilter_token");
|
||||
// if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) {
|
||||
@ -219,6 +217,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
likedBy != null ? likedBy.optLong("count", 0) : 0,
|
||||
comment.getBoolean("viewer_has_liked"),
|
||||
profileModel);
|
||||
commentModel.setPageCursor(!TextUtils.isEmpty(foundEndCursor), TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor);
|
||||
JSONObject tempJsonObject;
|
||||
final JSONArray childCommentsArray;
|
||||
final int childCommentsLen;
|
||||
@ -280,9 +279,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
|
||||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
|
||||
new Pair<>("commentModelsList.size", commentModels.size()));
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
break;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return commentModels;
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +0,0 @@
|
||||
package awais.instagrabber.asyncs;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.FeedStoryModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.NetworkUtils;
|
||||
import awaisomereport.LogCollector.LogFile;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
|
||||
public final class FeedStoriesFetcher extends AsyncTask<Void, Void, FeedStoryModel[]> {
|
||||
private final FetchListener<FeedStoryModel[]> fetchListener;
|
||||
|
||||
public FeedStoriesFetcher(final FetchListener<FeedStoryModel[]> fetchListener) {
|
||||
this.fetchListener = fetchListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FeedStoryModel[] doInBackground(final Void... voids) {
|
||||
FeedStoryModel[] result = null;
|
||||
String url = "https://www.instagram.com/graphql/query/?query_hash=b7b84d884400bc5aa7cfe12ae843a091&variables=" +
|
||||
"{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}";
|
||||
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.setUseCaches(false);
|
||||
conn.connect();
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
final JSONArray feedStoriesReel = new JSONObject(NetworkUtils.readFromConnection(conn))
|
||||
.getJSONObject("data")
|
||||
.getJSONObject(Constants.EXTRAS_USER)
|
||||
.getJSONObject("feed_reels_tray")
|
||||
.getJSONObject("edge_reels_tray_to_reel")
|
||||
.getJSONArray("edges");
|
||||
|
||||
conn.disconnect();
|
||||
|
||||
final int storiesLen = feedStoriesReel.length();
|
||||
final FeedStoryModel[] feedStoryModels = new FeedStoryModel[storiesLen];
|
||||
final String[] feedStoryIDs = new String[storiesLen];
|
||||
|
||||
for (int i = 0; i < storiesLen; ++i) {
|
||||
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
|
||||
|
||||
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
|
||||
final ProfileModel profileModel = new ProfileModel(false, false, false,
|
||||
user.getString("id"),
|
||||
user.getString("username"),
|
||||
null, null, null,
|
||||
user.getString("profile_pic_url"),
|
||||
null, 0, 0, 0, false, false, false, false);
|
||||
|
||||
final String id = node.getString("id");
|
||||
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media");
|
||||
feedStoryIDs[i] = id;
|
||||
feedStoryModels[i] = new FeedStoryModel(id, profileModel, fullyRead);
|
||||
}
|
||||
result = feedStoryModels;
|
||||
}
|
||||
|
||||
conn.disconnect();
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.ASYNC_FEED_STORY_FETCHER, "doInBackground");
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (fetchListener != null) fetchListener.doBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final FeedStoryModel[] result) {
|
||||
if (fetchListener != null) fetchListener.onResult(result);
|
||||
}
|
||||
}
|
@ -32,11 +32,18 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.CommentsAdapter;
|
||||
import awais.instagrabber.asyncs.CommentsFetcher;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
|
||||
import awais.instagrabber.databinding.FragmentCommentsBinding;
|
||||
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.CommentModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
@ -56,8 +63,9 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
|
||||
private CommentsAdapter commentsAdapter;
|
||||
private FragmentCommentsBinding binding;
|
||||
private String shortCode;
|
||||
private String userId;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private RecyclerLazyLoader lazyLoader;
|
||||
private String shortCode, userId, endCursor = null;
|
||||
private Resources resources;
|
||||
private InputMethodManager imm;
|
||||
private AppCompatActivity fragmentActivity;
|
||||
@ -65,8 +73,30 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
private boolean shouldRefresh = true;
|
||||
private MediaService mediaService;
|
||||
private String postId;
|
||||
private AsyncTask<Void, Void, List<CommentModel>> currentlyRunning;
|
||||
private CommentsViewModel commentsViewModel;
|
||||
|
||||
private final FetchListener<List<CommentModel>> fetchListener = new FetchListener<List<CommentModel>>() {
|
||||
@Override
|
||||
public void doBefore() {
|
||||
binding.swipeRefreshLayout.setRefreshing(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(final List<CommentModel> commentModels) {
|
||||
endCursor = commentModels.get(0).getEndCursor();
|
||||
if (commentModels != null && commentModels.size() > 0) {
|
||||
List<CommentModel> list = commentsViewModel.getList().getValue();
|
||||
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
|
||||
// final int oldSize = list != null ? list.size() : 0;
|
||||
list.addAll(commentModels);
|
||||
commentsViewModel.getList().postValue(list);
|
||||
}
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
stopCurrentExecutor();
|
||||
}
|
||||
};
|
||||
|
||||
private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() {
|
||||
@Override
|
||||
public void onClick(final CommentModel comment) {
|
||||
@ -181,11 +211,11 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
binding.swipeRefreshLayout.setRefreshing(true);
|
||||
new CommentsFetcher(shortCode, commentModels -> {
|
||||
commentsViewModel.getList().postValue(commentModels);
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
endCursor = null;
|
||||
lazyLoader.resetState();
|
||||
commentsViewModel.getList().postValue(Collections.emptyList());
|
||||
stopCurrentExecutor();
|
||||
currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
@ -198,7 +228,8 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(this);
|
||||
binding.swipeRefreshLayout.setRefreshing(true);
|
||||
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
|
||||
binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
layoutManager = new LinearLayoutManager(getContext());
|
||||
binding.rvComments.setLayoutManager(layoutManager);
|
||||
commentsAdapter = new CommentsAdapter(commentCallback);
|
||||
binding.rvComments.setAdapter(commentsAdapter);
|
||||
commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList);
|
||||
@ -226,6 +257,13 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
});
|
||||
binding.commentField.setEndIconOnClickListener(newCommentListener);
|
||||
}
|
||||
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
||||
if (!TextUtils.isEmpty(endCursor))
|
||||
currentlyRunning = new CommentsFetcher(shortCode, endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
endCursor = null;
|
||||
});
|
||||
binding.rvComments.addOnScrollListener(lazyLoader);
|
||||
stopCurrentExecutor();
|
||||
onRefresh();
|
||||
}
|
||||
|
||||
@ -301,8 +339,6 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText());
|
||||
break;
|
||||
case 3: // reply to comment
|
||||
// final View focus = binding.rvComments.findViewWithTag(commentModel);
|
||||
// focus.setBackgroundColor(0x80888888);
|
||||
commentsAdapter.setSelected(commentModel);
|
||||
String mention = "@" + profileModel.getUsername() + " ";
|
||||
binding.commentText.setText(mention);
|
||||
@ -326,7 +362,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
onRefresh();
|
||||
commentsAdapter.setLiked(commentModel, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -344,7 +380,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
onRefresh();
|
||||
commentsAdapter.setLiked(commentModel, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -389,4 +425,14 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username);
|
||||
NavHostFragment.findNavController(this).navigate(action);
|
||||
}
|
||||
|
||||
private void stopCurrentExecutor() {
|
||||
if (currentlyRunning != null) {
|
||||
try {
|
||||
currentlyRunning.cancel(true);
|
||||
} catch (final Exception e) {
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,11 +11,10 @@ public class CommentModel {
|
||||
private final ProfileModel profileModel;
|
||||
private final String id;
|
||||
private final String text;
|
||||
private final long likes;
|
||||
private long likes;
|
||||
private final long timestamp;
|
||||
private List<CommentModel> childCommentModels;
|
||||
private final boolean liked;
|
||||
private boolean hasNextPage;
|
||||
private boolean liked, hasNextPage;
|
||||
private String endCursor;
|
||||
|
||||
public CommentModel(final String id,
|
||||
@ -53,6 +52,11 @@ public class CommentModel {
|
||||
return liked;
|
||||
}
|
||||
|
||||
public void setLiked(boolean liked) {
|
||||
this.likes = liked ? likes + 1 : likes - 1;
|
||||
this.liked = liked;
|
||||
}
|
||||
|
||||
public ProfileModel getProfileModel() {
|
||||
return profileModel;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
<color name="dm_profile_button_color">#efefef</color>
|
||||
|
||||
<color name="comment_selected">#888888</color>
|
||||
<color name="comment_liked">#40FF69B4</color>
|
||||
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="white_a50">#80FFFFFF</color>
|
||||
|
Loading…
Reference in New Issue
Block a user