1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-26 08:37:29 +00:00

Merge remote-tracking branch 'origin/dm-notifications-enhancements' into dm-notifications-enhancements

This commit is contained in:
Ammar Githam 2021-01-24 23:40:56 +09:00
commit ea7236dcc1
44 changed files with 782 additions and 224 deletions

View File

@ -22,10 +22,13 @@ public class SavedCollectionsAdapter extends ListAdapter<SavedCollection, TopicC
@Override @Override
public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) { public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
if (oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) { if (oldItem.getCoverMedias() != null && newItem.getCoverMedias() != null
if (oldItem.getCoverMedias().size() == 0) return true; && oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) {
return oldItem.getCoverMedias().get(0).getId().equals(newItem.getCoverMedias().get(0).getId()); return oldItem.getCoverMedias().get(0).getId().equals(newItem.getCoverMedias().get(0).getId());
} }
else if (oldItem.getCoverMedia() != null && newItem.getCoverMedia() != null) {
return oldItem.getCoverMedia().getId().equals(newItem.getCoverMedia().getId());
}
return false; return false;
} }
}; };

View File

@ -127,7 +127,7 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
// binding.title.setTransitionName("title-" + topicCluster.getId()); // binding.title.setTransitionName("title-" + topicCluster.getId());
binding.cover.setTransitionName("cover-" + topicCluster.getId()); binding.cover.setTransitionName("cover-" + topicCluster.getId());
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null
? null ? topicCluster.getCoverMedia()
: topicCluster.getCoverMedias().get(0)); : topicCluster.getCoverMedias().get(0));
if (thumbUrl == null) { if (thumbUrl == null) {
binding.cover.setImageURI((String) null); binding.cover.setImageURI((String) null);

View File

@ -9,7 +9,7 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse; import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse;
import awais.instagrabber.repositories.responses.discover.TopicalExploreItem; import awais.instagrabber.repositories.responses.WrappedMedia;
import awais.instagrabber.webservices.DiscoverService; import awais.instagrabber.webservices.DiscoverService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
@ -35,13 +35,13 @@ public class DiscoverPostFetchService implements PostFetcher.PostFetchService {
} }
moreAvailable = result.isMoreAvailable(); moreAvailable = result.isMoreAvailable();
topicalExploreRequest.setMaxId(result.getNextMaxId()); topicalExploreRequest.setMaxId(result.getNextMaxId());
final List<TopicalExploreItem> items = result.getItems(); final List<WrappedMedia> items = result.getItems();
final List<Media> posts; final List<Media> posts;
if (items == null) { if (items == null) {
posts = Collections.emptyList(); posts = Collections.emptyList();
} else { } else {
posts = items.stream() posts = items.stream()
.map(TopicalExploreItem::getMedia) .map(WrappedMedia::getMedia)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -5,131 +5,81 @@ import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.json.JSONObject; import java.util.ArrayList;
import java.util.List;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants; import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.utils.NetworkUtils; import awais.instagrabber.webservices.UserService;
import awais.instagrabber.utils.TextUtils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; public final class ProfileFetcher extends AsyncTask<Void, Void, String> {
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class ProfileFetcher extends AsyncTask<Void, Void, User> {
private static final String TAG = ProfileFetcher.class.getSimpleName(); private static final String TAG = ProfileFetcher.class.getSimpleName();
private final UserService userService;
private final GraphQLService graphQLService;
private final FetchListener<User> fetchListener; private final FetchListener<User> fetchListener;
private final boolean isLoggedIn;
private final String userName; private final String userName;
public ProfileFetcher(String userName, FetchListener<User> fetchListener) { public ProfileFetcher(final String userName,
final boolean isLoggedIn,
final FetchListener<User> fetchListener) {
this.userName = userName; this.userName = userName;
this.isLoggedIn = isLoggedIn;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
userService = isLoggedIn ? UserService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
} }
@Nullable @Nullable
@Override @Override
protected User doInBackground(final Void... voids) { protected String doInBackground(final Void... voids) {
User result = null; if (isLoggedIn) {
userService.getUsernameInfo(userName, new ServiceCallback<User>() {
try { @Override
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/" + userName + "/?__a=1").openConnection(); public void onSuccess(final User user) {
conn.setUseCaches(true); Log.d("austin_debug", user.getUsername() + " " + userName);
conn.connect(); userService.getUserFriendship(user.getPk(), new ServiceCallback<FriendshipStatus>() {
@Override
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { public void onSuccess(final FriendshipStatus status) {
final String json = NetworkUtils.readFromConnection(conn); user.setFriendshipStatus(status);
// Log.d(TAG, "doInBackground: " + json); fetchListener.onResult(user);
final JSONObject userJson = new JSONObject(json).getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_USER);
final String cookie = settingsHelper.getString(Constants.COOKIE);
boolean isPrivate = userJson.getBoolean("is_private");
final long id = userJson.optLong(Constants.EXTRAS_ID, 0);
final long uid = CookieUtils.getUserIdFromCookie(cookie);
final JSONObject timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media");
// if (timelineMedia.has("edges")) {
// final JSONArray edges = timelineMedia.getJSONArray("edges");
// }
String url = userJson.optString("external_url");
if (TextUtils.isEmpty(url)) url = null;
return new User(
id,
userName,
userJson.getString("full_name"),
isPrivate,
userJson.getString("profile_pic_url_hd"),
null,
new FriendshipStatus(
userJson.optBoolean("followed_by_viewer"),
userJson.optBoolean("follows_viewer"),
userJson.optBoolean("blocked_by_viewer"),
false,
isPrivate,
userJson.optBoolean("has_requested_viewer"),
userJson.optBoolean("requested_by_viewer"),
false,
userJson.optBoolean("restricted_by_viewer"),
false
),
userJson.getBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
timelineMedia.getLong("count"),
userJson.getJSONObject("edge_followed_by").getLong("count"),
userJson.getJSONObject("edge_follow").getLong("count"),
0,
userJson.getString("biography"),
url,
0,
null);
// result = new ProfileModel(isPrivate,
// !user.optBoolean("followed_by_viewer") && (id != uid && isPrivate),
// user.getBoolean("is_verified"),
// id,
// userName,
// user.getString("full_name"),
// user.getString("biography"),
// url,
// user.getString("profile_pic_url"),
// user.getString("profile_pic_url_hd"),
// timelineMedia.getLong("count"),
// user.getJSONObject("edge_followed_by").getLong("count"),
// user.getJSONObject("edge_follow").getLong("count"),
// user.optBoolean("followed_by_viewer"),
// user.optBoolean("follows_viewer"),
// user.optBoolean("restricted_by_viewer"),
// user.optBoolean("blocked_by_viewer"),
// user.optBoolean("requested_by_viewer"));
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
} }
@Override @Override
protected void onPostExecute(final User result) { public void onFailure(final Throwable t) {
if (fetchListener != null) fetchListener.onResult(result); Log.e(TAG, "Error", t);
}
});
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
}
else {
graphQLService.fetchUser(userName, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
fetchListener.onResult(user);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
}
return "yeah";
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
} }
} }

View File

@ -10,6 +10,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -17,11 +18,14 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher; import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.PermissionChecker; import androidx.core.content.PermissionChecker;
import androidx.core.graphics.ColorUtils; import androidx.core.graphics.ColorUtils;
@ -49,20 +53,23 @@ import awais.instagrabber.asyncs.SavedPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.databinding.FragmentCollectionPostsBinding; import awais.instagrabber.databinding.FragmentCollectionPostsBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.CollectionPostsFragmentDirections;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.saved.SavedCollection; import awais.instagrabber.repositories.responses.saved.SavedCollection;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.CollectionService;
import awais.instagrabber.webservices.ServiceCallback;
import static androidx.core.content.PermissionChecker.checkSelfPermission; import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "CollectionPostsFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
@ -75,6 +82,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
private Set<Media> selectedFeedModels; private Set<Media> selectedFeedModels;
private Media downloadFeedModel; private Media downloadFeedModel;
private int downloadChildPosition = -1; private int downloadChildPosition = -1;
private CollectionService collectionService;
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_SAVED_POSTS_LAYOUT); private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_SAVED_POSTS_LAYOUT);
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@ -84,7 +92,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
} }
}; };
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() { R.menu.saved_collection_select_menu, new PrimaryActionModeCallback.CallbacksHelper() {
@Override @Override
public void onDestroy(final ActionMode mode) { public void onDestroy(final ActionMode mode) {
binding.posts.endSelection(); binding.posts.endSelection();
@ -241,6 +249,11 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
setSharedElementEnterTransition(transitionSet); setSharedElementEnterTransition(transitionSet);
postponeEnterTransition(); postponeEnterTransition();
setHasOptionsMenu(true); setHasOptionsMenu(true);
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
final long userId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
collectionService = CollectionService.getInstance(deviceUuid, csrfToken, userId);
} }
@Nullable @Nullable
@ -267,7 +280,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.topic_posts_menu, menu); inflater.inflate(R.menu.collection_posts_menu, menu);
} }
@Override @Override
@ -276,6 +289,58 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
showPostsLayoutPreferences(); showPostsLayoutPreferences();
return true; return true;
} }
else if (item.getItemId() == R.id.delete) {
final Context context = getContext();
new AlertDialog.Builder(context)
.setTitle(R.string.edit_collection)
.setMessage(R.string.delete_collection_note)
.setPositiveButton(R.string.confirm, (d, w) -> {
collectionService.deleteCollection(
savedCollection.getId(),
new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
SavedCollectionsFragment.pleaseRefresh = true;
NavHostFragment.findNavController(CollectionPostsFragment.this).navigateUp();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting collection", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
}
else if (item.getItemId() == R.id.edit) {
final Context context = getContext();
final EditText input = new EditText(context);
new AlertDialog.Builder(context)
.setTitle(R.string.edit_collection)
.setView(input)
.setPositiveButton(R.string.confirm, (d, w) -> {
collectionService.editCollectionName(
savedCollection.getId(),
input.getText().toString(),
new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
binding.collapsingToolbarLayout.setTitle(input.getText().toString());
SavedCollectionsFragment.pleaseRefresh = true;
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error editing collection", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
}
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -372,7 +437,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
private void setupCover() { private void setupCover() {
final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMedias() == null final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMedias() == null
? null ? savedCollection.getCoverMedia()
: savedCollection.getCoverMedias().get(0)); : savedCollection.getCoverMedias().get(0));
final DraweeController controller = Fresco final DraweeController controller = Fresco
.newDraweeControllerBuilder() .newDraweeControllerBuilder()

View File

@ -99,10 +99,13 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
try {
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor(); stopCurrentExecutor();
} }
catch(Throwable e) {}
}
}; };
private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() { private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() {

View File

@ -83,8 +83,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
@Override @Override
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
try {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(Throwable e) {}
Log.e(TAG, "Error fetching list (double, following)", t); Log.e(TAG, "Error fetching list (double, following)", t);
} }
}; };
@ -109,8 +112,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
@Override @Override
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
try {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(Throwable e) {}
Log.e(TAG, "Error fetching list (double, follower)", t); Log.e(TAG, "Error fetching list (double, follower)", t);
} }
}; };
@ -221,8 +227,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
@Override @Override
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
try {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(Throwable e) {}
Log.e(TAG, "Error fetching list (single)", t); Log.e(TAG, "Error fetching list (single)", t);
} }
}; };

View File

@ -271,9 +271,12 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
try {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
} }
catch(Throwable e) {}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break; break;
case "ayml": case "ayml":
@ -287,9 +290,12 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
@Override @Override
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
try {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
} }
catch(Throwable e) {}
}
}); });
break; break;
} }

View File

@ -49,7 +49,10 @@ import androidx.core.view.ViewCompat;
import androidx.core.widget.NestedScrollView; import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -130,6 +133,17 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im
private PopupMenu optionsPopup; private PopupMenu optionsPopup;
private EditTextDialogFragment editTextDialogFragment; private EditTextDialogFragment editTextDialogFragment;
private MutableLiveData<Object> backStackSavedStateResultLiveData;
private final Observer<Object> backStackSavedStateObserver = result -> {
if (result == null) return;
if (result instanceof String) {
final String collection = (String) result;
handleSaveUnsaveResourceLiveData(viewModel.toggleSave(collection, viewModel.getMedia().hasViewerSaved()));
}
// clear result
backStackSavedStateResultLiveData.postValue(null);
};
// private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() { // private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() {
// //
// @Override // @Override
@ -305,6 +319,17 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im
} }
} }
@Override
public void onResume() {
super.onResume();
final NavController navController = NavHostFragment.findNavController(this);
final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
if (backStackEntry != null) {
backStackSavedStateResultLiveData = backStackEntry.getSavedStateHandle().getLiveData("collection");
backStackSavedStateResultLiveData.observe(getViewLifecycleOwner(), backStackSavedStateObserver);
}
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
@ -666,7 +691,10 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im
handleSaveUnsaveResourceLiveData(viewModel.toggleSave()); handleSaveUnsaveResourceLiveData(viewModel.toggleSave());
}); });
binding.save.setOnLongClickListener(v -> { binding.save.setOnLongClickListener(v -> {
Utils.displayToastAboveView(context, v, getString(R.string.save)); final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putBoolean("isSaving", true);
navController.navigate(R.id.action_global_savedCollectionsFragment, bundle);
return true; return true;
}); });
} }

View File

@ -17,7 +17,10 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.fragment.FragmentNavigator; import androidx.navigation.fragment.FragmentNavigator;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@ -27,7 +30,6 @@ import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.SavedCollectionsAdapter; import awais.instagrabber.adapters.SavedCollectionsAdapter;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.databinding.FragmentSavedCollectionsBinding; import awais.instagrabber.databinding.FragmentSavedCollectionsBinding;
import awais.instagrabber.repositories.responses.StoryStickerResponse;
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; import awais.instagrabber.repositories.responses.saved.CollectionsListResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
@ -40,12 +42,14 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "SavedCollectionsFragment"; private static final String TAG = "SavedCollectionsFragment";
public static boolean pleaseRefresh = false;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private CoordinatorLayout root; private CoordinatorLayout root;
private FragmentSavedCollectionsBinding binding; private FragmentSavedCollectionsBinding binding;
private SavedCollectionsViewModel savedCollectionsViewModel; private SavedCollectionsViewModel savedCollectionsViewModel;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private boolean isSaving;
private ProfileService profileService; private ProfileService profileService;
@Override @Override
@ -82,6 +86,12 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
inflater.inflate(R.menu.saved_collection_menu, menu); inflater.inflate(R.menu.saved_collection_menu, menu);
} }
@Override
public void onResume() {
super.onResume();
if (pleaseRefresh) onRefresh();
}
@Override @Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) { public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.add) { if (item.getItemId() == R.id.add) {
@ -120,6 +130,8 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
private void init() { private void init() {
setupTopics(); setupTopics();
fetchTopics(null); fetchTopics(null);
final SavedCollectionsFragmentArgs fragmentArgs = SavedCollectionsFragmentArgs.fromBundle(getArguments());
isSaving = fragmentArgs.getIsSaving();
} }
@Override @Override
@ -131,11 +143,18 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
savedCollectionsViewModel = new ViewModelProvider(fragmentActivity).get(SavedCollectionsViewModel.class); savedCollectionsViewModel = new ViewModelProvider(fragmentActivity).get(SavedCollectionsViewModel.class);
binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2))); binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2)));
final SavedCollectionsAdapter adapter = new SavedCollectionsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> { final SavedCollectionsAdapter adapter = new SavedCollectionsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> {
final NavController navController = NavHostFragment.findNavController(this);
if (isSaving) {
setNavControllerResult(navController, topicCluster.getId());
navController.navigateUp();
}
else {
final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder()
.addSharedElement(cover, "collection-" + topicCluster.getId()); .addSharedElement(cover, "collection-" + topicCluster.getId());
final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections
.actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor); .actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor);
NavHostFragment.findNavController(this).navigate(action, builder.build()); navController.navigate(action, builder.build());
}
}); });
binding.topicsRecyclerView.setAdapter(adapter); binding.topicsRecyclerView.setAdapter(adapter);
savedCollectionsViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); savedCollectionsViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
@ -158,4 +177,11 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
} }
}); });
} }
private void setNavControllerResult(@NonNull final NavController navController, final String result) {
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
if (navBackStackEntry == null) return;
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
savedStateHandle.set("collection", result);
}
} }

View File

@ -236,9 +236,12 @@ public class StoryViewerFragment extends Fragment {
@Override @Override
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) { public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Log.e(TAG, "onFailure: ", t); Log.e(TAG, "onFailure: ", t);
} }
catch(Throwable e) {}
}
}); });
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e); Log.e(TAG, "Error", e);

View File

@ -538,7 +538,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void fetchProfileDetails() { private void fetchProfileDetails() {
if (TextUtils.isEmpty(username)) return; if (TextUtils.isEmpty(username)) return;
new ProfileFetcher(username.trim().substring(1), profileModel -> { new ProfileFetcher(username.trim().substring(1), isLoggedIn, profileModel -> {
if (getContext() == null) return; if (getContext() == null) return;
this.profileModel = profileModel; this.profileModel = profileModel;
setProfileDetails(); setProfileDetails();
@ -946,7 +946,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
}); });
profileDetailsBinding.btnSaved.setOnClickListener(v -> { profileDetailsBinding.btnSaved.setOnClickListener(v -> {
final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(); final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(false);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
}); });
profileDetailsBinding.btnLiked.setOnClickListener(v -> { profileDetailsBinding.btnLiked.setOnClickListener(v -> {

View File

@ -26,7 +26,9 @@ import awais.instagrabber.dialogs.TimeSettingsDialog;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DirectoryChooser; import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.UserAgentUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_PATH;
@ -111,6 +113,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
preference.setEntryValues(values); preference.setEntryValues(values);
preference.setOnPreferenceChangeListener((preference1, newValue) -> { preference.setOnPreferenceChangeListener((preference1, newValue) -> {
shouldRecreate(); shouldRecreate();
final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage());
settingsHelper.putString(Constants.APP_UA, appUa);
return true; return true;
}); });
return preference; return preference;

View File

@ -0,0 +1,23 @@
package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.UserFeedResponse;
import awais.instagrabber.repositories.responses.WrappedFeedResponse;
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface CollectionRepository {
@FormUrlEncoded
@POST("/api/v1/collections/{id}/{action}/")
Call<String> changeCollection(@Path("id") String id,
@Path("action") String action,
@FieldMap Map<String, String> signedForm);
}

View File

@ -4,9 +4,13 @@ import java.util.Map;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.QueryMap; import retrofit2.http.QueryMap;
public interface GraphQLRepository { public interface GraphQLRepository {
@GET("/graphql/query/") @GET("/graphql/query/")
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams); Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams);
@GET("/{username}/?__a=1")
Call<String> getUser(@Path("username") String username);
} }

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories;
import java.util.Map; import java.util.Map;
import awais.instagrabber.repositories.responses.WrappedFeedResponse;
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; import awais.instagrabber.repositories.responses.saved.CollectionsListResponse;
import awais.instagrabber.repositories.responses.UserFeedResponse; import awais.instagrabber.repositories.responses.UserFeedResponse;
import retrofit2.Call; import retrofit2.Call;
@ -18,10 +19,10 @@ public interface ProfileRepository {
Call<UserFeedResponse> fetch(@Path("uid") final long uid, @QueryMap Map<String, String> queryParams); Call<UserFeedResponse> fetch(@Path("uid") final long uid, @QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/saved/") @GET("/api/v1/feed/saved/")
Call<UserFeedResponse> fetchSaved(@QueryMap Map<String, String> queryParams); Call<WrappedFeedResponse> fetchSaved(@QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/collection/{collectionId}/") @GET("/api/v1/feed/collection/{collectionId}/")
Call<UserFeedResponse> fetchSavedCollection(@Path("collectionId") final String collectionId, Call<WrappedFeedResponse> fetchSavedCollection(@Path("collectionId") final String collectionId,
@QueryMap Map<String, String> queryParams); @QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/liked/") @GET("/api/v1/feed/liked/")

View File

@ -1,7 +1,8 @@
package awais.instagrabber.repositories; package awais.instagrabber.repositories;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.repositories.responses.UserSearchResponse;
import awais.instagrabber.repositories.responses.WrappedUser;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.Path; import retrofit2.http.Path;
@ -10,7 +11,13 @@ import retrofit2.http.Query;
public interface UserRepository { public interface UserRepository {
@GET("/api/v1/users/{uid}/info/") @GET("/api/v1/users/{uid}/info/")
Call<User> getUserInfo(@Path("uid") final long uid); Call<WrappedUser> getUserInfo(@Path("uid") final long uid);
@GET("/api/v1/users/{username}/usernameinfo/")
Call<WrappedUser> getUsernameInfo(@Path("username") final String username);
@GET("/api/v1/friendships/show/{uid}/")
Call<FriendshipStatus> getUserFriendship(@Path("uid") final long uid);
@GET("/api/v1/users/search/") @GET("/api/v1/users/search/")
Call<UserSearchResponse> search(@Query("timezone_offset") float timezoneOffset, Call<UserSearchResponse> search(@Query("timezone_offset") float timezoneOffset,

View File

@ -10,7 +10,7 @@ public class User implements Serializable {
private final boolean isPrivate; private final boolean isPrivate;
private final String profilePicUrl; private final String profilePicUrl;
private final String profilePicId; private final String profilePicId;
private final FriendshipStatus friendshipStatus; private FriendshipStatus friendshipStatus;
private final boolean isVerified; private final boolean isVerified;
private final boolean hasAnonymousProfilePicture; private final boolean hasAnonymousProfilePicture;
private final boolean isUnpublished; private final boolean isUnpublished;
@ -102,6 +102,10 @@ public class User implements Serializable {
return friendshipStatus; return friendshipStatus;
} }
public void setFriendshipStatus(final FriendshipStatus friendshipStatus) {
this.friendshipStatus = friendshipStatus;
}
public boolean isVerified() { public boolean isVerified() {
return isVerified; return isVerified;
} }

View File

@ -0,0 +1,43 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class WrappedFeedResponse {
private final int numResults;
private final String nextMaxId;
private final boolean moreAvailable;
private final String status;
private final List<WrappedMedia> items;
public WrappedFeedResponse(final int numResults,
final String nextMaxId,
final boolean moreAvailable,
final String status,
final List<WrappedMedia> items) {
this.numResults = numResults;
this.nextMaxId = nextMaxId;
this.moreAvailable = moreAvailable;
this.status = status;
this.items = items;
}
public int getNumResults() {
return numResults;
}
public String getNextMaxId() {
return nextMaxId;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public String getStatus() {
return status;
}
public List<WrappedMedia> getItems() {
return items;
}
}

View File

@ -0,0 +1,13 @@
package awais.instagrabber.repositories.responses;
public class WrappedMedia {
private final Media media;
public WrappedMedia(final Media media) {
this.media = media;
}
public Media getMedia() {
return media;
}
}

View File

@ -0,0 +1,13 @@
package awais.instagrabber.repositories.responses;
public class WrappedUser {
private final User user;
public WrappedUser(final User user) {
this.user = user;
}
public User getUser() {
return user;
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.discover; package awais.instagrabber.repositories.responses.discover;
import java.util.List; import java.util.List;
import awais.instagrabber.repositories.responses.WrappedMedia;
public class TopicalExploreFeedResponse { public class TopicalExploreFeedResponse {
private final boolean moreAvailable; private final boolean moreAvailable;
@ -9,7 +10,7 @@ public class TopicalExploreFeedResponse {
private final String status; private final String status;
private final int numResults; private final int numResults;
private final List<TopicCluster> clusters; private final List<TopicCluster> clusters;
private final List<TopicalExploreItem> items; private final List<WrappedMedia> items;
public TopicalExploreFeedResponse(final boolean moreAvailable, public TopicalExploreFeedResponse(final boolean moreAvailable,
final String nextMaxId, final String nextMaxId,
@ -17,7 +18,7 @@ public class TopicalExploreFeedResponse {
final String status, final String status,
final int numResults, final int numResults,
final List<TopicCluster> clusters, final List<TopicCluster> clusters,
final List<TopicalExploreItem> items) { final List<WrappedMedia> items) {
this.moreAvailable = moreAvailable; this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId; this.nextMaxId = nextMaxId;
this.maxId = maxId; this.maxId = maxId;
@ -51,7 +52,7 @@ public class TopicalExploreFeedResponse {
return clusters; return clusters;
} }
public List<TopicalExploreItem> getItems() { public List<WrappedMedia> getItems() {
return items; return items;
} }
} }

View File

@ -1,15 +0,0 @@
package awais.instagrabber.repositories.responses.discover;
import awais.instagrabber.repositories.responses.Media;
public class TopicalExploreItem {
private final Media media;
public TopicalExploreItem(final Media media) {
this.media = media;
}
public Media getMedia() {
return media;
}
}

View File

@ -10,17 +10,20 @@ public class SavedCollection implements Serializable {
private final String collectionName; private final String collectionName;
private final String collectionType; private final String collectionType;
private final int collectionMediacount; private final int collectionMediacount;
private final Media coverMedia;
private final List<Media> coverMediaList; private final List<Media> coverMediaList;
public SavedCollection(final String collectionId, public SavedCollection(final String collectionId,
final String collectionName, final String collectionName,
final String collectionType, final String collectionType,
final int collectionMediacount, final int collectionMediacount,
final Media coverMedia,
final List<Media> coverMediaList) { final List<Media> coverMediaList) {
this.collectionId = collectionId; this.collectionId = collectionId;
this.collectionName = collectionName; this.collectionName = collectionName;
this.collectionType = collectionType; this.collectionType = collectionType;
this.collectionMediacount = collectionMediacount; this.collectionMediacount = collectionMediacount;
this.coverMedia = coverMedia;
this.coverMediaList = coverMediaList; this.coverMediaList = coverMediaList;
} }
@ -40,6 +43,11 @@ public class SavedCollection implements Serializable {
return collectionMediacount; return collectionMediacount;
} }
// check the list first, then the single
// i have no idea what condition is required
public Media getCoverMedia() { return coverMedia; }
public List<Media> getCoverMedias() { public List<Media> getCoverMedias() {
return coverMediaList; return coverMediaList;
} }

View File

@ -20,49 +20,21 @@ public class UserAgentUtils {
// use APKpure, assume x86 // use APKpure, assume x86
private static final String igVersion = "169.3.0.30.135"; private static final String igVersion = "169.3.0.30.135";
private static final String igVersionCode = "264009054"; private static final String igVersionCode = "264009054";
// https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json // only pick the ones that has width 1440 for maximum download quality
// presumed constant, so no need to update
public static final String[] devices = { public static final String[] devices = {
"25/7.1.1; 440dpi; 1080x1920; Xiaomi; Mi Note 3; jason; qcom", // https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json
"23/6.0.1; 480dpi; 1080x1920; Xiaomi; Redmi Note 3; kenzo; qcom",
"23/6.0; 480dpi; 1080x1920; Xiaomi; Redmi Note 4; nikel; mt6797",
"24/7.0; 480dpi; 1080x1920; Xiaomi/xiaomi; Redmi Note 4; mido; qcom",
"23/6.0; 480dpi; 1080x1920; Xiaomi; Redmi Note 4X; nikel; mt6797",
"27/8.1.0; 440dpi; 1080x2030; Xiaomi/xiaomi; Redmi Note 5; whyred; qcom",
"23/6.0.1; 480dpi; 1080x1920; Xiaomi; Redmi 4; markw; qcom",
"27/8.1.0; 440dpi; 1080x2030; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom",
"25/7.1.2; 440dpi; 1080x2030; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom",
"26/8.0.0; 480dpi; 1080x1920; Xiaomi; MI 5; gemini; qcom",
"27/8.1.0; 480dpi; 1080x1920; Xiaomi/xiaomi; Mi A1; tissot_sprout; qcom",
"26/8.0.0; 480dpi; 1080x1920; Xiaomi; MI 6; sagit; qcom",
"25/7.1.1; 440dpi; 1080x1920; Xiaomi; MI MAX 2; oxygen; qcom",
"24/7.0; 480dpi; 1080x1920; Xiaomi; MI 5s; capricorn; qcom",
"26/8.0.0; 480dpi; 1080x1920; samsung; SM-A520F; a5y17lte; samsungexynos7880",
"26/8.0.0; 480dpi; 1080x2076; samsung; SM-G950F; dreamlte; samsungexynos8895",
"26/8.0.0; 640dpi; 1440x2768; samsung; SM-G950F; dreamlte; samsungexynos8895", "26/8.0.0; 640dpi; 1440x2768; samsung; SM-G950F; dreamlte; samsungexynos8895",
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-G955F; dream2lte; samsungexynos8895",
"26/8.0.0; 560dpi; 1440x2792; samsung; SM-G955F; dream2lte; samsungexynos8895", "26/8.0.0; 560dpi; 1440x2792; samsung; SM-G955F; dream2lte; samsungexynos8895",
"24/7.0; 480dpi; 1080x1920; samsung; SM-A510F; a5xelte; samsungexynos7580",
"26/8.0.0; 480dpi; 1080x1920; samsung; SM-G930F; herolte; samsungexynos8890",
"26/8.0.0; 480dpi; 1080x1920; samsung; SM-G935F; hero2lte; samsungexynos8890",
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-G965F; star2lte; samsungexynos9810",
"26/8.0.0; 480dpi; 1080x2076; samsung; SM-A530F; jackpotlte; samsungexynos7885",
"24/7.0; 640dpi; 1440x2560; samsung; SM-G925F; zerolte; samsungexynos7420", "24/7.0; 640dpi; 1440x2560; samsung; SM-G925F; zerolte; samsungexynos7420",
"26/8.0.0; 420dpi; 1080x1920; samsung; SM-A720F; a7y17lte; samsungexynos7880",
"24/7.0; 640dpi; 1440x2560; samsung; SM-G920F; zeroflte; samsungexynos7420", "24/7.0; 640dpi; 1440x2560; samsung; SM-G920F; zeroflte; samsungexynos7420",
"24/7.0; 420dpi; 1080x1920; samsung; SM-J730FM; j7y17lte; samsungexynos7870", // https://github.com/mimmi20/BrowserDetector/tree/master
"26/8.0.0; 480dpi; 1080x2076; samsung; SM-G960F; starlte; samsungexynos9810", "28/9; 560dpi; 1440x2792; samsung; SM-N960F; crownlte; samsungexynos9810",
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-N950F; greatlte; samsungexynos8895", // mgp25
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-A730F; jackpot2lte; samsungexynos7885", "23/6.0.1; 640dpi; 1440x2392; LGE/lge; RS988; h1; h1",
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-A605FN; a6plte; qcom", "24/7.0; 640dpi; 1440x2560; HUAWEI; LON-L29; HWLON; hi3660",
"26/8.0.0; 480dpi; 1080x1920; HUAWEI/HONOR; STF-L09; HWSTF; hi3660", "23/6.0.1; 640dpi; 1440x2560; ZTE; ZTE A2017U; ailsa_ii; qcom",
"27/8.1.0; 480dpi; 1080x2280; HUAWEI/HONOR; COL-L29; HWCOL; kirin970", "23/6.0.1; 640dpi; 1440x2560; samsung; SM-G935F; hero2lte; samsungexynos8890",
"26/8.0.0; 480dpi; 1080x2032; HUAWEI/HONOR; LLD-L31; HWLLD-H; hi6250", "23/6.0.1; 640dpi; 1440x2560; samsung; SM-G930F; herolte; samsungexynos8890"
"26/8.0.0; 480dpi; 1080x2150; HUAWEI; ANE-LX1; HWANE; hi6250",
"26/8.0.0; 480dpi; 1080x2032; HUAWEI; FIG-LX1; HWFIG-H; hi6250",
"27/8.1.0; 480dpi; 1080x2150; HUAWEI/HONOR; COL-L29; HWCOL; kirin970",
"26/8.0.0; 480dpi; 1080x2038; HUAWEI/HONOR; BND-L21; HWBND-H; hi6250",
"23/6.0.1; 420dpi; 1080x1920; LeMobile/LeEco; Le X527; le_s2_ww; qcom"
}; };
@NonNull @NonNull

View File

@ -83,6 +83,7 @@ public class AppStateViewModel extends AndroidViewModel {
if (TextUtils.isEmpty(username)) return; if (TextUtils.isEmpty(username)) return;
new ProfileFetcher( new ProfileFetcher(
username.trim().substring(1), username.trim().substring(1),
true,
currentUser::postValue currentUser::postValue
).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); ).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }

View File

@ -188,27 +188,33 @@ public class PostViewV2ViewModel extends ViewModel {
@NonNull @NonNull
public LiveData<Resource<Object>> toggleSave() { public LiveData<Resource<Object>> toggleSave() {
if (!media.hasViewerSaved()) { if (!media.hasViewerSaved()) {
return save(); return save(null, false);
} }
return unsave(); return unsave();
} }
public LiveData<Resource<Object>> save() { @NonNull
public LiveData<Resource<Object>> toggleSave(final String collection, final boolean ignoreSaveState) {
return save(collection, ignoreSaveState);
}
public LiveData<Resource<Object>> save(final String collection, final boolean ignoreSaveState) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null)); data.postValue(Resource.loading(null));
mediaService.save(media.getPk(), getSaveUnsaveCallback(data)); mediaService.save(media.getPk(), collection, getSaveUnsaveCallback(data, ignoreSaveState));
return data; return data;
} }
public LiveData<Resource<Object>> unsave() { public LiveData<Resource<Object>> unsave() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null)); data.postValue(Resource.loading(null));
mediaService.unsave(media.getPk(), getSaveUnsaveCallback(data)); mediaService.unsave(media.getPk(), getSaveUnsaveCallback(data, false));
return data; return data;
} }
@NonNull @NonNull
private ServiceCallback<Boolean> getSaveUnsaveCallback(final MutableLiveData<Resource<Object>> data) { private ServiceCallback<Boolean> getSaveUnsaveCallback(final MutableLiveData<Resource<Object>> data,
final boolean ignoreSaveState) {
return new ServiceCallback<Boolean>() { return new ServiceCallback<Boolean>() {
@Override @Override
public void onSuccess(final Boolean result) { public void onSuccess(final Boolean result) {
@ -217,7 +223,7 @@ public class PostViewV2ViewModel extends ViewModel {
return; return;
} }
data.postValue(Resource.success(true)); data.postValue(Resource.success(true));
media.setHasViewerSaved(!media.hasViewerSaved()); if (!ignoreSaveState) media.setHasViewerSaved(!media.hasViewerSaved());
saved.postValue(media.hasViewerSaved()); saved.postValue(media.hasViewerSaved());
} }

View File

@ -35,6 +35,7 @@ public abstract class BaseService {
final Gson gson = new GsonBuilder() final Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) .registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer())
.setLenient()
.create(); .create();
builder = new Retrofit.Builder() builder = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create())

View File

@ -0,0 +1,120 @@
package awais.instagrabber.webservices;
import androidx.annotation.NonNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import awais.instagrabber.repositories.CollectionRepository;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class CollectionService extends BaseService {
private static final String TAG = "ProfileService";
private final CollectionRepository repository;
private final String deviceUuid, csrfToken;
private final long userId;
private static CollectionService instance;
private CollectionService(final String deviceUuid,
final String csrfToken,
final long userId) {
this.deviceUuid = deviceUuid;
this.csrfToken = csrfToken;
this.userId = userId;
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(CollectionRepository.class);
}
public String getCsrfToken() {
return csrfToken;
}
public String getDeviceUuid() {
return deviceUuid;
}
public long getUserId() {
return userId;
}
public static CollectionService getInstance(final String deviceUuid, final String csrfToken, final long userId) {
if (instance == null
|| !Objects.equals(instance.getCsrfToken(), csrfToken)
|| !Objects.equals(instance.getDeviceUuid(), deviceUuid)
|| !Objects.equals(instance.getUserId(), userId)) {
instance = new CollectionService(deviceUuid, csrfToken, userId);
}
return instance;
}
public void addPostsToCollection(final String collectionId,
final List<Media> posts,
final ServiceCallback<String> callback) {
final Map<String, Object> form = new HashMap<>(2);
form.put("module_name", "feed_saved_add_to_collection");
final List<String> ids;
ids = posts.stream()
.map(Media::getPk)
.filter(Objects::nonNull)
.collect(Collectors.toList());
form.put("added_media_ids", "[" + String.join(",", ids) + "]");
changeCollection(collectionId, "edit", form, callback);
}
public void editCollectionName(final String collectionId,
final String name,
final ServiceCallback<String> callback) {
final Map<String, Object> form = new HashMap<>(1);
form.put("name", name);
changeCollection(collectionId, "edit", form, callback);
}
public void deleteCollection(final String collectionId,
final ServiceCallback<String> callback) {
changeCollection(collectionId, "delete", null, callback);
}
public void changeCollection(final String collectionId,
final String action,
final Map<String, Object> options,
final ServiceCallback<String> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
form.put("_uuid", deviceUuid);
form.put("_uid", userId);
if (options != null) form.putAll(options);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> request = repository.changeCollection(collectionId, action, signedForm);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (callback == null) return;
final String collectionsListResponse = response.body();
if (collectionsListResponse == null) {
callback.onSuccess(null);
return;
}
callback.onSuccess(collectionsListResponse);
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
}

View File

@ -271,4 +271,82 @@ public class GraphQLService extends BaseService {
} }
}); });
} }
public void fetchUser(final String username,
final ServiceCallback<User> callback) {
final Call<String> request = repository.getUser(username);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
Log.e(TAG, "Error occurred while fetching gql user of " + username);
callback.onSuccess(null);
return;
}
try {
final JSONObject body = new JSONObject(rawBody);
final JSONObject userJson = body.getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_USER);
boolean isPrivate = userJson.getBoolean("is_private");
final long id = userJson.optLong(Constants.EXTRAS_ID, 0);
final JSONObject timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media");
// if (timelineMedia.has("edges")) {
// final JSONArray edges = timelineMedia.getJSONArray("edges");
// }
String url = userJson.optString("external_url");
if (TextUtils.isEmpty(url)) url = null;
callback.onSuccess(new User(
id,
username,
userJson.getString("full_name"),
isPrivate,
userJson.getString("profile_pic_url_hd"),
null,
new FriendshipStatus(
userJson.optBoolean("followed_by_viewer"),
userJson.optBoolean("follows_viewer"),
userJson.optBoolean("blocked_by_viewer"),
false,
isPrivate,
userJson.optBoolean("has_requested_viewer"),
userJson.optBoolean("requested_by_viewer"),
false,
userJson.optBoolean("restricted_by_viewer"),
false
),
userJson.getBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
timelineMedia.getLong("count"),
userJson.getJSONObject("edge_followed_by").getLong("count"),
userJson.getJSONObject("edge_follow").getLong("count"),
0,
userJson.getString("biography"),
url,
0,
null));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
} }

View File

@ -102,33 +102,37 @@ public class MediaService extends BaseService {
public void like(final String mediaId, public void like(final String mediaId,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
action(mediaId, "like", callback); action(mediaId, "like", null, callback);
} }
public void unlike(final String mediaId, public void unlike(final String mediaId,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
action(mediaId, "unlike", callback); action(mediaId, "unlike", null, callback);
} }
public void save(final String mediaId, public void save(final String mediaId,
final String collection,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
action(mediaId, "save", callback); action(mediaId, "save", collection, callback);
} }
public void unsave(final String mediaId, public void unsave(final String mediaId,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
action(mediaId, "unsave", callback); action(mediaId, "unsave", null, callback);
} }
private void action(final String mediaId, private void action(final String mediaId,
final String action, final String action,
final String collection,
final ServiceCallback<Boolean> callback) { final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>(4); final Map<String, Object> form = new HashMap<>();
form.put("media_id", mediaId); form.put("media_id", mediaId);
form.put("_csrftoken", csrfToken); form.put("_csrftoken", csrfToken);
form.put("_uid", userId); form.put("_uid", userId);
form.put("_uuid", deviceUuid); form.put("_uuid", deviceUuid);
// form.put("radio_type", "wifi-none"); // form.put("radio_type", "wifi-none");
if (action.equals("save") && !TextUtils.isEmpty(collection)) form.put("added_collection_ids", "[" + collection + "]");
// there also exists "removed_collection_ids" which can be used with "save" and "unsave"
final Map<String, String> signedForm = Utils.sign(form); final Map<String, String> signedForm = Utils.sign(form);
final Call<String> request = repository.action(action, mediaId, signedForm); final Call<String> request = repository.action(action, mediaId, signedForm);
request.enqueue(new Callback<String>() { request.enqueue(new Callback<String>() {

View File

@ -4,12 +4,19 @@ import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import awais.instagrabber.repositories.ProfileRepository; import awais.instagrabber.repositories.ProfileRepository;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.UserFeedResponse; import awais.instagrabber.repositories.responses.UserFeedResponse;
import awais.instagrabber.repositories.responses.WrappedFeedResponse;
import awais.instagrabber.repositories.responses.WrappedMedia;
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; import awais.instagrabber.repositories.responses.saved.CollectionsListResponse;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -76,30 +83,40 @@ public class ProfileService extends BaseService {
final String collectionId, final String collectionId,
final ServiceCallback<PostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
Call<UserFeedResponse> request = null; Call<WrappedFeedResponse> request = null;
if (!TextUtils.isEmpty(maxId)) { if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId); builder.put("max_id", maxId);
} }
if (TextUtils.isEmpty(collectionId) || collectionId.equals("ALL_MEDIA_AUTO_COLLECTION")) request = repository.fetchSaved(builder.build()); if (TextUtils.isEmpty(collectionId) || collectionId.equals("ALL_MEDIA_AUTO_COLLECTION")) request = repository.fetchSaved(builder.build());
else request = repository.fetchSavedCollection(collectionId, builder.build()); else request = repository.fetchSavedCollection(collectionId, builder.build());
request.enqueue(new Callback<UserFeedResponse>() { request.enqueue(new Callback<WrappedFeedResponse>() {
@Override @Override
public void onResponse(@NonNull final Call<UserFeedResponse> call, @NonNull final Response<UserFeedResponse> response) { public void onResponse(@NonNull final Call<WrappedFeedResponse> call, @NonNull final Response<WrappedFeedResponse> response) {
if (callback == null) return; if (callback == null) return;
final UserFeedResponse userFeedResponse = response.body(); final WrappedFeedResponse userFeedResponse = response.body();
if (userFeedResponse == null) { if (userFeedResponse == null) {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
final List<WrappedMedia> items = userFeedResponse.getItems();
final List<Media> posts;
if (items == null) {
posts = Collections.emptyList();
} else {
posts = items.stream()
.map(WrappedMedia::getMedia)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
callback.onSuccess(new PostsFetchResponse( callback.onSuccess(new PostsFetchResponse(
userFeedResponse.getItems(), posts,
userFeedResponse.isMoreAvailable(), userFeedResponse.isMoreAvailable(),
userFeedResponse.getNextMaxId() userFeedResponse.getNextMaxId()
)); ));
} }
@Override @Override
public void onFailure(@NonNull final Call<UserFeedResponse> call, @NonNull final Throwable t) { public void onFailure(@NonNull final Call<WrappedFeedResponse> call, @NonNull final Throwable t) {
if (callback != null) { if (callback != null) {
callback.onFailure(t); callback.onFailure(t);
} }
@ -171,7 +188,6 @@ public class ProfileService extends BaseService {
}); });
} }
public void fetchLiked(final String maxId, public void fetchLiked(final String maxId,
final ServiceCallback<PostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();

View File

@ -5,8 +5,10 @@ import androidx.annotation.NonNull;
import java.util.TimeZone; import java.util.TimeZone;
import awais.instagrabber.repositories.UserRepository; import awais.instagrabber.repositories.UserRepository;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.repositories.responses.UserSearchResponse;
import awais.instagrabber.repositories.responses.WrappedUser;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -34,25 +36,66 @@ public class UserService extends BaseService {
} }
public void getUserInfo(final long uid, final ServiceCallback<User> callback) { public void getUserInfo(final long uid, final ServiceCallback<User> callback) {
final Call<User> request = repository.getUserInfo(uid); final Call<WrappedUser> request = repository.getUserInfo(uid);
request.enqueue(new Callback<User>() { request.enqueue(new Callback<WrappedUser>() {
@Override @Override
public void onResponse(@NonNull final Call<User> call, @NonNull final Response<User> response) { public void onResponse(@NonNull final Call<WrappedUser> call, @NonNull final Response<WrappedUser> response) {
final User user = response.body(); final WrappedUser user = response.body();
if (user == null) { if (user == null) {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
callback.onSuccess(user); callback.onSuccess(user.getUser());
} }
@Override @Override
public void onFailure(@NonNull final Call<User> call, @NonNull final Throwable t) { public void onFailure(@NonNull final Call<WrappedUser> call, @NonNull final Throwable t) {
callback.onFailure(t); callback.onFailure(t);
} }
}); });
} }
public void getUsernameInfo(final String username, final ServiceCallback<User> callback) {
final Call<WrappedUser> request = repository.getUsernameInfo(username);
request.enqueue(new Callback<WrappedUser>() {
@Override
public void onResponse(@NonNull final Call<WrappedUser> call, @NonNull final Response<WrappedUser> response) {
final WrappedUser user = response.body();
if (user == null) {
callback.onSuccess(null);
return;
}
callback.onSuccess(user.getUser());
}
@Override
public void onFailure(@NonNull final Call<WrappedUser> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void getUserFriendship(final long uid, final ServiceCallback<FriendshipStatus> callback) {
final Call<FriendshipStatus> request = repository.getUserFriendship(uid);
request.enqueue(new Callback<FriendshipStatus>() {
@Override
public void onResponse(@NonNull final Call<FriendshipStatus> call, @NonNull final Response<FriendshipStatus> response) {
final FriendshipStatus status = response.body();
if (status == null) {
callback.onSuccess(null);
return;
}
callback.onSuccess(status);
}
@Override
public void onFailure(@NonNull final Call<FriendshipStatus> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public Call<UserSearchResponse> search(final String query) { public Call<UserSearchResponse> search(final String query) {
final float timezoneOffset = (float) TimeZone.getDefault().getRawOffset() / 1000; final float timezoneOffset = (float) TimeZone.getDefault().getRawOffset() / 1000;
return repository.search(timezoneOffset, query); return repository.search(timezoneOffset, query);

View File

@ -0,0 +1,21 @@
<?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/edit"
android:icon="@android:drawable/ic_menu_edit"
android:title="@string/edit_collection"
app:showAsAction="always" />
<item
android:id="@+id/delete"
android:icon="@android:drawable/ic_menu_delete"
android:title="@string/delete_collection"
app:showAsAction="always" />
<item
android:id="@+id/layout"
android:title="@string/layout"
app:showAsAction="never" />
</menu>

View File

@ -0,0 +1,22 @@
<?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/action_add"
android:icon="@drawable/ic_add"
android:title="@string/add_to_collection"
android:titleCondensed="@string/action_download"
app:showAsAction="always" />
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_cancel"
android:title="@string/remove_from_collection"
android:titleCondensed="@string/action_download"
app:showAsAction="always" />
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_download"
android:title="@string/action_download"
android:titleCondensed="@string/action_download"
app:showAsAction="always" />
</menu>

View File

@ -76,6 +76,16 @@
app:nullable="false" /> app:nullable="false" />
</action> </action>
<include app:graph="@navigation/saved_nav_graph" />
<action
android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<include app:graph="@navigation/user_search_nav_graph" /> <include app:graph="@navigation/user_search_nav_graph" />
<action <action

View File

@ -70,6 +70,16 @@
app:nullable="false" /> app:nullable="false" />
</action> </action>
<include app:graph="@navigation/saved_nav_graph" />
<action
android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<include app:graph="@navigation/notification_viewer_nav_graph" /> <include app:graph="@navigation/notification_viewer_nav_graph" />
<action <action

View File

@ -70,6 +70,16 @@
app:nullable="false" /> app:nullable="false" />
</action> </action>
<include app:graph="@navigation/saved_nav_graph" />
<action
android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<include app:graph="@navigation/notification_viewer_nav_graph" /> <include app:graph="@navigation/notification_viewer_nav_graph" />
<action <action

View File

@ -38,6 +38,16 @@
app:nullable="false" /> app:nullable="false" />
</action> </action>
<include app:graph="@navigation/saved_nav_graph" />
<action
android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<action <action
android:id="@+id/action_global_profileFragment" android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph"> app:destination="@id/profile_nav_graph">

View File

@ -38,6 +38,16 @@
app:nullable="false" /> app:nullable="false" />
</action> </action>
<include app:graph="@navigation/saved_nav_graph" />
<action
android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<action <action
android:id="@+id/action_global_profileFragment" android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph"> app:destination="@id/profile_nav_graph">

View File

@ -61,6 +61,16 @@
app:nullable="false" /> app:nullable="false" />
</action> </action>
<include app:graph="@navigation/saved_nav_graph" />
<action
android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<fragment <fragment
android:id="@+id/storyViewerFragment" android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment" android:name="awais.instagrabber.fragments.StoryViewerFragment"

View File

@ -86,7 +86,11 @@
<action <action
android:id="@+id/action_global_savedCollectionsFragment" android:id="@+id/action_global_savedCollectionsFragment"
app:destination="@id/saved_nav_graph" /> app:destination="@id/saved_nav_graph">
<argument
android:name="isSaving"
app:argType="boolean" />
</action>
<fragment <fragment
android:id="@+id/profileFragment" android:id="@+id/profileFragment"

View File

@ -78,6 +78,10 @@
android:name="awais.instagrabber.fragments.SavedCollectionsFragment" android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved" android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections" > tools:layout="@layout/fragment_saved_collections" >
<argument
android:name="isSaving"
app:argType="boolean"
android:defaultValue="false" />
<action <action
android:id="@+id/action_savedCollectionsFragment_to_collectionPostsFragment" android:id="@+id/action_savedCollectionsFragment_to_collectionPostsFragment"
app:destination="@id/collectionPostsFragment" /> app:destination="@id/collectionPostsFragment" />

View File

@ -99,6 +99,12 @@
<string name="remove_all_acc_warning">This will remove all added accounts from the app!\nTo remove just one account, long tap the account from the account switcher dialog.\nDo you want to continue?</string> <string name="remove_all_acc_warning">This will remove all added accounts from the app!\nTo remove just one account, long tap the account from the account switcher dialog.\nDo you want to continue?</string>
<string name="time_settings">Date format</string> <string name="time_settings">Date format</string>
<string name="saved_create_collection">Create new collection</string> <string name="saved_create_collection">Create new collection</string>
<string name="edit_collection">Edit collection name</string>
<string name="delete_collection">Delete collection</string>
<string name="delete_collection_confirm">Are you sure you want to delete this collection?</string>
<string name="delete_collection_note">All contained media will remain in other collections.</string>
<string name="add_to_collection">Add to collection...</string>
<string name="remove_from_collection">Remove from collection</string>
<string name="liked">Liked</string> <string name="liked">Liked</string>
<string name="saved">Saved</string> <string name="saved">Saved</string>
<string name="tagged">Tagged</string> <string name="tagged">Tagged</string>