mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-23 05:16:58 +00:00
Merge remote-tracking branch 'origin/dm-notifications-enhancements' into dm-notifications-enhancements
This commit is contained in:
commit
ea7236dcc1
@ -22,10 +22,13 @@ public class SavedCollectionsAdapter extends ListAdapter<SavedCollection, TopicC
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
||||
if (oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) {
|
||||
if (oldItem.getCoverMedias().size() == 0) return true;
|
||||
if (oldItem.getCoverMedias() != null && newItem.getCoverMedias() != null
|
||||
&& oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
@ -127,7 +127,7 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
||||
// binding.title.setTransitionName("title-" + topicCluster.getId());
|
||||
binding.cover.setTransitionName("cover-" + topicCluster.getId());
|
||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null
|
||||
? null
|
||||
? topicCluster.getCoverMedia()
|
||||
: topicCluster.getCoverMedias().get(0));
|
||||
if (thumbUrl == null) {
|
||||
binding.cover.setImageURI((String) null);
|
||||
|
@ -9,7 +9,7 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
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.ServiceCallback;
|
||||
|
||||
@ -35,13 +35,13 @@ public class DiscoverPostFetchService implements PostFetcher.PostFetchService {
|
||||
}
|
||||
moreAvailable = result.isMoreAvailable();
|
||||
topicalExploreRequest.setMaxId(result.getNextMaxId());
|
||||
final List<TopicalExploreItem> items = result.getItems();
|
||||
final List<WrappedMedia> items = result.getItems();
|
||||
final List<Media> posts;
|
||||
if (items == null) {
|
||||
posts = Collections.emptyList();
|
||||
} else {
|
||||
posts = items.stream()
|
||||
.map(TopicalExploreItem::getMedia)
|
||||
.map(WrappedMedia::getMedia)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
@ -5,131 +5,81 @@ import android.util.Log;
|
||||
|
||||
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.repositories.responses.FriendshipStatus;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.CookieUtils;
|
||||
import awais.instagrabber.utils.NetworkUtils;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
import awaisomereport.LogCollector;
|
||||
import awais.instagrabber.webservices.GraphQLService;
|
||||
import awais.instagrabber.webservices.ServiceCallback;
|
||||
import awais.instagrabber.webservices.UserService;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||
|
||||
public final class ProfileFetcher extends AsyncTask<Void, Void, User> {
|
||||
public final class ProfileFetcher extends AsyncTask<Void, Void, String> {
|
||||
private static final String TAG = ProfileFetcher.class.getSimpleName();
|
||||
private final UserService userService;
|
||||
private final GraphQLService graphQLService;
|
||||
|
||||
private final FetchListener<User> fetchListener;
|
||||
private final boolean isLoggedIn;
|
||||
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.isLoggedIn = isLoggedIn;
|
||||
this.fetchListener = fetchListener;
|
||||
userService = isLoggedIn ? UserService.getInstance() : null;
|
||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected User doInBackground(final Void... voids) {
|
||||
User result = null;
|
||||
protected String doInBackground(final Void... voids) {
|
||||
if (isLoggedIn) {
|
||||
userService.getUsernameInfo(userName, new ServiceCallback<User>() {
|
||||
@Override
|
||||
public void onSuccess(final User user) {
|
||||
Log.d("austin_debug", user.getUsername() + " " + userName);
|
||||
userService.getUserFriendship(user.getPk(), new ServiceCallback<FriendshipStatus>() {
|
||||
@Override
|
||||
public void onSuccess(final FriendshipStatus status) {
|
||||
user.setFriendshipStatus(status);
|
||||
fetchListener.onResult(user);
|
||||
}
|
||||
|
||||
try {
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/" + userName + "/?__a=1").openConnection();
|
||||
conn.setUseCaches(true);
|
||||
conn.connect();
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "Error", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
final String json = NetworkUtils.readFromConnection(conn);
|
||||
// Log.d(TAG, "doInBackground: " + json);
|
||||
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);
|
||||
@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);
|
||||
}
|
||||
|
||||
return result;
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "Error", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
return "yeah";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final User result) {
|
||||
if (fetchListener != null) fetchListener.onResult(result);
|
||||
protected void onPreExecute() {
|
||||
if (fetchListener != null) fetchListener.doBefore();
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@ -17,11 +18,14 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.activity.OnBackPressedDispatcher;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.core.content.PermissionChecker;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
@ -49,20 +53,23 @@ import awais.instagrabber.asyncs.SavedPostFetchService;
|
||||
import awais.instagrabber.customviews.PrimaryActionModeCallback;
|
||||
import awais.instagrabber.databinding.FragmentCollectionPostsBinding;
|
||||
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
|
||||
import awais.instagrabber.fragments.CollectionPostsFragmentDirections;
|
||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||
import awais.instagrabber.models.enums.PostItemType;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.saved.SavedCollection;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.CookieUtils;
|
||||
import awais.instagrabber.utils.DownloadUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.webservices.CollectionService;
|
||||
import awais.instagrabber.webservices.ServiceCallback;
|
||||
|
||||
import static androidx.core.content.PermissionChecker.checkSelfPermission;
|
||||
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
|
||||
|
||||
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_FOR_SELECTION = 8030;
|
||||
|
||||
@ -75,6 +82,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
||||
private Set<Media> selectedFeedModels;
|
||||
private Media downloadFeedModel;
|
||||
private int downloadChildPosition = -1;
|
||||
private CollectionService collectionService;
|
||||
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_SAVED_POSTS_LAYOUT);
|
||||
|
||||
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
||||
@ -84,7 +92,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
||||
}
|
||||
};
|
||||
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
|
||||
public void onDestroy(final ActionMode mode) {
|
||||
binding.posts.endSelection();
|
||||
@ -241,6 +249,11 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
||||
setSharedElementEnterTransition(transitionSet);
|
||||
postponeEnterTransition();
|
||||
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
|
||||
@ -267,7 +280,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
||||
|
||||
@Override
|
||||
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
|
||||
@ -276,6 +289,58 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
||||
showPostsLayoutPreferences();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -372,7 +437,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
|
||||
|
||||
private void setupCover() {
|
||||
final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMedias() == null
|
||||
? null
|
||||
? savedCollection.getCoverMedia()
|
||||
: savedCollection.getCoverMedias().get(0));
|
||||
final DraweeController controller = Fresco
|
||||
.newDraweeControllerBuilder()
|
||||
|
@ -99,9 +99,12 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
stopCurrentExecutor();
|
||||
try {
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
stopCurrentExecutor();
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -83,8 +83,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
try {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
Log.e(TAG, "Error fetching list (double, following)", t);
|
||||
}
|
||||
};
|
||||
@ -109,8 +112,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
try {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
Log.e(TAG, "Error fetching list (double, follower)", t);
|
||||
}
|
||||
};
|
||||
@ -221,8 +227,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
try {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
Log.e(TAG, "Error fetching list (single)", t);
|
||||
}
|
||||
};
|
||||
|
@ -271,8 +271,11 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
try {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
}
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
break;
|
||||
@ -287,8 +290,11 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
try {
|
||||
binding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -49,7 +49,10 @@ import androidx.core.view.ViewCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@ -130,6 +133,17 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im
|
||||
private PopupMenu optionsPopup;
|
||||
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() {
|
||||
//
|
||||
// @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
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@ -666,7 +691,10 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im
|
||||
handleSaveUnsaveResourceLiveData(viewModel.toggleSave());
|
||||
});
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.SavedStateHandle;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.FragmentNavigator;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
@ -27,7 +30,6 @@ import awais.instagrabber.activities.MainActivity;
|
||||
import awais.instagrabber.adapters.SavedCollectionsAdapter;
|
||||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
|
||||
import awais.instagrabber.databinding.FragmentSavedCollectionsBinding;
|
||||
import awais.instagrabber.repositories.responses.StoryStickerResponse;
|
||||
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.CookieUtils;
|
||||
@ -40,12 +42,14 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||
|
||||
public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
||||
private static final String TAG = "SavedCollectionsFragment";
|
||||
public static boolean pleaseRefresh = false;
|
||||
|
||||
private MainActivity fragmentActivity;
|
||||
private CoordinatorLayout root;
|
||||
private FragmentSavedCollectionsBinding binding;
|
||||
private SavedCollectionsViewModel savedCollectionsViewModel;
|
||||
private boolean shouldRefresh = true;
|
||||
private boolean isSaving;
|
||||
private ProfileService profileService;
|
||||
|
||||
@Override
|
||||
@ -82,6 +86,12 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
|
||||
inflater.inflate(R.menu.saved_collection_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (pleaseRefresh) onRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
if (item.getItemId() == R.id.add) {
|
||||
@ -120,6 +130,8 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
|
||||
private void init() {
|
||||
setupTopics();
|
||||
fetchTopics(null);
|
||||
final SavedCollectionsFragmentArgs fragmentArgs = SavedCollectionsFragmentArgs.fromBundle(getArguments());
|
||||
isSaving = fragmentArgs.getIsSaving();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -131,11 +143,18 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa
|
||||
savedCollectionsViewModel = new ViewModelProvider(fragmentActivity).get(SavedCollectionsViewModel.class);
|
||||
binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2)));
|
||||
final SavedCollectionsAdapter adapter = new SavedCollectionsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> {
|
||||
final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder()
|
||||
.addSharedElement(cover, "collection-" + topicCluster.getId());
|
||||
final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections
|
||||
.actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor);
|
||||
NavHostFragment.findNavController(this).navigate(action, builder.build());
|
||||
final NavController navController = NavHostFragment.findNavController(this);
|
||||
if (isSaving) {
|
||||
setNavControllerResult(navController, topicCluster.getId());
|
||||
navController.navigateUp();
|
||||
}
|
||||
else {
|
||||
final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder()
|
||||
.addSharedElement(cover, "collection-" + topicCluster.getId());
|
||||
final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections
|
||||
.actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor);
|
||||
navController.navigate(action, builder.build());
|
||||
}
|
||||
});
|
||||
binding.topicsRecyclerView.setAdapter(adapter);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -236,8 +236,11 @@ public class StoryViewerFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
|
||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
Log.e(TAG, "onFailure: ", t);
|
||||
try {
|
||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
Log.e(TAG, "onFailure: ", t);
|
||||
}
|
||||
catch(Throwable e) {}
|
||||
}
|
||||
});
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
@ -538,7 +538,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
|
||||
private void fetchProfileDetails() {
|
||||
if (TextUtils.isEmpty(username)) return;
|
||||
new ProfileFetcher(username.trim().substring(1), profileModel -> {
|
||||
new ProfileFetcher(username.trim().substring(1), isLoggedIn, profileModel -> {
|
||||
if (getContext() == null) return;
|
||||
this.profileModel = profileModel;
|
||||
setProfileDetails();
|
||||
@ -946,7 +946,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
}
|
||||
});
|
||||
profileDetailsBinding.btnSaved.setOnClickListener(v -> {
|
||||
final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment();
|
||||
final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(false);
|
||||
NavHostFragment.findNavController(this).navigate(action);
|
||||
});
|
||||
profileDetailsBinding.btnLiked.setOnClickListener(v -> {
|
||||
|
@ -26,7 +26,9 @@ import awais.instagrabber.dialogs.TimeSettingsDialog;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.CookieUtils;
|
||||
import awais.instagrabber.utils.DirectoryChooser;
|
||||
import awais.instagrabber.utils.LocaleUtils;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
import awais.instagrabber.utils.UserAgentUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
@ -111,6 +113,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
|
||||
preference.setEntryValues(values);
|
||||
preference.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
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 preference;
|
||||
|
@ -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);
|
||||
}
|
@ -4,9 +4,13 @@ import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface GraphQLRepository {
|
||||
@GET("/graphql/query/")
|
||||
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams);
|
||||
|
||||
@GET("/{username}/?__a=1")
|
||||
Call<String> getUser(@Path("username") String username);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package awais.instagrabber.repositories;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import awais.instagrabber.repositories.responses.WrappedFeedResponse;
|
||||
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse;
|
||||
import awais.instagrabber.repositories.responses.UserFeedResponse;
|
||||
import retrofit2.Call;
|
||||
@ -18,11 +19,11 @@ public interface ProfileRepository {
|
||||
Call<UserFeedResponse> fetch(@Path("uid") final long uid, @QueryMap Map<String, String> queryParams);
|
||||
|
||||
@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}/")
|
||||
Call<UserFeedResponse> fetchSavedCollection(@Path("collectionId") final String collectionId,
|
||||
@QueryMap Map<String, String> queryParams);
|
||||
Call<WrappedFeedResponse> fetchSavedCollection(@Path("collectionId") final String collectionId,
|
||||
@QueryMap Map<String, String> queryParams);
|
||||
|
||||
@GET("/api/v1/feed/liked/")
|
||||
Call<UserFeedResponse> fetchLiked(@QueryMap Map<String, String> queryParams);
|
||||
|
@ -1,7 +1,8 @@
|
||||
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.WrappedUser;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Path;
|
||||
@ -10,7 +11,13 @@ import retrofit2.http.Query;
|
||||
public interface UserRepository {
|
||||
|
||||
@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/")
|
||||
Call<UserSearchResponse> search(@Query("timezone_offset") float timezoneOffset,
|
||||
|
@ -10,7 +10,7 @@ public class User implements Serializable {
|
||||
private final boolean isPrivate;
|
||||
private final String profilePicUrl;
|
||||
private final String profilePicId;
|
||||
private final FriendshipStatus friendshipStatus;
|
||||
private FriendshipStatus friendshipStatus;
|
||||
private final boolean isVerified;
|
||||
private final boolean hasAnonymousProfilePicture;
|
||||
private final boolean isUnpublished;
|
||||
@ -102,6 +102,10 @@ public class User implements Serializable {
|
||||
return friendshipStatus;
|
||||
}
|
||||
|
||||
public void setFriendshipStatus(final FriendshipStatus friendshipStatus) {
|
||||
this.friendshipStatus = friendshipStatus;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return isVerified;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package awais.instagrabber.repositories.responses.discover;
|
||||
|
||||
import java.util.List;
|
||||
import awais.instagrabber.repositories.responses.WrappedMedia;
|
||||
|
||||
public class TopicalExploreFeedResponse {
|
||||
private final boolean moreAvailable;
|
||||
@ -9,7 +10,7 @@ public class TopicalExploreFeedResponse {
|
||||
private final String status;
|
||||
private final int numResults;
|
||||
private final List<TopicCluster> clusters;
|
||||
private final List<TopicalExploreItem> items;
|
||||
private final List<WrappedMedia> items;
|
||||
|
||||
public TopicalExploreFeedResponse(final boolean moreAvailable,
|
||||
final String nextMaxId,
|
||||
@ -17,7 +18,7 @@ public class TopicalExploreFeedResponse {
|
||||
final String status,
|
||||
final int numResults,
|
||||
final List<TopicCluster> clusters,
|
||||
final List<TopicalExploreItem> items) {
|
||||
final List<WrappedMedia> items) {
|
||||
this.moreAvailable = moreAvailable;
|
||||
this.nextMaxId = nextMaxId;
|
||||
this.maxId = maxId;
|
||||
@ -51,7 +52,7 @@ public class TopicalExploreFeedResponse {
|
||||
return clusters;
|
||||
}
|
||||
|
||||
public List<TopicalExploreItem> getItems() {
|
||||
public List<WrappedMedia> getItems() {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -10,17 +10,20 @@ public class SavedCollection implements Serializable {
|
||||
private final String collectionName;
|
||||
private final String collectionType;
|
||||
private final int collectionMediacount;
|
||||
private final Media coverMedia;
|
||||
private final List<Media> coverMediaList;
|
||||
|
||||
public SavedCollection(final String collectionId,
|
||||
final String collectionName,
|
||||
final String collectionType,
|
||||
final int collectionMediacount,
|
||||
final Media coverMedia,
|
||||
final List<Media> coverMediaList) {
|
||||
this.collectionId = collectionId;
|
||||
this.collectionName = collectionName;
|
||||
this.collectionType = collectionType;
|
||||
this.collectionMediacount = collectionMediacount;
|
||||
this.coverMedia = coverMedia;
|
||||
this.coverMediaList = coverMediaList;
|
||||
}
|
||||
|
||||
@ -40,6 +43,11 @@ public class SavedCollection implements Serializable {
|
||||
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() {
|
||||
return coverMediaList;
|
||||
}
|
||||
|
@ -20,49 +20,21 @@ public class UserAgentUtils {
|
||||
// use APKpure, assume x86
|
||||
private static final String igVersion = "169.3.0.30.135";
|
||||
private static final String igVersionCode = "264009054";
|
||||
// https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json
|
||||
// presumed constant, so no need to update
|
||||
// only pick the ones that has width 1440 for maximum download quality
|
||||
public static final String[] devices = {
|
||||
"25/7.1.1; 440dpi; 1080x1920; Xiaomi; Mi Note 3; jason; qcom",
|
||||
"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",
|
||||
// https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json
|
||||
"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",
|
||||
"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",
|
||||
"26/8.0.0; 420dpi; 1080x1920; samsung; SM-A720F; a7y17lte; samsungexynos7880",
|
||||
"24/7.0; 640dpi; 1440x2560; samsung; SM-G920F; zeroflte; samsungexynos7420",
|
||||
"24/7.0; 420dpi; 1080x1920; samsung; SM-J730FM; j7y17lte; samsungexynos7870",
|
||||
"26/8.0.0; 480dpi; 1080x2076; samsung; SM-G960F; starlte; samsungexynos9810",
|
||||
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-N950F; greatlte; samsungexynos8895",
|
||||
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-A730F; jackpot2lte; samsungexynos7885",
|
||||
"26/8.0.0; 420dpi; 1080x2094; samsung; SM-A605FN; a6plte; qcom",
|
||||
"26/8.0.0; 480dpi; 1080x1920; HUAWEI/HONOR; STF-L09; HWSTF; hi3660",
|
||||
"27/8.1.0; 480dpi; 1080x2280; HUAWEI/HONOR; COL-L29; HWCOL; kirin970",
|
||||
"26/8.0.0; 480dpi; 1080x2032; HUAWEI/HONOR; LLD-L31; HWLLD-H; hi6250",
|
||||
"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"
|
||||
// https://github.com/mimmi20/BrowserDetector/tree/master
|
||||
"28/9; 560dpi; 1440x2792; samsung; SM-N960F; crownlte; samsungexynos9810",
|
||||
// mgp25
|
||||
"23/6.0.1; 640dpi; 1440x2392; LGE/lge; RS988; h1; h1",
|
||||
"24/7.0; 640dpi; 1440x2560; HUAWEI; LON-L29; HWLON; hi3660",
|
||||
"23/6.0.1; 640dpi; 1440x2560; ZTE; ZTE A2017U; ailsa_ii; qcom",
|
||||
"23/6.0.1; 640dpi; 1440x2560; samsung; SM-G935F; hero2lte; samsungexynos8890",
|
||||
"23/6.0.1; 640dpi; 1440x2560; samsung; SM-G930F; herolte; samsungexynos8890"
|
||||
};
|
||||
|
||||
@NonNull
|
||||
|
@ -83,6 +83,7 @@ public class AppStateViewModel extends AndroidViewModel {
|
||||
if (TextUtils.isEmpty(username)) return;
|
||||
new ProfileFetcher(
|
||||
username.trim().substring(1),
|
||||
true,
|
||||
currentUser::postValue
|
||||
).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
@ -188,27 +188,33 @@ public class PostViewV2ViewModel extends ViewModel {
|
||||
@NonNull
|
||||
public LiveData<Resource<Object>> toggleSave() {
|
||||
if (!media.hasViewerSaved()) {
|
||||
return save();
|
||||
return save(null, false);
|
||||
}
|
||||
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<>();
|
||||
data.postValue(Resource.loading(null));
|
||||
mediaService.save(media.getPk(), getSaveUnsaveCallback(data));
|
||||
mediaService.save(media.getPk(), collection, getSaveUnsaveCallback(data, ignoreSaveState));
|
||||
return data;
|
||||
}
|
||||
|
||||
public LiveData<Resource<Object>> unsave() {
|
||||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||
data.postValue(Resource.loading(null));
|
||||
mediaService.unsave(media.getPk(), getSaveUnsaveCallback(data));
|
||||
mediaService.unsave(media.getPk(), getSaveUnsaveCallback(data, false));
|
||||
return data;
|
||||
}
|
||||
|
||||
@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>() {
|
||||
@Override
|
||||
public void onSuccess(final Boolean result) {
|
||||
@ -217,7 +223,7 @@ public class PostViewV2ViewModel extends ViewModel {
|
||||
return;
|
||||
}
|
||||
data.postValue(Resource.success(true));
|
||||
media.setHasViewerSaved(!media.hasViewerSaved());
|
||||
if (!ignoreSaveState) media.setHasViewerSaved(!media.hasViewerSaved());
|
||||
saved.postValue(media.hasViewerSaved());
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ public abstract class BaseService {
|
||||
final Gson gson = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer())
|
||||
.setLenient()
|
||||
.create();
|
||||
builder = new Retrofit.Builder()
|
||||
.addConverterFactory(ScalarsConverterFactory.create())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -102,33 +102,37 @@ public class MediaService extends BaseService {
|
||||
|
||||
public void like(final String mediaId,
|
||||
final ServiceCallback<Boolean> callback) {
|
||||
action(mediaId, "like", callback);
|
||||
action(mediaId, "like", null, callback);
|
||||
}
|
||||
|
||||
public void unlike(final String mediaId,
|
||||
final ServiceCallback<Boolean> callback) {
|
||||
action(mediaId, "unlike", callback);
|
||||
action(mediaId, "unlike", null, callback);
|
||||
}
|
||||
|
||||
public void save(final String mediaId,
|
||||
final String collection,
|
||||
final ServiceCallback<Boolean> callback) {
|
||||
action(mediaId, "save", callback);
|
||||
action(mediaId, "save", collection, callback);
|
||||
}
|
||||
|
||||
public void unsave(final String mediaId,
|
||||
final ServiceCallback<Boolean> callback) {
|
||||
action(mediaId, "unsave", callback);
|
||||
action(mediaId, "unsave", null, callback);
|
||||
}
|
||||
|
||||
private void action(final String mediaId,
|
||||
final String action,
|
||||
final String collection,
|
||||
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("_csrftoken", csrfToken);
|
||||
form.put("_uid", userId);
|
||||
form.put("_uuid", deviceUuid);
|
||||
// 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 Call<String> request = repository.action(action, mediaId, signedForm);
|
||||
request.enqueue(new Callback<String>() {
|
||||
|
@ -4,12 +4,19 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.Collections;
|
||||
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.ProfileRepository;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||
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.utils.TextUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
@ -76,30 +83,40 @@ public class ProfileService extends BaseService {
|
||||
final String collectionId,
|
||||
final ServiceCallback<PostsFetchResponse> callback) {
|
||||
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
|
||||
Call<UserFeedResponse> request = null;
|
||||
Call<WrappedFeedResponse> request = null;
|
||||
if (!TextUtils.isEmpty(maxId)) {
|
||||
builder.put("max_id", maxId);
|
||||
}
|
||||
if (TextUtils.isEmpty(collectionId) || collectionId.equals("ALL_MEDIA_AUTO_COLLECTION")) request = repository.fetchSaved(builder.build());
|
||||
else request = repository.fetchSavedCollection(collectionId, builder.build());
|
||||
request.enqueue(new Callback<UserFeedResponse>() {
|
||||
request.enqueue(new Callback<WrappedFeedResponse>() {
|
||||
@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;
|
||||
final UserFeedResponse userFeedResponse = response.body();
|
||||
final WrappedFeedResponse userFeedResponse = response.body();
|
||||
if (userFeedResponse == null) {
|
||||
callback.onSuccess(null);
|
||||
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(
|
||||
userFeedResponse.getItems(),
|
||||
posts,
|
||||
userFeedResponse.isMoreAvailable(),
|
||||
userFeedResponse.getNextMaxId()
|
||||
));
|
||||
}
|
||||
|
||||
@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) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
@ -171,7 +188,6 @@ public class ProfileService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void fetchLiked(final String maxId,
|
||||
final ServiceCallback<PostsFetchResponse> callback) {
|
||||
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
|
||||
|
@ -5,8 +5,10 @@ import androidx.annotation.NonNull;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import awais.instagrabber.repositories.UserRepository;
|
||||
import awais.instagrabber.repositories.responses.FriendshipStatus;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.repositories.responses.UserSearchResponse;
|
||||
import awais.instagrabber.repositories.responses.WrappedUser;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
@ -34,25 +36,66 @@ public class UserService extends BaseService {
|
||||
}
|
||||
|
||||
public void getUserInfo(final long uid, final ServiceCallback<User> callback) {
|
||||
final Call<User> request = repository.getUserInfo(uid);
|
||||
request.enqueue(new Callback<User>() {
|
||||
final Call<WrappedUser> request = repository.getUserInfo(uid);
|
||||
request.enqueue(new Callback<WrappedUser>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull final Call<User> call, @NonNull final Response<User> response) {
|
||||
final User user = response.body();
|
||||
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);
|
||||
callback.onSuccess(user.getUser());
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
final float timezoneOffset = (float) TimeZone.getDefault().getRawOffset() / 1000;
|
||||
return repository.search(timezoneOffset, query);
|
||||
|
21
app/src/main/res/menu/collection_posts_menu.xml
Normal file
21
app/src/main/res/menu/collection_posts_menu.xml
Normal 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>
|
22
app/src/main/res/menu/saved_collection_select_menu.xml
Normal file
22
app/src/main/res/menu/saved_collection_select_menu.xml
Normal 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>
|
@ -76,6 +76,16 @@
|
||||
app:nullable="false" />
|
||||
</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" />
|
||||
|
||||
<action
|
||||
|
@ -70,6 +70,16 @@
|
||||
app:nullable="false" />
|
||||
</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" />
|
||||
|
||||
<action
|
||||
|
@ -70,6 +70,16 @@
|
||||
app:nullable="false" />
|
||||
</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" />
|
||||
|
||||
<action
|
||||
|
@ -38,6 +38,16 @@
|
||||
app:nullable="false" />
|
||||
</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
|
||||
android:id="@+id/action_global_profileFragment"
|
||||
app:destination="@id/profile_nav_graph">
|
||||
|
@ -38,6 +38,16 @@
|
||||
app:nullable="false" />
|
||||
</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
|
||||
android:id="@+id/action_global_profileFragment"
|
||||
app:destination="@id/profile_nav_graph">
|
||||
|
@ -61,6 +61,16 @@
|
||||
app:nullable="false" />
|
||||
</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
|
||||
android:id="@+id/storyViewerFragment"
|
||||
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||
|
@ -86,7 +86,11 @@
|
||||
|
||||
<action
|
||||
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
|
||||
android:id="@+id/profileFragment"
|
||||
|
@ -78,6 +78,10 @@
|
||||
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
|
||||
android:label="@string/saved"
|
||||
tools:layout="@layout/fragment_saved_collections" >
|
||||
<argument
|
||||
android:name="isSaving"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<action
|
||||
android:id="@+id/action_savedCollectionsFragment_to_collectionPostsFragment"
|
||||
app:destination="@id/collectionPostsFragment" />
|
||||
|
@ -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="time_settings">Date format</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="saved">Saved</string>
|
||||
<string name="tagged">Tagged</string>
|
||||
|
Loading…
Reference in New Issue
Block a user