Browse Source

Merge branch 'master' into add-sentry

renovate/org.robolectric-robolectric-4.x
Austin Huang 4 years ago
committed by GitHub
parent
commit
fba0a751b1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      app/src/main/java/awais/instagrabber/adapters/KeywordsFilterAdapter.java
  2. 51
      app/src/main/java/awais/instagrabber/adapters/viewholder/dialogs/KeywordsFilterDialogViewHolder.java
  3. 5
      app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java
  4. 2
      app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
  5. 101
      app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
  6. 2
      app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java
  7. 12
      app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
  8. 10
      app/src/main/java/awais/instagrabber/customviews/emoji/Emoji.java
  9. 16
      app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategory.java
  10. 78
      app/src/main/java/awais/instagrabber/dialogs/KeywordsFilterDialog.java
  11. 1
      app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
  12. 2
      app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
  13. 91
      app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
  14. 25
      app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java
  15. 3
      app/src/main/java/awais/instagrabber/utils/Constants.java
  16. 49
      app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.java
  17. 55
      app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
  18. 20
      app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
  19. 52
      app/src/main/java/awais/instagrabber/utils/emoji/EmojiCategoryDeserializer.java
  20. 44
      app/src/main/java/awais/instagrabber/utils/emoji/EmojiDeserializer.java
  21. 12
      app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java
  22. 19
      app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java
  23. 22
      app/src/main/java/awais/instagrabber/webservices/GraphQLService.java
  24. 12
      app/src/main/java/awais/instagrabber/webservices/NewsService.java
  25. 57
      app/src/main/res/layout/dialog_keywords_filter.xml
  26. 23
      app/src/main/res/layout/item_keyword.xml
  27. 5
      app/src/main/res/values/strings.xml

42
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<KeywordsFilterDialogViewHolder> {
private final Context context;
private final ArrayList<String> items;
public KeywordsFilterAdapter(Context context, ArrayList<String> 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();
}
}

51
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<String> 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;
}
}

5
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<Media> mediaResults = result.getFeedModels();
feedModels.addAll(mediaResults);
if (fetchListener != null) {
// if (feedModels.size() < 15 && hasNextPage) {
// feedService.fetch(csrfToken, nextCursor, this);

2
app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java

@ -133,7 +133,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, Media> {
// feedModelBuilder.setSliderItems(postModels);
// }
// return feedModelBuilder.build();
return ResponseBodyUtils.parseGraphQLItem(media);
return ResponseBodyUtils.parseGraphQLItem(media, null);
}
} catch (Exception e) {
// if (logCollector != null) {

101
app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java

@ -1,101 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.UserService;
public final class ProfileFetcher extends AsyncTask<Void, Void, Void> {
private static final String TAG = ProfileFetcher.class.getSimpleName();
private final UserService userService;
private final GraphQLService graphQLService;
private final FetchListener<User> fetchListener;
private final long myId;
private final boolean isLoggedIn;
private final String userName;
public ProfileFetcher(final String userName,
final long myId,
final boolean isLoggedIn,
final FetchListener<User> fetchListener) {
this.userName = userName;
this.myId = myId;
this.isLoggedIn = isLoggedIn;
this.fetchListener = fetchListener;
userService = isLoggedIn ? UserService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
}
@Nullable
@Override
protected Void doInBackground(final Void... voids) {
if (isLoggedIn && userName != null) {
userService.getUsernameInfo(userName, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
userService.getUserFriendship(user.getPk(), new ServiceCallback<FriendshipStatus>() {
@Override
public void onSuccess(final FriendshipStatus status) {
user.setFriendshipStatus(status);
fetchListener.onResult(user);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
fetchListener.onFailure(t);
}
});
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
fetchListener.onFailure(t);
}
});
}
else if (isLoggedIn) {
userService.getUserInfo(myId, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
fetchListener.onResult(user);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
fetchListener.onFailure(t);
}
});
}
else {
graphQLService.fetchUser(userName, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
fetchListener.onResult(user);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
fetchListener.onFailure(t);
}
});
}
return null;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
}

2
app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java

@ -49,7 +49,7 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
}
};
if (isLoggedIn) profileService.fetchPosts(profileModel.getPk(), nextMaxId, cb);
else graphQLService.fetchProfilePosts(profileModel.getPk(), 30, nextMaxId, cb);
else graphQLService.fetchProfilePosts(profileModel.getPk(), 30, nextMaxId, profileModel, cb);
}
@Override

12
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<Media> models = mediaViewModel.getList().getValue();
final List<Media> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.addAll(result);
if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)){
final ArrayList<String> items = new ArrayList<>(settingsHelper.getStringSet(Constants.KEYWORD_FILTERS));
modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result));
}
else {
modelsCopy.addAll(result);
}
mediaViewModel.getList().postValue(modelsCopy);
dispatchFetchStatus();
}

10
app/src/main/java/awais/instagrabber/customviews/emoji/Emoji.java

@ -2,7 +2,6 @@ package awais.instagrabber.customviews.emoji;
import androidx.annotation.NonNull;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@ -12,10 +11,12 @@ public class Emoji {
private final List<Emoji> variants;
private GoogleCompatEmojiDrawable drawable;
public Emoji(final String unicode, final String name) {
public Emoji(final String unicode,
final String name,
final List<Emoji> variants) {
this.unicode = unicode;
this.name = name;
this.variants = new LinkedList<>();
this.variants = variants;
}
public String getUnicode() {
@ -35,7 +36,7 @@ public class Emoji {
}
public GoogleCompatEmojiDrawable getDrawable() {
if (drawable == null) {
if (drawable == null && unicode != null) {
drawable = new GoogleCompatEmojiDrawable(unicode);
}
return drawable;
@ -60,6 +61,7 @@ public class Emoji {
return "Emoji{" +
"unicode='" + unicode + '\'' +
", name='" + name + '\'' +
", variants=" + variants +
'}';
}
}

16
app/src/main/java/awais/instagrabber/customviews/emoji/EmojiCategory.java

@ -1,8 +1,8 @@
package awais.instagrabber.customviews.emoji;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@ -10,12 +10,13 @@ import awais.instagrabber.R;
public class EmojiCategory {
private final EmojiCategoryType type;
private final Map<String, Emoji> emojis = new LinkedHashMap<>();
private final Map<String, Emoji> emojis;
@DrawableRes
private int drawableRes;
public EmojiCategory(final EmojiCategoryType type) {
public EmojiCategory(final EmojiCategoryType type, final Map<String, Emoji> emojis) {
this.type = type;
this.emojis = emojis;
}
public EmojiCategoryType getType() {
@ -73,4 +74,13 @@ public class EmojiCategory {
public int hashCode() {
return Objects.hash(type);
}
@NonNull
@Override
public String toString() {
return "EmojiCategory{" +
"type=" + type +
", emojis=" + emojis +
'}';
}
}

78
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<String> 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("");
});
}
}

1
app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java

@ -378,6 +378,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (getArguments() == null) return;
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
hashtag = fragmentArgs.getHashtag();
if (hashtag.charAt(0) == '#') hashtag = hashtag.substring(1);
fetchHashtagModel();
}

2
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java

@ -925,7 +925,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im
}
private void setupLocation(final Location location) {
if (location == null) {
if (location == null || !detailsVisible) {
binding.location.setVisibility(View.GONE);
return;
}

91
app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java

@ -4,7 +4,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -57,7 +56,6 @@ import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.asyncs.CreateThreadAction;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
@ -73,7 +71,6 @@ import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.InboxManager;
import awais.instagrabber.models.HighlightModel;
@ -93,11 +90,14 @@ import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import awais.instagrabber.webservices.UserService;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG;
@ -120,6 +120,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private FriendshipService friendshipService;
private StoriesService storiesService;
private MediaService mediaService;
private UserService userService;
private GraphQLService graphQLService;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private HighlightsAdapter highlightsAdapter;
@ -304,6 +306,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private LayoutProfileDetailsBinding profileDetailsBinding;
private AccountRepository accountRepository;
private FavoriteRepository favoriteRepository;
private AppStateViewModel appStateViewModel;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -317,8 +320,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
friendshipService = isLoggedIn ? FriendshipService.getInstance(deviceUuid, csrfToken, myId) : null;
storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null;
mediaService = isLoggedIn ? MediaService.getInstance(null, null, 0) : null;
userService = isLoggedIn ? UserService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext()));
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
setHasOptionsMenu(true);
}
@ -601,31 +607,66 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (usernameTemp.startsWith("@")) {
usernameTemp = usernameTemp.substring(1);
}
new ProfileFetcher(TextUtils.isEmpty(username) ? null : usernameTemp, myId, isLoggedIn, new FetchListener<User>() {
@Override
public void onResult(final User user) {
if (getContext() == null) return;
if (TextUtils.isEmpty(username)) {
username = user.getUsername();
setUsernameDelayed();
if (TextUtils.isEmpty(usernameTemp)) {
profileModel = appStateViewModel.getCurrentUser();
username = profileModel.getUsername();
setUsernameDelayed();
setProfileDetails();
}
else if (isLoggedIn) {
userService.getUsernameInfo(usernameTemp, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
userService.getUserFriendship(user.getPk(), new ServiceCallback<FriendshipStatus>() {
@Override
public void onSuccess(final FriendshipStatus status) {
user.setFriendshipStatus(status);
profileModel = user;
setProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error fetching profile relationship", t);
final Context context = getContext();
try {
if (t == null) Toast.makeText(context, R.string.error_loading_profile_loggedin, Toast.LENGTH_LONG).show();
else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
}
});
}
profileModel = user;
setProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error fetching profile", t);
final Context context = getContext();
try {
if (t == null) Toast.makeText(context,
isLoggedIn ? R.string.error_loading_profile_loggedin : R.string.error_loading_profile,
Toast.LENGTH_LONG).show();
else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error fetching profile", t);
final Context context = getContext();
try {
if (t == null) Toast.makeText(context, R.string.error_loading_profile_loggedin, Toast.LENGTH_LONG).show();
else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
}
});
}
else {
graphQLService.fetchUser(usernameTemp, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
profileModel = user;
setProfileDetails();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error fetching profile", t);
final Context context = getContext();
try {
if (t == null) Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_LONG).show();
else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
}
});
}
}
private void setProfileDetails() {

25
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;
}
}

3
app/src/main/java/awais/instagrabber/utils/Constants.java

@ -11,12 +11,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";

49
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<String> keywords;
public KeywordsFilterUtils(final ArrayList<String> 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<Media> filter(final List<Media> media){
if(keywords.isEmpty()) return media;
if(media == null) return new ArrayList<>();
final List<Media> result= new ArrayList<>();
for(final Media m:media){
if(!filter(m)) result.add(m);
}
return result;
}
}

55
app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java

@ -675,7 +675,8 @@ public final class ResponseBodyUtils {
// return feedModelBuilder.build();
// }
public static Media parseGraphQLItem(final JSONObject itemJson) throws JSONException {
// the "user" argument can be null, it's used because instagram redacts user details from responses
public static Media parseGraphQLItem(final JSONObject itemJson, final User backup) throws JSONException {
if (itemJson == null) {
return null;
}
@ -728,41 +729,28 @@ public final class ResponseBodyUtils {
width = dimensions.optInt("width");
}
String thumbnailUrl = null;
try {
thumbnailUrl = feedItem.getJSONArray("display_resources")
.getJSONObject(0)
.getString("src");
} catch (JSONException ignored) {}
// final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
// .setProfileModel(profileModel)
// .setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO
// : MediaItemType.MEDIA_TYPE_IMAGE)
// .setViewCount(videoViews)
// .setPostId(feedItem.getString(Constants.EXTRAS_ID))
// .setDisplayUrl(resourceUrl)
// .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl)
// .setShortCode(feedItem.getString(Constants.EXTRAS_SHORTCODE))
// .setPostCaption(captionText)
// .setCommentsCount(commentsCount)
// .setTimestamp(feedItem.optLong("taken_at_timestamp", -1))
// .setLiked(feedItem.optBoolean("viewer_has_liked"))
// .setBookmarked(feedItem.optBoolean("viewer_has_saved"))
// .setLikesCount(likesCount)
// .setLocationName(locationName)
// .setLocationId(String.valueOf(locationId))
// .setImageHeight(height)
// .setImageWidth(width);
final JSONArray displayResources = feedItem.getJSONArray("display_resources");
final List<MediaCandidate> candidates = new ArrayList<MediaCandidate>();
for (int i = 0; i < displayResources.length(); i++) {
final JSONObject displayResource = displayResources.getJSONObject(i);
candidates.add(new MediaCandidate(
displayResource.getInt("config_width"),
displayResource.getInt("config_height"),
displayResource.getString("src")
));
}
final ImageVersions2 imageVersions2 = new ImageVersions2(candidates);
User user = null;
User user = backup;
long userId = -1;
if (feedItem.has("owner")) {
if (feedItem.has("owner") && user == null) {
final JSONObject owner = feedItem.getJSONObject("owner");
final FriendshipStatus friendshipStatus = new FriendshipStatus(
false,
false,
false,
false,
owner.optBoolean("is_private"),
false,
false,
false,
false,
@ -774,7 +762,7 @@ public final class ResponseBodyUtils {
userId,
owner.optString(Constants.EXTRAS_USERNAME),
owner.optString("full_name"),
owner.optBoolean("is_private"),
false,
owner.optString("profile_pic_url"),
null,
friendshipStatus,
@ -783,13 +771,6 @@ public final class ResponseBodyUtils {
null, null, null, null);
}
final String id = feedItem.getString(Constants.EXTRAS_ID);
final ImageVersions2 imageVersions2 = new ImageVersions2(
Collections.singletonList(new MediaCandidate(
width,
height,
isVideo ? thumbnailUrl : resourceUrl
))
);
VideoVersion videoVersion = null;
if (isVideo) {
videoVersion = new VideoVersion(
@ -821,7 +802,7 @@ public final class ResponseBodyUtils {
for (int i = 0; i < children.length(); i++) {
final JSONObject child = children.optJSONObject(i);
if (child == null) continue;
final Media media = parseGraphQLItem(child);
final Media media = parseGraphQLItem(child, null);
media.setIsSidecarChild(true);
childItems.add(media);
}

20
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;
@ -39,6 +42,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;
@ -54,6 +58,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;
@ -69,6 +74,12 @@ public final class SettingsHelper {
return stringDefault;
}
public Set<String> getStringSet(@StringSetSettings final String key) {
final Set<String> 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);
@ -123,6 +134,10 @@ public final class SettingsHelper {
if (sharedPreferences != null) sharedPreferences.edit().putString(key, val).apply();
}
public void putStringSet(@StringSetSettings final String key, final Set<String> 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();
}
@ -146,9 +161,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, PREF_ENABLE_SENTRY})
FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY})
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 {}
}

52
app/src/main/java/awais/instagrabber/utils/emoji/EmojiCategoryDeserializer.java

@ -0,0 +1,52 @@
package awais.instagrabber.utils.emoji;
import android.util.Log;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import awais.instagrabber.customviews.emoji.Emoji;
import awais.instagrabber.customviews.emoji.EmojiCategory;
import awais.instagrabber.customviews.emoji.EmojiCategoryType;
public class EmojiCategoryDeserializer implements JsonDeserializer<EmojiCategory> {
private static final String TAG = EmojiCategoryDeserializer.class.getSimpleName();
@Override
public EmojiCategory deserialize(final JsonElement json,
final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement typeElement = jsonObject.get("type");
final JsonObject emojisObject = jsonObject.getAsJsonObject("emojis");
if (typeElement == null || emojisObject == null) {
throw new JsonParseException("Invalid json for EmojiCategory");
}
final String typeString = typeElement.getAsString();
EmojiCategoryType type;
try {
type = EmojiCategoryType.valueOf(typeString);
} catch (IllegalArgumentException e) {
Log.e(TAG, "deserialize: ", e);
type = EmojiCategoryType.OTHERS;
}
final Map<String, Emoji> emojis = new LinkedHashMap<>();
for (final Map.Entry<String, JsonElement> emojiObjectEntry : emojisObject.entrySet()) {
final String unicode = emojiObjectEntry.getKey();
final JsonElement value = emojiObjectEntry.getValue();
if (unicode == null || value == null) {
throw new JsonParseException("Invalid json for EmojiCategory");
}
final Emoji emoji = context.deserialize(value, Emoji.class);
emojis.put(unicode, emoji);
}
return new EmojiCategory(type, emojis);
}
}

44
app/src/main/java/awais/instagrabber/utils/emoji/EmojiDeserializer.java

@ -0,0 +1,44 @@
package awais.instagrabber.utils.emoji;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.customviews.emoji.Emoji;
public class EmojiDeserializer implements JsonDeserializer<Emoji> {
@Override
public Emoji deserialize(final JsonElement json,
final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement unicodeElement = jsonObject.get("unicode");
final JsonElement nameElement = jsonObject.get("name");
if (unicodeElement == null || nameElement == null) {
throw new JsonParseException("Invalid json for Emoji class");
}
final JsonElement variantsElement = jsonObject.get("variants");
final List<Emoji> variants = new LinkedList<>();
if (variantsElement != null) {
final JsonArray variantsArray = variantsElement.getAsJsonArray();
for (final JsonElement variantElement : variantsArray) {
final Emoji variant = context.deserialize(variantElement, Emoji.class);
if (variant != null) {
variants.add(variant);
}
}
}
return new Emoji(
unicodeElement.getAsString(),
nameElement.getAsString(),
variants
);
}
}

12
app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java

@ -3,10 +3,11 @@ package awais.instagrabber.utils.emoji;
import android.util.Log;
import com.google.common.collect.ImmutableList;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Collection;
@ -52,7 +53,12 @@ public final class EmojiParser {
}
try (final InputStream in = classLoader.getResourceAsStream(file)) {
final String json = NetworkUtils.readFromInputStream(in);
final Gson gson = new Gson();
final Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(EmojiCategory.class, new EmojiCategoryDeserializer())
.registerTypeAdapter(Emoji.class, new EmojiDeserializer())
.setLenient()
.create();
final Type type = new TypeToken<Map<EmojiCategoryType, EmojiCategory>>() {}.getType();
categoryMap = gson.fromJson(json, type);
// Log.d(TAG, "EmojiParser: " + categoryMap);
@ -68,7 +74,7 @@ public final class EmojiParser {
.build()
.stream())
.collect(Collectors.toMap(Emoji::getUnicode, Function.identity()));
} catch (IOException e) {
} catch (Exception e) {
Log.e(TAG, "EmojiParser: ", e);
}
}

19
app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java

@ -1,21 +1,18 @@
package awais.instagrabber.viewmodels;
import android.app.Application;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.UserService;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -27,6 +24,7 @@ public class AppStateViewModel extends AndroidViewModel {
private User currentUser;
private AccountRepository accountRepository;
private UserService userService;
public AppStateViewModel(@NonNull final Application application) {
super(application);
@ -34,6 +32,7 @@ public class AppStateViewModel extends AndroidViewModel {
cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
if (!isLoggedIn) return;
userService = UserService.getInstance();
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(application));
fetchProfileDetails();
}
@ -44,6 +43,14 @@ public class AppStateViewModel extends AndroidViewModel {
private void fetchProfileDetails() {
final long uid = CookieUtils.getUserIdFromCookie(cookie);
new ProfileFetcher(null, uid, true, user -> this.currentUser = user).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
userService.getUserInfo(uid, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
currentUser = user;
}
@Override
public void onFailure(final Throwable t) {}
});
}
}

22
app/src/main/java/awais/instagrabber/webservices/GraphQLService.java

@ -56,6 +56,7 @@ public class GraphQLService extends BaseService {
final String variables,
final String arg1,
final String arg2,
final User backup,
final ServiceCallback<PostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", queryHash);
@ -66,7 +67,7 @@ public class GraphQLService extends BaseService {
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
// Log.d(TAG, "onResponse: body: " + response.body());
final PostsFetchResponse postsFetchResponse = parsePostResponse(response, arg1, arg2);
final PostsFetchResponse postsFetchResponse = parsePostResponse(response, arg1, arg2, backup);
if (callback != null) {
callback.onSuccess(postsFetchResponse);
}
@ -96,6 +97,7 @@ public class GraphQLService extends BaseService {
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_LOCATION,
"edge_location_to_media",
null,
callback);
}
@ -108,12 +110,14 @@ public class GraphQLService extends BaseService {
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_HASHTAG,
"edge_hashtag_to_media",
null,
callback);
}
public void fetchProfilePosts(final long profileId,
final int postsPerPage,
final String maxId,
final User backup,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("18a7b935ab438c4514b1f742d8fa07a7",
"{\"id\":\"" + profileId + "\"," +
@ -121,6 +125,7 @@ public class GraphQLService extends BaseService {
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_USER,
"edge_owner_to_timeline_media",
backup,
callback);
}
@ -134,21 +139,28 @@ public class GraphQLService extends BaseService {
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_USER,
"edge_user_to_photos_of_you",
null,
callback);
}
@NonNull
private PostsFetchResponse parsePostResponse(@NonNull final Response<String> response, @NonNull final String arg1, @NonNull final String arg2)
private PostsFetchResponse parsePostResponse(@NonNull final Response<String> response,
@NonNull final String arg1,
@NonNull final String arg2,
final User backup)
throws JSONException {
if (TextUtils.isEmpty(response.body())) {
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code());
return new PostsFetchResponse(Collections.emptyList(), false, null);
}
return parseResponseBody(response.body(), arg1, arg2);
return parseResponseBody(response.body(), arg1, arg2, backup);
}
@NonNull
private PostsFetchResponse parseResponseBody(@NonNull final String body, @NonNull final String arg1, @NonNull final String arg2)
private PostsFetchResponse parseResponseBody(@NonNull final String body,
@NonNull final String arg1,
@NonNull final String arg2,
final User backup)
throws JSONException {
final List<Media> items = new ArrayList<>();
final JSONObject timelineFeed = new JSONObject(body)
@ -174,7 +186,7 @@ public class GraphQLService extends BaseService {
if (itemJson == null) {
continue;
}
final Media media = ResponseBodyUtils.parseGraphQLItem(itemJson);
final Media media = ResponseBodyUtils.parseGraphQLItem(itemJson, backup);
if (media != null) {
items.add(media);
}

12
app/src/main/java/awais/instagrabber/webservices/NewsService.java

@ -121,9 +121,15 @@ public class NewsService extends BaseService {
callback.onSuccess(null);
return;
}
final List<AymlUser> aymlUsers = new ArrayList<>();
aymlUsers.addAll(body.getNewSuggestedUsers().getSuggestions());
aymlUsers.addAll(body.getSuggestedUsers().getSuggestions());
final List<AymlUser> aymlUsers = new ArrayList<AymlUser>();
final List<AymlUser> newSuggestions = body.getNewSuggestedUsers().getSuggestions();
if (newSuggestions != null) {
aymlUsers.addAll(newSuggestions);
}
final List<AymlUser> oldSuggestions = body.getSuggestedUsers().getSuggestions();
if (oldSuggestions != null) {
aymlUsers.addAll(oldSuggestions);
}
final List<Notification> newsItems = aymlUsers.stream()
.map(i -> {

57
app/src/main/res/layout/dialog_keywords_filter.xml

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:paddingTop="16dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/hint_keyword"
android:singleLine="true"
app:layout_constraintEnd_toStartOf="@id/btnAdd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnAdd"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/ic_add"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/edit_text"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@id/edit_text" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerKeyword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btnAdd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:layout_editor_absoluteX="16dp"
tools:listitem="@layout/item_keyword" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnOK"
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/ok"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/recyclerKeyword" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

23
app/src/main/res/layout/item_keyword.xml

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingTop="8dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/keyword_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/keyword_delete"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/ic_delete"
android:scaleType="center" />
</LinearLayout>

5
app/src/main/res/values/strings.xml

@ -466,6 +466,11 @@
<string name="generic_null_response">Response is null!</string>
<string name="generic_not_ok_response">Response status is not ok!</string>
<string name="generic_failed_request">Request failed!</string>
<string name="hint_keyword">Keyword</string>
<string name="toggle_keyword_filter">Enable keyword filter</string>
<string name="edit_keyword_filter">Edit keyword filters</string>
<string name="added_keywords">Added keyword: %s to filter list</string>
<string name="removed_keywords">Removed keyword: %s from filter list</string>
<string name="marked_as_seen">Marked as seen</string>
<string name="delete_unsuccessful">Delete unsuccessful</string>
<string name="crash_report_subject">Barinsta Crash Report</string>

Loading…
Cancel
Save