From 4d9494cbcfeb1f210604eb7bc55d2316c722ca6f Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 29 Jun 2021 16:56:12 -0400 Subject: [PATCH] convert StoryModel to StoryMedia close #1151, close #1208 --- .../instagrabber/adapters/StoriesAdapter.java | 28 +- .../fragments/HashTagFragment.java | 2 +- .../fragments/LocationFragment.java | 2 +- .../fragments/StoryViewerFragment.java | 281 ++++++++++-------- .../awais/instagrabber/models/StoryModel.kt | 29 -- .../models/stickers/PollModel.java | 52 ---- .../models/stickers/QuestionModel.java | 20 -- .../models/stickers/QuizModel.java | 37 --- .../models/stickers/SliderModel.java | 53 ---- .../models/stickers/SwipeUpModel.java | 20 -- .../repositories/StoriesService.kt | 17 +- .../requests/StoryViewerOptions.java | 2 +- .../responses/stories/PollSticker.kt | 2 +- .../responses/stories/QuizSticker.kt | 2 +- .../responses/stories/ReelsMediaResponse.kt | 8 + .../responses/stories/ReelsResponse.kt | 3 +- .../responses/stories/SliderSticker.kt | 2 +- .../responses/stories/StoryMedia.kt | 2 + .../responses/stories/StoryMediaResponse.kt | 14 + .../awais/instagrabber/utils/DownloadUtils.kt | 17 +- .../instagrabber/utils/ResponseBodyUtils.java | 174 ++--------- .../viewmodels/ProfileFragmentViewModel.kt | 10 +- .../viewmodels/StoriesViewModel.java | 6 +- .../webservices/StoriesRepository.kt | 106 ++----- .../ProfileFragmentViewModelTest.kt | 6 +- 25 files changed, 292 insertions(+), 603 deletions(-) delete mode 100755 app/src/main/java/awais/instagrabber/models/StoryModel.kt delete mode 100755 app/src/main/java/awais/instagrabber/models/stickers/PollModel.java delete mode 100755 app/src/main/java/awais/instagrabber/models/stickers/QuestionModel.java delete mode 100755 app/src/main/java/awais/instagrabber/models/stickers/QuizModel.java delete mode 100755 app/src/main/java/awais/instagrabber/models/stickers/SliderModel.java delete mode 100755 app/src/main/java/awais/instagrabber/models/stickers/SwipeUpModel.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsMediaResponse.kt create mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMediaResponse.kt diff --git a/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java index 9afc52d3..9a502f01 100755 --- a/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java @@ -10,20 +10,21 @@ import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import awais.instagrabber.databinding.ItemStoryBinding; -import awais.instagrabber.models.StoryModel; +import awais.instagrabber.repositories.responses.stories.StoryMedia; +import awais.instagrabber.utils.ResponseBodyUtils; -public final class StoriesAdapter extends ListAdapter { +public final class StoriesAdapter extends ListAdapter { private final OnItemClickListener onItemClickListener; - private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { @Override - public boolean areItemsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) { - return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); + public boolean areItemsTheSame(@NonNull final StoryMedia oldItem, @NonNull final StoryMedia newItem) { + return oldItem.getId().equals(newItem.getId()); } @Override - public boolean areContentsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) { - return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); + public boolean areContentsTheSame(@NonNull final StoryMedia oldItem, @NonNull final StoryMedia newItem) { + return oldItem.getId().equals(newItem.getId()); } }; @@ -42,8 +43,8 @@ public final class StoriesAdapter extends ListAdapter AppExecutors.INSTANCE.getMainThread().execute(() -> { // if (throwable != null) { diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 3ee581d4..6e18f5f0 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -552,7 +552,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR // private void fetchStories() { // if (isLoggedIn) { // storiesFetching = true; -// storiesRepository.getUserStory( +// storiesRepository.getStories( // StoryViewerOptions.forLocation(locationId, locationModel.getName()), // CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { // if (throwable != null) { diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index 0786649e..ff499fe6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -58,8 +58,10 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import java.io.IOException; import java.text.NumberFormat; +import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; +import java.util.stream.Collectors; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -72,23 +74,17 @@ import awais.instagrabber.databinding.FragmentStoryViewerBinding; import awais.instagrabber.fragments.main.ProfileFragmentDirections; import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.interfaces.SwipeEvent; -import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.models.stickers.PollModel; -import awais.instagrabber.models.stickers.QuestionModel; -import awais.instagrabber.models.stickers.QuizModel; -import awais.instagrabber.models.stickers.SliderModel; -import awais.instagrabber.models.stickers.SwipeUpModel; import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions.Type; import awais.instagrabber.repositories.requests.directmessages.ThreadIdsOrUserIds; -import awais.instagrabber.repositories.responses.stories.Broadcast; -import awais.instagrabber.repositories.responses.stories.Story; +import awais.instagrabber.repositories.responses.stories.*; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CoroutineUtilsKt; import awais.instagrabber.utils.DownloadUtils; +import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.ArchivesViewModel; @@ -121,16 +117,16 @@ public class StoryViewerFragment extends Fragment { private GestureDetectorCompat gestureDetector; private StoriesRepository storiesRepository; private MediaRepository mediaRepository; - private StoryModel currentStory; + private StoryMedia currentStory; private Broadcast live; private int slidePos; private int lastSlidePos; private String url; - private PollModel poll; - private QuestionModel question; - private String[] mentions; - private QuizModel quiz; - private SliderModel slider; + private PollSticker poll; + private QuestionSticker question; + private List mentions = new ArrayList(); + private QuizSticker quiz; + private SliderSticker slider; private MenuItem menuDownload, menuDm, menuProfile; private SimpleExoPlayer player; // private boolean isHashtag; @@ -220,10 +216,10 @@ public class StoryViewerFragment extends Fragment { csrfToken, userId, deviceId, - ThreadIdsOrUserIds.Companion.ofOneUser(String.valueOf(currentStory.getUserId())), + ThreadIdsOrUserIds.Companion.ofOneUser(String.valueOf(currentStory.getUser().getPk())), input.getText().toString(), - currentStory.getStoryMediaId(), - String.valueOf(currentStory.getUserId()), + currentStory.getId(), + String.valueOf(currentStory.getUser().getPk()), CoroutineUtilsKt.getContinuation( (directThreadBroadcastResponse, throwable1) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { if (throwable1 != null) { @@ -253,7 +249,7 @@ public class StoryViewerFragment extends Fragment { return true; } if (itemId == R.id.action_profile) { - openProfile("@" + currentStory.getUsername()); + openProfile("@" + currentStory.getUser().getPk()); } return false; } @@ -357,7 +353,7 @@ public class StoryViewerFragment extends Fragment { final Context context = getContext(); if (context == null) return; swipeEvent = isRightSwipe -> { - final List storyModels = storiesViewModel.getList().getValue(); + final List storyModels = storiesViewModel.getList().getValue(); final int storiesLen = storyModels == null ? 0 : storyModels.size(); if (sticking) { Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_SHORT).show(); @@ -373,12 +369,14 @@ public class StoryViewerFragment extends Fragment { Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show(); return; } + removeStickers(); final Object feedStoryModel = isRightSwipe ? finalModels.get(index - 1) : finalModels.size() == index + 1 ? null : finalModels.get(index + 1); paginateStories(feedStoryModel, finalModels.get(index), context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2); return; } + removeStickers(); if (isRightSwipe) { if (--slidePos <= 0) { slidePos = 0; @@ -471,35 +469,31 @@ public class StoryViewerFragment extends Fragment { }); final View.OnClickListener storyActionListener = v -> { final Object tag = v.getTag(); - if (tag instanceof PollModel) { - poll = (PollModel) tag; - if (poll.getMyChoice() > -1) { + if (tag instanceof PollSticker) { + poll = (PollSticker) tag; + final List tallies = poll.getTallies(); + final String[] choices = tallies.stream() + .map(t -> (poll.getViewerVote() == tallies.indexOf(t) ? "√ " : "") + + t.getText() + " (" + t.getCount() + ")" ) + .toArray(String[]::new); + final ArrayAdapter adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices); + if (poll.getViewerVote() > -1) { new AlertDialog.Builder(context) .setTitle(R.string.voted_story_poll) - .setAdapter(new ArrayAdapter<>( - context, - android.R.layout.simple_list_item_1, - new String[]{ - (poll.getMyChoice() == 0 ? "√ " : "") + poll.getLeftChoice() + " (" + poll.getLeftCount() + ")", - (poll.getMyChoice() == 1 ? "√ " : "") + poll.getRightChoice() + " (" + poll.getRightCount() + ")" - }), - null) + .setAdapter(adapter, null) .setPositiveButton(R.string.ok, null) .show(); } else { new AlertDialog.Builder(context) .setTitle(poll.getQuestion()) - .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, new String[]{ - poll.getLeftChoice() + " (" + poll.getLeftCount() + ")", - poll.getRightChoice() + " (" + poll.getRightCount() + ")" - }), (d, w) -> { + .setAdapter(adapter, (d, w) -> { sticking = true; storiesRepository.respondToPoll( csrfToken, userId, deviceId, - currentStory.getStoryMediaId().split("_")[0], - poll.getId(), + currentStory.getId().split("_")[0], + poll.getPollId(), w, CoroutineUtilsKt.getContinuation( (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { @@ -513,7 +507,7 @@ public class StoryViewerFragment extends Fragment { } sticking = false; try { - poll.setMyChoice(w); + poll.setViewerVote(w); Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); } catch (Exception ignored) {} }), @@ -524,8 +518,8 @@ public class StoryViewerFragment extends Fragment { .setPositiveButton(R.string.cancel, null) .show(); } - } else if (tag instanceof QuestionModel) { - question = (QuestionModel) tag; + } else if (tag instanceof QuestionSticker) { + question = (QuestionSticker) tag; final EditText input = new EditText(context); input.setHint(R.string.answer_hint); final AlertDialog ad = new AlertDialog.Builder(context) @@ -537,8 +531,8 @@ public class StoryViewerFragment extends Fragment { csrfToken, userId, deviceId, - currentStory.getStoryMediaId().split("_")[0], - question.getId(), + currentStory.getId().split("_")[0], + question.getQuestionId(), input.getText().toString(), CoroutineUtilsKt.getContinuation( (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { @@ -575,28 +569,31 @@ public class StoryViewerFragment extends Fragment { public void afterTextChanged(final Editable s) {} }); } else if (tag instanceof String[]) { - mentions = (String[]) tag; + final String[] rawMentions = (String[]) tag; + mentions = new ArrayList(Arrays.asList(rawMentions)); new AlertDialog.Builder(context) .setTitle(R.string.story_mentions) - .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> openProfile(mentions[w])) + .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, rawMentions), (d, w) -> openProfile(mentions.get(w))) .setPositiveButton(R.string.cancel, null) .show(); - } else if (tag instanceof QuizModel) { - String[] choices = new String[quiz.getChoices().length]; - for (int q = 0; q < choices.length; ++q) { - choices[q] = (quiz.getMyChoice() == q ? "√ " : "") + quiz.getChoices()[q] + " (" + quiz.getCounts()[q] + ")"; - } + } else if (tag instanceof QuizSticker) { + final List tallies = quiz.getTallies(); + final String[] choices = tallies.stream().map( + t -> (quiz.getViewerAnswer() == tallies.indexOf(t) ? "√ " : "") + + (quiz.getCorrectAnswer() == tallies.indexOf(t) ? "*** " : "") + + t.getText() + " (" + t.getCount() + ")" + ).toArray(String[]::new); new AlertDialog.Builder(context) - .setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion()) + .setTitle(quiz.getViewerAnswer() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion()) .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices), (d, w) -> { - if (quiz.getMyChoice() == -1) { + if (quiz.getViewerAnswer() == -1) { sticking = true; storiesRepository.respondToQuiz( csrfToken, userId, deviceId, - currentStory.getStoryMediaId().split("_")[0], - quiz.getId(), + currentStory.getId().split("_")[0], + quiz.getQuizId(), w, CoroutineUtilsKt.getContinuation( (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { @@ -610,7 +607,7 @@ public class StoryViewerFragment extends Fragment { } sticking = false; try { - quiz.setMyChoice(w); + quiz.setViewerAnswer(w); Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); } catch (Exception ignored) {} }), @@ -621,8 +618,8 @@ public class StoryViewerFragment extends Fragment { }) .setPositiveButton(R.string.cancel, null) .show(); - } else if (tag instanceof SliderModel) { - slider = (SliderModel) tag; + } else if (tag instanceof SliderSticker) { + slider = (SliderSticker) tag; NumberFormat percentage = NumberFormat.getPercentInstance(); percentage.setMaximumFractionDigits(2); LinearLayout sliderView = new LinearLayout(context); @@ -633,11 +630,11 @@ public class StoryViewerFragment extends Fragment { TextView tv = new TextView(context); tv.setGravity(Gravity.CENTER_HORIZONTAL); final SeekBar input = new SeekBar(context); - double avg = slider.getAverage() * 100; + double avg = slider.getSliderVoteAverage() * 100; input.setProgress((int) avg); sliderView.addView(input); sliderView.addView(tv); - if (slider.getMyChoice().isNaN() && slider.canVote()) { + if (slider.getViewerVote().isNaN() && slider.getViewerCanVote()) { input.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { @@ -656,9 +653,9 @@ public class StoryViewerFragment extends Fragment { new AlertDialog.Builder(context) .setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion()) .setMessage(getResources().getQuantityString(R.plurals.slider_info, - slider.getVoteCount(), - slider.getVoteCount(), - percentage.format(slider.getAverage()))) + slider.getSliderVoteCount(), + slider.getSliderVoteCount(), + percentage.format(slider.getSliderVoteAverage()))) .setView(sliderView) .setPositiveButton(R.string.confirm, (d, w) -> { sticking = true; @@ -666,8 +663,8 @@ public class StoryViewerFragment extends Fragment { csrfToken, userId, deviceId, - currentStory.getStoryMediaId().split("_")[0], - slider.getId(), + currentStory.getId().split("_")[0], + slider.getSliderId(), sliderValue, CoroutineUtilsKt.getContinuation( (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { @@ -681,7 +678,7 @@ public class StoryViewerFragment extends Fragment { } sticking = false; try { - slider.setMyChoice(sliderValue); + slider.setViewerVote(sliderValue); Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); } catch (Exception ignored) {} }), Dispatchers.getIO() @@ -692,13 +689,13 @@ public class StoryViewerFragment extends Fragment { .show(); } else { input.setEnabled(false); - tv.setText(getString(R.string.slider_answer, percentage.format(slider.getMyChoice()))); + tv.setText(getString(R.string.slider_answer, percentage.format(slider.getViewerVote()))); new AlertDialog.Builder(context) .setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion()) .setMessage(getResources().getQuantityString(R.plurals.slider_info, - slider.getVoteCount(), - slider.getVoteCount(), - percentage.format(slider.getAverage()))) + slider.getSliderVoteCount(), + slider.getSliderVoteCount(), + percentage.format(slider.getSliderVoteAverage()))) .setView(sliderView) .setPositiveButton(R.string.ok, null) .show(); @@ -746,11 +743,12 @@ public class StoryViewerFragment extends Fragment { case FEED_STORY_POSITION: { final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; final List models = feedStoriesViewModel.getList().getValue(); - if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) return; + if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) + return; final Story model = models.get(currentFeedStoryIndex); - currentStoryMediaId = model.getId(); + currentStoryMediaId = String.valueOf(model.getUser().getPk()); currentStoryUsername = model.getUser().getUsername(); - fetchOptions = StoryViewerOptions.forUser(Long.parseLong(currentStoryMediaId), currentStoryUsername); + fetchOptions = StoryViewerOptions.forUser(model.getUser().getPk(), currentStoryUsername); live = model.getBroadcast(); break; } @@ -767,11 +765,12 @@ public class StoryViewerFragment extends Fragment { fetchOptions = StoryViewerOptions.forStoryArchive(model.getId()); break; } - } - if (type == Type.USER) { - currentStoryMediaId = String.valueOf(options.getId()); - currentStoryUsername = options.getName(); - fetchOptions = StoryViewerOptions.forUser(options.getId(), currentStoryUsername); + case USER: { + currentStoryMediaId = String.valueOf(options.getId()); + currentStoryUsername = options.getName(); + fetchOptions = StoryViewerOptions.forUser(options.getId(), currentStoryUsername); + break; + } } setTitle(type); storiesViewModel.getList().setValue(Collections.emptyList()); @@ -804,9 +803,9 @@ public class StoryViewerFragment extends Fragment { refreshLive(); return; } - final ServiceCallback> storyCallback = new ServiceCallback>() { + final ServiceCallback> storyCallback = new ServiceCallback>() { @Override - public void onSuccess(final List storyModels) { + public void onSuccess(final List storyModels) { fetching = false; if (storyModels == null || storyModels.isEmpty()) { storiesViewModel.getList().setValue(Collections.emptyList()); @@ -826,11 +825,10 @@ public class StoryViewerFragment extends Fragment { @Override public void onFailure(final Throwable t) { - Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); Log.e(TAG, "Error", t); } }; - storiesRepository.getUserStory( + storiesRepository.getStories( fetchOptions, CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { if (throwable != null) { @@ -838,7 +836,7 @@ public class StoryViewerFragment extends Fragment { return; } //noinspection unchecked - storyCallback.onSuccess((List) storyModels); + storyCallback.onSuccess((List) storyModels); }), Dispatchers.getIO()) ); } @@ -887,9 +885,9 @@ public class StoryViewerFragment extends Fragment { private synchronized void refreshStory() { if (binding.storiesList.getVisibility() == View.VISIBLE) { - final List storyModels = storiesViewModel.getList().getValue(); + final List storyModels = storiesViewModel.getList().getValue(); if (storyModels != null && storyModels.size() > 0) { - StoryModel item = storyModels.get(lastSlidePos); + StoryMedia item = storyModels.get(lastSlidePos); if (item != null) { item.setCurrentSlide(false); storiesAdapter.notifyItemChanged(lastSlidePos, item); @@ -903,59 +901,96 @@ public class StoryViewerFragment extends Fragment { } lastSlidePos = slidePos; - final MediaItemType itemType = currentStory.getItemType(); + final MediaItemType itemType = currentStory.getMediaType(); - url = itemType == MediaItemType.MEDIA_TYPE_IMAGE ? currentStory.getStoryUrl() : currentStory.getVideoUrl(); + url = itemType == MediaItemType.MEDIA_TYPE_IMAGE + ? ResponseBodyUtils.getImageUrl(currentStory) + : ResponseBodyUtils.getVideoUrl(currentStory); - final String shortCode = currentStory.getTappableShortCode(); - binding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE); - binding.viewStoryPost.setTag(shortCode); + if (currentStory.getStoryFeedMedia() != null) { + final String shortCode = currentStory.getStoryFeedMedia().get(0).getMediaId(); + binding.viewStoryPost.setVisibility(View.VISIBLE); + binding.viewStoryPost.setTag(shortCode); + } - final String spotify = currentStory.getSpotify(); - binding.spotify.setVisibility(spotify != null ? View.VISIBLE : View.GONE); - binding.spotify.setTag(spotify); + final StoryAppAttribution spotify = currentStory.getStoryAppAttribution(); + if (spotify != null) { + binding.spotify.setVisibility(View.VISIBLE); + binding.spotify.setText(spotify.getName()); + binding.spotify.setTag(spotify.getContentUrl().split("?")[0]); + } - poll = currentStory.getPoll(); - binding.poll.setVisibility(poll != null ? View.VISIBLE : View.GONE); - binding.poll.setTag(poll); + if (currentStory.getStoryPolls() != null) { + poll = currentStory.getStoryPolls().get(0).getPollSticker(); + binding.poll.setVisibility(View.VISIBLE); + binding.poll.setTag(poll); + } - question = currentStory.getQuestion(); - binding.answer.setVisibility((question != null) ? View.VISIBLE : View.GONE); - binding.answer.setTag(question); + if (currentStory.getStoryQuestions() != null) { + question = currentStory.getStoryQuestions().get(0).getQuestionSticker(); + binding.answer.setVisibility(View.VISIBLE); + binding.answer.setTag(question); + } - mentions = currentStory.getMentions(); - binding.mention.setVisibility((mentions != null && mentions.length > 0) ? View.VISIBLE : View.GONE); - binding.mention.setTag(mentions); + mentions.clear(); + if (currentStory.getReelMentions() != null) { + mentions.addAll(currentStory.getReelMentions().stream().map( + s -> s.getUser().getUsername() + ).distinct().collect(Collectors.toList())); + } + if (currentStory.getStoryHashtags() != null) { + mentions.addAll(currentStory.getStoryHashtags().stream().map( + s -> s.getHashtag().getName() + ).distinct().collect(Collectors.toList())); + } + if (currentStory.getStoryLocations() != null) { + mentions.addAll(currentStory.getStoryLocations().stream().map( + s -> s.getLocation().getShortName() + " (" + s.getLocation().getPk() + ")" + ).distinct().collect(Collectors.toList())); + } + if (mentions.size() > 0) { + binding.mention.setVisibility(View.VISIBLE); + binding.mention.setTag(mentions.stream().toArray(String[]::new)); + } - quiz = currentStory.getQuiz(); - binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE); - binding.quiz.setTag(quiz); + if (currentStory.getStoryQuizs() != null) { + quiz = currentStory.getStoryQuizs().get(0).getQuizSticker(); + binding.quiz.setVisibility(View.VISIBLE); + binding.quiz.setTag(quiz); + } - slider = currentStory.getSlider(); - binding.slider.setVisibility(slider != null ? View.VISIBLE : View.GONE); - binding.slider.setTag(slider); + if (currentStory.getStorySliders() != null) { + slider = currentStory.getStorySliders().get(0).getSliderSticker(); + binding.slider.setVisibility(View.VISIBLE); + binding.slider.setTag(slider); + } - final SwipeUpModel swipeUp = currentStory.getSwipeUp(); - if (swipeUp != null) { + if (currentStory.getStoryCta() != null) { + final StoryCta swipeUp = currentStory.getStoryCta().get(0).getLinks(); binding.swipeUp.setVisibility(View.VISIBLE); - binding.swipeUp.setText(swipeUp.getText()); - binding.swipeUp.setTag(swipeUp.getUrl()); - } else binding.swipeUp.setVisibility(View.GONE); + binding.swipeUp.setText(currentStory.getLinkText()); + final String swipeUpUrl = swipeUp.getWebUri(); + final String actualLink = swipeUpUrl.startsWith("https://l.instagram.com/") + ? Uri.parse(swipeUpUrl).getQueryParameter("u") + : null; + binding.swipeUp.setTag(actualLink == null && actualLink.startsWith("http") + ? swipeUpUrl : actualLink); + } releasePlayer(); final Type type = options.getType(); if (type == Type.HASHTAG || type == Type.LOCATION) { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); if (actionBar != null) { - actionBarTitle = currentStory.getUsername(); - actionBar.setTitle(currentStory.getUsername()); + actionBarTitle = currentStory.getUser().getUsername(); + actionBar.setTitle(currentStory.getUser().getUsername()); } } if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(); else setupImage(); final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - actionBarSubtitle = TextUtils.epochSecondToString(currentStory.getTimestamp()); + actionBarSubtitle = TextUtils.epochSecondToString(currentStory.getTakenAt()); if (actionBar != null) { try { actionBar.setSubtitle(actionBarSubtitle); @@ -969,13 +1004,23 @@ public class StoryViewerFragment extends Fragment { csrfToken, userId, deviceId, - currentStory.getStoryMediaId(), - currentStory.getTimestamp(), + currentStory.getId(), + currentStory.getTakenAt(), System.currentTimeMillis() / 1000, CoroutineUtilsKt.getContinuation((s, throwable) -> {}, Dispatchers.getIO()) ); } + private void removeStickers() { + binding.swipeUp.setVisibility(View.GONE); + binding.quiz.setVisibility(View.GONE); + binding.spotify.setVisibility(View.GONE); + binding.mention.setVisibility(View.GONE); + binding.viewStoryPost.setVisibility(View.GONE); + binding.answer.setVisibility(View.GONE); + binding.slider.setVisibility(View.GONE); + } + private void downloadStory() { final Context context = getContext(); if (context == null) return; @@ -1016,7 +1061,7 @@ public class StoryViewerFragment extends Fragment { dmVisible = true; menuDm.setVisible(true); } - if (!TextUtils.isEmpty(currentStory.getUsername())) { + if (!TextUtils.isEmpty(currentStory.getUser().getUsername())) { profileVisible = true; menuProfile.setVisible(true); } @@ -1057,7 +1102,7 @@ public class StoryViewerFragment extends Fragment { dmVisible = true; menuDm.setVisible(true); } - if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) { + if (!TextUtils.isEmpty(currentStory.getUser().getUsername()) && menuProfile != null) { profileVisible = true; menuProfile.setVisible(true); } @@ -1077,7 +1122,7 @@ public class StoryViewerFragment extends Fragment { dmVisible = true; menuDm.setVisible(true); } - if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) { + if (!TextUtils.isEmpty(currentStory.getUser().getUsername()) && menuProfile != null) { profileVisible = true; menuProfile.setVisible(true); } diff --git a/app/src/main/java/awais/instagrabber/models/StoryModel.kt b/app/src/main/java/awais/instagrabber/models/StoryModel.kt deleted file mode 100755 index 2858026c..00000000 --- a/app/src/main/java/awais/instagrabber/models/StoryModel.kt +++ /dev/null @@ -1,29 +0,0 @@ -package awais.instagrabber.models - -import awais.instagrabber.models.enums.MediaItemType -import awais.instagrabber.models.stickers.* -import java.io.Serializable - -data class StoryModel( - val storyMediaId: String? = null, - val storyUrl: String? = null, - var thumbnail: String? = null, - val itemType: MediaItemType? = null, - val timestamp: Long = 0, - val username: String? = null, - val userId: Long = 0, - val canReply: Boolean = false, -) : Serializable { - var videoUrl: String? = null - var tappableShortCode: String? = null - val tappableId: String? = null - var spotify: String? = null - var poll: PollModel? = null - var question: QuestionModel? = null - var slider: SliderModel? = null - var quiz: QuizModel? = null - var swipeUp: SwipeUpModel? = null - var mentions: Array? = null - var position = 0 - var isCurrentSlide = false -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/stickers/PollModel.java b/app/src/main/java/awais/instagrabber/models/stickers/PollModel.java deleted file mode 100755 index 3d0dcda9..00000000 --- a/app/src/main/java/awais/instagrabber/models/stickers/PollModel.java +++ /dev/null @@ -1,52 +0,0 @@ -package awais.instagrabber.models.stickers; - -import java.io.Serializable; - -public final class PollModel implements Serializable { - private int leftcount, rightcount, mychoice; - private final String id, question, leftchoice, rightchoice; - - public PollModel(final String id, final String question, final String leftchoice, final int leftcount, - final String rightchoice, final int rightcount, final int mychoice) { - this.id = id; // only the poll id - this.question = question; - this.leftchoice = leftchoice; - this.leftcount = leftcount; - this.rightchoice = rightchoice; - this.rightcount = rightcount; - this.mychoice = mychoice; - } - - public String getId() { - return id; - } - - public String getQuestion() { - return question; - } - - public String getLeftChoice() { - return leftchoice; - } - - public int getLeftCount() { - return leftcount; - } - - public String getRightChoice() { - return rightchoice; - } - - public int getRightCount() { - return rightcount; - } - - public int getMyChoice() { return mychoice; } - - public int setMyChoice(final int choice) { - this.mychoice = choice; - if (choice == 0) this.leftcount += 1; - else if (choice == 1) this.rightcount += 1; - return choice; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/stickers/QuestionModel.java b/app/src/main/java/awais/instagrabber/models/stickers/QuestionModel.java deleted file mode 100755 index 43c6eac2..00000000 --- a/app/src/main/java/awais/instagrabber/models/stickers/QuestionModel.java +++ /dev/null @@ -1,20 +0,0 @@ -package awais.instagrabber.models.stickers; - -import java.io.Serializable; - -public final class QuestionModel implements Serializable { - private final String id, question; - - public QuestionModel(final String id, final String question) { - this.id = id; // only the poll id - this.question = question; - } - - public String getId() { - return id; - } - - public String getQuestion() { - return question; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/stickers/QuizModel.java b/app/src/main/java/awais/instagrabber/models/stickers/QuizModel.java deleted file mode 100755 index 9c13b9b0..00000000 --- a/app/src/main/java/awais/instagrabber/models/stickers/QuizModel.java +++ /dev/null @@ -1,37 +0,0 @@ -package awais.instagrabber.models.stickers; - -import java.io.Serializable; - -public final class QuizModel implements Serializable { - private final String id, question; - private final String[] choices; - private Long[] counts; - private int mychoice; - - public QuizModel(final String id, final String question, final String[] choices, final Long[] counts, final int mychoice) { - this.id = id; // only the poll id - this.question = question; - this.choices = choices; - this.counts = counts; - this.mychoice = mychoice; - } - - public String getId() { - return id; - } - - public String getQuestion() { - return question; - } - - public String[] getChoices() { return choices;} - - public Long[] getCounts() { return counts;} - - public int getMyChoice() { return mychoice; } - - public void setMyChoice(final int choice) { - this.mychoice = choice; - counts[choice] += 1L; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/stickers/SliderModel.java b/app/src/main/java/awais/instagrabber/models/stickers/SliderModel.java deleted file mode 100755 index ff5c0f20..00000000 --- a/app/src/main/java/awais/instagrabber/models/stickers/SliderModel.java +++ /dev/null @@ -1,53 +0,0 @@ -package awais.instagrabber.models.stickers; - -import java.io.Serializable; - -public final class SliderModel implements Serializable { - private final int voteCount; - private final Double average; - private Double myChoice; - private final boolean canVote; - private final String id, question, emoji; - - public SliderModel(final String id, final String question, final String emoji, final boolean canVote, - final Double average, final int voteCount, final Double myChoice) { - this.id = id; - this.question = question; - this.emoji = emoji; - this.canVote = canVote; - this.average = average; - this.voteCount = voteCount; - this.myChoice = myChoice; - } - - public String getId() { - return id; - } - - public String getQuestion() { - return question; - } - - public String getEmoji() { - return emoji; - } - - public boolean canVote() { - return canVote; - } - - public int getVoteCount() { - return voteCount; - } - - public Double getAverage() { - return average; - } - - public Double getMyChoice() { return myChoice; } - - public Double setMyChoice(final Double choice) { - this.myChoice = choice; - return choice; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/stickers/SwipeUpModel.java b/app/src/main/java/awais/instagrabber/models/stickers/SwipeUpModel.java deleted file mode 100755 index 4007d9b1..00000000 --- a/app/src/main/java/awais/instagrabber/models/stickers/SwipeUpModel.java +++ /dev/null @@ -1,20 +0,0 @@ -package awais.instagrabber.models.stickers; - -import java.io.Serializable; - -public final class SwipeUpModel implements Serializable { - private final String url, text; - - public SwipeUpModel(final String url, final String text) { - this.url = url; - this.text = text; - } - - public String getUrl() { - return url; - } - - public String getText() { - return text; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt b/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt index 4d217fef..e964f54b 100644 --- a/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt +++ b/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt @@ -1,14 +1,17 @@ package awais.instagrabber.repositories import awais.instagrabber.repositories.responses.stories.ArchiveResponse +import awais.instagrabber.repositories.responses.stories.ReelsMediaResponse +import awais.instagrabber.repositories.responses.stories.ReelsResponse import awais.instagrabber.repositories.responses.stories.ReelsTrayResponse +import awais.instagrabber.repositories.responses.stories.StoryMediaResponse import awais.instagrabber.repositories.responses.stories.StoryStickerResponse import retrofit2.http.* interface StoriesService { // this one is the same as MediaRepository.fetch BUT you need to make sure it's a story @GET("/api/v1/media/{mediaId}/info/") - suspend fun fetch(@Path("mediaId") mediaId: Long): String + suspend fun fetch(@Path("mediaId") mediaId: Long): StoryMediaResponse @GET("/api/v1/feed/reels_tray/") suspend fun getFeedStories(): ReelsTrayResponse? @@ -19,14 +22,20 @@ interface StoriesService { @GET("/api/v1/archive/reel/day_shells/") suspend fun fetchArchive(@QueryMap queryParams: Map): ArchiveResponse? - @GET - suspend fun getUserStory(@Url url: String): String + @GET("/api/v1/feed/reels_media/") + suspend fun getReelsMedia(@Query("user_ids") id: String): ReelsMediaResponse + + @GET("/api/v1/{type}/{id}/story/") + suspend fun getStories(@Path("type") type: String, @Path("id") id: String): ReelsResponse + + @GET("/api/v1/feed/user/{id}/story/") + suspend fun getUserStories(@Path("id") id: String): ReelsResponse @FormUrlEncoded @POST("/api/v1/media/{storyId}/{stickerId}/{action}/") suspend fun respondToSticker( @Path("storyId") storyId: String, - @Path("stickerId") stickerId: String, + @Path("stickerId") stickerId: Long, @Path("action") action: String, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer @FieldMap form: Map, ): StoryStickerResponse diff --git a/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java b/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java index 901e8463..8202ec7d 100644 --- a/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java +++ b/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java @@ -42,7 +42,7 @@ public class StoryViewerOptions implements Serializable { } public static StoryViewerOptions forUser(final long id, final String name) { - return new StoryViewerOptions(id, name,Type.USER); + return new StoryViewerOptions(id, name, Type.USER); } public static StoryViewerOptions forHighlight(final String highlight) { diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/PollSticker.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/PollSticker.kt index bf486dde..d95e1329 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/PollSticker.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/PollSticker.kt @@ -9,5 +9,5 @@ data class PollSticker( val pollId: Long?, val question: String?, val tallies: List?, - val viewerVote: Int? + var viewerVote: Int = -1 ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt index fd79ef7a..5a82a88f 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt @@ -9,6 +9,6 @@ data class QuizSticker( val quizId: Long?, val question: String?, val tallies: List?, - val viewerAnswer: Int?, + var viewerAnswer: Int? = -1, val correctAnswer: Int? ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsMediaResponse.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsMediaResponse.kt new file mode 100644 index 00000000..7a165a69 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsMediaResponse.kt @@ -0,0 +1,8 @@ +package awais.instagrabber.repositories.responses.stories + +import java.io.Serializable + +data class ReelsMediaResponse( + val status: String?, + val reels: Map? +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsResponse.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsResponse.kt index 7aae5e7f..c23114cb 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsResponse.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/ReelsResponse.kt @@ -4,6 +4,7 @@ import java.io.Serializable data class ReelsResponse( val status: String?, - val reel: Story?, + val reel: Story?, // users + val story: Story?, // hashtag and locations (unused) val broadcast: Broadcast? ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt index 6261bccf..45356d85 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt @@ -10,7 +10,7 @@ data class SliderSticker( val question: String?, val emoji: String?, val viewerCanVote: Boolean?, - val viewerVote: Double?, + var viewerVote: Double?, val sliderVoteAverage: Double?, val sliderVoteCount: Int?, ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt index 3b8c7787..47af8625 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt @@ -41,6 +41,8 @@ data class StoryMedia( val storyAppAttribution: StoryAppAttribution? = null ) : Serializable { private var dateString: String? = null + var position = 0 + var isCurrentSlide = false // TODO use extension once all usages are converted to kotlin // val date: String by lazy { diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMediaResponse.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMediaResponse.kt new file mode 100644 index 00000000..7889b572 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMediaResponse.kt @@ -0,0 +1,14 @@ +package awais.instagrabber.repositories.responses.stories + +import awais.instagrabber.models.enums.MediaItemType +import awais.instagrabber.repositories.responses.ImageVersions2 +import awais.instagrabber.repositories.responses.MediaCandidate +import awais.instagrabber.repositories.responses.User +import awais.instagrabber.utils.TextUtils +import java.io.Serializable + +data class StoryMediaResponse( + val items: List?, // length 1 + val status: String? + // ignoring pagination properties +) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt index a9d466ba..2d529391 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt @@ -13,9 +13,9 @@ import androidx.documentfile.provider.DocumentFile import androidx.work.* import awais.instagrabber.R import awais.instagrabber.fragments.settings.PreferenceKeys -import awais.instagrabber.models.StoryModel import awais.instagrabber.models.enums.MediaItemType import awais.instagrabber.repositories.responses.Media +import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.workers.DownloadWorker import com.google.gson.Gson @@ -392,18 +392,19 @@ object DownloadUtils { @JvmStatic fun download( context: Context, - storyModel: StoryModel + storyModel: StoryMedia ) { - val downloadDir = getDownloadDir(context, storyModel.username) ?: return + val downloadDir = getDownloadDir(context, storyModel.user?.username) ?: return val url = - if (storyModel.itemType == MediaItemType.MEDIA_TYPE_VIDEO) storyModel.videoUrl else storyModel.storyUrl + if (storyModel.mediaType == MediaItemType.MEDIA_TYPE_VIDEO) ResponseBodyUtils.getVideoUrl(storyModel) + else ResponseBodyUtils.getImageUrl(storyModel) val extension = getFileExtensionFromUrl(url) - val baseFileName = (storyModel.storyMediaId + "_" - + storyModel.timestamp + extension) + val baseFileName = (storyModel.id + "_" + + storyModel.takenAt + extension) val usernamePrepend = if (Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME) - && storyModel.username != null - ) storyModel.username + "_" else "" + && storyModel.user?.username != null + ) storyModel.user.username + "_" else "" val fileName = usernamePrepend + baseFileName var saveFile = downloadDir.findFile(fileName) if (saveFile == null) { diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index a1152baf..ef0067a2 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -14,13 +14,7 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.models.stickers.PollModel; -import awais.instagrabber.models.stickers.QuestionModel; -import awais.instagrabber.models.stickers.QuizModel; -import awais.instagrabber.models.stickers.SliderModel; -import awais.instagrabber.models.stickers.SwipeUpModel; import awais.instagrabber.repositories.responses.Caption; import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.ImageVersions2; @@ -276,136 +270,6 @@ public final class ResponseBodyUtils { ); } - public static StoryModel parseStoryItem(final JSONObject data, - final boolean isLocOrHashtag, - final String username) throws JSONException { - final boolean isVideo = data.has("video_duration"); - final StoryModel model = new StoryModel(data.getString("id"), - data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0) - .getString("url"), null, - isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, - data.optLong("taken_at", 0), - isLocOrHashtag ? data.getJSONObject("user").getString("username") : username, - data.getJSONObject("user").getLong("pk"), - 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"); - if (isVideo && videoResources != null) - model.setVideoUrl(ResponseBodyUtils.getHighQualityPost(videoResources, true, true, false)); - - if (data.has("story_feed_media")) { - model.setTappableShortCode(data.getJSONArray("story_feed_media").getJSONObject(0).optString("media_id")); - } - - // TODO: this may not be limited to spotify - if (!data.isNull("story_app_attribution")) - model.setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]); - - if (data.has("story_polls")) { - final JSONArray storyPolls = data.optJSONArray("story_polls"); - JSONObject tappableObject = null; - if (storyPolls != null) { - tappableObject = storyPolls.getJSONObject(0).optJSONObject("poll_sticker"); - } - if (tappableObject != null) model.setPoll(new PollModel( - String.valueOf(tappableObject.getLong("poll_id")), - tappableObject.getString("question"), - tappableObject.getJSONArray("tallies").getJSONObject(0).getString("text"), - tappableObject.getJSONArray("tallies").getJSONObject(0).getInt("count"), - tappableObject.getJSONArray("tallies").getJSONObject(1).getString("text"), - tappableObject.getJSONArray("tallies").getJSONObject(1).getInt("count"), - tappableObject.optInt("viewer_vote", -1) - )); - } - if (data.has("story_questions")) { - final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0) - .optJSONObject("question_sticker"); - if (tappableObject != null && !tappableObject.getString("question_type").equals("music")) - model.setQuestion(new QuestionModel( - String.valueOf(tappableObject.getLong("question_id")), - tappableObject.getString("question") - )); - } - if (data.has("story_quizs")) { - JSONObject tappableObject = data.getJSONArray("story_quizs").getJSONObject(0).optJSONObject("quiz_sticker"); - if (tappableObject != null) { - String[] choices = new String[tappableObject.getJSONArray("tallies").length()]; - Long[] counts = new Long[choices.length]; - for (int q = 0; q < choices.length; ++q) { - JSONObject tempchoice = tappableObject.getJSONArray("tallies").getJSONObject(q); - choices[q] = (q == tappableObject.getInt("correct_answer") ? "*** " : "") - + tempchoice.getString("text"); - counts[q] = tempchoice.getLong("count"); - } - model.setQuiz(new QuizModel( - String.valueOf(tappableObject.getLong("quiz_id")), - tappableObject.getString("question"), - choices, - counts, - tappableObject.optInt("viewer_answer", -1) - )); - } - } - if (data.has("story_cta") && data.has("link_text")) { - JSONObject tappableObject = data.getJSONArray("story_cta").getJSONObject(0).getJSONArray("links").getJSONObject(0); - String swipeUpUrl = tappableObject.optString("webUri"); - final String backupSwipeUpUrl = swipeUpUrl; - if (swipeUpUrl != null && swipeUpUrl.startsWith("https://l.instagram.com/")) { - swipeUpUrl = Uri.parse(swipeUpUrl).getQueryParameter("u"); - } - if (swipeUpUrl != null && swipeUpUrl.startsWith("http")) - model.setSwipeUp(new SwipeUpModel(swipeUpUrl, data.getString("link_text"))); - else if (backupSwipeUpUrl != null && backupSwipeUpUrl.startsWith("http")) - model.setSwipeUp(new SwipeUpModel(backupSwipeUpUrl, data.getString("link_text"))); - } - if (data.has("story_sliders")) { - final JSONObject tappableObject = data.getJSONArray("story_sliders").getJSONObject(0) - .optJSONObject("slider_sticker"); - if (tappableObject != null) - model.setSlider(new SliderModel( - String.valueOf(tappableObject.getLong("slider_id")), - tappableObject.getString("question"), - tappableObject.getString("emoji"), - tappableObject.getBoolean("viewer_can_vote"), - tappableObject.optDouble("slider_vote_average"), - tappableObject.getInt("slider_vote_count"), - tappableObject.optDouble("viewer_vote") - )); - } - JSONArray hashtags = data.optJSONArray("story_hashtags"); - JSONArray locations = data.optJSONArray("story_locations"); - JSONArray atmarks = data.optJSONArray("reel_mentions"); - String[] mentions = new String[(hashtags == null ? 0 : hashtags.length()) - + (atmarks == null ? 0 : atmarks.length()) - + (locations == null ? 0 : locations.length())]; - if (hashtags != null) { - for (int h = 0; h < hashtags.length(); ++h) { - mentions[h] = "#" + hashtags.getJSONObject(h).getJSONObject("hashtag").getString("name"); - } - } - if (atmarks != null) { - for (int h = 0; h < atmarks.length(); ++h) { - mentions[h + (hashtags == null ? 0 : hashtags.length())] = - "@" + atmarks.getJSONObject(h).getJSONObject("user").getString("username"); - } - } - if (locations != null) { - for (int h = 0; h < locations.length(); ++h) { - mentions[h + (hashtags == null ? 0 : hashtags.length()) + (atmarks == null ? 0 : atmarks.length())] = - locations.getJSONObject(h).getJSONObject("location").getString("short_name") - + " (" + locations.getJSONObject(h).getJSONObject("location").getLong("pk") + ")"; - } - } - if (mentions.length != 0) model.setMentions(mentions); - - return model; - } - public static String getThumbUrl(final Object media) { return getImageCandidate(media, CandidateType.THUMBNAIL); } @@ -415,6 +279,7 @@ public final class ResponseBodyUtils { } private static String getImageCandidate(final Object rawMedia, final CandidateType type) { + if (rawMedia == null) return null; final ImageVersions2 imageVersions2; final int originalWidth, originalHeight; if (rawMedia instanceof StoryMedia) { @@ -453,22 +318,34 @@ public final class ResponseBodyUtils { return getVideoCandidate(media, CandidateType.VIDEO_THUMBNAIL); } - public static String getVideoUrl(final Media media) { + public static String getVideoUrl(final Object media) { return getVideoCandidate(media, CandidateType.DOWNLOAD); } // TODO: merge with getImageCandidate when Kotlin - private static String getVideoCandidate(final Media media, final CandidateType type) { - if (media == null) return null; - final List candidates = media.getVideoVersions(); + private static String getVideoCandidate(final Object rawMedia, final CandidateType type) { + if (rawMedia == null) return null; + final List candidates; + final int originalWidth, originalHeight; + if (rawMedia instanceof StoryMedia) { + candidates = ((StoryMedia) rawMedia).getVideoVersions(); + originalWidth = ((StoryMedia) rawMedia).getOriginalWidth(); + originalHeight = ((StoryMedia) rawMedia).getOriginalHeight(); + } + else if (rawMedia instanceof Media) { + candidates = ((Media) rawMedia).getVideoVersions(); + originalWidth = ((Media) rawMedia).getOriginalWidth(); + originalHeight = ((Media) rawMedia).getOriginalHeight(); + } + else return null; if (candidates == null || candidates.isEmpty()) return null; - final boolean isSquare = Integer.compare(media.getOriginalWidth(), media.getOriginalHeight()) == 0; + final boolean isSquare = Integer.compare(originalWidth, originalHeight) == 0; final List sortedCandidates = candidates.stream() .sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth())) .collect(Collectors.toList()); final List filteredCandidates = sortedCandidates.stream() .filter(c -> - c.getWidth() <= media.getOriginalWidth() + c.getWidth() <= originalWidth && c.getWidth() <= type.getValue() && (isSquare || Integer .compare(c.getWidth(), c.getHeight()) != 0) @@ -480,19 +357,6 @@ public final class ResponseBodyUtils { return candidate.getUrl(); } - public static StoryModel parseBroadcastItem(final JSONObject data) throws JSONException { - final StoryModel model = new StoryModel(data.getString("id"), - data.getString("cover_frame_url"), - data.getString("cover_frame_url"), - MediaItemType.MEDIA_TYPE_LIVE, - data.optLong("published_time", 0), - data.getJSONObject("broadcast_owner").getString("username"), - data.getJSONObject("broadcast_owner").getLong("pk"), - false); - model.setVideoUrl(data.getString("dash_playback_url")); - return model; - } - private enum CandidateType { VIDEO_THUMBNAIL(700), THUMBNAIL(1000), diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index b5cac8c1..de704c59 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -8,7 +8,6 @@ import awais.instagrabber.db.entities.Favorite import awais.instagrabber.db.repositories.FavoriteRepository import awais.instagrabber.managers.DirectMessagesManager import awais.instagrabber.models.Resource -import awais.instagrabber.models.StoryModel import awais.instagrabber.models.enums.BroadcastItemType import awais.instagrabber.models.enums.FavoriteType import awais.instagrabber.repositories.requests.StoryViewerOptions @@ -17,6 +16,7 @@ import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.UserProfileContextLink import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.stories.Story +import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.utils.ControlledRunner import awais.instagrabber.utils.Event import awais.instagrabber.utils.SingleRunner @@ -153,9 +153,9 @@ class ProfileFragmentViewModel( } } - private val storyFetchControlledRunner = ControlledRunner?>() - val userStories: LiveData?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair -> - liveData?>>(context = viewModelScope.coroutineContext + ioDispatcher) { + private val storyFetchControlledRunner = ControlledRunner?>() + val userStories: LiveData?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair -> + liveData?>>(context = viewModelScope.coroutineContext + ioDispatcher) { val (currentUserResource, profileResource, action) = currentUserAndProfilePair if (action != INIT && action != REFRESH) { return@liveData @@ -231,7 +231,7 @@ class ProfileFragmentViewModel( return graphQLRepository.fetchUser(stateUsername) } - private suspend fun fetchUserStory(fetchedUser: User): List = storiesRepository.getUserStory( + private suspend fun fetchUserStory(fetchedUser: User): List = storiesRepository.getStories( StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName) ) diff --git a/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java index 6d6b16e3..0f512403 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java @@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel; import java.util.List; -import awais.instagrabber.models.StoryModel; +import awais.instagrabber.repositories.responses.stories.StoryMedia; public class StoriesViewModel extends ViewModel { - private MutableLiveData> list; + private MutableLiveData> list; - public MutableLiveData> getList() { + public MutableLiveData> getList() { if (list == null) { list = new MutableLiveData<>(); } diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt b/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt index efe4e0e2..62c27847 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt @@ -1,26 +1,22 @@ package awais.instagrabber.webservices import awais.instagrabber.fragments.settings.PreferenceKeys -import awais.instagrabber.models.StoryModel import awais.instagrabber.repositories.StoriesService import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.responses.stories.ArchiveResponse import awais.instagrabber.repositories.responses.stories.Story +import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.repositories.responses.stories.StoryStickerResponse -import awais.instagrabber.utils.ResponseBodyUtils import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.Utils import awais.instagrabber.webservices.RetrofitFactory.retrofit -import org.json.JSONArray -import org.json.JSONObject -import java.util.* +import java.util.UUID open class StoriesRepository(private val service: StoriesService) { - suspend fun fetch(mediaId: Long): StoryModel { + suspend fun fetch(mediaId: Long): StoryMedia? { val response = service.fetch(mediaId) - val itemJson = JSONObject(response).getJSONArray("items").getJSONObject(0) - return ResponseBodyUtils.parseStoryItem(itemJson, false, null) + return response.items?.get(0) } suspend fun getFeedStories(): List { @@ -70,31 +66,30 @@ open class StoriesRepository(private val service: StoriesService) { return service.fetchArchive(form) } - open suspend fun getUserStory(options: StoryViewerOptions): List { - val url = buildUrl(options) ?: return emptyList() - val response = service.getUserStory(url) - val isLocOrHashtag = options.type == StoryViewerOptions.Type.LOCATION || options.type == StoryViewerOptions.Type.HASHTAG - val isHighlight = options.type == StoryViewerOptions.Type.HIGHLIGHT || options.type == StoryViewerOptions.Type.STORY_ARCHIVE - var data: JSONObject? = JSONObject(response) - data = if (!isHighlight) { - data?.optJSONObject(if (isLocOrHashtag) "story" else "reel") - } else { - data?.getJSONObject("reels")?.optJSONObject(options.name) - } - var username: String? = null - if (data != null && !isLocOrHashtag) { - username = data.getJSONObject("user").getString("username") - } - val media: JSONArray? = data?.optJSONArray("items") - return if (media?.length() ?: 0 > 0 && media?.optJSONObject(0) != null) { - val mediaLen = media.length() - val models: MutableList = ArrayList() - for (i in 0 until mediaLen) { - data = media.getJSONObject(i) - models.add(ResponseBodyUtils.parseStoryItem(data, isLocOrHashtag, username)) + open suspend fun getStories(options: StoryViewerOptions): List { + return when (options.type) { + StoryViewerOptions.Type.HIGHLIGHT, + StoryViewerOptions.Type.STORY_ARCHIVE + -> { + val response = service.getReelsMedia(options.name) + val story: Story? = response.reels?.get(options.name) + story?.items ?: emptyList() } - models - } else emptyList() + StoryViewerOptions.Type.USER -> { + val response = service.getUserStories(options.id.toString()) + response.reel?.items ?: emptyList() + } + // should not reach beyond this point + StoryViewerOptions.Type.LOCATION -> { + val response = service.getStories("locations", options.id.toString()) + response.story?.items ?: emptyList() + } + StoryViewerOptions.Type.HASHTAG -> { + val response = service.getStories("tags", options.name) + response.story?.items ?: emptyList() + } + else -> emptyList() + } } private suspend fun respondToSticker( @@ -102,7 +97,7 @@ open class StoriesRepository(private val service: StoriesService) { userId: Long, deviceUuid: String, storyId: String, - stickerId: String, + stickerId: Long, action: String, arg1: String, arg2: String, @@ -125,7 +120,7 @@ open class StoriesRepository(private val service: StoriesService) { userId: Long, deviceUuid: String, storyId: String, - stickerId: String, + stickerId: Long, answer: String, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer) @@ -134,7 +129,7 @@ open class StoriesRepository(private val service: StoriesService) { userId: Long, deviceUuid: String, storyId: String, - stickerId: String, + stickerId: Long, answer: Int, ): StoryStickerResponse { return respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_quiz_answer", "answer", answer.toString()) @@ -145,7 +140,7 @@ open class StoriesRepository(private val service: StoriesService) { userId: Long, deviceUuid: String, storyId: String, - stickerId: String, + stickerId: Long, answer: Int, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString()) @@ -154,7 +149,7 @@ open class StoriesRepository(private val service: StoriesService) { userId: Long, deviceUuid: String, storyId: String, - stickerId: String, + stickerId: Long, answer: Double, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString()) @@ -182,43 +177,6 @@ open class StoriesRepository(private val service: StoriesService) { return service.seen(queryMap, signedForm) } - private fun buildUrl(options: StoryViewerOptions): String? { - val builder = StringBuilder() - builder.append("https://i.instagram.com/api/v1/") - val type = options.type - var id: String? = null - when (type) { - StoryViewerOptions.Type.HASHTAG -> { - builder.append("tags/") - id = options.name - } - StoryViewerOptions.Type.LOCATION -> { - builder.append("locations/") - id = options.id.toString() - } - StoryViewerOptions.Type.USER -> { - builder.append("feed/user/") - id = options.id.toString() - } - StoryViewerOptions.Type.HIGHLIGHT, StoryViewerOptions.Type.STORY_ARCHIVE -> { - builder.append("feed/reels_media/?user_ids=") - id = options.name - } - StoryViewerOptions.Type.STORY -> { - } - else -> { - } - } - if (id == null) { - return null - } - builder.append(id) - if (type != StoryViewerOptions.Type.HIGHLIGHT && type != StoryViewerOptions.Type.STORY_ARCHIVE) { - builder.append("/story/") - } - return builder.toString() - } - private fun sort(list: List): List { val listCopy = ArrayList(list) listCopy.sortWith { o1, o2 -> diff --git a/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt b/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt index 165db358..cb3344d8 100644 --- a/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt +++ b/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt @@ -10,12 +10,12 @@ import awais.instagrabber.db.entities.Favorite import awais.instagrabber.db.repositories.FavoriteRepository import awais.instagrabber.getOrAwaitValue import awais.instagrabber.models.Resource -import awais.instagrabber.models.StoryModel import awais.instagrabber.models.enums.FavoriteType import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.responses.FriendshipStatus import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.stories.Story +import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.webservices.* import kotlinx.coroutines.ExperimentalCoroutinesApi import org.json.JSONException @@ -320,13 +320,13 @@ internal class ProfileFragmentViewModelTest { "username" to testPublicUser.username ) ) - val testUserStories = listOf(StoryModel()) + val testUserStories = listOf(StoryMedia()) val testUserHighlights = listOf(Story()) val userRepository = object : UserRepository(UserServiceAdapter()) { override suspend fun getUsernameInfo(username: String): User = testPublicUser } val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) { - override suspend fun getUserStory(options: StoryViewerOptions): List = testUserStories + override suspend fun getStories(options: StoryViewerOptions): List = testUserStories override suspend fun fetchHighlights(profileId: Long): List = testUserHighlights } val viewModel = ProfileFragmentViewModel(