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", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "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": { "react-native": {
"version": "0.63.2", "version": "0.63.2",
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.63.2.tgz", "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": { "react-native-image-modal": {
"version": "1.0.9", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/react-native-image-modal/-/react-native-image-modal-1.0.9.tgz", "resolved": "https://registry.npmjs.org/react-native-image-modal/-/react-native-image-modal-1.0.9.tgz",
"integrity": "sha512-d01lx82CgC2s8zzpmKAbSAK/rABFl8BhKHSrp6GLqColNuUHaNuDIuu67TdqzHsY9pT4OHY0rduWyvrmRiSrLw==" "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": { "react-native-iphone-x-helper": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.1.tgz", "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" "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": { "read-pkg": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" "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": { "snapdragon": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "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-camera": "^3.35.0",
"react-native-collapsible": "^1.5.3", "react-native-collapsible": "^1.5.3",
"react-native-gesture-handler": "^1.7.0", "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-keychain": "^6.1.1",
"react-native-linear-gradient": "^2.5.6", "react-native-linear-gradient": "^2.5.6",
"react-native-localize": "^1.4.1", "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 {Image, View} from 'react-native';
import Autolink from 'react-native-autolink'; import Autolink from 'react-native-autolink';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import ImageModal from 'react-native-image-modal';
import {StackNavigationProp} from '@react-navigation/stack'; import {StackNavigationProp} from '@react-navigation/stack';
import type {FeedItemType} from '../../screens/Home/HomeScreen'; import type {FeedItemType} from '../../screens/Home/HomeScreen';
import NewsSourcesConstants from '../../constants/NewsSourcesConstants'; import NewsSourcesConstants from '../../constants/NewsSourcesConstants';
import type {NewsSourceType} from '../../constants/NewsSourcesConstants'; import type {NewsSourceType} from '../../constants/NewsSourcesConstants';
import ImageGalleryButton from '../Media/ImageGalleryButton';
type PropsType = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp,
@ -82,16 +82,13 @@ class FeedItem extends React.Component<PropsType> {
/> />
{hasImage ? ( {hasImage ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}> <View style={{marginLeft: 'auto', marginRight: 'auto'}}>
<ImageModal <ImageGalleryButton
resizeMode="contain" navigation={props.navigation}
imageBackgroundColor="#000" images={[{url: item.image}]}
style={{ style={{
width: imageSize, width: imageSize,
height: imageSize, height: imageSize,
}} }}
source={{
uri: item.image,
}}
/> />
</View> </View>
) : null} ) : 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 EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfirmScreen';
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen'; import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
import GameStartScreen from '../screens/Game/screens/GameStartScreen'; import GameStartScreen from '../screens/Game/screens/GameStartScreen';
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
const modalTransition = const modalTransition =
Platform.OS === 'ios' Platform.OS === 'ios'
@ -62,6 +63,14 @@ function MainStackComponent(props: {
title: i18n.t('screens.home.title'), title: i18n.t('screens.home.title'),
}} }}
/> />
<MainStack.Screen
name="gallery"
component={ImageGalleryScreen}
options={{
headerShown: false,
...modalTransition,
}}
/>
{createScreenCollapsibleStack( {createScreenCollapsibleStack(
'settings', 'settings',
MainStack, MainStack,

View file

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

View file

@ -1,9 +1,8 @@
// @flow // @flow
import * as React from 'react'; 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 {Avatar, Card, Text, withTheme} from 'react-native-paper';
import ImageModal from 'react-native-image-modal';
import Autolink from 'react-native-autolink'; import Autolink from 'react-native-autolink';
import {StackNavigationProp} from '@react-navigation/stack'; import {StackNavigationProp} from '@react-navigation/stack';
import MaterialHeaderButtons, { import MaterialHeaderButtons, {
@ -12,6 +11,7 @@ import MaterialHeaderButtons, {
import CustomTabBar from '../../components/Tabbar/CustomTabBar'; import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {FeedItemType} from './HomeScreen'; import type {FeedItemType} from './HomeScreen';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
type PropsType = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp,
@ -69,6 +69,7 @@ class FeedItemScreen extends React.Component<PropsType> {
}; };
render(): React.Node { render(): React.Node {
const {navigation} = this.props;
const hasImage = const hasImage =
this.displayData.image !== '' && this.displayData.image != null; this.displayData.image !== '' && this.displayData.image != null;
return ( return (
@ -85,19 +86,16 @@ class FeedItemScreen extends React.Component<PropsType> {
)} )}
/> />
{hasImage ? ( {hasImage ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}> <ImageGalleryButton
<ImageModal navigation={navigation}
resizeMode="contain" images={[{url: this.displayData.image}]}
imageBackgroundColor="#000" style={{
style={{ width: 250,
width: 250, height: 250,
height: 250, marginLeft: 'auto',
}} marginRight: 'auto',
source={{ }}
uri: this.displayData.image, />
}}
/>
</View>
) : null} ) : null}
<Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}> <Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
{this.displayData.message !== undefined ? ( {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 * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import {Card, withTheme} from 'react-native-paper'; import {Card} from 'react-native-paper';
import ImageModal from 'react-native-image-modal';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack'; import {StackNavigationProp} from '@react-navigation/stack';
import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning'; import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning';
@ -13,14 +12,13 @@ import {apiRequest, ERROR_TYPE} from '../../utils/WebData';
import ErrorView from '../../components/Screens/ErrorView'; import ErrorView from '../../components/Screens/ErrorView';
import CustomHTML from '../../components/Overrides/CustomHTML'; import CustomHTML from '../../components/Overrides/CustomHTML';
import CustomTabBar from '../../components/Tabbar/CustomTabBar'; import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {CustomThemeType} from '../../managers/ThemeManager';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import type {PlanningEventType} from '../../utils/Planning'; import type {PlanningEventType} from '../../utils/Planning';
import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
type PropsType = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp,
route: {params: {data: PlanningEventType, id: number, eventId: number}}, route: {params: {data: PlanningEventType, id: number, eventId: number}},
theme: CustomThemeType,
}; };
type StateType = { type StateType = {
@ -95,7 +93,7 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
* @returns {*} * @returns {*}
*/ */
getContent(): React.Node { getContent(): React.Node {
const {theme} = this.props; const {navigation} = this.props;
const {displayData} = this; const {displayData} = this;
if (displayData == null) return null; if (displayData == null) return null;
let subtitle = getFormattedEventTime( let subtitle = getFormattedEventTime(
@ -111,19 +109,16 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
<CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab> <CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab>
<Card.Title title={displayData.title} subtitle={subtitle} /> <Card.Title title={displayData.title} subtitle={subtitle} />
{displayData.logo !== null ? ( {displayData.logo !== null ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}> <ImageGalleryButton
<ImageModal navigation={navigation}
resizeMode="contain" images={[{url: displayData.logo}]}
imageBackgroundColor={theme.colors.background} style={{
style={{ width: 300,
width: 300, height: 300,
height: 300, marginLeft: 'auto',
}} marginRight: 'auto',
source={{ }}
uri: displayData.logo, />
}}
/>
</View>
) : null} ) : null}
{displayData.description !== null ? ( {displayData.description !== null ? (
@ -181,4 +176,4 @@ class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
} }
} }
export default withTheme(PlanningDisplayScreen); export default PlanningDisplayScreen;