Replace image modal by image gallery screen

This improves performance and allows multi-images view
This commit is contained in:
Arnaud Vergnet 2020-08-09 20:41:49 +02:00
parent 6e7d891d5c
commit 6ec87821e8
9 changed files with 258 additions and 58 deletions

42
package-lock.json generated
View file

@ -10146,6 +10146,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-mixin": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/react-mixin/-/react-mixin-3.1.1.tgz",
"integrity": "sha512-z9fZ0aCRDjlgxLdMeWkJ9TwhmVLhQ09r8RFpin/cEPA2T6jsb7YHNWcIe0Oii+hhJNyMymdy91CSya5mRkuCkg==",
"requires": {
"object-assign": "^4.0.1",
"smart-mixin": "^2.0.0"
}
},
"react-native": {
"version": "0.63.2",
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.63.2.tgz",
@ -10383,11 +10392,34 @@
}
}
},
"react-native-image-gallery": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/react-native-image-gallery/-/react-native-image-gallery-2.1.5.tgz",
"integrity": "sha512-xC7nuPu4GUH0da6byofQ10LjtqlKj+VaLc0NHBJmeMHVvdvmRvFEO6UOq0Q0m/ePx3OQiPTNwGbf5BSPJEKa0w==",
"requires": {
"prop-types": "^15.6.0",
"react-mixin": "^3.0.5",
"react-timer-mixin": "^0.13.3"
}
},
"react-native-image-modal": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/react-native-image-modal/-/react-native-image-modal-1.0.9.tgz",
"integrity": "sha512-d01lx82CgC2s8zzpmKAbSAK/rABFl8BhKHSrp6GLqColNuUHaNuDIuu67TdqzHsY9pT4OHY0rduWyvrmRiSrLw=="
},
"react-native-image-pan-zoom": {
"version": "2.1.12",
"resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz",
"integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q=="
},
"react-native-image-zoom-viewer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz",
"integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==",
"requires": {
"react-native-image-pan-zoom": "^2.1.12"
}
},
"react-native-iphone-x-helper": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.1.tgz",
@ -10632,6 +10664,11 @@
"scheduler": "^0.19.1"
}
},
"react-timer-mixin": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.4.tgz",
"integrity": "sha512-4+ow23tp/Tv7hBM5Az5/Be/eKKF7DIvJ09voz5LyHGQaqqz9WV8YMs31eFvcYQs7d451LSg7kDJV70XYN/Ug/Q=="
},
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
@ -11227,6 +11264,11 @@
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
},
"smart-mixin": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/smart-mixin/-/smart-mixin-2.0.0.tgz",
"integrity": "sha1-o0oQVeMqdbMNK048oyPcmctT9Dc="
},
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",

View file

@ -39,7 +39,7 @@
"react-native-camera": "^3.35.0",
"react-native-collapsible": "^1.5.3",
"react-native-gesture-handler": "^1.7.0",
"react-native-image-modal": "^1.0.9",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-keychain": "^6.1.1",
"react-native-linear-gradient": "^2.5.6",
"react-native-localize": "^1.4.1",

View file

@ -5,11 +5,11 @@ import {Button, Card, Text, TouchableRipple} from 'react-native-paper';
import {Image, View} from 'react-native';
import Autolink from 'react-native-autolink';
import i18n from 'i18n-js';
import ImageModal from 'react-native-image-modal';
import {StackNavigationProp} from '@react-navigation/stack';
import type {FeedItemType} from '../../screens/Home/HomeScreen';
import NewsSourcesConstants from '../../constants/NewsSourcesConstants';
import type {NewsSourceType} from '../../constants/NewsSourcesConstants';
import ImageGalleryButton from '../Media/ImageGalleryButton';
type PropsType = {
navigation: StackNavigationProp,
@ -82,16 +82,13 @@ class FeedItem extends React.Component<PropsType> {
/>
{hasImage ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
<ImageModal
resizeMode="contain"
imageBackgroundColor="#000"
<ImageGalleryButton
navigation={props.navigation}
images={[{url: item.image}]}
style={{
width: imageSize,
height: imageSize,
}}
source={{
uri: item.image,
}}
/>
</View>
) : null}

View file

@ -0,0 +1,40 @@
// @flow
import * as React from 'react';
import {TouchableRipple, withTheme} from 'react-native-paper';
import {StackNavigationProp} from '@react-navigation/stack';
import {Image} from 'react-native-animatable';
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
import type {CustomThemeType} from '../../managers/ThemeManager';
type PropsType = {
navigation: StackNavigationProp,
images: Array<{url: string}>,
theme: CustomThemeType,
style: ViewStyleProp,
};
class ImageGalleryButton extends React.Component<PropsType> {
onPress = () => {
const {navigation, images} = this.props;
navigation.navigate('gallery', {images});
};
render(): React.Node {
const {style, images} = this.props;
return (
<TouchableRipple onPress={this.onPress} style={style}>
<Image
resizeMode="contain"
source={{uri: images[0].url}}
style={{
width: '100%',
height: '100%',
}}
/>
</TouchableRipple>
);
}
}
export default withTheme(ImageGalleryButton);

View file

@ -31,6 +31,7 @@ import EquipmentLendScreen from '../screens/Amicale/Equipment/EquipmentRentScree
import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfirmScreen';
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
const modalTransition =
Platform.OS === 'ios'
@ -62,6 +63,14 @@ function MainStackComponent(props: {
title: i18n.t('screens.home.title'),
}}
/>
<MainStack.Screen
name="gallery"
component={ImageGalleryScreen}
options={{
headerShown: false,
...modalTransition,
}}
/>
{createScreenCollapsibleStack(
'settings',
MainStack,

View file

@ -10,7 +10,6 @@ import {
Paragraph,
withTheme,
} from 'react-native-paper';
import ImageModal from 'react-native-image-modal';
import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack';
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
@ -22,6 +21,7 @@ import {ERROR_TYPE} from '../../../utils/WebData';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import type {ApiGenericDataType} from '../../../utils/WebData';
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
type PropsType = {
navigation: StackNavigationProp,
@ -188,7 +188,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
}
getScreen = (response: Array<ApiGenericDataType | null>): React.Node => {
const {props} = this;
const {navigation} = this.props;
let data: ClubType | null = null;
if (response[0] != null) {
[data] = response;
@ -199,25 +199,18 @@ class ClubDisplayScreen extends React.Component<PropsType> {
<CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab>
{this.getCategoriesRender(data.category)}
{data.logo !== null ? (
<View
<ImageGalleryButton
navigation={navigation}
images={[{url: data.logo}]}
style={{
width: 300,
height: 300,
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 10,
marginBottom: 10,
}}>
<ImageModal
resizeMode="contain"
imageBackgroundColor={props.theme.colors.background}
style={{
width: 300,
height: 300,
}}
source={{
uri: data.logo,
}}
/>
</View>
) : (
<View />
)}

View file

@ -1,9 +1,8 @@
// @flow
import * as React from 'react';
import {Linking, View} from 'react-native';
import {Linking} from 'react-native';
import {Avatar, Card, Text, withTheme} from 'react-native-paper';
import ImageModal from 'react-native-image-modal';
import Autolink from 'react-native-autolink';
import {StackNavigationProp} from '@react-navigation/stack';
import MaterialHeaderButtons, {
@ -12,6 +11,7 @@ import MaterialHeaderButtons, {
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {FeedItemType} from './HomeScreen';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
type PropsType = {
navigation: StackNavigationProp,
@ -69,6 +69,7 @@ class FeedItemScreen extends React.Component<PropsType> {
};
render(): React.Node {
const {navigation} = this.props;
const hasImage =
this.displayData.image !== '' && this.displayData.image != null;
return (
@ -85,19 +86,16 @@ class FeedItemScreen extends React.Component<PropsType> {
)}
/>
{hasImage ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
<ImageModal
resizeMode="contain"
imageBackgroundColor="#000"
<ImageGalleryButton
navigation={navigation}
images={[{url: this.displayData.image}]}
style={{
width: 250,
height: 250,
}}
source={{
uri: this.displayData.image,
marginLeft: 'auto',
marginRight: 'auto',
}}
/>
</View>
) : null}
<Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
{this.displayData.message !== undefined ? (

View file

@ -0,0 +1,126 @@
// @flow
import * as React from 'react';
import {IconButton, Text} from 'react-native-paper';
import ImageViewer from 'react-native-image-zoom-viewer';
import {StackNavigationProp} from '@react-navigation/stack';
import * as Animatable from 'react-native-animatable';
type PropsType = {
navigation: StackNavigationProp,
route: {params: {images: Array<{url: string}>}},
};
class ImageGalleryScreen extends React.Component<PropsType> {
images: Array<{url: string}>;
closeButtonRef: {current: null | Animatable.View};
indicatorRef: {current: null | Animatable.View};
controlsShown: boolean;
constructor(props: PropsType) {
super(props);
this.closeButtonRef = React.createRef();
this.indicatorRef = React.createRef();
this.controlsShown = true;
if (props.route.params != null) this.images = props.route.params.images;
}
goBack = () => {
const {navigation} = this.props;
navigation.goBack();
};
getRenderHeader = (): React.Node => {
return (
<Animatable.View
ref={this.closeButtonRef}
useNativeDriver
style={{
position: 'absolute',
top: 10,
left: 10,
zIndex: 1000,
}}>
<IconButton
onPress={this.goBack}
icon="close"
size={30}
style={{backgroundColor: 'rgba(0,0,0,0.6)'}}
/>
</Animatable.View>
);
};
getRenderIndicator = (
currentIndex?: number,
allSize?: number,
): React.Node => {
if (currentIndex != null && allSize != null && allSize !== 1)
return (
<Animatable.View
ref={this.indicatorRef}
useNativeDriver
style={{
width: '100%',
height: 50,
position: 'absolute',
}}>
<Text
style={{
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
fontSize: 15,
backgroundColor: 'rgba(0,0,0,0.6)',
padding: 10,
borderRadius: 20,
}}>
{currentIndex}/{allSize}
</Text>
</Animatable.View>
);
return null;
};
onImageClick = () => {
if (this.controlsShown) this.hideControls();
else this.showControls();
this.controlsShown = !this.controlsShown;
};
hideControls() {
if (this.closeButtonRef.current != null)
this.closeButtonRef.current.fadeOutUp(200);
if (this.indicatorRef.current != null)
this.indicatorRef.current.fadeOutUp(300);
}
showControls() {
if (this.closeButtonRef.current != null)
this.closeButtonRef.current.fadeInDown(300);
if (this.indicatorRef.current != null)
this.indicatorRef.current.fadeInDown(400);
}
render(): React.Node {
return (
<ImageViewer
useNativeDriver
imageUrls={this.images}
enableSwipeDown
onSwipeDown={this.goBack}
renderHeader={this.getRenderHeader}
renderIndicator={this.getRenderIndicator}
pageAnimateTime={100}
onClick={this.onImageClick}
doubleClickInterval={250}
/>
);
}
}
export default ImageGalleryScreen;

View file

@ -2,8 +2,7 @@
import * as React from 'react';
import {View} from 'react-native';
import {Card, withTheme} from 'react-native-paper';
import ImageModal from 'react-native-image-modal';
import {Card} from 'react-native-paper';
import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack';
import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning';
@ -13,14 +12,13 @@ import {apiRequest, ERROR_TYPE} from '../../utils/WebData';
import ErrorView from '../../components/Screens/ErrorView';
import CustomHTML from '../../components/Overrides/CustomHTML';
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {CustomThemeType} from '../../managers/ThemeManager';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import type {PlanningEventType} from '../../utils/Planning';
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
type PropsType = {
navigation: StackNavigationProp,
route: {params: {data: PlanningEventType, id: number, eventId: number}},
theme: CustomThemeType,
};
type StateType = {
@ -95,7 +93,7 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
* @returns {*}
*/
getContent(): React.Node {
const {theme} = this.props;
const {navigation} = this.props;
const {displayData} = this;
if (displayData == null) return null;
let subtitle = getFormattedEventTime(
@ -111,19 +109,16 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
<CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab>
<Card.Title title={displayData.title} subtitle={subtitle} />
{displayData.logo !== null ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
<ImageModal
resizeMode="contain"
imageBackgroundColor={theme.colors.background}
<ImageGalleryButton
navigation={navigation}
images={[{url: displayData.logo}]}
style={{
width: 300,
height: 300,
}}
source={{
uri: displayData.logo,
marginLeft: 'auto',
marginRight: 'auto',
}}
/>
</View>
) : null}
{displayData.description !== null ? (
@ -181,4 +176,4 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
}
}
export default withTheme(PlanningDisplayScreen);
export default PlanningDisplayScreen;