* Open/Close Gallery transitions * Include postId in file info & disable opening the gallery in some cases * Add progress bar component * Transition to gallery based on platform * Improve getting the filename for non attachments * Patch RNFetchBlob TS definition * Add gallery types * fix unit tests for message and post attachment * add getLocalPath method * Add react-native-share dependency * Re-style gallery footer * Refactor Gallery screen * Double tap zoom in/out and translate * Make android activity "transparent" * Do not animate to height on dismissing gallery * Open gallery for file uploads * Fix borderRadius for gallery action button * Use progress bar for file uploads * Replace progress bar for file attachment document * Upgrade RNN to 7.1.0 to fix share elements transitions * Fix Gallery unit tests * translate down when popping screen on iOS * Swipe, Pan & Tap fixes * fix gallery footer avatar eslint * Fix gallery snapshot tests * Use CompassIcon in Gallery * Feedback from UX review * Fix gallery UI for other file types * Set other file type gallery button to 48pt
168 lines
4.8 KiB
JavaScript
168 lines
4.8 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {Dimensions, Keyboard, Platform} from 'react-native';
|
|
|
|
import {goToScreen} from '@actions/navigation';
|
|
import {DeviceTypes} from '@constants';
|
|
import {
|
|
IMAGE_MAX_HEIGHT,
|
|
IMAGE_MIN_DIMENSION,
|
|
MAX_GIF_SIZE,
|
|
VIEWPORT_IMAGE_OFFSET,
|
|
VIEWPORT_IMAGE_REPLY_OFFSET,
|
|
} from '@constants/image';
|
|
import {isImage} from './file';
|
|
|
|
export const calculateDimensions = (height, width, viewPortWidth = 0, viewPortHeight = 0) => {
|
|
if (!height || !width) {
|
|
return {
|
|
height: null,
|
|
width: null,
|
|
};
|
|
}
|
|
|
|
const ratio = height / width;
|
|
const heightRatio = width / height;
|
|
|
|
let imageWidth = width;
|
|
let imageHeight = height;
|
|
|
|
if (width > viewPortWidth) {
|
|
imageWidth = viewPortWidth;
|
|
imageHeight = imageWidth * ratio;
|
|
} else if (width < IMAGE_MIN_DIMENSION) {
|
|
imageWidth = IMAGE_MIN_DIMENSION;
|
|
imageHeight = imageWidth * ratio;
|
|
}
|
|
|
|
if (
|
|
(
|
|
imageHeight > IMAGE_MAX_HEIGHT ||
|
|
(viewPortHeight && imageHeight > viewPortHeight)
|
|
) && viewPortHeight <= IMAGE_MAX_HEIGHT
|
|
) {
|
|
imageHeight = viewPortHeight || IMAGE_MAX_HEIGHT;
|
|
imageWidth = imageHeight * heightRatio;
|
|
} else if (
|
|
imageHeight < IMAGE_MIN_DIMENSION &&
|
|
IMAGE_MIN_DIMENSION * heightRatio <= viewPortWidth
|
|
) {
|
|
imageHeight = IMAGE_MIN_DIMENSION;
|
|
imageWidth = imageHeight * heightRatio;
|
|
} else if (viewPortHeight && imageHeight > viewPortHeight) {
|
|
imageHeight = viewPortHeight;
|
|
imageWidth = imageHeight * heightRatio;
|
|
}
|
|
|
|
return {
|
|
height: imageHeight,
|
|
width: imageWidth,
|
|
};
|
|
};
|
|
|
|
export function getViewPortWidth(isReplyPost, permanentSidebar = false) {
|
|
const {width, height} = Dimensions.get('window');
|
|
let portraitPostWidth = Math.min(width, height) - VIEWPORT_IMAGE_OFFSET;
|
|
|
|
if (permanentSidebar) {
|
|
portraitPostWidth -= DeviceTypes.TABLET_WIDTH;
|
|
}
|
|
|
|
if (isReplyPost) {
|
|
portraitPostWidth -= VIEWPORT_IMAGE_REPLY_OFFSET;
|
|
}
|
|
|
|
return portraitPostWidth;
|
|
}
|
|
|
|
export function openGalleryAtIndex(index, files) {
|
|
Keyboard.dismiss();
|
|
requestAnimationFrame(() => {
|
|
const screen = 'Gallery';
|
|
const passProps = {
|
|
index,
|
|
files,
|
|
};
|
|
const windowHeight = Dimensions.get('window').height;
|
|
const sharedElementTransitions = [];
|
|
const contentPush = {};
|
|
const contentPop = {};
|
|
const file = files[index];
|
|
|
|
if (isImage(file)) {
|
|
sharedElementTransitions.push({
|
|
fromId: `image-${file.id}`,
|
|
toId: `gallery-${file.id}`,
|
|
interpolation: 'overshoot',
|
|
});
|
|
} else {
|
|
contentPush.y = {
|
|
from: windowHeight,
|
|
to: 0,
|
|
duration: 300,
|
|
interpolation: 'decelerate',
|
|
};
|
|
|
|
if (Platform.OS === 'ios') {
|
|
contentPop.translationY = {
|
|
from: 0,
|
|
to: windowHeight,
|
|
duration: 300,
|
|
};
|
|
} else {
|
|
contentPop.y = {
|
|
from: 0,
|
|
to: windowHeight,
|
|
duration: 300,
|
|
};
|
|
contentPop.alpha = {
|
|
from: 1,
|
|
to: 0,
|
|
duration: 100,
|
|
};
|
|
}
|
|
}
|
|
|
|
const options = {
|
|
layout: {
|
|
backgroundColor: '#000',
|
|
componentBackgroundColor: '#000',
|
|
},
|
|
topBar: {
|
|
background: {
|
|
color: '#000',
|
|
},
|
|
visible: Platform.OS === 'android',
|
|
},
|
|
animations: {
|
|
push: {
|
|
waitForRender: true,
|
|
sharedElementTransitions,
|
|
...Platform.select({ios: {
|
|
content: contentPush,
|
|
}}),
|
|
},
|
|
pop: {
|
|
content: contentPop,
|
|
},
|
|
},
|
|
};
|
|
|
|
goToScreen(screen, '', passProps, options);
|
|
});
|
|
}
|
|
|
|
// isGifTooLarge returns true if we think that the GIF may cause the device to run out of memory when rendered
|
|
// based on the image's dimensions and frame count.
|
|
export function isGifTooLarge(imageMetadata) {
|
|
if (imageMetadata?.format !== 'gif') {
|
|
// Not a gif or from an older server that doesn't count frames
|
|
return false;
|
|
}
|
|
|
|
const {frame_count: frameCount, height, width} = imageMetadata;
|
|
|
|
// Try to estimate the in-memory size of the gif to prevent the device out of memory
|
|
return width * height * frameCount > MAX_GIF_SIZE;
|
|
}
|