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

story archive, so half of #460

This commit is contained in:
Austin Huang 2020-12-29 17:33:44 -05:00
parent 6e4fa9fdbf
commit 8ebe8dd0f9
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
23 changed files with 270 additions and 105 deletions

View File

@ -15,7 +15,6 @@ import java.util.List;
import awais.instagrabber.adapters.viewholder.StoryListViewHolder; import awais.instagrabber.adapters.viewholder.StoryListViewHolder;
import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.databinding.ItemNotificationBinding;
import awais.instagrabber.models.FeedStoryModel; import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, StoryListViewHolder> { public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, StoryListViewHolder> {
@ -49,48 +48,11 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
@Override @Override
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
final FeedStoryModel model = getItem(position); final FeedStoryModel model = getItem(position);
holder.bind(model, listener); holder.bind(model, position, listener);
}
@Override
public void submitList(@Nullable final List<FeedStoryModel> list, @Nullable final Runnable commitCallback) {
if (list == null) {
super.submitList(null, commitCallback);
return;
}
super.submitList(sort(list), commitCallback);
}
@Override
public void submitList(@Nullable final List<FeedStoryModel> list) {
if (list == null) {
super.submitList(null);
return;
}
super.submitList(sort(list));
}
private List<FeedStoryModel> sort(final List<FeedStoryModel> list) {
final List<FeedStoryModel> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
int result;
switch (Utils.settingsHelper.getString(Constants.STORY_SORT)) {
case "1":
result = o1.getTimestamp() > o2.getTimestamp() ? -1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1);
break;
case "2":
result = o1.getTimestamp() > o2.getTimestamp() ? 1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : -1);
break;
default:
result = 0;
}
return result;
});
return listCopy;
} }
public interface OnFeedStoryClickListener { public interface OnFeedStoryClickListener {
void onFeedStoryClick(final FeedStoryModel model); void onFeedStoryClick(final FeedStoryModel model, final int position);
void onProfileClick(final String username); void onProfileClick(final String username);
} }

View File

@ -42,12 +42,12 @@ public final class HighlightStoriesListAdapter extends ListAdapter<HighlightMode
@Override @Override
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
final HighlightModel model = getItem(position); final HighlightModel model = getItem(position);
holder.bind(model, listener); holder.bind(model, position, listener);
} }
public interface OnHighlightStoryClickListener { public interface OnHighlightStoryClickListener {
void onHighlightClick(HighlightModel model); void onHighlightClick(final HighlightModel model, final int position);
void onProfileClick(String username); void onProfileClick(final String username);
} }
} }

View File

@ -21,9 +21,14 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
} }
public void bind(final FeedStoryModel model, public void bind(final FeedStoryModel model,
final int position,
final OnFeedStoryClickListener notificationClickListener) { final OnFeedStoryClickListener notificationClickListener) {
if (model == null) return; if (model == null) return;
binding.tvComment.setVisibility(View.GONE);
final int storiesCount = model.getMediaCount();
binding.tvComment.setVisibility(View.VISIBLE);
binding.tvComment.setText(itemView.getResources().getQuantityString(R.plurals.stories_count, storiesCount, storiesCount));
binding.tvSubComment.setVisibility(View.GONE); binding.tvSubComment.setVisibility(View.GONE);
binding.tvDate.setText(model.getDateTime()); binding.tvDate.setText(model.getDateTime());
@ -35,23 +40,28 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
notificationClickListener.onProfileClick(model.getProfileModel().getUsername()); notificationClickListener.onProfileClick(model.getProfileModel().getUsername());
}); });
binding.ivPreviewPic.setVisibility(View.VISIBLE);
if (model.getFirstStoryModel() != null) {
binding.ivPreviewPic.setVisibility(View.VISIBLE); binding.ivPreviewPic.setVisibility(View.VISIBLE);
binding.ivPreviewPic.setImageURI(model.getFirstStoryModel().getThumbnail()); binding.ivPreviewPic.setImageURI(model.getFirstStoryModel().getThumbnail());
binding.ivPreviewPic.setOnClickListener(v -> { }
if (notificationClickListener == null) return; else binding.ivPreviewPic.setVisibility(View.INVISIBLE);
notificationClickListener.onFeedStoryClick(model);
});
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return; if (notificationClickListener == null) return;
notificationClickListener.onFeedStoryClick(model); notificationClickListener.onFeedStoryClick(model, position);
}); });
} }
public void bind(final HighlightModel model, public void bind(final HighlightModel model,
final int position,
final OnHighlightStoryClickListener notificationClickListener) { final OnHighlightStoryClickListener notificationClickListener) {
if (model == null) return; if (model == null) return;
binding.tvComment.setVisibility(View.GONE);
final int storiesCount = model.getMediaCount();
binding.tvComment.setVisibility(View.VISIBLE);
binding.tvComment.setText(itemView.getResources().getQuantityString(R.plurals.stories_count, storiesCount, storiesCount));
binding.tvSubComment.setVisibility(View.GONE); binding.tvSubComment.setVisibility(View.GONE);
binding.tvUsername.setText(model.getDateTime()); binding.tvUsername.setText(model.getDateTime());
@ -63,7 +73,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return; if (notificationClickListener == null) return;
notificationClickListener.onHighlightClick(model); notificationClickListener.onHighlightClick(model, position);
}); });
} }
} }

View File

@ -545,7 +545,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (!hasStories) return; if (!hasStories) return;
// show stories // show stories
final NavDirections action = HashTagFragmentDirections final NavDirections action = HashTagFragmentDirections
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName()); .actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName(), false);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
}); });
} }

View File

@ -160,7 +160,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
if (isComment && !isLoggedIn) { if (isComment && !isLoggedIn) {
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!TextUtils.isEmpty(endCursor)) if (!TextUtils.isEmpty(endCursor))
graphQLService.fetchCommentLikers(postId, null, acb); graphQLService.fetchCommentLikers(postId, endCursor, acb);
endCursor = null; endCursor = null;
}); });
binding.rvLikes.addOnScrollListener(lazyLoader); binding.rvLikes.addOnScrollListener(lazyLoader);

View File

@ -523,7 +523,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
if (hasStories) { if (hasStories) {
// show stories // show stories
final NavDirections action = LocationFragmentDirections final NavDirections action = LocationFragmentDirections
.actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName()); .actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName(), false);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
} }
}); });

View File

@ -2,12 +2,16 @@ package awais.instagrabber.fragments;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
@ -15,34 +19,47 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedStoriesListAdapter; import awais.instagrabber.adapters.FeedStoriesListAdapter;
import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener; import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener;
import awais.instagrabber.adapters.HighlightStoriesListAdapter;
import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentStoryListViewerBinding; import awais.instagrabber.databinding.FragmentStoryListViewerBinding;
import awais.instagrabber.fragments.main.FeedFragment;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.models.FeedStoryModel; import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.utils.Constants; import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.viewmodels.StoriesViewModel; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.viewmodels.ArchivesViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import awais.instagrabber.webservices.StoriesService.ArchiveFetchResponse;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "StoryListViewerFragment"; private static final String TAG = "StoryListViewerFragment";
private AppCompatActivity fragmentActivity;
private FragmentStoryListViewerBinding binding; private FragmentStoryListViewerBinding binding;
private SwipeRefreshLayout root; private SwipeRefreshLayout root;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private StoriesViewModel storiesViewModel; private FeedStoriesViewModel feedStoriesViewModel;
private ArchivesViewModel archivesViewModel;
private StoriesService storiesService; private StoriesService storiesService;
private Context context; private Context context;
private String type; private String type, endCursor = null;
private RecyclerLazyLoader lazyLoader;
private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() { private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() {
@Override @Override
public void onFeedStoryClick(final FeedStoryModel model) { public void onFeedStoryClick(final FeedStoryModel model, final int position) {
if (model == null) return; if (model == null) return;
// final NavDirections action = StoryListNavGraphDirections.actionStoryListFragmentToStoryViewerFragment(position, null, false, false, null, null); final NavDirections action = StoryListViewerFragmentDirections.actionStoryListFragmentToStoryViewerFragment(position, null, false, false, null, null, false);
// NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action);
} }
@Override @Override
@ -51,9 +68,47 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
} }
}; };
private final OnHighlightStoryClickListener archiveClickListener = new OnHighlightStoryClickListener() {
@Override
public void onHighlightClick(final HighlightModel model, final int position) {
if (model == null) return;
final NavDirections action = StoryListViewerFragmentDirections.actionStoryListFragmentToStoryViewerFragment(
position, getString(R.string.action_archive), false, false, null, null, true);
NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action);
}
@Override
public void onProfileClick(final String username) {
openProfile(username);
}
};
private final ServiceCallback<ArchiveFetchResponse> cb = new ServiceCallback<ArchiveFetchResponse>() {
@Override
public void onSuccess(final ArchiveFetchResponse result) {
endCursor = result.getNextCursor();
final List<HighlightModel> models = archivesViewModel.getList().getValue();
final List<HighlightModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.addAll(result.getResult());
archivesViewModel.getList().postValue(modelsCopy);
binding.swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
try {
final Context context = getContext();
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch (Exception e) {}
}
};
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) requireActivity();
context = getContext(); context = getContext();
if (context == null) return; if (context == null) return;
storiesService = StoriesService.getInstance(); storiesService = StoriesService.getInstance();
@ -78,26 +133,54 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
shouldRefresh = false; shouldRefresh = false;
} }
@Override
public void onDestroy() {
if (archivesViewModel != null) archivesViewModel.getList().postValue(null);
super.onDestroy();
}
private void init() { private void init() {
final Context context = getContext(); final Context context = getContext();
if (getArguments() == null) return; if (getArguments() == null) return;
final StoryListViewerFragmentArgs fragmentArgs = StoryListViewerFragmentArgs.fromBundle(getArguments()); final StoryListViewerFragmentArgs fragmentArgs = StoryListViewerFragmentArgs.fromBundle(getArguments());
type = fragmentArgs.getType(); type = fragmentArgs.getType();
binding.swipeRefreshLayout.setOnRefreshListener(this); binding.swipeRefreshLayout.setOnRefreshListener(this);
storiesViewModel = new ViewModelProvider(this).get(StoriesViewModel.class); final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
// final NotificationsAdapter adapter = new NotificationsAdapter(clickListener, mentionClickListener); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
binding.rvStories.setLayoutManager(new LinearLayoutManager(context)); if (type == "feed") {
// binding.rvStories.setAdapter(adapter); if (actionBar != null) actionBar.setTitle(R.string.feed_stories);
// storiesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); feedStoriesViewModel = new ViewModelProvider(this).get(FeedStoriesViewModel.class);
final FeedStoriesListAdapter adapter = new FeedStoriesListAdapter(clickListener);
binding.rvStories.setLayoutManager(layoutManager);
binding.rvStories.setAdapter(adapter);
feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
}
else {
if (actionBar != null) actionBar.setTitle(R.string.action_archive);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!TextUtils.isEmpty(endCursor)) onRefresh();
endCursor = null;
});
binding.rvStories.addOnScrollListener(lazyLoader);
archivesViewModel = new ViewModelProvider(fragmentActivity).get(ArchivesViewModel.class);
final HighlightStoriesListAdapter adapter = new HighlightStoriesListAdapter(archiveClickListener);
binding.rvStories.setLayoutManager(layoutManager);
binding.rvStories.setAdapter(adapter);
archivesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
}
onRefresh(); onRefresh();
} }
@Override @Override
public void onRefresh() { public void onRefresh() {
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
if (type == "feed") {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
// storiesViewModel.getList().postValue(); feedStoriesViewModel.getList().postValue(FeedFragment.feedStories);
}
else if (type == "archive") {
storiesService.fetchArchive(endCursor, cb);
}
} }
private void openProfile(final String username) { private void openProfile(final String username) {

View File

@ -91,6 +91,7 @@ import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.ArchivesViewModel;
import awais.instagrabber.viewmodels.FeedStoriesViewModel; import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.viewmodels.HighlightsViewModel; import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.viewmodels.StoriesViewModel; import awais.instagrabber.viewmodels.StoriesViewModel;
@ -138,7 +139,7 @@ public class StoryViewerFragment extends Fragment {
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private StoryViewerFragmentArgs fragmentArgs; private StoryViewerFragmentArgs fragmentArgs;
private ViewModel viewModel; private ViewModel viewModel;
private boolean isHighlight; private boolean isHighlight, isArchive;
private final String cookie = settingsHelper.getString(Constants.COOKIE); private final String cookie = settingsHelper.getString(Constants.COOKIE);
@ -255,9 +256,12 @@ public class StoryViewerFragment extends Fragment {
currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex(); currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex();
highlight = fragmentArgs.getHighlight(); highlight = fragmentArgs.getHighlight();
isHighlight = !TextUtils.isEmpty(highlight); isHighlight = !TextUtils.isEmpty(highlight);
isArchive = fragmentArgs.getIsArchive();
if (currentFeedStoryIndex >= 0) { if (currentFeedStoryIndex >= 0) {
viewModel = isHighlight viewModel = isHighlight
? new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class) ? isArchive
? new ViewModelProvider(fragmentActivity).get(ArchivesViewModel.class)
: new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class)
: new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); : new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
} }
// feedStoryModels = feedStoriesViewModel.getList().getValue(); // feedStoryModels = feedStoriesViewModel.getList().getValue();
@ -287,7 +291,11 @@ public class StoryViewerFragment extends Fragment {
final boolean hasFeedStories; final boolean hasFeedStories;
List<?> models = null; List<?> models = null;
if (currentFeedStoryIndex >= 0) { if (currentFeedStoryIndex >= 0) {
if (isHighlight) { if (isArchive) {
final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel;
models = archivesViewModel.getList().getValue();
}
else if (isHighlight) {
final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel;
models = highlightsViewModel.getList().getValue(); models = highlightsViewModel.getList().getValue();
// final HighlightModel model = models.get(currentFeedStoryIndex); // final HighlightModel model = models.get(currentFeedStoryIndex);
@ -633,7 +641,18 @@ public class StoryViewerFragment extends Fragment {
releasePlayer(); releasePlayer();
String currentStoryMediaId = null; String currentStoryMediaId = null;
if (currentFeedStoryIndex >= 0) { if (currentFeedStoryIndex >= 0) {
if (isHighlight) { if (isArchive) {
final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel;
final List<HighlightModel> models = archivesViewModel.getList().getValue();
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
final HighlightModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId();
currentStoryUsername = model.getTitle();
}
else if (isHighlight) {
final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel;
final List<HighlightModel> models = highlightsViewModel.getList().getValue(); final List<HighlightModel> models = highlightsViewModel.getList().getValue();
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) { if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) {
@ -658,7 +677,13 @@ public class StoryViewerFragment extends Fragment {
isHashtag = fragmentArgs.getIsHashtag(); isHashtag = fragmentArgs.getIsHashtag();
isLoc = fragmentArgs.getIsLoc(); isLoc = fragmentArgs.getIsLoc();
final boolean hasUsername = !TextUtils.isEmpty(currentStoryUsername); final boolean hasUsername = !TextUtils.isEmpty(currentStoryUsername);
if (hasUsername) { if (isHighlight) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(highlight);
}
}
else if (hasUsername) {
currentStoryUsername = currentStoryUsername.replace("@", ""); currentStoryUsername = currentStoryUsername.replace("@", "");
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {

View File

@ -78,6 +78,8 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_POSTS_LAYOUT); private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_POSTS_LAYOUT);
private RecyclerView storiesRecyclerView; private RecyclerView storiesRecyclerView;
public static List<FeedStoryModel> feedStories;
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override @Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) { public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
@ -362,7 +364,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
new FeedStoriesAdapter.OnFeedStoryClickListener() { new FeedStoriesAdapter.OnFeedStoryClickListener() {
@Override @Override
public void onFeedStoryClick(FeedStoryModel model, int position) { public void onFeedStoryClick(FeedStoryModel model, int position) {
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null); final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null, false);
NavHostFragment.findNavController(FeedFragment.this).navigate(action); NavHostFragment.findNavController(FeedFragment.this).navigate(action);
} }
@ -398,6 +400,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
@Override @Override
public void onSuccess(final List<FeedStoryModel> result) { public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result); feedStoriesViewModel.getList().postValue(result);
feedStories = result;
storiesFetching = false; storiesFetching = false;
updateSwipeRefreshState(); updateSwipeRefreshState();
} }

View File

@ -1008,7 +1008,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (which == 1) { if (which == 1) {
// show stories // show stories
final NavDirections action = ProfileFragmentDirections final NavDirections action = ProfileFragmentDirections
.actionProfileFragmentToStoryViewerFragment(-1, null, false, false, profileModel.getId(), username); .actionProfileFragmentToStoryViewerFragment(-1, null, false, false, profileModel.getId(), username, false);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
return; return;
} }
@ -1069,7 +1069,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class); highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class);
highlightsAdapter = new HighlightsAdapter((model, position) -> { highlightsAdapter = new HighlightsAdapter((model, position) -> {
final NavDirections action = ProfileFragmentDirections final NavDirections action = ProfileFragmentDirections
.actionProfileFragmentToStoryViewerFragment(position, model.getTitle(), false, false, null, null); .actionProfileFragmentToStoryViewerFragment(position, model.getTitle(), false, false, null, null, false);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
}); });
final Context context = getContext(); final Context context = getContext();

View File

@ -15,14 +15,16 @@ public final class FeedStoryModel implements Serializable {
private final StoryModel firstStoryModel; private final StoryModel firstStoryModel;
private Boolean fullyRead; private Boolean fullyRead;
private final long timestamp; private final long timestamp;
private final int mediaCount;
public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, final boolean fullyRead,
final boolean fullyRead, final long timestamp, final StoryModel firstStoryModel) { final long timestamp, final StoryModel firstStoryModel, final int mediaCount) {
this.storyMediaId = storyMediaId; this.storyMediaId = storyMediaId;
this.profileModel = profileModel; this.profileModel = profileModel;
this.fullyRead = fullyRead; this.fullyRead = fullyRead;
this.timestamp = timestamp; this.timestamp = timestamp;
this.firstStoryModel = firstStoryModel; this.firstStoryModel = firstStoryModel;
this.mediaCount = mediaCount;
} }
public String getStoryMediaId() { public String getStoryMediaId() {
@ -38,6 +40,10 @@ public final class FeedStoryModel implements Serializable {
return Utils.datetimeParser.format(new Date(timestamp * 1000L)); return Utils.datetimeParser.format(new Date(timestamp * 1000L));
} }
public int getMediaCount() {
return mediaCount;
}
public ProfileModel getProfileModel() { public ProfileModel getProfileModel() {
return profileModel; return profileModel;
} }

View File

@ -11,15 +11,18 @@ public final class HighlightModel {
private final String id; private final String id;
private final String thumbnailUrl; private final String thumbnailUrl;
private final long timestamp; private final long timestamp;
private final int mediaCount;
public HighlightModel(final String title, public HighlightModel(final String title,
final String id, final String id,
final String thumbnailUrl, final String thumbnailUrl,
final long timestamp) { final long timestamp,
final int mediaCount) {
this.title = title; this.title = title;
this.id = id; this.id = id;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
this.timestamp = timestamp; this.timestamp = timestamp;
this.mediaCount = mediaCount;
} }
public String getTitle() { public String getTitle() {
@ -42,4 +45,8 @@ public final class HighlightModel {
public String getDateTime() { public String getDateTime() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L)); return Utils.datetimeParser.format(new Date(timestamp * 1000L));
} }
public int getMediaCount() {
return mediaCount;
}
} }

View File

@ -11,7 +11,8 @@ import awais.instagrabber.models.stickers.SwipeUpModel;
public final class StoryModel implements Serializable { public final class StoryModel implements Serializable {
private final String storyMediaId; private final String storyMediaId;
private final String storyUrl, thumbnail; private final String storyUrl;
private String thumbnail;
private final String username; private final String username;
private final String userId; private final String userId;
private final MediaItemType itemType; private final MediaItemType itemType;
@ -96,6 +97,10 @@ public final class StoryModel implements Serializable {
return position; return position;
} }
public void setThumbnail(final String thumbnail) {
this.thumbnail = thumbnail;
}
public void setVideoUrl(final String videoUrl) { public void setVideoUrl(final String videoUrl) {
this.videoUrl = videoUrl; this.videoUrl = videoUrl;
} }

View File

@ -18,10 +18,8 @@ public interface StoriesRepository {
Call<String> fetch(@Path("mediaId") final String mediaId); Call<String> fetch(@Path("mediaId") final String mediaId);
// this one is the same as MediaRepository.fetch BUT you need to make sure it's a story // this one is the same as MediaRepository.fetch BUT you need to make sure it's a story
@FormUrlEncoded @GET("/api/v1/feed/reels_tray/")
@POST("/api/v1/feed/reels_tray/") Call<String> getFeedStories();
Call<String> getFeedStories(@Header("User-Agent") String userAgent,
@FieldMap Map<String, String> form);
@GET("/api/v1/highlights/{uid}/highlights_tray/") @GET("/api/v1/highlights/{uid}/highlights_tray/")
Call<String> fetchHighlights(@Path("uid") final String uid); Call<String> fetchHighlights(@Path("uid") final String uid);

View File

@ -889,9 +889,7 @@ public final class ResponseBodyUtils {
final boolean isVideo = data.has("video_duration"); final boolean isVideo = data.has("video_duration");
final StoryModel model = new StoryModel(data.getString("id"), final StoryModel model = new StoryModel(data.getString("id"),
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0) data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
.getString("url"), .getString("url"), null,
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1)
.getString("url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at", 0), data.optLong("taken_at", 0),
(isLoc || isHashtag) (isLoc || isHashtag)
@ -900,6 +898,11 @@ public final class ResponseBodyUtils {
data.getJSONObject("user").getString("pk"), data.getJSONObject("user").getString("pk"),
data.optBoolean("can_reply")); data.optBoolean("can_reply"));
if (data.getJSONObject("image_versions2").getJSONArray("candidates").length() > 1) {
model.setThumbnail(data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1)
.getString("url"));
}
final JSONArray videoResources = data.optJSONArray("video_versions"); final JSONArray videoResources = data.optJSONArray("video_versions");
if (isVideo && videoResources != null) if (isVideo && videoResources != null)
model.setVideoUrl(ResponseBodyUtils.getHighQualityPost(videoResources, true, true, false)); model.setVideoUrl(ResponseBodyUtils.getHighQualityPost(videoResources, true, true, false));

View File

@ -0,0 +1,19 @@
package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.HighlightModel;
public class ArchivesViewModel extends ViewModel {
private MutableLiveData<List<HighlightModel>> list;
public MutableLiveData<List<HighlightModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

View File

@ -9,6 +9,7 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -84,13 +85,7 @@ public class StoriesService extends BaseService {
} }
public void getFeedStories(final String csrfToken, final ServiceCallback<List<FeedStoryModel>> callback) { public void getFeedStories(final String csrfToken, final ServiceCallback<List<FeedStoryModel>> callback) {
final Map<String, Object> form = new HashMap<>(4); final Call<String> response = repository.getFeedStories();
form.put("reason", "cold_start");
form.put("_csrftoken", csrfToken);
form.put("_uuid", UUID.randomUUID().toString());
form.put("supported_capabilities_new", Constants.SUPPORTED_CAPABILITIES);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> response = repository.getFeedStories(Constants.I_USER_AGENT, signedForm);
response.enqueue(new Callback<String>() { response.enqueue(new Callback<String>() {
@Override @Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -124,12 +119,17 @@ public class StoriesService extends BaseService {
null, 0, 0, 0, false, false, false, false, false); null, 0, 0, 0, false, false, false, false, false);
final String id = node.getString("id"); final String id = node.getString("id");
final long timestamp = node.getLong("latest_reel_media"); final long timestamp = node.getLong("latest_reel_media");
final int mediaCount = node.getInt("media_count");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp; final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp;
final JSONObject itemJson = node.getJSONArray("items").getJSONObject(0); final JSONObject itemJson = node.has("items") ? node.getJSONArray("items").getJSONObject(0) : null;
final StoryModel firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, false, null); StoryModel firstStoryModel = null;
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead, timestamp, firstStoryModel)); if (itemJson != null) {
firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, false, null);
} }
callback.onSuccess(feedStoryModels); else Log.d("austin_debug", "node: "+node);
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead, timestamp, firstStoryModel, mediaCount));
}
callback.onSuccess(sort(feedStoryModels));
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "Error parsing json", e); Log.e(TAG, "Error parsing json", e);
} }
@ -163,7 +163,8 @@ public class StoriesService extends BaseService {
highlightNode.getJSONObject("cover_media") highlightNode.getJSONObject("cover_media")
.getJSONObject("cropped_image_version") .getJSONObject("cropped_image_version")
.getString("url"), .getString("url"),
highlightNode.getLong("latest_reel_media") highlightNode.getLong("latest_reel_media"),
highlightNode.getInt("media_count")
)); ));
} }
callback.onSuccess(highlightModels); callback.onSuccess(highlightModels);
@ -217,7 +218,8 @@ public class StoriesService extends BaseService {
null, null,
highlightNode.getString(Constants.EXTRAS_ID), highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_image_version").getString("url"), highlightNode.getJSONObject("cover_image_version").getString("url"),
highlightNode.getLong("timestamp") highlightNode.getLong("timestamp"),
highlightNode.getInt("media_count")
)); ));
} }
callback.onSuccess(new ArchiveFetchResponse(highlightModels, callback.onSuccess(new ArchiveFetchResponse(highlightModels,
@ -393,6 +395,25 @@ public class StoriesService extends BaseService {
return builder.toString(); return builder.toString();
} }
private List<FeedStoryModel> sort(final List<FeedStoryModel> list) {
final List<FeedStoryModel> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
int result;
switch (Utils.settingsHelper.getString(Constants.STORY_SORT)) {
case "1":
result = o1.getTimestamp() > o2.getTimestamp() ? -1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1);
break;
case "2":
result = o1.getTimestamp() > o2.getTimestamp() ? 1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : -1);
break;
default:
result = 0;
}
return result;
});
return listCopy;
}
public class ArchiveFetchResponse { public class ArchiveFetchResponse {
private final List<HighlightModel> archives; private final List<HighlightModel> archives;
private final boolean hasNextPage; private final boolean hasNextPage;
@ -404,7 +425,7 @@ public class StoriesService extends BaseService {
this.nextCursor = nextCursor; this.nextCursor = nextCursor;
} }
public List<HighlightModel> getArchives() { public List<HighlightModel> getResult() {
return archives; return archives;
} }
@ -416,5 +437,4 @@ public class StoriesService extends BaseService {
return nextCursor; return nextCursor;
} }
} }
} }

View File

@ -125,5 +125,9 @@
android:name="username" android:name="username"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<argument
android:name="isArchive"
app:argType="boolean"
app:nullable="false" />
</fragment> </fragment>
</navigation> </navigation>

View File

@ -97,6 +97,10 @@
android:name="username" android:name="username"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<argument
android:name="isArchive"
app:argType="boolean"
app:nullable="false" />
</fragment> </fragment>
<action <action
android:id="@+id/action_global_hashTagFragment" android:id="@+id/action_global_hashTagFragment"

View File

@ -105,5 +105,9 @@
android:name="username" android:name="username"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<argument
android:name="isArchive"
app:argType="boolean"
app:nullable="false" />
</fragment> </fragment>
</navigation> </navigation>

View File

@ -174,6 +174,10 @@
android:name="username" android:name="username"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<argument
android:name="isArchive"
app:argType="boolean"
app:nullable="false" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/directMessagesThreadFragment" android:id="@+id/directMessagesThreadFragment"

View File

@ -64,5 +64,9 @@
android:name="username" android:name="username"
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
<argument
android:name="isArchive"
app:argType="boolean"
app:nullable="false" />
</fragment> </fragment>
</navigation> </navigation>

View File

@ -347,5 +347,9 @@
<item quantity="one">%d comment</item> <item quantity="one">%d comment</item>
<item quantity="other">%d comments</item> <item quantity="other">%d comments</item>
</plurals> </plurals>
<plurals name="stories_count">
<item quantity="one">%s story</item>
<item quantity="other">%s stories</item>
</plurals>
<string name="download_permission">Storage permission not granted!</string> <string name="download_permission">Storage permission not granted!</string>
</resources> </resources>