1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-18 12:47:30 +00:00

Convert comment viewer activity to fragment and update layouts

This commit is contained in:
Ammar Githam 2020-09-05 02:38:36 +09:00
parent 7807fe7f59
commit c64ae3a101
37 changed files with 952 additions and 689 deletions

View File

@ -141,14 +141,14 @@
<!-- android:value=".activities.MainActivity" />--> <!-- android:value=".activities.MainActivity" />-->
<!--</activity>--> <!--</activity>-->
<activity <!--<activity-->
android:name=".activities.CommentsViewer" <!-- android:name=".activities.CommentsViewerFragment"-->
android:parentActivityName=".activities.PostViewer"> <!-- android:parentActivityName=".activities.PostViewer">-->
<meta-data <!-- <meta-data-->
android:name="android.support.PARENT_ACTIVITY" <!-- android:name="android.support.PARENT_ACTIVITY"-->
android:value=".activities.PostViewer" /> <!-- android:value=".activities.PostViewer" />-->
</activity> <!--</activity>-->
<!--<activity--> <!--<activity-->
<!-- android:name=".activities.StoryViewer"--> <!-- android:name=".activities.StoryViewer"-->

View File

@ -49,7 +49,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import awais.instagrabber.activities.CommentsViewer; import awais.instagrabber.activities.CommentsViewerFragment;
import awais.instagrabber.activities.FollowViewer; import awais.instagrabber.activities.FollowViewer;
import awais.instagrabber.activities.MainActivityBackup; import awais.instagrabber.activities.MainActivityBackup;
import awais.instagrabber.activities.PostViewer; import awais.instagrabber.activities.PostViewer;
@ -604,7 +604,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
final int id = v.getId(); final int id = v.getId();
switch (id) { switch (id) {
case R.id.btnComments: 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_SHORTCODE, feedModel.getShortCode())
.putExtra(Constants.EXTRAS_POST, feedModel.getPostId()) .putExtra(Constants.EXTRAS_POST, feedModel.getPostId())
.putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId()), 6969); .putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId()), 6969);

View File

@ -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<String> 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<CommentModel[]>() {
@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<CommentModel[]>() {
@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<String, Void, Void> {
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();
}
}
}

View File

@ -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<Boolean>() {
@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<Boolean>() {
@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<Boolean>() {
@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<Boolean>() {
@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);
}
}

View File

@ -21,6 +21,7 @@ import awais.instagrabber.fragments.directmessages.DirectMessageThreadFragmentAr
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
@Deprecated
public class DirectMessagesActivity extends BaseLanguageActivity implements NavController.OnDestinationChangedListener { public class DirectMessagesActivity extends BaseLanguageActivity implements NavController.OnDestinationChangedListener {
private TextView toolbarTitle; private TextView toolbarTitle;

View File

@ -14,9 +14,11 @@ import androidx.navigation.NavController;
import androidx.navigation.ui.NavigationUI; import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -46,7 +48,9 @@ public class MainActivity extends BaseLanguageActivity {
R.id.settingsPreferencesFragment, R.id.settingsPreferencesFragment,
R.id.hashTagFragment, R.id.hashTagFragment,
R.id.locationFragment, R.id.locationFragment,
R.id.savedViewerFragment); R.id.savedViewerFragment,
R.id.commentsViewerFragment);
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private ActivityMainBinding binding; private ActivityMainBinding binding;
private LiveData<NavController> currentNavControllerLiveData; private LiveData<NavController> currentNavControllerLiveData;
@ -110,6 +114,11 @@ public class MainActivity extends BaseLanguageActivity {
} else { } else {
removeScrollingBehaviour(); removeScrollingBehaviour();
} }
if (REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS.contains(destinationId)) {
removeCollapsingToolbarScrollFlags();
} else {
setCollapsingToolbarScrollFlags();
}
}); });
} }
@ -125,6 +134,22 @@ public class MainActivity extends BaseLanguageActivity {
binding.mainNavHost.requestLayout(); 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 @Override
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState);

View File

@ -221,7 +221,7 @@ public final class PostViewer extends BaseLanguageActivity {
viewerBinding.bottomPanel.btnComments.setVisibility(View.VISIBLE); viewerBinding.bottomPanel.btnComments.setVisibility(View.VISIBLE);
viewerBinding.bottomPanel.btnComments.setOnClickListener(v -> startActivityForResult( 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_SHORTCODE, postModel.getShortCode())
.putExtra(Constants.EXTRAS_POST, viewerPostModel.getPostId()) .putExtra(Constants.EXTRAS_POST, viewerPostModel.getPostId())
.putExtra(Constants.EXTRAS_USER, postUserId), .putExtra(Constants.EXTRAS_USER, postUserId),

View File

@ -72,6 +72,7 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS; import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
@Deprecated
public final class ProfileViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { public final class ProfileViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
private final ArrayList<PostModel> allItems = new ArrayList<>(), selectedItems = new ArrayList<>(); private final ArrayList<PostModel> allItems = new ArrayList<>(), selectedItems = new ArrayList<>();
private static AsyncTask<?, ?, ?> currentlyExecuting; private static AsyncTask<?, ?, ?> currentlyExecuting;

View File

@ -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;
// }
//
// }

View File

@ -10,9 +10,6 @@ import android.widget.Filterable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList; import java.util.ArrayList;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -24,7 +21,15 @@ import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolder> implements Filterable { public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolder> implements Filterable {
private CommentModel[] filteredCommentModels;
private LayoutInflater layoutInflater;
private final boolean isParent; 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() { private final Filter filter = new Filter() {
@NonNull @NonNull
@Override @Override
@ -66,14 +71,10 @@ public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolde
} }
} }
}; };
private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener;
private final CommentModel[] commentModels;
private final String[] quantityStrings = new String[2];
private LayoutInflater layoutInflater;
private CommentModel[] filteredCommentModels;
public CommentsAdapter(final CommentModel[] commentModels, final boolean isParent, final View.OnClickListener onClickListener, public CommentsAdapter(final CommentModel[] commentModels,
final boolean isParent,
final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) { final MentionClickListener mentionClickListener) {
this.commentModels = this.filteredCommentModels = commentModels; this.commentModels = this.filteredCommentModels = commentModels;
this.isParent = isParent; this.isParent = isParent;
@ -93,10 +94,13 @@ public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolde
if (quantityStrings[0] == null) quantityStrings[0] = context.getString(R.string.single_like); if (quantityStrings[0] == null) quantityStrings[0] = context.getString(R.string.single_like);
if (quantityStrings[1] == null) quantityStrings[1] = context.getString(R.string.multiple_likes); if (quantityStrings[1] == null) quantityStrings[1] = context.getString(R.string.multiple_likes);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
return new CommentViewHolder(layoutInflater.inflate( final View view = layoutInflater.inflate(isParent ? R.layout.item_comment
isParent ? R.layout.item_comment // parent : R.layout.item_comment_small,
: R.layout.item_comment_small, // child parent,
parent, false), onClickListener, mentionClickListener); false);
return new CommentViewHolder(view,
onClickListener,
mentionClickListener);
} }
@Override @Override
@ -105,7 +109,7 @@ public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolde
if (commentModel != null) { if (commentModel != null) {
holder.setCommentModel(commentModel); holder.setCommentModel(commentModel);
holder.setCommment(commentModel.getText()); holder.setComment(commentModel.getText());
holder.setDate(commentModel.getDateTime()); holder.setDate(commentModel.getDateTime());
holder.setLiked(commentModel.getLiked()); holder.setLiked(commentModel.getLiked());
@ -115,12 +119,8 @@ public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolde
final ProfileModel profileModel = commentModel.getProfileModel(); final ProfileModel profileModel = commentModel.getProfileModel();
if (profileModel != null) { if (profileModel != null) {
holder.setUsername(profileModel.getUsername()); holder.setUsername(profileModel.getUsername());
holder.getProfilePicView().setImageURI(profileModel.getSdProfilePic());
Glide.with(layoutInflater.getContext())
.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true))
.load(profileModel.getSdProfilePic()).into(holder.getProfilePicView());
} }
if (holder.isParent()) { if (holder.isParent()) {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels(); final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null && childCommentModels.length > 0) if (childCommentModels != null && childCommentModels.length > 0)

View File

@ -2,12 +2,13 @@ package awais.instagrabber.adapters.viewholder;
import android.text.Spannable; import android.text.Spannable;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter; import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.customviews.RamboTextView; import awais.instagrabber.customviews.RamboTextView;
@ -17,11 +18,16 @@ import awais.instagrabber.models.CommentModel;
public final class CommentViewHolder extends RecyclerView.ViewHolder { public final class CommentViewHolder extends RecyclerView.ViewHolder {
private final MentionClickListener mentionClickListener; private final MentionClickListener mentionClickListener;
private final RecyclerView rvChildComments; private final RecyclerView rvChildComments;
private final ImageView ivProfilePic; private final SimpleDraweeView ivProfilePic;
private final TextView tvUsername, tvDate, tvComment, tvLikes; private final TextView tvUsername;
private final TextView tvDate;
private final TextView tvComment;
private final TextView tvLikes;
private final View container; 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); super(itemView);
container = itemView.findViewById(R.id.container); container = itemView.findViewById(R.id.container);
@ -41,7 +47,7 @@ public final class CommentViewHolder extends RecyclerView.ViewHolder {
rvChildComments = itemView.findViewById(R.id.rvChildComments); rvChildComments = itemView.findViewById(R.id.rvChildComments);
} }
public final ImageView getProfilePicView() { public final SimpleDraweeView getProfilePicView() {
return ivProfilePic; return ivProfilePic;
} }
@ -69,9 +75,9 @@ public final class CommentViewHolder extends RecyclerView.ViewHolder {
if (liked) container.setBackgroundColor(0x40FF69B4); if (liked) container.setBackgroundColor(0x40FF69B4);
} }
public final void setCommment(final CharSequence commment) { public final void setComment(final CharSequence comment) {
if (tvComment != null) { 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); ((RamboTextView) tvComment).setMentionClickListener(mentionClickListener);
} }
} }

View File

@ -18,7 +18,6 @@ import java.net.URL;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
import awais.instagrabber.models.ImageUploadOptions; import awais.instagrabber.models.ImageUploadOptions;
@ -52,7 +51,7 @@ public class ImageUploader extends AsyncTask<ImageUploadOptions, Void, ImageUplo
final String contentLength = String.valueOf(bytes.length); final String contentLength = String.valueOf(bytes.length);
final Map<String, String> headers = new HashMap<>(); final Map<String, String> headers = new HashMap<>();
final String uploadId = String.valueOf(new Date().getTime()); 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 name = String.format("%s_0_%s", uploadId, random);
final String waterfallId = options.getWaterfallId() != null ? options.getWaterfallId() : UUID.randomUUID().toString(); final String waterfallId = options.getWaterfallId() != null ? options.getWaterfallId() : UUID.randomUUID().toString();
headers.put("X-Entity-Type", "image/jpeg"); headers.put("X-Entity-Type", "image/jpeg");

View File

@ -65,7 +65,7 @@ public final class iPostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
user.optBoolean("is_private"), user.optBoolean("is_private"),
user.optBoolean("is_private"), user.optBoolean("is_private"),
user.optBoolean("is_verified"), user.optBoolean("is_verified"),
null, user.optString("pk"),
user.getString(Constants.EXTRAS_USERNAME), user.getString(Constants.EXTRAS_USERNAME),
user.optString("fullname"), user.optString("fullname"),
null, null,

View File

@ -1,7 +1,6 @@
package awais.instagrabber.fragments; package awais.instagrabber.fragments;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -26,7 +25,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.adapters.PostViewAdapter; import awais.instagrabber.adapters.PostViewAdapter;
import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener;
import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.asyncs.PostFetcher;
@ -114,12 +112,18 @@ public class PostViewFragment extends Fragment {
case R.id.viewerCaption: case R.id.viewerCaption:
break; break;
case R.id.btnComments: case R.id.btnComments:
startActivity(new Intent(requireContext(), CommentsViewer.class) // startActivity(new Intent(requireContext(), CommentsViewerFragment.class)
.putExtra(Constants.EXTRAS_SHORTCODE, // .putExtra(Constants.EXTRAS_SHORTCODE, postModel.getShortCode())
postModel.getShortCode()) // .putExtra(Constants.EXTRAS_POST, postModel.getPostId())
.putExtra(Constants.EXTRAS_POST, postModel.getPostId()) // .putExtra(Constants.EXTRAS_USER, Utils.getUserIdFromCookie(COOKIE)));
.putExtra(Constants.EXTRAS_USER, String postId = postModel.getPostId();
Utils.getUserIdFromCookie(COOKIE))); 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; break;
case R.id.btnDownload: case R.id.btnDownload:
if (checkSelfPermission(requireContext(), if (checkSelfPermission(requireContext(),

View File

@ -1,7 +1,6 @@
package awais.instagrabber.fragments.main; package awais.instagrabber.fragments.main;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -35,7 +34,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapter; import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter; import awais.instagrabber.adapters.FeedStoriesAdapter;
@ -187,17 +185,14 @@ public class FeedFragment extends Fragment {
final int id = v.getId(); final int id = v.getId();
switch (id) { switch (id) {
case R.id.btnComments: case R.id.btnComments:
startActivity(new Intent(requireContext(), CommentsViewer.class) final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
.putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode()) feedModel.getShortCode(),
.putExtra(Constants.EXTRAS_POST, feedModel.getPostId()) feedModel.getPostId(),
.putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId())); feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(this).navigate(commentsAction);
break; break;
case R.id.viewStoryPost: 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<FeedModel> feedModels = feedViewModel.getList().getValue(); final List<FeedModel> feedModels = feedViewModel.getList().getValue();
if (feedModels == null || feedModels.size() == 0) return; if (feedModels == null || feedModels.size() == 0) return;
if (feedModels.get(0) == null) return; if (feedModels.get(0) == null) return;
@ -296,11 +291,9 @@ public class FeedFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
// setupActionBar();
setupFeedStories(); setupFeedStories();
setupFeed(); setupFeed();
shouldRefresh = false; shouldRefresh = false;
// feedService.getFeed(11, null);
} }
@Override @Override

View File

@ -456,16 +456,14 @@ public class ProfileFragment extends Fragment {
binding.mainFollowers.setClickable(true); binding.mainFollowers.setClickable(true);
if (isLoggedIn) { if (isLoggedIn) {
final View.OnClickListener followClickListener = v -> startActivity(new Intent( final View.OnClickListener followClickListener = v -> startActivity(
requireContext(), new Intent(requireContext(), FollowViewer.class)
FollowViewer.class).putExtra(Constants.EXTRAS_FOLLOWERS, v == binding.mainFollowers) .putExtra(Constants.EXTRAS_FOLLOWERS, v == binding.mainFollowers)
.putExtra(Constants.EXTRAS_NAME, profileModel.getUsername()) .putExtra(Constants.EXTRAS_NAME, profileModel.getUsername())
.putExtra(Constants.EXTRAS_ID, profileId)); .putExtra(Constants.EXTRAS_ID, profileId));
binding.mainFollowers binding.mainFollowers.setOnClickListener(followersCount > 0 ? followClickListener : null);
.setOnClickListener(followersCount > 0 ? followClickListener : null); binding.mainFollowing.setOnClickListener(followingCount > 0 ? followClickListener : null);
binding.mainFollowing
.setOnClickListener(followingCount > 0 ? followClickListener : null);
} }
if (profileModel.getPostCount() == 0) { if (profileModel.getPostCount() == 0) {

View File

@ -13,8 +13,32 @@ public interface MediaRepository {
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/media/{mediaId}/{action}/") @POST("/api/v1/media/{mediaId}/{action}/")
Call<String> likeAction(@Header("User-Agent") final String userAgent, Call<String> action(@Header("User-Agent") final String userAgent,
@Path("action") final String action, @Path("action") final String action,
@Path("mediaId") final String mediaId, @Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm); @FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/")
Call<String> comment(@Header("User-Agent") final String userAgent,
@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/bulk_delete/")
Call<String> commentsBulkDelete(@Header("User-Agent") final String userAgent,
@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_like/")
Call<String> commentLike(@Header("User-Agent") final String userAgent,
@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_unlike/")
Call<String> commentUnlike(@Header("User-Agent") final String userAgent,
@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
} }

View File

@ -11,6 +11,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory; import retrofit2.converter.scalars.ScalarsConverterFactory;
public abstract class BaseService { public abstract class BaseService {
private static final String TAG = "BaseService";
private Retrofit.Builder builder; private Retrofit.Builder builder;
@ -33,4 +34,29 @@ public abstract class BaseService {
} }
return builder; 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;
// }
// }
} }

View File

@ -21,7 +21,7 @@ class LoggingInterceptor implements Interceptor {
Request request = chain.request(); Request request = chain.request();
long t1 = System.nanoTime(); long t1 = System.nanoTime();
Log.i(TAG, String.format("Sending request %s on %s%n%s", 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); Response response = chain.proceed(request);
long t2 = System.nanoTime(); 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())); 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); Log.d("OkHttp", content);
ResponseBody wrappedBody = ResponseBody.create(contentType, content); ResponseBody wrappedBody = ResponseBody.create(contentType, content);
return response.newBuilder().body(wrappedBody).build(); return response.newBuilder()
.body(wrappedBody)
.build();
} }
} }

View File

@ -1,11 +1,16 @@
package awais.instagrabber.services; package awais.instagrabber.services;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -42,35 +47,35 @@ public class MediaService extends BaseService {
final String userId, final String userId,
final String csrfToken, final String csrfToken,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
likeAction(mediaId, userId, "like", csrfToken, callback); action(mediaId, userId, "like", csrfToken, callback);
} }
public void unlike(final String mediaId, public void unlike(final String mediaId,
final String userId, final String userId,
final String csrfToken, final String csrfToken,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
likeAction(mediaId, userId, "unlike", csrfToken, callback); action(mediaId, userId, "unlike", csrfToken, callback);
} }
public void save(final String mediaId, public void save(final String mediaId,
final String userId, final String userId,
final String csrfToken, final String csrfToken,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
likeAction(mediaId, userId, "save", csrfToken, callback); action(mediaId, userId, "save", csrfToken, callback);
} }
public void unsave(final String mediaId, public void unsave(final String mediaId,
final String userId, final String userId,
final String csrfToken, final String csrfToken,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
likeAction(mediaId, userId, "unsave", csrfToken, callback); action(mediaId, userId, "unsave", csrfToken, callback);
} }
private void likeAction(final String mediaId, private void action(final String mediaId,
final String userId, final String userId,
final String action, final String action,
final String csrfToken, final String csrfToken,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>(4); final Map<String, Object> form = new HashMap<>(4);
form.put("media_id", mediaId); form.put("media_id", mediaId);
form.put("_csrftoken", csrfToken); form.put("_csrftoken", csrfToken);
@ -78,7 +83,7 @@ public class MediaService extends BaseService {
form.put("_uuid", UUID.randomUUID().toString()); form.put("_uuid", UUID.randomUUID().toString());
// form.put("radio_type", "wifi-none"); // form.put("radio_type", "wifi-none");
final Map<String, String> signedForm = Utils.sign(form); final Map<String, String> signedForm = Utils.sign(form);
final Call<String> request = repository.likeAction(Constants.I_USER_AGENT, action, mediaId, signedForm); final Call<String> request = repository.action(Constants.I_USER_AGENT, action, mediaId, signedForm);
request.enqueue(new Callback<String>() { request.enqueue(new Callback<String>() {
@Override @Override
public void onResponse(@NonNull final Call<String> call, public void onResponse(@NonNull final Call<String> 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<Boolean> callback) {
final String module = "self_comments_v2";
final Map<String, Object> 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<String, String> signedForm = Utils.sign(form);
final Call<String> commentRequest = repository.comment(Constants.I_USER_AGENT, mediaId, signedForm);
commentRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> 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<String> 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<Boolean> callback) {
deleteComments(mediaId, userId, Collections.singletonList(commentId), csrfToken, callback);
}
public void deleteComments(final String mediaId,
final String userId,
final List<String> commentIds,
final String csrfToken,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> 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<String, String> signedForm = Utils.sign(form);
final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(Constants.USER_AGENT, mediaId, signedForm);
bulkDeleteRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> 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<String> 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<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
// form.put("_uid", userId);
// form.put("_uuid", UUID.randomUUID().toString());
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentLikeRequest = repository.commentLike(Constants.USER_AGENT, commentId, signedForm);
commentLikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> 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<String> 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<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
// form.put("_uid", userId);
// form.put("_uuid", UUID.randomUUID().toString());
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentUnlikeRequest = repository.commentUnlike(Constants.USER_AGENT, commentId, signedForm);
commentUnlikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> 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<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error unliking comment", t);
callback.onFailure(t);
}
});
}
} }

View File

@ -66,5 +66,6 @@ public final class Constants {
"\"gyroscope\", \"value\": \"gyroscope_enabled\" } ]"; "\"gyroscope\", \"value\": \"gyroscope_enabled\" } ]";
public static final String SIGNATURE_VERSION = "4"; public static final String SIGNATURE_VERSION = "4";
public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc"; public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc";
public static final String BREADCRUMB_KEY = "iN4$aGr0m";
public static final int LOGIN_RESULT_CODE = 5000; public static final int LOGIN_RESULT_CODE = 5000;
} }

View File

@ -63,6 +63,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -1220,9 +1221,13 @@ public final class Utils {
} }
public static String sign(final String message) { public static String sign(final String message) {
return sign(Constants.SIGNATURE_KEY, message);
}
public static String sign(final String key, final String message) {
try { try {
final Mac hasher = Mac.getInstance("HmacSHA256"); 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()); byte[] hash = hasher.doFinal(message.getBytes());
final StringBuilder hexString = new StringBuilder(); final StringBuilder hexString = new StringBuilder();
for (byte b : hash) { for (byte b : hash) {
@ -1458,4 +1463,29 @@ public final class Utils {
} }
return cookie.split("csrftoken=")[1].split(";")[0]; 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;
}
} }

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activities.CommentsViewer">
<include
android:id="@+id/toolbar"
android:layout_weight="1"
layout="@layout/layout_include_toolbar" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_weight="8"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvComments"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_comment" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/commentCancelParent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:paddingRight="8dp"
android:visibility="gone"
app:srcCompat="@android:drawable/ic_menu_revert" />
<EditText
android:id="@+id/commentText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:paddingLeft="8dp"
android:gravity="bottom"
android:hint="@string/comment_hint"
android:inputType="textMultiLine"
android:maxLength="2200"
android:maxLines="10"
android:visibility="gone"
android:scrollHorizontally="false" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/commentSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:paddingRight="8dp"
android:visibility="gone"
app:srcCompat="@android:drawable/ic_menu_send" />
</LinearLayout>
</LinearLayout>

View File

@ -17,6 +17,7 @@
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways" app:layout_scrollFlags="scroll|snap|enterAlways"

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvComments"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_comment" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/commentField"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/comment_hint"
app:counterEnabled="true"
app:counterMaxLength="2200"
app:endIconDrawable="@drawable/ic_send_24"
app:endIconMode="custom"
app:startIconDrawable="@drawable/ic_close_24">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/commentText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="no"
android:inputType="textMultiLine"
android:maxLength="2200"
android:maxLines="10"
android:scrollHorizontally="false" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@ -15,8 +15,8 @@
android:id="@+id/rvChildComments" android:id="@+id/rvChildComments"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="80dp" android:layout_marginStart="40dp"
android:layout_marginLeft="80dp" android:layout_marginLeft="40dp"
app:layoutManager="LinearLayoutManager" app:layoutManager="LinearLayoutManager"
tools:itemCount="5" tools:itemCount="5"
tools:listitem="@layout/item_comment_small" /> tools:listitem="@layout/item_comment_small" />
@ -25,6 +25,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:background="#80888888" /> android:background="#32888888" />
</LinearLayout> </LinearLayout>

View File

@ -23,34 +23,35 @@
android:paddingLeft="4dp" android:paddingLeft="4dp"
android:paddingRight="4dp"> android:paddingRight="4dp">
<androidx.appcompat.widget.AppCompatImageView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/ivProfilePic" android:id="@+id/ivProfilePic"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:gravity="center" android:gravity="center"
app:srcCompat="@mipmap/ic_launcher" /> app:actualImageScaleType="centerCrop"
app:roundAsCircle="true" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvUsername" android:id="@+id/tvUsername"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_gravity="start" android:layout_gravity="start"
android:layout_weight="1.0" android:layout_weight="1.0"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textStyle="bold" /> android:textStyle="bold" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
@ -68,31 +69,17 @@
android:paddingRight="8dp" android:paddingRight="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat" /> android:textAppearance="@style/TextAppearance.AppCompat" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:weightSum="3.5"> android:weightSum="3.5">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvLikes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDate" android:id="@+id/tvLikes"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2.5" android:layout_weight="1"
android:layout_gravity="start"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="4dp" android:paddingStart="4dp"
android:paddingLeft="4dp" android:paddingLeft="4dp"
@ -100,8 +87,24 @@
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:singleLine="true" android:singleLine="true"
android:textStyle="italic" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:gravity="right"/> android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_weight="2.5"
android:ellipsize="marquee"
android:gravity="end"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:singleLine="true"
android:textStyle="italic" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -9,7 +9,7 @@
android:id="@+id/postImage" android:id="@+id/postImage"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:actualImageScaleType="fitCenter" app:actualImageScaleType="centerCrop"
app:viewAspectRatio="1"/> app:viewAspectRatio="1"/>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView

View File

@ -17,6 +17,7 @@
android:id="@+id/ivProfilePic" android:id="@+id/ivProfilePic"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:actualImageScaleType="centerCrop"
app:roundAsCircle="true" /> app:roundAsCircle="true" />
<LinearLayout <LinearLayout
@ -52,8 +53,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="8dp" android:orientation="vertical"
android:orientation="vertical"> android:padding="8dp">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvUsername" android:id="@+id/tvUsername"

View File

@ -5,7 +5,7 @@
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
android:actionLayout="@layout/layout_searchview" android:actionLayout="@layout/layout_searchview"
android:icon="@android:drawable/ic_menu_search" android:icon="@drawable/ic_search_24"
android:title="@string/action_search" android:title="@string/action_search"
android:titleCondensed="@string/action_search" android:titleCondensed="@string/action_search"
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
@ -13,7 +13,7 @@
<item <item
android:id="@+id/action_compare" android:id="@+id/action_compare"
android:icon="@android:drawable/ic_menu_view" android:icon="@drawable/ic_outline_views_24"
android:title="@string/action_compare" android:title="@string/action_compare"
android:titleCondensed="@string/action_compare" android:titleCondensed="@string/action_compare"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />

View File

@ -55,7 +55,7 @@
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
android:actionLayout="@layout/layout_searchview" android:actionLayout="@layout/layout_searchview"
android:icon="@android:drawable/ic_menu_search" android:icon="@drawable/ic_search_24"
android:title="@string/action_search" android:title="@string/action_search"
android:titleCondensed="@string/action_search" android:titleCondensed="@string/action_search"
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/comments_nav_graph"
app:startDestination="@id/commentsViewerFragment">
<include app:graph="@navigation/hashtag_nav_graph" />
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<!--<include app:graph="@navigation/profile_nav_graph" />-->
<action
android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph">
<argument
android:name="username"
app:argType="string"
app:nullable="true" />
</action>
<fragment
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.activities.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="string"
app:nullable="false" />
</fragment>
<action
android:id="@+id/action_global_commentsViewerFragment"
app:destination="@id/commentsViewerFragment">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="string"
app:nullable="false" />
</action>
</navigation>

View File

@ -7,6 +7,7 @@
<include app:graph="@navigation/post_view_nav_graph" /> <include app:graph="@navigation/post_view_nav_graph" />
<include app:graph="@navigation/profile_nav_graph" /> <include app:graph="@navigation/profile_nav_graph" />
<include app:graph="@navigation/comments_nav_graph" />
<action <action
android:id="@+id/action_global_postViewFragment" android:id="@+id/action_global_postViewFragment"

View File

@ -54,6 +54,25 @@
app:argType="boolean" /> app:argType="boolean" />
</action> </action>
<include app:graph="@navigation/comments_nav_graph" />
<action
android:id="@+id/action_global_commentsViewerFragment"
app:destination="@id/comments_nav_graph">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="string"
app:nullable="false" />
</action>
<fragment <fragment
android:id="@+id/feedFragment" android:id="@+id/feedFragment"
android:name="awais.instagrabber.fragments.main.FeedFragment" android:name="awais.instagrabber.fragments.main.FeedFragment"

View File

@ -36,6 +36,24 @@
app:argType="string" app:argType="string"
app:nullable="false" /> app:nullable="false" />
</action> </action>
<action
android:id="@+id/action_global_commentsViewerFragment"
app:destination="@id/comments_nav_graph">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="string"
app:nullable="false" />
</action>
<fragment <fragment
android:id="@+id/postViewFragment" android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewFragment" android:name="awais.instagrabber.fragments.PostViewFragment"

View File

@ -32,6 +32,8 @@
app:argType="boolean" /> app:argType="boolean" />
</action> </action>
<include app:graph="@navigation/comments_nav_graph" />
<action <action
android:id="@+id/action_global_profileFragment" android:id="@+id/action_global_profileFragment"
app:destination="@id/profileFragment"> app:destination="@id/profileFragment">