Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout mode #1262

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*******************************************************************************
* This file is part of RedReader.
*
* RedReader is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* RedReader is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with RedReader. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

package org.quantumbadger.redreader.common;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.LayoutInflater;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import org.quantumbadger.redreader.R;
import org.quantumbadger.redreader.activities.BaseActivity;
import org.quantumbadger.redreader.account.RedditAccountManager;
import org.quantumbadger.redreader.cache.CacheManager;
import org.quantumbadger.redreader.cache.CacheRequest;
import org.quantumbadger.redreader.cache.CacheRequestCallbacks;
import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyIfNotCached;
import org.quantumbadger.redreader.common.datastream.SeekableInputStream;
import org.quantumbadger.redreader.common.time.TimestampUTC;
import org.quantumbadger.redreader.reddit.prepared.RedditParsedPost;
import org.quantumbadger.redreader.reddit.prepared.RedditPreparedPost;
import org.quantumbadger.redreader.views.RRAnimationShrinkHeight;
import org.quantumbadger.redreader.views.liststatus.ErrorView;

public class ImagePreviewUtils {

private static final String TAG = "ImagePreviewUtils";
private static final String PROMPT_PREF_KEY = "inline_image_prompt_accepted";
private static final AtomicInteger sInlinePreviewsShownThisSession = new AtomicInteger(0);

public interface ImagePreviewListener {
void setImageBitmap(Bitmap bitmap);
void setLoadingSpinnerVisible(boolean visible);
void setOuterViewVisible(boolean visible);
void setPlayOverlayVisible(boolean visible);
void setPreviewDimensions(String ratio);
LinearLayout getFooterView();
BaseActivity getActivity();
boolean isUsageIdValid(int usageId);
void addErrorView(ErrorView errorView);
void setErrorViewLayout(View errorView);
}

public static void showPrefPrompt(final ImagePreviewListener listener) {
final BaseActivity activity = listener.getActivity();
final SharedPrefsWrapper sharedPrefs = General.getSharedPrefs(activity);

LayoutInflater.from(activity).inflate(
R.layout.inline_images_question_view,
listener.getFooterView(),
true);

final FrameLayout promptView = listener.getFooterView()
.findViewById(R.id.inline_images_prompt_root);
final Button keepShowing = listener.getFooterView()
.findViewById(R.id.inline_preview_prompt_keep_showing_button);
final Button turnOff = listener.getFooterView()
.findViewById(R.id.inline_preview_prompt_turn_off_button);

keepShowing.setOnClickListener(v -> {
new RRAnimationShrinkHeight(promptView).start();
sharedPrefs.edit()
.putBoolean(PROMPT_PREF_KEY, true)
.apply();
});

turnOff.setOnClickListener(v -> {
final String prefPreview = activity.getApplicationContext()
.getString(R.string.pref_images_inline_image_previews_key);
sharedPrefs.edit()
.putBoolean(PROMPT_PREF_KEY, true)
.putString(prefPreview, "never")
.apply();
});
}

public static void downloadInlinePreview(
@NonNull final BaseActivity activity,
@NonNull final RedditPreparedPost post,
@NonNull final ImagePreviewListener listener,
final int usageId) {

final Rect windowVisibleDisplayFrame = DisplayUtils.getWindowVisibleDisplayFrame(activity);

final int screenWidth = Math.min(1080, Math.max(720, windowVisibleDisplayFrame.width()));
final int screenHeight = Math.min(2000, Math.max(400, windowVisibleDisplayFrame.height()));

final RedditParsedPost.ImagePreviewDetails preview = post.src.getPreview(screenWidth, 0);

if(preview == null || preview.width < 10 || preview.height < 10) {
listener.setOuterViewVisible(false);
listener.setLoadingSpinnerVisible(false);
return;
}

final int boundedImageHeight = Math.min(
(screenHeight * 2) / 3,
(int)(((long)preview.height * screenWidth) / preview.width));

listener.setPreviewDimensions(screenWidth + ":" + boundedImageHeight);
listener.setOuterViewVisible(true);
listener.setLoadingSpinnerVisible(true);

CacheManager.getInstance(activity).makeRequest(new CacheRequest(
preview.url,
RedditAccountManager.getAnon(),
null,
new Priority(Constants.Priority.INLINE_IMAGE_PREVIEW),
DownloadStrategyIfNotCached.INSTANCE,
Constants.FileType.INLINE_IMAGE_PREVIEW,
CacheRequest.DownloadQueueType.IMMEDIATE,
activity,
new CacheRequestCallbacks() {
@Override
public void onDataStreamComplete(
@NonNull final GenericFactory<SeekableInputStream, IOException> stream,
final TimestampUTC timestamp,
@NonNull final UUID session,
final boolean fromCache,
@Nullable final String mimetype) {

if(!listener.isUsageIdValid(usageId)) {
return;
}

try(InputStream is = stream.create()) {
final Bitmap data = BitmapFactory.decodeStream(is);

if(data == null) {
throw new IOException("Failed to decode bitmap");
}

// Avoid a crash on badly behaving Android ROMs (where the ImageView
// crashes if an image is too big)
// Should never happen as we limit the preview size to 3000x3000
if(data.getByteCount() > 50 * 1024 * 1024) {
throw new RuntimeException("Image was too large: "
+ data.getByteCount()
+ ", preview URL was "
+ preview.url
+ " and post was "
+ post.src.getIdAndType());
}

final boolean alreadyAcceptedPrompt = General.getSharedPrefs(activity)
.getBoolean(PROMPT_PREF_KEY, false);

final int totalPreviewsShown
= sInlinePreviewsShownThisSession.incrementAndGet();

final boolean isVideoPreview = post.isVideoPreview();

AndroidCommon.runOnUiThread(() -> {
listener.setImageBitmap(data);
listener.setLoadingSpinnerVisible(false);

if(isVideoPreview) {
listener.setPlayOverlayVisible(true);
}

// Show every 8 previews, starting at the second one
if(totalPreviewsShown % 8 == 2 && !alreadyAcceptedPrompt) {
showPrefPrompt(listener);
}
});

} catch(final Throwable t) {
onFailure(General.getGeneralErrorForFailure(
activity,
CacheRequest.RequestFailureType.CONNECTION,
t,
null,
preview.url,
Optional.empty()));
}
}

@Override
public void onFailure(@NonNull final RRError error) {
Log.e(TAG, "Failed to download image preview: " + error, error.t);

if(!listener.isUsageIdValid(usageId)) {
return;
}

AndroidCommon.runOnUiThread(() -> {
listener.setLoadingSpinnerVisible(false);
listener.setOuterViewVisible(false);

final ErrorView errorView = new ErrorView(
activity,
error);

listener.addErrorView(errorView);
listener.setErrorViewLayout(errorView);
});
}
}
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public final class PrefsUtility {
private static SharedPrefsWrapper sharedPrefs;
private static Resources mRes;

public static String getLayoutMode() {
return sharedPrefs.getString("pref_layout_mode", "thumbnails");
}

private static String getPrefKey(@StringRes final int prefKey) {
return mRes.getString(prefKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with RedReader. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

package org.quantumbadger.redreader.reddit.prepared

import androidx.appcompat.app.AppCompatActivity
Expand Down Expand Up @@ -141,9 +142,25 @@ class RedditParsedPost(
@JvmField val height: Int
)

fun getPreview(minWidth: Int, minHeight: Int) = src.preview?.images?.get(0)?.run {
getPreviewInternal(this, minWidth, minHeight)
}

val isGallery = src.gallery_data?.items?.firstOrNull()?.ok()?.media_id != null

fun getPreview(minWidth: Int, minHeight: Int): ImagePreviewDetails? {
if (isGallery) {
val firstItem = src.gallery_data?.items?.firstOrNull()?.ok()?.media_id ?: return null
val metadata = src.media_metadata?.get(firstItem)?.ok() ?: return null

return ImagePreviewDetails(
UriString(metadata.s.u?.decoded ?: return null),
metadata.s.x.toInt(),
metadata.s.y.toInt()
)
}

return src.preview?.images?.get(0)?.run {
getPreviewInternal(this, minWidth, minHeight)
}
}

fun getPreviewMP4(minWidth: Int, minHeight: Int)
= src.preview?.images?.get(0)?.variants?.mp4?.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public final class RedditPreparedPost implements RedditChangeDataManager.Listene
public final boolean canModerate;
public final boolean hasThumbnail;
public final boolean mIsProbablyAnImage;
public final boolean isGallery;

private final boolean mShowInlinePreviews;

Expand Down Expand Up @@ -109,6 +110,7 @@ public RedditPreparedPost(
isArchived = post.isArchived();
isLocked = post.isLocked();
canModerate = post.getCanModerate();
isGallery = post.isGallery();

mIsProbablyAnImage = LinkHandler.isProbablyAnImage(post.getUrl());

Expand All @@ -127,18 +129,27 @@ public RedditPreparedPost(
}

public boolean shouldShowInlinePreview() {
final String layoutMode = PrefsUtility.getLayoutMode();
final boolean alwaysPreviewMode = layoutMode.equals("always_preview")
|| layoutMode.equals("card");

return mShowInlinePreviews && (src.isPreviewEnabled()
|| "gfycat.com".equals(src.getDomain())
|| "i.imgur.com".equals(src.getDomain())
|| "streamable.com".equals(src.getDomain())
|| "i.redd.it".equals(src.getDomain())
|| "v.redd.it".equals(src.getDomain()));
|| "v.redd.it".equals(src.getDomain())
|| (isGallery && alwaysPreviewMode));
}

public boolean isVideoPreview() {
return src.isVideoPreview();
}

public boolean isGalleryPreview() {
return isGallery;
}

public void performAction(final BaseActivity activity, final RedditPostActions.Action action) {
RedditPostActions.INSTANCE.onActionMenuItemSelected(this, activity, action);
}
Expand Down
Loading
Loading