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

Feature: Use attachment gallery everywhere #31308

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
32c2a12
move ImageTransformer component
Nov 13, 2023
52b5d14
feat: extract ImageLightbox to separate component and use everywhere
Nov 14, 2023
dff2a96
fix: image not loading initially
Nov 14, 2023
2ef7f8b
fix: simplify
Nov 14, 2023
f72fdc9
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Nov 15, 2023
1fd70aa
fix: restructure props and unify zoom scale
Nov 15, 2023
b8d29cc
simplify bounce
Nov 15, 2023
bbf43b8
move bounce variables to constants
Nov 15, 2023
cbb6ad7
further improve ImageLightbox component
Nov 15, 2023
a1953e8
fix: restructure ImageTransformer and ImageLightbox components
Nov 15, 2023
bcc8539
fix: move GestureHandlerRootView to AttachmentModal
Nov 15, 2023
479dbbf
fix: improve zoom range on Android
Nov 15, 2023
b24bb32
Merge branch 'main' into feat/use-attachment-gallery-everywhere
chrispader Nov 21, 2023
70c2dd8
fix: update react-native-fast-image patches
chrispader Nov 21, 2023
8bd7d70
update comments
chrispader Nov 21, 2023
a40c790
add comments for prop types
chrispader Nov 21, 2023
d3d65ae
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Nov 23, 2023
e5c3203
start in loading state
Nov 23, 2023
eb7d9ba
Update src/components/Lightbox.js
chrispader Nov 29, 2023
e2fcb4d
simplify patch
Nov 23, 2023
3694860
fix: review comments
Nov 29, 2023
f202868
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Nov 30, 2023
84e8467
simplify multigesture content wrapper
Nov 30, 2023
3fb8957
improve zoomRange props and remove custom zoom ranges
Nov 30, 2023
270a3b3
fix: initial page wrong in carousel
Nov 30, 2023
d9d50bd
add optimizations for lightbox (only render fallback when in carousel…
Nov 30, 2023
931651d
fix: initial loading problem
Nov 30, 2023
5a5fd27
pass isSingleItem
Nov 30, 2023
735f237
add dependency
Nov 30, 2023
7ce9bc1
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Nov 30, 2023
0e2f27e
fix: initial index
Nov 30, 2023
7c5ba50
remove log
Nov 30, 2023
4912e0a
Merge branch '@chrispader/fix-status-bar-safe-area' into feat/use-att…
Dec 1, 2023
b241680
simplify lightbox
Dec 1, 2023
9251eea
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Dec 2, 2023
f74fd79
fix: Lightbox
Dec 2, 2023
307d075
simplify MultiGestureCanvas
Dec 2, 2023
9eced46
fix and simplify
Dec 2, 2023
7ea7ea0
remove comments
Dec 2, 2023
c09355c
impl: paging and multiple concurrent lightboxes
Dec 3, 2023
7b21a82
add comment
Dec 3, 2023
9bb72d6
fix: lint
Dec 3, 2023
e63226b
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Dec 8, 2023
a7fce89
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Dec 10, 2023
1e4c074
Merge branch 'main' into feat/use-attachment-gallery-everywhere
chrispader Dec 13, 2023
aa39a23
fix: onLoadEnd not triggered in "Send attachments"
chrispader Dec 13, 2023
cf71e1a
Merge branch 'main' into feat/use-attachment-gallery-everywhere
Dec 14, 2023
163468f
improve imports and exports
chrispader Dec 14, 2023
226b3d5
fix: more stuff
chrispader Dec 14, 2023
5531728
set minimum double tap scale
chrispader Dec 14, 2023
3ca5359
Update src/components/MultiGestureCanvas/propTypes.js
chrispader Dec 14, 2023
af087f6
Update src/components/MultiGestureCanvas/propTypes.js
chrispader Dec 14, 2023
89f5634
Update src/components/MultiGestureCanvas/propTypes.js
chrispader Dec 14, 2023
41bfc91
Update src/components/MultiGestureCanvas/propTypes.js
chrispader Dec 14, 2023
5cc91a6
Update src/components/MultiGestureCanvas/propTypes.js
chrispader Dec 14, 2023
27ab60c
Update src/components/MultiGestureCanvas/index.js
chrispader Dec 14, 2023
31b6455
remove defaultZoomRange
chrispader Dec 14, 2023
db5e3d4
fix: import
chrispader Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java
index 1339f5c..9dfec0c 100644
--- a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java
+++ b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java
@@ -176,7 +176,8 @@ class FastImageViewWithUrl extends AppCompatImageView {
.apply(FastImageViewConverter
.getOptions(context, imageSource, mSource)
.placeholder(mDefaultSource) // show until loaded
- .fallback(mDefaultSource)); // null will not be treated as error
+ .fallback(mDefaultSource))
+ .transform(new ResizeTransformation());

if (key != null)
builder.listener(new FastImageRequestListener(key));
diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java
new file mode 100644
index 0000000..1daa227
--- /dev/null
+++ b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java
@@ -0,0 +1,41 @@
+package com.dylanvann.fastimage;
+
+ import android.content.Context;
+ import android.graphics.Bitmap;
+
+ import androidx.annotation.NonNull;
+
+ import com.bumptech.glide.load.Transformation;
+ import com.bumptech.glide.load.engine.Resource;
+ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+ import com.bumptech.glide.load.resource.bitmap.BitmapResource;
+
+ import java.security.MessageDigest;
+
+ public class ResizeTransformation implements Transformation<Bitmap> {
+
+ private final double MAX_BYTES = 25000000.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an initial idea, not a blocker. Perhaps in the future, this value can be adjusted based on the system properties, similar to the values in canvas exception. This way, new devices might be able to see clearer images. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agree. Definitely something for a future PR. I think @ArekChr tested this value very thoroughly, so it should be high enough to display high-resolution images on high-end displays as well.

Could we already create an (internal) issue for this, so i can jump onto this at some point in the future? @pecanoro @dylanexpensify

+
+ @NonNull
+ @Override
+ public Resource<Bitmap> transform(@NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) {
+ Bitmap toTransform = resource.get();
+
+ if (toTransform.getByteCount() > MAX_BYTES) {
+ double scaleFactor = Math.sqrt(MAX_BYTES / (double) toTransform.getByteCount());
+ int newHeight = (int) (outHeight * scaleFactor);
+ int newWidth = (int) (outWidth * scaleFactor);
+
+ BitmapPool pool = GlideApp.get(context).getBitmapPool();
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(toTransform, newWidth, newHeight, true);
+ return BitmapResource.obtain(scaledBitmap, pool);
+ }
+
+ return resource;
+ }
+
+ @Override
+ public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+ messageDigest.update(("ResizeTransformation").getBytes());
+ }
+ }
\ No newline at end of file
139 changes: 71 additions & 68 deletions src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {Animated, Keyboard, View} from 'react-native';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -425,78 +426,80 @@ function AttachmentModal(props) {
}}
propagateSwipe
>
{props.isSmallScreenWidth && <HeaderGap />}
<HeaderWithBackButton
title={headerTitle}
shouldShowBorderBottom
shouldShowDownloadButton={shouldShowDownloadButton}
onDownloadButtonPress={() => downloadAttachment(source)}
shouldShowCloseButton={!props.isSmallScreenWidth}
shouldShowBackButton={props.isSmallScreenWidth}
onBackButtonPress={closeModal}
onCloseButtonPress={closeModal}
shouldShowThreeDotsButton={shouldShowThreeDotsButton}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)}
threeDotsMenuItems={threeDotsMenuItems}
shouldOverlay
/>
<View style={styles.imageModalImageCenterContainer}>
{!_.isEmpty(props.report) && !props.isReceiptAttachment ? (
<AttachmentCarousel
report={props.report}
onNavigate={onNavigate}
source={props.source}
onClose={closeModal}
onToggleKeyboard={updateConfirmButtonVisibility}
setDownloadButtonVisibility={setDownloadButtonVisibility}
/>
) : (
Boolean(sourceForAttachmentView) &&
shouldLoadAttachment && (
<AttachmentView
containerStyles={[styles.mh5]}
source={sourceForAttachmentView}
isAuthTokenRequired={isAuthTokenRequired}
file={file}
<GestureHandlerRootView style={styles.flex1}>
{props.isSmallScreenWidth && <HeaderGap />}
<HeaderWithBackButton
title={headerTitle}
shouldShowBorderBottom
shouldShowDownloadButton={shouldShowDownloadButton}
onDownloadButtonPress={() => downloadAttachment(source)}
shouldShowCloseButton={!props.isSmallScreenWidth}
shouldShowBackButton={props.isSmallScreenWidth}
onBackButtonPress={closeModal}
onCloseButtonPress={closeModal}
shouldShowThreeDotsButton={shouldShowThreeDotsButton}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)}
threeDotsMenuItems={threeDotsMenuItems}
shouldOverlay
/>
<View style={styles.imageModalImageCenterContainer}>
{!_.isEmpty(props.report) && !props.isReceiptAttachment ? (
<AttachmentCarousel
report={props.report}
onNavigate={onNavigate}
source={props.source}
onClose={closeModal}
onToggleKeyboard={updateConfirmButtonVisibility}
isWorkspaceAvatar={props.isWorkspaceAvatar}
fallbackSource={props.fallbackSource}
isUsedInAttachmentModal
transactionID={props.transaction.transactionID}
setDownloadButtonVisibility={setDownloadButtonVisibility}
/>
)
)}
</View>
{/* If we have an onConfirm method show a confirmation button */}
{Boolean(props.onConfirm) && (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
<Animated.View style={[StyleUtils.fade(confirmButtonFadeAnimation), safeAreaPaddingBottomStyle]}>
<Button
success
style={[styles.buttonConfirm, props.isSmallScreenWidth ? {} : styles.attachmentButtonBigScreen]}
textStyles={[styles.buttonConfirmText]}
text={translate('common.send')}
onPress={submitAndClose}
disabled={isConfirmButtonDisabled}
pressOnEnter
) : (
Boolean(sourceForAttachmentView) &&
shouldLoadAttachment && (
<AttachmentView
containerStyles={[styles.mh5]}
source={sourceForAttachmentView}
isAuthTokenRequired={isAuthTokenRequired}
file={file}
onToggleKeyboard={updateConfirmButtonVisibility}
isWorkspaceAvatar={props.isWorkspaceAvatar}
fallbackSource={props.fallbackSource}
isUsedInAttachmentModal
transactionID={props.transaction.transactionID}
/>
</Animated.View>
)
)}
</SafeAreaConsumer>
)}
{props.isReceiptAttachment && (
<ConfirmModal
title={translate('receipt.deleteReceipt')}
isVisible={isDeleteReceiptConfirmModalVisible}
onConfirm={deleteAndCloseModal}
onCancel={closeConfirmModal}
prompt={translate('receipt.deleteConfirmation')}
confirmText={translate('common.delete')}
cancelText={translate('common.cancel')}
danger
/>
)}
</View>
{/* If we have an onConfirm method show a confirmation button */}
{Boolean(props.onConfirm) && (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
<Animated.View style={[StyleUtils.fade(confirmButtonFadeAnimation), safeAreaPaddingBottomStyle]}>
<Button
success
style={[styles.buttonConfirm, props.isSmallScreenWidth ? {} : styles.attachmentButtonBigScreen]}
textStyles={[styles.buttonConfirmText]}
text={translate('common.send')}
onPress={submitAndClose}
disabled={isConfirmButtonDisabled}
pressOnEnter
/>
</Animated.View>
)}
</SafeAreaConsumer>
)}
{props.isReceiptAttachment && (
<ConfirmModal
title={translate('receipt.deleteReceipt')}
isVisible={isDeleteReceiptConfirmModalVisible}
onConfirm={deleteAndCloseModal}
onCancel={closeConfirmModal}
prompt={translate('receipt.deleteConfirmation')}
confirmText={translate('common.delete')}
cancelText={translate('common.cancel')}
danger
/>
)}
</GestureHandlerRootView>
</Modal>
{!props.isReceiptAttachment && (
<ConfirmModal
Expand Down
18 changes: 13 additions & 5 deletions src/components/Attachments/AttachmentCarousel/CarouselItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ const propTypes = {
transactionID: PropTypes.string,
}).isRequired,

/** Whether the attachment is currently being viewed in the carousel */
isFocused: PropTypes.bool.isRequired,
/** Whether there is only one element in the attachment carousel */
isSingleItem: PropTypes.bool.isRequired,

/** The index of the carousel item */
index: PropTypes.number.isRequired,

/** The index of the currently active carousel item */
activeIndex: PropTypes.number.isRequired,

/** onPress callback */
onPress: PropTypes.func,
Expand All @@ -48,7 +54,7 @@ const defaultProps = {
onPress: undefined,
};

function CarouselItem({item, isFocused, onPress}) {
function CarouselItem({item, index, activeIndex, isSingleItem, onPress}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isAttachmentHidden} = useContext(ReportAttachmentsContext);
Expand Down Expand Up @@ -98,9 +104,11 @@ function CarouselItem({item, isFocused, onPress}) {
source={item.source}
file={item.file}
isAuthTokenRequired={item.isAuthTokenRequired}
isFocused={isFocused}
onPress={onPress}
isUsedInCarousel
isSingleCarouselItem={isSingleItem}
carouselItemIndex={index}
carouselActiveItemIndex={activeIndex}
onPress={onPress}
transactionID={item.transactionID}
/>
</View>
Expand Down
Loading
Loading