diff --git a/app/build.gradle b/app/build.gradle index 74bbf1dc..e77a6ace 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -116,5 +116,6 @@ dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' } diff --git a/app/src/main/java/awais/instagrabber/adapters/KeywordsFilterAdapter.java b/app/src/main/java/awais/instagrabber/adapters/KeywordsFilterAdapter.java new file mode 100644 index 00000000..d3b0cc70 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/KeywordsFilterAdapter.java @@ -0,0 +1,42 @@ +package awais.instagrabber.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.viewholder.dialogs.KeywordsFilterDialogViewHolder; + +public class KeywordsFilterAdapter extends RecyclerView.Adapter { + + private final Context context; + private final ArrayList items; + + public KeywordsFilterAdapter(Context context, ArrayList items){ + this.context = context; + this.items = items; + } + + @NonNull + @Override + public KeywordsFilterDialogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_keyword, parent, false); + return new KeywordsFilterDialogViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull KeywordsFilterDialogViewHolder holder, int position) { + holder.bind(items, position, context, this); + } + + @Override + public int getItemCount() { + return items.size(); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/dialogs/KeywordsFilterDialogViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/dialogs/KeywordsFilterDialogViewHolder.java new file mode 100644 index 00000000..65fe7b77 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/dialogs/KeywordsFilterDialogViewHolder.java @@ -0,0 +1,51 @@ +package awais.instagrabber.adapters.viewholder.dialogs; + +import android.content.Context; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.HashSet; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.KeywordsFilterAdapter; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.SettingsHelper; + +public class KeywordsFilterDialogViewHolder extends RecyclerView.ViewHolder { + + private final Button deleteButton; + private final TextView item; + + public KeywordsFilterDialogViewHolder(@NonNull View itemView) { + super(itemView); + deleteButton = itemView.findViewById(R.id.keyword_delete); + item = itemView.findViewById(R.id.keyword_text); + } + + public void bind(ArrayList items, int position, Context context, KeywordsFilterAdapter adapter){ + item.setText(items.get(position)); + deleteButton.setOnClickListener(view -> { + final String s = items.get(position); + SettingsHelper settingsHelper = new SettingsHelper(context); + items.remove(position); + settingsHelper.putStringSet(Constants.KEYWORD_FILTERS, new HashSet<>(items)); + adapter.notifyDataSetChanged(); + final String message = context.getString(R.string.removed_keywords, s); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + }); + } + + public Button getDeleteButton(){ + return deleteButton; + } + + public TextView getTextView(){ + return item; + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java b/app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java index b07b7b45..4fe91772 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java +++ b/app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java @@ -40,7 +40,10 @@ public class FeedPostFetchService implements PostFetcher.PostFetchService { } else if (result == null) return; nextCursor = result.getNextCursor(); hasNextPage = result.hasNextPage(); - feedModels.addAll(result.getFeedModels()); + + final List mediaResults = result.getFeedModels(); + feedModels.addAll(mediaResults); + if (fetchListener != null) { // if (feedModels.size() < 15 && hasNextPage) { // feedService.fetch(csrfToken, nextCursor, this); diff --git a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java index f7aac54e..cdab91c9 100644 --- a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java +++ b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java @@ -32,11 +32,15 @@ import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.KeywordsFilterUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.MediaViewModel; import awais.instagrabber.workers.DownloadWorker; +import static awais.instagrabber.utils.Utils.settingsHelper; + public class PostsRecyclerView extends RecyclerView { private static final String TAG = "PostsRecyclerView"; @@ -70,7 +74,13 @@ public class PostsRecyclerView extends RecyclerView { } final List models = mediaViewModel.getList().getValue(); final List modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); - modelsCopy.addAll(result); + if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)){ + final ArrayList items = new ArrayList<>(settingsHelper.getStringSet(Constants.KEYWORD_FILTERS)); + modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result)); + } + else { + modelsCopy.addAll(result); + } mediaViewModel.getList().postValue(modelsCopy); dispatchFetchStatus(); } diff --git a/app/src/main/java/awais/instagrabber/dialogs/KeywordsFilterDialog.java b/app/src/main/java/awais/instagrabber/dialogs/KeywordsFilterDialog.java new file mode 100644 index 00000000..2a4d8a04 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/dialogs/KeywordsFilterDialog.java @@ -0,0 +1,78 @@ +package awais.instagrabber.dialogs; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.HashSet; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.KeywordsFilterAdapter; +import awais.instagrabber.databinding.DialogKeywordsFilterBinding; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.SettingsHelper; +import awais.instagrabber.utils.Utils; + +public final class KeywordsFilterDialog extends DialogFragment { + + @Override + public void onStart() { + super.onStart(); + final Dialog dialog = getDialog(); + if (dialog == null) return; + final Window window = dialog.getWindow(); + if (window == null) return; + final int height = ViewGroup.LayoutParams.WRAP_CONTENT; + final int width = (int) (Utils.displayMetrics.widthPixels * 0.8); + window.setLayout(width, height); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + final DialogKeywordsFilterBinding dialogKeywordsFilterBinding = DialogKeywordsFilterBinding.inflate(inflater, container, false); + init(dialogKeywordsFilterBinding, getContext()); + dialogKeywordsFilterBinding.btnOK.setOnClickListener(view -> this.dismiss()); + return dialogKeywordsFilterBinding.getRoot(); + } + + private void init(DialogKeywordsFilterBinding dialogKeywordsFilterBinding, Context context){ + final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context); + final RecyclerView recyclerView = dialogKeywordsFilterBinding.recyclerKeyword; + recyclerView.setLayoutManager(linearLayoutManager); + + final SettingsHelper settingsHelper = new SettingsHelper(context); + final ArrayList items = new ArrayList<>(settingsHelper.getStringSet(Constants.KEYWORD_FILTERS)); + final KeywordsFilterAdapter adapter = new KeywordsFilterAdapter(context, items); + recyclerView.setAdapter(adapter); + + final EditText editText = dialogKeywordsFilterBinding.editText; + + dialogKeywordsFilterBinding.btnAdd.setOnClickListener(view ->{ + final String s = editText.getText().toString(); + if(s.isEmpty()) return; + if(items.contains(s)) { + editText.setText(""); + return; + } + items.add(s.toLowerCase()); + settingsHelper.putStringSet(Constants.KEYWORD_FILTERS, new HashSet<>(items)); + adapter.notifyItemInserted(items.size()); + final String message = context.getString(R.string.added_keywords, s); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + editText.setText(""); + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java index 07012fe9..14daec7f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java @@ -8,10 +8,9 @@ import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreferenceCompat; import awais.instagrabber.R; +import awais.instagrabber.dialogs.KeywordsFilterDialog; import awais.instagrabber.utils.Constants; -import static awais.instagrabber.utils.Utils.settingsHelper; - public class PostPreferencesFragment extends BasePreferencesFragment { @Override void setupPreferenceScreen(final PreferenceScreen screen) { @@ -20,6 +19,8 @@ public class PostPreferencesFragment extends BasePreferencesFragment { // generalCategory.addPreference(getAutoPlayVideosPreference(context)); screen.addPreference(getAlwaysMuteVideosPreference(context)); screen.addPreference(getShowCaptionPreference(context)); + screen.addPreference(getToggleKeywordFilterPreference(context)); + screen.addPreference(getEditKeywordFilterPreference(context)); } private Preference getAutoPlayVideosPreference(@NonNull final Context context) { @@ -46,4 +47,24 @@ public class PostPreferencesFragment extends BasePreferencesFragment { preference.setIconSpaceReserved(false); return preference; } + + private Preference getToggleKeywordFilterPreference(@NonNull final Context context) { + final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); + preference.setKey(Constants.TOGGLE_KEYWORD_FILTER); + preference.setDefaultValue(false); + preference.setTitle(R.string.toggle_keyword_filter); + preference.setIconSpaceReserved(false); + return preference; + } + + private Preference getEditKeywordFilterPreference(@NonNull final Context context){ + final Preference preference = new Preference(context); + preference.setTitle(R.string.edit_keyword_filter); + preference.setIconSpaceReserved(false); + preference.setOnPreferenceClickListener(view ->{ + new KeywordsFilterDialog().show(getParentFragmentManager(), null); + return true; + }); + return preference; + } } diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index cb56eba2..0c92f0fd 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -9,12 +9,15 @@ public final class Constants { public static final String APP_THEME = "app_theme_v19"; public static final String APP_LANGUAGE = "app_language_v19"; public static final String STORY_SORT = "story_sort"; + // set string prefs + public static final String KEYWORD_FILTERS = "keyword_filters"; // int prefs, do not export public static final String PREV_INSTALL_VERSION = "prevVersion"; public static final String BROWSER_UA_CODE = "browser_ua_code"; public static final String APP_UA_CODE = "app_ua_code"; // boolean prefs public static final String DOWNLOAD_USER_FOLDER = "download_user_folder"; + public static final String TOGGLE_KEYWORD_FILTER = "toggle_keyword_filter"; // deprecated: public static final String BOTTOM_TOOLBAR = "bottom_toolbar"; public static final String FOLDER_SAVE_TO = "saved_to"; public static final String AUTOPLAY_VIDEOS = "autoplay_videos"; diff --git a/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.java b/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.java new file mode 100644 index 00000000..f4d42ac1 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.java @@ -0,0 +1,49 @@ +package awais.instagrabber.utils; + +import java.util.ArrayList; +import java.util.List; + +import awais.instagrabber.repositories.responses.Caption; +import awais.instagrabber.repositories.responses.Media; + +public final class KeywordsFilterUtils { + + private final ArrayList keywords; + + public KeywordsFilterUtils(final ArrayList keywords){ + this.keywords = keywords; + } + + public boolean filter(final String caption){ + if(caption == null) return false; + if(keywords.isEmpty()) return false; + final String temp = caption.toLowerCase(); + for(final String s:keywords){ + if(temp.contains(s)) return true; + } + return false; + } + + public boolean filter(final Media media){ + if(media == null) return false; + final Caption c = media.getCaption(); + if(c == null) return false; + if(keywords.isEmpty()) return false; + final String temp = c.getText().toLowerCase(); + for(final String s:keywords){ + if(temp.contains(s)) return true; + } + return false; + } + + public List filter(final List media){ + if(keywords.isEmpty()) return media; + if(media == null) return new ArrayList<>(); + + final List result= new ArrayList<>(); + for(final Media m:media){ + if(!filter(m)) result.add(m); + } + return result; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index b6238a97..e48a96cd 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -8,6 +8,9 @@ import androidx.annotation.NonNull; import androidx.annotation.StringDef; import androidx.appcompat.app.AppCompatDelegate; +import java.util.HashSet; +import java.util.Set; + import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT; @@ -38,6 +41,7 @@ import static awais.instagrabber.utils.Constants.MUTED_VIDEOS; import static awais.instagrabber.utils.Constants.PREF_DARK_THEME; import static awais.instagrabber.utils.Constants.PREF_EMOJI_VARIANTS; import static awais.instagrabber.utils.Constants.PREF_HASHTAG_POSTS_LAYOUT; +import static awais.instagrabber.utils.Constants.KEYWORD_FILTERS; import static awais.instagrabber.utils.Constants.PREF_LIGHT_THEME; import static awais.instagrabber.utils.Constants.PREF_LIKED_POSTS_LAYOUT; import static awais.instagrabber.utils.Constants.PREF_LOCATION_POSTS_LAYOUT; @@ -53,6 +57,7 @@ import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG; import static awais.instagrabber.utils.Constants.SKIPPED_VERSION; import static awais.instagrabber.utils.Constants.STORY_SORT; import static awais.instagrabber.utils.Constants.SWAP_DATE_TIME_FORMAT_ENABLED; +import static awais.instagrabber.utils.Constants.TOGGLE_KEYWORD_FILTER; public final class SettingsHelper { private final SharedPreferences sharedPreferences; @@ -68,6 +73,12 @@ public final class SettingsHelper { return stringDefault; } + public Set getStringSet(@StringSetSettings final String key) { + final Set stringSetDefault = new HashSet<>(); + if (sharedPreferences != null) return sharedPreferences.getStringSet(key, stringSetDefault); + return stringSetDefault; + } + public int getInteger(@IntegerSettings final String key) { final int integerDefault = getIntegerDefault(key); if (sharedPreferences != null) return sharedPreferences.getInt(key, integerDefault); @@ -122,6 +133,10 @@ public final class SettingsHelper { if (sharedPreferences != null) sharedPreferences.edit().putString(key, val).apply(); } + public void putStringSet(@StringSetSettings final String key, final Set val) { + if (sharedPreferences != null) sharedPreferences.edit().putStringSet(key, val).apply(); + } + public void putInteger(@IntegerSettings final String key, final int val) { if (sharedPreferences != null) sharedPreferences.edit().putInt(key, val).apply(); } @@ -141,9 +156,12 @@ public final class SettingsHelper { @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY, CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH, - FLAG_SECURE}) + FLAG_SECURE, TOGGLE_KEYWORD_FILTER}) public @interface BooleanSettings {} @StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER}) public @interface IntegerSettings {} + + @StringDef({KEYWORD_FILTERS}) + public @interface StringSetSettings {} } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_keywords_filter.xml b/app/src/main/res/layout/dialog_keywords_filter.xml new file mode 100644 index 00000000..507d3b54 --- /dev/null +++ b/app/src/main/res/layout/dialog_keywords_filter.xml @@ -0,0 +1,57 @@ + + + + + + + +