Browse Source
Merge branch 'master' of https://github.com/austinhuang0131/instagrabber
renovate/org.robolectric-robolectric-4.x
Merge branch 'master' of https://github.com/austinhuang0131/instagrabber
renovate/org.robolectric-robolectric-4.x
Ammar Githam
3 years ago
92 changed files with 526 additions and 1635 deletions
-
16app/build.gradle
-
2app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java
-
4app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
-
20app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
-
30app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java
-
14app/src/main/java/awais/instagrabber/adapters/HighlightStoriesListAdapter.java
-
14app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
-
111app/src/main/java/awais/instagrabber/adapters/MediaItemsAdapter.java
-
17app/src/main/java/awais/instagrabber/adapters/viewholder/FeedStoryViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java
-
23app/src/main/java/awais/instagrabber/adapters/viewholder/StoryListViewHolder.java
-
36app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
-
2app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java
-
20app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java
-
2app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
-
2app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java
-
4app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
-
2app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java
-
2app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java
-
2app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java
-
2app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java
-
2app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java
-
194app/src/main/java/awais/instagrabber/dialogs/MediaPickerBottomDialogFragment.java
-
10app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
31app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java
-
143app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
-
13app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java
-
43app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
14app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
3app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
-
11app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt
-
12app/src/main/java/awais/instagrabber/managers/ThreadManager.kt
-
20app/src/main/java/awais/instagrabber/models/FeedStoryModel.kt
-
14app/src/main/java/awais/instagrabber/models/HighlightModel.kt
-
10app/src/main/java/awais/instagrabber/repositories/StoriesService.kt
-
8app/src/main/java/awais/instagrabber/repositories/requests/directmessages/MediaShareBroadcastOptions.kt
-
16app/src/main/java/awais/instagrabber/repositories/responses/HdProfilePicUrlInfo.java
-
5app/src/main/java/awais/instagrabber/repositories/responses/ImageUrl.kt
-
30app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.java
-
5app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.kt
-
27app/src/main/java/awais/instagrabber/repositories/responses/LikersResponse.java
-
3app/src/main/java/awais/instagrabber/repositories/responses/LikersResponse.kt
-
4app/src/main/java/awais/instagrabber/repositories/responses/Media.kt
-
43app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.java
-
5app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.kt
-
15app/src/main/java/awais/instagrabber/repositories/responses/MediaInfoResponse.java
-
3app/src/main/java/awais/instagrabber/repositories/responses/MediaInfoResponse.kt
-
32app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.java
-
10app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.kt
-
62app/src/main/java/awais/instagrabber/repositories/responses/Place.java
-
12app/src/main/java/awais/instagrabber/repositories/responses/Place.kt
-
27app/src/main/java/awais/instagrabber/repositories/responses/PostsFetchResponse.java
-
7app/src/main/java/awais/instagrabber/repositories/responses/PostsFetchResponse.kt
-
20app/src/main/java/awais/instagrabber/repositories/responses/StoryStickerResponse.java
-
43app/src/main/java/awais/instagrabber/repositories/responses/TagFeedResponse.java
-
9app/src/main/java/awais/instagrabber/repositories/responses/TagFeedResponse.kt
-
2app/src/main/java/awais/instagrabber/repositories/responses/User.kt
-
43app/src/main/java/awais/instagrabber/repositories/responses/UserFeedResponse.java
-
9app/src/main/java/awais/instagrabber/repositories/responses/UserFeedResponse.kt
-
13app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java
-
3app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.kt
-
9app/src/main/java/awais/instagrabber/repositories/responses/stories/ArchiveResponse.kt
-
4app/src/main/java/awais/instagrabber/repositories/responses/stories/Broadcast.kt
-
5app/src/main/java/awais/instagrabber/repositories/responses/stories/CoverMedia.kt
-
37app/src/main/java/awais/instagrabber/repositories/responses/stories/Story.kt
-
5app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt
-
3app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryStickerResponse.kt
-
13app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt
-
386app/src/main/java/awais/instagrabber/utils/MediaController.java
-
28app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
-
6app/src/main/java/awais/instagrabber/viewmodels/ArchivesViewModel.java
-
19app/src/main/java/awais/instagrabber/viewmodels/CommentsViewerViewModel.java
-
6app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.kt
-
7app/src/main/java/awais/instagrabber/viewmodels/FeedStoriesViewModel.java
-
6app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java
-
41app/src/main/java/awais/instagrabber/viewmodels/MediaPickerViewModel.java
-
10app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.kt
-
14app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt
-
3app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt
-
6app/src/main/java/awais/instagrabber/webservices/ProfileService.java
-
155app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt
-
2app/src/main/java/awais/instagrabber/webservices/TagsService.java
-
5app/src/main/res/color/ic_read_button_tint.xml
-
2app/src/main/res/drawable/ic_check_all_24.xml
-
4app/src/main/res/layout/fragment_story_viewer.xml
-
22app/src/main/res/layout/layout_dm_media_share.xml
-
30app/src/main/res/layout/layout_media_picker.xml
-
3app/src/main/res/values/strings.xml
-
9app/src/test/java/awais/instagrabber/common/Adapters.kt
-
6app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt
-
2build.gradle
-
6gradle.properties
@ -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); |
|
||||
} |
|
||||
} |
|
@ -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); |
|
||||
} |
|
||||
} |
|
@ -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) |
|
||||
} |
|
@ -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) |
|
||||
} |
|
@ -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; |
|
||||
} |
|
||||
} |
|
@ -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 |
@ -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); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,5 @@ |
|||||
|
package awais.instagrabber.repositories.responses |
||||
|
|
||||
|
import java.io.Serializable |
||||
|
|
||||
|
data class ImageVersions2(val candidates: List<MediaCandidate>) : Serializable |
@ -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; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,3 @@ |
|||||
|
package awais.instagrabber.repositories.responses |
||||
|
|
||||
|
data class LikersResponse(val users: List<User>, val userCount: Long, val status: String) |
@ -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); |
|
||||
} |
|
||||
} |
|
@ -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 |
@ -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; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,3 @@ |
|||||
|
package awais.instagrabber.repositories.responses |
||||
|
|
||||
|
data class MediaInfoResponse(val items: List<Media>) |
@ -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; |
|
||||
} |
|
||||
} |
|
@ -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> |
||||
|
) |
@ -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); |
|
||||
} |
|
||||
} |
|
@ -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? |
||||
|
) |
@ -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; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,7 @@ |
|||||
|
package awais.instagrabber.repositories.responses |
||||
|
|
||||
|
class PostsFetchResponse( |
||||
|
val feedModels: List<Media>, |
||||
|
val hasNextPage: Boolean, |
||||
|
val nextCursor: String? |
||||
|
) |
@ -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 + '\'' + |
|
||||
'}'; |
|
||||
} |
|
||||
} |
|
@ -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; |
|
||||
} |
|
||||
} |
|
@ -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> |
||||
|
) |
@ -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; |
|
||||
} |
|
||||
} |
|
@ -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> |
||||
|
) |
@ -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; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,3 @@ |
|||||
|
package awais.instagrabber.repositories.responses |
||||
|
|
||||
|
class WrappedMedia(val media: Media) |
@ -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> |
||||
|
) |
@ -0,0 +1,5 @@ |
|||||
|
package awais.instagrabber.repositories.responses.stories |
||||
|
|
||||
|
import awais.instagrabber.repositories.responses.ImageUrl |
||||
|
|
||||
|
data class CoverMedia(val croppedImageVersion: ImageUrl) |
@ -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 |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
package awais.instagrabber.repositories.responses.stories |
||||
|
|
||||
|
data class StoryStickerResponse(val status: String?) |
@ -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(); |
|
||||
} |
|
||||
} |
|
@ -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; |
|
||||
} |
|
||||
} |
|
@ -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> |
@ -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> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue