From 027b356357aa2c62ab965d96daf821e1a9d7dc7a Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 15 Sep 2020 19:25:32 +0900 Subject: [PATCH 01/25] Fix recurring alert sound if notification not tapped --- .../services/ActivityCheckerService.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java index fb01201c..255d051c 100644 --- a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java +++ b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java @@ -9,6 +9,7 @@ import android.os.Handler; import android.os.IBinder; import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -115,18 +116,24 @@ public class ActivityCheckerService extends Service { } private void showNotification(final String notificationString) { - final Intent intent = new Intent(getApplicationContext(), MainActivity.class) - .setAction(Constants.ACTION_SHOW_ACTIVITY) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); final Notification notification = new NotificationCompat.Builder(this, Constants.ACTIVITY_CHANNEL_ID) .setCategory(NotificationCompat.CATEGORY_STATUS) .setSmallIcon(R.drawable.ic_notif) .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_MIN) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentTitle(getString(R.string.action_notif)) .setContentText(notificationString) - .setContentIntent(PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setContentIntent(getPendingIntent()) .build(); notificationManager.notify(Constants.ACTIVITY_NOTIFICATION_ID, notification); } + + @NonNull + private PendingIntent getPendingIntent() { + final Intent intent = new Intent(getApplicationContext(), MainActivity.class) + .setAction(Constants.ACTION_SHOW_ACTIVITY) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + return PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } } From cf974a74f3bd060747bd1622d418cfe85413bdae Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 19 Sep 2020 04:18:14 +0900 Subject: [PATCH 02/25] Add favorites, also fixes the hashtag follow, and some theming --- .gitignore | 1 + app/build.gradle | 2 +- .../instagrabber/activities/MainActivity.java | 9 +- .../adapters/FavoritesAdapter.java | 202 +++++++++++ .../viewholder/FavoriteViewHolder.java | 61 ++++ .../instagrabber/asyncs/FeedFetcher.java | 7 +- .../instagrabber/asyncs/HashtagFetcher.java | 35 +- .../instagrabber/asyncs/PostsFetcher.java | 58 +-- .../asyncs/SuggestionsFetcher.java | 3 +- .../instagrabber/asyncs/i/iLikedFetcher.java | 72 ++-- .../dialogs/QuickAccessDialog.java | 181 --------- .../fragments/FavoritesFragment.java | 215 +++++++++++ .../fragments/HashTagFragment.java | 161 +++++++-- .../fragments/LocationFragment.java | 79 +++- .../fragments/SavedViewerFragment.java | 21 +- .../fragments/main/ProfileFragment.java | 306 +++++++++------- .../settings/MorePreferencesFragment.java | 5 + .../models/enums/FavoriteType.java | 7 + .../repositories/TagsRepository.java | 19 + .../awais/instagrabber/utils/Constants.java | 1 + .../awais/instagrabber/utils/DataBox.java | 342 +++++++++++++----- .../instagrabber/utils/ExportImportUtils.java | 33 +- .../utils/NavigationExtensions.java | 3 +- .../java/awais/instagrabber/utils/Utils.java | 2 - .../viewmodels/FavoritesViewModel.java | 19 + .../instagrabber/webservices/TagsService.java | 101 ++++++ app/src/main/res/drawable/ic_block_24.xml | 10 + .../main/res/drawable/ic_highlight_off_24.xml | 10 + .../main/res/drawable/ic_outline_class_24.xml | 10 + .../main/res/drawable/ic_outline_map_24.xml | 10 + .../res/drawable/ic_outline_person_add_24.xml | 10 + .../ic_outline_person_add_disabled_24.xml | 10 + .../res/drawable/ic_outline_person_pin_24.xml | 10 + .../main/res/drawable/ic_outline_star_24.xml | 10 + .../res/drawable/ic_outline_star_plus_24.xml | 10 + .../main/res/drawable/ic_star_check_24.xml | 8 + .../drawable/rounder_corner_semi_black_bg.xml | 2 +- app/src/main/res/drawable/sl_favourite_24.xml | 6 + app/src/main/res/layout/activity_main.xml | 1 - app/src/main/res/layout/dialog_profilepic.xml | 2 +- .../main/res/layout/fragment_favorites.xml | 7 + app/src/main/res/layout/fragment_hashtag.xml | 119 ++++-- app/src/main/res/layout/fragment_location.xml | 155 ++++---- app/src/main/res/layout/fragment_profile.xml | 126 ++++--- .../res/layout/item_fav_section_header.xml | 13 + app/src/main/res/layout/item_feed_slider.xml | 2 +- app/src/main/res/layout/item_feed_top.xml | 4 +- app/src/main/res/layout/item_suggestion.xml | 17 +- app/src/main/res/menu/profile_menu.xml | 27 +- .../main/res/navigation/more_nav_graph.xml | 25 ++ app/src/main/res/values/color.xml | 64 +++- app/src/main/res/values/strings.xml | 10 +- app/src/main/res/values/styles.xml | 20 + app/src/main/res/values/themes.xml | 1 + 54 files changed, 1915 insertions(+), 729 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java create mode 100644 app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java delete mode 100755 app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java create mode 100644 app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/TagsRepository.java create mode 100644 app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java create mode 100644 app/src/main/java/awais/instagrabber/webservices/TagsService.java create mode 100644 app/src/main/res/drawable/ic_block_24.xml create mode 100644 app/src/main/res/drawable/ic_highlight_off_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_class_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_map_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_person_add_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_person_pin_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_star_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_star_plus_24.xml create mode 100644 app/src/main/res/drawable/ic_star_check_24.xml create mode 100644 app/src/main/res/drawable/sl_favourite_24.xml create mode 100644 app/src/main/res/layout/fragment_favorites.xml create mode 100644 app/src/main/res/layout/item_fav_section_header.xml diff --git a/.gitignore b/.gitignore index 57870dac..631fa4ed 100755 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ app/release .idea/git_toolbox_prj.xml +.idea/dbnavigator.xml diff --git a/app/build.gradle b/app/build.gradle index 9eb639d7..87be6298 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,7 +41,7 @@ dependencies { def nav_version = "2.3.0" def preference_version = "1.1.1" - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.exoplayer:exoplayer:2.11.1' implementation "androidx.appcompat:appcompat:$appcompat_version" diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 7f9338b8..ef176505 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -38,6 +38,7 @@ import androidx.navigation.ui.NavigationUI; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.bottomnavigation.BottomNavigationView; import java.util.ArrayList; import java.util.Arrays; @@ -91,7 +92,8 @@ public class MainActivity extends BaseLanguageActivity { R.id.followViewerFragment, R.id.directMessagesSettingsFragment, R.id.notificationsViewer, - R.id.themePreferencesFragment); + R.id.themePreferencesFragment, + R.id.favoritesFragment); private static final Map 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"; @@ -582,4 +584,9 @@ public class MainActivity extends BaseLanguageActivity { unbindService(serviceConnection); isActivityCheckerServiceBound = false; } + + @NonNull + public BottomNavigationView getBottomNavView() { + return binding.bottomNavView; + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java new file mode 100644 index 00000000..4a4f47fb --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java @@ -0,0 +1,202 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.ObjectsCompat; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.viewholder.FavoriteViewHolder; +import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; +import awais.instagrabber.databinding.ItemSuggestionBinding; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.DataBox; + +public class FavoritesAdapter extends RecyclerView.Adapter { + + private final OnFavoriteClickListener clickListener; + private final OnFavoriteLongClickListener longClickListener; + private final AsyncListDiffer differ; + + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) { + boolean areSame = oldItem.isHeader() && newItem.isHeader(); + if (!areSame) { + return false; + } + if (oldItem.isHeader()) { + return ObjectsCompat.equals(oldItem.header, newItem.header); + } + if (oldItem.model != null && newItem.model != null) { + return oldItem.model.getId() == newItem.model.getId(); + } + return false; + } + + @Override + public boolean areContentsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) { + boolean areSame = oldItem.isHeader() && newItem.isHeader(); + if (!areSame) { + return false; + } + if (oldItem.isHeader()) { + return ObjectsCompat.equals(oldItem.header, newItem.header); + } + return ObjectsCompat.equals(oldItem.model, newItem.model); + } + }; + + public FavoritesAdapter(final OnFavoriteClickListener clickListener, final OnFavoriteLongClickListener longClickListener) { + this.clickListener = clickListener; + this.longClickListener = longClickListener; + differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(diffCallback).build()); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + if (viewType == 0) { + // header + return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false)); + } + final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false); + return new FavoriteViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { + if (getItemViewType(position) == 0) { + final FavoriteModelOrHeader modelOrHeader = getItem(position); + if (!modelOrHeader.isHeader()) return; + ((FavSectionViewHolder) holder).bind(modelOrHeader.header); + return; + } + ((FavoriteViewHolder) holder).bind(getItem(position).model, clickListener, longClickListener); + } + + protected FavoriteModelOrHeader getItem(int position) { + return differ.getCurrentList().get(position); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + @Override + public int getItemViewType(final int position) { + return getItem(position).isHeader() ? 0 : 1; + } + + public void submitList(@Nullable final List list) { + if (list == null) { + differ.submitList(null); + return; + } + differ.submitList(sectionAndSort(list)); + } + + public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) { + if (list == null) { + differ.submitList(null, commitCallback); + return; + } + differ.submitList(sectionAndSort(list), commitCallback); + } + + @NonNull + private List sectionAndSort(@NonNull final List list) { + final List listCopy = new ArrayList<>(list); + Collections.sort(listCopy, (o1, o2) -> { + if (o1.getType() == o2.getType()) return 0; + // keep users at top + if (o1.getType() == FavoriteType.USER) return -1; + if (o2.getType() == FavoriteType.USER) return 1; + // keep locations at bottom + if (o1.getType() == FavoriteType.LOCATION) return 1; + if (o2.getType() == FavoriteType.LOCATION) return -1; + return 0; + }); + final List modelOrHeaders = new ArrayList<>(); + for (int i = 0; i < listCopy.size(); i++) { + final DataBox.FavoriteModel model = listCopy.get(i); + final FavoriteModelOrHeader prev = modelOrHeaders.isEmpty() ? null : modelOrHeaders.get(modelOrHeaders.size() - 1); + boolean prevWasSameType = prev != null && prev.model.getType() == model.getType(); + if (prevWasSameType) { + // just add model + final FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader(); + modelOrHeader.model = model; + modelOrHeaders.add(modelOrHeader); + continue; + } + // add header and model + FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader(); + modelOrHeader.header = model.getType(); + modelOrHeaders.add(modelOrHeader); + modelOrHeader = new FavoriteModelOrHeader(); + modelOrHeader.model = model; + modelOrHeaders.add(modelOrHeader); + } + return modelOrHeaders; + } + + private static class FavoriteModelOrHeader { + FavoriteType header; + DataBox.FavoriteModel model; + + boolean isHeader() { + return header != null; + } + } + + public interface OnFavoriteClickListener { + void onClick(final DataBox.FavoriteModel model); + } + + public interface OnFavoriteLongClickListener { + boolean onLongClick(final DataBox.FavoriteModel model); + } + + public static class FavSectionViewHolder extends RecyclerView.ViewHolder { + private final ItemFavSectionHeaderBinding binding; + + public FavSectionViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public void bind(final FavoriteType header) { + if (header == null) return; + final int headerText; + switch (header) { + case USER: + headerText = R.string.accounts; + break; + case HASHTAG: + headerText = R.string.hashtags; + break; + case LOCATION: + headerText = R.string.locations; + break; + default: + headerText = R.string.unknown; + break; + } + binding.getRoot().setText(headerText); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java new file mode 100644 index 00000000..360525f7 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java @@ -0,0 +1,61 @@ +package awais.instagrabber.adapters.viewholder; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import awais.instagrabber.adapters.FavoritesAdapter; +import awais.instagrabber.databinding.ItemSuggestionBinding; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DataBox; + +public class FavoriteViewHolder extends RecyclerView.ViewHolder { + private static final String TAG = "FavoriteViewHolder"; + + private final ItemSuggestionBinding binding; + + public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) { + super(binding.getRoot()); + this.binding = binding; + binding.isVerified.setVisibility(View.GONE); + } + + public void bind(final DataBox.FavoriteModel model, + final FavoritesAdapter.OnFavoriteClickListener clickListener, + final FavoritesAdapter.OnFavoriteLongClickListener longClickListener) { + // Log.d(TAG, "bind: " + model); + if (model == null) return; + itemView.setOnClickListener(v -> { + if (clickListener == null) return; + clickListener.onClick(model); + }); + itemView.setOnLongClickListener(v -> { + if (clickListener == null) return false; + return longClickListener.onLongClick(model); + }); + if (model.getType() == FavoriteType.HASHTAG) { + binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC); + } else { + binding.ivProfilePic.setImageURI(model.getPicUrl()); + } + binding.tvFullName.setText(model.getDisplayName()); + binding.tvUsername.setVisibility(View.VISIBLE); + String query = model.getQuery(); + switch (model.getType()) { + case HASHTAG: + query = "#" + query; + break; + case USER: + query = "@" + query; + break; + case LOCATION: + binding.tvUsername.setVisibility(View.GONE); + break; + default: + // do nothing + } + binding.tvUsername.setText(query); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java index 8f139ff7..855f7f17 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java @@ -212,9 +212,10 @@ public final class FeedFetcher extends AsyncTask { feedModelsList.trimToSize(); final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]); - if (feedModels[feedModels.length - 1] != null) - feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor); - + final int length = feedModels.length; + if (length >= 1 && feedModels[length - 1] != null) { + feedModels[length - 1].setPageCursor(hasNextPage, endCursor); + } result = feedModels; } diff --git a/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java index 95122ce4..96ff320d 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java @@ -8,6 +8,10 @@ import androidx.annotation.Nullable; import org.json.JSONArray; import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; @@ -21,6 +25,8 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; public final class HashtagFetcher extends AsyncTask { + private static final String TAG = "HashtagFetcher"; + private final FetchListener fetchListener; private final String hashtag; @@ -35,12 +41,14 @@ public final class HashtagFetcher extends AsyncTask { HashtagModel result = null; try { - final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1").openConnection(); + final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1") + .openConnection(); conn.setUseCaches(true); conn.connect(); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_HASHTAG); + final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql") + .getJSONObject(Constants.EXTRAS_HASHTAG); final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media"); if (timelineMedia.has("edges")) { @@ -53,13 +61,34 @@ public final class HashtagFetcher extends AsyncTask { user.getString("profile_pic_url"), timelineMedia.getLong("count"), user.optBoolean("is_following")); + } else { + BufferedReader bufferedReader = null; + try { + final InputStream responseInputStream = conn.getErrorStream(); + bufferedReader = new BufferedReader(new InputStreamReader(responseInputStream)); + final StringBuilder builder = new StringBuilder(); + for (String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) { + if (builder.length() != 0) { + builder.append("\n"); + } + builder.append(line); + } + Log.d(TAG, "doInBackground: " + builder.toString()); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException ignored) { + } + } + } } conn.disconnect(); } catch (final Exception e) { if (logCollector != null) logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } return result; diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java index 0ac8ed24..09f49123 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java @@ -10,6 +10,8 @@ import org.json.JSONObject; import java.io.File; 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; @@ -28,18 +30,18 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Utils.logCollector; -public final class PostsFetcher extends AsyncTask { +public final class PostsFetcher extends AsyncTask> { private static final String TAG = "PostsFetcher"; private final PostItemType type; private final String endCursor; private final String id; - private final FetchListener fetchListener; + private final FetchListener> fetchListener; private String username = null; public PostsFetcher(final String id, final PostItemType type, final String endCursor, - final FetchListener fetchListener) { + final FetchListener> fetchListener) { this.id = id; this.type = type; this.endCursor = endCursor == null ? "" : endCursor; @@ -52,7 +54,7 @@ public final class PostsFetcher extends AsyncTask { } @Override - protected PostModel[] doInBackground(final Void... voids) { + protected List doInBackground(final Void... voids) { // final boolean isHashTag = id.charAt(0) == '#'; // final boolean isSaved = id.charAt(0) == '$'; // final boolean isTagged = id.charAt(0) == '%'; @@ -79,7 +81,7 @@ public final class PostsFetcher extends AsyncTask { default: url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor; } - PostModel[] result = null; + List result = new ArrayList<>(); try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setUseCaches(false); @@ -126,8 +128,7 @@ public final class PostsFetcher extends AsyncTask { } final JSONArray edges = mediaPosts.getJSONArray("edges"); - final PostModel[] models = new PostModel[edges.length()]; - for (int i = 0; i < models.length; ++i) { + for (int i = 0; i < edges.length(); ++i) { final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges"); @@ -139,34 +140,43 @@ public final class PostsFetcher extends AsyncTask { else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else itemType = MediaItemType.MEDIA_TYPE_IMAGE; - models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), - mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"), - mediaNode.getString(Constants.EXTRAS_SHORTCODE), - captions.length() > 0 ? captions.getJSONObject(0).getJSONObject("node").getString("text") : null, - mediaNode.getLong("taken_at_timestamp"), mediaNode.optBoolean("viewer_has_liked"), - mediaNode.optBoolean("viewer_has_saved"), mediaNode.getJSONObject("edge_liked_by").getLong("count")); - - DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); + final PostModel model = new PostModel( + itemType, + mediaNode.getString(Constants.EXTRAS_ID), + mediaNode.getString("display_url"), + mediaNode.getString("thumbnail_src"), + mediaNode.getString(Constants.EXTRAS_SHORTCODE), + captions.length() > 0 ? captions.getJSONObject(0) + .getJSONObject("node") + .getString("text") + : null, + mediaNode.getLong("taken_at_timestamp"), + mediaNode.optBoolean("viewer_has_liked"), + mediaNode.optBoolean("viewer_has_saved"), + mediaNode.getJSONObject("edge_liked_by") + .getLong("count") + ); + result.add(model); + DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model); } - if (models.length != 0 && models[models.length - 1] != null) - models[models.length - 1].setPageCursor(hasNextPage, endCursor); - - result = models; + if (!result.isEmpty() && result.get(result.size() - 1) != null) + result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor); } - conn.disconnect(); } catch (Exception e) { - if (logCollector != null) + if (logCollector != null) { logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error fetching posts", e); + } } - return result; } @Override - protected void onPostExecute(final PostModel[] postModels) { + protected void onPostExecute(final List postModels) { if (fetchListener != null) fetchListener.onResult(postModels); } } diff --git a/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java index 21b77ac8..c40d0406 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java @@ -42,7 +42,6 @@ public final class SuggestionsFetcher extends AsyncTask { - private final String endCursor; - private final FetchListener fetchListener; +public final class iLikedFetcher extends AsyncTask> { + private static final String TAG = "iLikedFetcher"; - public iLikedFetcher(final FetchListener fetchListener) { + private final String endCursor; + private final FetchListener> fetchListener; + + public iLikedFetcher(final FetchListener> fetchListener) { this.endCursor = ""; this.fetchListener = fetchListener; } - public iLikedFetcher(final String endCursor, final FetchListener fetchListener) { + public iLikedFetcher(final String endCursor, final FetchListener> fetchListener) { this.endCursor = endCursor == null ? "" : endCursor; this.fetchListener = fetchListener; } @Override - protected PostModel[] doInBackground(final Void... voids) { - final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id="+endCursor; + protected List doInBackground(final Void... voids) { + final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor; - PostModel[] result = null; + List result = new ArrayList<>(); try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setUseCaches(false); @@ -68,8 +72,7 @@ public final class iLikedFetcher extends AsyncTask { } final JSONArray edges = body.getJSONArray("items"); - final PostModel[] models = new PostModel[edges.length()]; - for (int i = 0; i < models.length; ++i) { + for (int i = 0; i < edges.length(); ++i) { final JSONObject mediaNode = edges.getJSONObject(i); final boolean isSlider = mediaNode.has("carousel_media_count"); @@ -80,48 +83,57 @@ public final class iLikedFetcher extends AsyncTask { else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else itemType = MediaItemType.MEDIA_TYPE_IMAGE; - models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), - isSlider - ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) - : ResponseBodyUtils.getHighQualityImage(mediaNode), - isSlider - ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) - : ResponseBodyUtils.getLowQualityImage(mediaNode), + final PostModel model = new PostModel( + itemType, + mediaNode.getString(Constants.EXTRAS_ID), + isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media") + .getJSONObject(0)) + : ResponseBodyUtils.getHighQualityImage(mediaNode), + isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media") + .getJSONObject(0)) + : ResponseBodyUtils.getLowQualityImage(mediaNode), mediaNode.getString("code"), mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"), - mediaNode.getLong("taken_at"), true, - mediaNode.optBoolean("has_viewer_saved"), mediaNode.getLong("like_count")); - + mediaNode.getLong("taken_at"), + true, + mediaNode.optBoolean("has_viewer_saved"), + mediaNode.getLong("like_count")); + result.add(model); String username = mediaNode.getJSONObject("user").getString("username"); final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); + (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : "")); File customDir = null; if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { final String customPath = Utils.settingsHelper.getString(FOLDER_PATH + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); + (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) + ? ("/" + username) + : "")); if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath); } - DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); + DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model); } - if (models[models.length - 1] != null) - models[models.length - 1].setPageCursor(hasNextPage, endCursor); - - result = models; + final int length = result.size(); + if (length >= 1 && result.get(length - 1) != null) { + result.get(length - 1).setPageCursor(hasNextPage, endCursor); + } } conn.disconnect(); } catch (Exception e) { - if (logCollector != null) + if (logCollector != null) { logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "", e); + } } return result; } @Override - protected void onPostExecute(final PostModel[] postModels) { + protected void onPostExecute(final List postModels) { if (fetchListener != null) fetchListener.onResult(postModels); } } diff --git a/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java b/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java deleted file mode 100755 index e5cb4a69..00000000 --- a/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java +++ /dev/null @@ -1,181 +0,0 @@ -package awais.instagrabber.dialogs; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; - -import java.util.ArrayList; - -import awais.instagrabber.R; -import awais.instagrabber.adapters.SimpleAdapter; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; -import awais.instagrabber.utils.DownloadUtils; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; - -import static awais.instagrabber.utils.Utils.settingsHelper; - -public final class QuickAccessDialog extends BottomSheetDialogFragment implements DialogInterface.OnShowListener, - View.OnClickListener, View.OnLongClickListener { - private boolean cookieChanged, isQuery; - private Activity activity; - private String userQuery, displayName; - private View btnFavorite, btnImportExport; - private SimpleAdapter favoritesAdapter; - private RecyclerView rvFavorites, rvQuickAccess; - - public QuickAccessDialog setQuery(final String userQuery, final String displayName) { - this.userQuery = userQuery; - this.displayName = displayName; - return this; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - final Dialog dialog = super.onCreateDialog(savedInstanceState); - - dialog.setOnShowListener(this); - - final Context context = getContext(); - activity = context instanceof Activity ? (Activity) context : getActivity(); - - final View contentView = View.inflate(activity, R.layout.dialog_quick_access, null); - - btnFavorite = contentView.findViewById(R.id.btnFavorite); - btnImportExport = contentView.findViewById(R.id.importExport); - - isQuery = !TextUtils.isEmpty(userQuery); - btnFavorite.setVisibility(isQuery ? View.VISIBLE : View.GONE); - Utils.setTooltipText(btnImportExport, R.string.import_export); - - favoritesAdapter = new SimpleAdapter<>(activity, Utils.dataBox.getAllFavorites(), this, this); - - btnFavorite.setOnClickListener(this); - btnImportExport.setOnClickListener(this); - - rvFavorites = contentView.findViewById(R.id.rvFavorites); - rvQuickAccess = contentView.findViewById(R.id.rvQuickAccess); - - final DividerItemDecoration itemDecoration = new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL); - rvFavorites.addItemDecoration(itemDecoration); - rvFavorites.setAdapter(favoritesAdapter); - - final String cookieStr = settingsHelper.getString(Constants.COOKIE); - if (!TextUtils.isEmpty(cookieStr) - || Utils.dataBox.getCookieCount() > 0 // fallback for export / import - ) { - rvQuickAccess.addItemDecoration(itemDecoration); - final ArrayList allCookies = Utils.dataBox.getAllCookies(); - if (!TextUtils.isEmpty(cookieStr) && allCookies != null) { - for (final DataBox.CookieModel cookie : allCookies) { - if (cookieStr.equals(cookie.getCookie())) { - cookie.setSelected(true); - break; - } - } - } - rvQuickAccess.setAdapter(new SimpleAdapter<>(activity, allCookies, this, this)); - } else { - ((View) rvQuickAccess.getParent()).setVisibility(View.GONE); - } - - dialog.setContentView(contentView); - return dialog; - } - - @Override - public void onClick(@NonNull final View v) { - final Object tag = v.getTag(); - if (v == btnFavorite) { - if (isQuery) { - Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis(), displayName)); - favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); - } - } else if (v == btnImportExport) { - if (ContextCompat.checkSelfPermission(activity, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_DENIED) - requestPermissions(DownloadUtils.PERMS, 6007); - else Utils.showImportExportDialog(v.getContext()); - - } else if (tag instanceof DataBox.FavoriteModel) { - // if (MainActivityBackup.scanHack != null) { - // MainActivityBackup.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery()); - // dismiss(); - // } - - } else if (tag instanceof DataBox.CookieModel) { - final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; - if (!cookieModel.isSelected()) { - settingsHelper.putString(Constants.COOKIE, cookieModel.getCookie()); - CookieUtils.setupCookies(cookieModel.getCookie()); - cookieChanged = true; - } - dismiss(); - } - } - - @Override - public boolean onLongClick(@NonNull final View v) { - final Object tag = v.getTag(); - - if (tag instanceof DataBox.FavoriteModel) { - final DataBox.FavoriteModel favoriteModel = (DataBox.FavoriteModel) tag; - - new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> { - Utils.dataBox.delFavorite(favoriteModel); - favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); - }) - .setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete, - favoriteModel.getQuery())).show(); - - } else if (tag instanceof DataBox.CookieModel) { - final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; - - if (cookieModel.isSelected()) - Toast.makeText(v.getContext(), R.string.quick_access_cannot_delete_curr, Toast.LENGTH_SHORT).show(); - else - new AlertDialog.Builder(activity) - .setMessage(getString(R.string.quick_access_confirm_delete, cookieModel.getUsername())) - .setPositiveButton(R.string.yes, (d, which) -> { - Utils.dataBox.delUserCookie(cookieModel); - rvQuickAccess.findViewWithTag(cookieModel).setVisibility(View.GONE); - }) - .setNegativeButton(R.string.no, null) - .show(); - } - - return true; - } - - @Override - public void onDismiss(@NonNull final DialogInterface dialog) { - super.onDismiss(dialog); - if (cookieChanged && activity != null) activity.recreate(); - } - - @Override - public void onShow(final DialogInterface dialog) { - if (settingsHelper.getBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG)) - new AlertDialog.Builder(activity) - .setMessage(R.string.quick_access_info_dialog) - .setPositiveButton(R.string.ok, null) - .setNeutralButton(R.string.dont_show_again, (d, which) -> - settingsHelper.putBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG, false)).show(); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java new file mode 100644 index 00000000..ccf66168 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java @@ -0,0 +1,215 @@ +package awais.instagrabber.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.FavoritesAdapter; +import awais.instagrabber.asyncs.LocationFetcher; +import awais.instagrabber.asyncs.ProfileFetcher; +import awais.instagrabber.databinding.FragmentFavoritesBinding; +import awais.instagrabber.utils.DataBox; +import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; +import awais.instagrabber.viewmodels.FavoritesViewModel; + +public class FavoritesFragment extends Fragment { + private static final String TAG = "FavoritesFragment"; + + private boolean shouldRefresh = true; + private FragmentFavoritesBinding binding; + private RecyclerView root; + private FavoritesViewModel favoritesViewModel; + private FavoritesAdapter adapter; + + @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 = FragmentFavoritesBinding.inflate(getLayoutInflater()); + root = binding.getRoot(); + binding.favoriteList.setLayoutManager(new LinearLayoutManager(getContext())); + return root; + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + if (!shouldRefresh) return; + init(); + shouldRefresh = false; + } + + @Override + public void onResume() { + super.onResume(); + if (favoritesViewModel == null || adapter == null) return; + // refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh + favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + final List allFavorites = Utils.dataBox.getAllFavorites(); + favoritesViewModel.getList().postValue(allFavorites); + fetchMissingInfo(allFavorites); + } + + private void init() { + favoritesViewModel = new ViewModelProvider(this).get(FavoritesViewModel.class); + adapter = new FavoritesAdapter(model -> { + // navigate + switch (model.getType()) { + case USER: { + final String username = model.getQuery(); + // Log.d(TAG, "username: " + username); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putString("username", "@" + username); + navController.navigate(R.id.action_global_profileFragment, bundle); + break; + } + case LOCATION: { + final String locationId = model.getQuery(); + // Log.d(TAG, "locationId: " + locationId); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putString("locationId", locationId); + navController.navigate(R.id.action_global_locationFragment, bundle); + break; + } + case HASHTAG: { + final String hashtag = model.getQuery(); + // Log.d(TAG, "hashtag: " + hashtag); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putString("hashtag", "#" + hashtag); + navController.navigate(R.id.action_global_hashTagFragment, bundle); + break; + } + default: + // do nothing + } + }, model -> { + // delete + final Context context = getContext(); + if (context == null) return false; + new MaterialAlertDialogBuilder(context) + .setMessage(getString(R.string.quick_access_confirm_delete, model.getQuery())) + .setPositiveButton(R.string.yes, (d, which) -> { + Utils.dataBox.deleteFavorite(model.getQuery(), model.getType()); + d.dismiss(); + favoritesViewModel.getList().postValue(Utils.dataBox.getAllFavorites()); + }) + .setNegativeButton(R.string.no, null) + .show(); + return true; + }); + binding.favoriteList.setAdapter(adapter); + // favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + + } + + private void fetchMissingInfo(final List allFavorites) { + final Runnable runnable = () -> { + final List updatedList = new ArrayList<>(allFavorites); + // cyclic barrier is to make the async calls synchronous + final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> { + // Log.d(TAG, "fetchMissingInfo: barrier action"); + favoritesViewModel.getList().postValue(new ArrayList<>(updatedList)); + }); + try { + for (final DataBox.FavoriteModel model : allFavorites) { + cyclicBarrier.reset(); + // if the model has missing pic or display name (for user and location), fetch those details + switch (model.getType()) { + case LOCATION: + if (TextUtils.isEmpty(model.getDisplayName()) + || TextUtils.isEmpty(model.getPicUrl())) { + new LocationFetcher(model.getQuery(), result -> { + try { + if (result == null) return; + final int i = updatedList.indexOf(model); + updatedList.remove(i); + final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( + model.getId(), + model.getQuery(), + model.getType(), + result.getName(), + result.getSdProfilePic(), + model.getDateAdded() + ); + Utils.dataBox.addFavorite(updated); + updatedList.add(i, updated); + } finally { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + } + }).execute(); + cyclicBarrier.await(); + } + break; + case USER: + if (TextUtils.isEmpty(model.getDisplayName()) + || TextUtils.isEmpty(model.getPicUrl())) { + new ProfileFetcher(model.getQuery(), result -> { + try { + if (result == null) return; + final int i = updatedList.indexOf(model); + updatedList.remove(i); + final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( + model.getId(), + model.getQuery(), + model.getType(), + result.getName(), + result.getSdProfilePic(), + model.getDateAdded() + ); + Utils.dataBox.addFavorite(updated); + updatedList.add(i, updated); + } finally { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + } + }).execute(); + cyclicBarrier.await(); + } + break; + case HASHTAG: + default: + // hashtags don't require displayName or pic + // updatedList.add(model); + } + } + } catch (Exception e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + favoritesViewModel.getList().postValue(updatedList); + }; + new Thread(runnable).start(); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 522e9e1d..50c497ce 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -1,7 +1,6 @@ package awais.instagrabber.fragments; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.Typeface; import android.os.AsyncTask; import android.os.Bundle; @@ -22,16 +21,18 @@ import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import awais.instagrabber.R; @@ -49,21 +50,24 @@ import awais.instagrabber.databinding.FragmentHashtagBinding; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.PostModel; -import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.PostsViewModel; +import awais.instagrabber.webservices.ServiceCallback; +import awais.instagrabber.webservices.TagsService; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; -public class HashTagFragment extends Fragment { +public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "HashTagFragment"; private MainActivity fragmentActivity; @@ -79,7 +83,8 @@ public class HashTagFragment extends Fragment { private String endCursor; private AsyncTask currentlyExecuting; private boolean isLoggedIn; - private StoryModel[] storyModels; + private TagsService tagsService; + private boolean isPullToRefresh; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -116,19 +121,27 @@ public class HashTagFragment extends Fragment { return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { binding.swipeRefreshLayout.setRefreshing(false); if (result == null) return; binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); final List postModels = postsViewModel.getList().getValue(); - final List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); - finalList.addAll(Arrays.asList(result)); + List finalList = postModels == null || postModels.isEmpty() + ? new ArrayList<>() + : new ArrayList<>(postModels); + if (isPullToRefresh) { + finalList = result; + isPullToRefresh = false; + } else { + finalList.addAll(result); + } + finalList.addAll(result); postsViewModel.getList().postValue(finalList); PostModel model = null; - if (result.length != 0) { - model = result[result.length - 1]; + if (!result.isEmpty()) { + model = result.get(result.size() - 1); } if (model == null) return; endCursor = model.getEndCursor(); @@ -141,6 +154,7 @@ public class HashTagFragment extends Fragment { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); + tagsService = TagsService.getInstance(); } @Nullable @@ -158,10 +172,18 @@ public class HashTagFragment extends Fragment { @Override public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { if (!shouldRefresh) return; + binding.swipeRefreshLayout.setOnRefreshListener(this); init(); shouldRefresh = false; } + @Override + public void onRefresh() { + isPullToRefresh = true; + endCursor = null; + fetchHashtagModel(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -257,7 +279,6 @@ public class HashTagFragment extends Fragment { private void fetchPosts() { stopCurrentExecutor(); - binding.btnFollowTag.setVisibility(View.VISIBLE); binding.swipeRefreshLayout.setRefreshing(true); if (TextUtils.isEmpty(hashtag)) return; currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener) @@ -266,29 +287,109 @@ public class HashTagFragment extends Fragment { if (context == null) return; if (isLoggedIn) { new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> { - storyModels = stories; if (stories != null && stories.length > 0) { binding.mainHashtagImage.setStoriesBorder(); } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - + binding.btnFollowTag.setVisibility(View.VISIBLE); binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow); - ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( - ContextCompat.getColor(context, hashtagModel.getFollowing() - ? R.color.btn_purple_background - : R.color.btn_pink_background))); + binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing() + ? R.drawable.ic_outline_person_add_disabled_24 + : R.drawable.ic_outline_person_add_24); + binding.btnFollowTag.setOnClickListener(v -> { + final String cookie = settingsHelper.getString(Constants.COOKIE); + final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + binding.btnFollowTag.setClickable(false); + if (!hashtagModel.getFollowing()) { + tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + binding.btnFollowTag.setClickable(true); + if (!result) { + Log.e(TAG, "onSuccess: result is false"); + return; + } + onRefresh(); + } + + @Override + public void onFailure(@NonNull final Throwable t) { + binding.btnFollowTag.setClickable(true); + Log.e(TAG, "onFailure: ", t); + final String message = t.getMessage(); + Snackbar.make(root, + message != null ? message + : getString(R.string.downloader_unknown_error), + BaseTransientBottomBar.LENGTH_LONG) + .show(); + } + }); + return; + } + tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + binding.btnFollowTag.setClickable(true); + if (!result) { + Log.e(TAG, "onSuccess: result is false"); + return; + } + onRefresh(); + } + + @Override + public void onFailure(@NonNull final Throwable t) { + binding.btnFollowTag.setClickable(true); + Log.e(TAG, "onFailure: ", t); + final String message = t.getMessage(); + Snackbar.make(root, + message != null ? message + : getString(R.string.downloader_unknown_error), + BaseTransientBottomBar.LENGTH_LONG) + .show(); + } + }); + }); } else { - binding.btnFollowTag.setText(Utils.dataBox.getFavorite(hashtag) != null - ? R.string.unfavorite_short - : R.string.favorite_short); - ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( - ContextCompat.getColor(context, Utils.dataBox.getFavorite(hashtag) != null - ? R.color.btn_purple_background - : R.color.btn_pink_background))); + binding.btnFollowTag.setVisibility(View.GONE); } + final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG); + final boolean isFav = favorite != null; + binding.favChip.setVisibility(View.VISIBLE); + binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24 + : R.drawable.ic_outline_star_plus_24); + binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites); + binding.favChip.setOnClickListener(v -> { + final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG); + final boolean isFavorite = fav != null; + final String message; + if (isFavorite) { + Utils.dataBox.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG); + binding.favChip.setText(R.string.add_to_favorites); + binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + message = getString(R.string.removed_from_favs); + } else { + Utils.dataBox.addFavorite(new DataBox.FavoriteModel( + -1, + hashtag.substring(1), + FavoriteType.HASHTAG, + hashtagModel.getName(), + null, + new Date() + )); + binding.favChip.setText(R.string.favorite_short); + binding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + message = getString(R.string.added_to_favs); + } + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + }); binding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); final String postCount = String.valueOf(hashtagModel.getPostCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, postCount)); + final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); binding.mainTagPostCount.setText(span); @@ -312,9 +413,7 @@ public class HashTagFragment extends Fragment { if (actionBar != null) { Log.d(TAG, "setting title: " + hashtag); final Handler handler = new Handler(); - handler.postDelayed(() -> { - actionBar.setTitle(hashtag); - }, 200); + handler.postDelayed(() -> actionBar.setTitle(hashtag), 200); } } diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 9a3c79db..cc9ca169 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -27,10 +27,14 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import awais.instagrabber.R; @@ -48,11 +52,12 @@ import awais.instagrabber.databinding.FragmentLocationBinding; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.PostModel; -import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -62,7 +67,7 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; -public class LocationFragment extends Fragment { +public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "LocationFragment"; private MainActivity fragmentActivity; @@ -78,7 +83,7 @@ public class LocationFragment extends Fragment { private String endCursor; private AsyncTask currentlyExecuting; private boolean isLoggedIn; - private StoryModel[] storyModels; + private boolean isPullToRefresh; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -119,20 +124,25 @@ public class LocationFragment extends Fragment { return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { binding.swipeRefreshLayout.setRefreshing(false); if (result == null) return; binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); final List postModels = postsViewModel.getList().getValue(); - final List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() - : new ArrayList<>(postModels); - finalList.addAll(Arrays.asList(result)); + List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() + : new ArrayList<>(postModels); + if (isPullToRefresh) { + finalList = result; + isPullToRefresh = false; + } else { + finalList.addAll(result); + } postsViewModel.getList().postValue(finalList); PostModel model = null; - if (result.length != 0) { - model = result[result.length - 1]; + if (!result.isEmpty()) { + model = result.get(result.size() - 1); } if (model == null) return; endCursor = model.getEndCursor(); @@ -164,10 +174,18 @@ public class LocationFragment extends Fragment { @Override public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { if (!shouldRefresh) return; + binding.swipeRefreshLayout.setOnRefreshListener(this); init(); shouldRefresh = false; } + @Override + public void onRefresh() { + isPullToRefresh = true; + endCursor = null; + fetchLocationModel(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -182,6 +200,8 @@ public class LocationFragment extends Fragment { isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments()); locationId = fragmentArgs.getLocationId(); + binding.favChip.setVisibility(View.GONE); + binding.btnMap.setVisibility(View.GONE); setTitle(); setupPosts(); fetchLocationModel(); @@ -275,7 +295,6 @@ public class LocationFragment extends Fragment { false, false, stories -> { - storyModels = stories; if (stories != null && stories.length > 0) { binding.mainLocationImage.setStoriesBorder(); } @@ -283,7 +302,7 @@ public class LocationFragment extends Fragment { } binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); final String postCount = String.valueOf(locationModel.getPostCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, + final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); @@ -329,6 +348,40 @@ public class LocationFragment extends Fragment { binding.locationUrl.setVisibility(View.VISIBLE); binding.locationUrl.setText(TextUtils.getSpannableUrl(url)); } + final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION); + final boolean isFav = favorite != null; + binding.favChip.setVisibility(View.VISIBLE); + binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24 + : R.drawable.ic_outline_star_plus_24); + binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites); + binding.favChip.setOnClickListener(v -> { + final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION); + final boolean isFavorite = fav != null; + final String message; + if (isFavorite) { + Utils.dataBox.deleteFavorite(locationId, FavoriteType.LOCATION); + binding.favChip.setText(R.string.add_to_favorites); + binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + message = getString(R.string.removed_from_favs); + } else { + Utils.dataBox.addFavorite(new DataBox.FavoriteModel( + -1, + locationId, + FavoriteType.LOCATION, + locationModel.getName(), + locationModel.getSdProfilePic(), + new Date() + )); + binding.favChip.setText(R.string.favorite_short); + binding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + message = getString(R.string.added_to_favs); + } + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + }); } private void fetchPosts() { diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java index 9858a9eb..efaeb320 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java @@ -27,7 +27,6 @@ import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -58,7 +57,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL private static AsyncTask currentlyExecuting; private PostsAdapter postsAdapter; private boolean hasNextPage; - private boolean autoloadPosts; private FragmentSavedBinding binding; private String username; private String endCursor; @@ -107,17 +105,16 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { final List current = postsViewModel.getList().getValue(); - if (result != null && result.length > 0) { - final List resultList = Arrays.asList(result); + if (result != null && !result.isEmpty()) { if (current == null) { - postsViewModel.getList().postValue(resultList); + postsViewModel.getList().postValue(result); } else { final List currentCopy = new ArrayList<>(current); - currentCopy.addAll(resultList); + currentCopy.addAll(result); postsViewModel.getList().postValue(currentCopy); } binding.mainPosts.post(() -> { @@ -125,11 +122,11 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL binding.mainPosts.setVisibility(View.VISIBLE); }); - final PostModel model = result.length > 0 ? result[result.length - 1] : null; + final PostModel model = !result.isEmpty() ? result.get(result.size() - 1) : null; if (model != null) { endCursor = model.getEndCursor(); hasNextPage = model.hasNextPage(); - if (autoloadPosts && hasNextPage) { + if (hasNextPage) { fetchPosts(); } else { binding.swipeRefreshLayout.setRefreshing(false); @@ -246,7 +243,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL binding.swipeRefreshLayout.setRefreshing(true); lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { - if (!autoloadPosts && hasNextPage) { + if (hasNextPage) { binding.swipeRefreshLayout.setRefreshing(true); fetchPosts(); endCursor = null; @@ -258,7 +255,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL private void fetchPosts() { stopCurrentExecutor(); - final AsyncTask asyncTask; + final AsyncTask> asyncTask; switch (type) { case LIKED: asyncTask = new iLikedFetcher(endCursor, postsFetchListener); diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 28c62a76..037e30ca 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -2,7 +2,6 @@ package awais.instagrabber.fragments.main; import android.content.Context; import android.content.DialogInterface; -import android.content.res.ColorStateList; import android.graphics.Typeface; import android.os.AsyncTask; import android.os.Bundle; @@ -30,8 +29,6 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -42,9 +39,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; + import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import awais.instagrabber.ProfileNavGraphDirections; @@ -71,6 +71,7 @@ import awais.instagrabber.models.PostModel; import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.StoryViewerChoice; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; @@ -111,11 +112,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private StoryModel[] storyModels; private boolean hasNextPage; private String endCursor; - private AsyncTask currentlyExecuting; - private MenuItem favMenuItem; + private AsyncTask> currentlyExecuting; private boolean isPullToRefresh; private HighlightsAdapter highlightsAdapter; private HighlightsViewModel highlightsViewModel; + private MenuItem blockMenuItem; + private MenuItem restrictMenuItem; private final Runnable usernameSettingRunnable = () -> { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); @@ -161,11 +163,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { binding.swipeRefreshLayout.setRefreshing(false); - if (result == null || result.length <= 0) { + if (result == null || result.isEmpty()) { binding.privatePage1.setImageResource(R.drawable.ic_cancel); binding.privatePage2.setText(R.string.empty_acc); binding.privatePage.setVisibility(View.VISIBLE); @@ -175,15 +177,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe final List postModels = postsViewModel.getList().getValue(); List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); - final List resultList = Arrays.asList(result); if (isPullToRefresh) { - finalList = resultList; + finalList = result; isPullToRefresh = false; } else { - finalList.addAll(resultList); + finalList.addAll(result); } postsViewModel.getList().postValue(finalList); - final PostModel lastPostModel = result[result.length - 1]; + final PostModel lastPostModel = result.get(result.size() - 1); if (lastPostModel == null) return; endCursor = lastPostModel.getEndCursor(); hasNextPage = lastPostModel.hasNextPage(); @@ -262,7 +263,81 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { inflater.inflate(R.menu.profile_menu, menu); - favMenuItem = menu.findItem(R.id.favourites); + // favMenuItem = menu.findItem(R.id.favourites); + blockMenuItem = menu.findItem(R.id.block); + if (blockMenuItem != null) { + blockMenuItem.setVisible(false); + } + restrictMenuItem = menu.findItem(R.id.restrict); + if (restrictMenuItem != null) { + restrictMenuItem.setVisible(false); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + if (item.getItemId() == R.id.restrict) { + if (!isLoggedIn) return false; + final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; + friendshipService.toggleRestrict( + profileModel.getId(), + !profileModel.getRestricted(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoRestrictRootResponse result) { + Log.d(TAG, action + " success: " + result); + fetchProfileDetails(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error while performing " + action, t); + } + }); + return true; + } + if (item.getItemId() == R.id.block) { + final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); + if (!isLoggedIn) return false; + if (profileModel.getBlocked()) { + friendshipService.unblock( + userIdFromCookie, + profileModel.getId(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoChangeRootResponse result) { + Log.d(TAG, "Unblock success: " + result); + fetchProfileDetails(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error unblocking", t); + } + }); + return true; + } + friendshipService.block( + userIdFromCookie, + profileModel.getId(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoChangeRootResponse result) { + Log.d(TAG, "Block success: " + result); + fetchProfileDetails(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error blocking", t); + } + }); + return true; + } + return super.onOptionsItemSelected(item); } @Override @@ -340,14 +415,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe new ProfileFetcher(username.substring(1), profileModel -> { if (getContext() == null) return; this.profileModel = profileModel; - final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); - final boolean isSelf = isLoggedIn - && profileModel != null - && userIdFromCookie != null - && userIdFromCookie.equals(profileModel.getId()); - if (favMenuItem != null) { - favMenuItem.setVisible(isSelf); - } + // final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); + // final boolean isSelf = isLoggedIn + // && profileModel != null + // && userIdFromCookie != null + // && userIdFromCookie.equals(profileModel.getId()); + // if (favMenuItem != null) { + // favMenuItem.setVisible(isSelf); + // } setProfileDetails(); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -385,7 +460,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } else binding.highlightsList.setVisibility(View.GONE); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.ALOINSTAGRAM.getValue())) { - Log.d("austin_debug", "alo triggered"); + // Log.d(TAG, "alo triggered"); aloService.getUserStory(profileId, profileModel.getUsername(), false, new ServiceCallback>() { @Override public void onSuccess(final List result) { @@ -402,15 +477,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe }); } + final String myId = CookieUtils.getUserIdFromCookie(cookie); if (isLoggedIn) { - final String myId = CookieUtils.getUserIdFromCookie(cookie); if (profileId.equals(myId)) { binding.btnTagged.setVisibility(View.VISIBLE); binding.btnSaved.setVisibility(View.VISIBLE); binding.btnLiked.setVisibility(View.VISIBLE); binding.btnSaved.setText(R.string.saved); - ViewCompat.setBackgroundTintList(binding.btnSaved, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background))); } else { binding.btnTagged.setVisibility(View.GONE); binding.btnSaved.setVisibility(View.GONE); @@ -418,58 +491,50 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe binding.btnFollow.setVisibility(View.VISIBLE); if (profileModel.getFollowing()) { binding.btnFollow.setText(R.string.unfollow); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); + binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); } else if (profileModel.getRequested()) { binding.btnFollow.setText(R.string.cancel); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); + binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); } else { binding.btnFollow.setText(R.string.follow); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background))); + binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24); } - binding.btnRestrict.setVisibility(View.VISIBLE); - if (profileModel.getRestricted()) { - binding.btnRestrict.setText(R.string.unrestrict); - ViewCompat.setBackgroundTintList(binding.btnRestrict, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background))); - } else { - binding.btnRestrict.setText(R.string.restrict); - ViewCompat.setBackgroundTintList(binding.btnRestrict, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background))); + if (restrictMenuItem != null) { + restrictMenuItem.setVisible(true); + if (profileModel.getRestricted()) { + restrictMenuItem.setTitle(R.string.unrestrict); + } else { + restrictMenuItem.setTitle(R.string.restrict); + } } - binding.btnBlock.setVisibility(View.VISIBLE); binding.btnTagged.setVisibility(View.VISIBLE); - if (profileModel.getBlocked()) { - binding.btnBlock.setText(R.string.unblock); - ViewCompat.setBackgroundTintList(binding.btnBlock, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background))); - } else { - binding.btnBlock.setText(R.string.block); - ViewCompat.setBackgroundTintList(binding.btnBlock, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_red_background))); + if (blockMenuItem != null) { + blockMenuItem.setVisible(true); + if (profileModel.getBlocked()) { + blockMenuItem.setTitle(R.string.unblock); + } else { + blockMenuItem.setTitle(R.string.block); + } } } } else { - if (Utils.dataBox.getFavorite(username) != null) { - binding.btnFollow.setText(R.string.unfavorite_short); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); - } else { - binding.btnFollow.setText(R.string.favorite_short); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background))); - } - binding.btnFollow.setVisibility(View.VISIBLE); - if (!profileModel.isReallyPrivate()) { - binding.btnRestrict.setVisibility(View.VISIBLE); - binding.btnRestrict.setText(R.string.tagged); - ViewCompat.setBackgroundTintList(binding.btnRestrict, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_blue_background))); + if (!profileModel.isReallyPrivate() && restrictMenuItem != null) { + restrictMenuItem.setVisible(true); + if (profileModel.getRestricted()) { + restrictMenuItem.setTitle(R.string.unrestrict); + } else { + restrictMenuItem.setTitle(R.string.restrict); + } } } - + if (!profileId.equals(myId)) { + binding.favCb.setVisibility(View.VISIBLE); + final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null; + binding.favCb.setChecked(isFav); + binding.favCb.setButtonDrawable(isFav ? R.drawable.ic_star_check_24 : R.drawable.ic_outline_star_plus_24); + } else { + binding.favCb.setVisibility(View.GONE); + } binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic()); final long followersCount = profileModel.getFollowersCount(); @@ -570,23 +635,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); // final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie // .equals(profileModel.getId()); - final String favorite = Utils.dataBox.getFavorite(username); binding.btnFollow.setOnClickListener(v -> { - if (!isLoggedIn) { - if (favorite != null && v == binding.btnFollow) { - Utils.dataBox.delFavorite(new DataBox.FavoriteModel( - username, - Long.parseLong(favorite.split("/")[1]), - username.replaceAll("^@", ""))); - } else if (v == binding.btnFollow) { - Utils.dataBox.addFavorite(new DataBox.FavoriteModel( - username, - System.currentTimeMillis(), - username.replaceAll("^@", ""))); - } - fetchProfileDetails(); - return; - } if (profileModel.getFollowing() || profileModel.getRequested()) { friendshipService.unfollow( userIdFromCookie, @@ -623,64 +672,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe }); } }); - binding.btnRestrict.setOnClickListener(v -> { - if (!isLoggedIn) return; - final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; - friendshipService.toggleRestrict( - profileModel.getId(), - !profileModel.getRestricted(), - CookieUtils.getCsrfTokenFromCookie(cookie), - new ServiceCallback() { - @Override - public void onSuccess(final FriendshipRepoRestrictRootResponse result) { - Log.d(TAG, action + " success: " + result); - fetchProfileDetails(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error while performing " + action, t); - } - }); - }); - binding.btnBlock.setOnClickListener(v -> { - if (!isLoggedIn) return; - if (profileModel.getBlocked()) { - friendshipService.unblock( - userIdFromCookie, - profileModel.getId(), - CookieUtils.getCsrfTokenFromCookie(cookie), - new ServiceCallback() { - @Override - public void onSuccess(final FriendshipRepoChangeRootResponse result) { - Log.d(TAG, "Unblock success: " + result); - fetchProfileDetails(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error unblocking", t); - } - }); - return; - } - friendshipService.block( - userIdFromCookie, - profileModel.getId(), - CookieUtils.getCsrfTokenFromCookie(cookie), - new ServiceCallback() { - @Override - public void onSuccess(final FriendshipRepoChangeRootResponse result) { - Log.d(TAG, "Block success: " + result); - fetchProfileDetails(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error blocking", t); - } - }); - }); binding.btnSaved.setOnClickListener(v -> { final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(), profileModel.getId(), @@ -728,6 +719,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe .setNegativeButton(R.string.cancel, null) .show(); }); + binding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> { + // do not do anything if state matches the db, as listener is set before profile details are set + final String finalUsername = username.startsWith("@") ? username.substring(1) : username; + final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(finalUsername, FavoriteType.USER); + if ((isChecked && favorite != null) || (!isChecked && favorite == null)) { + return; + } + buttonView.setVisibility(View.GONE); + binding.favProgress.setVisibility(View.VISIBLE); + final String message; + if (isChecked) { + final DataBox.FavoriteModel model = new DataBox.FavoriteModel( + -1, + finalUsername, + FavoriteType.USER, + profileModel.getName(), + profileModel.getSdProfilePic(), + new Date() + ); + Utils.dataBox.addFavorite(model); + binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); + message = getString(R.string.added_to_favs); + } else { + Utils.dataBox.deleteFavorite(finalUsername, FavoriteType.USER); + message = getString(R.string.removed_from_favs); + binding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); + } + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + binding.favProgress.setVisibility(View.GONE); + binding.favCb.setVisibility(View.VISIBLE); + }); } private void showProfilePicDialog() { 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 ed3b1915..eb8da552 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -107,6 +107,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment { return true; })); } + generalCategory.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment(); + 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/enums/FavoriteType.java b/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java new file mode 100644 index 00000000..cdf926a9 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java @@ -0,0 +1,7 @@ +package awais.instagrabber.models.enums; + +public enum FavoriteType { + USER, + HASHTAG, + LOCATION +} diff --git a/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java b/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java new file mode 100644 index 00000000..13cb4857 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java @@ -0,0 +1,19 @@ +package awais.instagrabber.repositories; + +import retrofit2.Call; +import retrofit2.http.Header; +import retrofit2.http.POST; +import retrofit2.http.Path; + +public interface TagsRepository { + + @POST("/web/tags/follow/{tag}/") + Call follow(@Header("User-Agent") String userAgent, + @Header("x-csrftoken") String csrfToken, + @Path("tag") String tag); + + @POST("/web/tags/unfollow/{tag}/") + Call unfollow(@Header("User-Agent") String userAgent, + @Header("x-csrftoken") String csrfToken, + @Path("tag") String tag); +} diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 59251a02..8719d711 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -81,4 +81,5 @@ public final class Constants { public static final String ACTION_SHOW_ACTIVITY = "show_activity"; public static final String PREF_DARK_THEME = "dark_theme"; public static final String PREF_LIGHT_THEME = "light_theme"; + public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png"; } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DataBox.java b/app/src/main/java/awais/instagrabber/utils/DataBox.java index d6bad63f..e0e8e016 100755 --- a/app/src/main/java/awais/instagrabber/utils/DataBox.java +++ b/app/src/main/java/awais/instagrabber/utils/DataBox.java @@ -6,15 +6,17 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.ObjectsCompat; import java.util.ArrayList; +import java.util.Date; +import java.util.List; import awais.instagrabber.BuildConfig; +import awais.instagrabber.models.enums.FavoriteType; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; @@ -24,12 +26,9 @@ public final class DataBox extends SQLiteOpenHelper { private static DataBox sInstance; - private final static int VERSION = 2; + private final static int VERSION = 3; private final static String TABLE_COOKIES = "cookies"; private final static String TABLE_FAVORITES = "favorites"; - private final static String KEY_DATE_ADDED = "date_added"; - private final static String KEY_QUERY_TEXT = "query_text"; - private final static String KEY_QUERY_DISPLAY = "query_display"; private final static String KEY_ID = "id"; private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; @@ -38,7 +37,12 @@ public final class DataBox extends SQLiteOpenHelper { private final static String KEY_FULL_NAME = "full_name"; private final static String KEY_PROFILE_PIC = "profile_pic"; - private final Context c; + private final static String FAV_COL_ID = "id"; + private final static String FAV_COL_QUERY = "query_text"; + private final static String FAV_COL_TYPE = "type"; + private final static String FAV_COL_DISPLAY_NAME = "display_name"; + private final static String FAV_COL_PIC_URL = "pic_url"; + private final static String FAV_COL_DATE_ADDED = "date_added"; public static synchronized DataBox getInstance(final Context context) { if (sInstance == null) sInstance = new DataBox(context.getApplicationContext()); @@ -47,7 +51,6 @@ public final class DataBox extends SQLiteOpenHelper { private DataBox(@Nullable final Context context) { super(context, "cookiebox.db", null, VERSION); - c = context; } @Override @@ -60,42 +63,128 @@ public final class DataBox extends SQLiteOpenHelper { + KEY_COOKIE + " TEXT," + KEY_FULL_NAME + " TEXT," + KEY_PROFILE_PIC + " TEXT)"); - db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)"); + // db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)"); + db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" + + FAV_COL_ID + " INTEGER PRIMARY KEY," + + FAV_COL_QUERY + " TEXT," + + FAV_COL_TYPE + " TEXT," + + FAV_COL_DISPLAY_NAME + " TEXT," + + FAV_COL_PIC_URL + " TEXT," + + FAV_COL_DATE_ADDED + " INTEGER)"); Log.i(TAG, "Tables created!"); } @Override public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion)); - if (oldVersion == 1) { - db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); + // switch without break, so that all migrations from a previous version to new are run + switch (oldVersion) { + case 1: + db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); + case 2: + final List oldFavorites = backupOldFavorites(db); + // recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions) + db.execSQL("DROP TABLE " + TABLE_FAVORITES); + db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" + + FAV_COL_ID + " INTEGER PRIMARY KEY," + + FAV_COL_QUERY + " TEXT," + + FAV_COL_TYPE + " TEXT," + + FAV_COL_DISPLAY_NAME + " TEXT," + + FAV_COL_PIC_URL + " TEXT," + + FAV_COL_DATE_ADDED + " INTEGER)"); + // add the old favorites back + for (final FavoriteModel oldFavorite : oldFavorites) { + addFavorite(db, oldFavorite); + } } Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion)); } - public final void addFavorite(@NonNull final FavoriteModel favoriteModel) { - final String query = favoriteModel.getQuery(); - final String display = favoriteModel.getDisplayName(); + @NonNull + private List backupOldFavorites(@NonNull final SQLiteDatabase db) { + // check if old favorites table had the column query_display + final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display"); + Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists); + final List oldModels = new ArrayList<>(); + final String sql = "SELECT " + + "query_text," + + "date_added" + + (queryDisplayExists ? ",query_display" : "") + + " FROM " + TABLE_FAVORITES; + try (final Cursor cursor = db.rawQuery(sql, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + try { + final String queryText = cursor.getString(cursor.getColumnIndex("query_text")); + FavoriteType type = null; + String query = null; + if (queryText.startsWith("@")) { + type = FavoriteType.USER; + query = queryText.substring(1); + } else if (queryText.contains("/")) { + type = FavoriteType.LOCATION; + query = queryText.substring(0, queryText.indexOf("/")); + } else if (queryText.startsWith("#")) { + type = FavoriteType.HASHTAG; + query = queryText.substring(1); + } + oldModels.add(new FavoriteModel( + -1, + query, + type, + queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) + : null, + null, + new Date(cursor.getLong(cursor.getColumnIndex("date_added"))) + )); + } catch (Exception e) { + Log.e(TAG, "onUpgrade", e); + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + Log.e(TAG, "onUpgrade", e); + } + Log.d(TAG, "backupOldFavorites: oldModels:" + oldModels); + return oldModels; + } + + public boolean checkColumnExists(@NonNull final SQLiteDatabase db, + @NonNull final String tableName, + @NonNull final String columnName) { + boolean exists = false; + try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + tableName + ")", null)) { + if (cursor.moveToFirst()) { + do { + final String currentColumn = cursor.getString(cursor.getColumnIndex("name")); + if (currentColumn.equals(columnName)) { + exists = true; + } + } while (cursor.moveToNext()); + + } + } catch (Exception ex) { + Log.e(TAG, "checkColumnExists", ex); + } + return exists; + } + + public final void addFavorite(@NonNull final FavoriteModel model) { + final String query = model.getQuery(); if (!TextUtils.isEmpty(query)) { try (final SQLiteDatabase db = getWritableDatabase()) { db.beginTransaction(); try { - final ContentValues values = new ContentValues(); - values.put(KEY_DATE_ADDED, favoriteModel.getDate()); - values.put(KEY_QUERY_TEXT, query); - values.put(KEY_QUERY_DISPLAY, display); - - final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{query}); - - if (rows != 1) - db.insertOrThrow(TABLE_FAVORITES, null, values); - + addFavorite(db, model); db.setTransactionSuccessful(); } catch (final Exception e) { - if (logCollector != null) + if (logCollector != null) { logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "", e); + } } finally { db.endTransaction(); } @@ -103,23 +192,40 @@ public final class DataBox extends SQLiteOpenHelper { } } - public final synchronized void delFavorite(@NonNull final FavoriteModel favoriteModel) { - final String query = favoriteModel.getQuery(); + private void addFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) { + final ContentValues values = new ContentValues(); + values.put(FAV_COL_QUERY, model.getQuery()); + values.put(FAV_COL_TYPE, model.getType().toString()); + values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName()); + values.put(FAV_COL_PIC_URL, model.getPicUrl()); + values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime()); + int rows = 0; + if (model.getId() >= 1) { + rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())}); + } + if (rows != 1) { + db.insertOrThrow(TABLE_FAVORITES, null, values); + } + } + + public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) { if (!TextUtils.isEmpty(query)) { try (final SQLiteDatabase db = getWritableDatabase()) { db.beginTransaction(); try { - final int rowsDeleted = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", - new String[]{query, Long.toString(favoriteModel.getDate())}); + final int rowsDeleted = db.delete(TABLE_FAVORITES, + FAV_COL_QUERY + "=?" + + " AND " + FAV_COL_TYPE + "=?", + new String[]{query, type.toString()}); - final int rowsDeletedTwo = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", - new String[]{query.replaceAll("@", ""), Long.toString(favoriteModel.getDate())}); - - if (rowsDeleted > 0 || rowsDeletedTwo > 0) db.setTransactionSuccessful(); + if (rowsDeleted > 0) db.setTransactionSuccessful(); } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite"); - if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); + if (logCollector != null) { + logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error", e); + } } finally { db.endTransaction(); } @@ -127,73 +233,74 @@ public final class DataBox extends SQLiteOpenHelper { } } - @Nullable - public final ArrayList getAllFavorites() { - ArrayList favorites = null; - FavoriteModel tempFav; + @NonNull + public final List getAllFavorites() { + final List favorites = new ArrayList<>(); final SQLiteDatabase db = getWritableDatabase(); - - try (final Cursor cursor = db.rawQuery("SELECT query_text, date_added, query_display FROM favorites ORDER BY date_added DESC", null)) { + try (final Cursor cursor = db.rawQuery("SELECT " + + FAV_COL_ID + "," + + FAV_COL_QUERY + "," + + FAV_COL_TYPE + "," + + FAV_COL_DISPLAY_NAME + "," + + FAV_COL_PIC_URL + "," + + FAV_COL_DATE_ADDED + + " FROM " + TABLE_FAVORITES, + null)) { if (cursor != null && cursor.moveToFirst()) { db.beginTransaction(); - favorites = new ArrayList<>(); + FavoriteModel tempFav; do { + FavoriteType type = null; + try { + type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); + } catch (IllegalArgumentException ignored) {} tempFav = new FavoriteModel( - (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor.getString(0).contains("/")) - ? cursor.getString(0) - : "@" + cursor.getString(0), // query text - cursor.getLong(1), // date added - cursor.getString(2) == null ? (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor - .getString(0).contains("/")) - ? cursor.getString(0) - : "@" + cursor.getString(0) : cursor.getString(2) // display + cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), + cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), + type, + cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), + new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) ); - if (cursor.getString(2) == null) { - try { - final ContentValues values = new ContentValues(); - values.put(KEY_DATE_ADDED, tempFav.getDate()); - values.put(KEY_QUERY_TEXT, tempFav.getQuery()); - values.put(KEY_QUERY_DISPLAY, tempFav.getDisplayName()); - - final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{tempFav.getQuery()}); - - if (rows != 1) - db.insertOrThrow(TABLE_FAVORITES, null, values); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - } favorites.add(tempFav); } while (cursor.moveToNext()); db.endTransaction(); } - } catch (final Exception x) { - Log.e("austin_debug", "", x); - try { - db.execSQL("ALTER TABLE favorites ADD query_display TEXT"); - Toast.makeText(c, "DB has migrated, launch quick access again.", Toast.LENGTH_SHORT).show(); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "migrate"); - Toast.makeText(c, "DB migration failed, contact maintainer.", Toast.LENGTH_SHORT).show(); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); - } + } catch (final Exception e) { + Log.e(TAG, "", e); } - return favorites; } - public final String getFavorite(@NonNull final String query) { + @Nullable + public final FavoriteModel getFavorite(@NonNull final String query, @NonNull final FavoriteType type) { try (final SQLiteDatabase db = getReadableDatabase(); - final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE " - + KEY_QUERY_TEXT + "='" + query + "' ORDER BY date_added DESC", null)) { + final Cursor cursor = db.rawQuery("SELECT " + + FAV_COL_ID + "," + + FAV_COL_QUERY + "," + + FAV_COL_TYPE + "," + + FAV_COL_DISPLAY_NAME + "," + + FAV_COL_PIC_URL + "," + + FAV_COL_DATE_ADDED + + " FROM " + TABLE_FAVORITES + + " WHERE " + FAV_COL_QUERY + "='" + query + "'" + + " AND " + FAV_COL_TYPE + "='" + type.toString() + "'", + null)) { if (cursor != null && cursor.moveToFirst()) { - return cursor.getString(0) + "/" + String.valueOf(cursor.getLong(1)); + FavoriteType favoriteType = null; + try { + favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); + } catch (IllegalArgumentException ignored) {} + return new FavoriteModel( + cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), + cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), + favoriteType, + cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), + new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) + ); } } - return null; } @@ -399,31 +506,80 @@ public final class DataBox extends SQLiteOpenHelper { } public static class FavoriteModel { - private final String query, displayName; - private final long date; + private final int id; + private final String query; + private final FavoriteType type; + private final String displayName; + private final String picUrl; + private final Date dateAdded; - public FavoriteModel(final String query, final long date, final String displayName) { + public FavoriteModel(final int id, + final String query, + final FavoriteType type, + final String displayName, + final String picUrl, + final Date dateAdded) { + this.id = id; this.query = query; - this.date = date; + this.type = type; this.displayName = displayName; + this.picUrl = picUrl; + this.dateAdded = dateAdded; + } + + public int getId() { + return id; } public String getQuery() { return query; } + public FavoriteType getType() { + return type; + } + public String getDisplayName() { return displayName; } - public long getDate() { - return date; + public String getPicUrl() { + return picUrl; + } + + public Date getDateAdded() { + return dateAdded; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final FavoriteModel that = (FavoriteModel) o; + return id == that.id && + ObjectsCompat.equals(query, that.query) && + type == that.type && + ObjectsCompat.equals(displayName, that.displayName) && + ObjectsCompat.equals(picUrl, that.picUrl) && + ObjectsCompat.equals(dateAdded, that.dateAdded); + } + + @Override + public int hashCode() { + return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded); } @NonNull @Override public String toString() { - return query; + return "FavoriteModel{" + + "id=" + id + + ", query='" + query + '\'' + + ", type=" + type + + ", displayName='" + displayName + '\'' + + ", picUrl='" + picUrl + '\'' + + ", dateAdded=" + dateAdded + + '}'; } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index 750e3b80..33654fb4 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -35,6 +36,8 @@ import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; public final class ExportImportUtils { + private static final String TAG = "ExportImportUtils"; + public static final int FLAG_COOKIES = 1; public static final int FLAG_FAVORITES = 1 << 1; public static final int FLAG_SETTINGS = 1 << 2; @@ -60,7 +63,7 @@ public final class ExportImportUtils { if (fetchListener != null) fetchListener.onResult(false); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } else { exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING); @@ -75,7 +78,7 @@ public final class ExportImportUtils { if (fetchListener != null) fetchListener.onResult(false); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } else if (fetchListener != null) fetchListener.onResult(false); } @@ -111,7 +114,7 @@ public final class ExportImportUtils { if (fetchListener != null) fetchListener.onResult(false); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } else @@ -129,7 +132,7 @@ public final class ExportImportUtils { } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } @@ -172,9 +175,9 @@ public final class ExportImportUtils { final int favsLen = favs.length(); for (int i = 0; i < favsLen; ++i) { final JSONObject favsObject = favs.getJSONObject(i); - Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"), - favsObject.getLong("d"), - favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q"))); + // Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"), + // favsObject.getLong("d"), + // favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q"))); } } @@ -183,7 +186,7 @@ public final class ExportImportUtils { } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "saveToSettings"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } @@ -212,7 +215,7 @@ public final class ExportImportUtils { result = jsonObject.toString(); } catch (final Exception e) { if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } return result; } @@ -249,7 +252,7 @@ public final class ExportImportUtils { } catch (final Exception e) { result = null; if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getSettings"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } @@ -261,15 +264,15 @@ public final class ExportImportUtils { String result = null; if (Utils.dataBox != null) { try { - final ArrayList allFavorites = Utils.dataBox.getAllFavorites(); + final List allFavorites = Utils.dataBox.getAllFavorites(); final int allFavoritesSize; - if (allFavorites != null && (allFavoritesSize = allFavorites.size()) > 0) { + if ((allFavoritesSize = allFavorites.size()) > 0) { final JSONArray jsonArray = new JSONArray(); for (int i = 0; i < allFavoritesSize; i++) { final DataBox.FavoriteModel favorite = allFavorites.get(i); final JSONObject jsonObject = new JSONObject(); jsonObject.put("q", favorite.getQuery()); - jsonObject.put("d", favorite.getDate()); + jsonObject.put("d", favorite.getDateAdded().getTime()); jsonObject.put("s", favorite.getDisplayName()); jsonArray.put(jsonObject); } @@ -278,7 +281,7 @@ public final class ExportImportUtils { } catch (final Exception e) { result = null; if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } return result; @@ -305,7 +308,7 @@ public final class ExportImportUtils { } } catch (final Exception e) { result = null; - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } return result; diff --git a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java index 87c225a1..f7f58bc3 100644 --- a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java +++ b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java @@ -57,7 +57,8 @@ public class NavigationExtensions { return false; } String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); - if (!selectedItemTag[0].equals(newlySelectedItemTag)) { + String tag = selectedItemTag[0]; + if (tag != null && !tag.equals(newlySelectedItemTag)) { fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE); Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag); if (fragment == null) { diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 1fed5097..f665a041 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -6,7 +6,6 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.net.Uri; import android.os.Build; @@ -21,7 +20,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; import androidx.fragment.app.FragmentManager; import com.google.android.exoplayer2.database.ExoDatabaseProvider; diff --git a/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java new file mode 100644 index 00000000..e30d4116 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java @@ -0,0 +1,19 @@ +package awais.instagrabber.viewmodels; + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import java.util.List; + +import awais.instagrabber.utils.DataBox; + +public class FavoritesViewModel extends ViewModel { + private MutableLiveData> list; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} diff --git a/app/src/main/java/awais/instagrabber/webservices/TagsService.java b/app/src/main/java/awais/instagrabber/webservices/TagsService.java new file mode 100644 index 00000000..2834a570 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/TagsService.java @@ -0,0 +1,101 @@ +package awais.instagrabber.webservices; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import org.json.JSONException; +import org.json.JSONObject; + +import awais.instagrabber.repositories.TagsRepository; +import awais.instagrabber.utils.Constants; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class TagsService extends BaseService { + + private static final String TAG = "TagsService"; + + // web for www.instagram.com + private final TagsRepository webRepository; + + private static TagsService instance; + + private TagsService() { + final Retrofit webRetrofit = getRetrofitBuilder() + .baseUrl("https://www.instagram.com/") + .build(); + webRepository = webRetrofit.create(TagsRepository.class); + } + + public static TagsService getInstance() { + if (instance == null) { + instance = new TagsService(); + } + return instance; + } + + public void follow(@NonNull final String tag, + @NonNull final String csrfToken, + final ServiceCallback callback) { + final Call request = webRepository.follow(Constants.USER_AGENT, + csrfToken, + tag); + 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.onFailure(new RuntimeException("body is null")); + 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, "onResponse: ", e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + // Log.e(TAG, "onFailure: ", t); + callback.onFailure(t); + } + }); + } + + public void unfollow(@NonNull final String tag, + @NonNull final String csrfToken, + final ServiceCallback callback) { + final Call request = webRepository.unfollow(Constants.USER_AGENT, + csrfToken, + tag); + 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.onFailure(new RuntimeException("body is null")); + 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, "onResponse: ", e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + // Log.e(TAG, "onFailure: ", t); + callback.onFailure(t); + } + }); + } +} diff --git a/app/src/main/res/drawable/ic_block_24.xml b/app/src/main/res/drawable/ic_block_24.xml new file mode 100644 index 00000000..9fefeec6 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_highlight_off_24.xml b/app/src/main/res/drawable/ic_highlight_off_24.xml new file mode 100644 index 00000000..6a21d0d7 --- /dev/null +++ b/app/src/main/res/drawable/ic_highlight_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_class_24.xml b/app/src/main/res/drawable/ic_outline_class_24.xml new file mode 100644 index 00000000..bace1783 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_class_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_map_24.xml b/app/src/main/res/drawable/ic_outline_map_24.xml new file mode 100644 index 00000000..d0769b3e --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_map_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_person_add_24.xml b/app/src/main/res/drawable/ic_outline_person_add_24.xml new file mode 100644 index 00000000..a2a0572a --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_person_add_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml b/app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml new file mode 100644 index 00000000..e230319e --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_person_pin_24.xml b/app/src/main/res/drawable/ic_outline_person_pin_24.xml new file mode 100644 index 00000000..13963ebf --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_person_pin_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_star_24.xml b/app/src/main/res/drawable/ic_outline_star_24.xml new file mode 100644 index 00000000..b6d93cac --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_star_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_star_plus_24.xml b/app/src/main/res/drawable/ic_outline_star_plus_24.xml new file mode 100644 index 00000000..2977b0e5 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_star_plus_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_star_check_24.xml b/app/src/main/res/drawable/ic_star_check_24.xml new file mode 100644 index 00000000..b413c895 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_check_24.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml b/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml index a0706c60..67e019e3 100644 --- a/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml +++ b/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml @@ -1,6 +1,6 @@ - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 47477544..ad84f7a4 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -28,7 +28,6 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="none" - app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface" app:title="@string/app_name" tools:menu="@menu/main_menu" /> diff --git a/app/src/main/res/layout/dialog_profilepic.xml b/app/src/main/res/layout/dialog_profilepic.xml index 0b5a4000..38db7201 100644 --- a/app/src/main/res/layout/dialog_profilepic.xml +++ b/app/src/main/res/layout/dialog_profilepic.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/semi_transparent_black"> + android:background="@color/black_a50"> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_hashtag.xml b/app/src/main/res/layout/fragment_hashtag.xml index 311ec733..262a2f22 100644 --- a/app/src/main/res/layout/fragment_hashtag.xml +++ b/app/src/main/res/layout/fragment_hashtag.xml @@ -17,47 +17,120 @@ android:layout_height="wrap_content" app:layout_scrollFlags="scroll"> - + android:padding="@dimen/profile_info_container_bottom_space"> + android:background="?selectableItemBackgroundBorderless" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/mainTagPostCount" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:background="@mipmap/ic_launcher" /> + app:layout_constraintBottom_toTopOf="@id/btnFollowTag" + app:layout_constraintStart_toEndOf="@id/mainHashtagImage" + app:layout_constraintTop_toTopOf="@id/mainHashtagImage" + tools:text="35 Posts" /> - - + app:chipBackgroundColor="@null" + app:chipIcon="@drawable/ic_outline_person_add_24" + app:chipIconTint="@color/deep_purple_800" + app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage" + app:layout_constraintStart_toEndOf="@id/mainHashtagImage" + app:layout_constraintTop_toBottomOf="@id/mainTagPostCount" + app:rippleColor="@color/purple_200" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_location.xml b/app/src/main/res/layout/fragment_location.xml index 9ea5a610..8ae9ef80 100644 --- a/app/src/main/res/layout/fragment_location.xml +++ b/app/src/main/res/layout/fragment_location.xml @@ -17,101 +17,128 @@ android:layout_height="wrap_content" app:layout_scrollFlags="scroll|exitUntilCollapsed"> - + android:padding="8dp"> - + + + + + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:text="@string/map" + app:chipBackgroundColor="@null" + app:chipIcon="@drawable/ic_outline_map_24" + app:chipIconTint="@color/green_500" + app:layout_constraintBottom_toTopOf="@id/locationFullName" + app:layout_constraintStart_toEndOf="@id/mainLocationImage" + app:layout_constraintTop_toBottomOf="@id/mainLocPostCount" + app:rippleColor="@color/grey_500" + tools:visibility="visible" /> - - - - - - + + app:layout_constraintBottom_toTopOf="@id/locationUrl" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/locationFullName" + tools:text="IN THE MIDDLE OF OUR STREET" + tools:visibility="visible" /> - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/locationBiography" + tools:text="https://austinhuang.me/" + tools:visibility="visible" /> + diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 2552fd7f..1528e9ac 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -79,8 +79,10 @@ android:ellipsize="marquee" android:paddingStart="8dp" android:paddingLeft="8dp" + android:paddingTop="8dp" android:paddingEnd="4dp" android:paddingRight="4dp" + android:paddingBottom="8dp" android:singleLine="true" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textStyle="bold" @@ -102,15 +104,34 @@ app:srcCompat="@drawable/verified" tools:visibility="visible" /> + + + + + tools:visibility="gone" /> - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_slider.xml b/app/src/main/res/layout/item_feed_slider.xml index a6b31978..2267d089 100755 --- a/app/src/main/res/layout/item_feed_slider.xml +++ b/app/src/main/res/layout/item_feed_slider.xml @@ -18,7 +18,7 @@ android:id="@+id/media_list" android:layout_width="match_parent" android:layout_height="wrap_content" - tools:background="@color/semi_transparent_black" /> + tools:background="@color/black_a50" /> @@ -36,7 +36,6 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:textAppearance="@style/TextAppearance.AppCompat.Medium" - android:textColor="@color/feed_text_primary_color" tools:text="username" /> diff --git a/app/src/main/res/layout/item_suggestion.xml b/app/src/main/res/layout/item_suggestion.xml index 55425bb1..df114c34 100755 --- a/app/src/main/res/layout/item_suggestion.xml +++ b/app/src/main/res/layout/item_suggestion.xml @@ -7,7 +7,10 @@ android:background="?selectableItemBackground" android:clickable="true" android:focusable="true" - android:padding="8dp"> + android:paddingLeft="16dp" + android:paddingTop="8dp" + android:paddingRight="16dp" + android:paddingBottom="8dp"> - + + + + + + + + + + \ 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 242d25a7..c74b9611 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -34,6 +34,24 @@ app:nullable="true" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml index 1c18c8a9..b96ad8e7 100755 --- a/app/src/main/res/values/color.xml +++ b/app/src/main/res/values/color.xml @@ -28,15 +28,13 @@ #FFBB00 #FF000000 - @color/text_color_light - #efefef - #80000000 #FFFFFF #000000 #121212 + #80000000 #FAFAFA #F5F5F5 @@ -83,7 +81,65 @@ #bb86fc #4b01d0 - #cf6679 + #EDE7F6 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + #B388FF + #7C4DFF + #651FFF + #6200EA + + #FFEBEE + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + #FF8A80 + #FF5252 + #FF1744 + #D50000 + + #FBE9E7 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + #FF9E80 + #FF6E40 + #FF3D00 + #DD2C00 + + #FFFDE7 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + #FFFF8D + #FFFF00 + #FFEA00 + #FFD600 #a86735 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79a12a50..29ec26d2 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,6 +58,7 @@ Language What to do? %s\nPosts + %s Posts %s\nFollowers %s\nFollowing Video post @@ -146,7 +147,7 @@ Swap Time and Date positions Favorites panel is for adding your favorite hashtags and/or usernames.\n\nAnd the Quick Access panel is for quickly switching between accounts.\n\nNote 1: Make sure to Login into each account [Settings > Login] to add account to the list!\n\nNote 2: Log out of the current account and then log into the other account. Cannot delete currently in use account - Are you sure you want to delete %s? + Are you sure you want to delete \'%s\'? Width: %d\nHeight: %d \nColor depth: Select profile picture endpoint\n(Does not affect hashtags) @@ -283,4 +284,11 @@ Dark theme Barinsta Material Dark + Added to Favorites + Add to favorites + Accounts + Hashtags + Locations + Unknown + Removed from Favourites \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 459a3d33..d982645e 100755 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -108,4 +108,24 @@ ?colorSurface ?attr/colorSurface + + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index dad9accf..50650150 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -19,6 +19,7 @@ false @style/ThemeOverlay.MaterialComponents.ActionBar + @style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.Light + + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index c7b949a5..e1c4087a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -38,6 +38,7 @@ @color/white @style/Widget.BottomNavigationView.Light.White @style/Widget.MaterialComponents.Button.Light.White + @style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Light.White