diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 01838249..566e46ed 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -141,14 +141,14 @@ - + + + - - + + + + diff --git a/app/src/main/java/awais/instagrabber/MainHelper.java b/app/src/main/java/awais/instagrabber/MainHelper.java index 607ad088..79cafea6 100755 --- a/app/src/main/java/awais/instagrabber/MainHelper.java +++ b/app/src/main/java/awais/instagrabber/MainHelper.java @@ -49,7 +49,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import awais.instagrabber.activities.CommentsViewer; +import awais.instagrabber.activities.CommentsViewerFragment; import awais.instagrabber.activities.FollowViewer; import awais.instagrabber.activities.MainActivityBackup; import awais.instagrabber.activities.PostViewer; @@ -604,7 +604,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { final int id = v.getId(); switch (id) { case R.id.btnComments: - mainActivity.startActivityForResult(new Intent(mainActivity, CommentsViewer.class) + mainActivity.startActivityForResult(new Intent(mainActivity, CommentsViewerFragment.class) .putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode()) .putExtra(Constants.EXTRAS_POST, feedModel.getPostId()) .putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId()), 6969); diff --git a/app/src/main/java/awais/instagrabber/activities/CommentsViewer.java b/app/src/main/java/awais/instagrabber/activities/CommentsViewer.java deleted file mode 100755 index cf418180..00000000 --- a/app/src/main/java/awais/instagrabber/activities/CommentsViewer.java +++ /dev/null @@ -1,302 +0,0 @@ -package awais.instagrabber.activities; - -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.RelativeSizeSpan; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.ArrayAdapter; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.SearchView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import java.io.DataOutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; - -import awais.instagrabber.R; -import awais.instagrabber.adapters.CommentsAdapter; -import awais.instagrabber.asyncs.CommentsFetcher; -import awais.instagrabber.databinding.ActivityCommentsBinding; -import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.interfaces.MentionClickListener; -import awais.instagrabber.models.CommentModel; -import awais.instagrabber.models.ProfileModel; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.Utils; - -public final class CommentsViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { - private CommentsAdapter commentsAdapter; - private CommentModel commentModel; - private ActivityCommentsBinding commentsBinding; - private ArrayAdapter commmentDialogAdapter; - private String shortCode, postId, userId; - private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); - private Resources resources; - private InputMethodManager imm; - private View focus; - - @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - commentsBinding = ActivityCommentsBinding.inflate(getLayoutInflater()); - setContentView(commentsBinding.getRoot()); - commentsBinding.swipeRefreshLayout.setOnRefreshListener(this); - - final Intent intent = getIntent(); - if (intent == null || !intent.hasExtra(Constants.EXTRAS_SHORTCODE) - || Utils.isEmpty((shortCode = intent.getStringExtra(Constants.EXTRAS_SHORTCODE))) - || !intent.hasExtra(Constants.EXTRAS_POST) - || Utils.isEmpty((postId = intent.getStringExtra(Constants.EXTRAS_POST))) - || !intent.hasExtra(Constants.EXTRAS_USER) - || Utils.isEmpty((userId = intent.getStringExtra(Constants.EXTRAS_USER)))) { - Utils.errorFinish(this); - return; - } - - commentsBinding.swipeRefreshLayout.setRefreshing(true); - setSupportActionBar(commentsBinding.toolbar.toolbar); - commentsBinding.toolbar.toolbar.setTitle(R.string.title_comments); - commentsBinding.toolbar.toolbar.setSubtitle(shortCode); - - resources = getResources(); - - if (!Utils.isEmpty(cookie)) { - commentsBinding.commentText.setVisibility(View.VISIBLE); - commentsBinding.commentSend.setVisibility(View.VISIBLE); - - commentsBinding.commentSend.setOnClickListener(newCommentListener); - commentsBinding.commentCancelParent.setOnClickListener(newCommentListener); - } - - new CommentsFetcher(shortCode, new FetchListener() { - @Override - public void onResult(final CommentModel[] commentModels) { - commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener); - - commentsBinding.rvComments.setAdapter(commentsAdapter); - commentsBinding.swipeRefreshLayout.setRefreshing(false); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public void onRefresh() { - commentsBinding.swipeRefreshLayout.setRefreshing(true); - new CommentsFetcher(shortCode, new FetchListener() { - @Override - public void onResult(final CommentModel[] commentModels) { - commentsBinding.swipeRefreshLayout.setRefreshing(false); - - commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener); - - commentsBinding.rvComments.setAdapter(commentsAdapter); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { - final ProfileModel profileModel = commentModel.getProfileModel(); - - if (which == 0) { - searchUsername(profileModel.getUsername()); - } else if (which == 1) { - startActivity(new Intent(this, ProfilePicViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel)); - } else if (which == 2) { - Utils.copyText(this, profileModel.getUsername()); - } else if (which == 3) { - Utils.copyText(this, commentModel.getText().toString()); - } else if (which == 4) { - if (commentModel == null) { - Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } - else { - focus = commentsBinding.rvComments.findViewWithTag(commentModel); - focus.setBackgroundColor(0x80888888); - commentsBinding.commentCancelParent.setVisibility(View.VISIBLE); - String mention = "@" + profileModel.getUsername() + " "; - commentsBinding.commentText.setText(mention); - commentsBinding.commentText.requestFocus(); - commentsBinding.commentText.setSelection(mention.length()); - commentsBinding.commentText.postDelayed(new Runnable() { - @Override - public void run() { - imm = (InputMethodManager) getSystemService(getApplicationContext().INPUT_METHOD_SERVICE); - imm.showSoftInput(commentsBinding.commentText, 0); - } - }, 200); - } - } else if (which == 5) { - new CommentAction().execute((commentModel.getLiked() ? "unlike/" : "like/")+commentModel.getId()); - } else if (which == 6) { - new CommentAction().execute("delete/"+commentModel.getId()); - } - }; - - private final View.OnClickListener clickListener = v -> { - final Object tag = v.getTag(); - if (tag instanceof CommentModel) { - commentModel = (CommentModel) tag; - - final String username = commentModel.getProfileModel().getUsername(); - final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText()); - title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - String[] commentDialogList; - - if (!Utils.isEmpty(cookie) && - (Utils.getUserIdFromCookie(cookie).equals(commentModel.getProfileModel().getId()) || - Utils.getUserIdFromCookie(cookie).equals(userId))) commentDialogList = new String[]{ - resources.getString(R.string.open_profile), - resources.getString(R.string.view_pfp), - resources.getString(R.string.comment_viewer_copy_user), - resources.getString(R.string.comment_viewer_copy_comment), - resources.getString(R.string.comment_viewer_reply_comment), - commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment) : resources.getString(R.string.comment_viewer_like_comment), - resources.getString(R.string.comment_viewer_delete_comment) - }; - else if (!Utils.isEmpty(cookie)) commentDialogList = new String[]{ - resources.getString(R.string.open_profile), - resources.getString(R.string.view_pfp), - resources.getString(R.string.comment_viewer_copy_user), - resources.getString(R.string.comment_viewer_copy_comment), - resources.getString(R.string.comment_viewer_reply_comment), - commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment) : resources.getString(R.string.comment_viewer_like_comment), - }; - else commentDialogList = new String[]{ - resources.getString(R.string.open_profile), - resources.getString(R.string.view_pfp), - resources.getString(R.string.comment_viewer_copy_user), - resources.getString(R.string.comment_viewer_copy_comment) - }; - - commmentDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, commentDialogList); - - new AlertDialog.Builder(this).setTitle(title) - .setAdapter(commmentDialogAdapter, profileDialogListener) - .setNeutralButton(R.string.cancel, null) - .show(); - } - }; - - private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> - new AlertDialog.Builder(this).setTitle(text) - .setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) - .setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, - (dialog, which) -> searchUsername(text)).show(); - - private final View.OnClickListener newCommentListener = v -> { - if (Utils.isEmpty(commentsBinding.commentText.getText().toString()) && v == commentsBinding.commentSend) - Toast.makeText(getApplicationContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show(); - else if (v == commentsBinding.commentSend) new CommentAction().execute("add"); - else if (v == commentsBinding.commentCancelParent) { - focus.setBackgroundColor(commentModel.getLiked() ? 0x40FF69B4 : 0x00000000); - commentsBinding.commentCancelParent.setVisibility(View.GONE); - commentsBinding.commentText.setText(""); - commentModel = null; - focus = null; - } - }; - - private void searchUsername(final String text) { - startActivity( - new Intent(getApplicationContext(), ProfileViewer.class) - .putExtra(Constants.EXTRAS_USERNAME, text) - ); - } - - @Override - public boolean onCreateOptionsMenu(final Menu menu) { - getMenuInflater().inflate(R.menu.follow, menu); - - final MenuItem menuSearch = menu.findItem(R.id.action_search); - final SearchView searchView = (SearchView) menuSearch.getActionView(); - searchView.setQueryHint(getResources().getString(R.string.action_search)); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(final String query) { - return false; - } - - @Override - public boolean onQueryTextChange(final String query) { - if (commentsAdapter != null) commentsAdapter.getFilter().filter(query); - return true; - } - }); - - menu.findItem(R.id.action_compare).setVisible(false); - - return true; - } - - class CommentAction extends AsyncTask { - boolean ok = false; - - protected Void doInBackground(String... rawAction) { - final String action = rawAction[0]; - final String url = "https://www.instagram.com/web/comments/"+postId+"/"+action+"/"; - try { - final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setUseCaches(false); - urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); - urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]); - if (action == "add") { - // https://stackoverflow.com/questions/14321873/java-url-encoding-urlencoder-vs-uri - final String commentText = URLEncoder.encode(commentsBinding.commentText.getText().toString(), "UTF-8") - .replaceAll("\\+", "%20").replaceAll("\\%21", "!").replaceAll("\\%27", "'") - .replaceAll("\\%28", "(").replaceAll("\\%29", ")").replaceAll("\\%7E", "~"); - final String urlParameters = "comment_text="+commentText+"&replied_to_comment_id="+ - (commentModel == null ? "" : commentModel.getId()); - urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - urlConnection.setRequestProperty("Content-Length", "" + - urlParameters.getBytes().length); - urlConnection.setDoOutput(true); - DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); - wr.writeBytes(urlParameters); - wr.flush(); - wr.close(); - } - urlConnection.connect(); - if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { - ok = true; - if (action == "add") { - commentsBinding.commentText.setText(""); - commentsBinding.commentText.clearFocus(); - } - } - urlConnection.disconnect(); - } catch (Throwable ex) { - Log.e("austin_debug", action+": " + ex); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - if (ok == true) { - if (focus != null) { - focus.setBackgroundColor(commentModel.getLiked() ? 0x40FF69B4 : 0x00000000); - commentsBinding.commentCancelParent.setVisibility(View.GONE); - commentModel = null; - focus = null; - } - onRefresh(); - } - else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/CommentsViewerFragment.java b/app/src/main/java/awais/instagrabber/activities/CommentsViewerFragment.java new file mode 100644 index 00000000..73888826 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/activities/CommentsViewerFragment.java @@ -0,0 +1,370 @@ +package awais.instagrabber.activities; + +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextWatcher; +import android.text.style.RelativeSizeSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SearchView; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavDirections; +import androidx.navigation.fragment.NavHostFragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.CommentsAdapter; +import awais.instagrabber.asyncs.CommentsFetcher; +import awais.instagrabber.databinding.FragmentCommentsBinding; +import awais.instagrabber.interfaces.MentionClickListener; +import awais.instagrabber.models.CommentModel; +import awais.instagrabber.models.ProfileModel; +import awais.instagrabber.services.MediaService; +import awais.instagrabber.services.ServiceCallback; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.Utils; + +import static android.content.Context.INPUT_METHOD_SERVICE; + +public final class CommentsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { + private static final String TAG = "CommentsViewerFragment"; + + private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); + + private CommentsAdapter commentsAdapter; + private CommentModel commentModel; + private FragmentCommentsBinding binding; + private String shortCode; + private String userId; + private Resources resources; + private InputMethodManager imm; + private AppCompatActivity fragmentActivity; + private LinearLayout root; + private boolean shouldRefresh = true; + private MediaService mediaService; + private String postId; + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + fragmentActivity = (AppCompatActivity) getActivity(); + mediaService = MediaService.getInstance(); + setHasOptionsMenu(true); + } + + @NonNull + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + if (root != null) { + shouldRefresh = false; + return root; + } + binding = FragmentCommentsBinding.inflate(getLayoutInflater()); + root = binding.getRoot(); + return root; + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + if (!shouldRefresh) return; + init(); + shouldRefresh = false; + } + + @Override + public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { + inflater.inflate(R.menu.follow, menu); + final MenuItem favItem = menu.findItem(R.id.favourites); + if (favItem != null) favItem.setVisible(false); + menu.findItem(R.id.action_compare).setVisible(false); + final MenuItem menuSearch = menu.findItem(R.id.action_search); + final SearchView searchView = (SearchView) menuSearch.getActionView(); + searchView.setQueryHint(getResources().getString(R.string.action_search)); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(final String query) { + return false; + } + + @Override + public boolean onQueryTextChange(final String query) { + if (commentsAdapter != null) commentsAdapter.getFilter().filter(query); + return true; + } + }); + } + + @Override + public void onRefresh() { + binding.swipeRefreshLayout.setRefreshing(true); + new CommentsFetcher(shortCode, commentModels -> { + binding.swipeRefreshLayout.setRefreshing(false); + commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener); + binding.rvComments.setAdapter(commentsAdapter); + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void init() { + if (getArguments() == null) return; + final CommentsViewerFragmentArgs fragmentArgs = CommentsViewerFragmentArgs.fromBundle(getArguments()); + shortCode = fragmentArgs.getShortCode(); + postId = fragmentArgs.getPostId(); + userId = fragmentArgs.getPostUserId(); + setTitle(); + binding.swipeRefreshLayout.setOnRefreshListener(this); + binding.swipeRefreshLayout.setRefreshing(true); + resources = getResources(); + if (!Utils.isEmpty(cookie)) { + binding.commentField.setStartIconVisible(false); + binding.commentField.setEndIconVisible(false); + binding.commentField.setVisibility(View.VISIBLE); + binding.commentText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {} + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + binding.commentField.setStartIconVisible(s.length() > 0); + binding.commentField.setEndIconVisible(s.length() > 0); + } + + @Override + public void afterTextChanged(final Editable s) {} + }); + binding.commentField.setStartIconOnClickListener(v -> { + commentModel = null; + binding.commentText.setText(""); + }); + binding.commentField.setEndIconOnClickListener(newCommentListener); + } + new CommentsFetcher(this.shortCode, commentModels -> { + commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener); + binding.rvComments.setAdapter(commentsAdapter); + binding.swipeRefreshLayout.setRefreshing(false); + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void setTitle() { + final ActionBar actionBar = fragmentActivity.getSupportActionBar(); + if (actionBar == null) return; + actionBar.setTitle(R.string.title_comments); + // actionBar.setSubtitle(shortCode); + } + + final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { + if (commentModel == null) { + Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + return; + } + final ProfileModel profileModel = commentModel.getProfileModel(); + switch (which) { + case 0: // open profile + openProfile(profileModel.getUsername()); + break; + case 1: // view profile pic + startActivity(new Intent(requireContext(), ProfilePicViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel)); + break; + case 2: // copy username + Utils.copyText(requireContext(), profileModel.getUsername()); + break; + case 3: // copy comment + Utils.copyText(requireContext(), commentModel.getText().toString()); + break; + case 4: // reply to comment + final View focus = binding.rvComments.findViewWithTag(commentModel); + focus.setBackgroundColor(0x80888888); + String mention = "@" + profileModel.getUsername() + " "; + binding.commentText.setText(mention); + binding.commentText.requestFocus(); + binding.commentText.setSelection(mention.length()); + binding.commentText.postDelayed(new Runnable() { + @Override + public void run() { + imm = (InputMethodManager) requireContext().getSystemService(INPUT_METHOD_SERVICE); + if (imm == null) return; + imm.showSoftInput(binding.commentText, 0); + } + }, 200); + break; + case 5: // like/unlike comment + if (!commentModel.getLiked()) { + mediaService.commentLike(commentModel.getId(), Utils.getCsrfTokenFromCookie(cookie), new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + commentModel = null; + if (!result) { + Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + return; + } + onRefresh(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error liking comment", t); + Toast.makeText(requireContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + return; + } + mediaService.commentUnlike(commentModel.getId(), Utils.getCsrfTokenFromCookie(cookie), new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + commentModel = null; + if (!result) { + Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + return; + } + onRefresh(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error unliking comment", t); + Toast.makeText(requireContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + break; + case 6: // delete comment + final String userId = Utils.getUserIdFromCookie(cookie); + if (userId == null) return; + mediaService.deleteComment( + postId, userId, commentModel.getId(), Utils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + commentModel = null; + if (!result) { + Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + return; + } + onRefresh(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error deleting comment", t); + Toast.makeText(requireContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + break; + } + }; + + private final View.OnClickListener clickListener = v -> { + final Object tag = v.getTag(); + if (tag instanceof CommentModel) { + commentModel = (CommentModel) tag; + + final String username = commentModel.getProfileModel().getUsername(); + final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText()); + title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + String[] commentDialogList; + + final String userIdFromCookie = Utils.getUserIdFromCookie(cookie); + if (!Utils.isEmpty(cookie) + && userIdFromCookie != null + && (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) { + commentDialogList = new String[]{ + resources.getString(R.string.open_profile), + resources.getString(R.string.view_pfp), + resources.getString(R.string.comment_viewer_copy_user), + resources.getString(R.string.comment_viewer_copy_comment), + resources.getString(R.string.comment_viewer_reply_comment), + commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment) + : resources.getString(R.string.comment_viewer_like_comment), + resources.getString(R.string.comment_viewer_delete_comment) + }; + } else if (!Utils.isEmpty(cookie)) { + commentDialogList = new String[]{ + resources.getString(R.string.open_profile), + resources.getString(R.string.view_pfp), + resources.getString(R.string.comment_viewer_copy_user), + resources.getString(R.string.comment_viewer_copy_comment), + resources.getString(R.string.comment_viewer_reply_comment), + commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment) + : resources.getString(R.string.comment_viewer_like_comment), + }; + } else { + commentDialogList = new String[]{ + resources.getString(R.string.open_profile), + resources.getString(R.string.view_pfp), + resources.getString(R.string.comment_viewer_copy_user), + resources.getString(R.string.comment_viewer_copy_comment) + }; + } + new AlertDialog.Builder(requireContext()) + .setTitle(title) + .setItems(commentDialogList, profileDialogListener) + .setNegativeButton(R.string.cancel, null) + .show(); + } + }; + + private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> { + if (isHashtag) { + final NavDirections action = CommentsViewerFragmentDirections.actionGlobalHashTagFragment(text); + NavHostFragment.findNavController(this).navigate(action); + return; + } + openProfile(text); + }; + + private final View.OnClickListener newCommentListener = v -> { + final Editable text = binding.commentText.getText(); + if (text == null || Utils.isEmpty(text.toString())) { + Toast.makeText(requireContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show(); + return; + } + final String userId = Utils.getUserIdFromCookie(cookie); + if (userId == null) return; + String replyToId = null; + if (commentModel != null) { + replyToId = commentModel.getId(); + } + mediaService.comment(postId, text.toString(), userId, replyToId, Utils.getCsrfTokenFromCookie(cookie), new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + commentModel = null; + binding.commentText.setText(""); + if (!result) { + Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + return; + } + onRefresh(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error during comment", t); + Toast.makeText(requireContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + }; + + private void openProfile(final String username) { + final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment("@" + username); + NavHostFragment.findNavController(this).navigate(action); + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/DirectMessagesActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectMessagesActivity.java index d8043469..029ca84f 100644 --- a/app/src/main/java/awais/instagrabber/activities/DirectMessagesActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/DirectMessagesActivity.java @@ -21,6 +21,7 @@ import awais.instagrabber.fragments.directmessages.DirectMessageThreadFragmentAr import awais.instagrabber.utils.Constants; import static awais.instagrabber.utils.Utils.settingsHelper; +@Deprecated public class DirectMessagesActivity extends BaseLanguageActivity implements NavController.OnDestinationChangedListener { private TextView toolbarTitle; diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 673beb2c..2aa6b32b 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -14,9 +14,11 @@ import androidx.navigation.NavController; import androidx.navigation.ui.NavigationUI; import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.appbar.CollapsingToolbarLayout; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import awais.instagrabber.R; @@ -46,7 +48,9 @@ public class MainActivity extends BaseLanguageActivity { R.id.settingsPreferencesFragment, R.id.hashTagFragment, R.id.locationFragment, - R.id.savedViewerFragment); + R.id.savedViewerFragment, + R.id.commentsViewerFragment); + private static final List REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment); private ActivityMainBinding binding; private LiveData currentNavControllerLiveData; @@ -110,6 +114,11 @@ public class MainActivity extends BaseLanguageActivity { } else { removeScrollingBehaviour(); } + if (REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS.contains(destinationId)) { + removeCollapsingToolbarScrollFlags(); + } else { + setCollapsingToolbarScrollFlags(); + } }); } @@ -125,6 +134,22 @@ public class MainActivity extends BaseLanguageActivity { binding.mainNavHost.requestLayout(); } + private void setCollapsingToolbarScrollFlags() { + final CollapsingToolbarLayout collapsingToolbarLayout = binding.collapsingToolbarLayout; + final AppBarLayout.LayoutParams toolbarLayoutLayoutParams = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams(); + toolbarLayoutLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL + | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP + | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS); + binding.collapsingToolbarLayout.requestLayout(); + } + + private void removeCollapsingToolbarScrollFlags() { + final CollapsingToolbarLayout collapsingToolbarLayout = binding.collapsingToolbarLayout; + final AppBarLayout.LayoutParams toolbarLayoutLayoutParams = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams(); + toolbarLayoutLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL); + binding.collapsingToolbarLayout.requestLayout(); + } + @Override protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); diff --git a/app/src/main/java/awais/instagrabber/activities/PostViewer.java b/app/src/main/java/awais/instagrabber/activities/PostViewer.java index 00567bad..bc47dde0 100755 --- a/app/src/main/java/awais/instagrabber/activities/PostViewer.java +++ b/app/src/main/java/awais/instagrabber/activities/PostViewer.java @@ -221,7 +221,7 @@ public final class PostViewer extends BaseLanguageActivity { viewerBinding.bottomPanel.btnComments.setVisibility(View.VISIBLE); viewerBinding.bottomPanel.btnComments.setOnClickListener(v -> startActivityForResult( - new Intent(this, CommentsViewer.class) + new Intent(this, CommentsViewerFragment.class) .putExtra(Constants.EXTRAS_SHORTCODE, postModel.getShortCode()) .putExtra(Constants.EXTRAS_POST, viewerPostModel.getPostId()) .putExtra(Constants.EXTRAS_USER, postUserId), diff --git a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java b/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java index d781d637..30177311 100755 --- a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java +++ b/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java @@ -72,6 +72,7 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS; import static awais.instagrabber.utils.Utils.logCollector; +@Deprecated public final class ProfileViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { private final ArrayList allItems = new ArrayList<>(), selectedItems = new ArrayList<>(); private static AsyncTask currentlyExecuting; diff --git a/app/src/main/java/awais/instagrabber/activities/StoryViewer.java b/app/src/main/java/awais/instagrabber/activities/StoryViewer.java deleted file mode 100755 index dd63ba7a..00000000 --- a/app/src/main/java/awais/instagrabber/activities/StoryViewer.java +++ /dev/null @@ -1,167 +0,0 @@ -// package awais.instagrabber.activities; -// -// import android.content.Intent; -// import android.os.AsyncTask; -// import android.os.Bundle; -// import android.view.MenuItem; -// import android.view.View; -// import android.widget.Toast; -// -// import androidx.annotation.Nullable; -// import androidx.core.view.GestureDetectorCompat; -// import androidx.recyclerview.widget.LinearLayoutManager; -// -// import awais.instagrabber.R; -// import awais.instagrabber.adapters.StoriesAdapter; -// import awais.instagrabber.asyncs.SeenAction; -// import awais.instagrabber.asyncs.i.iStoryStatusFetcher; -// import awais.instagrabber.customviews.helpers.SwipeGestureListener; -// import awais.instagrabber.databinding.ActivityStoryViewerBinding; -// import awais.instagrabber.interfaces.SwipeEvent; -// import awais.instagrabber.models.FeedStoryModel; -// import awais.instagrabber.models.StoryModel; -// import awais.instagrabber.models.stickers.PollModel; -// import awais.instagrabber.models.stickers.QuestionModel; -// import awais.instagrabber.models.stickers.QuizModel; -// import awais.instagrabber.utils.Constants; -// import awais.instagrabber.utils.Utils; -// -// import static awais.instagrabber.utils.Constants.MARK_AS_SEEN; -// import static awais.instagrabber.utils.Utils.settingsHelper; -// -// public final class StoryViewer extends BaseLanguageActivity { -// private final StoriesAdapter storiesAdapter = new StoriesAdapter(null, new View.OnClickListener() { -// @Override -// public void onClick(final View v) { -// final Object tag = v.getTag(); -// if (tag instanceof StoryModel) { -// currentStory = (StoryModel) tag; -// slidePos = currentStory.getPosition(); -// refreshStory(); -// } -// } -// }); -// private ActivityStoryViewerBinding storyViewerBinding; -// private StoryModel[] storyModels; -// private GestureDetectorCompat gestureDetector; -// -// private SwipeEvent swipeEvent; -// private MenuItem menuDownload, menuDm; -// private PollModel poll; -// private QuestionModel question; -// private String[] mentions; -// private QuizModel quiz; -// private StoryModel currentStory; -// private String url, username; -// private int slidePos = 0, lastSlidePos = 0; -// private final String cookie = settingsHelper.getString(Constants.COOKIE); -// private boolean fetching = false; -// -// @Override -// protected void onCreate(@Nullable final Bundle savedInstanceState) { -// super.onCreate(savedInstanceState); -// storyViewerBinding = ActivityStoryViewerBinding.inflate(getLayoutInflater()); -// setContentView(storyViewerBinding.getRoot()); -// -// setSupportActionBar(storyViewerBinding.toolbar.toolbar); -// -// final Intent intent = getIntent(); -// if (intent == null || !intent.hasExtra(Constants.EXTRAS_STORIES) -// || (storyModels = (StoryModel[]) intent.getSerializableExtra(Constants.EXTRAS_STORIES)) == null) { -// Utils.errorFinish(this); -// return; -// } -// -// username = intent.getStringExtra(Constants.EXTRAS_USERNAME); -// final String highlight = intent.getStringExtra(Constants.EXTRAS_HIGHLIGHT); -// final boolean hasUsername = !Utils.isEmpty(username); -// final boolean hasHighlight = !Utils.isEmpty(highlight); -// -// if (hasUsername) { -// username = username.replace("@", ""); -// storyViewerBinding.toolbar.toolbar.setTitle(username); -// storyViewerBinding.toolbar.toolbar.setOnClickListener(v -> { -// searchUsername(username); -// }); -// if (hasHighlight) storyViewerBinding.toolbar.toolbar.setSubtitle(getString(R.string.title_highlight, highlight)); -// else storyViewerBinding.toolbar.toolbar.setSubtitle(R.string.title_user_story); -// } -// -// storyViewerBinding.storiesList.setVisibility(View.GONE); -// storyViewerBinding.storiesList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); -// storyViewerBinding.storiesList.setAdapter(storiesAdapter); -// -// swipeEvent = new SwipeEvent() { -// private final int storiesLen = storyModels != null ? storyModels.length : 0; -// -// @Override -// public void onSwipe(final boolean isRightSwipe) { -// if (storyModels != null && storiesLen > 0) { -// if (((slidePos + 1 >= storiesLen && isRightSwipe == false) || (slidePos == 0 && isRightSwipe == true)) -// && intent.hasExtra(Constants.FEED)) { -// final FeedStoryModel[] storyFeed = (FeedStoryModel[]) intent.getSerializableExtra(Constants.FEED); -// final int index = intent.getIntExtra(Constants.FEED_ORDER, 1738); -// if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction(cookie, storyModel).execute(); -// if ((isRightSwipe == true && index == 0) || (isRightSwipe == false && index == storyFeed.length - 1)) -// Toast.makeText(getApplicationContext(), R.string.no_more_stories, Toast.LENGTH_SHORT).show(); -// else { -// final FeedStoryModel feedStoryModel = isRightSwipe ? -// (index == 0 ? null : storyFeed[index - 1]) : -// (storyFeed.length == index + 1 ? null : storyFeed[index + 1]); -// if (feedStoryModel != null) { -// if (fetching) { -// Toast.makeText(getApplicationContext(), R.string.be_patient, Toast.LENGTH_SHORT).show(); -// } else { -// fetching = true; -// new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> { -// if (result != null && result.length > 0) { -// final Intent newIntent = new Intent(getApplicationContext(), StoryViewer.class) -// .putExtra(Constants.EXTRAS_STORIES, result) -// .putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername()) -// .putExtra(Constants.FEED, storyFeed) -// .putExtra(Constants.FEED_ORDER, isRightSwipe ? (index - 1) : (index + 1)); -// newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); -// startActivity(newIntent); -// } else -// Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); -// }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); -// } -// } -// } -// } -// else { -// if (isRightSwipe) { -// if (--slidePos <= 0) slidePos = 0; -// } else if (++slidePos >= storiesLen) slidePos = storiesLen - 1; -// currentStory = storyModels[slidePos]; -// refreshStory(); -// } -// } -// } -// }; -// gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent)); -// -// viewPost(); -// } -// -// private void searchUsername(final String text) { -// startActivity( -// new Intent(getApplicationContext(), ProfileViewer.class) -// .putExtra(Constants.EXTRAS_USERNAME, text) -// ); -// } -// -// -// -// public static int indexOfIntArray(Object[] array, Object key) { -// int returnvalue = -1; -// for (int i = 0; i < array.length; ++i) { -// if (key == array[i]) { -// returnvalue = i; -// break; -// } -// } -// return returnvalue; -// } -// -// } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java index 3a456c21..77e308cd 100755 --- a/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java @@ -10,9 +10,6 @@ import android.widget.Filterable; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; - import java.util.ArrayList; import awais.instagrabber.R; @@ -24,7 +21,15 @@ import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.Utils; public final class CommentsAdapter extends RecyclerView.Adapter implements Filterable { + + private CommentModel[] filteredCommentModels; + private LayoutInflater layoutInflater; + private final boolean isParent; + private final View.OnClickListener onClickListener; + private final MentionClickListener mentionClickListener; + private final CommentModel[] commentModels; + private final String[] quantityStrings = new String[2]; private final Filter filter = new Filter() { @NonNull @Override @@ -66,14 +71,10 @@ public final class CommentsAdapter extends RecyclerView.Adapter 0) diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/CommentViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/CommentViewHolder.java index d34b0597..0a2e7687 100755 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/CommentViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/CommentViewHolder.java @@ -2,12 +2,13 @@ package awais.instagrabber.adapters.viewholder; import android.text.Spannable; import android.view.View; -import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.facebook.drawee.view.SimpleDraweeView; + import awais.instagrabber.R; import awais.instagrabber.adapters.CommentsAdapter; import awais.instagrabber.customviews.RamboTextView; @@ -17,11 +18,16 @@ import awais.instagrabber.models.CommentModel; public final class CommentViewHolder extends RecyclerView.ViewHolder { private final MentionClickListener mentionClickListener; private final RecyclerView rvChildComments; - private final ImageView ivProfilePic; - private final TextView tvUsername, tvDate, tvComment, tvLikes; + private final SimpleDraweeView ivProfilePic; + private final TextView tvUsername; + private final TextView tvDate; + private final TextView tvComment; + private final TextView tvLikes; private final View container; - public CommentViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) { + public CommentViewHolder(@NonNull final View itemView, + final View.OnClickListener onClickListener, + final MentionClickListener mentionClickListener) { super(itemView); container = itemView.findViewById(R.id.container); @@ -41,7 +47,7 @@ public final class CommentViewHolder extends RecyclerView.ViewHolder { rvChildComments = itemView.findViewById(R.id.rvChildComments); } - public final ImageView getProfilePicView() { + public final SimpleDraweeView getProfilePicView() { return ivProfilePic; } @@ -69,9 +75,9 @@ public final class CommentViewHolder extends RecyclerView.ViewHolder { if (liked) container.setBackgroundColor(0x40FF69B4); } - public final void setCommment(final CharSequence commment) { + public final void setComment(final CharSequence comment) { if (tvComment != null) { - tvComment.setText(commment, commment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL); + tvComment.setText(comment, comment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL); ((RamboTextView) tvComment).setMentionClickListener(mentionClickListener); } } diff --git a/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java b/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java index e4cdaa6e..dd4b1cd1 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java +++ b/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java @@ -18,7 +18,6 @@ import java.net.URL; import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.Random; import java.util.UUID; import awais.instagrabber.models.ImageUploadOptions; @@ -52,7 +51,7 @@ public class ImageUploader extends AsyncTask headers = new HashMap<>(); final String uploadId = String.valueOf(new Date().getTime()); - final long random = LOWER + new Random().nextLong() * (UPPER - LOWER + 1); + final long random = Utils.random(LOWER, UPPER + 1); final String name = String.format("%s_0_%s", uploadId, random); final String waterfallId = options.getWaterfallId() != null ? options.getWaterfallId() : UUID.randomUUID().toString(); headers.put("X-Entity-Type", "image/jpeg"); diff --git a/app/src/main/java/awais/instagrabber/asyncs/i/iPostFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/i/iPostFetcher.java index 3e8378f2..c022a80f 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/i/iPostFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/i/iPostFetcher.java @@ -65,7 +65,7 @@ public final class iPostFetcher extends AsyncTask user.optBoolean("is_private"), user.optBoolean("is_private"), user.optBoolean("is_verified"), - null, + user.optString("pk"), user.getString(Constants.EXTRAS_USERNAME), user.optString("fullname"), null, diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java index bcb58d8b..f5a67884 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java @@ -1,7 +1,6 @@ package awais.instagrabber.fragments; import android.content.DialogInterface; -import android.content.Intent; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Bundle; @@ -26,7 +25,6 @@ import java.util.Collections; import java.util.List; import awais.instagrabber.R; -import awais.instagrabber.activities.CommentsViewer; import awais.instagrabber.adapters.PostViewAdapter; import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; import awais.instagrabber.asyncs.PostFetcher; @@ -114,12 +112,18 @@ public class PostViewFragment extends Fragment { case R.id.viewerCaption: break; case R.id.btnComments: - startActivity(new Intent(requireContext(), CommentsViewer.class) - .putExtra(Constants.EXTRAS_SHORTCODE, - postModel.getShortCode()) - .putExtra(Constants.EXTRAS_POST, postModel.getPostId()) - .putExtra(Constants.EXTRAS_USER, - Utils.getUserIdFromCookie(COOKIE))); + // startActivity(new Intent(requireContext(), CommentsViewerFragment.class) + // .putExtra(Constants.EXTRAS_SHORTCODE, postModel.getShortCode()) + // .putExtra(Constants.EXTRAS_POST, postModel.getPostId()) + // .putExtra(Constants.EXTRAS_USER, Utils.getUserIdFromCookie(COOKIE))); + String postId = postModel.getPostId(); + if (postId.contains("_")) postId = postId.substring(0, postId.indexOf("_")); + final NavDirections commentsAction = PostViewFragmentDirections.actionGlobalCommentsViewerFragment( + postModel.getShortCode(), + postId, + postModel.getProfileModel().getId() + ); + NavHostFragment.findNavController(this).navigate(commentsAction); break; case R.id.btnDownload: if (checkSelfPermission(requireContext(), diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index 6f441bc4..bdba2e0b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -1,7 +1,6 @@ package awais.instagrabber.fragments.main; import android.content.DialogInterface; -import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -35,7 +34,6 @@ import java.util.List; import java.util.Map; import awais.instagrabber.R; -import awais.instagrabber.activities.CommentsViewer; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.FeedAdapter; import awais.instagrabber.adapters.FeedStoriesAdapter; @@ -187,17 +185,14 @@ public class FeedFragment extends Fragment { final int id = v.getId(); switch (id) { case R.id.btnComments: - startActivity(new Intent(requireContext(), CommentsViewer.class) - .putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode()) - .putExtra(Constants.EXTRAS_POST, feedModel.getPostId()) - .putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId())); + final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment( + feedModel.getShortCode(), + feedModel.getPostId(), + feedModel.getProfileModel().getId() + ); + NavHostFragment.findNavController(this).navigate(commentsAction); break; - case R.id.viewStoryPost: - // startActivity(new Intent(requireContext(), PostViewer.class) - // .putExtra(Constants.EXTRAS_INDEX, feedModel.getPosition()) - // .putExtra(Constants.EXTRAS_POST, new PostModel(feedModel.getShortCode(), false)) - // .putExtra(Constants.EXTRAS_TYPE, ItemGetType.FEED_ITEMS)); final List feedModels = feedViewModel.getList().getValue(); if (feedModels == null || feedModels.size() == 0) return; if (feedModels.get(0) == null) return; @@ -296,11 +291,9 @@ public class FeedFragment extends Fragment { @Override public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { if (!shouldRefresh) return; - // setupActionBar(); setupFeedStories(); setupFeed(); shouldRefresh = false; - // feedService.getFeed(11, null); } @Override diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 7707fa13..5ab38b63 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -456,16 +456,14 @@ public class ProfileFragment extends Fragment { binding.mainFollowers.setClickable(true); if (isLoggedIn) { - final View.OnClickListener followClickListener = v -> startActivity(new Intent( - requireContext(), - FollowViewer.class).putExtra(Constants.EXTRAS_FOLLOWERS, v == binding.mainFollowers) - .putExtra(Constants.EXTRAS_NAME, profileModel.getUsername()) - .putExtra(Constants.EXTRAS_ID, profileId)); + final View.OnClickListener followClickListener = v -> startActivity( + new Intent(requireContext(), FollowViewer.class) + .putExtra(Constants.EXTRAS_FOLLOWERS, v == binding.mainFollowers) + .putExtra(Constants.EXTRAS_NAME, profileModel.getUsername()) + .putExtra(Constants.EXTRAS_ID, profileId)); - binding.mainFollowers - .setOnClickListener(followersCount > 0 ? followClickListener : null); - binding.mainFollowing - .setOnClickListener(followingCount > 0 ? followClickListener : null); + binding.mainFollowers.setOnClickListener(followersCount > 0 ? followClickListener : null); + binding.mainFollowing.setOnClickListener(followingCount > 0 ? followClickListener : null); } if (profileModel.getPostCount() == 0) { diff --git a/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java b/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java index e9075ab1..048df6ca 100644 --- a/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java @@ -13,8 +13,32 @@ public interface MediaRepository { @FormUrlEncoded @POST("/api/v1/media/{mediaId}/{action}/") - Call likeAction(@Header("User-Agent") final String userAgent, - @Path("action") final String action, - @Path("mediaId") final String mediaId, - @FieldMap final Map signedForm); + Call action(@Header("User-Agent") final String userAgent, + @Path("action") final String action, + @Path("mediaId") final String mediaId, + @FieldMap final Map signedForm); + + @FormUrlEncoded + @POST("/api/v1/media/{mediaId}/comment/") + Call comment(@Header("User-Agent") final String userAgent, + @Path("mediaId") final String mediaId, + @FieldMap final Map signedForm); + + @FormUrlEncoded + @POST("/api/v1/media/{mediaId}/comment/bulk_delete/") + Call commentsBulkDelete(@Header("User-Agent") final String userAgent, + @Path("mediaId") final String mediaId, + @FieldMap final Map signedForm); + + @FormUrlEncoded + @POST("/api/v1/media/{commentId}/comment_like/") + Call commentLike(@Header("User-Agent") final String userAgent, + @Path("commentId") final String commentId, + @FieldMap final Map signedForm); + + @FormUrlEncoded + @POST("/api/v1/media/{commentId}/comment_unlike/") + Call commentUnlike(@Header("User-Agent") final String userAgent, + @Path("commentId") final String commentId, + @FieldMap final Map signedForm); } diff --git a/app/src/main/java/awais/instagrabber/services/BaseService.java b/app/src/main/java/awais/instagrabber/services/BaseService.java index 99022370..6347ed0c 100644 --- a/app/src/main/java/awais/instagrabber/services/BaseService.java +++ b/app/src/main/java/awais/instagrabber/services/BaseService.java @@ -11,6 +11,7 @@ import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.scalars.ScalarsConverterFactory; public abstract class BaseService { + private static final String TAG = "BaseService"; private Retrofit.Builder builder; @@ -33,4 +34,29 @@ public abstract class BaseService { } return builder; } + + // protected String userBreadcrumb(final int size) { + // final long term = (random(2, 4) * 1000) + size + (random(15, 21) * 1000); + // final float div = (float) size / random(2, 4); + // final int round = Math.round(div); + // final long textChangeEventCount = round > 0 ? round : 1; + // final String data = String.format(Locale.getDefault(), "%d %d %d %d", size, term, textChangeEventCount, new Date().getTime()); + // try { + // final Mac hasher = Mac.getInstance("HmacSHA256"); + // hasher.init(new SecretKeySpec(Constants.BREADCRUMB_KEY.getBytes(), "HmacSHA256")); + // byte[] hash = hasher.doFinal(data.getBytes()); + // final StringBuilder hexString = new StringBuilder(); + // for (byte b : hash) { + // final String hex = Integer.toHexString(0xff & b); + // if (hex.length() == 1) hexString.append('0'); + // hexString.append(hex); + // } + // final String encodedData = Base64.encodeToString(data.getBytes(), Base64.NO_WRAP); + // final String encodedHex = Base64.encodeToString(hexString.toString().getBytes(), Base64.NO_WRAP); + // return String.format("%s\n%s\n", encodedHex, encodedData); + // } catch (Exception e) { + // Log.e(TAG, "Error creating breadcrumb", e); + // return null; + // } + // } } diff --git a/app/src/main/java/awais/instagrabber/services/LoggingInterceptor.java b/app/src/main/java/awais/instagrabber/services/LoggingInterceptor.java index 3da71476..15fce4c9 100644 --- a/app/src/main/java/awais/instagrabber/services/LoggingInterceptor.java +++ b/app/src/main/java/awais/instagrabber/services/LoggingInterceptor.java @@ -21,7 +21,7 @@ class LoggingInterceptor implements Interceptor { Request request = chain.request(); long t1 = System.nanoTime(); Log.i(TAG, String.format("Sending request %s on %s%n%s", - request.url(), chain.connection(), request.headers())); + request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); Log.i(TAG, String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); @@ -30,6 +30,8 @@ class LoggingInterceptor implements Interceptor { Log.d("OkHttp", content); ResponseBody wrappedBody = ResponseBody.create(contentType, content); - return response.newBuilder().body(wrappedBody).build(); + return response.newBuilder() + .body(wrappedBody) + .build(); } } diff --git a/app/src/main/java/awais/instagrabber/services/MediaService.java b/app/src/main/java/awais/instagrabber/services/MediaService.java index 273357c6..125b99c6 100644 --- a/app/src/main/java/awais/instagrabber/services/MediaService.java +++ b/app/src/main/java/awais/instagrabber/services/MediaService.java @@ -1,11 +1,16 @@ package awais.instagrabber.services; +import android.text.TextUtils; +import android.util.Log; + import androidx.annotation.NonNull; import org.json.JSONException; import org.json.JSONObject; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -42,35 +47,35 @@ public class MediaService extends BaseService { final String userId, final String csrfToken, final ServiceCallback callback) { - likeAction(mediaId, userId, "like", csrfToken, callback); + action(mediaId, userId, "like", csrfToken, callback); } public void unlike(final String mediaId, final String userId, final String csrfToken, final ServiceCallback callback) { - likeAction(mediaId, userId, "unlike", csrfToken, callback); + action(mediaId, userId, "unlike", csrfToken, callback); } public void save(final String mediaId, final String userId, final String csrfToken, final ServiceCallback callback) { - likeAction(mediaId, userId, "save", csrfToken, callback); + action(mediaId, userId, "save", csrfToken, callback); } public void unsave(final String mediaId, final String userId, final String csrfToken, final ServiceCallback callback) { - likeAction(mediaId, userId, "unsave", csrfToken, callback); + action(mediaId, userId, "unsave", csrfToken, callback); } - private void likeAction(final String mediaId, - final String userId, - final String action, - final String csrfToken, - final ServiceCallback callback) { + private void action(final String mediaId, + final String userId, + final String action, + final String csrfToken, + final ServiceCallback callback) { final Map form = new HashMap<>(4); form.put("media_id", mediaId); form.put("_csrftoken", csrfToken); @@ -78,7 +83,7 @@ public class MediaService extends BaseService { form.put("_uuid", UUID.randomUUID().toString()); // form.put("radio_type", "wifi-none"); final Map signedForm = Utils.sign(form); - final Call request = repository.likeAction(Constants.I_USER_AGENT, action, mediaId, signedForm); + final Call request = repository.action(Constants.I_USER_AGENT, action, mediaId, signedForm); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @@ -106,23 +111,170 @@ public class MediaService extends BaseService { } } }); - // const signedFormData = this.client.request.sign({ - // media_id: options.mediaId, - // _csrftoken: this.client.state.cookieCsrfToken, - // _uid: this.client.state.cookieUserId, - // _uuid: this.client.state.uuid, - // }); - // - // const { body } = await this.client.request.send({ - // url: `/api/v1/media/${options.mediaId}/${options.action}/`, - // method: 'POST', - // form: { - // ...signedFormData, - // d: options.d, - // }, - // }); - // return body; } + public void comment(@NonNull final String mediaId, + @NonNull final String comment, + @NonNull final String userId, + final String replyToCommentId, + final String csrfToken, + @NonNull final ServiceCallback callback) { + final String module = "self_comments_v2"; + final Map form = new HashMap<>(); + // form.put("user_breadcrumb", userBreadcrumb(comment.length())); + form.put("idempotence_token", UUID.randomUUID().toString()); + form.put("_csrftoken", csrfToken); + form.put("_uid", userId); + form.put("_uuid", UUID.randomUUID().toString()); + form.put("comment_text", comment); + form.put("containermodule", module); + if (!Utils.isEmpty(replyToCommentId)) { + form.put("replied_to_comment_id", replyToCommentId); + } + final Map signedForm = Utils.sign(form); + final Call commentRequest = repository.comment(Constants.I_USER_AGENT, mediaId, signedForm); + commentRequest.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + Log.e(TAG, "Error occurred while creating comment"); + callback.onSuccess(false); + return; + } + try { + final JSONObject jsonObject = new JSONObject(body); + final String status = jsonObject.optString("status"); + callback.onSuccess(status.equals("ok")); + } catch (JSONException e) { + // Log.e(TAG, "Error parsing body", e); + callback.onFailure(e); + } + } + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + callback.onFailure(t); + } + }); + } + + public void deleteComment(final String mediaId, + final String userId, + final String commentId, + final String csrfToken, + @NonNull final ServiceCallback callback) { + deleteComments(mediaId, userId, Collections.singletonList(commentId), csrfToken, callback); + } + + public void deleteComments(final String mediaId, + final String userId, + final List commentIds, + final String csrfToken, + @NonNull final ServiceCallback callback) { + final Map form = new HashMap<>(); + form.put("comment_ids_to_delete", TextUtils.join(",", commentIds)); + form.put("_csrftoken", csrfToken); + form.put("_uid", userId); + form.put("_uuid", UUID.randomUUID().toString()); + final Map signedForm = Utils.sign(form); + final Call bulkDeleteRequest = repository.commentsBulkDelete(Constants.USER_AGENT, mediaId, signedForm); + bulkDeleteRequest.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + Log.e(TAG, "Error occurred while deleting comments"); + callback.onSuccess(false); + return; + } + try { + final JSONObject jsonObject = new JSONObject(body); + final String status = jsonObject.optString("status"); + callback.onSuccess(status.equals("ok")); + } catch (JSONException e) { + // Log.e(TAG, "Error parsing body", e); + callback.onFailure(e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + // Log.e(TAG, "Error deleting comments", t); + callback.onFailure(t); + } + }); + } + + public void commentLike(@NonNull final String commentId, + @NonNull final String csrfToken, + @NonNull final ServiceCallback callback) { + final Map form = new HashMap<>(); + form.put("_csrftoken", csrfToken); + // form.put("_uid", userId); + // form.put("_uuid", UUID.randomUUID().toString()); + final Map signedForm = Utils.sign(form); + final Call commentLikeRequest = repository.commentLike(Constants.USER_AGENT, commentId, signedForm); + commentLikeRequest.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + Log.e(TAG, "Error occurred while liking comment"); + callback.onSuccess(false); + return; + } + try { + final JSONObject jsonObject = new JSONObject(body); + final String status = jsonObject.optString("status"); + callback.onSuccess(status.equals("ok")); + } catch (JSONException e) { + // Log.e(TAG, "Error parsing body", e); + callback.onFailure(e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + Log.e(TAG, "Error liking comment", t); + callback.onFailure(t); + } + }); + } + + public void commentUnlike(final String commentId, + @NonNull final String csrfToken, + @NonNull final ServiceCallback callback) { + final Map form = new HashMap<>(); + form.put("_csrftoken", csrfToken); + // form.put("_uid", userId); + // form.put("_uuid", UUID.randomUUID().toString()); + final Map signedForm = Utils.sign(form); + final Call commentUnlikeRequest = repository.commentUnlike(Constants.USER_AGENT, commentId, signedForm); + commentUnlikeRequest.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + Log.e(TAG, "Error occurred while unliking comment"); + callback.onSuccess(false); + return; + } + try { + final JSONObject jsonObject = new JSONObject(body); + final String status = jsonObject.optString("status"); + callback.onSuccess(status.equals("ok")); + } catch (JSONException e) { + // Log.e(TAG, "Error parsing body", e); + callback.onFailure(e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + Log.e(TAG, "Error unliking comment", t); + callback.onFailure(t); + } + }); + } } diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 4354f548..1fe142de 100755 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -66,5 +66,6 @@ public final class Constants { "\"gyroscope\", \"value\": \"gyroscope_enabled\" } ]"; public static final String SIGNATURE_VERSION = "4"; public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc"; + public static final String BREADCRUMB_KEY = "iN4$aGr0m"; public static final int LOGIN_RESULT_CODE = 5000; } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 75874785..1f4f3185 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -63,6 +63,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Random; import java.util.Set; import java.util.regex.Pattern; @@ -1220,9 +1221,13 @@ public final class Utils { } public static String sign(final String message) { + return sign(Constants.SIGNATURE_KEY, message); + } + + public static String sign(final String key, final String message) { try { final Mac hasher = Mac.getInstance("HmacSHA256"); - hasher.init(new SecretKeySpec(Constants.SIGNATURE_KEY.getBytes(), "HmacSHA256")); + hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")); byte[] hash = hasher.doFinal(message.getBytes()); final StringBuilder hexString = new StringBuilder(); for (byte b : hash) { @@ -1458,4 +1463,29 @@ public final class Utils { } return cookie.split("csrftoken=")[1].split(";")[0]; } + + // public static long random(final long lower, final long upper) { + // final long result = lower + new Random().nextLong() * (upper - lower + 1); + // return result; + // } + + public static long random(long origin, long bound) { + final Random random = new Random(); + long r = random.nextLong(); + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + r = (r & m) + origin; + else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = random.nextLong() >>> 1) // retry + ; + r += origin; + } + else { // range not representable as long + while (r < origin || r >= bound) + r = random.nextLong(); + } + return r; + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_search_24.xml b/app/src/main/res/drawable/ic_search_24.xml new file mode 100644 index 00000000..07b76d62 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_comments.xml b/app/src/main/res/layout/activity_comments.xml deleted file mode 100755 index 3006fc23..00000000 --- a/app/src/main/res/layout/activity_comments.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c02f7c58..2966a155 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -17,6 +17,7 @@ app:layout_constraintTop_toTopOf="parent"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_comment.xml b/app/src/main/res/layout/item_comment.xml index 064a50bc..c1bff83f 100755 --- a/app/src/main/res/layout/item_comment.xml +++ b/app/src/main/res/layout/item_comment.xml @@ -15,8 +15,8 @@ android:id="@+id/rvChildComments" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="80dp" - android:layout_marginLeft="80dp" + android:layout_marginStart="40dp" + android:layout_marginLeft="40dp" app:layoutManager="LinearLayoutManager" tools:itemCount="5" tools:listitem="@layout/item_comment_small" /> @@ -25,6 +25,8 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_gravity="bottom" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" android:layout_marginBottom="4dp" - android:background="#80888888" /> + android:background="#32888888" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_comment_small.xml b/app/src/main/res/layout/item_comment_small.xml index eff11707..69c07980 100755 --- a/app/src/main/res/layout/item_comment_small.xml +++ b/app/src/main/res/layout/item_comment_small.xml @@ -23,34 +23,35 @@ android:paddingLeft="4dp" android:paddingRight="4dp"> - + app:actualImageScaleType="centerCrop" + app:roundAsCircle="true" /> - + + - + android:textAppearance="@style/TextAppearance.AppCompat.Small" + android:textSize="12sp" /> + + diff --git a/app/src/main/res/layout/item_post.xml b/app/src/main/res/layout/item_post.xml index 3888f32b..077c3666 100755 --- a/app/src/main/res/layout/item_post.xml +++ b/app/src/main/res/layout/item_post.xml @@ -9,7 +9,7 @@ android:id="@+id/postImage" android:layout_width="match_parent" android:layout_height="wrap_content" - app:actualImageScaleType="fitCenter" + app:actualImageScaleType="centerCrop" app:viewAspectRatio="1"/> + android:orientation="vertical" + android:padding="8dp"> diff --git a/app/src/main/res/menu/menu.xml b/app/src/main/res/menu/menu.xml index bdc259b8..a760f439 100755 --- a/app/src/main/res/menu/menu.xml +++ b/app/src/main/res/menu/menu.xml @@ -55,7 +55,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/discover_nav_graph.xml b/app/src/main/res/navigation/discover_nav_graph.xml index f75fd475..a80ecb70 100644 --- a/app/src/main/res/navigation/discover_nav_graph.xml +++ b/app/src/main/res/navigation/discover_nav_graph.xml @@ -7,6 +7,7 @@ + + + + + + + + + + + + + + + + + +