mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-26 14:46:59 +00:00
activity: support for app inbox #114, better onclick, etc
This commit is contained in:
parent
d5161ac2ea
commit
49ba524305
@ -76,16 +76,23 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N
|
||||
private List<NotificationModel> sort(final List<NotificationModel> list) {
|
||||
final List<NotificationModel> listCopy = new ArrayList<>(list);
|
||||
Collections.sort(listCopy, (o1, o2) -> {
|
||||
if (o1.getType() == o2.getType()) return 0;
|
||||
// keep requests at top
|
||||
if (o1.getType() == NotificationType.REQUEST) return -1;
|
||||
if (o2.getType() == NotificationType.REQUEST) return 1;
|
||||
return 0;
|
||||
if (o1.getType() == o2.getType()
|
||||
&& o1.getType() == NotificationType.REQUEST
|
||||
&& o2.getType() == NotificationType.REQUEST) return 0;
|
||||
else if (o1.getType() == NotificationType.REQUEST) return -1;
|
||||
else if (o2.getType() == NotificationType.REQUEST) return 1;
|
||||
// timestamp
|
||||
return o1.getTimestamp() > o2.getTimestamp() ? -1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1);
|
||||
});
|
||||
return listCopy;
|
||||
}
|
||||
|
||||
public interface OnNotificationClickListener {
|
||||
void onNotificationClick(final NotificationModel model);
|
||||
|
||||
void onProfileClick(final String username);
|
||||
|
||||
void onPreviewClick(final NotificationModel model);
|
||||
}
|
||||
}
|
@ -24,10 +24,6 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
|
||||
public void bind(final NotificationModel model,
|
||||
final OnNotificationClickListener notificationClickListener) {
|
||||
if (model == null) return;
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (notificationClickListener == null) return;
|
||||
notificationClickListener.onNotificationClick(model);
|
||||
});
|
||||
int text = -1;
|
||||
CharSequence subtext = null;
|
||||
switch (model.getType()) {
|
||||
@ -52,20 +48,47 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
|
||||
text = R.string.request_notif;
|
||||
subtext = model.getText();
|
||||
break;
|
||||
case COMMENT_LIKE:
|
||||
case TAGGED_COMMENT:
|
||||
case RESPONDED_STORY:
|
||||
subtext = model.getText();
|
||||
break;
|
||||
}
|
||||
binding.tvUsername.setText(model.getUsername());
|
||||
binding.tvComment.setText(text);
|
||||
binding.tvSubComment.setText(subtext, subtext instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL);
|
||||
// binding.tvSubComment.setMentionClickListener(mentionClickListener);
|
||||
if (text == -1 && subtext != null) {
|
||||
binding.tvComment.setText(subtext);
|
||||
binding.tvSubComment.setVisibility(View.GONE);
|
||||
}
|
||||
else if (text != -1) {
|
||||
binding.tvComment.setText(text);
|
||||
binding.tvSubComment.setText(subtext);
|
||||
binding.tvSubComment.setVisibility(subtext == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
if (model.getType() != NotificationType.REQUEST) {
|
||||
binding.tvDate.setText(model.getDateTime());
|
||||
}
|
||||
|
||||
binding.tvUsername.setText(model.getUsername());
|
||||
binding.ivProfilePic.setImageURI(model.getProfilePic());
|
||||
binding.ivProfilePic.setOnClickListener(v -> {
|
||||
if (notificationClickListener == null) return;
|
||||
notificationClickListener.onProfileClick(model.getUsername());
|
||||
});
|
||||
|
||||
if (TextUtils.isEmpty(model.getPreviewPic())) {
|
||||
binding.ivPreviewPic.setVisibility(View.GONE);
|
||||
binding.ivPreviewPic.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
binding.ivPreviewPic.setVisibility(View.VISIBLE);
|
||||
binding.ivPreviewPic.setImageURI(model.getPreviewPic());
|
||||
binding.ivPreviewPic.setOnClickListener(v -> {
|
||||
if (notificationClickListener == null) return;
|
||||
notificationClickListener.onPreviewClick(model);
|
||||
});
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (notificationClickListener == null) return;
|
||||
notificationClickListener.onNotificationClick(model);
|
||||
});
|
||||
}
|
||||
}
|
@ -16,12 +16,21 @@ import awais.instagrabber.utils.TextUtils;
|
||||
public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsyncTask.NotificationCounts> {
|
||||
private static final String TAG = "GetActivityAsyncTask";
|
||||
|
||||
private OnTaskCompleteListener onTaskCompleteListener;
|
||||
private final OnTaskCompleteListener onTaskCompleteListener;
|
||||
|
||||
public GetActivityAsyncTask(final OnTaskCompleteListener onTaskCompleteListener) {
|
||||
this.onTaskCompleteListener = onTaskCompleteListener;
|
||||
}
|
||||
|
||||
/*
|
||||
This needs to be redone to fetch i inbox instead
|
||||
Within inbox, data is (body JSON => counts)
|
||||
Then we have these counts:
|
||||
new_posts, activity_feed_dot_badge, relationships, campaign_notification
|
||||
usertags, likes, comment_likes, shopping_notification, comments
|
||||
photos_of_you (not sure about difference to usertags), requests
|
||||
*/
|
||||
|
||||
protected NotificationCounts doInBackground(final String... cookiesArray) {
|
||||
if (cookiesArray == null) return null;
|
||||
final String cookie = cookiesArray[0];
|
||||
@ -70,11 +79,11 @@ public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsy
|
||||
}
|
||||
|
||||
public static class NotificationCounts {
|
||||
private int relationshipsCount;
|
||||
private int userTagsCount;
|
||||
private int commentsCount;
|
||||
private int commentLikesCount;
|
||||
private int likesCount;
|
||||
private final int relationshipsCount;
|
||||
private final int userTagsCount;
|
||||
private final int commentsCount;
|
||||
private final int commentLikesCount;
|
||||
private final int likesCount;
|
||||
|
||||
public NotificationCounts(final int relationshipsCount,
|
||||
final int userTagsCount,
|
||||
|
@ -3,21 +3,14 @@ package awais.instagrabber.asyncs;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
import awais.instagrabber.models.enums.NotificationType;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.LocaleUtils;
|
||||
import awais.instagrabber.utils.NetworkUtils;
|
||||
import awais.instagrabber.webservices.NewsService;
|
||||
import awais.instagrabber.webservices.ServiceCallback;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
@ -26,89 +19,48 @@ public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notif
|
||||
private static final String TAG = "NotificationsFetcher";
|
||||
|
||||
private final FetchListener<List<NotificationModel>> fetchListener;
|
||||
private final NewsService newsService;
|
||||
private final boolean markAsSeen;
|
||||
private boolean fetchedWeb = false;
|
||||
|
||||
public NotificationsFetcher(final FetchListener<List<NotificationModel>> fetchListener) {
|
||||
public NotificationsFetcher(final boolean markAsSeen,
|
||||
final FetchListener<List<NotificationModel>> fetchListener) {
|
||||
this.markAsSeen = markAsSeen;
|
||||
this.fetchListener = fetchListener;
|
||||
newsService = NewsService.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<NotificationModel> doInBackground(final Void... voids) {
|
||||
List<NotificationModel> result = new ArrayList<>();
|
||||
final String url = "https://www.instagram.com/accounts/activity/?__a=1";
|
||||
try {
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.setUseCaches(false);
|
||||
conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
|
||||
conn.connect();
|
||||
List<NotificationModel> notificationModels = new ArrayList<>();
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
final JSONObject page = new JSONObject(NetworkUtils.readFromConnection(conn))
|
||||
.getJSONObject("graphql")
|
||||
.getJSONObject("user");
|
||||
final JSONObject ewaf = page.getJSONObject("activity_feed")
|
||||
.optJSONObject("edge_web_activity_feed");
|
||||
final JSONObject efr = page.optJSONObject("edge_follow_requests");
|
||||
JSONObject data;
|
||||
JSONArray media;
|
||||
if (ewaf != null
|
||||
&& (media = ewaf.optJSONArray("edges")) != null
|
||||
&& media.length() > 0
|
||||
&& media.optJSONObject(0).optJSONObject("node") != null) {
|
||||
for (int i = 0; i < media.length(); ++i) {
|
||||
data = media.optJSONObject(i).optJSONObject("node");
|
||||
if (data == null) continue;
|
||||
final String type = data.getString("__typename");
|
||||
final NotificationType notificationType = NotificationType.valueOfType(type);
|
||||
if (notificationType == null) continue;
|
||||
final JSONObject user = data.getJSONObject("user");
|
||||
result.add(new NotificationModel(
|
||||
data.getString(Constants.EXTRAS_ID),
|
||||
data.optString("text"), // comments or mentions
|
||||
data.getLong("timestamp"),
|
||||
user.getString("id"),
|
||||
user.getString("username"),
|
||||
user.getString("profile_pic_url"),
|
||||
!data.isNull("media") ? data.getJSONObject("media").getString("shortcode") : null,
|
||||
!data.isNull("media") ? data.getJSONObject("media").getString("thumbnail_src") : null, notificationType));
|
||||
}
|
||||
newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<NotificationModel>>() {
|
||||
@Override
|
||||
public void onSuccess(final List<NotificationModel> result) {
|
||||
if (result == null) return;
|
||||
notificationModels.addAll(result);
|
||||
if (fetchedWeb) {
|
||||
fetchListener.onResult(notificationModels);
|
||||
}
|
||||
|
||||
if (efr != null
|
||||
&& (media = efr.optJSONArray("edges")) != null
|
||||
&& media.length() > 0
|
||||
&& media.optJSONObject(0).optJSONObject("node") != null) {
|
||||
for (int i = 0; i < media.length(); ++i) {
|
||||
data = media.optJSONObject(i).optJSONObject("node");
|
||||
if (data == null) continue;
|
||||
result.add(new NotificationModel(
|
||||
data.getString(Constants.EXTRAS_ID),
|
||||
data.optString("full_name"),
|
||||
0L,
|
||||
data.getString(Constants.EXTRAS_ID),
|
||||
data.getString("username"),
|
||||
data.getString("profile_pic_url"),
|
||||
null,
|
||||
null, NotificationType.REQUEST));
|
||||
}
|
||||
else {
|
||||
fetchedWeb = true;
|
||||
newsService.fetchWebInbox(markAsSeen, this);
|
||||
}
|
||||
}
|
||||
conn.disconnect();
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_NOTIFICATION_FETCHER, "doInBackground");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
return result;
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
// Log.e(TAG, "onFailure: ", t);
|
||||
if (fetchListener != null) {
|
||||
fetchListener.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
return notificationModels;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (fetchListener != null) fetchListener.doBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final List<NotificationModel> result) {
|
||||
if (fetchListener != null) fetchListener.onResult(result);
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
@ -30,8 +32,11 @@ import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListe
|
||||
import awais.instagrabber.asyncs.NotificationsFetcher;
|
||||
import awais.instagrabber.asyncs.PostFetcher;
|
||||
import awais.instagrabber.databinding.FragmentNotificationsViewerBinding;
|
||||
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
|
||||
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
import awais.instagrabber.models.enums.NotificationType;
|
||||
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
@ -40,8 +45,10 @@ import awais.instagrabber.utils.TextUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.viewmodels.NotificationViewModel;
|
||||
import awais.instagrabber.webservices.FriendshipService;
|
||||
import awais.instagrabber.webservices.MediaService;
|
||||
import awais.instagrabber.webservices.NewsService;
|
||||
import awais.instagrabber.webservices.ServiceCallback;
|
||||
import awais.instagrabber.webservices.StoriesService;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||
|
||||
@ -53,96 +60,153 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
private boolean shouldRefresh = true;
|
||||
private NotificationViewModel notificationViewModel;
|
||||
private FriendshipService friendshipService;
|
||||
private MediaService mediaService;
|
||||
private StoriesService storiesService;
|
||||
private String userId;
|
||||
private String csrfToken;
|
||||
private NewsService newsService;
|
||||
private Context context;
|
||||
|
||||
private final OnNotificationClickListener clickListener = model -> {
|
||||
if (model == null) return;
|
||||
final String username = model.getUsername();
|
||||
final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText())));
|
||||
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
String[] commentDialogList;
|
||||
if (model.getShortCode() != null) {
|
||||
commentDialogList = new String[]{
|
||||
getString(R.string.open_profile),
|
||||
getString(R.string.view_post)
|
||||
};
|
||||
} else if (model.getType() == NotificationType.REQUEST) {
|
||||
commentDialogList = new String[]{
|
||||
getString(R.string.open_profile),
|
||||
getString(R.string.request_approve),
|
||||
getString(R.string.request_reject)
|
||||
};
|
||||
} else {
|
||||
commentDialogList = new String[]{getString(R.string.open_profile)};
|
||||
private final OnNotificationClickListener clickListener = new OnNotificationClickListener() {
|
||||
@Override
|
||||
public void onProfileClick(final String username) {
|
||||
openProfile(username);
|
||||
}
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0:
|
||||
openProfile(model.getUsername());
|
||||
break;
|
||||
case 1:
|
||||
if (model.getType() == NotificationType.REQUEST) {
|
||||
friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
|
||||
@Override
|
||||
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
|
||||
// Log.d(TAG, "onSuccess: " + result);
|
||||
if (result.getStatus().equals("ok")) {
|
||||
onRefresh();
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "approve: status was not ok!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "approve: onFailure: ", t);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.dialog_opening_post)
|
||||
.create();
|
||||
alertDialog.show();
|
||||
new PostFetcher(model.getShortCode(), feedModel -> {
|
||||
@Override
|
||||
public void onPreviewClick(final NotificationModel model) {
|
||||
if (model.getType() == NotificationType.RESPONDED_STORY) {
|
||||
showProfilePicDialog(model);
|
||||
}
|
||||
else {
|
||||
mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() {
|
||||
@Override
|
||||
public void onSuccess(final FeedModel feedModel) {
|
||||
final PostViewV2Fragment fragment = PostViewV2Fragment
|
||||
.builder(feedModel)
|
||||
.build();
|
||||
fragment.setOnShowListener(dialog1 -> alertDialog.dismiss());
|
||||
fragment.show(getChildFragmentManager(), "post_view");
|
||||
}).execute();
|
||||
break;
|
||||
case 2:
|
||||
friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
|
||||
@Override
|
||||
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
|
||||
// Log.d(TAG, "onSuccess: " + result);
|
||||
if (result.getStatus().equals("ok")) {
|
||||
onRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationClick(final NotificationModel model) {
|
||||
if (model == null) return;
|
||||
final String username = model.getUsername();
|
||||
if (model.getType() == NotificationType.FOLLOW) {
|
||||
openProfile(username);
|
||||
}
|
||||
else {
|
||||
final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText())));
|
||||
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
String[] commentDialogList;
|
||||
if (model.getType() == NotificationType.RESPONDED_STORY) {
|
||||
commentDialogList = new String[]{
|
||||
getString(R.string.open_profile),
|
||||
getString(R.string.view_story)
|
||||
};
|
||||
}
|
||||
else if (model.getPostId() != null) {
|
||||
commentDialogList = new String[]{
|
||||
getString(R.string.open_profile),
|
||||
getString(R.string.view_post)
|
||||
};
|
||||
}
|
||||
else if (model.getType() == NotificationType.REQUEST) {
|
||||
commentDialogList = new String[]{
|
||||
getString(R.string.open_profile),
|
||||
getString(R.string.request_approve),
|
||||
getString(R.string.request_reject)
|
||||
};
|
||||
}
|
||||
else commentDialogList = null; // shouldn't happen
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0:
|
||||
openProfile(username);
|
||||
break;
|
||||
case 1:
|
||||
if (model.getType() == NotificationType.REQUEST) {
|
||||
friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
|
||||
@Override
|
||||
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
|
||||
// Log.d(TAG, "onSuccess: " + result);
|
||||
if (result.getStatus().equals("ok")) {
|
||||
onRefresh();
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "approve: status was not ok!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "approve: onFailure: ", t);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "ignore: status was not ok!");
|
||||
}
|
||||
else if (model.getType() == NotificationType.RESPONDED_STORY) {
|
||||
showProfilePicDialog(model);
|
||||
return;
|
||||
}
|
||||
final AlertDialog alertDialog = new AlertDialog.Builder(context)
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.dialog_opening_post)
|
||||
.create();
|
||||
alertDialog.show();
|
||||
mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() {
|
||||
@Override
|
||||
public void onSuccess(final FeedModel feedModel) {
|
||||
final PostViewV2Fragment fragment = PostViewV2Fragment
|
||||
.builder(feedModel)
|
||||
.build();
|
||||
fragment.setOnShowListener(dialog1 -> alertDialog.dismiss());
|
||||
fragment.show(getChildFragmentManager(), "post_view");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "ignore: onFailure: ", t);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
|
||||
@Override
|
||||
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
|
||||
// Log.d(TAG, "onSuccess: " + result);
|
||||
if (result.getStatus().equals("ok")) {
|
||||
onRefresh();
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "ignore: status was not ok!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "ignore: onFailure: ", t);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(title)
|
||||
.setItems(commentDialogList, profileDialogListener)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
};
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(title)
|
||||
.setItems(commentDialogList, profileDialogListener)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
};
|
||||
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
|
||||
if (getContext() == null) return;
|
||||
@ -158,7 +222,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Context context = getContext();
|
||||
context = getContext();
|
||||
if (context == null) return;
|
||||
NotificationManagerCompat.from(context.getApplicationContext()).cancel(Constants.ACTIVITY_NOTIFICATION_ID);
|
||||
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
|
||||
@ -167,6 +231,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
}
|
||||
friendshipService = FriendshipService.getInstance();
|
||||
newsService = NewsService.getInstance();
|
||||
mediaService = MediaService.getInstance();
|
||||
userId = CookieUtils.getUserIdFromCookie(cookie);
|
||||
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
||||
}
|
||||
@ -205,22 +270,9 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
binding.swipeRefreshLayout.setRefreshing(true);
|
||||
new NotificationsFetcher(notificationModels -> {
|
||||
new NotificationsFetcher(true, notificationModels -> {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
notificationViewModel.getList().postValue(notificationModels);
|
||||
final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
newsService.markChecked(timestamp, csrfToken, new ServiceCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(@NonNull final Boolean result) {
|
||||
// Log.d(TAG, "onResponse: body: " + result);
|
||||
if (!result) Log.e(TAG, "onSuccess: Error marking activity checked, response is false");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "onFailure: Error marking activity checked", t);
|
||||
}
|
||||
});
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@ -229,4 +281,15 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
.actionGlobalProfileFragment("@" + username);
|
||||
NavHostFragment.findNavController(this).navigate(action);
|
||||
}
|
||||
|
||||
private void showProfilePicDialog(final NotificationModel model) {
|
||||
final FragmentManager fragmentManager = getParentFragmentManager();
|
||||
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(model.getPostId(),
|
||||
model.getUsername(),
|
||||
model.getPreviewPic());
|
||||
final FragmentTransaction ft = fragmentManager.beginTransaction();
|
||||
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.add(fragment, "profilePicDialog")
|
||||
.commit();
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ public final class NotificationModel {
|
||||
private final String userId;
|
||||
private final String username;
|
||||
private final String profilePicUrl;
|
||||
private final String shortCode;
|
||||
private final String postId;
|
||||
private final String previewUrl;
|
||||
private final NotificationType type;
|
||||
private final CharSequence text;
|
||||
@ -25,7 +25,7 @@ public final class NotificationModel {
|
||||
final String userId,
|
||||
final String username,
|
||||
final String profilePicUrl,
|
||||
final String shortCode,
|
||||
final String postId,
|
||||
final String previewUrl,
|
||||
final NotificationType type) {
|
||||
this.id = id;
|
||||
@ -34,7 +34,7 @@ public final class NotificationModel {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.profilePicUrl = profilePicUrl;
|
||||
this.shortCode = shortCode;
|
||||
this.postId = postId;
|
||||
this.previewUrl = previewUrl;
|
||||
this.type = type;
|
||||
}
|
||||
@ -47,6 +47,10 @@ public final class NotificationModel {
|
||||
return text;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getDateTime() {
|
||||
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
|
||||
@ -64,8 +68,8 @@ public final class NotificationModel {
|
||||
return profilePicUrl;
|
||||
}
|
||||
|
||||
public String getShortCode() {
|
||||
return shortCode;
|
||||
public String getPostId() {
|
||||
return postId;
|
||||
}
|
||||
|
||||
public String getPreviewPic() {
|
||||
|
@ -5,11 +5,17 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum NotificationType implements Serializable {
|
||||
// web
|
||||
LIKE("GraphLikeAggregatedStory"),
|
||||
FOLLOW("GraphFollowAggregatedStory"),
|
||||
COMMENT("GraphCommentMediaStory"),
|
||||
MENTION("GraphMentionStory"),
|
||||
TAGGED("GraphUserTaggedStory"),
|
||||
// app story_type
|
||||
COMMENT_LIKE("13"),
|
||||
TAGGED_COMMENT("14"),
|
||||
RESPONDED_STORY("213"),
|
||||
// efr
|
||||
REQUEST("REQUEST");
|
||||
|
||||
private final String itemType;
|
||||
|
@ -12,6 +12,9 @@ import retrofit2.http.Path;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface MediaRepository {
|
||||
@GET("/api/v1/media/{mediaId}/info/")
|
||||
Call<String> fetch(@Path("mediaId") final String mediaId);
|
||||
|
||||
@GET("/api/v1/media/{mediaId}/likers/")
|
||||
Call<String> fetchLikes(@Path("mediaId") final String mediaId);
|
||||
|
||||
|
@ -6,16 +6,19 @@ import awais.instagrabber.utils.Constants;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface NewsRepository {
|
||||
|
||||
Call<String> inbox();
|
||||
|
||||
@FormUrlEncoded
|
||||
@Headers("User-Agent: " + Constants.USER_AGENT)
|
||||
@POST("https://www.instagram.com/web/activity/mark_checked/")
|
||||
Call<String> markChecked(@Header("x-csrftoken") String csrfToken, @FieldMap Map<String, String> map);
|
||||
@GET("https://www.instagram.com/accounts/activity/?__a=1")
|
||||
Call<String> webInbox();
|
||||
|
||||
@Headers("User-Agent: " + Constants.I_USER_AGENT)
|
||||
@GET("/api/v1/news/inbox/")
|
||||
Call<String> appInbox(@Query(value = "mark_as_seen", encoded = true) boolean markAsSeen);
|
||||
}
|
||||
|
@ -16,9 +16,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.repositories.MediaRepository;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
@ -46,6 +48,37 @@ public class MediaService extends BaseService {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void fetch(final String mediaId,
|
||||
final ServiceCallback<FeedModel> callback) {
|
||||
final Call<String> request = repository.fetch(mediaId);
|
||||
request.enqueue(new Callback<String>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull final Call<String> call,
|
||||
@NonNull final Response<String> response) {
|
||||
if (callback == null) return;
|
||||
final String body = response.body();
|
||||
if (body == null) {
|
||||
callback.onSuccess(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final JSONObject itemJson = new JSONObject(body).getJSONArray("items").getJSONObject(0);
|
||||
callback.onSuccess(ResponseBodyUtils.parseItem(itemJson));
|
||||
} catch (JSONException e) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Call<String> call,
|
||||
@NonNull final Throwable t) {
|
||||
if (callback != null) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void like(final String mediaId,
|
||||
final String userId,
|
||||
final String csrfToken,
|
||||
|
@ -1,14 +1,25 @@
|
||||
package awais.instagrabber.webservices;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
import awais.instagrabber.models.enums.NotificationType;
|
||||
import awais.instagrabber.repositories.NewsRepository;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.NetworkUtils;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
@ -35,25 +46,34 @@ public class NewsService extends BaseService {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void markChecked(final String timestamp,
|
||||
final String csrfToken,
|
||||
final ServiceCallback<Boolean> callback) {
|
||||
final Map<String, String> map = new HashMap<>();
|
||||
map.put("timestamp", timestamp);
|
||||
final Call<String> request = repository.markChecked(csrfToken, map);
|
||||
public void fetchAppInbox(final boolean markAsSeen,
|
||||
final ServiceCallback<List<NotificationModel>> callback) {
|
||||
final List<NotificationModel> result = new ArrayList<>();
|
||||
final Call<String> request = repository.appInbox(markAsSeen);
|
||||
request.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) {
|
||||
callback.onSuccess(false);
|
||||
callback.onSuccess(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final JSONObject jsonObject = new JSONObject(body);
|
||||
final String status = jsonObject.optString("status");
|
||||
callback.onSuccess(status.equals("ok"));
|
||||
final JSONArray oldStories = jsonObject.getJSONArray("old_stories"),
|
||||
newStories = jsonObject.getJSONArray("new_stories");
|
||||
|
||||
for (int j = 0; j < newStories.length(); ++j) {
|
||||
final NotificationModel newsItem = parseNewsItem(newStories.getJSONObject(j));
|
||||
if (newsItem != null) result.add(newsItem);
|
||||
}
|
||||
|
||||
for (int i = 0; i < oldStories.length(); ++i) {
|
||||
final NotificationModel newsItem = parseNewsItem(oldStories.getJSONObject(i));
|
||||
if (newsItem != null) result.add(newsItem);
|
||||
}
|
||||
|
||||
callback.onSuccess(result);
|
||||
} catch (JSONException e) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
@ -66,4 +86,112 @@ public class NewsService extends BaseService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void fetchWebInbox(final boolean markAsSeen,
|
||||
final ServiceCallback<List<NotificationModel>> callback) {
|
||||
final List<NotificationModel> result = new ArrayList<>();
|
||||
final Call<String> request = repository.webInbox();
|
||||
request.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) {
|
||||
callback.onSuccess(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final JSONObject page = new JSONObject(body)
|
||||
.getJSONObject("graphql")
|
||||
.getJSONObject("user");
|
||||
final JSONObject ewaf = page.getJSONObject("activity_feed")
|
||||
.optJSONObject("edge_web_activity_feed");
|
||||
final JSONObject efr = page.optJSONObject("edge_follow_requests");
|
||||
JSONObject data;
|
||||
JSONArray media;
|
||||
if (ewaf != null
|
||||
&& (media = ewaf.optJSONArray("edges")) != null
|
||||
&& media.length() > 0
|
||||
&& media.optJSONObject(0).optJSONObject("node") != null) {
|
||||
for (int i = 0; i < media.length(); ++i) {
|
||||
data = media.optJSONObject(i).optJSONObject("node");
|
||||
if (data == null) continue;
|
||||
final String type = data.getString("__typename");
|
||||
final NotificationType notificationType = NotificationType.valueOfType(type);
|
||||
if (notificationType == null) continue;
|
||||
final JSONObject user = data.getJSONObject("user");
|
||||
result.add(new NotificationModel(
|
||||
data.getString(Constants.EXTRAS_ID),
|
||||
data.optString("text"), // comments or mentions
|
||||
data.getLong("timestamp"),
|
||||
user.getString("id"),
|
||||
user.getString("username"),
|
||||
user.getString("profile_pic_url"),
|
||||
!data.isNull("media") ? data.getJSONObject("media").getString("id") : null,
|
||||
!data.isNull("media") ? data.getJSONObject("media").getString("thumbnail_src") : null, notificationType));
|
||||
}
|
||||
}
|
||||
|
||||
if (efr != null
|
||||
&& (media = efr.optJSONArray("edges")) != null
|
||||
&& media.length() > 0
|
||||
&& media.optJSONObject(0).optJSONObject("node") != null) {
|
||||
for (int i = 0; i < media.length(); ++i) {
|
||||
data = media.optJSONObject(i).optJSONObject("node");
|
||||
if (data == null) continue;
|
||||
result.add(new NotificationModel(
|
||||
data.getString(Constants.EXTRAS_ID),
|
||||
data.optString("full_name"),
|
||||
0L,
|
||||
data.getString(Constants.EXTRAS_ID),
|
||||
data.getString("username"),
|
||||
data.getString("profile_pic_url"),
|
||||
null,
|
||||
null, NotificationType.REQUEST));
|
||||
}
|
||||
}
|
||||
callback.onSuccess(result);
|
||||
} catch (JSONException e) {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||
callback.onFailure(t);
|
||||
// Log.e(TAG, "onFailure: ", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private NotificationModel parseNewsItem(final JSONObject itemJson) throws JSONException {
|
||||
if (itemJson == null) return null;
|
||||
final String type = itemJson.getString("story_type");
|
||||
final NotificationType notificationType = NotificationType.valueOfType(type);
|
||||
if (notificationType == null) {
|
||||
Log.d("austin_debug", "unhandled news type: "+itemJson);
|
||||
return null;
|
||||
}
|
||||
final JSONObject data = itemJson.getJSONObject("args");
|
||||
return new NotificationModel(
|
||||
data.getString("tuuid"),
|
||||
data.has("text") ? data.getString("text") : cleanRichText(data.optString("rich_text", "")),
|
||||
data.getLong("timestamp"),
|
||||
data.getString("profile_id"),
|
||||
data.getString("profile_name"),
|
||||
data.getString("profile_image"),
|
||||
!data.isNull("media") ? data.getJSONArray("media").getJSONObject(0).getString("id") : null,
|
||||
!data.isNull("media") ? data.getJSONArray("media").getJSONObject(0).getString("image") : null,
|
||||
notificationType);
|
||||
}
|
||||
|
||||
private String cleanRichText(final String raw) {
|
||||
final Matcher matcher = Pattern.compile("\\{[\\p{L}\\d._]+\\|000000\\|1\\|user\\?id=\\d+\\}").matcher(raw);
|
||||
String result = raw;
|
||||
while (matcher.find()) {
|
||||
final String richObject = raw.substring(matcher.start(), matcher.end());
|
||||
final String username = richObject.split("\\|")[0].substring(1);
|
||||
result = result.replace(richObject, username);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -105,13 +105,13 @@
|
||||
android:gravity="end"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingRight="16dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
android:textStyle="italic"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/ivPreviewPic"
|
||||
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvSubComment"
|
||||
tools:text="some long long long long long date" />
|
||||
|
@ -145,6 +145,7 @@
|
||||
<string name="quick_access_cannot_delete_curr">Cannot delete currently in use account</string>
|
||||
<string name="quick_access_confirm_delete">Are you sure you want to delete \'%s\'?</string>
|
||||
<string name="open_profile">Open profile</string>
|
||||
<string name="view_story">View story</string>
|
||||
<string name="view_pfp">View profile picture</string>
|
||||
<string name="direct_messages_you">You</string>
|
||||
<string name="direct_messages_sent_link">Shared a link</string>
|
||||
|
Loading…
Reference in New Issue
Block a user