From 6e4fa9fdbfd7922f1ee7cc6714589d6539963e02 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 29 Dec 2020 15:19:00 -0500 Subject: [PATCH] wip: archive and feed stories as a list --- .../adapters/FeedStoriesListAdapter.java | 97 ++++++++++++++++ .../adapters/HighlightStoriesListAdapter.java | 53 +++++++++ .../viewholder/StoryListViewHolder.java | 69 +++++++++++ .../fragments/StoryListViewerFragment.java | 108 ++++++++++++++++++ .../fragments/main/FeedFragment.java | 7 +- .../settings/MorePreferencesFragment.java | 5 + .../instagrabber/models/FeedStoryModel.java | 25 ++-- .../instagrabber/models/HighlightModel.java | 20 +++- .../awais/instagrabber/models/StoryModel.java | 9 +- .../repositories/StoriesRepository.java | 3 + .../instagrabber/utils/ResponseBodyUtils.java | 2 + .../webservices/StoriesService.java | 65 ++++++++++- app/src/main/res/drawable/ic_archive.xml | 10 ++ app/src/main/res/drawable/ic_story_list.xml | 10 ++ .../res/layout/fragment_story_list_viewer.xml | 15 +++ app/src/main/res/menu/feed_menu.xml | 5 + .../main/res/navigation/feed_nav_graph.xml | 11 ++ .../main/res/navigation/more_nav_graph.xml | 10 ++ .../res/navigation/story_list_nav_graph.xml | 68 +++++++++++ app/src/main/res/values/strings.xml | 2 + 20 files changed, 579 insertions(+), 15 deletions(-) create mode 100755 app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java create mode 100755 app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java create mode 100644 app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java create mode 100644 app/src/main/res/drawable/ic_archive.xml create mode 100644 app/src/main/res/drawable/ic_story_list.xml create mode 100644 app/src/main/res/layout/fragment_story_list_viewer.xml create mode 100644 app/src/main/res/navigation/story_list_nav_graph.xml diff --git a/app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java new file mode 100755 index 00000000..fb4584fa --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java @@ -0,0 +1,97 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import awais.instagrabber.adapters.viewholder.StoryListViewHolder; +import awais.instagrabber.databinding.ItemNotificationBinding; +import awais.instagrabber.models.FeedStoryModel; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.Utils; + +public final class FeedStoriesListAdapter extends ListAdapter { + private final OnFeedStoryClickListener listener; + + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) { + return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); + } + + @Override + public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) { + return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead()); + } + }; + + public FeedStoriesListAdapter(final OnFeedStoryClickListener listener) { + super(diffCallback); + this.listener = listener; + } + + @NonNull + @Override + public StoryListViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false); + return new StoryListViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { + final FeedStoryModel model = getItem(position); + holder.bind(model, listener); + } + + @Override + public void submitList(@Nullable final List 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 list) { + if (list == null) { + super.submitList(null); + return; + } + super.submitList(sort(list)); + } + + private List sort(final List list) { + final List 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 { + void onFeedStoryClick(final FeedStoryModel model); + + void onProfileClick(final String username); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java b/app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java new file mode 100755 index 00000000..d88ef28c --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java @@ -0,0 +1,53 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; + +import awais.instagrabber.adapters.viewholder.StoryListViewHolder; +import awais.instagrabber.databinding.ItemNotificationBinding; +import awais.instagrabber.models.HighlightModel; + +public final class HighlightStoriesListAdapter extends ListAdapter { + private final OnHighlightStoryClickListener listener; + + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) { + return oldItem.getId().equals(newItem.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) { + return oldItem.getId().equals(newItem.getId()); + } + }; + + public HighlightStoriesListAdapter(final OnHighlightStoryClickListener listener) { + super(diffCallback); + this.listener = listener; + } + + @NonNull + @Override + public StoryListViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false); + return new StoryListViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { + final HighlightModel model = getItem(position); + holder.bind(model, listener); + } + + public interface OnHighlightStoryClickListener { + void onHighlightClick(HighlightModel model); + + void onProfileClick(String username); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java new file mode 100644 index 00000000..4c78a014 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java @@ -0,0 +1,69 @@ +package awais.instagrabber.adapters.viewholder; + +import android.text.TextUtils; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener; +import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener; +import awais.instagrabber.databinding.ItemNotificationBinding; +import awais.instagrabber.models.FeedStoryModel; +import awais.instagrabber.models.HighlightModel; + +public final class StoryListViewHolder extends RecyclerView.ViewHolder { + private final ItemNotificationBinding binding; + + public StoryListViewHolder(final ItemNotificationBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public void bind(final FeedStoryModel model, + final OnFeedStoryClickListener notificationClickListener) { + if (model == null) return; + binding.tvComment.setVisibility(View.GONE); + binding.tvSubComment.setVisibility(View.GONE); + + binding.tvDate.setText(model.getDateTime()); + + binding.tvUsername.setText(model.getProfileModel().getUsername()); + binding.ivProfilePic.setImageURI(model.getProfileModel().getSdProfilePic()); + binding.ivProfilePic.setOnClickListener(v -> { + if (notificationClickListener == null) return; + notificationClickListener.onProfileClick(model.getProfileModel().getUsername()); + }); + + binding.ivPreviewPic.setVisibility(View.VISIBLE); + binding.ivPreviewPic.setImageURI(model.getFirstStoryModel().getThumbnail()); + binding.ivPreviewPic.setOnClickListener(v -> { + if (notificationClickListener == null) return; + notificationClickListener.onFeedStoryClick(model); + }); + + itemView.setOnClickListener(v -> { + if (notificationClickListener == null) return; + notificationClickListener.onFeedStoryClick(model); + }); + } + + public void bind(final HighlightModel model, + final OnHighlightStoryClickListener notificationClickListener) { + if (model == null) return; + binding.tvComment.setVisibility(View.GONE); + binding.tvSubComment.setVisibility(View.GONE); + + binding.tvUsername.setText(model.getDateTime()); + + binding.ivProfilePic.setVisibility(View.GONE); + + binding.ivPreviewPic.setVisibility(View.VISIBLE); + binding.ivPreviewPic.setImageURI(model.getThumbnailUrl()); + + itemView.setOnClickListener(v -> { + if (notificationClickListener == null) return; + notificationClickListener.onHighlightClick(model); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java new file mode 100644 index 00000000..01a061fb --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java @@ -0,0 +1,108 @@ +package awais.instagrabber.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavDirections; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import awais.instagrabber.adapters.FeedStoriesListAdapter; +import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener; +import awais.instagrabber.databinding.FragmentStoryListViewerBinding; +import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; +import awais.instagrabber.models.FeedStoryModel; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.viewmodels.StoriesViewModel; +import awais.instagrabber.webservices.StoriesService; + +import static awais.instagrabber.utils.Utils.settingsHelper; + +public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { + private static final String TAG = "StoryListViewerFragment"; + + private FragmentStoryListViewerBinding binding; + private SwipeRefreshLayout root; + private boolean shouldRefresh = true; + private StoriesViewModel storiesViewModel; + private StoriesService storiesService; + private Context context; + private String type; + + private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() { + @Override + public void onFeedStoryClick(final FeedStoryModel model) { + if (model == null) return; +// final NavDirections action = StoryListNavGraphDirections.actionStoryListFragmentToStoryViewerFragment(position, null, false, false, null, null); +// NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); + } + + @Override + public void onProfileClick(final String username) { + openProfile(username); + } + }; + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + context = getContext(); + if (context == null) return; + storiesService = StoriesService.getInstance(); + } + + @NonNull + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + if (root != null) { + shouldRefresh = false; + return root; + } + binding = FragmentStoryListViewerBinding.inflate(getLayoutInflater()); + root = binding.getRoot(); + return root; + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + if (!shouldRefresh) return; + init(); + shouldRefresh = false; + } + + private void init() { + final Context context = getContext(); + if (getArguments() == null) return; + final StoryListViewerFragmentArgs fragmentArgs = StoryListViewerFragmentArgs.fromBundle(getArguments()); + type = fragmentArgs.getType(); + binding.swipeRefreshLayout.setOnRefreshListener(this); + storiesViewModel = new ViewModelProvider(this).get(StoriesViewModel.class); +// final NotificationsAdapter adapter = new NotificationsAdapter(clickListener, mentionClickListener); + binding.rvStories.setLayoutManager(new LinearLayoutManager(context)); +// binding.rvStories.setAdapter(adapter); +// storiesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + onRefresh(); + } + + @Override + public void onRefresh() { + binding.swipeRefreshLayout.setRefreshing(true); + + binding.swipeRefreshLayout.setRefreshing(false); +// storiesViewModel.getList().postValue(); + } + + private void openProfile(final String username) { + final NavDirections action = MorePreferencesFragmentDirections + .actionGlobalProfileFragment("@" + username); + NavHostFragment.findNavController(this).navigate(action); + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index f5af3ff4..23993c82 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -279,7 +279,11 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre @Override public boolean onOptionsItemSelected(@NonNull final MenuItem item) { - if (item.getItemId() == R.id.layout) { + if (item.getItemId() == R.id.storyList) { + final NavDirections action = FeedFragmentDirections.actionGlobalStoryListViewerFragment("feed"); + NavHostFragment.findNavController(FeedFragment.this).navigate(action); + } + else if (item.getItemId() == R.id.layout) { showPostsLayoutPreferences(); return true; } @@ -358,7 +362,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre new FeedStoriesAdapter.OnFeedStoryClickListener() { @Override public void onFeedStoryClick(FeedStoryModel model, int position) { - Log.d("austin_debug", "read status is "+model.isFullyRead()); final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null); NavHostFragment.findNavController(FeedFragment.this).navigate(action); } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 5dcdde7a..12b94137 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -137,6 +137,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment { NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment); return true; })); + screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive"); + NavHostFragment.findNavController(this).navigate(navDirections); + return true; + })); } screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> { final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment(); diff --git a/app/src/main/java/awais/instagrabber/models/FeedStoryModel.java b/app/src/main/java/awais/instagrabber/models/FeedStoryModel.java index 6273b020..242af21f 100755 --- a/app/src/main/java/awais/instagrabber/models/FeedStoryModel.java +++ b/app/src/main/java/awais/instagrabber/models/FeedStoryModel.java @@ -2,21 +2,27 @@ package awais.instagrabber.models; import android.util.Log; +import androidx.annotation.NonNull; + import java.io.Serializable; +import java.util.Date; + +import awais.instagrabber.utils.Utils; public final class FeedStoryModel implements Serializable { private final String storyMediaId; private final ProfileModel profileModel; -// private StoryModel[] storyModels; + private final StoryModel firstStoryModel; private Boolean fullyRead; private final long timestamp; public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, - final boolean fullyRead, final long timestamp) { + final boolean fullyRead, final long timestamp, final StoryModel firstStoryModel) { this.storyMediaId = storyMediaId; this.profileModel = profileModel; this.fullyRead = fullyRead; this.timestamp = timestamp; + this.firstStoryModel = firstStoryModel; } public String getStoryMediaId() { @@ -27,17 +33,22 @@ public final class FeedStoryModel implements Serializable { return timestamp; } + @NonNull + public String getDateTime() { + return Utils.datetimeParser.format(new Date(timestamp * 1000L)); + } + public ProfileModel getProfileModel() { return profileModel; } -// public void setStoryModels(final StoryModel[] storyModels) { -// this.storyModels = storyModels; +// public void setFirstStoryModel(final StoryModel firstStoryModel) { +// this.firstStoryModel = firstStoryModel; // } -// public StoryModel[] getStoryModels() { -// return storyModels; -// } + public StoryModel getFirstStoryModel() { + return firstStoryModel; + } public Boolean isFullyRead() { return fullyRead; diff --git a/app/src/main/java/awais/instagrabber/models/HighlightModel.java b/app/src/main/java/awais/instagrabber/models/HighlightModel.java index 70e4032a..741d9d47 100755 --- a/app/src/main/java/awais/instagrabber/models/HighlightModel.java +++ b/app/src/main/java/awais/instagrabber/models/HighlightModel.java @@ -1,16 +1,25 @@ package awais.instagrabber.models; +import androidx.annotation.NonNull; + +import java.util.Date; + +import awais.instagrabber.utils.Utils; + public final class HighlightModel { private final String title; private final String id; private final String thumbnailUrl; + private final long timestamp; public HighlightModel(final String title, final String id, - final String thumbnailUrl) { + final String thumbnailUrl, + final long timestamp) { this.title = title; this.id = id; this.thumbnailUrl = thumbnailUrl; + this.timestamp = timestamp; } public String getTitle() { @@ -24,4 +33,13 @@ public final class HighlightModel { public String getThumbnailUrl() { return thumbnailUrl; } + + public long getTimestamp() { + return timestamp; + } + + @NonNull + public String getDateTime() { + return Utils.datetimeParser.format(new Date(timestamp * 1000L)); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/StoryModel.java b/app/src/main/java/awais/instagrabber/models/StoryModel.java index c9615c8b..d535a212 100755 --- a/app/src/main/java/awais/instagrabber/models/StoryModel.java +++ b/app/src/main/java/awais/instagrabber/models/StoryModel.java @@ -11,7 +11,7 @@ import awais.instagrabber.models.stickers.SwipeUpModel; public final class StoryModel implements Serializable { private final String storyMediaId; - private final String storyUrl; + private final String storyUrl, thumbnail; private final String username; private final String userId; private final MediaItemType itemType; @@ -30,10 +30,11 @@ public final class StoryModel implements Serializable { private boolean isCurrentSlide = false; private final boolean canReply; - public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType, + public StoryModel(final String storyMediaId, final String storyUrl, final String thumbnail, final MediaItemType itemType, final long timestamp, final String username, final String userId, final boolean canReply) { this.storyMediaId = storyMediaId; this.storyUrl = storyUrl; + this.thumbnail = thumbnail; this.itemType = itemType; this.timestamp = timestamp; this.username = username; @@ -45,6 +46,10 @@ public final class StoryModel implements Serializable { return storyUrl; } + public String getThumbnail() { + return thumbnail; + } + public String getStoryMediaId() { return storyMediaId; } diff --git a/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java b/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java index 4f61964c..945b76c2 100644 --- a/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java @@ -26,6 +26,9 @@ public interface StoriesRepository { @GET("/api/v1/highlights/{uid}/highlights_tray/") Call fetchHighlights(@Path("uid") final String uid); + @GET("/api/v1/archive/reel/day_shells/") + Call fetchArchive(@QueryMap Map queryParams); + @GET Call getUserStory(@Header("User-Agent") String userAgent, @Url String url); diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index c4633f04..5ebef48b 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -890,6 +890,8 @@ public final class ResponseBodyUtils { final StoryModel model = new StoryModel(data.getString("id"), data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0) .getString("url"), + data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1) + .getString("url"), isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, data.optLong("taken_at", 0), (isLoc || isHashtag) diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java index a9a9e1ef..f77cdee3 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import java.util.UUID; import awais.instagrabber.models.FeedStoryModel; @@ -124,7 +125,9 @@ public class StoriesService extends BaseService { final String id = node.getString("id"); final long timestamp = node.getLong("latest_reel_media"); final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp; - feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead, timestamp)); + final JSONObject itemJson = node.getJSONArray("items").getJSONObject(0); + final StoryModel firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, false, null); + feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead, timestamp, firstStoryModel)); } callback.onSuccess(feedStoryModels); } catch (JSONException e) { @@ -179,6 +182,62 @@ public class StoriesService extends BaseService { }); } + public void fetchArchive(final String maxId, + final ServiceCallback callback) { + final Map form = new HashMap<>(); + form.put("include_suggested_highlights", "false"); + form.put("is_in_archive_home", "true"); + form.put("include_cover", "1"); + form.put("timezone_offset", String.valueOf(TimeZone.getDefault().getRawOffset() / 1000)); + if (!TextUtils.isEmpty(maxId)) { + form.put("max_id", maxId); // NOT TESTED + } + final Call request = repository.fetchArchive(form); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + try { + if (callback == null) { + return; + } + final String body = response.body(); + if (TextUtils.isEmpty(body)) { + callback.onSuccess(null); + return; + } + final JSONObject data = new JSONObject(body); + final JSONArray highlightsReel = data.getJSONArray("items"); + + final int length = highlightsReel.length(); + final List highlightModels = new ArrayList<>(); + + for (int i = 0; i < length; ++i) { + final JSONObject highlightNode = highlightsReel.getJSONObject(i); + highlightModels.add(new HighlightModel( + null, + highlightNode.getString(Constants.EXTRAS_ID), + highlightNode.getJSONObject("cover_image_version").getString("url"), + highlightNode.getLong("timestamp") + )); + } + callback.onSuccess(new ArchiveFetchResponse(highlightModels, + data.getBoolean("more_available"), + data.getString("max_id"))); + } catch (JSONException e) { + Log.e(TAG, "onResponse", e); + callback.onFailure(e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + if (callback != null) { + callback.onFailure(t); + } + } + }); + } + public void getUserStory(final String id, final String username, final boolean isLoc, @@ -335,11 +394,11 @@ public class StoriesService extends BaseService { } public class ArchiveFetchResponse { - private List archives; + private final List archives; private final boolean hasNextPage; private final String nextCursor; - public ArchiveFetchResponse(final List highlightModels, final boolean hasNextPage, final String nextCursor) { + public ArchiveFetchResponse(final List archives, final boolean hasNextPage, final String nextCursor) { this.archives = archives; this.hasNextPage = hasNextPage; this.nextCursor = nextCursor; diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml new file mode 100644 index 00000000..65001884 --- /dev/null +++ b/app/src/main/res/drawable/ic_archive.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_story_list.xml b/app/src/main/res/drawable/ic_story_list.xml new file mode 100644 index 00000000..54589b26 --- /dev/null +++ b/app/src/main/res/drawable/ic_story_list.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_story_list_viewer.xml b/app/src/main/res/layout/fragment_story_list_viewer.xml new file mode 100644 index 00000000..d02d34b3 --- /dev/null +++ b/app/src/main/res/layout/fragment_story_list_viewer.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/feed_menu.xml b/app/src/main/res/menu/feed_menu.xml index 5a9e835f..db67ad61 100644 --- a/app/src/main/res/menu/feed_menu.xml +++ b/app/src/main/res/menu/feed_menu.xml @@ -1,6 +1,11 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/navigation/story_list_nav_graph.xml b/app/src/main/res/navigation/story_list_nav_graph.xml new file mode 100644 index 00000000..ef7db84f --- /dev/null +++ b/app/src/main/res/navigation/story_list_nav_graph.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ef1714e4..cc45796e 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -231,6 +231,7 @@ App crashed Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (: Activity + Story archive Copyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Select Picture @@ -321,6 +322,7 @@ Delete Comment Layout + Feed stories Opening post... Share Layout style