Browse Source

Convert StoriesRepository and StoriesService to kotlin

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
cae457aa9a
  1. 23
      app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
  2. 24
      app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
  3. 42
      app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java
  4. 165
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
  5. 30
      app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
  6. 45
      app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
  7. 45
      app/src/main/java/awais/instagrabber/repositories/StoriesRepository.kt
  8. 733
      app/src/main/java/awais/instagrabber/webservices/StoriesService.kt

23
app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java

@ -39,7 +39,6 @@ import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Set; import java.util.Set;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -55,7 +54,6 @@ import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.FollowingType; import awais.instagrabber.models.enums.FollowingType;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
@ -301,7 +299,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
tagsService = isLoggedIn ? TagsService.getInstance() : null; tagsService = isLoggedIn ? TagsService.getInstance() : null;
storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null; storiesService = isLoggedIn ? StoriesService.INSTANCE : null;
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE; graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@ -582,9 +580,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
storiesFetching = true; storiesFetching = true;
storiesService.getUserStory( storiesService.getUserStory(
StoryViewerOptions.forHashtag(hashtagModel.getName()), StoryViewerOptions.forHashtag(hashtagModel.getName()),
new ServiceCallback<List<StoryModel>>() { CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@Override if (throwable != null) {
public void onSuccess(final List<StoryModel> storyModels) { Log.e(TAG, "Error", throwable);
storiesFetching = false;
return;
}
if (storyModels != null && !storyModels.isEmpty()) { if (storyModels != null && !storyModels.isEmpty()) {
hashtagDetailsBinding.mainHashtagImage.setStoriesBorder(1); hashtagDetailsBinding.mainHashtagImage.setStoriesBorder(1);
hasStories = true; hasStories = true;
@ -592,14 +593,8 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
hasStories = false; hasStories = false;
} }
storiesFetching = false; storiesFetching = false;
} }), Dispatchers.getIO())
);
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
storiesFetching = false;
}
});
} }
private void setTitle() { private void setTitle() {

24
app/src/main/java/awais/instagrabber/fragments/LocationFragment.java

@ -37,7 +37,6 @@ import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Set; import java.util.Set;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -53,7 +52,6 @@ import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Location;
@ -70,6 +68,7 @@ import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.LocationService; import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import kotlinx.coroutines.Dispatchers;
import static androidx.core.content.PermissionChecker.checkSelfPermission; import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
@ -293,7 +292,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
locationService = isLoggedIn ? LocationService.getInstance() : null; locationService = isLoggedIn ? LocationService.getInstance() : null;
storiesService = StoriesService.getInstance(null, 0L, null); storiesService = StoriesService.INSTANCE;
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE; graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@ -586,22 +585,19 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
storiesFetching = true; storiesFetching = true;
storiesService.getUserStory( storiesService.getUserStory(
StoryViewerOptions.forLocation(locationId, locationModel.getName()), StoryViewerOptions.forLocation(locationId, locationModel.getName()),
new ServiceCallback<List<StoryModel>>() { CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@Override if (throwable != null) {
public void onSuccess(final List<StoryModel> storyModels) { Log.e(TAG, "Error", throwable);
storiesFetching = false;
return;
}
if (storyModels != null && !storyModels.isEmpty()) { if (storyModels != null && !storyModels.isEmpty()) {
locationDetailsBinding.mainLocationImage.setStoriesBorder(1); locationDetailsBinding.mainLocationImage.setStoriesBorder(1);
hasStories = true; hasStories = true;
} }
storiesFetching = false; storiesFetching = false;
} }), Dispatchers.getIO())
);
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
storiesFetching = false;
}
});
} }
} }

42
app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java

@ -41,12 +41,15 @@ import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.models.FeedStoryModel; import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HighlightModel; import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.viewmodels.ArchivesViewModel; import awais.instagrabber.viewmodels.ArchivesViewModel;
import awais.instagrabber.viewmodels.FeedStoriesViewModel; import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import awais.instagrabber.webservices.StoriesService.ArchiveFetchResponse; import awais.instagrabber.webservices.StoriesService.ArchiveFetchResponse;
import kotlinx.coroutines.Dispatchers;
public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "StoryListViewerFragment"; private static final String TAG = "StoryListViewerFragment";
@ -133,7 +136,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
context = getContext(); context = getContext();
if (context == null) return; if (context == null) return;
setHasOptionsMenu(true); setHasOptionsMenu(true);
storiesService = StoriesService.getInstance(null, 0L, null); storiesService = StoriesService.INSTANCE;
} }
@NonNull @NonNull
@ -239,22 +242,31 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
} }
firstRefresh = false; firstRefresh = false;
} else if (type.equals("feed")) { } else if (type.equals("feed")) {
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { storiesService.getFeedStories(
@Override CoroutineUtilsKt.getContinuation((feedStoryModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
public void onSuccess(final List<FeedStoryModel> result) { if (throwable != null) {
feedStoriesViewModel.getList().postValue(result); Log.e(TAG, "failed", throwable);
adapter.submitList(result); Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_SHORT).show();
binding.swipeRefreshLayout.setRefreshing(false); return;
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "failed", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} }
}); //noinspection unchecked
feedStoriesViewModel.getList().postValue((List<FeedStoryModel>) feedStoryModels);
//noinspection unchecked
adapter.submitList((List<FeedStoryModel>) feedStoryModels);
binding.swipeRefreshLayout.setRefreshing(false);
}), Dispatchers.getIO())
);
} else if (type.equals("archive")) { } else if (type.equals("archive")) {
storiesService.fetchArchive(endCursor, cb); storiesService.fetchArchive(
endCursor,
CoroutineUtilsKt.getContinuation((archiveFetchResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(archiveFetchResponse);
}), Dispatchers.getIO())
);
} }
} }

165
app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java

@ -87,7 +87,6 @@ import awais.instagrabber.models.stickers.SwipeUpModel;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.requests.StoryViewerOptions.Type; import awais.instagrabber.repositories.requests.StoryViewerOptions.Type;
import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds; import awais.instagrabber.repositories.requests.directmessages.ThreadIdOrUserIds;
import awais.instagrabber.repositories.responses.StoryStickerResponse;
import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
@ -113,6 +112,8 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class StoryViewerFragment extends Fragment { public class StoryViewerFragment extends Fragment {
private static final String TAG = "StoryViewerFragment"; private static final String TAG = "StoryViewerFragment";
private final String cookie = settingsHelper.getString(Constants.COOKIE);
private AppCompatActivity fragmentActivity; private AppCompatActivity fragmentActivity;
private View root; private View root;
private FragmentStoryViewerBinding binding; private FragmentStoryViewerBinding binding;
@ -148,21 +149,22 @@ public class StoryViewerFragment extends Fragment {
// private boolean isArchive; // private boolean isArchive;
// private boolean isNotification; // private boolean isNotification;
private DirectMessagesService directMessagesService; private DirectMessagesService directMessagesService;
private final String cookie = settingsHelper.getString(Constants.COOKIE);
private StoryViewerOptions options; private StoryViewerOptions options;
private String csrfToken;
private String deviceId;
private long userId;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (csrfToken == null) return; if (csrfToken == null) return;
final long userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); userId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceId = settingsHelper.getString(Constants.DEVICE_UUID); deviceId = settingsHelper.getString(Constants.DEVICE_UUID);
fragmentActivity = (AppCompatActivity) requireActivity(); fragmentActivity = (AppCompatActivity) requireActivity();
storiesService = StoriesService.getInstance(csrfToken, userIdFromCookie, deviceId); storiesService = StoriesService.INSTANCE;
mediaService = MediaService.INSTANCE; mediaService = MediaService.INSTANCE;
directMessagesService = DirectMessagesService.getInstance(csrfToken, userIdFromCookie, deviceId); directMessagesService = DirectMessagesService.getInstance(csrfToken, userId, deviceId);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@ -514,28 +516,31 @@ public class StoryViewerFragment extends Fragment {
}), (d, w) -> { }), (d, w) -> {
sticking = true; sticking = true;
storiesService.respondToPoll( storiesService.respondToPoll(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0], currentStory.getStoryMediaId().split("_")[0],
poll.getId(), poll.getId(),
w, w,
new ServiceCallback<StoryStickerResponse>() { CoroutineUtilsKt.getContinuation(
@Override (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
public void onSuccess(final StoryStickerResponse result) { if (throwable != null) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", throwable);
try { try {
poll.setMyChoice(w); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
return;
} }
@Override
public void onFailure(final Throwable t) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", t);
try { try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); poll.setMyChoice(w);
Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }),
}); Dispatchers.getIO()
)
);
}) })
.setPositiveButton(R.string.cancel, null) .setPositiveButton(R.string.cancel, null)
.show(); .show();
@ -550,27 +555,30 @@ public class StoryViewerFragment extends Fragment {
.setPositiveButton(R.string.confirm, (d, w) -> { .setPositiveButton(R.string.confirm, (d, w) -> {
sticking = true; sticking = true;
storiesService.respondToQuestion( storiesService.respondToQuestion(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0], currentStory.getStoryMediaId().split("_")[0],
question.getId(), question.getId(),
input.getText().toString(), input.getText().toString(),
new ServiceCallback<StoryStickerResponse>() { CoroutineUtilsKt.getContinuation(
@Override (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
public void onSuccess(final StoryStickerResponse result) { if (throwable != null) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", throwable);
try { try {
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
return;
} }
@Override
public void onFailure(final Throwable t) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", t);
try { try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }),
}); Dispatchers.getIO()
)
);
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
@ -605,28 +613,31 @@ public class StoryViewerFragment extends Fragment {
if (quiz.getMyChoice() == -1) { if (quiz.getMyChoice() == -1) {
sticking = true; sticking = true;
storiesService.respondToQuiz( storiesService.respondToQuiz(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0], currentStory.getStoryMediaId().split("_")[0],
quiz.getId(), quiz.getId(),
w, w,
new ServiceCallback<StoryStickerResponse>() { CoroutineUtilsKt.getContinuation(
@Override (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
public void onSuccess(final StoryStickerResponse result) { if (throwable != null) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", throwable);
try { try {
quiz.setMyChoice(w); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
return;
} }
@Override
public void onFailure(final Throwable t) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", t);
try { try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); quiz.setMyChoice(w);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }),
}); Dispatchers.getIO()
)
);
} }
}) })
.setPositiveButton(R.string.cancel, null) .setPositiveButton(R.string.cancel, null)
@ -673,28 +684,30 @@ public class StoryViewerFragment extends Fragment {
.setPositiveButton(R.string.confirm, (d, w) -> { .setPositiveButton(R.string.confirm, (d, w) -> {
sticking = true; sticking = true;
storiesService.respondToSlider( storiesService.respondToSlider(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0], currentStory.getStoryMediaId().split("_")[0],
slider.getId(), slider.getId(),
sliderValue, sliderValue,
new ServiceCallback<StoryStickerResponse>() { CoroutineUtilsKt.getContinuation(
@Override (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
public void onSuccess(final StoryStickerResponse result) { if (throwable != null) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", throwable);
try { try {
slider.setMyChoice(sliderValue); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
return;
} }
@Override
public void onFailure(final Throwable t) {
sticking = false; sticking = false;
Log.e(TAG, "Error responding", t);
try { try {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); slider.setMyChoice(sliderValue);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }), Dispatchers.getIO()
}); )
);
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
@ -786,9 +799,14 @@ public class StoryViewerFragment extends Fragment {
setTitle(type); setTitle(type);
storiesViewModel.getList().setValue(Collections.emptyList()); storiesViewModel.getList().setValue(Collections.emptyList());
if (type == Type.STORY) { if (type == Type.STORY) {
storiesService.fetch(options.getId(), new ServiceCallback<StoryModel>() { storiesService.fetch(
@Override options.getId(),
public void onSuccess(final StoryModel storyModel) { CoroutineUtilsKt.getContinuation((storyModel, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error", throwable);
return;
}
fetching = false; fetching = false;
binding.storiesList.setVisibility(View.GONE); binding.storiesList.setVisibility(View.GONE);
if (storyModel == null) { if (storyModel == null) {
@ -799,14 +817,8 @@ public class StoryViewerFragment extends Fragment {
storiesViewModel.getList().setValue(Collections.singletonList(storyModel)); storiesViewModel.getList().setValue(Collections.singletonList(storyModel));
currentStory = storyModel; currentStory = storyModel;
refreshStory(); refreshStory();
} }), Dispatchers.getIO())
);
@Override
public void onFailure(final Throwable t) {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error", t);
}
});
return; return;
} }
if (currentStoryMediaId == null) return; if (currentStoryMediaId == null) return;
@ -840,7 +852,17 @@ public class StoryViewerFragment extends Fragment {
storyCallback.onSuccess(Collections.singletonList(live)); storyCallback.onSuccess(Collections.singletonList(live));
return; return;
} }
storiesService.getUserStory(fetchOptions, storyCallback); storiesService.getUserStory(
fetchOptions,
CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
storyCallback.onFailure(throwable);
return;
}
//noinspection unchecked
storyCallback.onSuccess((List<StoryModel>) storyModels);
}), Dispatchers.getIO())
);
} }
private void setTitle(final Type type) { private void setTitle(final Type type) {
@ -944,10 +966,15 @@ public class StoryViewerFragment extends Fragment {
} }
if (settingsHelper.getBoolean(MARK_AS_SEEN)) if (settingsHelper.getBoolean(MARK_AS_SEEN))
storiesService.seen(currentStory.getStoryMediaId(), storiesService.seen(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId(),
currentStory.getTimestamp(), currentStory.getTimestamp(),
System.currentTimeMillis() / 1000, System.currentTimeMillis() / 1000,
null); CoroutineUtilsKt.getContinuation((s, throwable) -> {}, Dispatchers.getIO())
);
} }
private void downloadStory() { private void downloadStory() {

30
app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java

@ -48,12 +48,14 @@ import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedStoriesViewModel; import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import kotlinx.coroutines.Dispatchers;
import static androidx.core.content.PermissionChecker.checkSelfPermission; import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
@ -274,7 +276,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance(null, 0L, null); storiesService = StoriesService.INSTANCE;
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@ -428,23 +430,23 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
// final String cookie = settingsHelper.getString(Constants.COOKIE); // final String cookie = settingsHelper.getString(Constants.COOKIE);
storiesFetching = true; storiesFetching = true;
updateSwipeRefreshState(); updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { storiesService.getFeedStories(
@Override CoroutineUtilsKt.getContinuation((feedStoryModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
public void onSuccess(final List<FeedStoryModel> result) { if (throwable != null) {
Log.e(TAG, "failed", throwable);
storiesFetching = false; storiesFetching = false;
feedStoriesViewModel.getList().postValue(result);
feedStoriesAdapter.submitList(result);
if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState(); updateSwipeRefreshState();
return;
} }
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "failed", t);
storiesFetching = false; storiesFetching = false;
//noinspection unchecked
feedStoriesViewModel.getList().postValue((List<FeedStoryModel>) feedStoryModels);
//noinspection unchecked
feedStoriesAdapter.submitList((List<FeedStoryModel>) feedStoryModels);
if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState(); updateSwipeRefreshState();
} }), Dispatchers.getIO())
}); );
} }
private void showPostsLayoutPreferences() { private void showPostsLayoutPreferences() {

45
app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java

@ -74,7 +74,6 @@ import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.InboxManager; import awais.instagrabber.managers.InboxManager;
import awais.instagrabber.models.HighlightModel; import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
@ -337,7 +336,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
friendshipService = isLoggedIn ? FriendshipService.INSTANCE : null; friendshipService = isLoggedIn ? FriendshipService.INSTANCE : null;
directMessagesService = isLoggedIn ? DirectMessagesService.getInstance(csrfToken, myId, deviceUuid) : null; directMessagesService = isLoggedIn ? DirectMessagesService.getInstance(csrfToken, myId, deviceUuid) : null;
storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null; storiesService = isLoggedIn ? StoriesService.INSTANCE : null;
mediaService = isLoggedIn ? MediaService.INSTANCE : null; mediaService = isLoggedIn ? MediaService.INSTANCE : null;
userService = isLoggedIn ? UserService.INSTANCE : null; userService = isLoggedIn ? UserService.INSTANCE : null;
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE; graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
@ -1044,36 +1043,34 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void fetchStoryAndHighlights(final long profileId) { private void fetchStoryAndHighlights(final long profileId) {
storiesService.getUserStory( storiesService.getUserStory(
StoryViewerOptions.forUser(profileId, profileModel.getFullName()), StoryViewerOptions.forUser(profileId, profileModel.getFullName()),
new ServiceCallback<List<StoryModel>>() { CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@Override if (throwable != null) {
public void onSuccess(final List<StoryModel> storyModels) { Log.e(TAG, "Error", throwable);
return;
}
if (storyModels != null && !storyModels.isEmpty()) { if (storyModels != null && !storyModels.isEmpty()) {
profileDetailsBinding.mainProfileImage.setStoriesBorder(1); profileDetailsBinding.mainProfileImage.setStoriesBorder(1);
hasStories = true; hasStories = true;
} }
}), Dispatchers.getIO())
);
storiesService.fetchHighlights(
profileId,
CoroutineUtilsKt.getContinuation((highlightModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
profileDetailsBinding.highlightsList.setVisibility(View.GONE);
Log.e(TAG, "Error", throwable);
return;
} }
if (highlightModels != null) {
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
storiesService.fetchHighlights(profileId,
new ServiceCallback<List<HighlightModel>>() {
@Override
public void onSuccess(final List<HighlightModel> result) {
if (result != null) {
profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result); //noinspection unchecked
} else profileDetailsBinding.highlightsList.setVisibility(View.GONE); highlightsViewModel.getList().postValue((List<HighlightModel>) highlightModels);
} } else {
@Override
public void onFailure(final Throwable t) {
profileDetailsBinding.highlightsList.setVisibility(View.GONE); profileDetailsBinding.highlightsList.setVisibility(View.GONE);
Log.e(TAG, "Error", t);
} }
}); }), Dispatchers.getIO())
);
} }
private void setupCommonListeners() { private void setupCommonListeners() {

45
app/src/main/java/awais/instagrabber/repositories/StoriesRepository.kt

@ -1,43 +1,38 @@
package awais.instagrabber.repositories; package awais.instagrabber.repositories
import java.util.Map; import awais.instagrabber.repositories.responses.StoryStickerResponse
import retrofit2.http.*
import awais.instagrabber.repositories.responses.StoryStickerResponse; interface StoriesRepository {
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;
public interface StoriesRepository {
@GET("/api/v1/media/{mediaId}/info/")
Call<String> fetch(@Path("mediaId") final long mediaId);
// this one is the same as MediaRepository.fetch BUT you need to make sure it's a story // this one is the same as MediaRepository.fetch BUT you need to make sure it's a story
@GET("/api/v1/media/{mediaId}/info/")
suspend fun fetch(@Path("mediaId") mediaId: Long): String
@GET("/api/v1/feed/reels_tray/") @GET("/api/v1/feed/reels_tray/")
Call<String> getFeedStories(); suspend fun getFeedStories(): String
@GET("/api/v1/highlights/{uid}/highlights_tray/") @GET("/api/v1/highlights/{uid}/highlights_tray/")
Call<String> fetchHighlights(@Path("uid") final long uid); suspend fun fetchHighlights(@Path("uid") uid: Long): String
@GET("/api/v1/archive/reel/day_shells/") @GET("/api/v1/archive/reel/day_shells/")
Call<String> fetchArchive(@QueryMap Map<String, String> queryParams); suspend fun fetchArchive(@QueryMap queryParams: Map<String, String>): String
@GET @GET
Call<String> getUserStory(@Url String url); suspend fun getUserStory(@Url url: String): String
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/media/{storyId}/{stickerId}/{action}/") @POST("/api/v1/media/{storyId}/{stickerId}/{action}/")
Call<StoryStickerResponse> respondToSticker(@Path("storyId") String storyId, suspend fun respondToSticker(
@Path("stickerId") String stickerId, @Path("storyId") storyId: String,
@Path("action") String action, @Path("stickerId") stickerId: String,
// story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer @Path("action") action: String, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer
@FieldMap Map<String, String> form); @FieldMap form: Map<String, String>,
): StoryStickerResponse
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v2/media/seen/") @POST("/api/v2/media/seen/")
Call<String> seen(@QueryMap Map<String, String> queryParams, @FieldMap Map<String, String> form); suspend fun seen(
@QueryMap queryParams: Map<String, String>,
@FieldMap form: Map<String, String>,
): String
} }

733
app/src/main/java/awais/instagrabber/webservices/StoriesService.kt

@ -1,158 +1,61 @@
package awais.instagrabber.webservices; package awais.instagrabber.webservices
import android.util.Log
import android.util.Log; import awais.instagrabber.fragments.settings.PreferenceKeys
import awais.instagrabber.models.FeedStoryModel
import androidx.annotation.NonNull; import awais.instagrabber.models.HighlightModel
import androidx.annotation.Nullable; import awais.instagrabber.models.StoryModel
import awais.instagrabber.repositories.StoriesRepository
import org.json.JSONArray; import awais.instagrabber.repositories.requests.StoryViewerOptions
import org.json.JSONException; import awais.instagrabber.repositories.responses.StoryStickerResponse
import org.json.JSONObject; import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.Constants
import java.util.ArrayList; import awais.instagrabber.utils.ResponseBodyUtils
import java.util.Collections; import awais.instagrabber.utils.TextUtils.isEmpty
import java.util.HashMap; import awais.instagrabber.utils.Utils
import java.util.List; import awais.instagrabber.utils.extensions.TAG
import java.util.Map; import awais.instagrabber.webservices.RetrofitFactory.retrofit
import java.util.Objects; import org.json.JSONArray
import java.util.UUID; import org.json.JSONObject
import java.util.*
import awais.instagrabber.fragments.settings.PreferenceKeys; object StoriesService : BaseService() {
import awais.instagrabber.models.FeedStoryModel; private val repository: StoriesRepository = retrofit.create(StoriesRepository::class.java)
import awais.instagrabber.models.HighlightModel; suspend fun fetch(mediaId: Long): StoryModel {
import awais.instagrabber.models.StoryModel; val response = repository.fetch(mediaId)
import awais.instagrabber.repositories.StoriesRepository; val itemJson = JSONObject(response).getJSONArray("items").getJSONObject(0)
import awais.instagrabber.repositories.requests.StoryViewerOptions; return ResponseBodyUtils.parseStoryItem(itemJson, false, null)
import awais.instagrabber.repositories.responses.StoryStickerResponse; }
import awais.instagrabber.repositories.responses.User; suspend fun getFeedStories(): List<FeedStoryModel> {
import awais.instagrabber.utils.Constants; val response = repository.getFeedStories()
import awais.instagrabber.utils.ResponseBodyUtils; return parseStoriesBody(response)
import awais.instagrabber.utils.TextUtils; }
import awais.instagrabber.utils.Utils; private fun parseStoriesBody(body: String): List<FeedStoryModel> {
import retrofit2.Call; val feedStoryModels: MutableList<FeedStoryModel> = ArrayList()
import retrofit2.Callback; val feedStoriesReel = JSONObject(body).getJSONArray("tray")
import retrofit2.Response; for (i in 0 until feedStoriesReel.length()) {
val node = feedStoriesReel.getJSONObject(i)
public class StoriesService extends BaseService { if (node.optBoolean("hide_from_feed_unit") && Utils.settingsHelper.getBoolean(PreferenceKeys.HIDE_MUTED_REELS)) continue
private static final String TAG = "StoriesService"; val userJson = node.getJSONObject(if (node.has("user")) "user" else "owner")
private static StoriesService instance;
private final StoriesRepository repository;
private final String csrfToken;
private final long userId;
private final String deviceUuid;
private StoriesService(@NonNull final String csrfToken,
final long userId,
@NonNull final String deviceUuid) {
this.csrfToken = csrfToken;
this.userId = userId;
this.deviceUuid = deviceUuid;
repository = RetrofitFactory.INSTANCE
.getRetrofit()
.create(StoriesRepository.class);
}
public String getCsrfToken() {
return csrfToken;
}
public long getUserId() {
return userId;
}
public String getDeviceUuid() {
return deviceUuid;
}
public static StoriesService getInstance(final String csrfToken,
final long userId,
final String deviceUuid) {
if (instance == null
|| !Objects.equals(instance.getCsrfToken(), csrfToken)
|| !Objects.equals(instance.getUserId(), userId)
|| !Objects.equals(instance.getDeviceUuid(), deviceUuid)) {
instance = new StoriesService(csrfToken, userId, deviceUuid);
}
return instance;
}
public void fetch(final long mediaId,
final ServiceCallback<StoryModel> callback) {
final Call<String> request = repository.fetch(mediaId);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call,
@NonNull final Response<String> response) {
if (callback == null) return;
final String body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
try {
final JSONObject itemJson = new JSONObject(body).getJSONArray("items").getJSONObject(0);
callback.onSuccess(ResponseBodyUtils.parseStoryItem(itemJson, false, null));
} catch (JSONException e) {
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call,
@NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void getFeedStories(final ServiceCallback<List<FeedStoryModel>> callback) {
final Call<String> response = repository.getFeedStories();
response.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "getFeedStories: body is empty");
return;
}
parseStoriesBody(body, callback);
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
private void parseStoriesBody(final String body, final ServiceCallback<List<FeedStoryModel>> callback) {
try {
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
final JSONArray feedStoriesReel = new JSONObject(body).getJSONArray("tray");
for (int i = 0; i < feedStoriesReel.length(); ++i) {
final JSONObject node = feedStoriesReel.getJSONObject(i);
if (node.optBoolean("hide_from_feed_unit") && Utils.settingsHelper.getBoolean(PreferenceKeys.HIDE_MUTED_REELS)) continue;
final JSONObject userJson = node.getJSONObject(node.has("user") ? "user" : "owner");
try { try {
final User user = new User(userJson.getLong("pk"), val user = User(userJson.getLong("pk"),
userJson.getString("username"), userJson.getString("username"),
userJson.optString("full_name"), userJson.optString("full_name"),
userJson.optBoolean("is_private"), userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"), userJson.getString("profile_pic_url"),
userJson.optBoolean("is_verified") userJson.optBoolean("is_verified")
); )
final long timestamp = node.getLong("latest_reel_media"); val timestamp = node.getLong("latest_reel_media")
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp; val fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp
final JSONObject itemJson = node.has("items") ? node.getJSONArray("items").optJSONObject(0) : null; val itemJson = if (node.has("items")) node.getJSONArray("items").optJSONObject(0) else null
StoryModel firstStoryModel = null; var firstStoryModel: StoryModel? = null
if (itemJson != null) { if (itemJson != null) {
firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, null); firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, null)
} }
feedStoryModels.add(new FeedStoryModel( feedStoryModels.add(FeedStoryModel(
node.getString("id"), node.getString("id"),
user, user,
fullyRead, fullyRead,
@ -160,68 +63,44 @@ public class StoriesService extends BaseService {
firstStoryModel, firstStoryModel,
node.getInt("media_count"), node.getInt("media_count"),
false, false,
node.optBoolean("has_besties_media"))); node.optBoolean("has_besties_media")))
} catch (Exception e) { } catch (e: Exception) {
Log.e(TAG, "parseStoriesBody: ", e); Log.e(TAG, "parseStoriesBody: ", e)
} // to cover promotional reels with non-long user pk's } // to cover promotional reels with non-long user pk's
} }
final JSONArray broadcasts = new JSONObject(body).getJSONArray("broadcasts"); val broadcasts = JSONObject(body).getJSONArray("broadcasts")
for (int i = 0; i < broadcasts.length(); ++i) { for (i in 0 until broadcasts.length()) {
final JSONObject node = broadcasts.getJSONObject(i); val node = broadcasts.getJSONObject(i)
final JSONObject userJson = node.getJSONObject("broadcast_owner"); val userJson = node.getJSONObject("broadcast_owner")
// final ProfileModel profileModel = new ProfileModel(false, false, false, val user = User(userJson.getLong("pk"),
// userJson.getString("pk"),
// userJson.getString("username"),
// null, null, null,
// userJson.getString("profile_pic_url"),
// null, 0, 0, 0, false, false, false, false, false);
final User user = new User(userJson.getLong("pk"),
userJson.getString("username"), userJson.getString("username"),
userJson.optString("full_name"), userJson.optString("full_name"),
userJson.optBoolean("is_private"), userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"), userJson.getString("profile_pic_url"),
userJson.optBoolean("is_verified") userJson.optBoolean("is_verified")
); )
feedStoryModels.add(new FeedStoryModel( feedStoryModels.add(FeedStoryModel(
node.getString("id"), node.getString("id"),
user, user,
false, false,
node.getLong("published_time"), node.getLong("published_time"),
ResponseBodyUtils.parseBroadcastItem(node), ResponseBodyUtils.parseBroadcastItem(node),
1, 1,
true, isLive = true,
false isBestie = false
)); ))
}
callback.onSuccess(sort(feedStoryModels));
} catch (JSONException e) {
Log.e(TAG, "Error parsing json", e);
} }
return sort(feedStoryModels)
} }
public void fetchHighlights(final long profileId, suspend fun fetchHighlights(profileId: Long): List<HighlightModel> {
final ServiceCallback<List<HighlightModel>> callback) { val response = repository.fetchHighlights(profileId)
final Call<String> request = repository.fetchHighlights(profileId); val highlightsReel = JSONObject(response).getJSONArray("tray")
request.enqueue(new Callback<String>() { val length = highlightsReel.length()
@Override val highlightModels: MutableList<HighlightModel> = ArrayList()
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { for (i in 0 until length) {
try { val highlightNode = highlightsReel.getJSONObject(i)
if (callback == null) { highlightModels.add(HighlightModel(
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final JSONArray highlightsReel = new JSONObject(body).getJSONArray("tray");
final int length = highlightsReel.length();
final List<HighlightModel> highlightModels = new ArrayList<>();
for (int i = 0; i < length; ++i) {
final JSONObject highlightNode = highlightsReel.getJSONObject(i);
highlightModels.add(new HighlightModel(
highlightNode.getString("title"), highlightNode.getString("title"),
highlightNode.getString(Constants.EXTRAS_ID), highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_media") highlightNode.getJSONObject("cover_media")
@ -229,320 +108,202 @@ public class StoriesService extends BaseService {
.getString("url"), .getString("url"),
highlightNode.getLong("latest_reel_media"), highlightNode.getLong("latest_reel_media"),
highlightNode.getInt("media_count") highlightNode.getInt("media_count")
)); ))
} }
callback.onSuccess(highlightModels); return highlightModels
} catch (JSONException e) { }
Log.e(TAG, "onResponse", e); suspend fun fetchArchive(maxId: String): ArchiveFetchResponse {
callback.onFailure(e); val form = mutableMapOf(
} "include_suggested_highlights" to "false",
} "is_in_archive_home" to "true",
"include_cover" to "1",
@Override )
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { if (!isEmpty(maxId)) {
if (callback != null) { form["max_id"] = maxId // NOT TESTED
callback.onFailure(t); }
} val response = repository.fetchArchive(form)
} val data = JSONObject(response)
}); val highlightsReel = data.getJSONArray("items")
} val length = highlightsReel.length()
val highlightModels: MutableList<HighlightModel> = ArrayList()
public void fetchArchive(final String maxId, for (i in 0 until length) {
final ServiceCallback<ArchiveFetchResponse> callback) { val highlightNode = highlightsReel.getJSONObject(i)
final Map<String, String> form = new HashMap<>(); highlightModels.add(HighlightModel(
form.put("include_suggested_highlights", "false");
form.put("is_in_archive_home", "true");
form.put("include_cover", "1");
if (!TextUtils.isEmpty(maxId)) {
form.put("max_id", maxId); // NOT TESTED
}
final Call<String> request = repository.fetchArchive(form);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> 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<HighlightModel> highlightModels = new ArrayList<>();
for (int i = 0; i < length; ++i) {
final JSONObject highlightNode = highlightsReel.getJSONObject(i);
highlightModels.add(new HighlightModel(
null, null,
highlightNode.getString(Constants.EXTRAS_ID), highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_image_version").getString("url"), highlightNode.getJSONObject("cover_image_version").getString("url"),
highlightNode.getLong("latest_reel_media"), highlightNode.getLong("latest_reel_media"),
highlightNode.getInt("media_count") highlightNode.getInt("media_count")
)); ))
}
callback.onSuccess(new ArchiveFetchResponse(highlightModels,
data.getBoolean("more_available"),
data.getString("max_id")));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
} }
return ArchiveFetchResponse(highlightModels, data.getBoolean("more_available"), data.getString("max_id"))
} }
@Override suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> {
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { val url = buildUrl(options) ?: return emptyList()
if (callback != null) { val response = repository.getUserStory(url)
callback.onFailure(t); 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")
public void getUserStory(final StoryViewerOptions options,
final ServiceCallback<List<StoryModel>> callback) {
final String url = buildUrl(options);
final Call<String> userStoryCall = repository.getUserStory(url);
final boolean isLocOrHashtag = options.getType() == StoryViewerOptions.Type.LOCATION || options.getType() == StoryViewerOptions.Type.HASHTAG;
final boolean isHighlight = options.getType() == StoryViewerOptions.Type.HIGHLIGHT || options
.getType() == StoryViewerOptions.Type.STORY_ARCHIVE;
userStoryCall.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
JSONObject data;
try {
final String body = response.body();
if (body == null) {
Log.e(TAG, "body is null");
return;
}
data = new JSONObject(body);
if (!isHighlight) {
data = data.optJSONObject((isLocOrHashtag) ? "story" : "reel");
} else { } else {
data = data.getJSONObject("reels").optJSONObject(options.getName()); data?.getJSONObject("reels")?.optJSONObject(options.name)
} }
var username: String? = null
String username = null; if (data != null && !isLocOrHashtag) {
if (data != null username = data.getJSONObject("user").getString("username")
// && localUsername == null }
&& !isLocOrHashtag) { val media: JSONArray? = data?.optJSONArray("items")
username = data.getJSONObject("user").getString("username"); return if (media?.length() ?: 0 > 0 && media?.optJSONObject(0) != null) {
} val mediaLen = media.length()
val models: MutableList<StoryModel> = ArrayList()
JSONArray media; for (i in 0 until mediaLen) {
if (data != null data = media.getJSONObject(i)
&& (media = data.optJSONArray("items")) != null models.add(ResponseBodyUtils.parseStoryItem(data, isLocOrHashtag, username))
&& media.length() > 0 && media.optJSONObject(0) != null) { }
final int mediaLen = media.length(); models
final List<StoryModel> models = new ArrayList<>(); } else emptyList()
for (int i = 0; i < mediaLen; ++i) { }
data = media.getJSONObject(i); private suspend fun respondToSticker(
models.add(ResponseBodyUtils.parseStoryItem(data, isLocOrHashtag, username)); csrfToken: String,
} userId: Long,
callback.onSuccess(models); deviceUuid: String,
} else { storyId: String,
callback.onSuccess(null); stickerId: String,
} action: String,
} catch (JSONException e) { arg1: String,
Log.e(TAG, "Error parsing string", e); arg2: String,
} ): StoryStickerResponse {
} val form = mapOf(
"_csrftoken" to csrfToken,
@Override "_uid" to userId,
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { "_uuid" to deviceUuid,
callback.onFailure(t); "mutation_token" to UUID.randomUUID().toString(),
} "client_context" to UUID.randomUUID().toString(),
}); "radio_type" to "wifi-none",
} arg1 to arg2,
)
private void respondToSticker(final String storyId, val signedForm = Utils.sign(form)
final String stickerId, return repository.respondToSticker(storyId, stickerId, action, signedForm)
final String action, }
final String arg1, suspend fun respondToQuestion(
final String arg2, csrfToken: String,
final ServiceCallback<StoryStickerResponse> callback) { userId: Long,
final Map<String, Object> form = new HashMap<>(); deviceUuid: String,
form.put("_csrftoken", csrfToken); storyId: String,
form.put("_uid", userId); stickerId: String,
form.put("_uuid", deviceUuid); answer: String,
form.put("mutation_token", UUID.randomUUID().toString()); ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer)
form.put("client_context", UUID.randomUUID().toString()); suspend fun respondToQuiz(
form.put("radio_type", "wifi-none"); csrfToken: String,
form.put(arg1, arg2); userId: Long,
final Map<String, String> signedForm = Utils.sign(form); deviceUuid: String,
final Call<StoryStickerResponse> request = storyId: String,
repository.respondToSticker(storyId, stickerId, action, signedForm); stickerId: String,
request.enqueue(new Callback<StoryStickerResponse>() { answer: Int,
@Override ): StoryStickerResponse {
public void onResponse(@NonNull final Call<StoryStickerResponse> call, return respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_quiz_answer", "answer", answer.toString())
@NonNull final Response<StoryStickerResponse> response) { }
if (callback != null) { suspend fun respondToPoll(
callback.onSuccess(response.body()); csrfToken: String,
} userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
answer: Int,
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString())
suspend fun respondToSlider(
csrfToken: String,
userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
answer: Double,
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString())
suspend fun seen(
csrfToken: String,
userId: Long,
deviceUuid: String,
storyMediaId: String,
takenAt: Long,
seenAt: Long,
): String {
val reelsForm = mapOf(storyMediaId to listOf(takenAt.toString() + "_" + seenAt))
val form = mutableMapOf(
"_csrftoken" to csrfToken,
"_uid" to userId,
"_uuid" to deviceUuid,
"container_module" to "feed_timeline",
"reels" to reelsForm,
)
val signedForm = Utils.sign(form)
val queryMap = mapOf(
"reel" to "1",
"live_vod" to "0",
)
return repository.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 -> {
} }
@Override
public void onFailure(@NonNull final Call<StoryStickerResponse> call,
@NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
// RespondAction.java
public void respondToQuestion(final String storyId,
final String stickerId,
final String answer,
final ServiceCallback<StoryStickerResponse> callback) {
respondToSticker(storyId, stickerId, "story_question_response", "response", answer, callback);
}
// QuizAction.java
public void respondToQuiz(final String storyId,
final String stickerId,
final int answer,
final ServiceCallback<StoryStickerResponse> callback) {
respondToSticker(storyId, stickerId, "story_quiz_answer", "answer", String.valueOf(answer), callback);
}
// VoteAction.java
public void respondToPoll(final String storyId,
final String stickerId,
final int answer,
final ServiceCallback<StoryStickerResponse> callback) {
respondToSticker(storyId, stickerId, "story_poll_vote", "vote", String.valueOf(answer), callback);
}
public void respondToSlider(final String storyId,
final String stickerId,
final double answer,
final ServiceCallback<StoryStickerResponse> callback) {
respondToSticker(storyId, stickerId, "story_slider_vote", "vote", String.valueOf(answer), callback);
}
public void seen(final String storyMediaId,
final long takenAt,
final long seenAt,
final ServiceCallback<String> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", deviceUuid);
form.put("container_module", "feed_timeline");
final Map<String, Object> reelsForm = new HashMap<>();
reelsForm.put(storyMediaId, Collections.singletonList(takenAt + "_" + seenAt));
form.put("reels", reelsForm);
final Map<String, String> signedForm = Utils.sign(form);
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("reel", "1");
queryMap.put("live_vod", "0");
final Call<String> request = repository.seen(queryMap, signedForm);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call,
@NonNull final Response<String> response) {
if (callback != null) {
callback.onSuccess(response.body());
}
}
@Override
public void onFailure(@NonNull final Call<String> call,
@NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
@Nullable
private String buildUrl(@NonNull final StoryViewerOptions options) {
final StringBuilder builder = new StringBuilder();
builder.append("https://i.instagram.com/api/v1/");
final StoryViewerOptions.Type type = options.getType();
String id = null;
switch (type) {
case HASHTAG:
builder.append("tags/");
id = options.getName();
break;
case LOCATION:
builder.append("locations/");
id = String.valueOf(options.getId());
break;
case USER:
builder.append("feed/user/");
id = String.valueOf(options.getId());
break;
case HIGHLIGHT:
case STORY_ARCHIVE:
builder.append("feed/reels_media/?user_ids=");
id = options.getName();
break;
case STORY:
break;
// case FEED_STORY_POSITION:
// break;
} }
if (id == null) { if (id == null) {
return null; return null
} }
builder.append(id); builder.append(id)
if (type != StoryViewerOptions.Type.HIGHLIGHT && type != StoryViewerOptions.Type.STORY_ARCHIVE) { if (type != StoryViewerOptions.Type.HIGHLIGHT && type != StoryViewerOptions.Type.STORY_ARCHIVE) {
builder.append("/story/"); builder.append("/story/")
}
return builder.toString();
} }
return builder.toString()
private List<FeedStoryModel> sort(final List<FeedStoryModel> list) {
final List<FeedStoryModel> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
int result;
switch (Utils.settingsHelper.getString(PreferenceKeys.STORY_SORT)) {
case "1":
result = Long.compare(o2.getTimestamp(), o1.getTimestamp());
break;
case "2":
result = Long.compare(o1.getTimestamp(), o2.getTimestamp());
break;
default:
result = 0;
}
return result;
});
return listCopy;
} }
public static class ArchiveFetchResponse { private fun sort(list: List<FeedStoryModel>): List<FeedStoryModel> {
private final List<HighlightModel> archives; val listCopy = ArrayList(list)
private final boolean hasNextPage; listCopy.sortWith { o1, o2 ->
private final String nextCursor; when (Utils.settingsHelper.getString(PreferenceKeys.STORY_SORT)) {
"1" -> return@sortWith o2.timestamp.compareTo(o1.timestamp)
public ArchiveFetchResponse(final List<HighlightModel> archives, final boolean hasNextPage, final String nextCursor) { "2" -> return@sortWith o1.timestamp.compareTo(o2.timestamp)
this.archives = archives; else -> return@sortWith 0
this.hasNextPage = hasNextPage;
this.nextCursor = nextCursor;
} }
public List<HighlightModel> getResult() {
return archives;
} }
return listCopy
public boolean hasNextPage() {
return hasNextPage;
} }
public String getNextCursor() { class ArchiveFetchResponse(val result: List<HighlightModel>, val hasNextPage: Boolean, val nextCursor: String) {
return nextCursor; fun hasNextPage(): Boolean {
return hasNextPage
} }
} }
} }
|||||||
100:0
Loading…
Cancel
Save