1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-22 14:47:29 +00:00

Add favorites, also fixes the hashtag follow, and some theming

This commit is contained in:
Ammar Githam 2020-09-19 04:18:14 +09:00
parent 027b356357
commit cf974a74f3
54 changed files with 1915 additions and 729 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@
app/release app/release
.idea/git_toolbox_prj.xml .idea/git_toolbox_prj.xml
.idea/dbnavigator.xml

View File

@ -41,7 +41,7 @@ dependencies {
def nav_version = "2.3.0" def nav_version = "2.3.0"
def preference_version = "1.1.1" 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 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"

View File

@ -38,6 +38,7 @@ import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -91,7 +92,8 @@ public class MainActivity extends BaseLanguageActivity {
R.id.followViewerFragment, R.id.followViewerFragment,
R.id.directMessagesSettingsFragment, R.id.directMessagesSettingsFragment,
R.id.notificationsViewer, R.id.notificationsViewer,
R.id.themePreferencesFragment); R.id.themePreferencesFragment,
R.id.favoritesFragment);
private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>(); private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>();
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment); private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"; private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
@ -582,4 +584,9 @@ public class MainActivity extends BaseLanguageActivity {
unbindService(serviceConnection); unbindService(serviceConnection);
isActivityCheckerServiceBound = false; isActivityCheckerServiceBound = false;
} }
@NonNull
public BottomNavigationView getBottomNavView() {
return binding.bottomNavView;
}
} }

View File

@ -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<RecyclerView.ViewHolder> {
private final OnFavoriteClickListener clickListener;
private final OnFavoriteLongClickListener longClickListener;
private final AsyncListDiffer<FavoriteModelOrHeader> differ;
private static final DiffUtil.ItemCallback<FavoriteModelOrHeader> diffCallback = new DiffUtil.ItemCallback<FavoriteModelOrHeader>() {
@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<DataBox.FavoriteModel> list) {
if (list == null) {
differ.submitList(null);
return;
}
differ.submitList(sectionAndSort(list));
}
public void submitList(@Nullable final List<DataBox.FavoriteModel> list, @Nullable final Runnable commitCallback) {
if (list == null) {
differ.submitList(null, commitCallback);
return;
}
differ.submitList(sectionAndSort(list), commitCallback);
}
@NonNull
private List<FavoriteModelOrHeader> sectionAndSort(@NonNull final List<DataBox.FavoriteModel> list) {
final List<DataBox.FavoriteModel> 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<FavoriteModelOrHeader> 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);
}
}
}

View File

@ -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);
}
}

View File

@ -212,9 +212,10 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
feedModelsList.trimToSize(); feedModelsList.trimToSize();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]); final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
if (feedModels[feedModels.length - 1] != null) final int length = feedModels.length;
feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor); if (length >= 1 && feedModels[length - 1] != null) {
feedModels[length - 1].setPageCursor(hasNextPage, endCursor);
}
result = feedModels; result = feedModels;
} }

View File

@ -8,6 +8,10 @@ import androidx.annotation.Nullable;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; 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.HttpURLConnection;
import java.net.URL; import java.net.URL;
@ -21,6 +25,8 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> { public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
private static final String TAG = "HashtagFetcher";
private final FetchListener<HashtagModel> fetchListener; private final FetchListener<HashtagModel> fetchListener;
private final String hashtag; private final String hashtag;
@ -35,12 +41,14 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
HashtagModel result = null; HashtagModel result = null;
try { 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.setUseCaches(true);
conn.connect(); conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { 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"); final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media");
if (timelineMedia.has("edges")) { if (timelineMedia.has("edges")) {
@ -53,13 +61,34 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
user.getString("profile_pic_url"), user.getString("profile_pic_url"),
timelineMedia.getLong("count"), timelineMedia.getLong("count"),
user.optBoolean("is_following")); 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(); conn.disconnect();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); 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; return result;

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; 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.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> { public final class PostsFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private static final String TAG = "PostsFetcher"; private static final String TAG = "PostsFetcher";
private final PostItemType type; private final PostItemType type;
private final String endCursor; private final String endCursor;
private final String id; private final String id;
private final FetchListener<PostModel[]> fetchListener; private final FetchListener<List<PostModel>> fetchListener;
private String username = null; private String username = null;
public PostsFetcher(final String id, public PostsFetcher(final String id,
final PostItemType type, final PostItemType type,
final String endCursor, final String endCursor,
final FetchListener<PostModel[]> fetchListener) { final FetchListener<List<PostModel>> fetchListener) {
this.id = id; this.id = id;
this.type = type; this.type = type;
this.endCursor = endCursor == null ? "" : endCursor; this.endCursor = endCursor == null ? "" : endCursor;
@ -52,7 +54,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
} }
@Override @Override
protected PostModel[] doInBackground(final Void... voids) { protected List<PostModel> doInBackground(final Void... voids) {
// final boolean isHashTag = id.charAt(0) == '#'; // final boolean isHashTag = id.charAt(0) == '#';
// final boolean isSaved = id.charAt(0) == '$'; // final boolean isSaved = id.charAt(0) == '$';
// final boolean isTagged = id.charAt(0) == '%'; // final boolean isTagged = id.charAt(0) == '%';
@ -79,7 +81,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
default: default:
url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor; url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor;
} }
PostModel[] result = null; List<PostModel> result = new ArrayList<>();
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false); conn.setUseCaches(false);
@ -126,8 +128,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
} }
final JSONArray edges = mediaPosts.getJSONArray("edges"); final JSONArray edges = mediaPosts.getJSONArray("edges");
final PostModel[] models = new PostModel[edges.length()]; for (int i = 0; i < edges.length(); ++i) {
for (int i = 0; i < models.length; ++i) {
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node");
final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges"); final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges");
@ -139,34 +140,43 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE; else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), final PostModel model = new PostModel(
mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"), itemType,
mediaNode.getString(Constants.EXTRAS_ID),
mediaNode.getString("display_url"),
mediaNode.getString("thumbnail_src"),
mediaNode.getString(Constants.EXTRAS_SHORTCODE), mediaNode.getString(Constants.EXTRAS_SHORTCODE),
captions.length() > 0 ? captions.getJSONObject(0).getJSONObject("node").getString("text") : null, captions.length() > 0 ? captions.getJSONObject(0)
mediaNode.getLong("taken_at_timestamp"), mediaNode.optBoolean("viewer_has_liked"), .getJSONObject("node")
mediaNode.optBoolean("viewer_has_saved"), mediaNode.getJSONObject("edge_liked_by").getLong("count")); .getString("text")
: null,
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); 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) if (!result.isEmpty() && result.get(result.size() - 1) != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor); result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor);
result = models;
} }
conn.disconnect(); conn.disconnect();
} catch (Exception e) { } catch (Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); 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; return result;
} }
@Override @Override
protected void onPostExecute(final PostModel[] postModels) { protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels); if (fetchListener != null) fetchListener.onResult(postModels);
} }
} }

View File

@ -42,7 +42,6 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
conn.connect(); conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final String defaultHashTagPic = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
final JSONObject jsonObject = new JSONObject(NetworkUtils.readFromConnection(conn)); final JSONObject jsonObject = new JSONObject(NetworkUtils.readFromConnection(conn));
conn.disconnect(); conn.disconnect();
@ -63,7 +62,7 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
suggestionModels.add(new SuggestionModel(false, suggestionModels.add(new SuggestionModel(false,
hashtag.getString(Constants.EXTRAS_NAME), hashtag.getString(Constants.EXTRAS_NAME),
null, null,
hashtag.optString("profile_pic_url", defaultHashTagPic), hashtag.optString("profile_pic_url", Constants.DEFAULT_HASH_TAG_PIC),
SuggestionType.TYPE_HASHTAG, SuggestionType.TYPE_HASHTAG,
hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1))); hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1)));
} }

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -28,25 +30,27 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> { public final class iLikedFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private final String endCursor; private static final String TAG = "iLikedFetcher";
private final FetchListener<PostModel[]> fetchListener;
public iLikedFetcher(final FetchListener<PostModel[]> fetchListener) { private final String endCursor;
private final FetchListener<List<PostModel>> fetchListener;
public iLikedFetcher(final FetchListener<List<PostModel>> fetchListener) {
this.endCursor = ""; this.endCursor = "";
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
public iLikedFetcher(final String endCursor, final FetchListener<PostModel[]> fetchListener) { public iLikedFetcher(final String endCursor, final FetchListener<List<PostModel>> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor; this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@Override @Override
protected PostModel[] doInBackground(final Void... voids) { protected List<PostModel> doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor; final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor;
PostModel[] result = null; List<PostModel> result = new ArrayList<>();
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false); conn.setUseCaches(false);
@ -68,8 +72,7 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
} }
final JSONArray edges = body.getJSONArray("items"); final JSONArray edges = body.getJSONArray("items");
final PostModel[] models = new PostModel[edges.length()]; for (int i = 0; i < edges.length(); ++i) {
for (int i = 0; i < models.length; ++i) {
final JSONObject mediaNode = edges.getJSONObject(i); final JSONObject mediaNode = edges.getJSONObject(i);
final boolean isSlider = mediaNode.has("carousel_media_count"); final boolean isSlider = mediaNode.has("carousel_media_count");
@ -80,48 +83,57 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE; else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), final PostModel model = new PostModel(
isSlider itemType,
? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) mediaNode.getString(Constants.EXTRAS_ID),
isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getHighQualityImage(mediaNode), : ResponseBodyUtils.getHighQualityImage(mediaNode),
isSlider isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media")
? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) .getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode), : ResponseBodyUtils.getLowQualityImage(mediaNode),
mediaNode.getString("code"), mediaNode.getString("code"),
mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"), mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"),
mediaNode.getLong("taken_at"), true, mediaNode.getLong("taken_at"),
mediaNode.optBoolean("has_viewer_saved"), mediaNode.getLong("like_count")); true,
mediaNode.optBoolean("has_viewer_saved"),
mediaNode.getLong("like_count"));
result.add(model);
String username = mediaNode.getJSONObject("user").getString("username"); String username = mediaNode.getJSONObject("user").getString("username");
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + 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; File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH + 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); 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) final int length = result.size();
models[models.length - 1].setPageCursor(hasNextPage, endCursor); if (length >= 1 && result.get(length - 1) != null) {
result.get(length - 1).setPageCursor(hasNextPage, endCursor);
result = models; }
} }
conn.disconnect(); conn.disconnect();
} catch (Exception e) { } catch (Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); 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; return result;
} }
@Override @Override
protected void onPostExecute(final PostModel[] postModels) { protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels); if (fetchListener != null) fetchListener.onResult(postModels);
} }
} }

View File

@ -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<DataBox.FavoriteModel> 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<DataBox.CookieModel> 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();
}
}

View File

@ -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<DataBox.FavoriteModel> 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<DataBox.FavoriteModel> allFavorites) {
final Runnable runnable = () -> {
final List<DataBox.FavoriteModel> 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();
}
}

View File

@ -1,7 +1,6 @@
package awais.instagrabber.fragments; package awais.instagrabber.fragments;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -22,16 +21,18 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; 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.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -49,21 +50,24 @@ import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel; import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; 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 static final String TAG = "HashTagFragment";
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
@ -79,7 +83,8 @@ public class HashTagFragment extends Fragment {
private String endCursor; private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting; private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn; private boolean isLoggedIn;
private StoryModel[] storyModels; private TagsService tagsService;
private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
@ -116,19 +121,27 @@ public class HashTagFragment extends Fragment {
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return; if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue(); final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); List<PostModel> finalList = postModels == null || postModels.isEmpty()
finalList.addAll(Arrays.asList(result)); ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
finalList.addAll(result);
postsViewModel.getList().postValue(finalList); postsViewModel.getList().postValue(finalList);
PostModel model = null; PostModel model = null;
if (result.length != 0) { if (!result.isEmpty()) {
model = result[result.length - 1]; model = result.get(result.size() - 1);
} }
if (model == null) return; if (model == null) return;
endCursor = model.getEndCursor(); endCursor = model.getEndCursor();
@ -141,6 +154,7 @@ public class HashTagFragment extends Fragment {
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
tagsService = TagsService.getInstance();
} }
@Nullable @Nullable
@ -158,10 +172,18 @@ public class HashTagFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init(); init();
shouldRefresh = false; shouldRefresh = false;
} }
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchHashtagModel();
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -257,7 +279,6 @@ public class HashTagFragment extends Fragment {
private void fetchPosts() { private void fetchPosts() {
stopCurrentExecutor(); stopCurrentExecutor();
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
if (TextUtils.isEmpty(hashtag)) return; if (TextUtils.isEmpty(hashtag)) return;
currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener) currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener)
@ -266,29 +287,109 @@ public class HashTagFragment extends Fragment {
if (context == null) return; if (context == null) return;
if (isLoggedIn) { if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> { new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) { if (stories != null && stories.length > 0) {
binding.mainHashtagImage.setStoriesBorder(); binding.mainHashtagImage.setStoriesBorder();
} }
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow); binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
ContextCompat.getColor(context, hashtagModel.getFollowing() ? R.drawable.ic_outline_person_add_disabled_24
? R.color.btn_purple_background : R.drawable.ic_outline_person_add_24);
: R.color.btn_pink_background))); binding.btnFollowTag.setOnClickListener(v -> {
} else { final String cookie = settingsHelper.getString(Constants.COOKIE);
binding.btnFollowTag.setText(Utils.dataBox.getFavorite(hashtag) != null final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
? R.string.unfavorite_short binding.btnFollowTag.setClickable(false);
: R.string.favorite_short); if (!hashtagModel.getFollowing()) {
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
ContextCompat.getColor(context, Utils.dataBox.getFavorite(hashtag) != null @Override
? R.color.btn_purple_background public void onSuccess(final Boolean result) {
: R.color.btn_pink_background))); 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<Boolean>() {
@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.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()); binding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
final String postCount = String.valueOf(hashtagModel.getPostCount()); 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 RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
binding.mainTagPostCount.setText(span); binding.mainTagPostCount.setText(span);
@ -312,9 +413,7 @@ public class HashTagFragment extends Fragment {
if (actionBar != null) { if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag); Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler(); final Handler handler = new Handler();
handler.postDelayed(() -> { handler.postDelayed(() -> actionBar.setTitle(hashtag), 200);
actionBar.setTitle(hashtag);
}, 200);
} }
} }

View File

@ -27,10 +27,14 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; 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.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -48,11 +52,12 @@ import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; 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.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; 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 static final String TAG = "LocationFragment";
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
@ -78,7 +83,7 @@ public class LocationFragment extends Fragment {
private String endCursor; private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting; private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn; private boolean isLoggedIn;
private StoryModel[] storyModels; private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
@ -119,20 +124,25 @@ public class LocationFragment extends Fragment {
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return; if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue(); final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels); : new ArrayList<>(postModels);
finalList.addAll(Arrays.asList(result)); if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList); postsViewModel.getList().postValue(finalList);
PostModel model = null; PostModel model = null;
if (result.length != 0) { if (!result.isEmpty()) {
model = result[result.length - 1]; model = result.get(result.size() - 1);
} }
if (model == null) return; if (model == null) return;
endCursor = model.getEndCursor(); endCursor = model.getEndCursor();
@ -164,10 +174,18 @@ public class LocationFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init(); init();
shouldRefresh = false; shouldRefresh = false;
} }
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchLocationModel();
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -182,6 +200,8 @@ public class LocationFragment extends Fragment {
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments()); final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments());
locationId = fragmentArgs.getLocationId(); locationId = fragmentArgs.getLocationId();
binding.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE);
setTitle(); setTitle();
setupPosts(); setupPosts();
fetchLocationModel(); fetchLocationModel();
@ -275,7 +295,6 @@ public class LocationFragment extends Fragment {
false, false,
false, false,
stories -> { stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) { if (stories != null && stories.length > 0) {
binding.mainLocationImage.setStoriesBorder(); binding.mainLocationImage.setStoriesBorder();
} }
@ -283,7 +302,7 @@ public class LocationFragment extends Fragment {
} }
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount()); 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)); postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 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.setVisibility(View.VISIBLE);
binding.locationUrl.setText(TextUtils.getSpannableUrl(url)); 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() { private void fetchPosts() {

View File

@ -27,7 +27,6 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -58,7 +57,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
private static AsyncTask<?, ?, ?> currentlyExecuting; private static AsyncTask<?, ?, ?> currentlyExecuting;
private PostsAdapter postsAdapter; private PostsAdapter postsAdapter;
private boolean hasNextPage; private boolean hasNextPage;
private boolean autoloadPosts;
private FragmentSavedBinding binding; private FragmentSavedBinding binding;
private String username; private String username;
private String endCursor; private String endCursor;
@ -107,17 +105,16 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
final List<PostModel> current = postsViewModel.getList().getValue(); final List<PostModel> current = postsViewModel.getList().getValue();
if (result != null && result.length > 0) { if (result != null && !result.isEmpty()) {
final List<PostModel> resultList = Arrays.asList(result);
if (current == null) { if (current == null) {
postsViewModel.getList().postValue(resultList); postsViewModel.getList().postValue(result);
} else { } else {
final List<PostModel> currentCopy = new ArrayList<>(current); final List<PostModel> currentCopy = new ArrayList<>(current);
currentCopy.addAll(resultList); currentCopy.addAll(result);
postsViewModel.getList().postValue(currentCopy); postsViewModel.getList().postValue(currentCopy);
} }
binding.mainPosts.post(() -> { binding.mainPosts.post(() -> {
@ -125,11 +122,11 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
binding.mainPosts.setVisibility(View.VISIBLE); 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) { if (model != null) {
endCursor = model.getEndCursor(); endCursor = model.getEndCursor();
hasNextPage = model.hasNextPage(); hasNextPage = model.hasNextPage();
if (autoloadPosts && hasNextPage) { if (hasNextPage) {
fetchPosts(); fetchPosts();
} else { } else {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
@ -246,7 +243,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!autoloadPosts && hasNextPage) { if (hasNextPage) {
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts(); fetchPosts();
endCursor = null; endCursor = null;
@ -258,7 +255,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
private void fetchPosts() { private void fetchPosts() {
stopCurrentExecutor(); stopCurrentExecutor();
final AsyncTask<Void, Void, PostModel[]> asyncTask; final AsyncTask<Void, Void, List<PostModel>> asyncTask;
switch (type) { switch (type) {
case LIKED: case LIKED:
asyncTask = new iLikedFetcher(endCursor, postsFetchListener); asyncTask = new iLikedFetcher(endCursor, postsFetchListener);

View File

@ -2,7 +2,6 @@ package awais.instagrabber.fragments.main;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -30,8 +29,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
@ -42,9 +39,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 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.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.ProfileNavGraphDirections;
@ -71,6 +71,7 @@ import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.models.enums.StoryViewerChoice; import awais.instagrabber.models.enums.StoryViewerChoice;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
@ -111,11 +112,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private StoryModel[] storyModels; private StoryModel[] storyModels;
private boolean hasNextPage; private boolean hasNextPage;
private String endCursor; private String endCursor;
private AsyncTask<Void, Void, PostModel[]> currentlyExecuting; private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private MenuItem favMenuItem;
private boolean isPullToRefresh; private boolean isPullToRefresh;
private HighlightsAdapter highlightsAdapter; private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel; private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem;
private MenuItem restrictMenuItem;
private final Runnable usernameSettingRunnable = () -> { private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -161,11 +163,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null || result.length <= 0) { if (result == null || result.isEmpty()) {
binding.privatePage1.setImageResource(R.drawable.ic_cancel); binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc); binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE); binding.privatePage.setVisibility(View.VISIBLE);
@ -175,15 +177,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final List<PostModel> postModels = postsViewModel.getList().getValue(); final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels); : new ArrayList<>(postModels);
final List<PostModel> resultList = Arrays.asList(result);
if (isPullToRefresh) { if (isPullToRefresh) {
finalList = resultList; finalList = result;
isPullToRefresh = false; isPullToRefresh = false;
} else { } else {
finalList.addAll(resultList); finalList.addAll(result);
} }
postsViewModel.getList().postValue(finalList); postsViewModel.getList().postValue(finalList);
final PostModel lastPostModel = result[result.length - 1]; final PostModel lastPostModel = result.get(result.size() - 1);
if (lastPostModel == null) return; if (lastPostModel == null) return;
endCursor = lastPostModel.getEndCursor(); endCursor = lastPostModel.getEndCursor();
hasNextPage = lastPostModel.hasNextPage(); hasNextPage = lastPostModel.hasNextPage();
@ -262,7 +263,81 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.profile_menu, menu); 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<FriendshipRepoRestrictRootResponse>() {
@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<FriendshipRepoChangeRootResponse>() {
@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<FriendshipRepoChangeRootResponse>() {
@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 @Override
@ -340,14 +415,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new ProfileFetcher(username.substring(1), profileModel -> { new ProfileFetcher(username.substring(1), profileModel -> {
if (getContext() == null) return; if (getContext() == null) return;
this.profileModel = profileModel; this.profileModel = profileModel;
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); // final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
final boolean isSelf = isLoggedIn // final boolean isSelf = isLoggedIn
&& profileModel != null // && profileModel != null
&& userIdFromCookie != null // && userIdFromCookie != null
&& userIdFromCookie.equals(profileModel.getId()); // && userIdFromCookie.equals(profileModel.getId());
if (favMenuItem != null) { // if (favMenuItem != null) {
favMenuItem.setVisible(isSelf); // favMenuItem.setVisible(isSelf);
} // }
setProfileDetails(); setProfileDetails();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -385,7 +460,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else binding.highlightsList.setVisibility(View.GONE); } else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.ALOINSTAGRAM.getValue())) { } 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<List<StoryModel>>() { aloService.getUserStory(profileId, profileModel.getUsername(), false, new ServiceCallback<List<StoryModel>>() {
@Override @Override
public void onSuccess(final List<StoryModel> result) { public void onSuccess(final List<StoryModel> result) {
@ -402,15 +477,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}); });
} }
if (isLoggedIn) {
final String myId = CookieUtils.getUserIdFromCookie(cookie); final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) {
if (profileId.equals(myId)) { if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE); binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE); binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE); binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnSaved.setText(R.string.saved); binding.btnSaved.setText(R.string.saved);
ViewCompat.setBackgroundTintList(binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
} else { } else {
binding.btnTagged.setVisibility(View.GONE); binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.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); binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) { if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow); binding.btnFollow.setText(R.string.unfollow);
ViewCompat.setBackgroundTintList(binding.btnFollow, binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
} else if (profileModel.getRequested()) { } else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel); binding.btnFollow.setText(R.string.cancel);
ViewCompat.setBackgroundTintList(binding.btnFollow, binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
} else { } else {
binding.btnFollow.setText(R.string.follow); binding.btnFollow.setText(R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollow, binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background)));
} }
binding.btnRestrict.setVisibility(View.VISIBLE); if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) { if (profileModel.getRestricted()) {
binding.btnRestrict.setText(R.string.unrestrict); restrictMenuItem.setTitle(R.string.unrestrict);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background)));
} else { } else {
binding.btnRestrict.setText(R.string.restrict); restrictMenuItem.setTitle(R.string.restrict);
ViewCompat.setBackgroundTintList(binding.btnRestrict, }
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
} }
binding.btnBlock.setVisibility(View.VISIBLE);
binding.btnTagged.setVisibility(View.VISIBLE); binding.btnTagged.setVisibility(View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) { if (profileModel.getBlocked()) {
binding.btnBlock.setText(R.string.unblock); blockMenuItem.setTitle(R.string.unblock);
ViewCompat.setBackgroundTintList(binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background)));
} else { } else {
binding.btnBlock.setText(R.string.block); blockMenuItem.setTitle(R.string.block);
ViewCompat.setBackgroundTintList(binding.btnBlock, }
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_red_background)));
} }
} }
} else { } else {
if (Utils.dataBox.getFavorite(username) != null) { if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
binding.btnFollow.setText(R.string.unfavorite_short); restrictMenuItem.setVisible(true);
ViewCompat.setBackgroundTintList(binding.btnFollow, if (profileModel.getRestricted()) {
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); restrictMenuItem.setTitle(R.string.unrestrict);
} else { } else {
binding.btnFollow.setText(R.string.favorite_short); restrictMenuItem.setTitle(R.string.restrict);
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 (!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()); binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
final long followersCount = profileModel.getFollowersCount(); final long followersCount = profileModel.getFollowersCount();
@ -570,23 +635,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie // final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie
// .equals(profileModel.getId()); // .equals(profileModel.getId());
final String favorite = Utils.dataBox.getFavorite(username);
binding.btnFollow.setOnClickListener(v -> { 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()) { if (profileModel.getFollowing() || profileModel.getRequested()) {
friendshipService.unfollow( friendshipService.unfollow(
userIdFromCookie, 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<FriendshipRepoRestrictRootResponse>() {
@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<FriendshipRepoChangeRootResponse>() {
@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<FriendshipRepoChangeRootResponse>() {
@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 -> { binding.btnSaved.setOnClickListener(v -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(), final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(),
profileModel.getId(), profileModel.getId(),
@ -728,6 +719,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .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() { private void showProfilePicDialog() {

View File

@ -107,6 +107,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
return true; 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 -> { generalCategory.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment(); final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections); NavHostFragment.findNavController(this).navigate(navDirections);

View File

@ -0,0 +1,7 @@
package awais.instagrabber.models.enums;
public enum FavoriteType {
USER,
HASHTAG,
LOCATION
}

View File

@ -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<String> follow(@Header("User-Agent") String userAgent,
@Header("x-csrftoken") String csrfToken,
@Path("tag") String tag);
@POST("/web/tags/unfollow/{tag}/")
Call<String> unfollow(@Header("User-Agent") String userAgent,
@Header("x-csrftoken") String csrfToken,
@Path("tag") String tag);
}

View File

@ -81,4 +81,5 @@ public final class Constants {
public static final String ACTION_SHOW_ACTIVITY = "show_activity"; public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String PREF_DARK_THEME = "dark_theme"; public static final String PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_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";
} }

View File

@ -6,15 +6,17 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat; import androidx.core.util.ObjectsCompat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
@ -24,12 +26,9 @@ public final class DataBox extends SQLiteOpenHelper {
private static DataBox sInstance; 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_COOKIES = "cookies";
private final static String TABLE_FAVORITES = "favorites"; 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_ID = "id";
private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; 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_FULL_NAME = "full_name";
private final static String KEY_PROFILE_PIC = "profile_pic"; 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) { public static synchronized DataBox getInstance(final Context context) {
if (sInstance == null) sInstance = new DataBox(context.getApplicationContext()); if (sInstance == null) sInstance = new DataBox(context.getApplicationContext());
@ -47,7 +51,6 @@ public final class DataBox extends SQLiteOpenHelper {
private DataBox(@Nullable final Context context) { private DataBox(@Nullable final Context context) {
super(context, "cookiebox.db", null, VERSION); super(context, "cookiebox.db", null, VERSION);
c = context;
} }
@Override @Override
@ -60,42 +63,128 @@ public final class DataBox extends SQLiteOpenHelper {
+ KEY_COOKIE + " TEXT," + KEY_COOKIE + " TEXT,"
+ KEY_FULL_NAME + " TEXT," + KEY_FULL_NAME + " TEXT,"
+ KEY_PROFILE_PIC + " 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!"); Log.i(TAG, "Tables created!");
} }
@Override @Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { 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)); Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion));
if (oldVersion == 1) { // 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_FULL_NAME + " TEXT");
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT");
case 2:
final List<FavoriteModel> 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)); Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion));
} }
public final void addFavorite(@NonNull final FavoriteModel favoriteModel) { @NonNull
final String query = favoriteModel.getQuery(); private List<FavoriteModel> backupOldFavorites(@NonNull final SQLiteDatabase db) {
final String display = favoriteModel.getDisplayName(); // 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<FavoriteModel> 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)) { if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) { try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction(); db.beginTransaction();
try { try {
final ContentValues values = new ContentValues(); addFavorite(db, model);
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);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite"); 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 { } finally {
db.endTransaction(); db.endTransaction();
} }
@ -103,23 +192,40 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
public final synchronized void delFavorite(@NonNull final FavoriteModel favoriteModel) { private void addFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) {
final String query = favoriteModel.getQuery(); 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)) { if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) { try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction(); db.beginTransaction();
try { try {
final int rowsDeleted = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", final int rowsDeleted = db.delete(TABLE_FAVORITES,
new String[]{query, Long.toString(favoriteModel.getDate())}); FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{query, type.toString()});
final int rowsDeletedTwo = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", if (rowsDeleted > 0) db.setTransactionSuccessful();
new String[]{query.replaceAll("@", ""), Long.toString(favoriteModel.getDate())});
if (rowsDeleted > 0 || rowsDeletedTwo > 0) db.setTransactionSuccessful();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite"); logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite");
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); }
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error", e);
}
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
@ -127,73 +233,74 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
@Nullable @NonNull
public final ArrayList<FavoriteModel> getAllFavorites() { public final List<FavoriteModel> getAllFavorites() {
ArrayList<FavoriteModel> favorites = null; final List<FavoriteModel> favorites = new ArrayList<>();
FavoriteModel tempFav;
final SQLiteDatabase db = getWritableDatabase(); final SQLiteDatabase db = getWritableDatabase();
try (final Cursor cursor = db.rawQuery("SELECT "
try (final Cursor cursor = db.rawQuery("SELECT query_text, date_added, query_display FROM favorites ORDER BY date_added DESC", null)) { + 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()) { if (cursor != null && cursor.moveToFirst()) {
db.beginTransaction(); db.beginTransaction();
favorites = new ArrayList<>(); FavoriteModel tempFav;
do { do {
tempFav = new FavoriteModel( FavoriteType type = null;
(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
);
if (cursor.getString(2) == null) {
try { try {
final ContentValues values = new ContentValues(); type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
values.put(KEY_DATE_ADDED, tempFav.getDate()); } catch (IllegalArgumentException ignored) {}
values.put(KEY_QUERY_TEXT, tempFav.getQuery()); tempFav = new FavoriteModel(
values.put(KEY_QUERY_DISPLAY, tempFav.getDisplayName()); cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{tempFav.getQuery()}); type,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
if (rows != 1) cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
db.insertOrThrow(TABLE_FAVORITES, null, values); new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
} 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); favorites.add(tempFav);
} while (cursor.moveToNext()); } while (cursor.moveToNext());
db.endTransaction(); 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) { } catch (final Exception e) {
if (logCollector != null) Log.e(TAG, "", e);
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);
} }
}
return favorites; 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(); try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE " final Cursor cursor = db.rawQuery("SELECT "
+ KEY_QUERY_TEXT + "='" + query + "' ORDER BY date_added DESC", null)) { + 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()) { 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; return null;
} }
@ -399,31 +506,80 @@ public final class DataBox extends SQLiteOpenHelper {
} }
public static class FavoriteModel { public static class FavoriteModel {
private final String query, displayName; private final int id;
private final long date; 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.query = query;
this.date = date; this.type = type;
this.displayName = displayName; this.displayName = displayName;
this.picUrl = picUrl;
this.dateAdded = dateAdded;
}
public int getId() {
return id;
} }
public String getQuery() { public String getQuery() {
return query; return query;
} }
public FavoriteType getType() {
return type;
}
public String getDisplayName() { public String getDisplayName() {
return displayName; return displayName;
} }
public long getDate() { public String getPicUrl() {
return date; 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 @NonNull
@Override @Override
public String toString() { public String toString() {
return query; return "FavoriteModel{" +
"id=" + id +
", query='" + query + '\'' +
", type=" + type +
", displayName='" + displayName + '\'' +
", picUrl='" + picUrl + '\'' +
", dateAdded=" + dateAdded +
'}';
} }
} }
} }

View File

@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
@ -35,6 +36,8 @@ import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public final class ExportImportUtils { public final class ExportImportUtils {
private static final String TAG = "ExportImportUtils";
public static final int FLAG_COOKIES = 1; public static final int FLAG_COOKIES = 1;
public static final int FLAG_FAVORITES = 1 << 1; public static final int FLAG_FAVORITES = 1 << 1;
public static final int FLAG_SETTINGS = 1 << 2; public static final int FLAG_SETTINGS = 1 << 2;
@ -60,7 +63,7 @@ public final class ExportImportUtils {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
} else { } else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING); 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 (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass"); 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); } else if (fetchListener != null) fetchListener.onResult(false);
} }
@ -111,7 +114,7 @@ public final class ExportImportUtils {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
} else } else
@ -129,7 +132,7 @@ public final class ExportImportUtils {
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); 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(); final int favsLen = favs.length();
for (int i = 0; i < favsLen; ++i) { for (int i = 0; i < favsLen; ++i) {
final JSONObject favsObject = favs.getJSONObject(i); final JSONObject favsObject = favs.getJSONObject(i);
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"), // Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"),
favsObject.getLong("d"), // favsObject.getLong("d"),
favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q"))); // favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q")));
} }
} }
@ -183,7 +186,7 @@ public final class ExportImportUtils {
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "saveToSettings"); 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(); result = jsonObject.toString();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); 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; return result;
} }
@ -249,7 +252,7 @@ public final class ExportImportUtils {
} catch (final Exception e) { } catch (final Exception e) {
result = null; result = null;
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getSettings"); 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; String result = null;
if (Utils.dataBox != null) { if (Utils.dataBox != null) {
try { try {
final ArrayList<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites(); final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
final int allFavoritesSize; final int allFavoritesSize;
if (allFavorites != null && (allFavoritesSize = allFavorites.size()) > 0) { if ((allFavoritesSize = allFavorites.size()) > 0) {
final JSONArray jsonArray = new JSONArray(); final JSONArray jsonArray = new JSONArray();
for (int i = 0; i < allFavoritesSize; i++) { for (int i = 0; i < allFavoritesSize; i++) {
final DataBox.FavoriteModel favorite = allFavorites.get(i); final DataBox.FavoriteModel favorite = allFavorites.get(i);
final JSONObject jsonObject = new JSONObject(); final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery()); jsonObject.put("q", favorite.getQuery());
jsonObject.put("d", favorite.getDate()); jsonObject.put("d", favorite.getDateAdded().getTime());
jsonObject.put("s", favorite.getDisplayName()); jsonObject.put("s", favorite.getDisplayName());
jsonArray.put(jsonObject); jsonArray.put(jsonObject);
} }
@ -278,7 +281,7 @@ public final class ExportImportUtils {
} catch (final Exception e) { } catch (final Exception e) {
result = null; result = null;
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); 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; return result;
@ -305,7 +308,7 @@ public final class ExportImportUtils {
} }
} catch (final Exception e) { } catch (final Exception e) {
result = null; result = null;
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
} }
return result; return result;

View File

@ -57,7 +57,8 @@ public class NavigationExtensions {
return false; return false;
} }
String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); 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); fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag); Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragment == null) { if (fragment == null) {

View File

@ -6,7 +6,6 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -21,7 +20,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;

View File

@ -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<DataBox.FavoriteModel>> list;
public MutableLiveData<List<DataBox.FavoriteModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

View File

@ -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<Boolean> callback) {
final Call<String> request = webRepository.follow(Constants.USER_AGENT,
csrfToken,
tag);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
callback.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<String> 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<Boolean> callback) {
final Call<String> request = webRepository.unfollow(Constants.USER_AGENT,
csrfToken,
tag);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
callback.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<String> call, @NonNull final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
callback.onFailure(t);
}
});
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM9,4h2v5l-1,-0.75L9,9L9,4zM18,20L6,20L6,4h1v9l3,-2.25L13,13L13,4h5v16z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM15,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4zM9,18c0.22,-0.72 3.31,-2 6,-2 2.7,0 5.8,1.29 6,2L9,18zM6,15v-3h3v-2L6,10L6,7L4,7v3L1,10v2h3v3z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15,6c1.1,0 2,0.9 2,2 0,0.99 -0.73,1.82 -1.67,1.97l-2.31,-2.31C13.19,6.72 14.01,6 15,6m0,-2c-2.21,0 -4,1.79 -4,4 0,0.18 0.03,0.35 0.05,0.52l3.43,3.43c0.17,0.02 0.34,0.05 0.52,0.05 2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4zM16.69,14.16L22.53,20L23,20v-2c0,-2.14 -3.56,-3.5 -6.31,-3.84zM13.01,16.13L14.88,18L9,18c0.08,-0.24 0.88,-1.01 2.91,-1.57l1.1,-0.3M1.41,1.71L0,3.12l4,4L4,10L1,10v2h3v3h2v-3h2.88l2.51,2.51C9.19,15.11 7,16.3 7,18v2h9.88l4,4 1.41,-1.41L1.41,1.71zM6,10v-0.88l0.88,0.88L6,10z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM19,18h-4.83l-0.59,0.59L12,20.17l-1.59,-1.59 -0.58,-0.58L5,18L5,4h14v14zM12,11c1.65,0 3,-1.35 3,-3s-1.35,-3 -3,-3 -3,1.35 -3,3 1.35,3 3,3zM12,7c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM18,15.58c0,-2.5 -3.97,-3.58 -6,-3.58s-6,1.08 -6,3.58L6,17h12v-1.42zM8.48,15c0.74,-0.51 2.23,-1 3.52,-1s2.78,0.49 3.52,1L8.48,15z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M5.8 21L7.4 14L2 9.2L9.2 8.6L12 2L14.8 8.6L22 9.2L18.8 12H18C17.3 12 16.6 12.1 15.9 12.4L18.1 10.5L13.7 10.1L12 6.1L10.3 10.1L5.9 10.5L9.2 13.4L8.2 17.7L12 15.4L12.5 15.7C12.3 16.2 12.1 16.8 12.1 17.3L5.8 21M17 14V17H14V19H17V22H19V19H22V17H19V14H17Z" />
</vector>

View File

@ -0,0 +1,8 @@
<!-- drawable/star_check.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M5.8 21L7.4 14L2 9.2L9.2 8.6L12 2L14.8 8.6L22 9.2L18.8 12H18C14.9 12 12.4 14.3 12 17.3L5.8 21M17.8 21.2L22.6 16.4L21.3 15L17.7 18.6L16.2 17L15 18.2L17.8 21.2" />
</vector>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" > <shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/semi_transparent_black" /> <solid android:color="@color/black_a50" />
<padding <padding
android:left="2dp" android:left="2dp"
android:right="2dp" android:right="2dp"

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_outline_star_plus_24" android:state_checked="false" />
<item android:drawable="@drawable/ic_star_check_24" android:state_checked="true" />
<item android:drawable="@drawable/ic_outline_star_plus_24" />
</selector>

View File

@ -28,7 +28,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="none" app:layout_collapseMode="none"
app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface"
app:title="@string/app_name" app:title="@string/app_name"
tools:menu="@menu/main_menu" /> tools:menu="@menu/main_menu" />
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/semi_transparent_black"> android:background="@color/black_a50">
<awais.instagrabber.customviews.drawee.ZoomableDraweeView <awais.instagrabber.customviews.drawee.ZoomableDraweeView
android:id="@+id/imageViewer" android:id="@+id/imageViewer"

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/favorite_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_suggestion" />

View File

@ -17,47 +17,120 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll"> app:layout_scrollFlags="scroll">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tagInfoContainer" android:id="@+id/tagInfoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:padding="@dimen/profile_info_container_bottom_space">
android:padding="@dimen/profile_info_container_bottom_space"
android:visibility="visible">
<awais.instagrabber.customviews.CircularImageView <awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainHashtagImage" android:id="@+id/mainHashtagImage"
android:layout_width="@dimen/profile_picture_size" android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size" android:layout_height="@dimen/profile_picture_size"
android:adjustViewBounds="true" android:background="?selectableItemBackgroundBorderless"
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" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainTagPostCount" android:id="@+id/mainTagPostCount"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp" app:layout_constraintBottom_toTopOf="@id/btnFollowTag"
tools:text="35\nPosts" /> app:layout_constraintStart_toEndOf="@id/mainHashtagImage"
app:layout_constraintTop_toTopOf="@id/mainHashtagImage"
tools:text="35 Posts" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.chip.Chip
android:id="@+id/btnFollowTag" android:id="@+id/btnFollowTag"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_marginStart="8dp"
android:layout_weight="2" android:layout_marginLeft="8dp"
android:text="@string/follow" android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="20sp"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" /> app:chipBackgroundColor="@null"
</LinearLayout> 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" />
<com.google.android.material.chip.Chip
android:id="@+id/fav_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/add_to_favorites"
android:visibility="gone"
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_star_plus_24"
app:chipIconTint="@color/yellow_800"
app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"
app:layout_constraintStart_toEndOf="@id/btnFollowTag"
app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"
app:rippleColor="@color/yellow_400" />
<!--<com.google.android.material.chip.Chip-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintStart_toEndOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintTop_toBottomOf="@id/mainTagPostCount" />-->
<!--<com.google.android.material.button.MaterialButton-->
<!-- android:id="@+id/btnFollowTag"-->
<!-- style="@style/Widget.MaterialComponents.Button.TextButton"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="0dp"-->
<!-- android:text="@string/follow"-->
<!-- android:textColor="@color/deep_purple_200"-->
<!-- android:visibility="gone"-->
<!-- app:icon="@drawable/ic_outline_person_add_24"-->
<!-- app:iconGravity="top"-->
<!-- app:iconTint="@color/deep_purple_200"-->
<!-- app:layout_constraintBottom_toTopOf="@id/fav_cb"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/mainTagPostCount"-->
<!-- app:layout_constraintTop_toTopOf="@id/mainHashtagImage"-->
<!-- tools:visibility="visible" />-->
<!--<CheckBox-->
<!-- android:id="@+id/fav_cb"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:button="@drawable/sl_favourite_24"-->
<!-- android:paddingStart="8dp"-->
<!-- android:paddingEnd="8dp"-->
<!-- android:text="Add to favorites"-->
<!-- android:visibility="gone"-->
<!-- app:buttonTint="@color/yellow_800"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintTop_toBottomOf="@id/btnFollowTag"-->
<!-- tools:visibility="gone" />-->
<!--<ProgressBar-->
<!-- android:id="@+id/fav_progress"-->
<!-- style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"-->
<!-- android:layout_width="24dp"-->
<!-- android:layout_height="24dp"-->
<!-- android:paddingStart="8dp"-->
<!-- android:paddingEnd="8dp"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintStart_toStartOf="@id/fav_cb"-->
<!-- app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"-->
<!-- tools:visibility="gone" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -17,101 +17,128 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"> app:layout_scrollFlags="scroll|exitUntilCollapsed">
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/locInfoContainer" android:id="@+id/locInfoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@null" android:background="@null"
android:orientation="vertical" android:padding="8dp">
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/locInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView <awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainLocationImage" android:id="@+id/mainLocationImage"
android:layout_width="@dimen/profile_picture_size" android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size" android:layout_height="@dimen/profile_picture_size"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
app:actualImageScaleType="fitCenter" /> app:actualImageScaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@id/mainLocPostCount"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@mipmap/ic_launcher" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainLocPostCount" android:id="@+id/mainLocPostCount"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_marginStart="12dp" android:gravity="center_vertical"
android:layout_marginLeft="12dp" android:maxLines="1"
android:layout_marginEnd="12dp" android:paddingStart="12dp"
android:layout_marginRight="12dp" android:paddingEnd="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp" app:layout_constraintBottom_toTopOf="@id/btnMap"
tools:text="35\nPosts" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/mainLocationImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="35 Posts" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.chip.Chip
android:id="@+id/btnMap" android:id="@+id/btnMap"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_marginStart="8dp"
android:layout_weight="2" android:layout_marginLeft="8dp"
android:text="@string/map" android:text="@string/map"
android:textColor="@color/btn_green_text_color" app:chipBackgroundColor="@null"
android:textSize="20sp" app:chipIcon="@drawable/ic_outline_map_24"
android:visibility="gone" app:chipIconTint="@color/green_500"
app:backgroundTint="@color/btn_green_background" /> app:layout_constraintBottom_toTopOf="@id/locationFullName"
</LinearLayout> app:layout_constraintStart_toEndOf="@id/mainLocationImage"
app:layout_constraintTop_toBottomOf="@id/mainLocPostCount"
app:rippleColor="@color/grey_500"
tools:visibility="visible" />
<com.google.android.material.chip.Chip
android:id="@+id/fav_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/add_to_favorites"
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_star_plus_24"
app:chipIconTint="@color/yellow_800"
app:layout_constraintBottom_toBottomOf="@id/mainLocationImage"
app:layout_constraintStart_toEndOf="@id/btnMap"
app:layout_constraintTop_toBottomOf="@id/mainLocPostCount"
app:rippleColor="@color/yellow_400" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/locationFullName" android:id="@+id/locationFullName"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/locInfo"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="10dp" android:paddingStart="8dp"
android:paddingLeft="10dp" android:paddingLeft="8dp"
android:paddingEnd="10dp" android:paddingTop="4dp"
android:paddingRight="10dp" android:paddingEnd="8dp"
android:paddingRight="8dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/locationBiography"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainLocationImage"
tools:text="OUR HOUSE" /> tools:text="OUR HOUSE" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationBiography" android:id="@+id/locationBiography"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/locationFullName" android:layout_below="@id/locationFullName"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:paddingStart="10dp" android:paddingStart="8dp"
android:paddingLeft="10dp" android:paddingLeft="8dp"
android:paddingEnd="10dp" android:paddingEnd="8dp"
android:paddingRight="10dp" android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone" android:visibility="gone"
tools:text="IN THE MIDDLE OF OUR STREET" /> 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" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationUrl" android:id="@+id/locationUrl"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/locationBiography" android:layout_below="@id/locationBiography"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="10dp" android:paddingStart="8dp"
android:paddingLeft="10dp" android:paddingLeft="8dp"
android:paddingEnd="10dp" android:paddingEnd="8dp"
android:paddingRight="10dp" android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone" android:visibility="gone"
tools:text="https://austinhuang.me/" /> app:layout_constraintBottom_toBottomOf="parent"
</RelativeLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/locationBiography"
tools:text="https://austinhuang.me/"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -79,8 +79,10 @@
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingTop="8dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:paddingBottom="8dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textStyle="bold" android:textStyle="bold"
@ -102,15 +104,34 @@
app:srcCompat="@drawable/verified" app:srcCompat="@drawable/verified"
tools:visibility="visible" /> tools:visibility="visible" />
<CheckBox
android:id="@+id/fav_cb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@drawable/sl_favourite_24"
android:visibility="gone"
app:buttonTint="@color/yellow_800"
app:layout_constraintBaseline_toBaselineOf="@id/mainFullName"
app:layout_constraintBottom_toTopOf="@id/mainBiography"
app:layout_constraintStart_toEndOf="@id/isVerified" />
<ProgressBar
android:id="@+id/fav_progress"
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/mainFullName"
app:layout_constraintStart_toEndOf="@id/isVerified"
app:layout_constraintTop_toTopOf="@id/mainFullName"
tools:visibility="gone" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/mainBiography" android:id="@+id/mainBiography"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/mainUrl" app:layout_constraintBottom_toTopOf="@id/mainUrl"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -124,10 +145,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/mainBiography" android:layout_below="@id/mainBiography"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="8dp" android:padding="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -135,97 +153,77 @@
app:layout_constraintTop_toBottomOf="@id/mainBiography" app:layout_constraintTop_toBottomOf="@id/mainBiography"
tools:text="https://austinhuang.me/" tools:text="https://austinhuang.me/"
tools:textColor="@android:color/holo_blue_dark" tools:textColor="@android:color/holo_blue_dark"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnFollow" android:id="@+id/btnFollow"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/follow" android:text="@string/follow"
android:textColor="@color/btn_pink_text_color" android:textColor="@color/deep_purple_200"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" app:icon="@drawable/ic_outline_person_add_24"
app:layout_constraintBottom_toTopOf="@id/button_barrier" app:iconGravity="top"
app:layout_constraintEnd_toStartOf="@id/btnRestrict" app:iconTint="@color/deep_purple_200"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnTagged"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainUrl" app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/purple_200"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnRestrict" android:id="@+id/btnTagged"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/restrict" android:text="@string/tagged"
android:textColor="@color/btn_orange_text_color" android:textColor="@color/deep_orange_600"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" app:icon="@drawable/ic_outline_person_pin_24"
app:layout_constraintBottom_toTopOf="@id/button_barrier" app:iconGravity="top"
app:layout_constraintEnd_toStartOf="@id/btnBlock" app:iconTint="@color/deep_orange_600"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnSaved"
app:layout_constraintStart_toEndOf="@id/btnFollow" app:layout_constraintStart_toEndOf="@id/btnFollow"
app:layout_constraintTop_toBottomOf="@id/mainUrl" app:layout_constraintTop_toBottomOf="@id/mainUrl"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnBlock"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/block"
android:textColor="@color/btn_red_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_red_background"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnRestrict"
app:layout_constraintTop_toBottomOf="@id/mainUrl"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/button_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="btnFollow, btnRestrict, btnBlock" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnTagged"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/tagged"
android:textColor="@color/btn_blue_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_blue_background"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnSaved"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/button_barrier"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaved" android:id="@+id/btnSaved"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/saved" android:text="@string/saved"
android:textColor="@color/btn_orange_text_color" android:textColor="@color/blue_700"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" app:icon="@drawable/ic_outline_class_24"
app:iconGravity="top"
app:iconTint="@color/blue_700"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier" app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnLiked" app:layout_constraintEnd_toStartOf="@id/btnLiked"
app:layout_constraintStart_toEndOf="@id/btnTagged" app:layout_constraintStart_toEndOf="@id/btnTagged"
app:layout_constraintTop_toTopOf="@id/button_barrier" app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/blue_A400"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnLiked" android:id="@+id/btnLiked"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/liked" android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color" android:textColor="@color/red_600"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_lightpink_background" app:icon="@drawable/ic_like"
app:iconGravity="top"
app:iconTint="@color/red_600"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier" app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnSaved" app:layout_constraintStart_toEndOf="@id/btnSaved"
app:layout_constraintTop_toTopOf="@id/button_barrier" app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/red_300"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="?colorAccent"
android:textStyle="bold"
tools:text="HEADERS" />

View File

@ -18,7 +18,7 @@
android:id="@+id/media_list" android:id="@+id/media_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:background="@color/semi_transparent_black" /> tools:background="@color/black_a50" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:layout_width="24dp" android:layout_width="24dp"

View File

@ -22,10 +22,10 @@
android:layout_weight="1" android:layout_weight="1"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@null" android:background="@null"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:gravity="center"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:weightSum="2"> android:weightSum="2">
@ -36,7 +36,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/feed_text_primary_color"
tools:text="username" /> tools:text="username" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
@ -46,7 +45,6 @@
android:layout_below="@+id/title" android:layout_below="@+id/title"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textColor="@color/feed_text_primary_color"
android:textSize="15sp" android:textSize="15sp"
android:visibility="visible" android:visibility="visible"
tools:text="location" /> tools:text="location" />

View File

@ -7,7 +7,10 @@
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="8dp"> android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/ivProfilePic" android:id="@+id/ivProfilePic"
@ -26,8 +29,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="8dp" android:paddingStart="16dp"
android:paddingLeft="8dp" android:paddingLeft="16dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
@ -42,10 +45,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="8dp" android:paddingStart="16dp"
android:paddingLeft="8dp" android:paddingLeft="16dp"
android:paddingEnd="8dp" android:paddingEnd="0dp"
android:paddingRight="8dp" android:paddingRight="0dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body2"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -1,12 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <!--<item-->
android:id="@+id/favourites" <!-- android:id="@+id/favourites"-->
android:enabled="true" <!-- android:enabled="true"-->
android:icon="@drawable/ic_star_24" <!-- android:icon="@drawable/ic_star_24"-->
android:title="@string/title_favorites" <!-- android:title="@string/title_favorites"-->
android:visible="false" <!-- android:visible="false"-->
app:showAsAction="ifRoom" /> <!-- app:showAsAction="ifRoom" />-->
<item
android:id="@+id/block"
android:icon="@drawable/ic_block_24"
android:title="@string/block"
android:visible="false"
app:showAsAction="never" />
<item
android:id="@+id/restrict"
android:icon="@drawable/ic_highlight_off_24"
android:title="@string/restrict"
android:visible="false"
app:showAsAction="never" />
</menu> </menu>

View File

@ -34,6 +34,24 @@
app:nullable="true" /> app:nullable="true" />
</action> </action>
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<action
android:id="@+id/action_global_locationFragment"
app:destination="@id/location_nav_graph">
<argument
android:name="locationId"
app:argType="string"
app:nullable="false" />
</action>
<fragment <fragment
android:id="@+id/morePreferencesFragment" android:id="@+id/morePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment" android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment"
@ -47,6 +65,9 @@
<action <action
android:id="@+id/action_morePreferencesFragment_to_notificationsViewer" android:id="@+id/action_morePreferencesFragment_to_notificationsViewer"
app:destination="@id/notificationsViewer" /> app:destination="@id/notificationsViewer" />
<action
android:id="@+id/action_morePreferencesFragment_to_favoritesFragment"
app:destination="@id/favoritesFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/settingsPreferencesFragment" android:id="@+id/settingsPreferencesFragment"
@ -69,4 +90,8 @@
android:id="@+id/themePreferencesFragment" android:id="@+id/themePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.ThemePreferencesFragment" android:name="awais.instagrabber.fragments.settings.ThemePreferencesFragment"
android:label="@string/theme_settings" /> android:label="@string/theme_settings" />
<fragment
android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites" />
</navigation> </navigation>

View File

@ -28,15 +28,13 @@
<color name="btn_lightorange_background">#FFBB00</color> <color name="btn_lightorange_background">#FFBB00</color>
<color name="btn_lightorange_text_color">#FF000000</color> <color name="btn_lightorange_text_color">#FF000000</color>
<color name="feed_text_primary_color">@color/text_color_light</color>
<color name="dm_profile_button_color">#efefef</color> <color name="dm_profile_button_color">#efefef</color>
<color name="semi_transparent_black">#80000000</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="black">#000000</color> <color name="black">#000000</color>
<color name="black_800">#121212</color> <color name="black_800">#121212</color>
<color name="black_a50">#80000000</color>
<color name="grey_50">#FAFAFA</color> <color name="grey_50">#FAFAFA</color>
<color name="grey_100">#F5F5F5</color> <color name="grey_100">#F5F5F5</color>
@ -83,7 +81,65 @@
<color name="purple_200">#bb86fc</color> <color name="purple_200">#bb86fc</color>
<color name="purple_600">#4b01d0</color> <color name="purple_600">#4b01d0</color>
<color name="red_200">#cf6679</color> <color name="deep_purple_50">#EDE7F6</color>
<color name="deep_purple_100">#D1C4E9</color>
<color name="deep_purple_200">#B39DDB</color>
<color name="deep_purple_300">#9575CD</color>
<color name="deep_purple_400">#7E57C2</color>
<color name="deep_purple_500">#673AB7</color>
<color name="deep_purple_600">#5E35B1</color>
<color name="deep_purple_700">#512DA8</color>
<color name="deep_purple_800">#4527A0</color>
<color name="deep_purple_900">#311B92</color>
<color name="deep_purple_A100">#B388FF</color>
<color name="deep_purple_A200">#7C4DFF</color>
<color name="deep_purple_A400">#651FFF</color>
<color name="deep_purple_A700">#6200EA</color>
<color name="red_50">#FFEBEE</color>
<color name="red_100">#FFCDD2</color>
<color name="red_200">#EF9A9A</color>
<color name="red_300">#E57373</color>
<color name="red_400">#EF5350</color>
<color name="red_500">#F44336</color>
<color name="red_600">#E53935</color>
<color name="red_700">#D32F2F</color>
<color name="red_800">#C62828</color>
<color name="red_900">#B71C1C</color>
<color name="red_A100">#FF8A80</color>
<color name="red_A200">#FF5252</color>
<color name="red_A400">#FF1744</color>
<color name="red_A700">#D50000</color>
<color name="deep_orange_50">#FBE9E7</color>
<color name="deep_orange_100">#FFCCBC</color>
<color name="deep_orange_200">#FFAB91</color>
<color name="deep_orange_300">#FF8A65</color>
<color name="deep_orange_400">#FF7043</color>
<color name="deep_orange_500">#FF5722</color>
<color name="deep_orange_600">#F4511E</color>
<color name="deep_orange_700">#E64A19</color>
<color name="deep_orange_800">#D84315</color>
<color name="deep_orange_900">#BF360C</color>
<color name="deep_orange_A100">#FF9E80</color>
<color name="deep_orange_A200">#FF6E40</color>
<color name="deep_orange_A400">#FF3D00</color>
<color name="deep_orange_A700">#DD2C00</color>
<color name="yellow_50">#FFFDE7</color>
<color name="yellow_100">#FFF9C4</color>
<color name="yellow_200">#FFF59D</color>
<color name="yellow_300">#FFF176</color>
<color name="yellow_400">#FFEE58</color>
<color name="yellow_500">#FFEB3B</color>
<color name="yellow_600">#FDD835</color>
<color name="yellow_700">#FBC02D</color>
<color name="yellow_800">#F9A825</color>
<color name="yellow_900">#F57F17</color>
<color name="yellow_A100">#FFFF8D</color>
<color name="yellow_A200">#FFFF00</color>
<color name="yellow_A400">#FFEA00</color>
<color name="yellow_A700">#FFD600</color>
<!-- Barinsta Theme colors --> <!-- Barinsta Theme colors -->
<color name="barinstaColorPrimary">#a86735</color> <color name="barinstaColorPrimary">#a86735</color>

View File

@ -58,6 +58,7 @@
<string name="select_language">Language</string> <string name="select_language">Language</string>
<string name="what_to_do_dialog">What to do?</string> <string name="what_to_do_dialog">What to do?</string>
<string name="main_posts_count">%s\nPosts</string> <string name="main_posts_count">%s\nPosts</string>
<string name="main_posts_count_inline">%s Posts</string>
<string name="main_posts_followers">%s\nFollowers</string> <string name="main_posts_followers">%s\nFollowers</string>
<string name="main_posts_following">%s\nFollowing</string> <string name="main_posts_following">%s\nFollowing</string>
<string name="post_viewer_video_post">Video post</string> <string name="post_viewer_video_post">Video post</string>
@ -146,7 +147,7 @@
<string name="time_settings_swap_time">Swap Time and Date positions</string> <string name="time_settings_swap_time">Swap Time and Date positions</string>
<string name="quick_access_info_dialog">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 &gt; Login] to add account to the list!\n\nNote 2: Log out of the current account and then log into the other account.</string> <string name="quick_access_info_dialog">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 &gt; Login] to add account to the list!\n\nNote 2: Log out of the current account and then log into the other account.</string>
<string name="quick_access_cannot_delete_curr">Cannot delete currently in use account</string> <string name="quick_access_cannot_delete_curr">Cannot delete currently in use account</string>
<string name="quick_access_confirm_delete">Are you sure you want to delete %s?</string> <string name="quick_access_confirm_delete">Are you sure you want to delete \'%s\'?</string>
<string name="profile_viewer_imageinfo">Width: %d\nHeight: %d</string> <string name="profile_viewer_imageinfo">Width: %d\nHeight: %d</string>
<string name="profile_viewer_colordepth_prefix">\nColor depth:</string> <string name="profile_viewer_colordepth_prefix">\nColor depth:</string>
<string name="profile_endpoint">Select profile picture endpoint\n(Does not affect hashtags)</string> <string name="profile_endpoint">Select profile picture endpoint\n(Does not affect hashtags)</string>
@ -283,4 +284,11 @@
<string name="dark_theme_settings">Dark theme</string> <string name="dark_theme_settings">Dark theme</string>
<string name="light_barinsta_theme">Barinsta</string> <string name="light_barinsta_theme">Barinsta</string>
<string name="dark_material_dark_theme">Material Dark</string> <string name="dark_material_dark_theme">Material Dark</string>
<string name="added_to_favs">Added to Favorites</string>
<string name="add_to_favorites">Add to favorites</string>
<string name="accounts">Accounts</string>
<string name="hashtags">Hashtags</string>
<string name="locations">Locations</string>
<string name="unknown">Unknown</string>
<string name="removed_from_favs">Removed from Favourites</string>
</resources> </resources>

View File

@ -108,4 +108,24 @@
<item name="android:background">?colorSurface</item> <item name="android:background">?colorSurface</item>
<item name="android:windowBackground">?attr/colorSurface</item> <item name="android:windowBackground">?attr/colorSurface</item>
</style> </style>
<style name="ThemeOverlay.MaterialComponents.MaterialAlertDialog.Light" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<!--<item name="colorPrimary">?attr/colorPrimaryDark</item>-->
<!--<item name="colorSecondary">?attr/colorSecondaryVariant</item>-->
<!--<item name="colorSurface">@color/shrine_pink_light</item>-->
<!--<item name="colorOnSurface">@color/shrine_pink_900</item>-->
<!--<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.App.Title.Text</item>-->
<!--<item name="colorPrimary">?attr/colorPrimaryDark</item>-->
<item name="buttonBarPositiveButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item>
<item name="buttonBarNeutralButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item>
<item name="buttonBarNegativeButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item>
</style>
<style name="Widget.MaterialComponents.Button.TextButton.Dialog.Light" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
<item name="materialThemeOverlay">@style/ThemeOverlay.MaterialComponents.Button.TextButton.Light</item>
</style>
<style name="ThemeOverlay.MaterialComponents.Button.TextButton.Light" parent="">
<item name="colorPrimary">?attr/colorPrimaryDark</item>
</style>
</resources> </resources>

View File

@ -19,6 +19,7 @@
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item> <item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
<item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.ActionBar</item> <item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.ActionBar</item>
<!--<item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.Primary</item>--> <!--<item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.Primary</item>-->
<item name="materialAlertDialogTheme">@style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.Light</item>
</style> </style>
<style name="AppTheme.Light.White" parent="AppTheme.Light"> <style name="AppTheme.Light.White" parent="AppTheme.Light">