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
.idea/git_toolbox_prj.xml
.idea/dbnavigator.xml

View File

@ -41,7 +41,7 @@ dependencies {
def nav_version = "2.3.0"
def preference_version = "1.1.1"
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation "androidx.appcompat:appcompat:$appcompat_version"

View File

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

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();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
if (feedModels[feedModels.length - 1] != null)
feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor);
final int length = feedModels.length;
if (length >= 1 && feedModels[length - 1] != null) {
feedModels[length - 1].setPageCursor(hasNextPage, endCursor);
}
result = feedModels;
}

View File

@ -8,6 +8,10 @@ import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
@ -21,6 +25,8 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
private static final String TAG = "HashtagFetcher";
private final FetchListener<HashtagModel> fetchListener;
private final String hashtag;
@ -35,12 +41,14 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
HashtagModel result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1").openConnection();
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1")
.openConnection();
conn.setUseCaches(true);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_HASHTAG);
final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_HASHTAG);
final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media");
if (timelineMedia.has("edges")) {
@ -53,13 +61,34 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
user.getString("profile_pic_url"),
timelineMedia.getLong("count"),
user.optBoolean("is_following"));
} else {
BufferedReader bufferedReader = null;
try {
final InputStream responseInputStream = conn.getErrorStream();
bufferedReader = new BufferedReader(new InputStreamReader(responseInputStream));
final StringBuilder builder = new StringBuilder();
for (String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
if (builder.length() != 0) {
builder.append("\n");
}
builder.append(line);
}
Log.d(TAG, "doInBackground: " + builder.toString());
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ignored) {
}
}
}
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
return result;

View File

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

View File

@ -42,7 +42,6 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
conn.connect();
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));
conn.disconnect();
@ -63,7 +62,7 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
suggestionModels.add(new SuggestionModel(false,
hashtag.getString(Constants.EXTRAS_NAME),
null,
hashtag.optString("profile_pic_url", defaultHashTagPic),
hashtag.optString("profile_pic_url", Constants.DEFAULT_HASH_TAG_PIC),
SuggestionType.TYPE_HASHTAG,
hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1)));
}

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
@ -28,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.Utils.logCollector;
public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
private final String endCursor;
private final FetchListener<PostModel[]> fetchListener;
public final class iLikedFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private static final String TAG = "iLikedFetcher";
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.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.fetchListener = fetchListener;
}
@Override
protected PostModel[] doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id="+endCursor;
protected List<PostModel> doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor;
PostModel[] result = null;
List<PostModel> result = new ArrayList<>();
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
@ -68,8 +72,7 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
}
final JSONArray edges = body.getJSONArray("items");
final PostModel[] models = new PostModel[edges.length()];
for (int i = 0; i < models.length; ++i) {
for (int i = 0; i < edges.length(); ++i) {
final JSONObject mediaNode = edges.getJSONObject(i);
final boolean isSlider = mediaNode.has("carousel_media_count");
@ -80,48 +83,57 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID),
isSlider
? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0))
: ResponseBodyUtils.getHighQualityImage(mediaNode),
isSlider
? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode),
final PostModel model = new PostModel(
itemType,
mediaNode.getString(Constants.EXTRAS_ID),
isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getHighQualityImage(mediaNode),
isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode),
mediaNode.getString("code"),
mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"),
mediaNode.getLong("taken_at"), true,
mediaNode.optBoolean("has_viewer_saved"), mediaNode.getLong("like_count"));
mediaNode.getLong("taken_at"),
true,
mediaNode.optBoolean("has_viewer_saved"),
mediaNode.getLong("like_count"));
result.add(model);
String username = mediaNode.getJSONObject("user").getString("username");
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? ("/" + username)
: ""));
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
}
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]);
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
}
if (models[models.length - 1] != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor);
result = models;
final int length = result.size();
if (length >= 1 && result.get(length - 1) != null) {
result.get(length - 1).setPageCursor(hasNextPage, endCursor);
}
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
}
return result;
}
@Override
protected void onPostExecute(final PostModel[] postModels) {
protected void onPostExecute(final List<PostModel> 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;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
@ -22,16 +21,18 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.R;
@ -49,21 +50,24 @@ import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class HashTagFragment extends Fragment {
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "HashTagFragment";
private MainActivity fragmentActivity;
@ -79,7 +83,8 @@ public class HashTagFragment extends Fragment {
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn;
private StoryModel[] storyModels;
private TagsService tagsService;
private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
@ -116,19 +121,27 @@ public class HashTagFragment extends Fragment {
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final PostModel[] result) {
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels);
finalList.addAll(Arrays.asList(result));
List<PostModel> finalList = postModels == null || postModels.isEmpty()
? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
finalList.addAll(result);
postsViewModel.getList().postValue(finalList);
PostModel model = null;
if (result.length != 0) {
model = result[result.length - 1];
if (!result.isEmpty()) {
model = result.get(result.size() - 1);
}
if (model == null) return;
endCursor = model.getEndCursor();
@ -141,6 +154,7 @@ public class HashTagFragment extends Fragment {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
tagsService = TagsService.getInstance();
}
@Nullable
@ -158,10 +172,18 @@ public class HashTagFragment extends Fragment {
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchHashtagModel();
}
@Override
public void onDestroy() {
super.onDestroy();
@ -257,7 +279,6 @@ public class HashTagFragment extends Fragment {
private void fetchPosts() {
stopCurrentExecutor();
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.swipeRefreshLayout.setRefreshing(true);
if (TextUtils.isEmpty(hashtag)) return;
currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener)
@ -266,29 +287,109 @@ public class HashTagFragment extends Fragment {
if (context == null) return;
if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) {
binding.mainHashtagImage.setStoriesBorder();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf(
ContextCompat.getColor(context, hashtagModel.getFollowing()
? R.color.btn_purple_background
: R.color.btn_pink_background)));
binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
? R.drawable.ic_outline_person_add_disabled_24
: R.drawable.ic_outline_person_add_24);
binding.btnFollowTag.setOnClickListener(v -> {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
binding.btnFollowTag.setClickable(false);
if (!hashtagModel.getFollowing()) {
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<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();
}
});
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.setText(Utils.dataBox.getFavorite(hashtag) != null
? R.string.unfavorite_short
: R.string.favorite_short);
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf(
ContextCompat.getColor(context, Utils.dataBox.getFavorite(hashtag) != null
? R.color.btn_purple_background
: R.color.btn_pink_background)));
binding.btnFollowTag.setVisibility(View.GONE);
}
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
final boolean isFav = favorite != null;
binding.favChip.setVisibility(View.VISIBLE);
binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24
: R.drawable.ic_outline_star_plus_24);
binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites);
binding.favChip.setOnClickListener(v -> {
final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
final boolean isFavorite = fav != null;
final String message;
if (isFavorite) {
Utils.dataBox.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
binding.favChip.setText(R.string.add_to_favorites);
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs);
} else {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(
-1,
hashtag.substring(1),
FavoriteType.HASHTAG,
hashtagModel.getName(),
null,
new Date()
));
binding.favChip.setText(R.string.favorite_short);
binding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
});
binding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
final String postCount = String.valueOf(hashtagModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, postCount));
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
binding.mainTagPostCount.setText(span);
@ -312,9 +413,7 @@ public class HashTagFragment extends Fragment {
if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler();
handler.postDelayed(() -> {
actionBar.setTitle(hashtag);
}, 200);
handler.postDelayed(() -> actionBar.setTitle(hashtag), 200);
}
}

View File

@ -27,10 +27,14 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.R;
@ -48,11 +52,12 @@ import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -62,7 +67,7 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class LocationFragment extends Fragment {
public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment";
private MainActivity fragmentActivity;
@ -78,7 +83,7 @@ public class LocationFragment extends Fragment {
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn;
private StoryModel[] storyModels;
private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
@ -119,20 +124,25 @@ public class LocationFragment extends Fragment {
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final PostModel[] result) {
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
finalList.addAll(Arrays.asList(result));
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList);
PostModel model = null;
if (result.length != 0) {
model = result[result.length - 1];
if (!result.isEmpty()) {
model = result.get(result.size() - 1);
}
if (model == null) return;
endCursor = model.getEndCursor();
@ -164,10 +174,18 @@ public class LocationFragment extends Fragment {
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchLocationModel();
}
@Override
public void onDestroy() {
super.onDestroy();
@ -182,6 +200,8 @@ public class LocationFragment extends Fragment {
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments());
locationId = fragmentArgs.getLocationId();
binding.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE);
setTitle();
setupPosts();
fetchLocationModel();
@ -275,7 +295,6 @@ public class LocationFragment extends Fragment {
false,
false,
stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) {
binding.mainLocationImage.setStoriesBorder();
}
@ -283,7 +302,7 @@ public class LocationFragment extends Fragment {
}
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count,
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline,
postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
@ -329,6 +348,40 @@ public class LocationFragment extends Fragment {
binding.locationUrl.setVisibility(View.VISIBLE);
binding.locationUrl.setText(TextUtils.getSpannableUrl(url));
}
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION);
final boolean isFav = favorite != null;
binding.favChip.setVisibility(View.VISIBLE);
binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24
: R.drawable.ic_outline_star_plus_24);
binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites);
binding.favChip.setOnClickListener(v -> {
final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION);
final boolean isFavorite = fav != null;
final String message;
if (isFavorite) {
Utils.dataBox.deleteFavorite(locationId, FavoriteType.LOCATION);
binding.favChip.setText(R.string.add_to_favorites);
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs);
} else {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(
-1,
locationId,
FavoriteType.LOCATION,
locationModel.getName(),
locationModel.getSdProfilePic(),
new Date()
));
binding.favChip.setText(R.string.favorite_short);
binding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
});
}
private void fetchPosts() {

View File

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

View File

@ -2,7 +2,6 @@ package awais.instagrabber.fragments.main;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
@ -30,8 +29,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@ -42,9 +39,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.ProfileNavGraphDirections;
@ -71,6 +71,7 @@ import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.models.enums.StoryViewerChoice;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
@ -111,11 +112,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private StoryModel[] storyModels;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<Void, Void, PostModel[]> currentlyExecuting;
private MenuItem favMenuItem;
private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private boolean isPullToRefresh;
private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem;
private MenuItem restrictMenuItem;
private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -161,11 +163,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final PostModel[] result) {
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null || result.length <= 0) {
if (result == null || result.isEmpty()) {
binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE);
@ -175,15 +177,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
final List<PostModel> resultList = Arrays.asList(result);
if (isPullToRefresh) {
finalList = resultList;
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(resultList);
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList);
final PostModel lastPostModel = result[result.length - 1];
final PostModel lastPostModel = result.get(result.size() - 1);
if (lastPostModel == null) return;
endCursor = lastPostModel.getEndCursor();
hasNextPage = lastPostModel.hasNextPage();
@ -262,7 +263,81 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.profile_menu, menu);
favMenuItem = menu.findItem(R.id.favourites);
// favMenuItem = menu.findItem(R.id.favourites);
blockMenuItem = menu.findItem(R.id.block);
if (blockMenuItem != null) {
blockMenuItem.setVisible(false);
}
restrictMenuItem = menu.findItem(R.id.restrict);
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(false);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.restrict) {
if (!isLoggedIn) return false;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
friendshipService.toggleRestrict(
profileModel.getId(),
!profileModel.getRestricted(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<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
@ -340,14 +415,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new ProfileFetcher(username.substring(1), profileModel -> {
if (getContext() == null) return;
this.profileModel = profileModel;
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
final boolean isSelf = isLoggedIn
&& profileModel != null
&& userIdFromCookie != null
&& userIdFromCookie.equals(profileModel.getId());
if (favMenuItem != null) {
favMenuItem.setVisible(isSelf);
}
// final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn
// && profileModel != null
// && userIdFromCookie != null
// && userIdFromCookie.equals(profileModel.getId());
// if (favMenuItem != null) {
// favMenuItem.setVisible(isSelf);
// }
setProfileDetails();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -385,7 +460,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.ALOINSTAGRAM.getValue())) {
Log.d("austin_debug", "alo triggered");
// Log.d(TAG, "alo triggered");
aloService.getUserStory(profileId, profileModel.getUsername(), false, new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> result) {
@ -402,15 +477,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
});
}
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) {
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnSaved.setText(R.string.saved);
ViewCompat.setBackgroundTintList(binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
} else {
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
@ -418,58 +491,50 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
binding.btnFollow.setText(R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background)));
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
}
binding.btnRestrict.setVisibility(View.VISIBLE);
if (profileModel.getRestricted()) {
binding.btnRestrict.setText(R.string.unrestrict);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background)));
} else {
binding.btnRestrict.setText(R.string.restrict);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
binding.btnBlock.setVisibility(View.VISIBLE);
binding.btnTagged.setVisibility(View.VISIBLE);
if (profileModel.getBlocked()) {
binding.btnBlock.setText(R.string.unblock);
ViewCompat.setBackgroundTintList(binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background)));
} else {
binding.btnBlock.setText(R.string.block);
ViewCompat.setBackgroundTintList(binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_red_background)));
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
}
}
}
} else {
if (Utils.dataBox.getFavorite(username) != null) {
binding.btnFollow.setText(R.string.unfavorite_short);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
} else {
binding.btnFollow.setText(R.string.favorite_short);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background)));
}
binding.btnFollow.setVisibility(View.VISIBLE);
if (!profileModel.isReallyPrivate()) {
binding.btnRestrict.setVisibility(View.VISIBLE);
binding.btnRestrict.setText(R.string.tagged);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_blue_background)));
if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
}
if (!profileId.equals(myId)) {
binding.favCb.setVisibility(View.VISIBLE);
final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null;
binding.favCb.setChecked(isFav);
binding.favCb.setButtonDrawable(isFav ? R.drawable.ic_star_check_24 : R.drawable.ic_outline_star_plus_24);
} else {
binding.favCb.setVisibility(View.GONE);
}
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
final long followersCount = profileModel.getFollowersCount();
@ -570,23 +635,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie
// .equals(profileModel.getId());
final String favorite = Utils.dataBox.getFavorite(username);
binding.btnFollow.setOnClickListener(v -> {
if (!isLoggedIn) {
if (favorite != null && v == binding.btnFollow) {
Utils.dataBox.delFavorite(new DataBox.FavoriteModel(
username,
Long.parseLong(favorite.split("/")[1]),
username.replaceAll("^@", "")));
} else if (v == binding.btnFollow) {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(
username,
System.currentTimeMillis(),
username.replaceAll("^@", "")));
}
fetchProfileDetails();
return;
}
if (profileModel.getFollowing() || profileModel.getRequested()) {
friendshipService.unfollow(
userIdFromCookie,
@ -623,64 +672,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
});
}
});
binding.btnRestrict.setOnClickListener(v -> {
if (!isLoggedIn) return;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
friendshipService.toggleRestrict(
profileModel.getId(),
!profileModel.getRestricted(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<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 -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(),
profileModel.getId(),
@ -728,6 +719,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setNegativeButton(R.string.cancel, null)
.show();
});
binding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> {
// do not do anything if state matches the db, as listener is set before profile details are set
final String finalUsername = username.startsWith("@") ? username.substring(1) : username;
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(finalUsername, FavoriteType.USER);
if ((isChecked && favorite != null) || (!isChecked && favorite == null)) {
return;
}
buttonView.setVisibility(View.GONE);
binding.favProgress.setVisibility(View.VISIBLE);
final String message;
if (isChecked) {
final DataBox.FavoriteModel model = new DataBox.FavoriteModel(
-1,
finalUsername,
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
new Date()
);
Utils.dataBox.addFavorite(model);
binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
} else {
Utils.dataBox.deleteFavorite(finalUsername, FavoriteType.USER);
message = getString(R.string.removed_from_favs);
binding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
binding.favProgress.setVisibility(View.GONE);
binding.favCb.setVisibility(View.VISIBLE);
});
}
private void showProfilePicDialog() {

View File

@ -107,6 +107,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
return true;
}));
}
generalCategory.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
generalCategory.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);

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 PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_theme";
public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
}

View File

@ -6,15 +6,17 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
@ -24,12 +26,9 @@ public final class DataBox extends SQLiteOpenHelper {
private static DataBox sInstance;
private final static int VERSION = 2;
private final static int VERSION = 3;
private final static String TABLE_COOKIES = "cookies";
private final static String TABLE_FAVORITES = "favorites";
private final static String KEY_DATE_ADDED = "date_added";
private final static String KEY_QUERY_TEXT = "query_text";
private final static String KEY_QUERY_DISPLAY = "query_display";
private final static String KEY_ID = "id";
private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME;
@ -38,7 +37,12 @@ public final class DataBox extends SQLiteOpenHelper {
private final static String KEY_FULL_NAME = "full_name";
private final static String KEY_PROFILE_PIC = "profile_pic";
private final Context c;
private final static String FAV_COL_ID = "id";
private final static String FAV_COL_QUERY = "query_text";
private final static String FAV_COL_TYPE = "type";
private final static String FAV_COL_DISPLAY_NAME = "display_name";
private final static String FAV_COL_PIC_URL = "pic_url";
private final static String FAV_COL_DATE_ADDED = "date_added";
public static synchronized DataBox getInstance(final Context context) {
if (sInstance == null) sInstance = new DataBox(context.getApplicationContext());
@ -47,7 +51,6 @@ public final class DataBox extends SQLiteOpenHelper {
private DataBox(@Nullable final Context context) {
super(context, "cookiebox.db", null, VERSION);
c = context;
}
@Override
@ -60,42 +63,128 @@ public final class DataBox extends SQLiteOpenHelper {
+ KEY_COOKIE + " TEXT,"
+ KEY_FULL_NAME + " TEXT,"
+ KEY_PROFILE_PIC + " TEXT)");
db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)");
// db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)");
db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " ("
+ FAV_COL_ID + " INTEGER PRIMARY KEY,"
+ FAV_COL_QUERY + " TEXT,"
+ FAV_COL_TYPE + " TEXT,"
+ FAV_COL_DISPLAY_NAME + " TEXT,"
+ FAV_COL_PIC_URL + " TEXT,"
+ FAV_COL_DATE_ADDED + " INTEGER)");
Log.i(TAG, "Tables created!");
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion));
if (oldVersion == 1) {
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT");
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT");
// switch without break, so that all migrations from a previous version to new are run
switch (oldVersion) {
case 1:
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT");
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT");
case 2:
final List<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));
}
public final void addFavorite(@NonNull final FavoriteModel favoriteModel) {
final String query = favoriteModel.getQuery();
final String display = favoriteModel.getDisplayName();
@NonNull
private List<FavoriteModel> backupOldFavorites(@NonNull final SQLiteDatabase db) {
// check if old favorites table had the column query_display
final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display");
Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists);
final List<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)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final ContentValues values = new ContentValues();
values.put(KEY_DATE_ADDED, favoriteModel.getDate());
values.put(KEY_QUERY_TEXT, query);
values.put(KEY_QUERY_DISPLAY, display);
final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{query});
if (rows != 1)
db.insertOrThrow(TABLE_FAVORITES, null, values);
addFavorite(db, model);
db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null)
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
} finally {
db.endTransaction();
}
@ -103,23 +192,40 @@ public final class DataBox extends SQLiteOpenHelper {
}
}
public final synchronized void delFavorite(@NonNull final FavoriteModel favoriteModel) {
final String query = favoriteModel.getQuery();
private void addFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) {
final ContentValues values = new ContentValues();
values.put(FAV_COL_QUERY, model.getQuery());
values.put(FAV_COL_TYPE, model.getType().toString());
values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName());
values.put(FAV_COL_PIC_URL, model.getPicUrl());
values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime());
int rows = 0;
if (model.getId() >= 1) {
rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())});
}
if (rows != 1) {
db.insertOrThrow(TABLE_FAVORITES, null, values);
}
}
public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?",
new String[]{query, Long.toString(favoriteModel.getDate())});
final int rowsDeleted = db.delete(TABLE_FAVORITES,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{query, type.toString()});
final int rowsDeletedTwo = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?",
new String[]{query.replaceAll("@", ""), Long.toString(favoriteModel.getDate())});
if (rowsDeleted > 0 || rowsDeletedTwo > 0) db.setTransactionSuccessful();
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite");
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e);
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error", e);
}
} finally {
db.endTransaction();
}
@ -127,73 +233,74 @@ public final class DataBox extends SQLiteOpenHelper {
}
}
@Nullable
public final ArrayList<FavoriteModel> getAllFavorites() {
ArrayList<FavoriteModel> favorites = null;
FavoriteModel tempFav;
@NonNull
public final List<FavoriteModel> getAllFavorites() {
final List<FavoriteModel> favorites = new ArrayList<>();
final SQLiteDatabase db = getWritableDatabase();
try (final Cursor cursor = db.rawQuery("SELECT query_text, date_added, query_display FROM favorites ORDER BY date_added DESC", null)) {
try (final Cursor cursor = db.rawQuery("SELECT "
+ FAV_COL_ID + ","
+ FAV_COL_QUERY + ","
+ FAV_COL_TYPE + ","
+ FAV_COL_DISPLAY_NAME + ","
+ FAV_COL_PIC_URL + ","
+ FAV_COL_DATE_ADDED
+ " FROM " + TABLE_FAVORITES,
null)) {
if (cursor != null && cursor.moveToFirst()) {
db.beginTransaction();
favorites = new ArrayList<>();
FavoriteModel tempFav;
do {
FavoriteType type = null;
try {
type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
tempFav = new FavoriteModel(
(cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor.getString(0).contains("/"))
? cursor.getString(0)
: "@" + cursor.getString(0), // query text
cursor.getLong(1), // date added
cursor.getString(2) == null ? (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor
.getString(0).contains("/"))
? cursor.getString(0)
: "@" + cursor.getString(0) : cursor.getString(2) // display
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
type,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
if (cursor.getString(2) == null) {
try {
final ContentValues values = new ContentValues();
values.put(KEY_DATE_ADDED, tempFav.getDate());
values.put(KEY_QUERY_TEXT, tempFav.getQuery());
values.put(KEY_QUERY_DISPLAY, tempFav.getDisplayName());
final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{tempFav.getQuery()});
if (rows != 1)
db.insertOrThrow(TABLE_FAVORITES, null, values);
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
favorites.add(tempFav);
} while (cursor.moveToNext());
db.endTransaction();
}
} catch (final Exception x) {
Log.e("austin_debug", "", x);
try {
db.execSQL("ALTER TABLE favorites ADD query_display TEXT");
Toast.makeText(c, "DB has migrated, launch quick access again.", Toast.LENGTH_SHORT).show();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "migrate");
Toast.makeText(c, "DB migration failed, contact maintainer.", Toast.LENGTH_SHORT).show();
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} catch (final Exception e) {
Log.e(TAG, "", e);
}
return favorites;
}
public final String getFavorite(@NonNull final String query) {
@Nullable
public final FavoriteModel getFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE "
+ KEY_QUERY_TEXT + "='" + query + "' ORDER BY date_added DESC", null)) {
final Cursor cursor = db.rawQuery("SELECT "
+ FAV_COL_ID + ","
+ FAV_COL_QUERY + ","
+ FAV_COL_TYPE + ","
+ FAV_COL_DISPLAY_NAME + ","
+ FAV_COL_PIC_URL + ","
+ FAV_COL_DATE_ADDED
+ " FROM " + TABLE_FAVORITES
+ " WHERE " + FAV_COL_QUERY + "='" + query + "'"
+ " AND " + FAV_COL_TYPE + "='" + type.toString() + "'",
null)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0) + "/" + String.valueOf(cursor.getLong(1));
FavoriteType favoriteType = null;
try {
favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
return new FavoriteModel(
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
favoriteType,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
}
}
return null;
}
@ -399,31 +506,80 @@ public final class DataBox extends SQLiteOpenHelper {
}
public static class FavoriteModel {
private final String query, displayName;
private final long date;
private final int id;
private final String query;
private final FavoriteType type;
private final String displayName;
private final String picUrl;
private final Date dateAdded;
public FavoriteModel(final String query, final long date, final String displayName) {
public FavoriteModel(final int id,
final String query,
final FavoriteType type,
final String displayName,
final String picUrl,
final Date dateAdded) {
this.id = id;
this.query = query;
this.date = date;
this.type = type;
this.displayName = displayName;
this.picUrl = picUrl;
this.dateAdded = dateAdded;
}
public int getId() {
return id;
}
public String getQuery() {
return query;
}
public FavoriteType getType() {
return type;
}
public String getDisplayName() {
return displayName;
}
public long getDate() {
return date;
public String getPicUrl() {
return picUrl;
}
public Date getDateAdded() {
return dateAdded;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final FavoriteModel that = (FavoriteModel) o;
return id == that.id &&
ObjectsCompat.equals(query, that.query) &&
type == that.type &&
ObjectsCompat.equals(displayName, that.displayName) &&
ObjectsCompat.equals(picUrl, that.picUrl) &&
ObjectsCompat.equals(dateAdded, that.dateAdded);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded);
}
@NonNull
@Override
public String toString() {
return query;
return "FavoriteModel{" +
"id=" + id +
", query='" + query + '\'' +
", type=" + type +
", displayName='" + displayName + '\'' +
", picUrl='" + picUrl + '\'' +
", dateAdded=" + dateAdded +
'}';
}
}
}

View File

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

View File

@ -57,7 +57,8 @@ public class NavigationExtensions {
return false;
}
String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
if (!selectedItemTag[0].equals(newlySelectedItemTag)) {
String tag = selectedItemTag[0];
if (tag != null && !tag.equals(newlySelectedItemTag)) {
fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragment == null) {

View File

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

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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/semi_transparent_black" />
<solid android:color="@color/black_a50" />
<padding
android:left="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_height="?attr/actionBarSize"
app:layout_collapseMode="none"
app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface"
app:title="@string/app_name"
tools:menu="@menu/main_menu" />
</com.google.android.material.appbar.CollapsingToolbarLayout>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/semi_transparent_black">
android:background="@color/black_a50">
<awais.instagrabber.customviews.drawee.ZoomableDraweeView
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"
app:layout_scrollFlags="scroll">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tagInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space"
android:visibility="visible">
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainHashtagImage"
android:layout_width="@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
android:id="@+id/mainTagPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
app:layout_constraintBottom_toTopOf="@id/btnFollowTag"
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:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="20sp"
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" />
</LinearLayout>
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_person_add_24"
app:chipIconTint="@color/deep_purple_800"
app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"
app:layout_constraintStart_toEndOf="@id/mainHashtagImage"
app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"
app:rippleColor="@color/purple_200" />
<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.AppBarLayout>

View File

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

View File

@ -79,8 +79,10 @@
android:ellipsize="marquee"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingTop="8dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textStyle="bold"
@ -102,15 +104,34 @@
app:srcCompat="@drawable/verified"
tools:visibility="visible" />
<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
android:id="@+id/mainBiography"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="?android:selectableItemBackground"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/mainUrl"
app:layout_constraintEnd_toEndOf="parent"
@ -124,10 +145,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/mainBiography"
android:ellipsize="marquee"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
@ -135,97 +153,77 @@
app:layout_constraintTop_toBottomOf="@id/mainBiography"
tools:text="https://austinhuang.me/"
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"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textColor="@color/deep_purple_200"
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
app:layout_constraintEnd_toStartOf="@id/btnRestrict"
app:icon="@drawable/ic_outline_person_add_24"
app:iconGravity="top"
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_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/purple_200"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnRestrict"
<com.google.android.material.button.MaterialButton
android:id="@+id/btnTagged"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/restrict"
android:textColor="@color/btn_orange_text_color"
android:text="@string/tagged"
android:textColor="@color/deep_orange_600"
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
app:layout_constraintEnd_toStartOf="@id/btnBlock"
app:icon="@drawable/ic_outline_person_pin_24"
app:iconGravity="top"
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_constraintTop_toBottomOf="@id/mainUrl"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton
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
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaved"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/saved"
android:textColor="@color/btn_orange_text_color"
android:textColor="@color/blue_700"
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_constraintEnd_toStartOf="@id/btnLiked"
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" />
<androidx.appcompat.widget.AppCompatButton
<com.google.android.material.button.MaterialButton
android:id="@+id/btnLiked"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color"
android:textColor="@color/red_600"
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_constraintEnd_toEndOf="parent"
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" />
<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:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/semi_transparent_black" />
tools:background="@color/black_a50" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="24dp"

View File

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

View File

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

View File

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

View File

@ -34,6 +34,24 @@
app:nullable="true" />
</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
android:id="@+id/morePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment"
@ -47,6 +65,9 @@
<action
android:id="@+id/action_morePreferencesFragment_to_notificationsViewer"
app:destination="@id/notificationsViewer" />
<action
android:id="@+id/action_morePreferencesFragment_to_favoritesFragment"
app:destination="@id/favoritesFragment" />
</fragment>
<fragment
android:id="@+id/settingsPreferencesFragment"
@ -69,4 +90,8 @@
android:id="@+id/themePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.ThemePreferencesFragment"
android:label="@string/theme_settings" />
<fragment
android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites" />
</navigation>

View File

@ -28,15 +28,13 @@
<color name="btn_lightorange_background">#FFBB00</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="semi_transparent_black">#80000000</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="black_800">#121212</color>
<color name="black_a50">#80000000</color>
<color name="grey_50">#FAFAFA</color>
<color name="grey_100">#F5F5F5</color>
@ -83,7 +81,65 @@
<color name="purple_200">#bb86fc</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 -->
<color name="barinstaColorPrimary">#a86735</color>

View File

@ -58,6 +58,7 @@
<string name="select_language">Language</string>
<string name="what_to_do_dialog">What to do?</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_following">%s\nFollowing</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="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_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_colordepth_prefix">\nColor depth:</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="light_barinsta_theme">Barinsta</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>

View File

@ -108,4 +108,24 @@
<item name="android:background">?colorSurface</item>
<item name="android:windowBackground">?attr/colorSurface</item>
</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>

View File

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