From 37912854d0a1cef590ab5c2c803d887673ed53de Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 11 Sep 2020 22:27:09 +0900 Subject: [PATCH] Add activity/notification view fragment --- app/src/main/AndroidManifest.xml | 7 - .../instagrabber/activities/MainActivity.java | 3 +- .../activities/NotificationsViewer.java | 189 --------------- .../adapters/NotificationsAdapter.java | 90 +++----- .../viewholder/NotificationViewHolder.java | 105 ++++----- .../asyncs/NotificationsFetcher.java | 71 +++--- .../NotificationsViewerFragment.java | 215 ++++++++++++++++++ .../fragments/PostViewFragment.java | 1 + .../settings/MorePreferencesFragment.java | 6 +- .../models/NotificationModel.java | 29 ++- .../repositories/NewsRepository.java | 21 ++ .../services/AddCookiesInterceptor.java | 7 +- .../instagrabber/services/BaseService.java | 4 +- .../services/FriendshipService.java | 14 ++ .../instagrabber/services/NewsService.java | 69 ++++++ .../awais/instagrabber/utils/Constants.java | 1 + .../viewmodels/NotificationViewModel.java | 19 ++ .../main/res/layout/activity_notification.xml | 30 --- .../layout/fragment_notifications_viewer.xml | 15 ++ app/src/main/res/layout/item_notification.xml | 199 ++++++++++++++-- .../res/layout/layout_include_notif_item.xml | 152 ------------- .../main/res/navigation/more_nav_graph.xml | 38 ++++ app/src/main/res/values/dimens.xml | 2 + 23 files changed, 729 insertions(+), 558 deletions(-) delete mode 100755 app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java mode change 100755 => 100644 app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java mode change 100755 => 100644 app/src/main/java/awais/instagrabber/adapters/viewholder/NotificationViewHolder.java mode change 100755 => 100644 app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/NewsRepository.java create mode 100644 app/src/main/java/awais/instagrabber/services/NewsService.java mode change 100755 => 100644 app/src/main/java/awais/instagrabber/utils/Constants.java create mode 100644 app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java delete mode 100755 app/src/main/res/layout/activity_notification.xml create mode 100644 app/src/main/res/layout/fragment_notifications_viewer.xml mode change 100755 => 100644 app/src/main/res/layout/item_notification.xml delete mode 100755 app/src/main/res/layout/layout_include_notif_item.xml mode change 100755 => 100644 app/src/main/res/values/dimens.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b0576c00..7b9d8c27 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -120,13 +120,6 @@ - - - NAV_TO_MENU_ID_MAP = new HashMap<>(); private static final List REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment); private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"; diff --git a/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java b/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java deleted file mode 100755 index 2f754167..00000000 --- a/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java +++ /dev/null @@ -1,189 +0,0 @@ -package awais.instagrabber.activities; - -import android.content.DialogInterface; -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.View; -import android.widget.ArrayAdapter; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import java.io.DataOutputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -import awais.instagrabber.R; -import awais.instagrabber.adapters.NotificationsAdapter; -import awais.instagrabber.asyncs.NotificationsFetcher; -import awais.instagrabber.databinding.ActivityNotificationBinding; -import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.interfaces.MentionClickListener; -import awais.instagrabber.models.NotificationModel; -import awais.instagrabber.models.enums.NotificationType; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; - -import static awais.instagrabber.utils.Utils.notificationManager; - -public final class NotificationsViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { - private NotificationModel notificationModel; - private ActivityNotificationBinding notificationsBinding; - private ArrayAdapter commmentDialogAdapter; - private String shortCode, postId, userId; - private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); - private Resources resources; - String[] commentDialogList; - - @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { - notificationManager.cancel(1800000000); - if (TextUtils.isEmpty(cookie)) { - Toast.makeText(this, R.string.activity_notloggedin, Toast.LENGTH_SHORT).show(); - } - super.onCreate(savedInstanceState); - notificationsBinding = ActivityNotificationBinding.inflate(getLayoutInflater()); - setContentView(notificationsBinding.getRoot()); - notificationsBinding.swipeRefreshLayout.setOnRefreshListener(this); - resources = getResources(); - setSupportActionBar(notificationsBinding.toolbar.toolbar); - notificationsBinding.toolbar.toolbar.setTitle(R.string.action_notif); - onRefresh(); - } - - @Override - public void onRefresh() { - notificationsBinding.swipeRefreshLayout.setRefreshing(true); - new NotificationsFetcher(new FetchListener() { - @Override - public void onResult(final NotificationModel[] notificationModels) { - notificationsBinding.rvComments.setAdapter(new NotificationsAdapter(notificationModels, clickListener, mentionClickListener)); - notificationsBinding.swipeRefreshLayout.setRefreshing(false); - new SeenAction().execute(); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { - if (which == 0) - searchUsername(notificationModel.getUsername()); - else if (which == 1 && commentDialogList.length == 2) { - // startActivity(new Intent(getApplicationContext(), PostViewer.class) - // .putExtra(Constants.EXTRAS_POST, new PostModel(notificationModel.getShortcode(), false))); - } - else if (which == 1) new ProfileAction().execute("/approve/"); - else if (which == 2) new ProfileAction().execute("/ignore/"); - }; - - private final View.OnClickListener clickListener = v -> { - final Object tag = v.getTag(); - if (tag instanceof NotificationModel) { - notificationModel = (NotificationModel) tag; - - final String username = notificationModel.getUsername(); - final SpannableString title = new SpannableString(username + ":\n" + notificationModel.getText()); - title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - if (notificationModel.getShortcode() != null) commentDialogList = new String[]{ - resources.getString(R.string.open_profile), - resources.getString(R.string.view_post) - }; - else if (notificationModel.getType() == NotificationType.REQUEST) - commentDialogList = new String[]{ - resources.getString(R.string.open_profile), - resources.getString(R.string.request_approve), - resources.getString(R.string.request_reject) - }; - else commentDialogList = new String[]{ - resources.getString(R.string.open_profile) - }; - - 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 void searchUsername(final String text) { - // startActivity(new Intent(getApplicationContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text)); - } - - class ProfileAction extends AsyncTask { - boolean ok = false; - String action; - - protected Void doInBackground(String... rawAction) { - action = rawAction[0]; - final String url = "https://www.instagram.com/web/friendships/"+notificationModel.getId()+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", - Utils.settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]); - urlConnection.connect(); - if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { - ok = true; - } - urlConnection.disconnect(); - } catch (Throwable ex) { - Log.e("austin_debug", action+": " + ex); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - if (ok == true) { - onRefresh(); - } - else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } - } - - class SeenAction extends AsyncTask { - protected Void doInBackground(Void... lmao) { - try { - final HttpURLConnection urlConnection = - (HttpURLConnection) new URL("https://www.instagram.com/web/activity/mark_checked/").openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setUseCaches(false); - urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); - urlConnection.setRequestProperty("x-csrftoken", - Utils.settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]); - final String urlParameters = "timestamp="+(System.currentTimeMillis()/1000); - 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(); - } catch (Throwable ex) { - Log.e("austin_debug", "seen: " + ex); - } - return null; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java old mode 100755 new mode 100644 index b5d9606b..dac5f477 --- a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java @@ -1,91 +1,55 @@ package awais.instagrabber.adapters; -import android.content.Context; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; -import com.bumptech.glide.Glide; -import com.bumptech.glide.RequestManager; -import com.bumptech.glide.request.RequestOptions; - -import awais.instagrabber.R; import awais.instagrabber.adapters.viewholder.NotificationViewHolder; +import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.models.NotificationModel; -import awais.instagrabber.models.enums.NotificationType; -public final class NotificationsAdapter extends RecyclerView.Adapter { - private final View.OnClickListener onClickListener; +public final class NotificationsAdapter extends ListAdapter { + private final OnNotificationClickListener notificationClickListener; private final MentionClickListener mentionClickListener; - private final NotificationModel[] notificationModels; - private LayoutInflater layoutInflater; - public NotificationsAdapter(final NotificationModel[] notificationModels, final View.OnClickListener onClickListener, + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final NotificationModel oldItem, @NonNull final NotificationModel newItem) { + return oldItem.getId().equals(newItem.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull final NotificationModel oldItem, @NonNull final NotificationModel newItem) { + return oldItem.getId().equals(newItem.getId()); + } + }; + + public NotificationsAdapter(final OnNotificationClickListener notificationClickListener, final MentionClickListener mentionClickListener) { - this.notificationModels = notificationModels; - this.onClickListener = onClickListener; + super(DIFF_CALLBACK); + this.notificationClickListener = notificationClickListener; this.mentionClickListener = mentionClickListener; } @NonNull @Override public NotificationViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) { - final Context context = parent.getContext(); - if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); - return new NotificationViewHolder(layoutInflater.inflate(R.layout.item_notification, - parent, false), onClickListener, mentionClickListener); + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false); + return new NotificationViewHolder(binding); } @Override public void onBindViewHolder(@NonNull final NotificationViewHolder holder, final int position) { - final NotificationModel notificationModel = notificationModels[position]; - if (notificationModel != null) { - holder.setNotificationModel(notificationModel); - - int text = -1; - CharSequence subtext = null; - switch (notificationModel.getType()) { - case LIKE: - text = R.string.liked_notif; - break; - case COMMENT: - text = R.string.comment_notif; - subtext = notificationModel.getText(); - break; - case MENTION: - text = R.string.mention_notif; - subtext = notificationModel.getText(); - break; - case FOLLOW: - text = R.string.follow_notif; - break; - case REQUEST: - text = R.string.request_notif; - subtext = notificationModel.getText(); - break; - } - - holder.setCommment(text); - holder.setSubCommment(subtext); - if (notificationModel.getType() != NotificationType.REQUEST) - holder.setDate(notificationModel.getDateTime()); - - holder.setUsername(notificationModel.getUsername()); - - final RequestManager rm = Glide.with(layoutInflater.getContext()) - .applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true)); - - rm.load(notificationModel.getProfilePic()).into(holder.getProfilePicView()); - rm.load(notificationModel.getPreviewPic()).into(holder.getPreviewPicView()); - } + final NotificationModel notificationModel = getItem(position); + holder.bind(notificationModel, notificationClickListener); } - @Override - public int getItemCount() { - return notificationModels == null ? 0 : notificationModels.length; + public interface OnNotificationClickListener { + void onNotificationClick(final NotificationModel model); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/NotificationViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/NotificationViewHolder.java old mode 100755 new mode 100644 index e660c5e2..fbd527c0 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/NotificationViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/NotificationViewHolder.java @@ -1,75 +1,68 @@ package awais.instagrabber.adapters.viewholder; import android.text.Spannable; +import android.text.TextUtils; import android.view.View; -import android.widget.ImageView; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import awais.instagrabber.R; -import awais.instagrabber.customviews.RamboTextView; -import awais.instagrabber.interfaces.MentionClickListener; +import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; +import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.models.NotificationModel; +import awais.instagrabber.models.enums.NotificationType; public final class NotificationViewHolder extends RecyclerView.ViewHolder { - private final MentionClickListener mentionClickListener; - private final ImageView ivProfilePic, ivPreviewPic; - private final TextView tvUsername, tvDate, tvComment, tvSubComment; - private final View container, rightContainer; + private final ItemNotificationBinding binding; - public NotificationViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) { - super(itemView); - - container = itemView.findViewById(R.id.container); - rightContainer = itemView.findViewById(R.id.rightContainer); - if (onClickListener != null) container.setOnClickListener(onClickListener); - - this.mentionClickListener = mentionClickListener; - - ivProfilePic = itemView.findViewById(R.id.ivProfilePic); - ivPreviewPic = itemView.findViewById(R.id.ivPreviewPic); - tvUsername = itemView.findViewById(R.id.tvUsername); - tvDate = itemView.findViewById(R.id.tvDate); - tvComment = itemView.findViewById(R.id.tvComment); - tvSubComment = itemView.findViewById(R.id.tvSubComment); - - tvUsername.setSelected(true); - tvDate.setSelected(true); + public NotificationViewHolder(final ItemNotificationBinding binding) { + super(binding.getRoot()); + this.binding = binding; } - public final ImageView getProfilePicView() { - return ivProfilePic; - } - - public final ImageView getPreviewPicView() { - return ivPreviewPic; - } - - public final void setNotificationModel(final NotificationModel notificationModel) { - if (container != null) container.setTag(notificationModel); - if (rightContainer != null) rightContainer.setTag(notificationModel); - } - - public final void setUsername(final String username) { - if (tvUsername != null) tvUsername.setText(username); - } - - public final void setDate(final String date) { - if (tvDate != null) tvDate.setText(date); - } - - public final void setCommment(final int commment) { - if (tvComment != null) { - tvComment.setText(commment); + 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()) { + case LIKE: + text = R.string.liked_notif; + break; + case COMMENT: + text = R.string.comment_notif; + subtext = model.getText(); + break; + case MENTION: + text = R.string.mention_notif; + subtext = model.getText(); + break; + case FOLLOW: + text = R.string.follow_notif; + break; + case REQUEST: + text = R.string.request_notif; + subtext = model.getText(); + break; } - } - - public final void setSubCommment(final CharSequence commment) { - if (tvSubComment != null) { - tvSubComment.setText(commment, commment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL); - ((RamboTextView) tvSubComment).setMentionClickListener(mentionClickListener); + 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 (model.getType() != NotificationType.REQUEST) { + binding.tvDate.setText(model.getDateTime()); + } + binding.ivProfilePic.setImageURI(model.getProfilePic()); + if (TextUtils.isEmpty(model.getPreviewPic())) { + binding.ivPreviewPic.setVisibility(View.GONE); + } else { + binding.ivPreviewPic.setVisibility(View.VISIBLE); + binding.ivPreviewPic.setImageURI(model.getPreviewPic()); } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java old mode 100755 new mode 100644 index 1cf9b4bf..50284647 --- a/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java @@ -8,6 +8,8 @@ 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; @@ -22,16 +24,18 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; -public final class NotificationsFetcher extends AsyncTask { - private final FetchListener fetchListener; +public final class NotificationsFetcher extends AsyncTask> { + private static final String TAG = "NotificationsFetcher"; - public NotificationsFetcher(final FetchListener fetchListener) { + private final FetchListener> fetchListener; + + public NotificationsFetcher(final FetchListener> fetchListener) { this.fetchListener = fetchListener; } @Override - protected NotificationModel[] doInBackground(final Void... voids) { - NotificationModel[] result = null; + protected List doInBackground(final Void... voids) { + List result = new ArrayList<>(); final String url = "https://www.instagram.com/accounts/activity/?__a=1"; CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); @@ -43,37 +47,34 @@ public final class NotificationsFetcher extends AsyncTask 0 && media.optJSONObject(0).optJSONObject("node") != null) { - mediaLen = media.length(); - models = new NotificationModel[mediaLen]; - for (int i = 0; i < mediaLen; ++i) { + 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; - models[i] = new NotificationModel( + final JSONObject user = data.getJSONObject("user"); + result.add(new NotificationModel( data.getString(Constants.EXTRAS_ID), data.optString("text"), // comments or mentions data.getLong("timestamp"), - data.getJSONObject("user").getString("username"), - data.getJSONObject("user").getString("profile_pic_url"), + 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); + !data.isNull("media") ? data.getJSONObject("media").getString("thumbnail_src") : null, notificationType)); } } @@ -81,29 +82,27 @@ public final class NotificationsFetcher extends AsyncTask 0 && media.optJSONObject(0).optJSONObject("node") != null) { - reqLen = media.length(); - req = new NotificationModel[reqLen]; - for (int i = 0; i < reqLen; ++i) { + for (int i = 0; i < media.length(); ++i) { data = media.optJSONObject(i).optJSONObject("node"); if (data == null) continue; - req[i] = new NotificationModel(data.getString(Constants.EXTRAS_ID), - data.optString("full_name"), 0L, data.getString("username"), - data.getString("profile_pic_url"), null, null, NotificationType.REQUEST); + 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)); } } - - result = new NotificationModel[mediaLen + reqLen]; - if (req != null) System.arraycopy(req, 0, result, 0, reqLen); - if (models != null) System.arraycopy(models, 0, result, reqLen, mediaLen); } - conn.disconnect(); } catch (final Exception e) { if (logCollector != null) logCollector.appendException(e, LogCollector.LogFile.ASYNC_NOTIFICATION_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } - return result; } @@ -113,7 +112,7 @@ public final class NotificationsFetcher extends AsyncTask result) { if (fetchListener != null) fetchListener.onResult(result); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java new file mode 100644 index 00000000..9db22df9 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -0,0 +1,215 @@ +package awais.instagrabber.fragments; + +import android.content.DialogInterface; +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.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavDirections; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.NotificationsAdapter; +import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; +import awais.instagrabber.asyncs.NotificationsFetcher; +import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; +import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; +import awais.instagrabber.interfaces.MentionClickListener; +import awais.instagrabber.models.enums.NotificationType; +import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; +import awais.instagrabber.services.FriendshipService; +import awais.instagrabber.services.NewsService; +import awais.instagrabber.services.ServiceCallback; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; +import awais.instagrabber.viewmodels.NotificationViewModel; + +import static awais.instagrabber.utils.Utils.notificationManager; + +public final class NotificationsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { + private static final String TAG = "NotificationsViewer"; + + private FragmentNotificationsViewerBinding binding; + private SwipeRefreshLayout root; + private boolean shouldRefresh = true; + private NotificationViewModel notificationViewModel; + private FriendshipService friendshipService; + private String userId; + private String csrfToken; + private NewsService newsService; + + 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)}; + } + if (getContext() == 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() { + @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 NavDirections action = MorePreferencesFragmentDirections + .actionGlobalPostViewFragment(0, new String[]{model.getShortCode()}, false); + NavHostFragment.findNavController(this).navigate(action); + break; + case 2: + friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback() { + @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(getContext()) + .setTitle(title) + .setItems(commentDialogList, profileDialogListener) + .setNegativeButton(R.string.cancel, null) + .show(); + }; + private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> { + if (getContext() == null) return; + new AlertDialog.Builder(getContext()) + .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) -> openProfile(text)) + .show(); + }; + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + notificationManager.cancel(Constants.ACTIVITY_NOTIFICATION_ID); + final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); + if (TextUtils.isEmpty(cookie)) { + Toast.makeText(getContext(), R.string.activity_notloggedin, Toast.LENGTH_SHORT).show(); + } + friendshipService = FriendshipService.getInstance(); + newsService = NewsService.getInstance(); + userId = CookieUtils.getUserIdFromCookie(cookie); + csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + } + + @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 = FragmentNotificationsViewerBinding.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; + } + + private void init() { + binding.swipeRefreshLayout.setOnRefreshListener(this); + notificationViewModel = new ViewModelProvider(this).get(NotificationViewModel.class); + final NotificationsAdapter adapter = new NotificationsAdapter(clickListener, mentionClickListener); + binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext())); + binding.rvComments.setAdapter(adapter); + notificationViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + onRefresh(); + } + + @Override + public void onRefresh() { + binding.swipeRefreshLayout.setRefreshing(true); + new NotificationsFetcher(notificationModels -> { + binding.swipeRefreshLayout.setRefreshing(false); + notificationViewModel.getList().postValue(notificationModels); + final String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + newsService.markChecked(timestamp, csrfToken, new ServiceCallback() { + @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); + } + + private void openProfile(final String username) { + final NavDirections action = MorePreferencesFragmentDirections + .actionGlobalProfileFragment("@" + username); + NavHostFragment.findNavController(this).navigate(action); + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java index f68df3b0..405bcfee 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java @@ -291,6 +291,7 @@ public class PostViewFragment extends Fragment { return; } } + if (currentPostIndex >= idOrCodeList.size() || currentPostIndex < 0) return; final String idOrShortCode = idOrCodeList.get(currentPostIndex); if (isId) { new iPostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 797e0bcd..36419e5b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -100,7 +100,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment { generalCategory.setTitle("General"); generalCategory.setIconSpaceReserved(false); screen.addPreference(generalCategory); - generalCategory.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> false)); + generalCategory.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToNotificationsViewer(); + NavHostFragment.findNavController(this).navigate(navDirections); + return true; + })); generalCategory.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> { final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment(); NavHostFragment.findNavController(this).navigate(navDirections); diff --git a/app/src/main/java/awais/instagrabber/models/NotificationModel.java b/app/src/main/java/awais/instagrabber/models/NotificationModel.java index 850d184a..d461a033 100755 --- a/app/src/main/java/awais/instagrabber/models/NotificationModel.java +++ b/app/src/main/java/awais/instagrabber/models/NotificationModel.java @@ -9,19 +9,32 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; public final class NotificationModel { - private final String id, username, profilePicUrl, shortcode, previewUrl; + private final String id; + private final String userId; + private final String username; + private final String profilePicUrl; + private final String shortCode; + private final String previewUrl; private final NotificationType type; private final CharSequence text; private final long timestamp; - public NotificationModel(final String id, final String text, final long timestamp, final String username, - final String profilePicUrl, final String shortcode, final String previewUrl, final NotificationType type) { + public NotificationModel(final String id, + final String text, + final long timestamp, + final String userId, + final String username, + final String profilePicUrl, + final String shortCode, + final String previewUrl, + final NotificationType type) { this.id = id; this.text = TextUtils.hasMentions(text) ? TextUtils.getMentionText(text) : text; this.timestamp = timestamp; + this.userId = userId; this.username = username; this.profilePicUrl = profilePicUrl; - this.shortcode = shortcode; + this.shortCode = shortCode; this.previewUrl = previewUrl; this.type = type; } @@ -39,6 +52,10 @@ public final class NotificationModel { return Utils.datetimeParser.format(new Date(timestamp * 1000L)); } + public String getUserId() { + return userId; + } + public String getUsername() { return username; } @@ -47,8 +64,8 @@ public final class NotificationModel { return profilePicUrl; } - public String getShortcode() { - return shortcode; + public String getShortCode() { + return shortCode; } public String getPreviewPic() { diff --git a/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java b/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java new file mode 100644 index 00000000..e8e71116 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java @@ -0,0 +1,21 @@ +package awais.instagrabber.repositories; + +import java.util.Map; + +import awais.instagrabber.utils.Constants; +import retrofit2.Call; +import retrofit2.http.FieldMap; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.Header; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +public interface NewsRepository { + + Call inbox(); + + @FormUrlEncoded + @Headers("User-Agent: " + Constants.USER_AGENT) + @POST("https://www.instagram.com/web/activity/mark_checked/") + Call markChecked(@Header("x-csrftoken") String csrfToken, @FieldMap Map map); +} diff --git a/app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java b/app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java index 38709e09..6b6d04bb 100644 --- a/app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java +++ b/app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java @@ -17,8 +17,11 @@ public class AddCookiesInterceptor implements Interceptor { final Request request = chain.request(); final Request.Builder builder = request.newBuilder(); final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); - builder.addHeader("Cookie", cookie) - .addHeader("User-Agent", Constants.I_USER_AGENT); + builder.addHeader("Cookie", cookie); + final String userAgentHeader = "User-Agent"; + if (request.header(userAgentHeader) == null) { + builder.addHeader(userAgentHeader, Constants.I_USER_AGENT); + } final Request updatedRequest = builder.build(); return chain.proceed(updatedRequest); } diff --git a/app/src/main/java/awais/instagrabber/services/BaseService.java b/app/src/main/java/awais/instagrabber/services/BaseService.java index 6347ed0c..1f78a22d 100644 --- a/app/src/main/java/awais/instagrabber/services/BaseService.java +++ b/app/src/main/java/awais/instagrabber/services/BaseService.java @@ -19,8 +19,8 @@ public abstract class BaseService { if (builder == null) { final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() .addInterceptor(new AddCookiesInterceptor()) - .followRedirects(true) - .followSslRedirects(true); + .followRedirects(false) + .followSslRedirects(false); if (BuildConfig.DEBUG) { // clientBuilder.addInterceptor(new LoggingInterceptor()); } diff --git a/app/src/main/java/awais/instagrabber/services/FriendshipService.java b/app/src/main/java/awais/instagrabber/services/FriendshipService.java index c3b500d7..f6ec3b3a 100644 --- a/app/src/main/java/awais/instagrabber/services/FriendshipService.java +++ b/app/src/main/java/awais/instagrabber/services/FriendshipService.java @@ -99,6 +99,20 @@ public class FriendshipService extends BaseService { }); } + public void approve(final String userId, + final String targetUserId, + final String crsfToken, + final ServiceCallback callback) { + change("approve", userId, targetUserId, crsfToken, callback); + } + + public void ignore(final String userId, + final String targetUserId, + final String crsfToken, + final ServiceCallback callback) { + change("ignore", userId, targetUserId, crsfToken, callback); + } + private void change(final String action, final String userId, final String targetUserId, diff --git a/app/src/main/java/awais/instagrabber/services/NewsService.java b/app/src/main/java/awais/instagrabber/services/NewsService.java new file mode 100644 index 00000000..d8acc607 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/services/NewsService.java @@ -0,0 +1,69 @@ +package awais.instagrabber.services; + +import androidx.annotation.NonNull; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import awais.instagrabber.repositories.NewsRepository; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class NewsService extends BaseService { + private static final String TAG = "NewsService"; + + private final NewsRepository repository; + + private static NewsService instance; + + private NewsService() { + final Retrofit retrofit = getRetrofitBuilder() + .baseUrl("https://i.instagram.com") + .build(); + repository = retrofit.create(NewsRepository.class); + } + + public static NewsService getInstance() { + if (instance == null) { + instance = new NewsService(); + } + return instance; + } + + public void markChecked(final String timestamp, + final String csrfToken, + final ServiceCallback callback) { + final Map map = new HashMap<>(); + map.put("timestamp", timestamp); + final Call request = repository.markChecked(csrfToken, map); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + 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) { + callback.onFailure(e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + callback.onFailure(t); + // Log.e(TAG, "onFailure: ", t); + } + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java old mode 100755 new mode 100644 index 158ebd8f..df57564a --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -74,4 +74,5 @@ public final class Constants { public static final String CHANNEL_ID = "InstaGrabber"; public static final String CHANNEL_NAME = "Instagrabber"; public static final String NOTIF_GROUP_NAME = "awais.instagrabber.InstaNotif"; + public static final int ACTIVITY_NOTIFICATION_ID = 1800000000; } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java new file mode 100644 index 00000000..aa8fc194 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java @@ -0,0 +1,19 @@ +package awais.instagrabber.viewmodels; + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import java.util.List; + +import awais.instagrabber.models.NotificationModel; + +public class NotificationViewModel extends ViewModel { + private MutableLiveData> list; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml deleted file mode 100755 index f8439a55..00000000 --- a/app/src/main/res/layout/activity_notification.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_notifications_viewer.xml b/app/src/main/res/layout/fragment_notifications_viewer.xml new file mode 100644 index 00000000..dd3cda20 --- /dev/null +++ b/app/src/main/res/layout/fragment_notifications_viewer.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_notification.xml b/app/src/main/res/layout/item_notification.xml old mode 100755 new mode 100644 index e65bfcdc..f2c344f1 --- a/app/src/main/res/layout/item_notification.xml +++ b/app/src/main/res/layout/item_notification.xml @@ -1,19 +1,192 @@ - + android:background="?android:selectableItemBackground" + android:baselineAligned="false" + android:padding="8dp"> - + - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_include_notif_item.xml b/app/src/main/res/layout/layout_include_notif_item.xml deleted file mode 100755 index 828e2547..00000000 --- a/app/src/main/res/layout/layout_include_notif_item.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml index f025d67e..1efae0f1 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -1,9 +1,39 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml old mode 100755 new mode 100644 index 65515f29..3029893c --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -15,7 +15,9 @@ 80dp 35dp + 40dp + 56dp @dimen/simple_item_picture_size @dimen/feed_profile_size 500dp