diff --git a/app/src/main/java/me/ccrama/redditslide/Activities/GalleryImage.java b/app/src/main/java/me/ccrama/redditslide/Activities/GalleryImage.java index 36eed0475..27f2bd359 100644 --- a/app/src/main/java/me/ccrama/redditslide/Activities/GalleryImage.java +++ b/app/src/main/java/me/ccrama/redditslide/Activities/GalleryImage.java @@ -1,11 +1,9 @@ package me.ccrama.redditslide.Activities; - import com.fasterxml.jackson.databind.JsonNode; - import org.apache.commons.text.StringEscapeUtils; - import java.io.Serializable; +import java.util.Arrays; /** * Created by ccrama on 09/22/2020. @@ -15,13 +13,123 @@ public class GalleryImage implements Serializable { public int width; public int height; + public String mediaId; + public MediaMetadata metadata; + public GalleryImage(JsonNode data) { if(data.has("u")) { url = StringEscapeUtils.unescapeHtml4(data.get("u").asText()); - } else if(data.has("gif")) { - url = StringEscapeUtils.unescapeHtml4(data.get("gif").asText()); + } else if(data.has("mp4")) { + url = StringEscapeUtils.unescapeHtml4(data.get("mp4").asText()); } width = data.get("x").asInt(); height = data.get("y").asInt(); + + // Add metadata population + metadata = new MediaMetadata(); + if(data.has("e")) { + metadata.e = data.get("e").asText(); + } + if(data.has("m")) { + metadata.m = data.get("m").asText(); + } + if(data.has("s")) { + JsonNode s = data.get("s"); + metadata.source = new MediaMetadata.Source(); + if(s.has("mp4")) { + metadata.source.mp4 = StringEscapeUtils.unescapeHtml4(s.get("mp4").asText()); + } + if(s.has("gif")) { + metadata.source.gif = StringEscapeUtils.unescapeHtml4(s.get("gif").asText()); + } + if(s.has("y")) { + metadata.source.y = s.get("y").asInt(); + } + if(s.has("x")) { + metadata.source.x = s.get("x").asInt(); + } + } + + // Set animated based on type + metadata.animated = "AnimatedImage".equals(metadata.e); + } + + public boolean isAnimated() { + if (metadata != null) { + // Check metadata first + if (metadata.animated) { + return true; + } + if ("AnimatedImage".equals(metadata.e)) { + return true; + } + if (metadata.m != null && metadata.m.contains("gif")) { + return true; + } + } + + // Fallback to URL check if metadata is missing + return url != null && (url.endsWith(".gif") || url.endsWith(".gifv") || url.endsWith(".mp4")); + } + + public String getImageUrl() { + // For animated content, use the direct MP4 URL from metadata + if (isAnimated() && metadata != null && metadata.source != null) { + if (metadata.source.mp4 != null && !metadata.source.mp4.isEmpty()) { + // Return the complete MP4 URL directly + return metadata.source.mp4; + } + } + // For non-animated content or if no MP4 URL exists + if (metadata != null && metadata.source != null && metadata.source.u != null) { + // Use the direct URL from source if available + return metadata.source.u; + } + // Fall back to original URL if nothing else works + return url; + } + + public static class MediaMetadata implements Serializable { + private static final long serialVersionUID = 1L; + + public String e; // type (e.g., "Image", "AnimatedImage") + public String m; // mimetype (e.g., "image/gif", "image/jpg") + public String s; // status + public long id; // media id + public boolean animated; // whether media is animated + public String ext; // file extension with dot (e.g., ".gif") + + public Preview[] p; // array of preview images + public Source source; // source object containing URLs + + @Override + public String toString() { + return "MediaMetadata{" + + "e='" + e + '\'' + + ", m='" + m + '\'' + + ", s='" + s + '\'' + + ", id=" + id + + ", animated=" + animated + + ", ext='" + ext + '\'' + + ", p=" + (p != null ? Arrays.toString(p) : "null") + + ", source=" + source + + '}'; + } + + public static class Preview implements Serializable { + private static final long serialVersionUID = 1L; + public int y; // height + public int x; // width + public String u; // preview URL + } + + public static class Source implements Serializable { + private static final long serialVersionUID = 1L; + public int y; // height + public int x; // width + public String u; // direct URL for non-animated + public String gif; // gif URL for animated + public String mp4; // mp4 URL for animated + } } } diff --git a/app/src/main/java/me/ccrama/redditslide/Activities/RedditGalleryPager.java b/app/src/main/java/me/ccrama/redditslide/Activities/RedditGalleryPager.java index 40ae4d973..c795e1289 100644 --- a/app/src/main/java/me/ccrama/redditslide/Activities/RedditGalleryPager.java +++ b/app/src/main/java/me/ccrama/redditslide/Activities/RedditGalleryPager.java @@ -17,6 +17,7 @@ import android.widget.AdapterView; import android.widget.GridView; import android.widget.Toast; +import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -48,6 +49,9 @@ import me.ccrama.redditslide.util.LinkUtil; import me.ccrama.redditslide.util.NetworkUtil; import me.ccrama.redditslide.util.ShareUtil; +import me.ccrama.redditslide.Views.ExoVideoView; +import me.ccrama.redditslide.util.GifUtils; +import me.ccrama.redditslide.util.LogUtil; import static me.ccrama.redditslide.Notifications.ImageDownloadNotificationService.EXTRA_SUBMISSION_TITLE; @@ -251,12 +255,21 @@ public Fragment getItem(int i) { } i--; - Fragment f = new ImageFullNoSubmission(); - Bundle args = new Bundle(); - args.putInt("page", i); - f.setArguments(args); + GalleryImage current = images.get(i); - return f; + if (current.isAnimated()) { + Fragment f = new Gif(); + Bundle args = new Bundle(); + args.putInt("page", i); + f.setArguments(args); + return f; + } else { + Fragment f = new ImageFullNoSubmission(); + Bundle args = new Bundle(); + args.putInt("page", i); + f.setArguments(args); + return f; + } } @Override @@ -268,6 +281,81 @@ public int getCount() { } } + public static class Gif extends Fragment { + private int i = 0; + private View gif; + ViewGroup rootView; + ProgressBar loader; + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (this.isVisible()) { + if (!isVisibleToUser) { + ((ExoVideoView) gif).pause(); + gif.setVisibility(View.GONE); + } + if (isVisibleToUser) { + ((ExoVideoView) gif).play(); + gif.setVisibility(View.VISIBLE); + } + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + rootView = (ViewGroup) inflater.inflate(R.layout.submission_gifcard_album, container, false); + loader = rootView.findViewById(R.id.gifprogress); + gif = rootView.findViewById(R.id.gif); + + gif.setVisibility(View.VISIBLE); + final ExoVideoView v = (ExoVideoView) gif; + v.clearFocus(); + + GalleryImage current = ((RedditGalleryPager) getActivity()).images.get(i); + final String url = current.getImageUrl(); // Now uses metadata-aware URL getter + + LogUtil.i(url); + + new GifUtils.AsyncLoadGif(getActivity(), + rootView.findViewById(R.id.gif), + loader, + null, + null, + false, + true, + rootView.findViewById(R.id.size), + ((RedditGalleryPager)getActivity()).subreddit, + getActivity().getIntent().getStringExtra(EXTRA_SUBMISSION_TITLE) + ).execute(url); + + rootView.findViewById(R.id.more).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ((RedditGalleryPager) getActivity()).showBottomSheetImage(url, true, i); + } + }); + rootView.findViewById(R.id.save).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaView.doOnClick.run(); + } + }); + if (!SettingValues.imageDownloadButton) { + rootView.findViewById(R.id.save).setVisibility(View.INVISIBLE); + } + return rootView; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle bundle = this.getArguments(); + i = bundle.getInt("page", 0); + } + } + public void showBottomSheetImage(final String contentUrl, final boolean isGif, final int index) { diff --git a/app/src/main/java/me/ccrama/redditslide/util/GifUtils.java b/app/src/main/java/me/ccrama/redditslide/util/GifUtils.java index 7712659c9..0349e48ae 100644 --- a/app/src/main/java/me/ccrama/redditslide/util/GifUtils.java +++ b/app/src/main/java/me/ccrama/redditslide/util/GifUtils.java @@ -330,7 +330,7 @@ protected void onPreExecute() { Gson gson; public enum VideoType { - IMGUR, STREAMABLE, GFYCAT, DIRECT, OTHER, VREDDIT; + IMGUR, STREAMABLE, GFYCAT, DIRECT, OTHER, VREDDIT, REDDIT_GALLERY; public boolean shouldLoadPreview() { return this == OTHER; @@ -378,6 +378,9 @@ public static String formatUrl(String s) { */ public static VideoType getVideoType(String url) { String realURL = url.toLowerCase(Locale.ENGLISH); + if (realURL.contains("i.redd.it")) { + return VideoType.REDDIT_GALLERY; + } if (realURL.contains("v.redd.it")) { return VideoType.VREDDIT; } @@ -392,23 +395,23 @@ public static VideoType getVideoType(String url) { return VideoType.OTHER; } - public static Map makeHeaderMap(String domain){ - Map map = new HashMap<>(); - map.put("Host", domain); - map.put("Sec-Fetch-Dest", "empty"); - map.put("Sec-Fetch-Mode", "cors"); - map.put("Sec-Fetch-Site", "same-origin"); - map.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:107.0) Gecko/20100101 Firefox/107.0"); - return map; - } - - /** - * Get an API response for a given host and gfy name - * - * @param host the host to send the req to - * @param name the name of the gfy - * @return the result - */ + public static Map makeHeaderMap(String domain){ + Map map = new HashMap<>(); + map.put("Host", domain); + map.put("Sec-Fetch-Dest", "empty"); + map.put("Sec-Fetch-Mode", "cors"); + map.put("Sec-Fetch-Site", "same-origin"); + map.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; rv:107.0) Gecko/20100101 Firefox/107.0"); + return map; + } + + /** + * Get an API response for a given host and gfy name + * + * @param host the host to send the req to + * @param name the name of the gfy + * @return the result + */ JsonObject getApiResponse(String host, String name){ String domain = "api." + host + ".com"; String gfycatUrl = "https://" + domain + "/v1/gfycats" + name; @@ -416,12 +419,12 @@ JsonObject getApiResponse(String host, String name){ return HttpUtil.getJsonObject(client, gson, gfycatUrl, makeHeaderMap(domain)); } - /** - * Get the correct mp4/mobile url from a given result JsonObject - * - * @param result the result to check - * @return the video url - */ + /** + * Get the correct mp4/mobile url from a given result JsonObject + * + * @param result the result to check + * @return the video url + */ String getUrlFromApi(JsonObject result){ if (!SettingValues.hqgif && result.getAsJsonObject("gfyItem").has("mobileUrl")) { return result.getAsJsonObject("gfyItem").get("mobileUrl").getAsString(); @@ -611,6 +614,8 @@ protected Uri doInBackground(String... sub) { + "]"); } break; + case REDDIT_GALLERY: + return Uri.parse(url); case DIRECT: case IMGUR: try { diff --git a/app/src/main/java/me/ccrama/redditslide/util/JsonUtil.java b/app/src/main/java/me/ccrama/redditslide/util/JsonUtil.java index df29ec9f8..6e0b7aa22 100644 --- a/app/src/main/java/me/ccrama/redditslide/util/JsonUtil.java +++ b/app/src/main/java/me/ccrama/redditslide/util/JsonUtil.java @@ -1,8 +1,8 @@ package me.ccrama.redditslide.util; import com.fasterxml.jackson.databind.JsonNode; - import java.util.ArrayList; +import org.apache.commons.text.StringEscapeUtils; import me.ccrama.redditslide.Activities.GalleryImage; @@ -12,13 +12,32 @@ public class JsonUtil { public static void getGalleryData(final JsonNode data, final ArrayList urls) { for (JsonNode identifier : data.get("gallery_data").get("items")) { - if (data.has("media_metadata") && data.get( - "media_metadata") - .has(identifier.get("media_id").asText()) - ) { - urls.add(new GalleryImage(data.get("media_metadata") - .get(identifier.get("media_id").asText()) - .get("s"))); + String mediaId = identifier.get("media_id").asText(); + if (data.has("media_metadata") && data.get("media_metadata").has(mediaId)) { + JsonNode mediaNode = data.get("media_metadata").get(mediaId); + GalleryImage image = new GalleryImage(mediaNode.get("s")); + + // Set metadata + image.mediaId = mediaId; + image.metadata = new GalleryImage.MediaMetadata(); + image.metadata.e = mediaNode.get("e").asText(); + image.metadata.m = mediaNode.get("m").asText(); + image.metadata.animated = "AnimatedImage".equals(image.metadata.e); + + // Set source data + image.metadata.source = new GalleryImage.MediaMetadata.Source(); + JsonNode s = mediaNode.get("s"); + if (s.has("mp4")) { + image.metadata.source.mp4 = StringEscapeUtils.unescapeHtml4(s.get("mp4").asText()); + } + if (s.has("gif")) { + image.metadata.source.gif = StringEscapeUtils.unescapeHtml4(s.get("gif").asText()); + } + if (s.has("u")) { + image.metadata.source.u = StringEscapeUtils.unescapeHtml4(s.get("u").asText()); + } + + urls.add(image); } } }