Browse Source

Merge branch 'master' of https://github.com/austinhuang0131/instagrabber

renovate/org.robolectric-robolectric-4.x
Ammar Githam 3 years ago
parent
commit
16f0a34f55
  1. 16
      app/build.gradle
  2. 2
      app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java
  3. 4
      app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
  4. 20
      app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
  5. 30
      app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java
  6. 14
      app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java
  7. 14
      app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
  8. 111
      app/src/main/java/awais/instagrabber/adapters/MediaItemsAdapter.java
  9. 17
      app/src/main/java/awais/instagrabber/adapters/viewholder/FeedStoryViewHolder.java
  10. 6
      app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java
  11. 23
      app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java
  12. 36
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
  13. 2
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java
  14. 20
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java
  15. 2
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
  16. 2
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java
  17. 4
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
  18. 2
      app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java
  19. 2
      app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java
  20. 2
      app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java
  21. 2
      app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java
  22. 2
      app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java
  23. 194
      app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java
  24. 10
      app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
  25. 31
      app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java
  26. 143
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
  27. 13
      app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java
  28. 43
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
  29. 14
      app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
  30. 3
      app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
  31. 11
      app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt
  32. 12
      app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
  33. 20
      app/src/main/java/awais/instagrabber/models/FeedStoryModel.kt
  34. 14
      app/src/main/java/awais/instagrabber/models/HighlightModel.kt
  35. 10
      app/src/main/java/awais/instagrabber/repositories/StoriesService.kt
  36. 8
      app/src/main/java/awais/instagrabber/repositories/requests/directmessages/MediaShareBroadcastOptions.kt
  37. 16
      app/src/main/java/awais/instagrabber/repositories/responses/HdProfilePicUrlInfo.java
  38. 5
      app/src/main/java/awais/instagrabber/repositories/responses/ImageUrl.kt
  39. 30
      app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.java
  40. 5
      app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.kt
  41. 27
      app/src/main/java/awais/instagrabber/repositories/responses/LikersResponse.java
  42. 3
      app/src/main/java/awais/instagrabber/repositories/responses/LikersResponse.kt
  43. 4
      app/src/main/java/awais/instagrabber/repositories/responses/Media.kt
  44. 43
      app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.java
  45. 5
      app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.kt
  46. 15
      app/src/main/java/awais/instagrabber/repositories/responses/MediaInfoResponse.java
  47. 3
      app/src/main/java/awais/instagrabber/repositories/responses/MediaInfoResponse.kt
  48. 32
      app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.java
  49. 10
      app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.kt
  50. 62
      app/src/main/java/awais/instagrabber/repositories/responses/Place.java
  51. 12
      app/src/main/java/awais/instagrabber/repositories/responses/Place.kt
  52. 27
      app/src/main/java/awais/instagrabber/repositories/responses/PostsFetchResponse.java
  53. 7
      app/src/main/java/awais/instagrabber/repositories/responses/PostsFetchResponse.kt
  54. 20
      app/src/main/java/awais/instagrabber/repositories/responses/StoryStickerResponse.java
  55. 43
      app/src/main/java/awais/instagrabber/repositories/responses/TagFeedResponse.java
  56. 9
      app/src/main/java/awais/instagrabber/repositories/responses/TagFeedResponse.kt
  57. 2
      app/src/main/java/awais/instagrabber/repositories/responses/User.kt
  58. 43
      app/src/main/java/awais/instagrabber/repositories/responses/UserFeedResponse.java
  59. 9
      app/src/main/java/awais/instagrabber/repositories/responses/UserFeedResponse.kt
  60. 13
      app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java
  61. 3
      app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.kt
  62. 9
      app/src/main/java/awais/instagrabber/repositories/responses/stories/ArchiveResponse.kt
  63. 4
      app/src/main/java/awais/instagrabber/repositories/responses/stories/Broadcast.kt
  64. 5
      app/src/main/java/awais/instagrabber/repositories/responses/stories/CoverMedia.kt
  65. 37
      app/src/main/java/awais/instagrabber/repositories/responses/stories/Story.kt
  66. 5
      app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt
  67. 3
      app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryStickerResponse.kt
  68. 13
      app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt
  69. 386
      app/src/main/java/awais/instagrabber/utils/MediaController.java
  70. 28
      app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
  71. 6
      app/src/main/java/awais/instagrabber/viewmodels/ArchivesViewModel.java
  72. 19
      app/src/main/java/awais/instagrabber/viewmodels/CommentsViewerViewModel.java
  73. 6
      app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
  74. 7
      app/src/main/java/awais/instagrabber/viewmodels/FeedStoriesViewModel.java
  75. 6
      app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java
  76. 41
      app/src/main/java/awais/instagrabber/viewmodels/MediaPickerViewModel.java
  77. 10
      app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.kt
  78. 14
      app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt
  79. 3
      app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt
  80. 6
      app/src/main/java/awais/instagrabber/webservices/ProfileService.java
  81. 155
      app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt
  82. 2
      app/src/main/java/awais/instagrabber/webservices/TagsService.java
  83. 5
      app/src/main/res/color/ic_read_button_tint.xml
  84. 2
      app/src/main/res/drawable/ic_check_all_24.xml
  85. 4
      app/src/main/res/layout/fragment_story_viewer.xml
  86. 22
      app/src/main/res/layout/layout_dm_media_share.xml
  87. 30
      app/src/main/res/layout/layout_media_picker.xml
  88. 3
      app/src/main/res/values/strings.xml
  89. 9
      app/src/test/java/awais/instagrabber/common/Adapters.kt
  90. 6
      app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt
  91. 2
      build.gradle
  92. 6
      gradle.properties

16
app/build.gradle

@ -57,7 +57,7 @@ android {
buildTypes { buildTypes {
debug { debug {
minifyEnabled true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
@ -166,13 +166,13 @@ dependencies {
def nav_version = '2.3.5' def nav_version = '2.3.5'
def exoplayer_version = '2.14.1' def exoplayer_version = '2.14.1'
implementation 'com.google.android.material:material:1.4.0-beta01'
implementation 'com.google.android.material:material:1.4.0-rc01'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-fragment:$nav_version"
@ -183,11 +183,11 @@ dependencies {
implementation 'com.google.guava:guava:27.0.1-android' implementation 'com.google.guava:guava:27.0.1-android'
def core_version = "1.6.0-beta01"
def core_version = "1.6.0-rc01"
implementation "androidx.core:core:$core_version" implementation "androidx.core:core:$core_version"
// Fragment // Fragment
implementation "androidx.fragment:fragment-ktx:1.3.4"
implementation "androidx.fragment:fragment-ktx:1.3.5"
// Lifecycle // Lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
@ -203,10 +203,10 @@ dependencies {
annotationProcessor "androidx.room:room-compiler:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version"
// CameraX // CameraX
def camerax_version = "1.1.0-alpha04"
def camerax_version = "1.1.0-alpha05"
implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha24"
implementation "androidx.camera:camera-view:1.0.0-alpha25"
// EmojiCompat // EmojiCompat
def emoji_compat_version = "1.1.0" def emoji_compat_version = "1.1.0"
@ -238,7 +238,7 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
githubImplementation 'io.sentry:sentry-android:4.3.0'
githubImplementation 'io.sentry:sentry-android:5.0.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
testImplementation "androidx.test.ext:junit-ktx:1.1.2" testImplementation "androidx.test.ext:junit-ktx:1.1.2"

2
app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java

@ -82,7 +82,7 @@ public class DirectorySelectActivity extends BaseLanguageActivity {
return; return;
} }
if (!"com.android.externalstorage.documents".equals(data.getData().getAuthority())) { if (!"com.android.externalstorage.documents".equals(data.getData().getAuthority())) {
showErrorDialog(getString(R.string.dir_select_no_download_folder));
showErrorDialog(getString(R.string.dir_select_no_download_folder, data.getData().getAuthority()));
return; return;
} }
AppExecutors.INSTANCE.getMainThread().execute(() -> { AppExecutors.INSTANCE.getMainThread().execute(() -> {

4
app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java

@ -171,7 +171,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
return new DirectItemVideoCallEventViewHolder(baseBinding, binding, currentUser, thread, callback); return new DirectItemVideoCallEventViewHolder(baseBinding, binding, currentUser, thread, callback);
} }
case PLACEHOLDER: { case PLACEHOLDER: {
final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false);
final LayoutDmStoryShareBinding binding = LayoutDmStoryShareBinding.inflate(layoutInflater, baseBinding.message, false);
return new DirectItemPlaceholderViewHolder(baseBinding, binding, currentUser, thread, callback); return new DirectItemPlaceholderViewHolder(baseBinding, binding, currentUser, thread, callback);
} }
case ANIMATED_MEDIA: { case ANIMATED_MEDIA: {
@ -401,7 +401,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
void onEmailClick(String email); void onEmailClick(String email);
void onMediaClick(Media media);
void onMediaClick(Media media, int index);
void onStoryClick(DirectItemStoryShare storyShare); void onStoryClick(DirectItemStoryShare storyShare);

20
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java

@ -9,20 +9,20 @@ import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder; import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder;
import awais.instagrabber.databinding.ItemHighlightBinding; import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.repositories.responses.stories.Story;
public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> {
public final class FeedStoriesAdapter extends ListAdapter<Story, FeedStoryViewHolder> {
private final OnFeedStoryClickListener listener; private final OnFeedStoryClickListener listener;
private static final DiffUtil.ItemCallback<FeedStoryModel> diffCallback = new DiffUtil.ItemCallback<FeedStoryModel>() {
private static final DiffUtil.ItemCallback<Story> diffCallback = new DiffUtil.ItemCallback<Story>() {
@Override @Override
public boolean areItemsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
public boolean areItemsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId());
} }
@Override @Override
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead() == newItem.isFullyRead();
public boolean areContentsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId()) && oldItem.getSeen() == newItem.getSeen();
} }
}; };
@ -41,13 +41,13 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt
@Override @Override
public void onBindViewHolder(@NonNull final FeedStoryViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final FeedStoryViewHolder holder, final int position) {
final FeedStoryModel model = getItem(position);
final Story model = getItem(position);
holder.bind(model, position, listener); holder.bind(model, position, listener);
} }
public interface OnFeedStoryClickListener { public interface OnFeedStoryClickListener {
void onFeedStoryClick(FeedStoryModel model, int position);
void onFeedStoryClick(Story model, int position);
void onFeedStoryLongClick(FeedStoryModel model, int position);
void onFeedStoryLongClick(Story model, int position);
} }
} }

30
app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java

@ -15,22 +15,22 @@ import java.util.stream.Collectors;
import awais.instagrabber.adapters.viewholder.StoryListViewHolder; import awais.instagrabber.adapters.viewholder.StoryListViewHolder;
import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.databinding.ItemNotificationBinding;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.repositories.responses.stories.Story;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, StoryListViewHolder> implements Filterable {
public final class FeedStoriesListAdapter extends ListAdapter<Story, StoryListViewHolder> implements Filterable {
private final OnFeedStoryClickListener listener; private final OnFeedStoryClickListener listener;
private List<FeedStoryModel> list;
private List<Story> list;
private final Filter filter = new Filter() { private final Filter filter = new Filter() {
@NonNull @NonNull
@Override @Override
protected FilterResults performFiltering(final CharSequence filter) { protected FilterResults performFiltering(final CharSequence filter) {
final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase(); final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase();
List<FeedStoryModel> filteredList = list;
List<Story> filteredList = list;
if (list != null && query != null) { if (list != null && query != null) {
filteredList = list.stream() filteredList = list.stream()
.filter(feedStoryModel -> feedStoryModel.getProfileModel()
.filter(feedStoryModel -> feedStoryModel.getUser()
.getUsername() .getUsername()
.toLowerCase() .toLowerCase()
.contains(query)) .contains(query))
@ -45,19 +45,19 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
@Override @Override
protected void publishResults(final CharSequence constraint, final FilterResults results) { protected void publishResults(final CharSequence constraint, final FilterResults results) {
//noinspection unchecked //noinspection unchecked
submitList((List<FeedStoryModel>) results.values, true);
submitList((List<Story>) results.values, true);
} }
}; };
private static final DiffUtil.ItemCallback<FeedStoryModel> diffCallback = new DiffUtil.ItemCallback<FeedStoryModel>() {
private static final DiffUtil.ItemCallback<Story> diffCallback = new DiffUtil.ItemCallback<Story>() {
@Override @Override
public boolean areItemsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
public boolean areItemsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId());
} }
@Override @Override
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead() == newItem.isFullyRead();
public boolean areContentsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId()) && oldItem.getSeen() == newItem.getSeen();
} }
}; };
@ -71,7 +71,7 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
return filter; return filter;
} }
private void submitList(@Nullable final List<FeedStoryModel> list, final boolean isFiltered) {
private void submitList(@Nullable final List<Story> list, final boolean isFiltered) {
if (!isFiltered) { if (!isFiltered) {
this.list = list; this.list = list;
} }
@ -79,7 +79,7 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
} }
@Override @Override
public void submitList(final List<FeedStoryModel> list) {
public void submitList(final List<Story> list) {
submitList(list, false); submitList(list, false);
} }
@ -93,12 +93,12 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
@Override @Override
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
final FeedStoryModel model = getItem(position);
final Story model = getItem(position);
holder.bind(model, listener); holder.bind(model, listener);
} }
public interface OnFeedStoryClickListener { public interface OnFeedStoryClickListener {
void onFeedStoryClick(final FeedStoryModel model);
void onFeedStoryClick(final Story model);
void onProfileClick(final String username); void onProfileClick(final String username);
} }

14
app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java

@ -9,19 +9,19 @@ import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.adapters.viewholder.StoryListViewHolder; import awais.instagrabber.adapters.viewholder.StoryListViewHolder;
import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.databinding.ItemNotificationBinding;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.responses.stories.Story;
public final class HighlightStoriesListAdapter extends ListAdapter<HighlightModel, StoryListViewHolder> {
public final class HighlightStoriesListAdapter extends ListAdapter<Story, StoryListViewHolder> {
private final OnHighlightStoryClickListener listener; private final OnHighlightStoryClickListener listener;
private static final DiffUtil.ItemCallback<HighlightModel> diffCallback = new DiffUtil.ItemCallback<HighlightModel>() {
private static final DiffUtil.ItemCallback<Story> diffCallback = new DiffUtil.ItemCallback<Story>() {
@Override @Override
public boolean areItemsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) {
public boolean areItemsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId()); return oldItem.getId().equals(newItem.getId());
} }
@Override @Override
public boolean areContentsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) {
public boolean areContentsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId()); return oldItem.getId().equals(newItem.getId());
} }
}; };
@ -41,12 +41,12 @@ public final class HighlightStoriesListAdapter extends ListAdapter<HighlightMode
@Override @Override
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
final HighlightModel model = getItem(position);
final Story model = getItem(position);
holder.bind(model, position, listener); holder.bind(model, position, listener);
} }
public interface OnHighlightStoryClickListener { public interface OnHighlightStoryClickListener {
void onHighlightClick(final HighlightModel model, final int position);
void onHighlightClick(final Story model, final int position);
void onProfileClick(final String username); void onProfileClick(final String username);
} }

14
app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java

@ -9,20 +9,20 @@ import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.adapters.viewholder.HighlightViewHolder; import awais.instagrabber.adapters.viewholder.HighlightViewHolder;
import awais.instagrabber.databinding.ItemHighlightBinding; import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.responses.stories.Story;
public final class HighlightsAdapter extends ListAdapter<HighlightModel, HighlightViewHolder> {
public final class HighlightsAdapter extends ListAdapter<Story, HighlightViewHolder> {
private final OnHighlightClickListener clickListener; private final OnHighlightClickListener clickListener;
private static final DiffUtil.ItemCallback<HighlightModel> diffCallback = new DiffUtil.ItemCallback<HighlightModel>() {
private static final DiffUtil.ItemCallback<Story> diffCallback = new DiffUtil.ItemCallback<Story>() {
@Override @Override
public boolean areItemsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) {
public boolean areItemsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId()); return oldItem.getId().equals(newItem.getId());
} }
@Override @Override
public boolean areContentsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) {
public boolean areContentsTheSame(@NonNull final Story oldItem, @NonNull final Story newItem) {
return oldItem.getId().equals(newItem.getId()); return oldItem.getId().equals(newItem.getId());
} }
}; };
@ -42,7 +42,7 @@ public final class HighlightsAdapter extends ListAdapter<HighlightModel, Highlig
@Override @Override
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) {
final HighlightModel highlightModel = getItem(position);
final Story highlightModel = getItem(position);
if (clickListener != null) { if (clickListener != null) {
holder.itemView.setOnClickListener(v -> clickListener.onHighlightClick(highlightModel, position)); holder.itemView.setOnClickListener(v -> clickListener.onHighlightClick(highlightModel, position));
} }
@ -50,6 +50,6 @@ public final class HighlightsAdapter extends ListAdapter<HighlightModel, Highlig
} }
public interface OnHighlightClickListener { public interface OnHighlightClickListener {
void onHighlightClick(final HighlightModel model, final int position);
void onHighlightClick(final Story model, final int position);
} }
} }

111
app/src/main/java/awais/instagrabber/adapters/MediaItemsAdapter.java

@ -1,111 +0,0 @@
package awais.instagrabber.adapters;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.io.File;
import awais.instagrabber.databinding.ItemMediaBinding;
import awais.instagrabber.utils.MediaController.MediaEntry;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public class MediaItemsAdapter extends ListAdapter<MediaEntry, MediaItemsAdapter.MediaItemViewHolder> {
private static final DiffUtil.ItemCallback<MediaEntry> diffCallback = new DiffUtil.ItemCallback<MediaEntry>() {
@Override
public boolean areItemsTheSame(@NonNull final MediaEntry oldItem, @NonNull final MediaEntry newItem) {
return oldItem.imageId == newItem.imageId;
}
@Override
public boolean areContentsTheSame(@NonNull final MediaEntry oldItem, @NonNull final MediaEntry newItem) {
return oldItem.imageId == newItem.imageId;
}
};
private final OnItemClickListener onItemClickListener;
public MediaItemsAdapter(final OnItemClickListener onItemClickListener) {
super(diffCallback);
this.onItemClickListener = onItemClickListener;
}
@NonNull
@Override
public MediaItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemMediaBinding binding = ItemMediaBinding.inflate(layoutInflater, parent, false);
return new MediaItemViewHolder(binding, onItemClickListener);
}
@Override
public void onBindViewHolder(@NonNull final MediaItemViewHolder holder, final int position) {
holder.bind(getItem(position));
}
public static class MediaItemViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = MediaItemViewHolder.class.getSimpleName();
private static final int size = Utils.displayMetrics.widthPixels / 3;
private final ItemMediaBinding binding;
private final OnItemClickListener onItemClickListener;
public MediaItemViewHolder(@NonNull final ItemMediaBinding binding,
final OnItemClickListener onItemClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onItemClickListener = onItemClickListener;
}
public void bind(final MediaEntry item) {
final Uri uri = Uri.fromFile(new File(item.path));
if (onItemClickListener != null) {
itemView.setOnClickListener(v -> onItemClickListener.onItemClick(item));
}
final BaseControllerListener<ImageInfo> controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
Log.e(TAG, "onFailure: ", throwable);
}
};
final ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(ResizeOptions.forDimensions(size, size))
.build();
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setControllerListener(controllerListener);
binding.item.setController(builder.build());
if (item.isVideo && item.duration >= 0) {
final String timeString = TextUtils.millisToTimeString(item.duration);
binding.duration.setVisibility(View.VISIBLE);
binding.duration.setText(timeString);
} else {
binding.duration.setVisibility(View.GONE);
}
}
}
public interface OnItemClickListener {
void onItemClick(MediaEntry entry);
}
}

17
app/src/main/java/awais/instagrabber/adapters/viewholder/FeedStoryViewHolder.java

@ -4,8 +4,8 @@ import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.adapters.FeedStoriesAdapter; import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.databinding.ItemHighlightBinding; import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.stories.Story;
public final class FeedStoryViewHolder extends RecyclerView.ViewHolder { public final class FeedStoryViewHolder extends RecyclerView.ViewHolder {
@ -16,7 +16,7 @@ public final class FeedStoryViewHolder extends RecyclerView.ViewHolder {
this.binding = binding; this.binding = binding;
} }
public void bind(final FeedStoryModel model,
public void bind(final Story model,
final int position, final int position,
final FeedStoriesAdapter.OnFeedStoryClickListener listener) { final FeedStoriesAdapter.OnFeedStoryClickListener listener) {
if (model == null) return; if (model == null) return;
@ -28,14 +28,17 @@ public final class FeedStoryViewHolder extends RecyclerView.ViewHolder {
if (listener != null) listener.onFeedStoryLongClick(model, position); if (listener != null) listener.onFeedStoryLongClick(model, position);
return true; return true;
}); });
final User profileModel = model.getProfileModel();
final User profileModel = model.getUser();
binding.title.setText(profileModel.getUsername()); binding.title.setText(profileModel.getUsername());
binding.title.setAlpha(model.isFullyRead() ? 0.5F : 1.0F);
final boolean isFullyRead =
model.getSeen() != null &&
model.getSeen().equals(model.getLatestReelMedia());
binding.title.setAlpha(isFullyRead ? 0.5F : 1.0F);
binding.icon.setImageURI(profileModel.getProfilePicUrl()); binding.icon.setImageURI(profileModel.getProfilePicUrl());
binding.icon.setAlpha(model.isFullyRead() ? 0.5F : 1.0F);
binding.icon.setAlpha(isFullyRead ? 0.5F : 1.0F);
if (model.isLive()) binding.icon.setStoriesBorder(2);
else if (model.isBestie()) binding.icon.setStoriesBorder(1);
if (model.getBroadcast() != null) binding.icon.setStoriesBorder(2);
else if (model.getHasBestiesMedia()) binding.icon.setStoriesBorder(1);
else binding.icon.setStoriesBorder(0); else binding.icon.setStoriesBorder(0);
} }
} }

6
app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java

@ -3,7 +3,7 @@ package awais.instagrabber.adapters.viewholder;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.databinding.ItemHighlightBinding; import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.responses.stories.Story;
public final class HighlightViewHolder extends RecyclerView.ViewHolder { public final class HighlightViewHolder extends RecyclerView.ViewHolder {
@ -14,10 +14,10 @@ public final class HighlightViewHolder extends RecyclerView.ViewHolder {
this.binding = binding; this.binding = binding;
} }
public void bind(final HighlightModel model) {
public void bind(final Story model) {
if (model == null) return; if (model == null) return;
binding.title.setText(model.getTitle()); binding.title.setText(model.getTitle());
binding.icon.setImageURI(model.getThumbnailUrl());
binding.icon.setImageURI(model.getCoverMedia().getCroppedImageVersion().getUrl());
// binding.getRoot().setOnClickListener(v -> { // binding.getRoot().setOnClickListener(v -> {
// if (listener == null) return; // if (listener == null) return;
// listener.onFeedStoryClick(model, position); // listener.onFeedStoryClick(model, position);

23
app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java

@ -8,8 +8,8 @@ import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener; import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener;
import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener; import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener;
import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.databinding.ItemNotificationBinding;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.responses.stories.Story;
import awais.instagrabber.utils.ResponseBodyUtils;
public final class StoryListViewHolder extends RecyclerView.ViewHolder { public final class StoryListViewHolder extends RecyclerView.ViewHolder {
private final ItemNotificationBinding binding; private final ItemNotificationBinding binding;
@ -19,7 +19,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
this.binding = binding; this.binding = binding;
} }
public void bind(final FeedStoryModel model,
public void bind(final Story model,
final OnFeedStoryClickListener notificationClickListener) { final OnFeedStoryClickListener notificationClickListener) {
if (model == null) return; if (model == null) return;
@ -31,19 +31,20 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
binding.tvDate.setText(model.getDateTime()); binding.tvDate.setText(model.getDateTime());
binding.tvUsername.setText(model.getProfileModel().getUsername());
binding.ivProfilePic.setImageURI(model.getProfileModel().getProfilePicUrl());
binding.tvUsername.setText(model.getUser().getUsername());
binding.ivProfilePic.setImageURI(model.getUser().getProfilePicUrl());
binding.ivProfilePic.setOnClickListener(v -> { binding.ivProfilePic.setOnClickListener(v -> {
if (notificationClickListener == null) return; if (notificationClickListener == null) return;
notificationClickListener.onProfileClick(model.getProfileModel().getUsername());
notificationClickListener.onProfileClick(model.getUser().getUsername());
}); });
if (model.getFirstStoryModel() != null) {
if (model.getItems() != null && model.getItems().size() > 0) {
binding.ivPreviewPic.setVisibility(View.VISIBLE); binding.ivPreviewPic.setVisibility(View.VISIBLE);
binding.ivPreviewPic.setImageURI(model.getFirstStoryModel().getThumbnail());
binding.ivPreviewPic.setImageURI(ResponseBodyUtils.getThumbUrl(model.getItems().get(0)));
} else binding.ivPreviewPic.setVisibility(View.INVISIBLE); } else binding.ivPreviewPic.setVisibility(View.INVISIBLE);
float alpha = model.isFullyRead() ? 0.5F : 1.0F;
float alpha = model.getSeen() != null && model.getSeen().equals(model.getLatestReelMedia())
? 0.5F : 1.0F;
binding.ivProfilePic.setAlpha(alpha); binding.ivProfilePic.setAlpha(alpha);
binding.ivPreviewPic.setAlpha(alpha); binding.ivPreviewPic.setAlpha(alpha);
binding.tvUsername.setAlpha(alpha); binding.tvUsername.setAlpha(alpha);
@ -56,7 +57,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
}); });
} }
public void bind(final HighlightModel model,
public void bind(final Story model,
final int position, final int position,
final OnHighlightStoryClickListener notificationClickListener) { final OnHighlightStoryClickListener notificationClickListener) {
if (model == null) return; if (model == null) return;
@ -72,7 +73,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
binding.ivProfilePic.setVisibility(View.GONE); binding.ivProfilePic.setVisibility(View.GONE);
binding.ivPreviewPic.setVisibility(View.VISIBLE); binding.ivPreviewPic.setVisibility(View.VISIBLE);
binding.ivPreviewPic.setImageURI(model.getThumbnailUrl());
binding.ivPreviewPic.setImageURI(model.getCoverImageVersion().getUrl());
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return; if (notificationClickListener == null) return;

36
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java

@ -65,19 +65,29 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
if (media == null) return; if (media == null) return;
itemView.post(() -> { itemView.post(() -> {
setupUser(media); setupUser(media);
setupTitle(media);
setupCaption(media); setupCaption(media);
}); });
final int index;
final Media toDisplay;
final MediaItemType mediaType = media.getMediaType();
switch (mediaType) {
case MEDIA_TYPE_SLIDER:
toDisplay = media.getCarouselMedia().stream()
.filter(m -> media.getCarouselShareChildMediaId() != null &&
media.getCarouselShareChildMediaId().equals(m.getId()))
.findAny()
.orElse(media.getCarouselMedia().get(0));
index = media.getCarouselMedia().indexOf(toDisplay);
break;
default:
toDisplay = media;
index = 0;
}
itemView.post(() -> { itemView.post(() -> {
final MediaItemType mediaType = media.getMediaType();
setupTypeIndicator(mediaType); setupTypeIndicator(mediaType);
if (mediaType == MediaItemType.MEDIA_TYPE_SLIDER) {
setupPreview(media.getCarouselMedia().get(0), messageDirection);
return;
}
setupPreview(media, messageDirection);
setupPreview(toDisplay, messageDirection);
}); });
itemView.setOnClickListener(v -> openMedia(media));
itemView.setOnClickListener(v -> openMedia(media, index));
} }
private void setupTypeIndicator(final MediaItemType mediaType) { private void setupTypeIndicator(final MediaItemType mediaType) {
@ -129,16 +139,6 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
} }
} }
private void setupTitle(@NonNull final Media media) {
final String title = media.getTitle();
if (!TextUtils.isEmpty(title)) {
binding.title.setVisibility(View.VISIBLE);
binding.title.setText(title);
} else {
binding.title.setVisibility(View.GONE);
}
}
private void setupUser(@NonNull final Media media) { private void setupUser(@NonNull final Media media) {
final User user = media.getUser(); final User user = media.getUser();
if (user != null) { if (user != null) {

2
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java

@ -47,7 +47,7 @@ public class DirectItemMediaViewHolder extends DirectItemViewHolder {
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP) .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.build()); .build());
final Media media = directItemModel.getMedia(); final Media media = directItemModel.getMedia();
itemView.setOnClickListener(v -> openMedia(media));
itemView.setOnClickListener(v -> openMedia(media, -1));
final MediaItemType modelMediaType = media.getMediaType(); final MediaItemType modelMediaType = media.getMediaType();
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
? View.VISIBLE ? View.VISIBLE

20
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java

@ -1,21 +1,23 @@
package awais.instagrabber.adapters.viewholder.directmessages; package awais.instagrabber.adapters.viewholder.directmessages;
import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmBaseBinding;
import awais.instagrabber.databinding.LayoutDmTextBinding;
import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
public class DirectItemPlaceholderViewHolder extends DirectItemViewHolder { public class DirectItemPlaceholderViewHolder extends DirectItemViewHolder {
private final LayoutDmTextBinding binding;
private final LayoutDmStoryShareBinding binding;
public DirectItemPlaceholderViewHolder(@NonNull final LayoutDmBaseBinding baseBinding, public DirectItemPlaceholderViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
final LayoutDmTextBinding binding,
final LayoutDmStoryShareBinding binding,
final User currentUser, final User currentUser,
final DirectThread thread, final DirectThread thread,
final DirectItemCallback callback) { final DirectItemCallback callback) {
@ -26,13 +28,11 @@ public class DirectItemPlaceholderViewHolder extends DirectItemViewHolder {
@Override @Override
public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) { public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {
final String text = String.format("%s: %s", directItemModel.getPlaceholder().getTitle(), directItemModel.getPlaceholder().getMessage());
binding.tvMessage.setText(text);
}
@Override
protected boolean showBackground() {
return true;
binding.shareInfo.setText(directItemModel.getPlaceholder().getTitle());
binding.text.setVisibility(View.VISIBLE);
binding.text.setText(directItemModel.getPlaceholder().getMessage());
binding.ivMediaPreview.setVisibility(View.GONE);
binding.typeIcon.setVisibility(View.GONE);
} }
@Override @Override

2
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java

@ -50,7 +50,7 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
setPreview(visualMedia, messageDirection); setPreview(visualMedia, messageDirection);
final boolean expired = TextUtils.isEmpty(media.getId()); final boolean expired = TextUtils.isEmpty(media.getId());
if (expired) return; if (expired) return;
itemView.setOnClickListener(v -> openMedia(media));
itemView.setOnClickListener(v -> openMedia(media, -1));
/*final boolean isExpired = visualMedia == null || (mediaModel = visualMedia.getMedia()) == null || /*final boolean isExpired = visualMedia == null || (mediaModel = visualMedia.getMedia()) == null ||
TextUtils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1; TextUtils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;

2
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java

@ -76,7 +76,7 @@ public class DirectItemReelShareViewHolder extends DirectItemViewHolder {
} }
if (!expired) { if (!expired) {
setPreview(media); setPreview(media);
itemView.setOnClickListener(v -> openMedia(media));
itemView.setOnClickListener(v -> openMedia(media, -1));
} }
} }

4
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java

@ -501,8 +501,8 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
callback.onURLClick(url); callback.onURLClick(url);
} }
protected void openMedia(final Media media) {
callback.onMediaClick(media);
protected void openMedia(final Media media, final int index) {
callback.onMediaClick(media, index);
} }
protected void openStory(final DirectItemStoryShare storyShare) { protected void openStory(final DirectItemStoryShare storyShare) {

2
app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java

@ -39,7 +39,7 @@ public class FeedPostFetchService implements PostFetcher.PostFetchService {
return; return;
} else if (result == null) return; } else if (result == null) return;
nextCursor = result.getNextCursor(); nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage();
hasNextPage = result.getHasNextPage();
final List<Media> mediaResults = result.getFeedModels(); final List<Media> mediaResults = result.getFeedModels();
feedModels.addAll(mediaResults); feedModels.addAll(mediaResults);

2
app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java

@ -35,7 +35,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
public void onSuccess(final PostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextCursor(); nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels()); fetchListener.onResult(result.getFeedModels());
} }

2
app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java

@ -35,7 +35,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
public void onSuccess(final PostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextCursor(); nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels()); fetchListener.onResult(result.getFeedModels());
} }

2
app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java

@ -36,7 +36,7 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
public void onSuccess(final PostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextCursor(); nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels()); fetchListener.onResult(result.getFeedModels());
} }

2
app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java

@ -40,7 +40,7 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
public void onSuccess(final PostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextCursor(); nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels()); fetchListener.onResult(result.getFeedModels());
} }

194
app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java

@ -1,194 +0,0 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.adapters.MediaItemsAdapter;
import awais.instagrabber.databinding.LayoutMediaPickerBinding;
import awais.instagrabber.utils.MediaController;
import awais.instagrabber.utils.PermissionUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.viewmodels.MediaPickerViewModel;
public class MediaPickerBottomDialogFragment extends BottomSheetDialogFragment {
private static final String TAG = MediaPickerBottomDialogFragment.class.getSimpleName();
private static final int ATTACH_MEDIA_REQUEST_CODE = 100;
// private static final int HEIGHT_PIXELS = Utils.displayMetrics.heightPixels;
private LayoutMediaPickerBinding binding;
private MediaPickerViewModel viewModel;
private MediaItemsAdapter mediaItemsAdapter;
private OnSelectListener onSelectListener;
// private int actionBarHeight;
// private int statusBarHeight;
// private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
// @Override
// public void onStateChanged(@NonNull final View bottomSheet, final int newState) {
//
// }
//
// @Override
// public void onSlide(@NonNull final View bottomSheet, final float slideOffset) {
// // Log.d(TAG, "onSlide: " + slideOffset);
// final float sheetHeight = HEIGHT_PIXELS * slideOffset;
// final Context context = getContext();
// if (context == null) return;
// final float remaining = HEIGHT_PIXELS - sheetHeight - statusBarHeight;
// if (remaining <= actionBarHeight) {
// final ViewGroup.LayoutParams layoutParams = binding.toolbar.getLayoutParams();
// layoutParams.height = (int) (actionBarHeight - remaining);
// binding.toolbar.requestLayout();
// }
// }
// };
public static MediaPickerBottomDialogFragment newInstance() {
final Bundle args = new Bundle();
final MediaPickerBottomDialogFragment fragment = new MediaPickerBottomDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = LayoutMediaPickerBinding.inflate(inflater, container, false);
viewModel = new ViewModelProvider(this).get(MediaPickerViewModel.class);
// final Context context = getContext();
// if (context == null) return binding.getRoot();
// actionBarHeight = Utils.getActionBarHeight(context);
// statusBarHeight = Utils.getStatusBarHeight(context);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
// final Dialog dialog = getDialog();
// if (dialog == null) return;
// dialog.setOnShowListener(dialog1 -> {
// final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
// final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
// if (bottomSheetInternal == null) return;
// final BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheetInternal);
// behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
// });
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
if (bottomSheetInternal == null) return;
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomSheetInternal.requestLayout();
// final BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheetInternal);
// behavior.addBottomSheetCallback(bottomSheetCallback);
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
if (requestCode == ATTACH_MEDIA_REQUEST_CODE) {
final Context context = getContext();
if (context == null) return;
final boolean hasAttachMediaPerms = PermissionUtils.hasAttachMediaPerms(context);
if (hasAttachMediaPerms) {
viewModel.loadMedia(context);
}
}
}
private void init() {
setupList();
setupAlbumPicker();
final Context context = getContext();
if (context == null) return;
if (!PermissionUtils.hasAttachMediaPerms(context)) {
PermissionUtils.requestAttachMediaPerms(this, ATTACH_MEDIA_REQUEST_CODE);
return;
}
viewModel.loadMedia(context);
}
private void setupList() {
final Context context = getContext();
if (context == null) return;
binding.mediaList.setLayoutManager(new GridLayoutManager(context, 3));
binding.mediaList.setHasFixedSize(true);
mediaItemsAdapter = new MediaItemsAdapter(entry -> {
if (onSelectListener == null) return;
onSelectListener.onSelect(entry);
});
binding.mediaList.setAdapter(mediaItemsAdapter);
}
private void setupAlbumPicker() {
final Context context = getContext();
if (context == null) return;
final ArrayAdapter<String> albumPickerAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item);
albumPickerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.albumPicker.setAdapter(albumPickerAdapter);
binding.albumPicker.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
final List<MediaController.AlbumEntry> albumEntries = viewModel.getAllAlbums().getValue();
if (albumEntries == null) return;
final MediaController.AlbumEntry albumEntry = albumEntries.get(position);
mediaItemsAdapter.submitList(albumEntry.photos, () -> binding.mediaList.scrollToPosition(0));
}
@Override
public void onNothingSelected(final AdapterView<?> parent) {
mediaItemsAdapter.submitList(Collections.emptyList());
}
});
viewModel.getAllAlbums().observe(getViewLifecycleOwner(), albums -> {
albumPickerAdapter.clear();
albumPickerAdapter.addAll(albums.stream()
.map(albumEntry -> albumEntry.bucketName)
.filter(name -> !TextUtils.isEmpty(name))
.collect(Collectors.toList()));
albumPickerAdapter.notifyDataSetChanged();
if (albums.isEmpty()) return;
mediaItemsAdapter.submitList(albums.get(0).photos);
});
}
public void setOnSelectListener(final OnSelectListener onSelectListener) {
this.onSelectListener = onSelectListener;
}
public interface OnSelectListener {
void onSelect(MediaController.MediaEntry entry);
}
}

10
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java

@ -2,7 +2,6 @@ package awais.instagrabber.fragments;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect; import android.graphics.Rect;
@ -93,8 +92,8 @@ import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.Caption; import awais.instagrabber.repositories.responses.Caption;
import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.MediaCandidate; import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient; import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NullSafePair; import awais.instagrabber.utils.NullSafePair;
@ -152,7 +151,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
if (context != null) { if (context != null) {
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show();
} }
viewModel.shareDm((RankedRecipient) result);
viewModel.shareDm((RankedRecipient) result, sliderPosition);
} else if ((result instanceof Set)) { } else if ((result instanceof Set)) {
try { try {
// Log.d(TAG, "result: " + result); // Log.d(TAG, "result: " + result);
@ -161,7 +160,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show();
} }
//noinspection unchecked //noinspection unchecked
viewModel.shareDm((Set<RankedRecipient>) result);
viewModel.shareDm((Set<RankedRecipient>) result, sliderPosition);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "share: ", e); Log.e(TAG, "share: ", e);
} }
@ -294,7 +293,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
return; return;
} }
final Media media = (Media) feedModelSerializable; final Media media = (Media) feedModelSerializable;
if (media.getMediaType() == MediaItemType.MEDIA_TYPE_SLIDER) {
if (media.getMediaType() == MediaItemType.MEDIA_TYPE_SLIDER && sliderPosition == -1) {
sliderPosition = arguments.getInt(ARG_SLIDER_POSITION, 0); sliderPosition = arguments.getInt(ARG_SLIDER_POSITION, 0);
} }
viewModel.setMedia(media); viewModel.setMedia(media);
@ -1039,6 +1038,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
final String text = "1/" + carouselMedia.size(); final String text = "1/" + carouselMedia.size();
binding.mediaCounter.setText(text); binding.mediaCounter.setText(text);
sliderItemsAdapter.submitList(media.getCarouselMedia()); sliderItemsAdapter.submitList(media.getCarouselMedia());
sliderParent.setCurrentItem(sliderPosition);
} }
private void pauseSliderPlayer() { private void pauseSliderPlayer() {

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

@ -38,9 +38,9 @@ import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryC
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentStoryListViewerBinding; import awais.instagrabber.databinding.FragmentStoryListViewerBinding;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.stories.ArchiveResponse;
import awais.instagrabber.repositories.responses.stories.Story;
import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.CoroutineUtilsKt; import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
@ -48,7 +48,6 @@ 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.StoriesRepository; import awais.instagrabber.webservices.StoriesRepository;
import awais.instagrabber.webservices.StoriesRepository.ArchiveFetchResponse;
import kotlinx.coroutines.Dispatchers; import kotlinx.coroutines.Dispatchers;
public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
@ -69,12 +68,12 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() { private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() {
@Override @Override
public void onFeedStoryClick(final FeedStoryModel model) {
public void onFeedStoryClick(final Story model) {
if (model == null) return; if (model == null) return;
final List<FeedStoryModel> feedStoryModels = feedStoriesViewModel.getList().getValue();
final List<Story> feedStoryModels = feedStoriesViewModel.getList().getValue();
if (feedStoryModels == null) return; if (feedStoryModels == null) return;
final int position = Iterables.indexOf(feedStoryModels, feedStoryModel -> feedStoryModel != null final int position = Iterables.indexOf(feedStoryModels, feedStoryModel -> feedStoryModel != null
&& Objects.equals(feedStoryModel.getStoryMediaId(), model.getStoryMediaId()));
&& Objects.equals(feedStoryModel.getId(), model.getId()));
final NavDirections action = StoryListViewerFragmentDirections final NavDirections action = StoryListViewerFragmentDirections
.actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); .actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position));
NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action);
@ -88,7 +87,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
private final OnHighlightStoryClickListener archiveClickListener = new OnHighlightStoryClickListener() { private final OnHighlightStoryClickListener archiveClickListener = new OnHighlightStoryClickListener() {
@Override @Override
public void onHighlightClick(final HighlightModel model, final int position) {
public void onHighlightClick(final Story model, final int position) {
if (model == null) return; if (model == null) return;
final NavDirections action = StoryListViewerFragmentDirections final NavDirections action = StoryListViewerFragmentDirections
.actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forStoryArchive(position)); .actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forStoryArchive(position));
@ -101,9 +100,9 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
} }
}; };
private final ServiceCallback<ArchiveFetchResponse> cb = new ServiceCallback<ArchiveFetchResponse>() {
private final ServiceCallback<ArchiveResponse> cb = new ServiceCallback<ArchiveResponse>() {
@Override @Override
public void onSuccess(final ArchiveFetchResponse result) {
public void onSuccess(final ArchiveResponse result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) { if (result == null) {
try { try {
@ -111,10 +110,10 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
Toast.makeText(context, R.string.empty_list, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.empty_list, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {} } catch (Exception ignored) {}
} else { } else {
endCursor = result.getNextCursor();
final List<HighlightModel> models = archivesViewModel.getList().getValue();
final List<HighlightModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.addAll(result.getResult());
endCursor = result.getMaxId();
final List<Story> models = archivesViewModel.getList().getValue();
final List<Story> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.addAll(result.getItems());
archivesViewModel.getList().postValue(modelsCopy); archivesViewModel.getList().postValue(modelsCopy);
} }
} }
@ -236,7 +235,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
if (type.equals("feed") && firstRefresh) { if (type.equals("feed") && firstRefresh) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
final List<FeedStoryModel> value = feedStoriesViewModel.getList().getValue();
final List<Story> value = feedStoriesViewModel.getList().getValue();
if (value != null) { if (value != null) {
adapter.submitList(value); adapter.submitList(value);
} }
@ -250,9 +249,9 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
return; return;
} }
//noinspection unchecked //noinspection unchecked
feedStoriesViewModel.getList().postValue((List<FeedStoryModel>) feedStoryModels);
feedStoriesViewModel.getList().postValue((List<Story>) feedStoryModels);
//noinspection unchecked //noinspection unchecked
adapter.submitList((List<FeedStoryModel>) feedStoryModels);
adapter.submitList((List<Story>) feedStoryModels);
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
}), Dispatchers.getIO()) }), Dispatchers.getIO())
); );

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

@ -72,8 +72,6 @@ import awais.instagrabber.databinding.FragmentStoryViewerBinding;
import awais.instagrabber.fragments.main.ProfileFragmentDirections; import awais.instagrabber.fragments.main.ProfileFragmentDirections;
import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.SwipeEvent; import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel; import awais.instagrabber.models.stickers.PollModel;
@ -84,6 +82,8 @@ 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.ThreadIdsOrUserIds; import awais.instagrabber.repositories.requests.directmessages.ThreadIdsOrUserIds;
import awais.instagrabber.repositories.responses.stories.Broadcast;
import awais.instagrabber.repositories.responses.stories.Story;
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;
@ -122,6 +122,7 @@ public class StoryViewerFragment extends Fragment {
private StoriesRepository storiesRepository; private StoriesRepository storiesRepository;
private MediaRepository mediaRepository; private MediaRepository mediaRepository;
private StoryModel currentStory; private StoryModel currentStory;
private Broadcast live;
private int slidePos; private int slidePos;
private int lastSlidePos; private int lastSlidePos;
private String url; private String url;
@ -714,7 +715,7 @@ public class StoryViewerFragment extends Fragment {
private void resetView() { private void resetView() {
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
StoryModel live = null;
live = null;
slidePos = 0; slidePos = 0;
lastSlidePos = 0; lastSlidePos = 0;
if (menuDownload != null) menuDownload.setVisible(false); if (menuDownload != null) menuDownload.setVisible(false);
@ -731,12 +732,12 @@ public class StoryViewerFragment extends Fragment {
switch (type) { switch (type) {
case HIGHLIGHT: { case HIGHLIGHT: {
final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel;
final List<HighlightModel> models = highlightsViewModel.getList().getValue();
final List<Story> models = highlightsViewModel.getList().getValue();
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) { if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return; return;
} }
final HighlightModel model = models.get(currentFeedStoryIndex);
final Story model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId(); currentStoryMediaId = model.getId();
fetchOptions = StoryViewerOptions.forHighlight(model.getId()); fetchOptions = StoryViewerOptions.forHighlight(model.getId());
highlightTitle = model.getTitle(); highlightTitle = model.getTitle();
@ -744,25 +745,23 @@ public class StoryViewerFragment extends Fragment {
} }
case FEED_STORY_POSITION: { case FEED_STORY_POSITION: {
final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel;
final List<FeedStoryModel> models = feedStoriesViewModel.getList().getValue();
final List<Story> models = feedStoriesViewModel.getList().getValue();
if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) return; if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) return;
final FeedStoryModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getStoryMediaId();
currentStoryUsername = model.getProfileModel().getUsername();
final Story model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId();
currentStoryUsername = model.getUser().getUsername();
fetchOptions = StoryViewerOptions.forUser(Long.parseLong(currentStoryMediaId), currentStoryUsername); fetchOptions = StoryViewerOptions.forUser(Long.parseLong(currentStoryMediaId), currentStoryUsername);
if (model.isLive()) {
live = model.getFirstStoryModel();
}
live = model.getBroadcast();
break; break;
} }
case STORY_ARCHIVE: { case STORY_ARCHIVE: {
final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel; final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel;
final List<HighlightModel> models = archivesViewModel.getList().getValue();
final List<Story> models = archivesViewModel.getList().getValue();
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) { if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return; return;
} }
final HighlightModel model = models.get(currentFeedStoryIndex);
final Story model = models.get(currentFeedStoryIndex);
currentStoryMediaId = parseStoryMediaId(model.getId()); currentStoryMediaId = parseStoryMediaId(model.getId());
currentStoryUsername = model.getTitle(); currentStoryUsername = model.getTitle();
fetchOptions = StoryViewerOptions.forStoryArchive(model.getId()); fetchOptions = StoryViewerOptions.forStoryArchive(model.getId());
@ -800,6 +799,11 @@ public class StoryViewerFragment extends Fragment {
return; return;
} }
if (currentStoryMediaId == null) return; if (currentStoryMediaId == null) return;
if (live != null) {
currentStory = null;
refreshLive();
return;
}
final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() { final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
@Override @Override
public void onSuccess(final List<StoryModel> storyModels) { public void onSuccess(final List<StoryModel> storyModels) {
@ -826,10 +830,6 @@ public class StoryViewerFragment extends Fragment {
Log.e(TAG, "Error", t); Log.e(TAG, "Error", t);
} }
}; };
if (live != null) {
storyCallback.onSuccess(Collections.singletonList(live));
return;
}
storiesRepository.getUserStory( storiesRepository.getUserStory(
fetchOptions, fetchOptions,
CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@ -861,6 +861,30 @@ public class StoryViewerFragment extends Fragment {
} }
} }
private synchronized void refreshLive() {
binding.storiesList.setVisibility(View.INVISIBLE);
binding.viewStoryPost.setVisibility(View.GONE);
binding.spotify.setVisibility(View.GONE);
binding.poll.setVisibility(View.GONE);
binding.answer.setVisibility(View.GONE);
binding.mention.setVisibility(View.GONE);
binding.quiz.setVisibility(View.GONE);
binding.slider.setVisibility(View.GONE);
lastSlidePos = slidePos;
releasePlayer();
url = live.getDashPlaybackUrl();
setupLive();
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
actionBarSubtitle = TextUtils.epochSecondToString(live.getPublishedTime());
if (actionBar != null) {
try {
actionBar.setSubtitle(actionBarSubtitle);
} catch (Exception e) {
Log.e(TAG, "refreshLive: ", e);
}
}
}
private synchronized void refreshStory() { private synchronized void refreshStory() {
if (binding.storiesList.getVisibility() == View.VISIBLE) { if (binding.storiesList.getVisibility() == View.VISIBLE) {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
@ -883,42 +907,40 @@ public class StoryViewerFragment extends Fragment {
url = itemType == MediaItemType.MEDIA_TYPE_IMAGE ? currentStory.getStoryUrl() : currentStory.getVideoUrl(); url = itemType == MediaItemType.MEDIA_TYPE_IMAGE ? currentStory.getStoryUrl() : currentStory.getVideoUrl();
if (itemType != MediaItemType.MEDIA_TYPE_LIVE) {
final String shortCode = currentStory.getTappableShortCode();
binding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
binding.viewStoryPost.setTag(shortCode);
final String spotify = currentStory.getSpotify();
binding.spotify.setVisibility(spotify != null ? View.VISIBLE : View.GONE);
binding.spotify.setTag(spotify);
poll = currentStory.getPoll();
binding.poll.setVisibility(poll != null ? View.VISIBLE : View.GONE);
binding.poll.setTag(poll);
question = currentStory.getQuestion();
binding.answer.setVisibility((question != null) ? View.VISIBLE : View.GONE);
binding.answer.setTag(question);
mentions = currentStory.getMentions();
binding.mention.setVisibility((mentions != null && mentions.length > 0) ? View.VISIBLE : View.GONE);
binding.mention.setTag(mentions);
quiz = currentStory.getQuiz();
binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
binding.quiz.setTag(quiz);
slider = currentStory.getSlider();
binding.slider.setVisibility(slider != null ? View.VISIBLE : View.GONE);
binding.slider.setTag(slider);
final SwipeUpModel swipeUp = currentStory.getSwipeUp();
if (swipeUp != null) {
binding.swipeUp.setVisibility(View.VISIBLE);
binding.swipeUp.setText(swipeUp.getText());
binding.swipeUp.setTag(swipeUp.getUrl());
} else binding.swipeUp.setVisibility(View.GONE);
}
final String shortCode = currentStory.getTappableShortCode();
binding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
binding.viewStoryPost.setTag(shortCode);
final String spotify = currentStory.getSpotify();
binding.spotify.setVisibility(spotify != null ? View.VISIBLE : View.GONE);
binding.spotify.setTag(spotify);
poll = currentStory.getPoll();
binding.poll.setVisibility(poll != null ? View.VISIBLE : View.GONE);
binding.poll.setTag(poll);
question = currentStory.getQuestion();
binding.answer.setVisibility((question != null) ? View.VISIBLE : View.GONE);
binding.answer.setTag(question);
mentions = currentStory.getMentions();
binding.mention.setVisibility((mentions != null && mentions.length > 0) ? View.VISIBLE : View.GONE);
binding.mention.setTag(mentions);
quiz = currentStory.getQuiz();
binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
binding.quiz.setTag(quiz);
slider = currentStory.getSlider();
binding.slider.setVisibility(slider != null ? View.VISIBLE : View.GONE);
binding.slider.setTag(slider);
final SwipeUpModel swipeUp = currentStory.getSwipeUp();
if (swipeUp != null) {
binding.swipeUp.setVisibility(View.VISIBLE);
binding.swipeUp.setText(swipeUp.getText());
binding.swipeUp.setTag(swipeUp.getUrl());
} else binding.swipeUp.setVisibility(View.GONE);
releasePlayer(); releasePlayer();
final Type type = options.getType(); final Type type = options.getType();
@ -930,7 +952,6 @@ public class StoryViewerFragment extends Fragment {
} }
} }
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(); if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else if (itemType == MediaItemType.MEDIA_TYPE_LIVE) setupLive();
else setupImage(); else setupImage();
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -1202,14 +1223,14 @@ public class StoryViewerFragment extends Fragment {
return; return;
} }
if (settingsHelper.getBoolean(MARK_AS_SEEN) if (settingsHelper.getBoolean(MARK_AS_SEEN)
&& oldFeedStory instanceof FeedStoryModel
&& oldFeedStory instanceof Story
&& viewModel instanceof FeedStoriesViewModel) { && viewModel instanceof FeedStoriesViewModel) {
final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel;
final FeedStoryModel oldFeedStoryModel = (FeedStoryModel) oldFeedStory;
if (!oldFeedStoryModel.isFullyRead()) {
oldFeedStoryModel.setFullyRead(true);
final List<FeedStoryModel> models = feedStoriesViewModel.getList().getValue();
final List<FeedStoryModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
final Story oldFeedStoryModel = (Story) oldFeedStory;
if (oldFeedStoryModel.getSeen() == null || !oldFeedStoryModel.getSeen().equals(oldFeedStoryModel.getLatestReelMedia())) {
oldFeedStoryModel.setSeen(oldFeedStoryModel.getLatestReelMedia());
final List<Story> models = feedStoriesViewModel.getList().getValue();
final List<Story> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.set(currentFeedStoryIndex, oldFeedStoryModel); modelsCopy.set(currentFeedStoryIndex, oldFeedStoryModel);
feedStoriesViewModel.getList().postValue(models); feedStoriesViewModel.getList().postValue(models);
} }

13
app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java

@ -174,7 +174,7 @@ public class RepliesFragment extends Fragment {
} }
private void setupToolbar() { private void setupToolbar() {
binding.toolbar.setTitle("Replies");
binding.toolbar.setTitle(R.string.title_replies);
binding.toolbar.setNavigationIcon(R.drawable.ic_round_arrow_back_24); binding.toolbar.setNavigationIcon(R.drawable.ic_round_arrow_back_24);
binding.toolbar.setNavigationOnClickListener(v -> { binding.toolbar.setNavigationOnClickListener(v -> {
final FragmentManager fragmentManager = getParentFragmentManager(); final FragmentManager fragmentManager = getParentFragmentManager();
@ -187,7 +187,16 @@ public class RepliesFragment extends Fragment {
if (context == null) return; if (context == null) return;
commentsAdapter = new CommentsAdapter(currentUserId, commentsAdapter = new CommentsAdapter(currentUserId,
true, true,
Helper.getCommentCallback(context, getViewLifecycleOwner(), getNavController(), viewModel, null));
Helper.getCommentCallback(context,
getViewLifecycleOwner(),
getNavController(),
viewModel,
(comment, focusInput) -> {
viewModel.setReplyTo(comment);
binding.commentText.setText(String.format("@%s ", comment.getUser().getUsername()));
if (focusInput) Utils.showKeyboard(binding.commentText);
return null;
}));
binding.comments.setAdapter(commentsAdapter); binding.comments.setAdapter(commentsAdapter);
final Resource<List<Comment>> listResource = viewModel.getReplyList().getValue(); final Resource<List<Comment>> listResource = viewModel.getReplyList().getValue();
commentsAdapter.submitList(listResource != null ? listResource.data : Collections.emptyList()); commentsAdapter.submitList(listResource != null ? listResource.data : Collections.emptyList());

43
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java

@ -8,7 +8,6 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
@ -32,7 +31,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.view.menu.ActionMenuItemView; import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat; import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsAnimationControlListenerCompat; import androidx.core.view.WindowInsetsAnimationControlListenerCompat;
@ -97,7 +95,6 @@ import awais.instagrabber.customviews.helpers.TranslateDeferringInsetsAnimationC
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding; import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment; import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
import awais.instagrabber.dialogs.GifPickerBottomDialogFragment; import awais.instagrabber.dialogs.GifPickerBottomDialogFragment;
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.UserSearchFragment; import awais.instagrabber.fragments.UserSearchFragment;
import awais.instagrabber.fragments.UserSearchFragmentDirections; import awais.instagrabber.fragments.UserSearchFragmentDirections;
@ -130,6 +127,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private static final String TAG = DirectMessageThreadFragment.class.getSimpleName(); private static final String TAG = DirectMessageThreadFragment.class.getSimpleName();
private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000; private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000;
private static final int CAMERA_REQUEST_CODE = 200; private static final int CAMERA_REQUEST_CODE = 200;
private static final int FILE_PICKER_REQUEST_CODE = 500;
private static final String TRANSLATION_Y = "translationY"; private static final String TRANSLATION_Y = "translationY";
private DirectItemsAdapter itemsAdapter; private DirectItemsAdapter itemsAdapter;
@ -221,7 +219,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
@Override @Override
public void onMediaClick(final Media media) {
public void onMediaClick(final Media media, final int index) {
if (media.isReelMedia()) { if (media.isReelMedia()) {
final String pk = media.getPk(); final String pk = media.getPk();
try { try {
@ -240,6 +238,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this); final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this);
final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media); bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media);
bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, index);
try { try {
navController.navigate(R.id.action_global_post_view, bundle); navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) { } catch (Exception e) {
@ -474,6 +473,24 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
@Override @Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FILE_PICKER_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) {
Log.w(TAG, "data is null!");
return;
}
final Context context = getContext();
if (context == null) {
Log.w(TAG, "conetxt is null!");
return;
}
final Uri uri = data.getData();
final String mimeType = Utils.getMimeType(uri, context.getContentResolver());
if (mimeType.startsWith("image")) {
navigateToImageEditFragment(uri);
return;
}
handleSentMessage(viewModel.sendUri(uri));
}
if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) { if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) { if (data == null || data.getData() == null) {
Log.w(TAG, "data is null!"); Log.w(TAG, "data is null!");
@ -1109,17 +1126,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
setupInsetsCallback(); setupInsetsCallback();
setupEmojiPicker(); setupEmojiPicker();
binding.gallery.setOnClickListener(v -> { binding.gallery.setOnClickListener(v -> {
final MediaPickerBottomDialogFragment mediaPicker = MediaPickerBottomDialogFragment.newInstance();
mediaPicker.setOnSelectListener(entry -> {
mediaPicker.dismiss();
if (!isAdded()) return;
if (!entry.isVideo) {
navigateToImageEditFragment(entry.path);
return;
}
handleSentMessage(viewModel.sendUri(entry));
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{
"image/*",
"video/mp4"
}); });
mediaPicker.show(getChildFragmentManager(), "MediaPicker");
startActivityForResult(intent, FILE_PICKER_REQUEST_CODE);
}); });
binding.gif.setOnClickListener(v -> { binding.gif.setOnClickListener(v -> {
final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance(); final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance();

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

@ -1,7 +1,6 @@
package awais.instagrabber.fragments.main; package awais.instagrabber.fragments.main;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
@ -12,7 +11,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher; import androidx.activity.OnBackPressedDispatcher;
@ -43,10 +41,10 @@ import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.databinding.FragmentFeedBinding; import awais.instagrabber.databinding.FragmentFeedBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.PostViewV2Fragment;
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.repositories.responses.stories.Story;
import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CoroutineUtilsKt; import awais.instagrabber.utils.CoroutineUtilsKt;
@ -76,7 +74,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
private final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter( private final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter(
new FeedStoriesAdapter.OnFeedStoryClickListener() { new FeedStoriesAdapter.OnFeedStoryClickListener() {
@Override @Override
public void onFeedStoryClick(FeedStoryModel model, int position) {
public void onFeedStoryClick(Story model, int position) {
final NavController navController = NavHostFragment.findNavController(FeedFragment.this); final NavController navController = NavHostFragment.findNavController(FeedFragment.this);
if (isSafeToNavigate(navController)) { if (isSafeToNavigate(navController)) {
final NavDirections action = FeedFragmentDirections final NavDirections action = FeedFragmentDirections
@ -86,8 +84,8 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
@Override @Override
public void onFeedStoryLongClick(FeedStoryModel model, int position) {
navigateToProfile("@" + model.getProfileModel().getUsername());
public void onFeedStoryLongClick(Story model, int position) {
navigateToProfile("@" + model.getUser().getUsername());
} }
} }
); );
@ -399,9 +397,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
storiesFetching = false; storiesFetching = false;
//noinspection unchecked //noinspection unchecked
feedStoriesViewModel.getList().postValue((List<FeedStoryModel>) feedStoryModels);
feedStoriesViewModel.getList().postValue((List<Story>) feedStoryModels);
//noinspection unchecked //noinspection unchecked
feedStoriesAdapter.submitList((List<FeedStoryModel>) feedStoryModels);
feedStoriesAdapter.submitList((List<Story>) feedStoryModels);
if (storyListMenu != null) storyListMenu.setVisible(true); if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState(); updateSwipeRefreshState();
}), Dispatchers.getIO()) }), Dispatchers.getIO())

3
app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java

@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.dialogs.ConfirmDialogFragment; import awais.instagrabber.dialogs.ConfirmDialogFragment;
import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -111,7 +110,7 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
R.string.error, R.string.error,
"com.android.externalstorage.documents".equals(data.getData().getAuthority()) "com.android.externalstorage.documents".equals(data.getData().getAuthority())
? "Please report this error to the developers:\n\n" + sw.toString() ? "Please report this error to the developers:\n\n" + sw.toString()
: getString(R.string.dir_select_no_download_folder),
: getString(R.string.dir_select_no_download_folder, data.getData().getAuthority()),
R.string.ok, R.string.ok,
0, 0,
0 0

11
app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt

@ -70,20 +70,21 @@ object DirectMessagesManager {
suspend fun createThread(userPk: Long): DirectThread = suspend fun createThread(userPk: Long): DirectThread =
directMessagesRepository.createThread(csrfToken, viewerId, deviceUuid, listOf(userPk), null) directMessagesRepository.createThread(csrfToken, viewerId, deviceUuid, listOf(userPk), null)
fun sendMedia(recipient: RankedRecipient, mediaId: String, itemType: BroadcastItemType, scope: CoroutineScope) {
sendMedia(setOf(recipient), mediaId, itemType, scope)
fun sendMedia(recipient: RankedRecipient, mediaId: String, secondId: String?, itemType: BroadcastItemType, scope: CoroutineScope) {
sendMedia(setOf(recipient), mediaId, secondId, itemType, scope)
} }
fun sendMedia( fun sendMedia(
recipients: Set<RankedRecipient>, recipients: Set<RankedRecipient>,
mediaId: String, mediaId: String,
secondId: String?,
itemType: BroadcastItemType, itemType: BroadcastItemType,
scope: CoroutineScope, scope: CoroutineScope,
) { ) {
val threadIds = recipients.mapNotNull { it.thread?.threadId } val threadIds = recipients.mapNotNull { it.thread?.threadId }
val userIdsTemp = recipients.mapNotNull { it.user?.pk } val userIdsTemp = recipients.mapNotNull { it.user?.pk }
val userIds = userIdsTemp.map { listOf(it.toString(10)) } val userIds = userIdsTemp.map { listOf(it.toString(10)) }
sendMedia(threadIds, userIds, mediaId, itemType, scope) {
sendMedia(threadIds, userIds, mediaId, secondId, itemType, scope) {
inboxManager.refresh(scope) inboxManager.refresh(scope)
} }
} }
@ -92,6 +93,7 @@ object DirectMessagesManager {
threadIds: List<String>, threadIds: List<String>,
userIds: List<List<String>>, userIds: List<List<String>>,
mediaId: String, mediaId: String,
secondId: String?,
itemType: BroadcastItemType, itemType: BroadcastItemType,
scope: CoroutineScope, scope: CoroutineScope,
callback: (() -> Unit)?, callback: (() -> Unit)?,
@ -107,7 +109,8 @@ object DirectMessagesManager {
deviceUuid, deviceUuid,
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
ThreadIdsOrUserIds(threadIds, userIds), ThreadIdsOrUserIds(threadIds, userIds),
mediaId
mediaId,
secondId
) )
if (itemType == BroadcastItemType.PROFILE) if (itemType == BroadcastItemType.PROFILE)
directMessagesRepository.broadcastProfile( directMessagesRepository.broadcastProfile(

12
app/src/main/java/awais/instagrabber/managers/ThreadManager.kt

@ -39,7 +39,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.Call import retrofit2.Call
import java.io.File
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.util.* import java.util.*
@ -368,17 +367,6 @@ class ThreadManager(
return data return data
} }
fun sendUri(entry: MediaController.MediaEntry, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>()
val uri = Uri.fromFile(File(entry.path))
if (!entry.isVideo) {
sendPhoto(data, uri, entry.width, entry.height, scope)
return data
}
sendVideo(data, uri, entry.size, entry.duration, entry.width, entry.height, scope)
return data
}
fun sendUri(uri: Uri, scope: CoroutineScope): LiveData<Resource<Any?>> { fun sendUri(uri: Uri, scope: CoroutineScope): LiveData<Resource<Any?>> {
val data = MutableLiveData<Resource<Any?>>() val data = MutableLiveData<Resource<Any?>>()
val mimeType = Utils.getMimeType(uri, contentResolver) val mimeType = Utils.getMimeType(uri, contentResolver)

20
app/src/main/java/awais/instagrabber/models/FeedStoryModel.kt

@ -1,20 +0,0 @@
package awais.instagrabber.models
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.TextUtils
import java.io.Serializable
import java.util.*
data class FeedStoryModel(
val storyMediaId: String,
val profileModel: User,
var isFullyRead: Boolean,
val timestamp: Long,
val firstStoryModel: StoryModel?,
val mediaCount: Int,
val isLive: Boolean,
val isBestie: Boolean
) : Serializable {
val dateTime: String
get() = TextUtils.epochSecondToString(timestamp)
}

14
app/src/main/java/awais/instagrabber/models/HighlightModel.kt

@ -1,14 +0,0 @@
package awais.instagrabber.models
import awais.instagrabber.utils.TextUtils
data class HighlightModel(
val title: String? = null,
val id: String = "",
val thumbnailUrl: String = "",
val timestamp: Long = 0,
val mediaCount: Int = 0,
) {
val dateTime: String
get() = TextUtils.epochSecondToString(timestamp)
}

10
app/src/main/java/awais/instagrabber/repositories/StoriesService.kt

@ -1,6 +1,8 @@
package awais.instagrabber.repositories package awais.instagrabber.repositories
import awais.instagrabber.repositories.responses.StoryStickerResponse
import awais.instagrabber.repositories.responses.stories.ArchiveResponse
import awais.instagrabber.repositories.responses.stories.ReelsTrayResponse
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
import retrofit2.http.* import retrofit2.http.*
interface StoriesService { interface StoriesService {
@ -9,13 +11,13 @@ interface StoriesService {
suspend fun fetch(@Path("mediaId") mediaId: Long): String suspend fun fetch(@Path("mediaId") mediaId: Long): String
@GET("/api/v1/feed/reels_tray/") @GET("/api/v1/feed/reels_tray/")
suspend fun getFeedStories(): String
suspend fun getFeedStories(): ReelsTrayResponse?
@GET("/api/v1/highlights/{uid}/highlights_tray/") @GET("/api/v1/highlights/{uid}/highlights_tray/")
suspend fun fetchHighlights(@Path("uid") uid: Long): String
suspend fun fetchHighlights(@Path("uid") uid: Long): ReelsTrayResponse?
@GET("/api/v1/archive/reel/day_shells/") @GET("/api/v1/archive/reel/day_shells/")
suspend fun fetchArchive(@QueryMap queryParams: Map<String, String>): String
suspend fun fetchArchive(@QueryMap queryParams: Map<String, String>): ArchiveResponse?
@GET @GET
suspend fun getUserStory(@Url url: String): String suspend fun getUserStory(@Url url: String): String

8
app/src/main/java/awais/instagrabber/repositories/requests/directmessages/MediaShareBroadcastOptions.kt

@ -5,12 +5,16 @@ import awais.instagrabber.models.enums.BroadcastItemType
class MediaShareBroadcastOptions( class MediaShareBroadcastOptions(
clientContext: String, clientContext: String,
threadIdsOrUserIds: ThreadIdsOrUserIds, threadIdsOrUserIds: ThreadIdsOrUserIds,
val mediaId: String
val mediaId: String,
val childId: String?
) : BroadcastOptions( ) : BroadcastOptions(
clientContext, clientContext,
threadIdsOrUserIds, threadIdsOrUserIds,
BroadcastItemType.MEDIA_SHARE BroadcastItemType.MEDIA_SHARE
) { ) {
override val formMap: Map<String, String> override val formMap: Map<String, String>
get() = mapOf("media_id" to mediaId)
get() = listOfNotNull(
"media_id" to mediaId,
if (childId != null) "carousel_share_child_media_id" to childId else null
).toMap()
} }

16
app/src/main/java/awais/instagrabber/repositories/responses/HdProfilePicUrlInfo.java

@ -1,16 +0,0 @@
package awais.instagrabber.repositories.responses;
public class HdProfilePicUrlInfo {
private final String url;
private final int width, height;
public HdProfilePicUrlInfo(final String url, final int width, final int height) {
this.url = url;
this.width = width;
this.height = height;
}
public String getUrl() {
return url;
}
}

5
app/src/main/java/awais/instagrabber/repositories/responses/ImageUrl.kt

@ -0,0 +1,5 @@
package awais.instagrabber.repositories.responses
import java.io.Serializable
data class ImageUrl(val url: String, private val width: Int, private val height: Int) : Serializable

30
app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.java

@ -1,30 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class ImageVersions2 implements Serializable {
private final List<MediaCandidate> candidates;
public ImageVersions2(final List<MediaCandidate> candidates) {
this.candidates = candidates;
}
public List<MediaCandidate> getCandidates() {
return candidates;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ImageVersions2 that = (ImageVersions2) o;
return Objects.equals(candidates, that.candidates);
}
@Override
public int hashCode() {
return Objects.hash(candidates);
}
}

5
app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.kt

@ -0,0 +1,5 @@
package awais.instagrabber.repositories.responses
import java.io.Serializable
data class ImageVersions2(val candidates: List<MediaCandidate>) : Serializable

27
app/src/main/java/awais/instagrabber/repositories/responses/LikersResponse.java

@ -1,27 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class LikersResponse {
private final List<User> users;
private final long userCount;
private final String status;
public LikersResponse(final List<User> users, final long userCount, final String status) {
this.users = users;
this.userCount = userCount;
this.status = status;
}
public List<User> getUsers() {
return users;
}
public long getUserCount() {
return userCount;
}
public String getStatus() {
return status;
}
}

3
app/src/main/java/awais/instagrabber/repositories/responses/LikersResponse.kt

@ -0,0 +1,3 @@
package awais.instagrabber.repositories.responses
data class LikersResponse(val users: List<User>, val userCount: Long, val status: String)

4
app/src/main/java/awais/instagrabber/repositories/responses/Media.kt

@ -4,7 +4,6 @@ import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.repositories.responses.feed.EndOfFeedDemarcator import awais.instagrabber.repositories.responses.feed.EndOfFeedDemarcator
import awais.instagrabber.utils.TextUtils import awais.instagrabber.utils.TextUtils
import java.io.Serializable import java.io.Serializable
import java.util.*
data class Media( data class Media(
val pk: String? = null, val pk: String? = null,
@ -38,7 +37,8 @@ data class Media(
var isSidecarChild: Boolean = false, var isSidecarChild: Boolean = false,
var hasViewerSaved: Boolean = false, var hasViewerSaved: Boolean = false,
private val injected: Map<String, Any>? = null, private val injected: Map<String, Any>? = null,
val endOfFeedDemarcator: EndOfFeedDemarcator? = null
val endOfFeedDemarcator: EndOfFeedDemarcator? = null,
val carouselShareChildMediaId: String? = null // which specific child should dm show first
) : Serializable { ) : Serializable {
private var dateString: String? = null private var dateString: String? = null

43
app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.java

@ -1,43 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.Objects;
public class MediaCandidate implements Serializable {
private final int width;
private final int height;
private final String url;
public MediaCandidate(final int width, final int height, final String url) {
this.width = width;
this.height = height;
this.url = url;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getUrl() {
return url;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MediaCandidate that = (MediaCandidate) o;
return width == that.width &&
height == that.height &&
Objects.equals(url, that.url);
}
@Override
public int hashCode() {
return Objects.hash(width, height, url);
}
}

5
app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.kt

@ -0,0 +1,5 @@
package awais.instagrabber.repositories.responses
import java.io.Serializable
data class MediaCandidate(val width: Int, val height: Int, val url: String) : Serializable

15
app/src/main/java/awais/instagrabber/repositories/responses/MediaInfoResponse.java

@ -1,15 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class MediaInfoResponse {
private final List<Media> items;
public MediaInfoResponse(final List<Media> items) {
this.items = items;
}
public List<Media> getItems() {
return items;
}
}

3
app/src/main/java/awais/instagrabber/repositories/responses/MediaInfoResponse.kt

@ -0,0 +1,3 @@
package awais.instagrabber.repositories.responses
data class MediaInfoResponse(val items: List<Media>)

32
app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.java

@ -1,32 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
import awais.instagrabber.repositories.responses.notification.Notification;
import awais.instagrabber.repositories.responses.notification.NotificationCounts;
public class NewsInboxResponse {
private final NotificationCounts counts;
private final List<Notification> newStories;
private final List<Notification> oldStories;
public NewsInboxResponse(final NotificationCounts counts,
final List<Notification> newStories,
final List<Notification> oldStories) {
this.counts = counts;
this.newStories = newStories;
this.oldStories = oldStories;
}
public NotificationCounts getCounts() {
return counts;
}
public List<Notification> getNewStories() {
return newStories;
}
public List<Notification> getOldStories() {
return oldStories;
}
}

10
app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.kt

@ -0,0 +1,10 @@
package awais.instagrabber.repositories.responses
import awais.instagrabber.repositories.responses.notification.Notification
import awais.instagrabber.repositories.responses.notification.NotificationCounts
data class NewsInboxResponse(
val counts: NotificationCounts,
val newStories: List<Notification>,
val oldStories: List<Notification>
)

62
app/src/main/java/awais/instagrabber/repositories/responses/Place.java

@ -1,62 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.Objects;
public class Place {
private final Location location;
// for search
private final String title; // those are repeated within location
private final String subtitle; // address
private final String slug; // browser only; for end of address
// for location info
private final String status;
public Place(final Location location,
final String title,
final String subtitle,
final String slug,
final String status) {
this.location = location;
this.title = title;
this.subtitle = subtitle;
this.slug = slug;
this.status = status;
}
public Location getLocation() {
return location;
}
public String getTitle() {
return title;
}
public String getSubtitle() {
return subtitle;
}
public String getSlug() {
return slug;
}
public String getStatus() {
return status;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Place place = (Place) o;
return Objects.equals(location, place.location) &&
Objects.equals(title, place.title) &&
Objects.equals(subtitle, place.subtitle) &&
Objects.equals(slug, place.slug) &&
Objects.equals(status, place.status);
}
@Override
public int hashCode() {
return Objects.hash(location, title, subtitle, slug, status);
}
}

12
app/src/main/java/awais/instagrabber/repositories/responses/Place.kt

@ -0,0 +1,12 @@
package awais.instagrabber.repositories.responses
data class Place(
val location: Location,
// for search
val title: String, // those are repeated within location
val subtitle: String?, // address
// browser only; for end of address
val slug: String?,
// for location info
val status: String?
)

27
app/src/main/java/awais/instagrabber/repositories/responses/PostsFetchResponse.java

@ -1,27 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class PostsFetchResponse {
private final List<Media> feedModels;
private final boolean hasNextPage;
private final String nextCursor;
public PostsFetchResponse(final List<Media> feedModels, final boolean hasNextPage, final String nextCursor) {
this.feedModels = feedModels;
this.hasNextPage = hasNextPage;
this.nextCursor = nextCursor;
}
public List<Media> getFeedModels() {
return feedModels;
}
public boolean hasNextPage() {
return hasNextPage;
}
public String getNextCursor() {
return nextCursor;
}
}

7
app/src/main/java/awais/instagrabber/repositories/responses/PostsFetchResponse.kt

@ -0,0 +1,7 @@
package awais.instagrabber.repositories.responses
class PostsFetchResponse(
val feedModels: List<Media>,
val hasNextPage: Boolean,
val nextCursor: String?
)

20
app/src/main/java/awais/instagrabber/repositories/responses/StoryStickerResponse.java

@ -1,20 +0,0 @@
package awais.instagrabber.repositories.responses;
public class StoryStickerResponse {
private final String status;
public StoryStickerResponse(final String status) {
this.status = status;
}
public String getStatus() {
return status;
}
@Override
public String toString() {
return "StoryStickerResponse{" +
"status='" + status + '\'' +
'}';
}
}

43
app/src/main/java/awais/instagrabber/repositories/responses/TagFeedResponse.java

@ -1,43 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class TagFeedResponse {
private final int numResults;
private final String nextMaxId;
private final boolean moreAvailable;
private final String status;
private final List<Media> items;
public TagFeedResponse(final int numResults,
final String nextMaxId,
final boolean moreAvailable,
final String status,
final List<Media> items) {
this.numResults = numResults;
this.nextMaxId = nextMaxId;
this.moreAvailable = moreAvailable;
this.status = status;
this.items = items;
}
public int getNumResults() {
return numResults;
}
public String getNextMaxId() {
return nextMaxId;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public String getStatus() {
return status;
}
public List<Media> getItems() {
return items;
}
}

9
app/src/main/java/awais/instagrabber/repositories/responses/TagFeedResponse.kt

@ -0,0 +1,9 @@
package awais.instagrabber.repositories.responses
class TagFeedResponse(
val numResults: Int,
val nextMaxId: String?,
val moreAvailable: Boolean,
val status: String,
val items: List<Media>
)

2
app/src/main/java/awais/instagrabber/repositories/responses/User.kt

@ -27,7 +27,7 @@ data class User @JvmOverloads constructor(
val externalUrl: String? = null, val externalUrl: String? = null,
val usertagsCount: Long = 0, val usertagsCount: Long = 0,
val publicEmail: String? = null, val publicEmail: String? = null,
val hdProfilePicUrlInfo: HdProfilePicUrlInfo? = null,
val hdProfilePicUrlInfo: ImageUrl? = null,
val profileContext: String? = null, // "also followed by" your friends val profileContext: String? = null, // "also followed by" your friends
val profileContextLinksWithUserIds: List<UserProfileContextLink>? = null, // ^ val profileContextLinksWithUserIds: List<UserProfileContextLink>? = null, // ^
val socialContext: String? = null, // AYML val socialContext: String? = null, // AYML

43
app/src/main/java/awais/instagrabber/repositories/responses/UserFeedResponse.java

@ -1,43 +0,0 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class UserFeedResponse {
private final int numResults;
private final String nextMaxId;
private final boolean moreAvailable;
private final String status;
private final List<Media> items;
public UserFeedResponse(final int numResults,
final String nextMaxId,
final boolean moreAvailable,
final String status,
final List<Media> items) {
this.numResults = numResults;
this.nextMaxId = nextMaxId;
this.moreAvailable = moreAvailable;
this.status = status;
this.items = items;
}
public int getNumResults() {
return numResults;
}
public String getNextMaxId() {
return nextMaxId;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public String getStatus() {
return status;
}
public List<Media> getItems() {
return items;
}
}

9
app/src/main/java/awais/instagrabber/repositories/responses/UserFeedResponse.kt

@ -0,0 +1,9 @@
package awais.instagrabber.repositories.responses
class UserFeedResponse(
val numResults: Int,
val nextMaxId: String?,
val moreAvailable: Boolean,
val status: String,
val items: List<Media>
)

13
app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java

@ -1,13 +0,0 @@
package awais.instagrabber.repositories.responses;
public class WrappedMedia {
private final Media media;
public WrappedMedia(final Media media) {
this.media = media;
}
public Media getMedia() {
return media;
}
}

3
app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.kt

@ -0,0 +1,3 @@
package awais.instagrabber.repositories.responses
class WrappedMedia(val media: Media)

9
app/src/main/java/awais/instagrabber/repositories/responses/stories/ArchiveResponse.kt

@ -0,0 +1,9 @@
package awais.instagrabber.repositories.responses.stories
data class ArchiveResponse(
val numResults: Int,
val maxId: String?,
val moreAvailable: Boolean,
val status: String,
val items: List<Story>
)

4
app/src/main/java/awais/instagrabber/repositories/responses/stories/Broadcast.kt

@ -1,10 +1,10 @@
package awais.instagrabber.repositories.responses.stories package awais.instagrabber.repositories.responses.stories
import java.io.Serializable
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import java.io.Serializable
data class Broadcast( data class Broadcast(
val id: Long?,
val id: String?,
val dashPlaybackUrl: String?, val dashPlaybackUrl: String?,
val dashAbrPlaybackUrl: String?, // adaptive quality val dashAbrPlaybackUrl: String?, // adaptive quality
val viewerCount: Double?, // always .0 val viewerCount: Double?, // always .0

5
app/src/main/java/awais/instagrabber/repositories/responses/stories/CoverMedia.kt

@ -0,0 +1,5 @@
package awais.instagrabber.repositories.responses.stories
import awais.instagrabber.repositories.responses.ImageUrl
data class CoverMedia(val croppedImageVersion: ImageUrl)

37
app/src/main/java/awais/instagrabber/repositories/responses/stories/Story.kt

@ -1,16 +1,31 @@
package awais.instagrabber.repositories.responses.stories package awais.instagrabber.repositories.responses.stories
import java.io.Serializable
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.repositories.responses.ImageUrl
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.TextUtils
import java.io.Serializable
data class Story( data class Story(
val id: Long?,
val latestReelMedia: Long?, // = timestamp
val seen: Long?,
val user: User?,
val muted: Boolean?,
val hasBestiesMedia: Boolean?,
val mediaCount: Int?,
val items: List<Media>? // may be null
) : Serializable
// universal
val id: String? = null,
val latestReelMedia: Long? = null, // = timestamp
val mediaCount: Int? = null,
// for stories and highlights
var seen: Long? = null,
val user: User? = null,
// for stories
val muted: Boolean? = null,
val hasBestiesMedia: Boolean? = null,
val items: List<StoryMedia>? = null, // may be null
// for highlights
val coverMedia: CoverMedia? = null,
val title: String? = null,
// for archives
val coverImageVersion: ImageUrl? = null,
// invented fields
val broadcast: Broadcast? = null, // does not naturally occur
) : Serializable {
val dateTime: String
get() = if (latestReelMedia != null) TextUtils.epochSecondToString(latestReelMedia) else ""
// note that archives have property "timestamp" but is ignored
}

5
app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt

@ -1,12 +1,11 @@
package awais.instagrabber.repositories.responses.stories package awais.instagrabber.repositories.responses.stories
import awais.instagrabber.models.enums.MediaItemType import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.utils.TextUtils
import awais.instagrabber.repositories.responses.ImageVersions2 import awais.instagrabber.repositories.responses.ImageVersions2
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.MediaCandidate import awais.instagrabber.repositories.responses.MediaCandidate
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.TextUtils
import java.io.Serializable import java.io.Serializable
import java.util.*
data class StoryMedia( data class StoryMedia(
// inherited from Media // inherited from Media

3
app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryStickerResponse.kt

@ -0,0 +1,3 @@
package awais.instagrabber.repositories.responses.stories
data class StoryStickerResponse(val status: String?)

13
app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt

@ -1,6 +1,5 @@
package awais.instagrabber.utils package awais.instagrabber.utils
import android.Manifest
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.UriPermission import android.content.UriPermission
@ -38,8 +37,6 @@ object DownloadUtils {
private const val DIR_TEMP = "Temp" private const val DIR_TEMP = "Temp"
private const val DIR_BACKUPS = "Backups" private const val DIR_BACKUPS = "Backups"
private var root: DocumentFile? = null private var root: DocumentFile? = null
const val WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE
val PERMS = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
@JvmStatic @JvmStatic
@Throws(ReselectDocumentTreeException::class) @Throws(ReselectDocumentTreeException::class)
fun init( fun init(
@ -49,11 +46,11 @@ object DownloadUtils {
if (isEmpty(barinstaDirUri)) { if (isEmpty(barinstaDirUri)) {
throw ReselectDocumentTreeException("folder path is null or empty") throw ReselectDocumentTreeException("folder path is null or empty")
} }
val uri = Uri.parse(barinstaDirUri)
if (!barinstaDirUri!!.startsWith("content://com.android.externalstorage.documents")) { if (!barinstaDirUri!!.startsWith("content://com.android.externalstorage.documents")) {
// reselect the folder in selector view // reselect the folder in selector view
throw ReselectDocumentTreeException(Uri.parse(barinstaDirUri))
throw ReselectDocumentTreeException(uri)
} }
val uri = Uri.parse(barinstaDirUri)
val existingPermissions = context.contentResolver.persistedUriPermissions val existingPermissions = context.contentResolver.persistedUriPermissions
if (existingPermissions.isEmpty()) { if (existingPermissions.isEmpty()) {
// reselect the folder in selector view // reselect the folder in selector view
@ -150,7 +147,7 @@ object DownloadUtils {
list.add(DIR_DOWNLOADS) list.add(DIR_DOWNLOADS)
return list return list
} }
val finalUsername = if (username!!.startsWith("@")) username.substring(1) else username
val finalUsername = if (username.startsWith("@")) username.substring(1) else username
list.add(DIR_DOWNLOADS) list.add(DIR_DOWNLOADS)
list.add(finalUsername) list.add(finalUsername)
return list return list
@ -337,7 +334,7 @@ object DownloadUtils {
private fun checkPathExists(paths: List<String>, context: Context): Boolean { private fun checkPathExists(paths: List<String>, context: Context): Boolean {
if (root == null) return false if (root == null) return false
val uri = root!!.getUri()
val uri = root!!.uri
var found = false var found = false
var docId = DocumentsContract.getTreeDocumentId(uri) var docId = DocumentsContract.getTreeDocumentId(uri)
for (path in paths) { for (path in paths) {
@ -349,7 +346,7 @@ object DownloadUtils {
), null, null, null ), null, null, null
) )
if (docCursor == null) return false if (docCursor == null) return false
while (docCursor!!.moveToNext() && !found) {
while (docCursor.moveToNext() && !found) {
if (path.equals(docCursor.getString(0))) { if (path.equals(docCursor.getString(0))) {
docId = docCursor.getString(1) docId = docCursor.getString(1)
found = true found = true

386
app/src/main/java/awais/instagrabber/utils/MediaController.java

@ -1,386 +0,0 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Collections;
import awais.instagrabber.R;
/*
* This is the source code of Telegram for Android v. 1.3.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2018.
*/
public class MediaController {
private static final String TAG = MediaController.class.getSimpleName();
private static final String[] PROJECTION_PHOTOS = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
MediaStore.Images.Media.DATA,
Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.DATE_TAKEN : MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.ORIENTATION,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.SIZE
};
private static final String[] PROJECTION_VIDEO = {
MediaStore.Video.Media._ID,
MediaStore.Video.Media.BUCKET_ID,
MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
MediaStore.Video.Media.DATA,
Build.VERSION.SDK_INT > 28 ? MediaStore.Video.Media.DATE_TAKEN : MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.WIDTH,
MediaStore.Video.Media.HEIGHT,
MediaStore.Video.Media.SIZE
};
private final Context context;
private final OnLoadListener onLoadListener;
private final AppExecutors appExecutors;
private static Runnable broadcastPhotosRunnable;
private ArrayList<AlbumEntry> allMediaAlbums;
private ArrayList<AlbumEntry> allPhotoAlbums;
private AlbumEntry allPhotosAlbumEntry;
private AlbumEntry allMediaAlbumEntry;
private AlbumEntry allVideosAlbumEntry;
public MediaController(final Context context, final OnLoadListener onLoadListener) {
this.context = context;
this.onLoadListener = onLoadListener;
appExecutors = AppExecutors.INSTANCE;
}
public void load() {
loadGalleryAlbums();
}
private void loadGalleryAlbums() {
final Thread thread = new Thread(() -> {
final ArrayList<AlbumEntry> mediaAlbumsSorted = new ArrayList<>();
final ArrayList<AlbumEntry> photoAlbumsSorted = new ArrayList<>();
SparseArray<AlbumEntry> mediaAlbums = new SparseArray<>();
SparseArray<AlbumEntry> photoAlbums = new SparseArray<>();
AlbumEntry allPhotosAlbum = null;
AlbumEntry allVideosAlbum = null;
AlbumEntry allMediaAlbum = null;
String cameraFolder = null;
try {
cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/";
} catch (Exception e) {
Log.e(TAG, "loadGalleryAlbums: ", e);
}
Integer mediaCameraAlbumId = null;
Integer photoCameraAlbumId = null;
Cursor cursor = null;
try {
if (PermissionUtils.hasAttachMediaPerms(context)) {
cursor = MediaStore.Images.Media.query(context.getContentResolver(),
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
PROJECTION_PHOTOS,
null,
null,
(Build.VERSION.SDK_INT > 28
? MediaStore.Images.Media.DATE_TAKEN
: MediaStore.Images.Media.DATE_MODIFIED) + " DESC");
if (cursor != null) {
int imageIdColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID);
int bucketIdColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
int bucketNameColumn = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
int dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.DATE_TAKEN
: MediaStore.Images.Media.DATE_MODIFIED);
int orientationColumn = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION);
int widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH);
int heightColumn = cursor.getColumnIndex(MediaStore.Images.Media.HEIGHT);
int sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE);
while (cursor.moveToNext()) {
String path = cursor.getString(dataColumn);
if (TextUtils.isEmpty(path)) {
continue;
}
int imageId = cursor.getInt(imageIdColumn);
int bucketId = cursor.getInt(bucketIdColumn);
String bucketName = cursor.getString(bucketNameColumn);
long dateTaken = cursor.getLong(dateColumn);
int orientation = cursor.getInt(orientationColumn);
int width = cursor.getInt(widthColumn);
int height = cursor.getInt(heightColumn);
long size = cursor.getLong(sizeColumn);
MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, orientation, -1, false, width, height, size);
if (allPhotosAlbum == null) {
allPhotosAlbum = new AlbumEntry(0, context.getString(R.string.all_photos), mediaEntry);
photoAlbumsSorted.add(0, allPhotosAlbum);
}
if (allMediaAlbum == null) {
allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry);
mediaAlbumsSorted.add(0, allMediaAlbum);
}
allPhotosAlbum.addPhoto(mediaEntry);
allMediaAlbum.addPhoto(mediaEntry);
AlbumEntry albumEntry = mediaAlbums.get(bucketId);
if (albumEntry == null) {
albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry);
mediaAlbums.put(bucketId, albumEntry);
if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) {
mediaAlbumsSorted.add(0, albumEntry);
mediaCameraAlbumId = bucketId;
} else {
mediaAlbumsSorted.add(albumEntry);
}
}
albumEntry.addPhoto(mediaEntry);
albumEntry = photoAlbums.get(bucketId);
if (albumEntry == null) {
albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry);
photoAlbums.put(bucketId, albumEntry);
if (photoCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) {
photoAlbumsSorted.add(0, albumEntry);
photoCameraAlbumId = bucketId;
} else {
photoAlbumsSorted.add(albumEntry);
}
}
albumEntry.addPhoto(mediaEntry);
}
}
}
} catch (Throwable e) {
Log.e(TAG, "loadGalleryAlbums: ", e);
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "loadGalleryAlbums: ", e);
}
}
}
try {
if (PermissionUtils.hasAttachMediaPerms(context)) {
cursor = MediaStore.Images.Media.query(context.getContentResolver(),
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
PROJECTION_VIDEO,
MediaStore.Video.Media.MIME_TYPE + "=?",
new String[]{"video/mp4"},
(Build.VERSION.SDK_INT > 28
? MediaStore.Video.Media.DATE_TAKEN
: MediaStore.Video.Media.DATE_MODIFIED) + " DESC");
if (cursor != null) {
int imageIdColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID);
int bucketIdColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_ID);
int bucketNameColumn = cursor.getColumnIndex(MediaStore.Video.Media.BUCKET_DISPLAY_NAME);
int dataColumn = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
int dateColumn = cursor.getColumnIndex(Build.VERSION.SDK_INT > 28 ? MediaStore.Video.Media.DATE_TAKEN
: MediaStore.Video.Media.DATE_MODIFIED);
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
int widthColumn = cursor.getColumnIndex(MediaStore.Video.Media.WIDTH);
int heightColumn = cursor.getColumnIndex(MediaStore.Video.Media.HEIGHT);
int sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE);
while (cursor.moveToNext()) {
String path = cursor.getString(dataColumn);
if (TextUtils.isEmpty(path)) {
continue;
}
int imageId = cursor.getInt(imageIdColumn);
int bucketId = cursor.getInt(bucketIdColumn);
String bucketName = cursor.getString(bucketNameColumn);
long dateTaken = cursor.getLong(dateColumn);
long duration = cursor.getLong(durationColumn);
int width = cursor.getInt(widthColumn);
int height = cursor.getInt(heightColumn);
long size = cursor.getLong(sizeColumn);
MediaEntry mediaEntry = new MediaEntry(bucketId, imageId, dateTaken, path, -1, duration, true, width, height, size);
if (allVideosAlbum == null) {
allVideosAlbum = new AlbumEntry(0, context.getString(R.string.all_videos), mediaEntry);
allVideosAlbum.videoOnly = true;
int index = 0;
if (allMediaAlbum != null) {
index++;
}
if (allPhotosAlbum != null) {
index++;
}
mediaAlbumsSorted.add(index, allVideosAlbum);
}
if (allMediaAlbum == null) {
allMediaAlbum = new AlbumEntry(0, context.getString(R.string.all_media), mediaEntry);
mediaAlbumsSorted.add(0, allMediaAlbum);
}
allVideosAlbum.addPhoto(mediaEntry);
allMediaAlbum.addPhoto(mediaEntry);
AlbumEntry albumEntry = mediaAlbums.get(bucketId);
if (albumEntry == null) {
albumEntry = new AlbumEntry(bucketId, bucketName, mediaEntry);
mediaAlbums.put(bucketId, albumEntry);
if (mediaCameraAlbumId == null && cameraFolder != null && path.startsWith(cameraFolder)) {
mediaAlbumsSorted.add(0, albumEntry);
mediaCameraAlbumId = bucketId;
} else {
mediaAlbumsSorted.add(albumEntry);
}
}
albumEntry.addPhoto(mediaEntry);
}
}
}
} catch (Throwable e) {
Log.e(TAG, "loadGalleryAlbums: ", e);
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "loadGalleryAlbums: ", e);
}
}
}
for (int a = 0; a < mediaAlbumsSorted.size(); a++) {
Collections.sort(mediaAlbumsSorted.get(a).photos, (o1, o2) -> {
if (o1.dateTaken < o2.dateTaken) {
return 1;
} else if (o1.dateTaken > o2.dateTaken) {
return -1;
}
return 0;
});
}
broadcastNewPhotos(mediaAlbumsSorted, photoAlbumsSorted, mediaCameraAlbumId, allMediaAlbum, allPhotosAlbum, allVideosAlbum, 0);
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
private void broadcastNewPhotos(final ArrayList<AlbumEntry> mediaAlbumsSorted,
final ArrayList<AlbumEntry> photoAlbumsSorted,
final Integer cameraAlbumIdFinal,
final AlbumEntry allMediaAlbumFinal,
final AlbumEntry allPhotosAlbumFinal,
final AlbumEntry allVideosAlbumFinal,
int delay) {
if (broadcastPhotosRunnable != null) {
appExecutors.getMainThread().cancel(broadcastPhotosRunnable);
}
appExecutors.getMainThread().execute(broadcastPhotosRunnable = () -> {
allMediaAlbums = mediaAlbumsSorted;
allPhotoAlbums = photoAlbumsSorted;
broadcastPhotosRunnable = null;
allPhotosAlbumEntry = allPhotosAlbumFinal;
allMediaAlbumEntry = allMediaAlbumFinal;
allVideosAlbumEntry = allVideosAlbumFinal;
if (onLoadListener != null) {
onLoadListener.onLoad();
}
}, delay);
}
public AlbumEntry getAllMediaAlbumEntry() {
return allMediaAlbumEntry;
}
public AlbumEntry getAllPhotosAlbumEntry() {
return allPhotosAlbumEntry;
}
public AlbumEntry getAllVideosAlbumEntry() {
return allVideosAlbumEntry;
}
public ArrayList<AlbumEntry> getAllMediaAlbums() {
return allMediaAlbums;
}
public ArrayList<AlbumEntry> getAllPhotoAlbums() {
return allPhotoAlbums;
}
public static class AlbumEntry {
public int bucketId;
public boolean videoOnly;
public String bucketName;
public MediaEntry coverPhoto;
public ArrayList<MediaEntry> photos = new ArrayList<>();
public SparseArray<MediaEntry> photosByIds = new SparseArray<>();
public AlbumEntry(int bucketId, String bucketName, MediaEntry coverPhoto) {
this.bucketId = bucketId;
this.bucketName = bucketName;
this.coverPhoto = coverPhoto;
}
public void addPhoto(MediaEntry mediaEntry) {
photos.add(mediaEntry);
photosByIds.put(mediaEntry.imageId, mediaEntry);
}
}
public static class MediaEntry {
public int bucketId;
public int imageId;
public long dateTaken;
public long duration;
public int width;
public int height;
public long size;
public String path;
public int orientation;
public boolean isVideo;
public boolean isMuted;
public boolean canDeleteAfter;
public MediaEntry(int bucketId,
int imageId,
long dateTaken,
String path,
int orientation,
long duration,
boolean isVideo,
int width,
int height,
long size) {
this.bucketId = bucketId;
this.imageId = imageId;
this.dateTaken = dateTaken;
this.path = path;
this.width = width;
this.height = height;
this.size = size;
if (isVideo) {
this.duration = duration;
} else {
this.orientation = orientation;
}
this.isVideo = isVideo;
}
}
public interface OnLoadListener {
void onLoad();
}
}

28
app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java

@ -28,7 +28,7 @@ import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.MediaCandidate; import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.stories.StoryMedia;
public final class ResponseBodyUtils { public final class ResponseBodyUtils {
private static final String TAG = "ResponseBodyUtils"; private static final String TAG = "ResponseBodyUtils";
@ -271,6 +271,7 @@ public final class ResponseBodyUtils {
false, false,
false, false,
null, null,
null,
null null
); );
} }
@ -405,27 +406,38 @@ public final class ResponseBodyUtils {
return model; return model;
} }
public static String getThumbUrl(final Media media) {
public static String getThumbUrl(final Object media) {
return getImageCandidate(media, CandidateType.THUMBNAIL); return getImageCandidate(media, CandidateType.THUMBNAIL);
} }
public static String getImageUrl(final Media media) {
public static String getImageUrl(final Object media) {
return getImageCandidate(media, CandidateType.DOWNLOAD); return getImageCandidate(media, CandidateType.DOWNLOAD);
} }
private static String getImageCandidate(final Media media, final CandidateType type) {
if (media == null) return null;
final ImageVersions2 imageVersions2 = media.getImageVersions2();
private static String getImageCandidate(final Object rawMedia, final CandidateType type) {
final ImageVersions2 imageVersions2;
final int originalWidth, originalHeight;
if (rawMedia instanceof StoryMedia) {
imageVersions2 = ((StoryMedia) rawMedia).getImageVersions2();
originalWidth = ((StoryMedia) rawMedia).getOriginalWidth();
originalHeight = ((StoryMedia) rawMedia).getOriginalHeight();
}
else if (rawMedia instanceof Media) {
imageVersions2 = ((Media) rawMedia).getImageVersions2();
originalWidth = ((Media) rawMedia).getOriginalWidth();
originalHeight = ((Media) rawMedia).getOriginalHeight();
}
else return null;
if (imageVersions2 == null) return null; if (imageVersions2 == null) return null;
final List<MediaCandidate> candidates = imageVersions2.getCandidates(); final List<MediaCandidate> candidates = imageVersions2.getCandidates();
if (candidates == null || candidates.isEmpty()) 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<MediaCandidate> sortedCandidates = candidates.stream() final List<MediaCandidate> sortedCandidates = candidates.stream()
.sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth())) .sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth()))
.collect(Collectors.toList()); .collect(Collectors.toList());
final List<MediaCandidate> filteredCandidates = sortedCandidates.stream() final List<MediaCandidate> filteredCandidates = sortedCandidates.stream()
.filter(c -> .filter(c ->
c.getWidth() <= media.getOriginalWidth()
c.getWidth() <= originalWidth
&& c.getWidth() <= type.getValue() && c.getWidth() <= type.getValue()
&& (isSquare || Integer && (isSquare || Integer
.compare(c.getWidth(), c.getHeight()) != 0) .compare(c.getWidth(), c.getHeight()) != 0)

6
app/src/main/java/awais/instagrabber/viewmodels/ArchivesViewModel.java

@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel;
import java.util.List; import java.util.List;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.responses.stories.Story;
public class ArchivesViewModel extends ViewModel { public class ArchivesViewModel extends ViewModel {
private MutableLiveData<List<HighlightModel>> list;
private MutableLiveData<List<Story>> list;
public MutableLiveData<List<HighlightModel>> getList() {
public MutableLiveData<List<Story>> getList() {
if (list == null) { if (list == null) {
list = new MutableLiveData<>(); list = new MutableLiveData<>();
} }

19
app/src/main/java/awais/instagrabber/viewmodels/CommentsViewerViewModel.java

@ -54,7 +54,7 @@ public class CommentsViewerViewModel extends ViewModel {
private String postId; private String postId;
private String rootCursor; private String rootCursor;
private boolean rootHasNext = true; private boolean rootHasNext = true;
private Comment repliesParent;
private Comment repliesParent, replyTo;
private String repliesCursor; private String repliesCursor;
private boolean repliesHasNext = true; private boolean repliesHasNext = true;
private final CommentService commentService; private final CommentService commentService;
@ -153,6 +153,11 @@ public class CommentsViewerViewModel extends ViewModel {
return repliesParent; return repliesParent;
} }
@Nullable
public void setReplyTo(final Comment replyTo) {
this.replyTo = replyTo;
}
public LiveData<Resource<List<Comment>>> getRootList() { public LiveData<Resource<List<Comment>>> getRootList() {
return rootList; return rootList;
} }
@ -297,6 +302,7 @@ public class CommentsViewerViewModel extends ViewModel {
if (comment == null) return; if (comment == null) return;
if (repliesParent == null || !Objects.equals(repliesParent.getPk(), comment.getPk())) { if (repliesParent == null || !Objects.equals(repliesParent.getPk(), comment.getPk())) {
repliesParent = comment; repliesParent = comment;
replyTo = comment;
prevReplies = null; prevReplies = null;
prevRepliesCursor = null; prevRepliesCursor = null;
prevRepliesHasNext = true; prevRepliesHasNext = true;
@ -368,8 +374,8 @@ public class CommentsViewerViewModel extends ViewModel {
final boolean isReply) { final boolean isReply) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(Resource.loading(null)); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(Resource.loading(null));
String replyToId = null; String replyToId = null;
if (isReply && repliesParent != null) {
replyToId = repliesParent.getPk();
if (isReply && replyTo != null) {
replyToId = replyTo.getPk();
} }
if (isReply && replyToId == null) { if (isReply && replyToId == null) {
data.postValue(Resource.error(null, null)); data.postValue(Resource.error(null, null));
@ -399,10 +405,9 @@ public class CommentsViewerViewModel extends ViewModel {
final List<Comment> list = getPrevList(isReply ? replyList : rootList); final List<Comment> list = getPrevList(isReply ? replyList : rootList);
final ImmutableList.Builder<Comment> builder = ImmutableList.builder(); final ImmutableList.Builder<Comment> builder = ImmutableList.builder();
if (isReply) { if (isReply) {
// in a reply list the first comment is the parent comment
builder.add(list.get(0))
.add(comment)
.addAll(list.subList(1, list.size()));
// replies are added to the bottom of the list to preserve chronological order
builder.addAll(list)
.add(comment);
} else { } else {
builder.add(comment) builder.add(comment)
.addAll(list); .addAll(list);

6
app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt

@ -1,6 +1,5 @@
package awais.instagrabber.viewmodels package awais.instagrabber.viewmodels
import android.R.attr
import android.app.Application import android.app.Application
import android.content.ContentResolver import android.content.ContentResolver
import android.net.Uri import android.net.Uri
@ -23,7 +22,6 @@ import awais.instagrabber.utils.MediaUtils.OnInfoLoadListener
import awais.instagrabber.utils.MediaUtils.VideoInfo import awais.instagrabber.utils.MediaUtils.VideoInfo
import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback import awais.instagrabber.utils.VoiceRecorder.VoiceRecorderCallback
import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult import awais.instagrabber.utils.VoiceRecorder.VoiceRecordingResult
import java.util.*
class DirectThreadViewModel( class DirectThreadViewModel(
@ -76,10 +74,6 @@ class DirectThreadViewModel(
return threadManager.sendText(text, viewModelScope) return threadManager.sendText(text, viewModelScope)
} }
fun sendUri(entry: MediaController.MediaEntry): LiveData<Resource<Any?>> {
return threadManager.sendUri(entry, viewModelScope)
}
fun sendUri(uri: Uri): LiveData<Resource<Any?>> { fun sendUri(uri: Uri): LiveData<Resource<Any?>> {
return threadManager.sendUri(uri, viewModelScope) return threadManager.sendUri(uri, viewModelScope)
} }

7
app/src/main/java/awais/instagrabber/viewmodels/FeedStoriesViewModel.java

@ -3,15 +3,14 @@ package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.repositories.responses.stories.Story;
public class FeedStoriesViewModel extends ViewModel { public class FeedStoriesViewModel extends ViewModel {
private MutableLiveData<List<FeedStoryModel>> list;
private MutableLiveData<List<Story>> list;
public MutableLiveData<List<FeedStoryModel>> getList() {
public MutableLiveData<List<Story>> getList() {
if (list == null) { if (list == null) {
list = new MutableLiveData<>(); list = new MutableLiveData<>();
} }

6
app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java

@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel;
import java.util.List; import java.util.List;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.repositories.responses.stories.Story;
public class HighlightsViewModel extends ViewModel { public class HighlightsViewModel extends ViewModel {
private MutableLiveData<List<HighlightModel>> list;
private MutableLiveData<List<Story>> list;
public MutableLiveData<List<HighlightModel>> getList() {
public MutableLiveData<List<Story>> getList() {
if (list == null) { if (list == null) {
list = new MutableLiveData<>(); list = new MutableLiveData<>();
} }

41
app/src/main/java/awais/instagrabber/viewmodels/MediaPickerViewModel.java

@ -1,41 +0,0 @@
package awais.instagrabber.viewmodels;
import android.content.Context;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.utils.MediaController;
import awais.instagrabber.utils.MediaController.AlbumEntry;
public class MediaPickerViewModel extends ViewModel implements MediaController.OnLoadListener {
private final MutableLiveData<List<AlbumEntry>> allAlbums = new MutableLiveData<>(Collections.emptyList());
private MediaController mediaController;
public MediaPickerViewModel() {
}
public void loadMedia(final Context context) {
mediaController = new MediaController(context, this);
mediaController.load();
}
@Override
public void onLoad() {
if (mediaController == null) {
return;
}
final List<AlbumEntry> allPhotoAlbums = mediaController.getAllMediaAlbums();
this.allAlbums.postValue(allPhotoAlbums);
}
public LiveData<List<AlbumEntry>> getAllAlbums() {
return allAlbums;
}
}

10
app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.kt

@ -331,19 +331,21 @@ class PostViewV2ViewModel : ViewModel() {
return data return data
} }
fun shareDm(result: RankedRecipient) {
fun shareDm(result: RankedRecipient, child: Int) {
if (messageManager == null) { if (messageManager == null) {
messageManager = DirectMessagesManager messageManager = DirectMessagesManager
} }
val mediaId = media.id ?: return val mediaId = media.id ?: return
messageManager?.sendMedia(result, mediaId, BroadcastItemType.MEDIA_SHARE, viewModelScope)
val childId = if (child == -1) null else media.carouselMedia?.get(child)?.id
messageManager?.sendMedia(result, mediaId, childId, BroadcastItemType.MEDIA_SHARE, viewModelScope)
} }
fun shareDm(recipients: Set<RankedRecipient>) {
fun shareDm(recipients: Set<RankedRecipient>, child: Int) {
if (messageManager == null) { if (messageManager == null) {
messageManager = DirectMessagesManager messageManager = DirectMessagesManager
} }
val mediaId = media.id ?: return val mediaId = media.id ?: return
messageManager?.sendMedia(recipients, mediaId, BroadcastItemType.MEDIA_SHARE, viewModelScope)
val childId = if (child == -1) null else media.carouselMedia?.get(child)?.id
messageManager?.sendMedia(recipients, mediaId, childId, BroadcastItemType.MEDIA_SHARE, viewModelScope)
} }
} }

14
app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt

@ -8,7 +8,6 @@ import awais.instagrabber.db.entities.Favorite
import awais.instagrabber.db.repositories.AccountRepository import awais.instagrabber.db.repositories.AccountRepository
import awais.instagrabber.db.repositories.FavoriteRepository import awais.instagrabber.db.repositories.FavoriteRepository
import awais.instagrabber.managers.DirectMessagesManager import awais.instagrabber.managers.DirectMessagesManager
import awais.instagrabber.models.HighlightModel
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
import awais.instagrabber.models.StoryModel import awais.instagrabber.models.StoryModel
import awais.instagrabber.models.enums.BroadcastItemType import awais.instagrabber.models.enums.BroadcastItemType
@ -18,6 +17,7 @@ import awais.instagrabber.repositories.responses.FriendshipStatus
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.UserProfileContextLink import awais.instagrabber.repositories.responses.UserProfileContextLink
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.utils.ControlledRunner import awais.instagrabber.utils.ControlledRunner
import awais.instagrabber.utils.Event import awais.instagrabber.utils.Event
import awais.instagrabber.utils.SingleRunner import awais.instagrabber.utils.SingleRunner
@ -185,9 +185,9 @@ class ProfileFragmentViewModel(
} }
} }
private val highlightsFetchControlledRunner = ControlledRunner<List<HighlightModel>?>()
val userHighlights: LiveData<Resource<List<HighlightModel>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
liveData<Resource<List<HighlightModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
private val highlightsFetchControlledRunner = ControlledRunner<List<Story>?>()
val userHighlights: LiveData<Resource<List<Story>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
liveData<Resource<List<Story>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
val (currentUserResource, profileResource, action) = currentUserAndProfilePair val (currentUserResource, profileResource, action) = currentUserAndProfilePair
if (action != INIT && action != REFRESH) { if (action != INIT && action != REFRESH) {
return@liveData return@liveData
@ -236,7 +236,7 @@ class ProfileFragmentViewModel(
StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName) StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName)
) )
private suspend fun fetchUserHighlights(fetchedUser: User): List<HighlightModel> = storiesRepository.fetchHighlights(fetchedUser.pk)
private suspend fun fetchUserHighlights(fetchedUser: User): List<Story> = storiesRepository.fetchHighlights(fetchedUser.pk)
private suspend fun checkAndUpdateFavorite(fetchedUser: User) { private suspend fun checkAndUpdateFavorite(fetchedUser: User) {
try { try {
@ -268,12 +268,12 @@ class ProfileFragmentViewModel(
fun shareDm(result: RankedRecipient) { fun shareDm(result: RankedRecipient) {
val mediaId = profile.value?.data?.pk ?: return val mediaId = profile.value?.data?.pk ?: return
messageManager?.sendMedia(result, mediaId.toString(10), BroadcastItemType.PROFILE, viewModelScope)
messageManager?.sendMedia(result, mediaId.toString(10), null, BroadcastItemType.PROFILE, viewModelScope)
} }
fun shareDm(recipients: Set<RankedRecipient>) { fun shareDm(recipients: Set<RankedRecipient>) {
val mediaId = profile.value?.data?.pk ?: return val mediaId = profile.value?.data?.pk ?: return
messageManager?.sendMedia(recipients, mediaId.toString(10), BroadcastItemType.PROFILE, viewModelScope)
messageManager?.sendMedia(recipients, mediaId.toString(10), null, BroadcastItemType.PROFILE, viewModelScope)
} }
fun refresh() { fun refresh() {

3
app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt

@ -173,8 +173,9 @@ open class DirectMessagesRepository(private val service: DirectMessagesService)
clientContext: String, clientContext: String,
threadIdsOrUserIds: ThreadIdsOrUserIds, threadIdsOrUserIds: ThreadIdsOrUserIds,
mediaId: String, mediaId: String,
childId: String?,
): DirectThreadBroadcastResponse = ): DirectThreadBroadcastResponse =
broadcast(csrfToken, userId, deviceUuid, MediaShareBroadcastOptions(clientContext, threadIdsOrUserIds, mediaId))
broadcast(csrfToken, userId, deviceUuid, MediaShareBroadcastOptions(clientContext, threadIdsOrUserIds, mediaId, childId))
suspend fun broadcastProfile( suspend fun broadcastProfile(
csrfToken: String, csrfToken: String,

6
app/src/main/java/awais/instagrabber/webservices/ProfileService.java

@ -63,7 +63,7 @@ public class ProfileService {
} }
callback.onSuccess(new PostsFetchResponse( callback.onSuccess(new PostsFetchResponse(
body.getItems(), body.getItems(),
body.isMoreAvailable(),
body.getMoreAvailable(),
body.getNextMaxId() body.getNextMaxId()
)); ));
} }
@ -204,7 +204,7 @@ public class ProfileService {
} }
callback.onSuccess(new PostsFetchResponse( callback.onSuccess(new PostsFetchResponse(
userFeedResponse.getItems(), userFeedResponse.getItems(),
userFeedResponse.isMoreAvailable(),
userFeedResponse.getMoreAvailable(),
userFeedResponse.getNextMaxId() userFeedResponse.getNextMaxId()
)); ));
} }
@ -237,7 +237,7 @@ public class ProfileService {
} }
callback.onSuccess(new PostsFetchResponse( callback.onSuccess(new PostsFetchResponse(
userFeedResponse.getItems(), userFeedResponse.getItems(),
userFeedResponse.isMoreAvailable(),
userFeedResponse.getMoreAvailable(),
userFeedResponse.getNextMaxId() userFeedResponse.getNextMaxId()
)); ));
} }

155
app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt

@ -1,19 +1,15 @@
package awais.instagrabber.webservices package awais.instagrabber.webservices
import android.util.Log
import awais.instagrabber.fragments.settings.PreferenceKeys import awais.instagrabber.fragments.settings.PreferenceKeys
import awais.instagrabber.models.FeedStoryModel
import awais.instagrabber.models.HighlightModel
import awais.instagrabber.models.StoryModel import awais.instagrabber.models.StoryModel
import awais.instagrabber.repositories.StoriesService import awais.instagrabber.repositories.StoriesService
import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.StoryStickerResponse
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.Constants
import awais.instagrabber.repositories.responses.stories.ArchiveResponse
import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
import awais.instagrabber.utils.ResponseBodyUtils import awais.instagrabber.utils.ResponseBodyUtils
import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.utils.Utils import awais.instagrabber.utils.Utils
import awais.instagrabber.utils.extensions.TAG
import awais.instagrabber.webservices.RetrofitFactory.retrofit import awais.instagrabber.webservices.RetrofitFactory.retrofit
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@ -27,101 +23,42 @@ open class StoriesRepository(private val service: StoriesService) {
return ResponseBodyUtils.parseStoryItem(itemJson, false, null) return ResponseBodyUtils.parseStoryItem(itemJson, false, null)
} }
suspend fun getFeedStories(): List<FeedStoryModel> {
suspend fun getFeedStories(): List<Story> {
val response = service.getFeedStories() val response = service.getFeedStories()
return parseStoriesBody(response)
}
private fun parseStoriesBody(body: String): List<FeedStoryModel> {
val feedStoryModels: MutableList<FeedStoryModel> = ArrayList()
val feedStoriesReel = JSONObject(body).getJSONArray("tray")
for (i in 0 until feedStoriesReel.length()) {
val node = feedStoriesReel.getJSONObject(i)
if (node.optBoolean("hide_from_feed_unit") && Utils.settingsHelper.getBoolean(PreferenceKeys.HIDE_MUTED_REELS)) continue
val userJson = node.getJSONObject(if (node.has("user")) "user" else "owner")
try {
val user = User(
userJson.getLong("pk"),
userJson.getString("username"),
userJson.optString("full_name"),
userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"),
userJson.optBoolean("is_verified")
)
val timestamp = node.getLong("latest_reel_media")
val fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp
val itemJson = if (node.has("items")) node.getJSONArray("items").optJSONObject(0) else null
var firstStoryModel: StoryModel? = null
if (itemJson != null) {
firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, null)
}
feedStoryModels.add(
FeedStoryModel(
node.getString("id"),
user,
fullyRead,
timestamp,
firstStoryModel,
node.getInt("media_count"),
false,
node.optBoolean("has_besties_media")
val result: MutableList<Story> = mutableListOf()
if (response?.broadcasts != null) {
val length = response.broadcasts.size
for (i in 0 until length) {
val broadcast = response.broadcasts.get(i)
result.add(
Story(
broadcast.id,
broadcast.publishedTime,
1,
0L,
broadcast.broadcastOwner,
broadcast.muted,
false, // unclear
null,
null,
null,
null,
broadcast
) )
) )
} catch (e: Exception) {
Log.e(TAG, "parseStoriesBody: ", e)
} // to cover promotional reels with non-long user pk's
}
val broadcasts = JSONObject(body).getJSONArray("broadcasts")
for (i in 0 until broadcasts.length()) {
val node = broadcasts.getJSONObject(i)
val userJson = node.getJSONObject("broadcast_owner")
val user = User(
userJson.getLong("pk"),
userJson.getString("username"),
userJson.optString("full_name"),
userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"),
userJson.optBoolean("is_verified")
)
feedStoryModels.add(
FeedStoryModel(
node.getString("id"),
user,
false,
node.getLong("published_time"),
ResponseBodyUtils.parseBroadcastItem(node),
1,
isLive = true,
isBestie = false
)
)
}
} }
return sort(feedStoryModels)
if (response?.tray != null) result.addAll(response.tray)
return sort(result.toList())
} }
open suspend fun fetchHighlights(profileId: Long): List<HighlightModel> {
open suspend fun fetchHighlights(profileId: Long): List<Story> {
val response = service.fetchHighlights(profileId) val response = service.fetchHighlights(profileId)
val highlightsReel = JSONObject(response).getJSONArray("tray")
val length = highlightsReel.length()
val highlightModels: MutableList<HighlightModel> = ArrayList()
for (i in 0 until length) {
val highlightNode = highlightsReel.getJSONObject(i)
highlightModels.add(
HighlightModel(
highlightNode.getString("title"),
highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_media")
.getJSONObject("cropped_image_version")
.getString("url"),
highlightNode.getLong("latest_reel_media"),
highlightNode.getInt("media_count")
)
)
}
val highlightModels = response?.tray ?: listOf()
return highlightModels return highlightModels
} }
suspend fun fetchArchive(maxId: String): ArchiveFetchResponse {
suspend fun fetchArchive(maxId: String): ArchiveResponse? {
val form = mutableMapOf( val form = mutableMapOf(
"include_suggested_highlights" to "false", "include_suggested_highlights" to "false",
"is_in_archive_home" to "true", "is_in_archive_home" to "true",
@ -130,24 +67,7 @@ open class StoriesRepository(private val service: StoriesService) {
if (!isEmpty(maxId)) { if (!isEmpty(maxId)) {
form["max_id"] = maxId // NOT TESTED form["max_id"] = maxId // NOT TESTED
} }
val response = service.fetchArchive(form)
val data = JSONObject(response)
val highlightsReel = data.getJSONArray("items")
val length = highlightsReel.length()
val highlightModels: MutableList<HighlightModel> = ArrayList()
for (i in 0 until length) {
val highlightNode = highlightsReel.getJSONObject(i)
highlightModels.add(
HighlightModel(
null,
highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_image_version").getString("url"),
highlightNode.getLong("latest_reel_media"),
highlightNode.getInt("media_count")
)
)
}
return ArchiveFetchResponse(highlightModels, data.getBoolean("more_available"), data.getString("max_id"))
return service.fetchArchive(form)
} }
open suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> { open suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> {
@ -299,24 +219,19 @@ open class StoriesRepository(private val service: StoriesService) {
return builder.toString() return builder.toString()
} }
private fun sort(list: List<FeedStoryModel>): List<FeedStoryModel> {
private fun sort(list: List<Story>): List<Story> {
val listCopy = ArrayList(list) val listCopy = ArrayList(list)
listCopy.sortWith { o1, o2 -> listCopy.sortWith { o1, o2 ->
when (Utils.settingsHelper.getString(PreferenceKeys.STORY_SORT)) {
"1" -> return@sortWith o2.timestamp.compareTo(o1.timestamp)
"2" -> return@sortWith o1.timestamp.compareTo(o2.timestamp)
if (o1.latestReelMedia == null || o2.latestReelMedia == null) return@sortWith 0
else when (Utils.settingsHelper.getString(PreferenceKeys.STORY_SORT)) {
"1" -> return@sortWith o2.latestReelMedia.compareTo(o1.latestReelMedia)
"2" -> return@sortWith o1.latestReelMedia.compareTo(o2.latestReelMedia)
else -> return@sortWith 0 else -> return@sortWith 0
} }
} }
return listCopy return listCopy
} }
class ArchiveFetchResponse(val result: List<HighlightModel>, val hasNextPage: Boolean, val nextCursor: String) {
fun hasNextPage(): Boolean {
return hasNextPage
}
}
companion object { companion object {
@Volatile @Volatile
private var INSTANCE: StoriesRepository? = null private var INSTANCE: StoriesRepository? = null

2
app/src/main/java/awais/instagrabber/webservices/TagsService.java

@ -122,7 +122,7 @@ public class TagsService {
} }
callback.onSuccess(new PostsFetchResponse( callback.onSuccess(new PostsFetchResponse(
body.getItems(), body.getItems(),
body.isMoreAvailable(),
body.getMoreAvailable(),
body.getNextMaxId() body.getNextMaxId()
)); ));
} }

5
app/src/main/res/color/ic_read_button_tint.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/grey_600" android:state_enabled="false" />
<item android:color="?colorControlNormal" />
</selector>

2
app/src/main/res/drawable/ic_check_all_24.xml

@ -3,7 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:tint="@color/ic_read_button_tint">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/> android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>

4
app/src/main/res/layout/fragment_story_viewer.xml

@ -133,7 +133,7 @@
android:id="@+id/btnBackward" android:id="@+id/btnBackward"
style="@style/Widget.MaterialComponents.Button.TextButton" style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="0dp"
android:layout_height="@dimen/story_item_height"
android:visibility="visible" android:visibility="visible"
app:icon="@drawable/exo_ic_skip_previous" app:icon="@drawable/exo_ic_skip_previous"
app:iconGravity="textStart" app:iconGravity="textStart"
@ -157,7 +157,7 @@
android:id="@+id/btnForward" android:id="@+id/btnForward"
style="@style/Widget.MaterialComponents.Button.TextButton" style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="0dp"
android:layout_height="@dimen/story_item_height"
android:visibility="visible" android:visibility="visible"
app:icon="@drawable/exo_ic_skip_next" app:icon="@drawable/exo_ic_skip_next"
app:iconGravity="textStart" app:iconGravity="textStart"

22
app/src/main/res/layout/layout_dm_media_share.xml

@ -76,26 +76,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="0dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="@color/white"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/caption"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Title" />
app:layout_constraintTop_toTopOf="@id/caption" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/caption" android:id="@+id/caption"
@ -107,6 +88,5 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
tools:text="Caption Caption Caption Caption Caption Caption " /> tools:text="Caption Caption Caption Caption Caption Caption " />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

30
app/src/main/res/layout/layout_media_picker.xml

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:layout_constraintBottom_toTopOf="@id/media_list"
app:layout_constraintTop_toTopOf="parent">
<Spinner
android:id="@+id/album_picker"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</androidx.appcompat.widget.Toolbar>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/media_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

3
app/src/main/res/values/strings.xml

@ -22,6 +22,7 @@
<string name="title_favorites">Favorites</string> <string name="title_favorites">Favorites</string>
<string name="title_discover">Discover</string> <string name="title_discover">Discover</string>
<string name="title_comments">Comments</string> <string name="title_comments">Comments</string>
<string name="title_replies">Replies</string>
<string name="title_notifications">Activity</string> <string name="title_notifications">Activity</string>
<string name="update_check">Check for updates at startup</string> <string name="update_check">Check for updates at startup</string>
<string name="flag_secure">Block screenshots &amp; app preview</string> <string name="flag_secure">Block screenshots &amp; app preview</string>
@ -513,7 +514,7 @@
<string name="dir_select_folder_not_exist">The previously selected folder does not exist now:</string> <string name="dir_select_folder_not_exist">The previously selected folder does not exist now:</string>
<string name="dir_select_message2">Re-select the directory or select a new directory by clicking the button below.</string> <string name="dir_select_message2">Re-select the directory or select a new directory by clicking the button below.</string>
<string name="select_a_folder">No folder selected!</string> <string name="select_a_folder">No folder selected!</string>
<string name="dir_select_no_download_folder">Please choose a directory from your storage, not a category on the sidebar.</string>
<string name="dir_select_no_download_folder">Please choose a directory from your storage, not a category on the sidebar.\n(%s)</string>
<string name="dir_select_success_message">Success! Please wait. Starting app…</string> <string name="dir_select_success_message">Success! Please wait. Starting app…</string>
<string name="barinsta_folder">Barinsta folder</string> <string name="barinsta_folder">Barinsta folder</string>
<string name="top">Top</string> <string name="top">Top</string>

9
app/src/test/java/awais/instagrabber/common/Adapters.kt

@ -8,6 +8,9 @@ import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.repositories.* import awais.instagrabber.repositories.*
import awais.instagrabber.repositories.responses.* import awais.instagrabber.repositories.responses.*
import awais.instagrabber.repositories.responses.directmessages.* import awais.instagrabber.repositories.responses.directmessages.*
import awais.instagrabber.repositories.responses.stories.ArchiveResponse
import awais.instagrabber.repositories.responses.stories.ReelsTrayResponse
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
open class UserServiceAdapter : UserService { open class UserServiceAdapter : UserService {
override suspend fun getUserInfo(uid: Long): WrappedUser { override suspend fun getUserInfo(uid: Long): WrappedUser {
@ -48,15 +51,15 @@ open class StoriesServiceAdapter : StoriesService {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun getFeedStories(): String {
override suspend fun getFeedStories(): ReelsTrayResponse? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun fetchHighlights(uid: Long): String {
override suspend fun fetchHighlights(uid: Long): ReelsTrayResponse? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun fetchArchive(queryParams: Map<String, String>): String {
override suspend fun fetchArchive(queryParams: Map<String, String>): ArchiveResponse? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

6
app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt

@ -9,13 +9,13 @@ import awais.instagrabber.db.datasources.FavoriteDataSource
import awais.instagrabber.db.entities.Favorite import awais.instagrabber.db.entities.Favorite
import awais.instagrabber.db.repositories.FavoriteRepository import awais.instagrabber.db.repositories.FavoriteRepository
import awais.instagrabber.getOrAwaitValue import awais.instagrabber.getOrAwaitValue
import awais.instagrabber.models.HighlightModel
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
import awais.instagrabber.models.StoryModel 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.FriendshipStatus import awais.instagrabber.repositories.responses.FriendshipStatus
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.webservices.* import awais.instagrabber.webservices.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.json.JSONException import org.json.JSONException
@ -321,13 +321,13 @@ internal class ProfileFragmentViewModelTest {
) )
) )
val testUserStories = listOf(StoryModel()) val testUserStories = listOf(StoryModel())
val testUserHighlights = listOf(HighlightModel())
val testUserHighlights = listOf(Story())
val userRepository = object : UserRepository(UserServiceAdapter()) { val userRepository = object : UserRepository(UserServiceAdapter()) {
override suspend fun getUsernameInfo(username: String): User = testPublicUser override suspend fun getUsernameInfo(username: String): User = testPublicUser
} }
val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) { val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) {
override suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> = testUserStories override suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> = testUserStories
override suspend fun fetchHighlights(profileId: Long): List<HighlightModel> = testUserHighlights
override suspend fun fetchHighlights(profileId: Long): List<Story> = testUserHighlights
} }
val viewModel = ProfileFragmentViewModel( val viewModel = ProfileFragmentViewModel(
state, state,

2
build.gradle

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.5.0'
ext.kotlin_version = '1.5.20'
repositories { repositories {
google() google()

6
gradle.properties

@ -10,7 +10,7 @@
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
#Tue May 04 02:33:38 JST 2021
#Wed Jun 30 00:40:18 JST 2021
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -XX:+UseParallelGC
android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx1536M"
android.useAndroidX=true
Loading…
Cancel
Save