Merge branch 'development' of github.com:ecency/ecency-mobile into feature/accounts-bottom-sheet

This commit is contained in:
Furkan Kılıç 2021-01-19 00:18:35 +03:00
commit 8475f2c91b
65 changed files with 1788 additions and 795 deletions

View File

@ -27,7 +27,7 @@ buildscript {
jcenter()
}
dependencies {
classpath('com.android.tools.build:gradle:3.5.2')
classpath('com.android.tools.build:gradle:4.0.1')
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -43,7 +43,7 @@
05B6C4B024C306CE00B7FA60 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 980BC9BC0D3B4AC69645C842 /* Zocial.ttf */; };
0A1D279E0D3CD306C889592E /* libPods-Ecency-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7093E51BBC0EE2F41AB19EBA /* libPods-Ecency-tvOS.a */; };
1CD0B89E258019B600A7D78E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1CD0B89B258019B600A7D78E /* GoogleService-Info.plist */; };
2B3CF3607B7CB9B7296FD5EF /* (null) in Frameworks */ = {isa = PBXBuildFile; };
2B3CF3607B7CB9B7296FD5EF /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
@ -55,7 +55,7 @@
CFAA2A599FD65F360D9B3E1E /* libPods-EcencyTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B344CAA24725C973F48BE81E /* libPods-EcencyTests.a */; };
D71EB20EDB9B987C0574BAFE /* libPods-EcencyTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C97456BE898C00B5EDA21C2E /* libPods-EcencyTests.a */; };
DC0E25610BB5F49AFF4514AD /* libPods-Ecency.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 388DF3FF85F08109F722083B /* libPods-Ecency.a */; };
F77F6C7E54F3C783A2773E9D /* (null) in Frameworks */ = {isa = PBXBuildFile; };
F77F6C7E54F3C783A2773E9D /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -204,8 +204,8 @@
buildActionMask = 2147483647;
files = (
05B6C49424C306CE00B7FA60 /* StoreKit.framework in Frameworks */,
F77F6C7E54F3C783A2773E9D /* (null) in Frameworks */,
2B3CF3607B7CB9B7296FD5EF /* (null) in Frameworks */,
F77F6C7E54F3C783A2773E9D /* BuildFile in Frameworks */,
2B3CF3607B7CB9B7296FD5EF /* BuildFile in Frameworks */,
DC0E25610BB5F49AFF4514AD /* libPods-Ecency.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -752,7 +752,7 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Ecency/Pods-Ecency-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/QBImagePickerController/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
@ -769,7 +769,7 @@
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_ROOT}/RSKImageCropper/RSKImageCropper/RSKImageCropperStrings.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
@ -790,7 +790,7 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RSKImageCropperStrings.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;

View File

@ -145,7 +145,6 @@ PODS:
- nanopb/encode (1.30905.0)
- PromisesObjC (1.2.9)
- Protobuf (3.12.0)
- QBImagePickerController (3.4.0)
- RCTRequired (0.61.5)
- RCTTypeSafety (0.61.5):
- FBLazyVector (= 0.61.5)
@ -400,11 +399,15 @@ PODS:
- React
- RNIap (3.4.15):
- React
- RNImageCropPicker (0.26.2):
- QBImagePickerController
- RNImageCropPicker (0.35.2):
- React-Core
- React-RCTImage
- RSKImageCropper
- RNImageCropPicker/QBImagePickerController (= 0.35.2)
- TOCropViewController
- RNImageCropPicker/QBImagePickerController (0.35.2):
- React-Core
- React-RCTImage
- TOCropViewController
- RNReanimated (1.13.2):
- React-Core
- RNScreens (2.10.1):
@ -413,13 +416,13 @@ PODS:
- React
- RNVectorIcons (6.7.0):
- React
- RSKImageCropper (2.2.3)
- SDWebImage (5.8.4):
- SDWebImage/Core (= 5.8.4)
- SDWebImage/Core (5.8.4)
- SDWebImageWebPCoder (0.6.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.7)
- TOCropViewController (2.6.0)
- toolbar-android (0.1.0-rc.2):
- React
- Yoga (1.14.0)
@ -509,10 +512,9 @@ SPEC REPOS:
- nanopb
- PromisesObjC
- Protobuf
- QBImagePickerController
- RSKImageCropper
- SDWebImage
- SDWebImageWebPCoder
- TOCropViewController
EXTERNAL SOURCES:
appcenter-analytics:
@ -666,7 +668,6 @@ SPEC CHECKSUMS:
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
PromisesObjC: b48e0338dbbac2207e611750777895f7a5811b75
Protobuf: 2793fcd0622a00b546c60e7cbbcc493e043e9bb9
QBImagePickerController: d54cf93db6decf26baf6ed3472f336ef35cae022
RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
@ -707,17 +708,17 @@ SPEC CHECKSUMS:
RNFBMessaging: 3bb7dcf398789ce359a9f6b97b83472a3090f65a
RNGestureHandler: b6b359bb800ae399a9c8b27032bdbf7c18f08a08
RNIap: b4c77c8bc4501203f4b743126a05da23f10f40b4
RNImageCropPicker: 9d1a7eea4f8368fc479cbd2bf26459bd3c74d9aa
RNImageCropPicker: 9e0bf18cf4184a846fed55747c8e622208b39947
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: b748efec66e095134c7166ca333b628cd7e6f3e2
RNSVG: 8ba35cbeb385a52fd960fd28db9d7d18b4c2974f
RNVectorIcons: 368d6d8b8301224e5ffb6254191f4f8876c2be0d
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
TOCropViewController: 3105367e808b7d3d886a74ff59bf4804e7d3ab38
toolbar-android: 85f3ef4d691469f2d304e7dee4bca013aa1ba1ff
Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
PODFILE CHECKSUM: 1f30c7da5061dbc47185442a6ab4a3c95ac48c04
COCOAPODS: 1.10.0
COCOAPODS: 1.9.3

View File

@ -27,7 +27,7 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ecency/render-helper": "^2.0.11",
"@ecency/render-helper": "^2.0.15",
"@esteemapp/dhive": "0.15.0",
"@esteemapp/react-native-autocomplete-input": "^4.2.1",
"@esteemapp/react-native-modal-popover": "^0.0.15",
@ -75,7 +75,8 @@
"react-native-fast-image": "^8.3.2",
"react-native-gesture-handler": "^1.4.1",
"react-native-iap": "3.4.15",
"react-native-image-crop-picker": "^0.26.1",
"react-native-image-crop-picker": "^0.35.2",
"react-native-image-size": "^1.1.3",
"react-native-image-zoom-viewer": "^2.2.27",
"react-native-keyboard-aware-scroll-view": "^0.9.1",
"react-native-linear-gradient": "^2.4.2",

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,7 @@ import UserListItem from './view/userListItem/userListItem';
import WalletLineItem from './view/walletLineItem/walletLineItemView';
import CommunityListItem from './view/communityListItem/communityListItem';
import Separator from './view/separator/separatorView';
import EmptyScreen from './view/emptyScreen/emptyScreenView';
// Placeholders
import ListItemPlaceHolder from './view/placeHolder/listItemPlaceHolderView';
@ -48,4 +49,5 @@ export {
WalletUnclaimedPlaceHolder,
CommunitiesPlaceHolder,
Separator,
EmptyScreen,
};

View File

@ -0,0 +1,18 @@
import React from 'react';
import { View, Text } from 'react-native';
import LottieView from 'lottie-react-native';
import globalStyles from '../../../../globalStyles';
const EmptyScreenView = ({ style, textStyle }) => (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<LottieView
style={[{ width: 150, height: 150, marginBottom: 12 }, style]}
source={require('../../../../assets/animations/empty_screen.json')}
autoPlay
loop={true}
/>
<Text style={[globalStyles.title, textStyle]}>Nothing found!</Text>
</View>
);
export default EmptyScreenView;

View File

@ -0,0 +1,3 @@
import CommunitiesList from './view/communitiesList';
export default CommunitiesList;

View File

@ -2,23 +2,22 @@ import React from 'react';
import { SafeAreaView, FlatList } from 'react-native';
// Components
import CommunitiesListItem from './CommunitiesListItem';
import { CommunitiesPlaceHolder } from '../../../components/basicUIElements';
import { CommunitiesPlaceHolder } from '../../basicUIElements';
import CommunitiesListItem from './communitiesListItem';
// Styles
import styles from './communitiesListStyles';
const CommunitiesList = ({
votes,
data,
subscribingCommunities,
handleOnPress,
handleSubscribeButtonPress,
allSubscriptions,
isLoggedIn,
noResult,
screen,
}) => {
const _renderItem = ({ item, index }) => {
const isSubscribed = allSubscriptions.some((sub) => sub[0] === item.name);
return (
<CommunitiesListItem
index={index}
@ -33,8 +32,13 @@ const CommunitiesList = ({
name={item.name}
handleOnPress={handleOnPress}
handleSubscribeButtonPress={handleSubscribeButtonPress}
isSubscribed={isSubscribed}
isSubscribed={item.isSubscribed}
isLoggedIn={isLoggedIn}
loading={
subscribingCommunities.hasOwnProperty(item.name) &&
subscribingCommunities[item.name].loading
}
screen={screen}
/>
);
};
@ -57,8 +61,8 @@ const CommunitiesList = ({
<SafeAreaView style={styles.container}>
{!noResult && (
<FlatList
data={votes}
keyExtractor={(item) => item.id && item.id.toString()}
data={data}
keyExtractor={(item, index) => index.toString()}
renderItem={_renderItem}
ListEmptyComponent={_renderEmptyContent}
/>

View File

@ -0,0 +1,3 @@
import CommunitiesListItem from './view/CommunitiesListItem';
export default CommunitiesListItem;

View File

@ -1,12 +1,12 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
import { useIntl } from 'react-intl';
import styles from './communitiesListItemStyles';
import { Tag } from '../../../components/basicUIElements';
import { Tag } from '../../../../basicUIElements';
const UserListItem = ({
const CommunitiesListItem = ({
index,
handleOnPress,
handleOnLongPress,
@ -22,14 +22,13 @@ const UserListItem = ({
handleSubscribeButtonPress,
isSubscribed,
isLoggedIn,
loading,
screen,
}) => {
const [subscribed, setSubscribed] = useState(isSubscribed);
const intl = useIntl();
const _handleSubscribeButtonPress = () => {
handleSubscribeButtonPress({ subscribed: !subscribed, communityId: name }).then(() => {
setSubscribed(!subscribed);
});
handleSubscribeButtonPress({ isSubscribed: isSubscribed, communityId: name }, screen);
};
return (
@ -41,12 +40,17 @@ const UserListItem = ({
<View style={styles.content}>
<View style={styles.header}>
<Text style={styles.title}>{title}</Text>
{isLoggedIn && (
{isLoggedIn &&
(loading ? (
<View style={styles.indicatorView}>
<ActivityIndicator />
</View>
) : (
<Tag
style={styles.subscribeButton}
textStyle={subscribed && styles.subscribeButtonText}
textStyle={isSubscribed && styles.subscribeButtonText}
value={
!subscribed
!isSubscribed
? intl.formatMessage({
id: 'search_result.communities.subscribe',
})
@ -54,11 +58,11 @@ const UserListItem = ({
id: 'search_result.communities.unsubscribe',
})
}
isPin={!subscribed}
isPin={!isSubscribed}
isFilter
onPress={_handleSubscribeButtonPress}
/>
)}
))}
</View>
{!!about && <Text style={styles.about}>{about}</Text>}
<View style={styles.separator} />
@ -77,4 +81,4 @@ const UserListItem = ({
);
};
export default UserListItem;
export default CommunitiesListItem;

View File

@ -59,4 +59,9 @@ export default EStyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
},
indicatorView: {
width: 65,
alignItems: 'center',
justifyContent: 'center',
},
});

View File

@ -34,6 +34,7 @@ import { PostForm } from './postForm';
import { PostHeaderDescription, PostBody, Tags } from './postElements';
import { PostListItem } from './postListItem';
import { ProfileSummary } from './profileSummary';
import { ProgressiveImage } from './progressiveImage';
import { SearchInput } from './searchInput';
import { SearchModal } from './searchModal';
@ -64,6 +65,8 @@ import ScaleSlider from './scaleSlider/scaleSliderView';
import { ProductItemLine } from './productItemLine/productItemLineView';
import { HorizontalIconList } from './horizontalIconList/horizontalIconListView';
import { PopoverWrapper } from './popoverWrapper/popoverWrapperView';
import CommunitiesList from './communitiesList';
import SubscribedCommunitiesList from './subscribedCommunitiesList';
// View
import { Comment } from './comment';
@ -104,6 +107,7 @@ import {
WalletLineItem,
WalletUnclaimedPlaceHolder,
Separator,
EmptyScreen,
} from './basicUIElements';
export {
@ -207,6 +211,9 @@ export {
WalletLineItem,
WalletUnclaimedPlaceHolder,
Separator,
EmptyScreen,
HorizontalIconList,
PopoverWrapper,
CommunitiesList,
SubscribedCommunitiesList,
};

View File

@ -44,6 +44,7 @@ const MarkdownEditorView = ({
const [text, setText] = useState(draftBody || '');
const [selection, setSelection] = useState({ start: 0, end: 0 });
const [editable, setEditable] = useState(true);
const [height, setHeight] = useState(0);
const inputRef = useRef(null);
const galleryRef = useRef(null);
@ -260,7 +261,7 @@ const MarkdownEditorView = ({
<TextInput
multiline
autoCorrect={true}
autoFocus={false}
autoFocus={isReply ? true : false}
onChangeText={_changeText}
onSelectionChange={_handleOnSelectionChange}
placeholder={intl.formatMessage({

View File

@ -32,8 +32,8 @@ export default EStyleSheet.create({
margin: 0,
alignItems: 'center',
alignSelf: 'center',
height: 200,
width: '$deviceWidth - 16',
//height: 200,
//width: '$deviceWidth - 16',
borderRadius: 8,
backgroundColor: '$primaryLightGray',
},

View File

@ -1,8 +1,8 @@
import React, { Component, useState, useEffect } from 'react';
import get from 'lodash/get';
import { TouchableOpacity, Text, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import { TouchableOpacity, Text, View, Dimensions } from 'react-native';
import { injectIntl } from 'react-intl';
import ImageSize from 'react-native-image-size';
// Utils
import { getTimeFromNow } from '../../../utils/time';
@ -18,8 +18,13 @@ import { Upvote } from '../../upvote';
import styles from './postCardStyles';
// Defaults
import DEFAULT_IMAGE from '../../../assets/no_image.png';
import NSFW_IMAGE from '../../../assets/nsfw.png';
import ProgressiveImage from '../../progressiveImage';
const dim = Dimensions.get('window');
const DEFAULT_IMAGE =
'https://images.ecency.com/DQmT8R33geccEjJfzZEdsRHpP3VE8pu3peRCnQa1qukU4KR/no_image_3x.png';
const NSFW_IMAGE =
'https://images.ecency.com/DQmZ1jW4p7o5GyoqWyCib1fSLE2ftbewsMCt2GvbmT9kmoY/nsfw_3x.png';
const PostCardView = ({
handleOnUserPress,
@ -36,6 +41,7 @@ const PostCardView = ({
}) => {
const [rebloggedBy, setRebloggedBy] = useState(get(content, 'reblogged_by[0]', null));
const [activeVot, setActiveVot] = useState(activeVotes);
const [calcImgHeight, setCalcImgHeight] = useState(300);
//console.log(activeVotes);
// Component Functions
@ -62,11 +68,16 @@ const PostCardView = ({
const _getPostImage = (content, isNsfwPost) => {
if (content && content.image) {
if (isNsfwPost && content.nsfw) {
return NSFW_IMAGE;
return { image: NSFW_IMAGE, thumbnail: NSFW_IMAGE };
}
return { uri: content.image, priority: FastImage.priority.high };
//console.log(content)
ImageSize.getSize(content.image).then((size) => {
setCalcImgHeight((size.height / size.width) * dim.width);
});
return { image: content.image, thumbnail: content.thumbnail };
} else {
return { image: DEFAULT_IMAGE, thumbnail: DEFAULT_IMAGE };
}
return DEFAULT_IMAGE;
};
useEffect(() => {
@ -102,11 +113,13 @@ const PostCardView = ({
<View style={styles.postBodyWrapper}>
<TouchableOpacity style={styles.hiddenImages} onPress={_handleOnContentPress}>
{!isHideImage && (
<FastImage
source={_image}
resizeMode={FastImage.resizeMode.contain}
style={styles.thumbnail}
defaultSource={DEFAULT_IMAGE}
<ProgressiveImage
source={{ uri: _image.image }}
thumbnailSource={{ uri: _image.thumbnail }}
style={[
styles.thumbnail,
{ width: dim.width - 16, height: Math.min(calcImgHeight, dim.height) },
]}
/>
)}
<View style={[styles.postDescripton]}>

View File

@ -18,8 +18,8 @@ export default EStyleSheet.create({
margin: 0,
alignItems: 'center',
alignSelf: 'center',
height: 200,
width: '$deviceWidth - 16',
//height: 200,
//width: '$deviceWidth - 16',
borderRadius: 8,
backgroundColor: '$primaryLightGray',
// paddingVertical: 10,

View File

@ -1,8 +1,8 @@
import React, { useRef, Fragment } from 'react';
import React, { useRef, useState, useEffect, Fragment } from 'react';
import ActionSheet from 'react-native-actionsheet';
import { View, Text, TouchableOpacity } from 'react-native';
import { View, Text, TouchableOpacity, Dimensions } from 'react-native';
import { injectIntl } from 'react-intl';
import FastImage from 'react-native-fast-image';
import ImageSize from 'react-native-image-size';
// Utils
import { getTimeFromNow } from '../../../utils/time';
@ -10,13 +10,17 @@ import { getTimeFromNow } from '../../../utils/time';
// Components
import { PostHeaderDescription } from '../../postElements';
import { IconButton } from '../../iconButton';
// Defaults
import DEFAULT_IMAGE from '../../../assets/no_image.png';
import ProgressiveImage from '../../progressiveImage';
// Styles
import styles from './postListItemStyles';
// Defaults
const DEFAULT_IMAGE =
'https://images.ecency.com/DQmT8R33geccEjJfzZEdsRHpP3VE8pu3peRCnQa1qukU4KR/no_image_3x.png';
const dim = Dimensions.get('window');
const PostListItemView = ({
title,
summary,
@ -25,6 +29,7 @@ const PostListItemView = ({
reputation,
created,
image,
thumbnail,
handleOnPressItem,
handleOnRemoveItem,
id,
@ -32,9 +37,21 @@ const PostListItemView = ({
isFormatedDate,
}) => {
const actionSheet = useRef(null);
const [calcImgHeight, setCalcImgHeight] = useState(300);
// Component Life Cycles
useEffect(() => {
let _isMounted = false;
if (image) {
if (!_isMounted) {
ImageSize.getSize(image.uri).then((size) => {
setCalcImgHeight((size.height / size.width) * dim.width);
});
}
}
return () => {
_isMounted = true;
};
}, []);
// Component Functions
return (
@ -60,7 +77,14 @@ const PostListItemView = ({
</View>
<View style={styles.body}>
<TouchableOpacity onPress={() => handleOnPressItem(id)}>
<FastImage source={image} style={styles.image} defaultSource={DEFAULT_IMAGE} />
<ProgressiveImage
source={image}
thumbnailSource={thumbnail}
style={[
styles.thumbnail,
{ width: dim.width - 16, height: Math.min(calcImgHeight, dim.height) },
]}
/>
<View style={[styles.postDescripton]}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.summary}>{summary}</Text>

View File

@ -462,7 +462,9 @@ const PostsContainer = ({
});
}
dispatch(subscribeAction(currentAccount, pinCode, data, successToastText, failToastText));
dispatch(
subscribeAction(currentAccount, pinCode, data, successToastText, failToastText, 'feedScreen'),
);
};
return (

View File

@ -0,0 +1,57 @@
import React from 'react';
import { View, StyleSheet, Animated } from 'react-native';
const styles = StyleSheet.create({
imageOverlay: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
},
container: {
backgroundColor: '#f6f6f6',
},
});
class ProgressiveImage extends React.Component {
thumbnailAnimated = new Animated.Value(0);
imageAnimated = new Animated.Value(0);
handleThumbnailLoad = () => {
Animated.timing(this.thumbnailAnimated, {
toValue: 1,
}).start();
};
onImageLoad = () => {
Animated.timing(this.imageAnimated, {
toValue: 1,
}).start();
};
render() {
const { thumbnailSource, source, style, ...props } = this.props;
return (
<View style={styles.container}>
<Animated.Image
{...props}
source={thumbnailSource}
style={[style, { opacity: this.thumbnailAnimated }]}
onLoad={this.handleThumbnailLoad}
blurRadius={1}
/>
<Animated.Image
{...props}
source={source}
style={[styles.imageOverlay, { opacity: this.imageAnimated }, style]}
onLoad={this.onImageLoad}
/>
</View>
);
}
}
export default ProgressiveImage;

View File

@ -0,0 +1,4 @@
import SubscribedCommunitiesListView from './view/subscribedCommunitiesListView';
import SubscribedCommunitiesList from './view/subscribedCommunitiesListView';
export default SubscribedCommunitiesList;

View File

@ -0,0 +1,51 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
itemWrapper: {
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 8,
borderRadius: 8,
backgroundColor: '$primaryBackgroundColor',
flexDirection: 'row',
alignItems: 'center',
},
itemWrapperGray: {
backgroundColor: '$primaryLightBackground',
},
username: {
marginLeft: 10,
color: '$primaryBlack',
},
communityWrapper: {
paddingHorizontal: 16,
paddingTop: 10,
paddingBottom: 10,
borderRadius: 8,
backgroundColor: '$primaryBackgroundColor',
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
subscribeButton: {
maxWidth: 75,
borderWidth: 1,
borderColor: '$primaryBlue',
},
subscribeButtonText: {
textAlign: 'center',
color: '$primaryBlue',
},
community: {
justifyContent: 'center',
marginLeft: 15,
color: '$primaryBlack',
},
tabbarItem: {
flex: 1,
},
});

View File

@ -0,0 +1,82 @@
import React from 'react';
import { View, FlatList, TouchableOpacity, Text, ActivityIndicator } from 'react-native';
import { useIntl } from 'react-intl';
import { Tag, UserAvatar } from '../../index';
import { ListPlaceHolder } from '../../basicUIElements';
import DEFAULT_IMAGE from '../../../assets/no_image.png';
import styles from './subscribedCommunitiesListStyles';
const SubscribedCommunitiesListView = ({
data,
subscribingCommunities,
handleOnPress,
handleSubscribeButtonPress,
}) => {
const intl = useIntl();
const _renderEmptyContent = () => {
return (
<>
<ListPlaceHolder />
</>
);
};
return (
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<View style={[styles.communityWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<View style={{ flex: 3, flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity onPress={() => handleOnPress(item[0])}>
<UserAvatar username={item[0]} defaultSource={DEFAULT_IMAGE} noAction />
</TouchableOpacity>
<TouchableOpacity onPress={() => handleOnPress(item[0])}>
<Text style={styles.community}>{item[1]}</Text>
</TouchableOpacity>
</View>
<View>
{subscribingCommunities.hasOwnProperty(item[0]) &&
subscribingCommunities[item[0]].loading ? (
<View style={{ width: 65, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
) : (
<Tag
style={styles.subscribeButton}
textStyle={item[4] && styles.subscribeButtonText}
value={
!item[4]
? intl.formatMessage({
id: 'search_result.communities.subscribe',
})
: intl.formatMessage({
id: 'search_result.communities.unsubscribe',
})
}
isPin={!item[4]}
isFilter
onPress={() =>
handleSubscribeButtonPress(
{
isSubscribed: item[4],
communityId: item[0],
},
'communitiesScreenJoinedTab',
)
}
/>
)}
</View>
</View>
)}
ListEmptyComponent={_renderEmptyContent}
/>
);
};
export default SubscribedCommunitiesListView;

View File

@ -87,23 +87,22 @@ class UpvoteContainer extends PureComponent {
const quote = get(globalProps, 'quote', 0);
const sbdPrintRate = get(globalProps, 'sbdPrintRate', 0);
const SBD_PRINT_RATE_MAX = 10000;
const percent_steem_dollars =
(content.percent_hbd || content.percent_steem_dollars || 10000) / 20000;
const percent_steem_dollars = (content.percent_hbd || 10000) / 20000;
const pending_payout_sbd = pendingPayout * percent_steem_dollars;
const pending_payout_hbd = pendingPayout * percent_steem_dollars;
const price_per_steem = base / quote;
const pending_payout_sp = (pendingPayout - pending_payout_sbd) / price_per_steem;
const pending_payout_printed_sbd = pending_payout_sbd * (sbdPrintRate / SBD_PRINT_RATE_MAX);
const pending_payout_printed_steem =
(pending_payout_sbd - pending_payout_printed_sbd) / price_per_steem;
const pending_payout_hp = (pendingPayout - pending_payout_hbd) / price_per_steem;
const pending_payout_printed_hbd = pending_payout_hbd * (sbdPrintRate / SBD_PRINT_RATE_MAX);
const pending_payout_printed_hive =
(pending_payout_hbd - pending_payout_printed_hbd) / price_per_steem;
const breakdownPayout =
pending_payout_printed_sbd.toFixed(3) +
pending_payout_printed_hbd.toFixed(3) +
' HBD, ' +
pending_payout_printed_steem.toFixed(3) +
pending_payout_printed_hive.toFixed(3) +
' HIVE, ' +
pending_payout_sp.toFixed(3) +
pending_payout_hp.toFixed(3) +
' HP';
return (

View File

@ -252,6 +252,7 @@
"schedules": "Schedules",
"gallery": "Gallery",
"settings": "Settings",
"communities": "Communities",
"add_account": "Add Account",
"logout": "Logout",
"cancel": "Cancel",
@ -549,7 +550,7 @@
"title": "Topics"
},
"communities": {
"title": "Communities",
"title": "Groups",
"subscribe": "Join",
"unsubscribe": "Leave",
"subscribers": "Members",
@ -580,5 +581,9 @@
"user": {
"follow": "Follow",
"unfollow": "Unfollow"
},
"communities": {
"joined": "Membership",
"discover": "Discover"
}
}

View File

@ -29,6 +29,7 @@ export default {
COMMENTS: `Comments${SCREEN_SUFFIX}`,
ACCOUNT_BOOST: `AccountBoost${SCREEN_SUFFIX}`,
COMMUNITY: `Community${SCREEN_SUFFIX}`,
COMMUNITIES: `Communities${SCREEN_SUFFIX}`,
},
DRAWER: {
MAIN: `Main${DRAWER_SUFFIX}`,

View File

@ -25,6 +25,12 @@ const authMenuItems = [
// icon: 'photo-library',
// id: 'gallery',
// },
{
name: 'Communities',
route: ROUTES.SCREENS.COMMUNITIES,
icon: 'people',
id: 'communities',
},
{
name: 'Settings',
route: ROUTES.SCREENS.SETTINGS,

View File

@ -144,9 +144,9 @@ const WalletContainer = ({
const _isHasUnclaimedRewards = (account) => {
return (
parseToken(get(account, 'reward_steem_balance', account.reward_hive_balance)) > 0 ||
parseToken(get(account, 'reward_sbd_balance', account.reward_hbd_balance)) > 0 ||
parseToken(get(account, 'reward_vesting_steem', account.reward_vesting_hive)) > 0
parseToken(get(account, 'reward_hive_balance')) > 0 ||
parseToken(get(account, 'reward_hbd_balance')) > 0 ||
parseToken(get(account, 'reward_vesting_hive')) > 0
);
};

View File

@ -37,6 +37,7 @@ import {
AccountBoost,
TagResult,
Community,
Communities,
} from '../screens';
const bottomTabNavigator = createBottomTabNavigator(
@ -148,6 +149,7 @@ const stackNavigator = createStackNavigator(
[ROUTES.SCREENS.SPIN_GAME]: { screen: SpinGame },
[ROUTES.SCREENS.ACCOUNT_BOOST]: { screen: AccountBoost },
[ROUTES.SCREENS.COMMUNITY]: { screen: Community },
[ROUTES.SCREENS.COMMUNITIES]: { screen: Communities },
},
{
headerMode: 'none',

View File

@ -86,12 +86,10 @@ export const fetchGlobalProps = async () => {
}
const steemPerMVests =
(parseToken(
get(globalDynamic, 'total_vesting_fund_steem', globalDynamic.total_vesting_fund_hive),
) /
(parseToken(get(globalDynamic, 'total_vesting_fund_hive')) /
parseToken(get(globalDynamic, 'total_vesting_shares'))) *
1e6;
const sbdPrintRate = get(globalDynamic, 'sbd_print_rate', globalDynamic.hbd_print_rate);
const sbdPrintRate = get(globalDynamic, 'hbd_print_rate');
const base = parseAsset(get(feedHistory, 'current_median_history.base')).amount;
const quote = parseAsset(get(feedHistory, 'current_median_history.quote')).amount;
const fundRecentClaims = get(rewardFund, 'recent_claims');
@ -216,17 +214,17 @@ export const getUser = async (user, loggedIn = true) => {
_account.steem_power = await vestToSteem(
_account.vesting_shares,
globalProperties.total_vesting_shares,
globalProperties.total_vesting_fund_steem || globalProperties.total_vesting_fund_hive,
globalProperties.total_vesting_fund_hive,
);
_account.received_steem_power = await vestToSteem(
get(_account, 'received_vesting_shares'),
get(globalProperties, 'total_vesting_shares'),
get(globalProperties, 'total_vesting_fund_steem', globalProperties.total_vesting_fund_hive),
get(globalProperties, 'total_vesting_fund_hive'),
);
_account.delegated_steem_power = await vestToSteem(
get(_account, 'delegated_vesting_shares'),
get(globalProperties, 'total_vesting_shares'),
get(globalProperties, 'total_vesting_fund_steem', globalProperties.total_vesting_fund_hive),
get(globalProperties, 'total_vesting_fund_hive'),
);
if (has(_account, 'posting_json_metadata')) {
@ -316,7 +314,8 @@ export const getCommunities = async (
resolve({});
}
} catch (error) {
reject(error);
console.log(error);
resolve({});
}
});
@ -459,7 +458,7 @@ export const getRankedPosts = async (query, currentUserName, filterNsfw) => {
let posts = await client.call('bridge', 'get_ranked_posts', query);
if (posts) {
posts = parsePosts(posts, currentUserName, true);
posts = parsePosts(posts, currentUserName);
if (filterNsfw !== '0') {
const updatedPosts = filterNsfwPost(posts, filterNsfw);
@ -477,7 +476,7 @@ export const getAccountPosts = async (query, currentUserName, filterNsfw) => {
let posts = await client.call('bridge', 'get_account_posts', query);
if (posts) {
posts = parsePosts(posts, currentUserName, true);
posts = parsePosts(posts, currentUserName);
if (filterNsfw !== '0') {
const updatedPosts = filterNsfwPost(posts, filterNsfw);
@ -1140,9 +1139,9 @@ export const lookupAccounts = async (username) => {
}
};
export const getTrendingTags = async (tag) => {
export const getTrendingTags = async (tag, number = 20) => {
try {
const tags = await client.database.call('get_trending_tags', [tag, 20]);
const tags = await client.database.call('get_trending_tags', [tag, number]);
return tags;
} catch (error) {
return [];

View File

@ -61,19 +61,26 @@ export const fetchSubscribedCommunitiesFail = (payload) => ({
});
// Subscribe Community
export const subscribeCommunity = (currentAccount, pin, data, successToastText, failToastText) => {
export const subscribeCommunity = (
currentAccount,
pin,
data,
successToastText,
failToastText,
screen,
) => {
return (dispatch) => {
dispatch({ type: SUBSCRIBE_COMMUNITY, payload: data });
dispatch({ type: SUBSCRIBE_COMMUNITY, payload: { ...data, screen } });
subscribeCommunityReq(currentAccount, pin, data)
.then((res) => dispatch(subscribeCommunitySuccess(data, successToastText)))
.catch((err) => dispatch(subscribeCommunityFail(err, data, failToastText)));
.then((res) => dispatch(subscribeCommunitySuccess(data, successToastText, screen)))
.catch((err) => dispatch(subscribeCommunityFail(err, data, failToastText, screen)));
};
};
export const subscribeCommunitySuccess = (data, successToastText) => {
export const subscribeCommunitySuccess = (data, successToastText, screen) => {
return (dispatch) => [
dispatch({
payload: data,
payload: { ...data, screen },
type: SUBSCRIBE_COMMUNITY_SUCCESS,
}),
dispatch({
@ -83,10 +90,10 @@ export const subscribeCommunitySuccess = (data, successToastText) => {
];
};
export const subscribeCommunityFail = (error, data, failToastText) => {
export const subscribeCommunityFail = (error, data, failToastText, screen) => {
return (dispatch) => [
dispatch({
payload: data,
payload: { ...data, screen },
type: SUBSCRIBE_COMMUNITY_FAIL,
}),
dispatch({
@ -97,19 +104,26 @@ export const subscribeCommunityFail = (error, data, failToastText) => {
};
// Leave Community
export const leaveCommunity = (currentAccount, pin, data, successToastText, failToastText) => {
export const leaveCommunity = (
currentAccount,
pin,
data,
successToastText,
failToastText,
screen,
) => {
return (dispatch) => {
dispatch({ type: LEAVE_COMMUNITY, payload: data });
dispatch({ type: LEAVE_COMMUNITY, payload: { ...data, screen } });
subscribeCommunityReq(currentAccount, pin, data)
.then((res) => dispatch(leaveCommunitySuccess(data, successToastText)))
.catch((err) => dispatch(leaveCommunityFail(err, data, failToastText)));
.then((res) => dispatch(leaveCommunitySuccess(data, successToastText, screen)))
.catch((err) => dispatch(leaveCommunityFail(err, data, failToastText, screen)));
};
};
export const leaveCommunitySuccess = (data, successToastText) => {
export const leaveCommunitySuccess = (data, successToastText, screen) => {
return (dispatch) => [
dispatch({
payload: data,
payload: { ...data, screen },
type: LEAVE_COMMUNITY_SUCCESS,
}),
dispatch({
@ -119,10 +133,10 @@ export const leaveCommunitySuccess = (data, successToastText) => {
];
};
export const leaveCommunityFail = (error, data, failToastText) => {
export const leaveCommunityFail = (error, data, failToastText, screen) => {
return (dispatch) => [
dispatch({
payload: data,
payload: { ...data, screen },
type: LEAVE_COMMUNITY_FAIL,
}),
dispatch({

View File

@ -31,6 +31,27 @@ const initialState = {
// error: false,
//}
},
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
//['name']: {
// isSubscribed: false,
// loading: false,
// error: false,
//}
},
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
//['name']: {
// isSubscribed: false,
// loading: false,
// error: false,
//}
},
subscribingCommunitiesInSearchResultsScreen: {
//['name']: {
// isSubscribed: false,
// loading: false,
// error: false,
//}
},
};
export default function (state = initialState, action) {
@ -90,6 +111,32 @@ export default function (state = initialState, action) {
},
};
case SUBSCRIBE_COMMUNITY:
switch (action.payload.screen) {
case 'communitiesScreenDiscoverTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
...state.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
[action.payload.communityId]: {
isSubscribed: false,
loading: true,
error: false,
},
},
};
case 'communitiesScreenJoinedTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
...state.subscribingCommunitiesInCommunitiesScreenJoinedTab,
[action.payload.communityId]: {
isSubscribed: false,
loading: true,
error: false,
},
},
};
case 'feedScreen':
return {
...state,
subscribingCommunitiesInFeedScreen: {
@ -101,7 +148,48 @@ export default function (state = initialState, action) {
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: true,
error: false,
},
},
};
default:
return state;
}
case SUBSCRIBE_COMMUNITY_SUCCESS:
switch (action.payload.screen) {
case 'communitiesScreenDiscoverTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
...state.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: false,
},
},
};
case 'communitiesScreenJoinedTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
...state.subscribingCommunitiesInCommunitiesScreenJoinedTab,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: false,
},
},
};
case 'feedScreen':
return {
...state,
subscribingCommunitiesInFeedScreen: {
@ -113,7 +201,48 @@ export default function (state = initialState, action) {
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: false,
},
},
};
default:
return state;
}
case SUBSCRIBE_COMMUNITY_FAIL:
switch (action.payload.screen) {
case 'communitiesScreenDiscoverTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
...state.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: true,
},
},
};
case 'communitiesScreenJoinedTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
...state.subscribingCommunitiesInCommunitiesScreenJoinedTab,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: true,
},
},
};
case 'feedScreen':
return {
...state,
subscribingCommunitiesInFeedScreen: {
@ -125,7 +254,48 @@ export default function (state = initialState, action) {
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: true,
},
},
};
default:
return state;
}
case LEAVE_COMMUNITY:
switch (action.payload.screen) {
case 'communitiesScreenDiscoverTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
...state.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
[action.payload.communityId]: {
isSubscribed: true,
loading: true,
error: false,
},
},
};
case 'communitiesScreenJoinedTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
...state.subscribingCommunitiesInCommunitiesScreenJoinedTab,
[action.payload.communityId]: {
isSubscribed: true,
loading: true,
error: false,
},
},
};
case 'feedScreen':
return {
...state,
subscribingCommunitiesInFeedScreen: {
@ -137,7 +307,48 @@ export default function (state = initialState, action) {
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: true,
error: false,
},
},
};
default:
return state;
}
case LEAVE_COMMUNITY_SUCCESS:
switch (action.payload.screen) {
case 'communitiesScreenDiscoverTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
...state.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: false,
},
},
};
case 'communitiesScreenJoinedTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
...state.subscribingCommunitiesInCommunitiesScreenJoinedTab,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: false,
},
},
};
case 'feedScreen':
return {
...state,
subscribingCommunitiesInFeedScreen: {
@ -149,7 +360,48 @@ export default function (state = initialState, action) {
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: false,
},
},
};
default:
return state;
}
case LEAVE_COMMUNITY_FAIL:
switch (action.payload.screen) {
case 'communitiesScreenDiscoverTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenDiscoverTab: {
...state.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: true,
},
},
};
case 'communitiesScreenJoinedTab':
return {
...state,
subscribingCommunitiesInCommunitiesScreenJoinedTab: {
...state.subscribingCommunitiesInCommunitiesScreenJoinedTab,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: true,
},
},
};
case 'feedScreen':
return {
...state,
subscribingCommunitiesInFeedScreen: {
@ -161,6 +413,21 @@ export default function (state = initialState, action) {
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: true,
},
},
};
default:
return state;
}
default:
return state;
}

View File

@ -0,0 +1,153 @@
import { useState, useEffect } from 'react';
import { withNavigation } from 'react-navigation';
import { useSelector, useDispatch } from 'react-redux';
import { shuffle } from 'lodash';
import { useIntl } from 'react-intl';
import ROUTES from '../../../constants/routeNames';
import { getCommunities, getSubscriptions } from '../../../providers/hive/dhive';
import { toastNotification } from '../../../redux/actions/uiAction';
import { subscribeCommunity, leaveCommunity } from '../../../redux/actions/communitiesAction';
const CommunitiesContainer = ({ children, navigation }) => {
const dispatch = useDispatch();
const intl = useIntl();
const [discovers, setDiscovers] = useState([]);
const [subscriptions, setSubscriptions] = useState([]);
const currentAccount = useSelector((state) => state.account.currentAccount);
const pinCode = useSelector((state) => state.application.pin);
const subscribingCommunitiesInDiscoverTab = useSelector(
(state) => state.communities.subscribingCommunitiesInCommunitiesScreenDiscoverTab,
);
const subscribingCommunitiesInJoinedTab = useSelector(
(state) => state.communities.subscribingCommunitiesInCommunitiesScreenJoinedTab,
);
useEffect(() => {
getSubscriptions(currentAccount.username).then((subs) => {
subs.forEach((item) => item.push(true));
getCommunities('', 50, '', 'rank').then((communities) => {
communities.forEach((community) =>
Object.assign(community, {
isSubscribed: subs.some(
(subscribedCommunity) => subscribedCommunity[0] === community.name,
),
}),
);
setSubscriptions(subs);
setDiscovers(shuffle(communities));
});
});
}, []);
useEffect(() => {
const discoversData = [...discovers];
Object.keys(subscribingCommunitiesInDiscoverTab).map((communityId) => {
if (!subscribingCommunitiesInDiscoverTab[communityId].loading) {
if (!subscribingCommunitiesInDiscoverTab[communityId].error) {
if (subscribingCommunitiesInDiscoverTab[communityId].isSubscribed) {
discoversData.forEach((item) => {
if (item.name === communityId) {
item.isSubscribed = true;
}
});
} else {
discoversData.forEach((item) => {
if (item.name === communityId) {
item.isSubscribed = false;
}
});
}
}
}
});
setDiscovers(discoversData);
}, [subscribingCommunitiesInDiscoverTab]);
useEffect(() => {
const subscribedsData = [...subscriptions];
Object.keys(subscribingCommunitiesInJoinedTab).map((communityId) => {
if (!subscribingCommunitiesInJoinedTab[communityId].loading) {
if (!subscribingCommunitiesInJoinedTab[communityId].error) {
if (subscribingCommunitiesInJoinedTab[communityId].isSubscribed) {
subscribedsData.forEach((item) => {
if (item[0] === communityId) {
item[4] = true;
}
});
} else {
subscribedsData.forEach((item) => {
if (item[0] === communityId) {
item[4] = false;
}
});
}
}
}
});
setSubscriptions(subscribedsData);
}, [subscribingCommunitiesInJoinedTab]);
// Component Functions
const _handleOnPress = (name) => {
navigation.navigate({
routeName: ROUTES.SCREENS.COMMUNITY,
params: {
tag: name,
},
});
};
const _handleSubscribeButtonPress = (data, screen) => {
let subscribeAction;
let successToastText = '';
let failToastText = '';
if (!data.isSubscribed) {
subscribeAction = subscribeCommunity;
successToastText = intl.formatMessage({
id: 'alert.success_subscribe',
});
failToastText = intl.formatMessage({
id: 'alert.fail_subscribe',
});
} else {
subscribeAction = leaveCommunity;
successToastText = intl.formatMessage({
id: 'alert.success_leave',
});
failToastText = intl.formatMessage({
id: 'alert.fail_leave',
});
}
dispatch(
subscribeAction(currentAccount, pinCode, data, successToastText, failToastText, screen),
);
};
return (
children &&
children({
subscriptions,
discovers,
subscribingCommunitiesInDiscoverTab,
subscribingCommunitiesInJoinedTab,
handleOnPress: _handleOnPress,
handleSubscribeButtonPress: _handleSubscribeButtonPress,
})
);
};
export default withNavigation(CommunitiesContainer);

View File

@ -0,0 +1,3 @@
import Communities from './view/communitiesScreen';
export default Communities;

View File

@ -0,0 +1,106 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { FlatList, View, Text, TouchableOpacity } from 'react-native';
import get from 'lodash/get';
import { SafeAreaView } from 'react-navigation';
import ScrollableTabView from 'react-native-scrollable-tab-view';
// Components
import {
FilterBar,
UserAvatar,
TabBar,
BasicHeader,
CommunitiesList,
SubscribedCommunitiesList,
} from '../../../components';
import { CommunitiesPlaceHolder } from '../../../components/basicUIElements';
import CommunitiesContainer from '../container/communitiesContainer';
import DEFAULT_IMAGE from '../../../assets/no_image.png';
import Tag from '../../../components/basicUIElements/view/tag/tagView';
import styles from './communitiesScreenStyles';
import globalStyles from '../../../globalStyles';
const CommunitiesScreen = ({ navigation, searchValue }) => {
const intl = useIntl();
const _renderEmptyContent = () => {
return (
<>
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
</>
);
};
const _renderTabbar = () => (
<TabBar
style={styles.tabbar}
tabUnderlineDefaultWidth={80}
tabUnderlineScaleX={2}
tabBarPosition="overlayTop"
textStyle={styles.tabBarText}
/>
);
return (
<CommunitiesContainer>
{({
subscriptions,
discovers,
handleOnPress,
handleSubscribeButtonPress,
subscribingCommunitiesInDiscoverTab,
subscribingCommunitiesInJoinedTab,
}) => {
return (
<View style={styles.container}>
<SafeAreaView forceInset={{ bottom: 'never' }} style={{ flex: 1 }}>
<BasicHeader
title={intl.formatMessage({
id: 'side_menu.communities',
})}
/>
<ScrollableTabView
style={globalStyles.tabView}
renderTabBar={_renderTabbar}
prerenderingSiblingsNumber={Infinity}
>
<View
tabLabel={intl.formatMessage({ id: 'communities.joined' })}
style={styles.tabbarItem}
>
<SubscribedCommunitiesList
data={subscriptions}
subscribingCommunities={subscribingCommunitiesInJoinedTab}
handleSubscribeButtonPress={handleSubscribeButtonPress}
handleOnPress={handleOnPress}
/>
</View>
<View
tabLabel={intl.formatMessage({ id: 'communities.discover' })}
style={styles.tabbarItem}
>
<CommunitiesList
data={discovers}
subscribingCommunities={subscribingCommunitiesInDiscoverTab}
handleOnPress={handleOnPress}
handleSubscribeButtonPress={handleSubscribeButtonPress}
isLoggedIn={true}
noResult={discovers.length === 0}
screen="communitiesScreenDiscoverTab"
/>
</View>
</ScrollableTabView>
</SafeAreaView>
</View>
);
}}
</CommunitiesContainer>
);
};
export default CommunitiesScreen;

View File

@ -0,0 +1,51 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
itemWrapper: {
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 8,
borderRadius: 8,
backgroundColor: '$primaryBackgroundColor',
flexDirection: 'row',
alignItems: 'center',
},
itemWrapperGray: {
backgroundColor: '$primaryLightBackground',
},
username: {
marginLeft: 10,
color: '$primaryBlack',
},
communityWrapper: {
paddingHorizontal: 16,
paddingTop: 10,
paddingBottom: 10,
borderRadius: 8,
backgroundColor: '$primaryBackgroundColor',
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
subscribeButton: {
maxWidth: 75,
borderWidth: 1,
borderColor: '$primaryBlue',
},
subscribeButtonText: {
textAlign: 'center',
color: '$primaryBlue',
},
community: {
justifyContent: 'center',
marginLeft: 15,
color: '$primaryBlack',
},
tabbarItem: {
flex: 1,
},
});

View File

@ -13,7 +13,7 @@ import styles from './communityStyles';
import { GLOBAL_POST_FILTERS, GLOBAL_POST_FILTERS_VALUE } from '../../../constants/options/filters';
const TagResultScreen = ({ navigation }) => {
const CommunityScreen = ({ navigation }) => {
const tag = navigation.getParam('tag', '');
const filter = navigation.getParam('filter', '');
@ -114,4 +114,4 @@ const TagResultScreen = ({ navigation }) => {
);
};
export default TagResultScreen;
export default CommunityScreen;

View File

@ -47,6 +47,7 @@ const DraftsScreen = ({
const tags = item.tags ? item.tags.split(/[ ,]+/) : [];
const tag = tags[0] || '';
const image = catchDraftImage(item.body);
const thumbnail = catchDraftImage(item.body, 'match', true);
const summary = postBodySummary({ item, last_update: item.created }, 100);
const isSchedules = type === 'schedules';
@ -57,7 +58,8 @@ const DraftsScreen = ({
title={item.title}
summary={summary}
isFormatedDate={isSchedules}
image={image ? { uri: catchDraftImage(item.body) } : null}
image={image ? { uri: image } : null}
thumbnail={thumbnail ? { uri: thumbnail } : null}
username={currentAccount.name}
reputation={currentAccount.reputation}
handleOnPressItem={() => (isSchedules ? setSelectedId(item._id) : editDraft(item._id))}

View File

@ -24,6 +24,7 @@ import AccountBoost from './accountBoost/screen/accountBoostScreen';
import Register from './register/registerScreen';
import TagResult from './tagResult';
import { Community } from './community';
import Communities from './communities';
export {
Bookmarks,
@ -52,4 +53,5 @@ export {
Wallet,
TagResult,
Community,
Communities,
};

View File

@ -1,111 +0,0 @@
import { useState, useEffect } from 'react';
import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux';
import isEmpty from 'lodash/isEmpty';
import ROUTES from '../../../constants/routeNames';
import {
subscribeCommunity,
getCommunities,
getSubscriptions,
} from '../../../providers/hive/dhive';
const CommunitiesContainer = ({
children,
navigation,
searchValue,
currentAccount,
pinCode,
isLoggedIn,
}) => {
const [data, setData] = useState();
const [filterIndex, setFilterIndex] = useState(1);
const [query, setQuery] = useState('');
const [sort, setSort] = useState('rank');
const [allSubscriptions, setAllSubscriptions] = useState([]);
const [noResult, setNoResult] = useState(false);
useEffect(() => {
let isCancelled = false;
setData([]);
if (sort === 'my') {
setNoResult(true);
} else {
getCommunities('', 100, query, sort).then((res) => {
if (!isCancelled) {
if (!isEmpty(res)) {
setData(res);
setNoResult(false);
} else {
setNoResult(true);
}
}
});
}
return () => {
isCancelled = true;
};
}, [query, sort]);
useEffect(() => {
setData([]);
setQuery(searchValue);
setNoResult(false);
}, [searchValue]);
useEffect(() => {
let isCancelled = false;
if (data && !isCancelled) {
getSubscriptions(currentAccount.username).then((result) => {
if (result) {
setAllSubscriptions(result);
}
});
}
return () => {
isCancelled = true;
};
}, [data]);
// Component Functions
const _handleOnVotersDropdownSelect = (index, value) => {
setFilterIndex(index);
setSort(value);
};
const _handleOnPress = (name) => {
navigation.navigate({
routeName: ROUTES.SCREENS.COMMUNITY,
params: {
tag: name,
},
});
};
const _handleSubscribeButtonPress = (_data) => {
return subscribeCommunity(currentAccount, pinCode, _data);
};
return (
children &&
children({
data,
filterIndex,
allSubscriptions,
handleOnVotersDropdownSelect: _handleOnVotersDropdownSelect,
handleOnPress: _handleOnPress,
handleSubscribeButtonPress: _handleSubscribeButtonPress,
isLoggedIn,
noResult,
})
);
};
const mapStateToProps = (state) => ({
currentAccount: state.account.currentAccount,
pinCode: state.application.pin,
isLoggedIn: state.application.isLoggedIn,
});
export default connect(mapStateToProps)(withNavigation(CommunitiesContainer));

View File

@ -1,84 +0,0 @@
import { useState, useEffect } from 'react';
import get from 'lodash/get';
import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux';
import ROUTES from '../../../constants/routeNames';
import { lookupAccounts, getTrendingTags } from '../../../providers/hive/dhive';
import { getLeaderboard } from '../../../providers/ecency/ecency';
const OtherResultContainer = (props) => {
const [users, setUsers] = useState([]);
const [tags, setTags] = useState([]);
const [filterIndex, setFilterIndex] = useState(0);
const { children, navigation, searchValue, username } = props;
useEffect(() => {
setUsers([]);
setTags([]);
if (searchValue) {
lookupAccounts(searchValue.replace(/\s+/g, '')).then((res) => {
setUsers(res);
});
getTrendingTags(searchValue.replace(/\s+/g, '')).then((res) => {
setTags(res);
});
} else {
getLeaderboard().then((result) => {
const sos = result.map((item) => item._id);
setUsers(sos);
});
}
}, [searchValue]);
// Component Functions
const _handleOnPress = (item) => {
switch (filterIndex) {
case 0:
navigation.navigate({
routeName: item === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE,
params: {
username: item,
},
key: item.text,
});
break;
case 1:
navigation.navigate({
routeName: ROUTES.SCREENS.TAG_RESULT,
params: {
tag: get(item, 'name', ''),
},
});
break;
default:
break;
}
};
const _handleFilterChanged = (index, value) => {
setFilterIndex(index);
};
return (
children &&
children({
users,
tags,
filterIndex,
handleOnPress: _handleOnPress,
handleFilterChanged: _handleFilterChanged,
})
);
};
const mapStateToProps = (state) => ({
username: state.account.currentAccount.name,
});
export default connect(mapStateToProps)(withNavigation(OtherResultContainer));

View File

@ -1,91 +0,0 @@
import { useState, useEffect } from 'react';
import get from 'lodash/get';
import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux';
import ROUTES from '../../../constants/routeNames';
import { search, getPromotePosts } from '../../../providers/ecency/ecency';
import { getPost } from '../../../providers/hive/dhive';
const PostResultContainer = ({ children, navigation, searchValue, currentAccountUsername }) => {
const [data, setData] = useState([]);
const [filterIndex, setFilterIndex] = useState(0);
const [sort, setSort] = useState('relevance');
const [scrollId, setScrollId] = useState('');
useEffect(() => {
setData([]);
if (searchValue) {
search({ q: searchValue, sort }).then((res) => {
setScrollId(res.scroll_id);
setData(res.results);
});
} else {
getPromotePosts()
.then((result) => {
return Promise.all(
result.map((item) =>
getPost(
get(item, 'author'),
get(item, 'permlink'),
currentAccountUsername,
true,
).then((post) => {
post.author_rep = post.author_reputation;
post.body = (post.summary && post.summary.substring(0, 130)) || '';
return post;
}),
),
);
})
.then((result) => {
setData(result);
});
}
}, [searchValue, sort]);
// Component Functions
const _handleOnPress = (item) => {
navigation.navigate({
routeName: ROUTES.SCREENS.POST,
params: {
author: get(item, 'author'),
permlink: get(item, 'permlink'),
},
key: get(item, 'permlink'),
});
};
const _handleFilterChanged = (index, value) => {
setFilterIndex(index);
setSort(value);
};
const _loadMore = (index, value) => {
if (scrollId) {
search({ q: searchValue, sort, scroll_id: scrollId }).then((res) => {
setData([...data, ...res.results]);
});
}
};
return (
children &&
children({
data,
filterIndex,
handleOnPress: _handleOnPress,
handleFilterChanged: _handleFilterChanged,
loadMore: _loadMore,
})
);
};
const mapStateToProps = (state) => ({
currentAccountUsername: state.account.currentAccount.username,
});
export default connect(mapStateToProps)(withNavigation(PostResultContainer));

View File

@ -1,108 +0,0 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { FlatList, View, Text, TouchableOpacity } from 'react-native';
import get from 'lodash/get';
// Components
import { FilterBar, UserAvatar } from '../../../components';
import CommunitiesList from './communitiesList';
import { CommunitiesPlaceHolder } from '../../../components/basicUIElements';
import CommunitiesContainer from '../container/communitiesContainer';
import styles from './otherResultsStyles';
import DEFAULT_IMAGE from '../../../assets/no_image.png';
import Tag from '../../../components/basicUIElements/view/tag/tagView';
const filterOptions = ['my', 'rank', 'subs', 'new'];
const CommunitiesScreen = ({ navigation, searchValue }) => {
const intl = useIntl();
const activeVotes = get(navigation, 'state.params.activeVotes');
const _renderEmptyContent = () => {
return (
<>
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
</>
);
};
return (
<CommunitiesContainer data={activeVotes} searchValue={searchValue}>
{({
data,
filterIndex,
allSubscriptions,
handleOnVotersDropdownSelect,
handleOnPress,
handleSubscribeButtonPress,
isLoggedIn,
noResult,
}) => (
<>
<FilterBar
dropdownIconName="arrow-drop-down"
options={filterOptions.map((item) =>
intl.formatMessage({
id: `search_result.communities_filter.${item}`,
}),
)}
defaultText={intl.formatMessage({
id: `search_result.communities_filter.${filterOptions[filterIndex]}`,
})}
selectedOptionIndex={filterIndex}
onDropdownSelect={(index) => handleOnVotersDropdownSelect(index, filterOptions[index])}
/>
{filterIndex !== 0 && (
<CommunitiesList
votes={data}
allSubscriptions={allSubscriptions}
handleOnPress={handleOnPress}
handleSubscribeButtonPress={handleSubscribeButtonPress}
isLoggedIn={isLoggedIn}
noResult={noResult}
/>
)}
{filterIndex === 0 && allSubscriptions && allSubscriptions.length > 0 && (
<FlatList
data={allSubscriptions}
//keyExtractor={(item, ind) => `${item}-${ind}`}
renderItem={({ item, index }) => (
<View style={[styles.communityWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<View style={{ flex: 3, flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity onPress={() => handleOnPress(item[0])}>
<UserAvatar username={item[0]} defaultSource={DEFAULT_IMAGE} noAction />
</TouchableOpacity>
<TouchableOpacity onPress={() => handleOnPress(item[0])}>
<Text style={styles.community}>{item[1]}</Text>
</TouchableOpacity>
</View>
<View style={{ flex: 1 }}>
<Tag
style={styles.subscribeButton}
textStyle={styles.subscribeButtonText}
value={intl.formatMessage({
id: 'search_result.communities.unsubscribe',
})}
isPin={false}
isFilter
onPress={() =>
handleSubscribeButtonPress({ isSubscribed: true, communityId: item[0] })
}
/>
</View>
</View>
)}
ListEmptyComponent={_renderEmptyContent}
/>
)}
</>
)}
</CommunitiesContainer>
);
};
export default CommunitiesScreen;

View File

@ -1,107 +0,0 @@
import React from 'react';
import { SafeAreaView, FlatList, View, Text, TouchableOpacity } from 'react-native';
import { useIntl } from 'react-intl';
// Components
import { FilterBar, UserAvatar } from '../../../components';
import { CommunitiesPlaceHolder } from '../../../components/basicUIElements';
import OtherResultContainer from '../container/otherResultContainer';
import styles from './otherResultsStyles';
import DEFAULT_IMAGE from '../../../assets/no_image.png';
const filterOptions = ['user', 'tag'];
const OtherResult = ({ navigation, searchValue }) => {
const intl = useIntl();
const _renderUserItem = (item, index) => (
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<UserAvatar username={item} defaultSource={DEFAULT_IMAGE} noAction />
<Text style={styles.username}>{item}</Text>
</View>
);
const _renderTagItem = (item, index) => (
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<Text style={styles.username}>{`#${item.name}`}</Text>
</View>
);
const _renderEmptyContent = () => {
return (
<>
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
<CommunitiesPlaceHolder />
</>
);
};
const _renderList = (users, tags, filterIndex, handleOnPress) => {
switch (filterIndex) {
case 0:
if (users && users.length > 0) {
return (
<FlatList
data={users}
keyExtractor={(item, ind) => `${item}-${ind}`}
renderItem={({ item, index }) => (
<TouchableOpacity onPress={() => handleOnPress(item)}>
{_renderUserItem(item, index)}
</TouchableOpacity>
)}
ListEmptyComponent={_renderEmptyContent}
/>
);
}
case 1:
if (tags && tags.length > 0) {
return (
<FlatList
data={tags}
keyExtractor={(item) => `${item.name}-${item.comments}`}
renderItem={({ item, index }) => (
<TouchableOpacity onPress={() => handleOnPress(item)}>
{_renderTagItem(item, index)}
</TouchableOpacity>
)}
ListEmptyComponent={_renderEmptyContent}
/>
);
}
default:
break;
}
};
return (
<OtherResultContainer searchValue={searchValue}>
{({ users, tags, filterIndex, handleFilterChanged, handleOnPress, loadMore }) => (
<SafeAreaView style={styles.container}>
<FilterBar
dropdownIconName="arrow-drop-down"
options={filterOptions.map((item) =>
intl.formatMessage({
id: `search_result.other_result_filter.${item}`,
}),
)}
defaultText={intl.formatMessage({
id: `search_result.other_result_filter.${filterOptions[filterIndex]}`,
})}
selectedOptionIndex={filterIndex}
onDropdownSelect={(index) => handleFilterChanged(index, filterOptions[index])}
/>
{_renderList(users, tags, filterIndex, handleOnPress)}
</SafeAreaView>
)}
</OtherResultContainer>
);
};
export default OtherResult;

View File

@ -2,12 +2,14 @@ import React, { useState, useEffect } from 'react';
import { View, SafeAreaView } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import { useIntl } from 'react-intl';
import { debounce } from 'lodash';
// Components
import { SearchInput, TabBar } from '../../../components';
import Communities from './communities';
import PostResult from './postResult';
import OtherResult from './otherResults';
import { SearchInput, TabBar, IconButton } from '../../../components';
import Communities from './tabs/communities/view/communitiesResults';
import PostsResults from './tabs/best/view/postsResults';
import TopicsResults from './tabs/topics/view/topicsResults';
import PeopleResults from './tabs/people/view/peopleResults';
// Styles
import styles from './searchResultStyles';
@ -15,17 +17,8 @@ import globalStyles from '../../../globalStyles';
const SearchResultScreen = ({ navigation }) => {
const [searchValue, setSearchValue] = useState('');
const [text, setText] = useState('');
const intl = useIntl();
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
setSearchValue(text);
}, 100);
return () => clearTimeout(delayDebounceFn);
}, [text]);
const _navigationGoBack = () => {
navigation.goBack();
};
@ -36,38 +29,64 @@ const SearchResultScreen = ({ navigation }) => {
tabUnderlineDefaultWidth={80}
tabUnderlineScaleX={2}
tabBarPosition="overlayTop"
textStyle={styles.tabBarText}
/>
);
const _handleChangeText = debounce((value) => {
setSearchValue(value);
}, 250);
return (
<View style={styles.container}>
<SafeAreaView>
<View style={styles.headerContainer}>
<View style={{ flex: 11 }}>
<SearchInput
handleOnModalClose={_navigationGoBack}
//handleOnModalClose={_navigationGoBack}
placeholder={intl.formatMessage({ id: 'header.search' })}
onChangeText={setText}
onChangeText={_handleChangeText}
/>
</View>
<View style={{ flex: 1, marginTop: 20 }}>
<IconButton
iconType="Ionicons"
name="ios-close-circle-outline"
iconStyle={styles.backIcon}
onPress={_navigationGoBack}
/>
</View>
</View>
</SafeAreaView>
<ScrollableTabView
style={globalStyles.tabView}
renderTabBar={_renderTabbar}
prerenderingSiblingsNumber={Infinity}
>
<View
tabLabel={intl.formatMessage({ id: 'search_result.best.title' })}
style={styles.tabbarItem}
>
<PostsResults searchValue={searchValue} />
</View>
<View
tabLabel={intl.formatMessage({ id: 'search_result.people.title' })}
style={styles.tabbarItem}
>
<PeopleResults searchValue={searchValue} />
</View>
<View
tabLabel={intl.formatMessage({ id: 'search_result.topics.title' })}
style={styles.tabbarItem}
>
<TopicsResults searchValue={searchValue} />
</View>
<View
tabLabel={intl.formatMessage({ id: 'search_result.communities.title' })}
style={styles.tabbarItem}
>
<Communities searchValue={searchValue} />
</View>
<View tabLabel={intl.formatMessage({ id: 'search.posts' })} style={styles.tabbarItem}>
<PostResult searchValue={searchValue} />
</View>
<View
tabLabel={intl.formatMessage({ id: 'search_result.others' })}
style={styles.tabbarItem}
>
<OtherResult searchValue={searchValue} />
</View>
</ScrollableTabView>
</View>
);

View File

@ -5,6 +5,11 @@ export default EStyleSheet.create({
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
headerContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingRight: 12,
},
buttonContainer: {
width: '50%',
alignItems: 'center',
@ -28,4 +33,12 @@ export default EStyleSheet.create({
tabs: {
flex: 1,
},
tabBarText: {
fontSize: 14,
},
backIcon: {
fontSize: 24,
color: '$iconColor',
justifyContent: 'center',
},
});

View File

@ -0,0 +1,106 @@
import { useState, useEffect } from 'react';
import get from 'lodash/get';
import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux';
import ROUTES from '../../../../../../constants/routeNames';
import { search, getPromotePosts } from '../../../../../../providers/ecency/ecency';
import { getPost, getAccountPosts } from '../../../../../../providers/hive/dhive';
const PostsResultsContainer = ({ children, navigation, searchValue, currentAccountUsername }) => {
const [data, setData] = useState([]);
const [sort, setSort] = useState('newest');
const [scrollId, setScrollId] = useState('');
const [noResult, setNoResult] = useState(false);
useEffect(() => {
setNoResult(false);
setData([]);
if (searchValue) {
search({ q: `${searchValue} type:post`, sort })
.then((res) => {
setScrollId(res.scroll_id);
setData(res.results);
if (res.results.length === 0) {
setNoResult(true);
}
})
.catch((err) => console.log(err, 'search error'));
} else {
getInitialPosts();
}
}, [searchValue]);
const getInitialPosts = async () => {
// const promoteds = await getPromotePosts();
// return await Promise.all(
// promoteds.map(async (item) => {
// const post = await getPost(
// get(item, 'author'),
// get(item, 'permlink'),
// currentAccountUsername,
// true,
// );
// post.author_rep = post.author_reputation;
// post.body = (post.summary && post.summary.substring(0, 130)) || '';
// // return await call to your function
// return post;
// }),
// );
try {
const options = {
observer: currentAccountUsername,
account: 'ecency',
limit: 7,
sort: 'blog',
};
const _data = await getAccountPosts(options);
if (_data.length === 0) {
setNoResult(true);
}
setData(_data);
} catch (err) {
console.log(err);
}
};
// Component Functions
const _handleOnPress = (item) => {
navigation.navigate({
routeName: ROUTES.SCREENS.POST,
params: {
author: get(item, 'author'),
permlink: get(item, 'permlink'),
},
key: get(item, 'permlink'),
});
};
const _loadMore = (index, value) => {
if (scrollId) {
search({ q: `${searchValue} type:post`, sort, scroll_id: scrollId }).then((res) => {
setData([...data, ...res.results]);
});
}
};
return (
children &&
children({
data,
handleOnPress: _handleOnPress,
loadMore: _loadMore,
noResult,
})
);
};
const mapStateToProps = (state) => ({
currentAccountUsername: state.account.currentAccount.username,
});
export default connect(mapStateToProps)(withNavigation(PostsResultsContainer));

View File

@ -6,28 +6,35 @@ import FastImage from 'react-native-fast-image';
import { useIntl } from 'react-intl';
// Components
import { PostHeaderDescription, FilterBar } from '../../../components';
import { TextWithIcon, CommunitiesPlaceHolder } from '../../../components/basicUIElements';
import PostResultContainer from '../container/postResultContainer';
import { PostHeaderDescription, FilterBar } from '../../../../../../components';
import {
TextWithIcon,
CommunitiesPlaceHolder,
EmptyScreen,
} from '../../../../../../components/basicUIElements';
import PostsResultsContainer from '../container/postsResultsContainer';
import { getTimeFromNow } from '../../../utils/time';
import { getTimeFromNow } from '../../../../../../utils/time';
import styles from './postResultStyles';
import styles from './postsResultsStyles';
import DEFAULT_IMAGE from '../../../assets/no_image.png';
import DEFAULT_IMAGE from '../../../../../../assets/no_image.png';
const filterOptions = ['relevance', 'popularity', 'newest'];
const PostResult = ({ navigation, searchValue }) => {
const PostsResults = ({ navigation, searchValue }) => {
const intl = useIntl();
const _renderItem = (item, index) => {
const reputation =
get(item, 'author_rep', undefined) || get(item, 'author_reputation', undefined);
return (
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<PostHeaderDescription
date={getTimeFromNow(get(item, 'created_at'))}
name={get(item, 'author')}
reputation={Math.floor(get(item, 'author_rep'))}
reputation={Math.floor(reputation)}
size={36}
tag={item.category}
/>
@ -78,25 +85,15 @@ const PostResult = ({ navigation, searchValue }) => {
};
return (
<PostResultContainer searchValue={searchValue}>
{({ data, filterIndex, handleFilterChanged, handleOnPress, loadMore }) => (
<PostsResultsContainer searchValue={searchValue}>
{({ data, handleOnPress, loadMore, noResult }) => (
<SafeAreaView style={styles.container}>
<FilterBar
dropdownIconName="arrow-drop-down"
options={filterOptions.map((item) =>
intl.formatMessage({
id: `search_result.post_result_filter.${item}`,
}),
)}
defaultText={intl.formatMessage({
id: `search_result.post_result_filter.${filterOptions[filterIndex]}`,
})}
selectedOptionIndex={filterIndex}
onDropdownSelect={(index) => handleFilterChanged(index, filterOptions[index])}
/>
{noResult ? (
<EmptyScreen />
) : (
<FlatList
data={data}
keyExtractor={(item) => item.id && item.id.toString()}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<TouchableOpacity onPress={() => handleOnPress(item)}>
{_renderItem(item, index)}
@ -106,10 +103,11 @@ const PostResult = ({ navigation, searchValue }) => {
ListEmptyComponent={_renderEmptyContent}
ListFooterComponent={<CommunitiesPlaceHolder />}
/>
)}
</SafeAreaView>
)}
</PostResultContainer>
</PostsResultsContainer>
);
};
export default PostResult;
export default PostsResults;

View File

@ -0,0 +1,150 @@
import { useState, useEffect } from 'react';
import { withNavigation } from 'react-navigation';
import { useSelector, useDispatch } from 'react-redux';
import { useIntl } from 'react-intl';
import { shuffle } from 'lodash';
import ROUTES from '../../../../../../constants/routeNames';
import { getCommunities, getSubscriptions } from '../../../../../../providers/hive/dhive';
import {
subscribeCommunity,
leaveCommunity,
} from '../../../../../../redux/actions/communitiesAction';
// const DEFAULT_COMMUNITIES = [
// 'hive-125125',
// 'hive-174301',
// 'hive-140217',
// 'hive-179017',
// 'hive-160545',
// 'hive-194913',
// 'hive-166847',
// 'hive-176853',
// 'hive-183196',
// 'hive-163772',
// 'hive-106444',
// ];
const CommunitiesResultsContainer = ({ children, navigation, searchValue }) => {
const intl = useIntl();
const dispatch = useDispatch();
const [data, setData] = useState([]);
const [noResult, setNoResult] = useState(false);
const pinCode = useSelector((state) => state.application.pin);
const currentAccount = useSelector((state) => state.account.currentAccount);
const isLoggedIn = useSelector((state) => state.application.isLoggedIn);
const subscribingCommunities = useSelector(
(state) => state.communities.subscribingCommunitiesInSearchResultsScreen,
);
useEffect(() => {
setData([]);
setNoResult(false);
getSubscriptions(currentAccount.username).then((subs) => {
getCommunities('', searchValue ? 100 : 20, searchValue, 'rank').then((communities) => {
communities.forEach((community) =>
Object.assign(community, {
isSubscribed: subs.some(
(subscribedCommunity) => subscribedCommunity[0] === community.name,
),
}),
);
if (searchValue) {
setData(communities);
} else {
setData(shuffle(communities));
}
if (communities.length === 0) {
setNoResult(true);
}
});
});
}, [searchValue]);
useEffect(() => {
const communitiesData = [...data];
Object.keys(subscribingCommunities).map((communityId) => {
if (!subscribingCommunities[communityId].loading) {
if (!subscribingCommunities[communityId].error) {
if (subscribingCommunities[communityId].isSubscribed) {
communitiesData.forEach((item) => {
if (item.name === communityId) {
item.isSubscribed = true;
}
});
} else {
communitiesData.forEach((item) => {
if (item.name === communityId) {
item.isSubscribed = false;
}
});
}
}
}
});
setData(communitiesData);
}, [subscribingCommunities]);
// Component Functions
const _handleOnPress = (name) => {
navigation.navigate({
routeName: ROUTES.SCREENS.COMMUNITY,
params: {
tag: name,
},
});
};
const _handleSubscribeButtonPress = (_data, screen) => {
let subscribeAction;
let successToastText = '';
let failToastText = '';
if (!_data.isSubscribed) {
subscribeAction = subscribeCommunity;
successToastText = intl.formatMessage({
id: 'alert.success_subscribe',
});
failToastText = intl.formatMessage({
id: 'alert.fail_subscribe',
});
} else {
subscribeAction = leaveCommunity;
successToastText = intl.formatMessage({
id: 'alert.success_leave',
});
failToastText = intl.formatMessage({
id: 'alert.fail_leave',
});
}
dispatch(
subscribeAction(currentAccount, pinCode, _data, successToastText, failToastText, screen),
);
};
return (
children &&
children({
data,
subscribingCommunities,
handleOnPress: _handleOnPress,
handleSubscribeButtonPress: _handleSubscribeButtonPress,
isLoggedIn,
noResult,
})
);
};
export default withNavigation(CommunitiesResultsContainer);

View File

@ -0,0 +1,40 @@
import React from 'react';
import get from 'lodash/get';
// Components
import { CommunitiesList, EmptyScreen } from '../../../../../../components';
import CommunitiesResultsContainer from '../container/communitiesResultsContainer';
const CommunitiesResultsScreen = ({ navigation, searchValue }) => {
const activeVotes = get(navigation, 'state.params.activeVotes');
return (
<CommunitiesResultsContainer data={activeVotes} searchValue={searchValue}>
{({
data,
subscribingCommunities,
handleOnPress,
handleSubscribeButtonPress,
isLoggedIn,
noResult,
}) =>
noResult ? (
<EmptyScreen />
) : (
<CommunitiesList
data={data}
subscribingCommunities={subscribingCommunities}
handleOnPress={handleOnPress}
handleSubscribeButtonPress={handleSubscribeButtonPress}
isLoggedIn={isLoggedIn}
noResult={noResult}
screen="searchResultsScreen"
/>
)
}
</CommunitiesResultsContainer>
);
};
export default CommunitiesResultsScreen;

View File

@ -0,0 +1,65 @@
import { useState, useEffect } from 'react';
import get from 'lodash/get';
import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux';
import ROUTES from '../../../../../../constants/routeNames';
import { lookupAccounts } from '../../../../../../providers/hive/dhive';
import { getLeaderboard } from '../../../../../../providers/ecency/ecency';
const PeopleResultsContainer = (props) => {
const [users, setUsers] = useState([]);
const [noResult, setNoResult] = useState(false);
const { children, navigation, searchValue, username } = props;
useEffect(() => {
setNoResult(false);
setUsers([]);
if (searchValue) {
lookupAccounts(searchValue).then((res) => {
if (res.length === 0) {
setNoResult(true);
}
setUsers(res);
});
} else {
getLeaderboard().then((result) => {
const sos = result.map((item) => item._id);
if (sos.length === 0) {
setNoResult(true);
}
setUsers(sos);
});
}
}, [searchValue]);
// Component Functions
const _handleOnPress = (item) => {
navigation.navigate({
routeName: item === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE,
params: {
username: item,
},
key: item.text,
});
};
return (
children &&
children({
users,
handleOnPress: _handleOnPress,
noResult,
})
);
};
const mapStateToProps = (state) => ({
username: state.account.currentAccount.name,
});
export default connect(mapStateToProps)(withNavigation(PeopleResultsContainer));

View File

@ -0,0 +1,52 @@
import React from 'react';
import { SafeAreaView, FlatList } from 'react-native';
import { useIntl } from 'react-intl';
// Components
import {
ListPlaceHolder,
EmptyScreen,
UserListItem,
} from '../../../../../../components/basicUIElements';
import PeopleResultsContainer from '../container/peopleResultsContainer';
import styles from './peopleResultsStyles';
const PeopleResults = ({ navigation, searchValue }) => {
const intl = useIntl();
const _renderEmptyContent = () => {
return (
<>
<ListPlaceHolder />
</>
);
};
return (
<PeopleResultsContainer searchValue={searchValue}>
{({ users, handleOnPress, noResult }) => (
<SafeAreaView style={styles.container}>
{noResult ? (
<EmptyScreen />
) : (
<FlatList
data={users}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<UserListItem
handleOnPress={() => handleOnPress(item)}
index={index}
username={item}
/>
)}
ListEmptyComponent={_renderEmptyContent}
/>
)}
</SafeAreaView>
)}
</PeopleResultsContainer>
);
};
export default PeopleResults;

View File

@ -0,0 +1,57 @@
import { useState, useEffect } from 'react';
import get from 'lodash/get';
import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux';
import ROUTES from '../../../../../../constants/routeNames';
import { getTrendingTags } from '../../../../../../providers/hive/dhive';
import { getLeaderboard } from '../../../../../../providers/ecency/ecency';
import { isCommunity } from '../../../../../../utils/communityValidation';
const OtherResultContainer = (props) => {
const [tags, setTags] = useState([]);
const [noResult, setNoResult] = useState(false);
const { children, navigation, searchValue } = props;
useEffect(() => {
if (searchValue.length <= 10) {
setNoResult(false);
setTags([]);
getTrendingTags(searchValue.trim(), 100).then((res) => {
const data = res?.filter((item) => !isCommunity(item.name));
if (data.length === 0) {
setNoResult(true);
}
setTags(data);
});
}
}, [searchValue]);
// Component Functions
const _handleOnPress = (item) => {
navigation.navigate({
routeName: ROUTES.SCREENS.TAG_RESULT,
params: {
tag: get(item, 'name', ''),
},
});
};
return (
children &&
children({
tags,
handleOnPress: _handleOnPress,
noResult,
})
);
};
const mapStateToProps = (state) => ({
username: state.account.currentAccount.name,
});
export default connect(mapStateToProps)(withNavigation(OtherResultContainer));

View File

@ -0,0 +1,54 @@
import React from 'react';
import { SafeAreaView, FlatList, View, Text, TouchableOpacity } from 'react-native';
import { useIntl } from 'react-intl';
// Components
import { ListPlaceHolder, EmptyScreen } from '../../../../../../components/basicUIElements';
import TopicsResultsContainer from '../container/topicsResultsContainer';
import styles from './topicsResultsStyles';
const filterOptions = ['user', 'tag'];
const TopicsResults = ({ navigation, searchValue }) => {
const intl = useIntl();
const _renderTagItem = (item, index) => (
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<Text style={styles.username}>{`#${item.name}`}</Text>
</View>
);
const _renderEmptyContent = () => {
return (
<>
<ListPlaceHolder />
</>
);
};
return (
<TopicsResultsContainer searchValue={searchValue}>
{({ tags, handleOnPress, noResult }) => (
<SafeAreaView style={styles.container}>
{noResult ? (
<EmptyScreen />
) : (
<FlatList
data={tags}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<TouchableOpacity onPress={() => handleOnPress(item)}>
{_renderTagItem(item, index)}
</TouchableOpacity>
)}
ListEmptyComponent={_renderEmptyContent}
/>
)}
</SafeAreaView>
)}
</TopicsResultsContainer>
);
};
export default TopicsResults;

View File

@ -0,0 +1,48 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor',
},
itemWrapper: {
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 8,
borderRadius: 8,
backgroundColor: '$primaryBackgroundColor',
flexDirection: 'row',
alignItems: 'center',
},
itemWrapperGray: {
backgroundColor: '$primaryLightBackground',
},
username: {
marginLeft: 10,
color: '$primaryBlack',
},
communityWrapper: {
paddingHorizontal: 16,
paddingTop: 10,
paddingBottom: 10,
borderRadius: 8,
backgroundColor: '$primaryBackgroundColor',
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
subscribeButton: {
maxWidth: 75,
borderWidth: 1,
borderColor: '$primaryBlue',
},
subscribeButtonText: {
textAlign: 'center',
color: '$primaryBlue',
},
community: {
justifyContent: 'center',
marginLeft: 15,
color: '$primaryBlack',
},
});

View File

@ -1,5 +1,7 @@
import { isNumber } from 'lodash';
export const isCommunity = (text) => {
if (/^hive-\d+/.test(text) && text.length === 11) {
if (/hive-[1-3]\d{4,6}$/.test(text) && isNumber(Number(text.split('-')[1]))) {
return true;
}

View File

@ -63,14 +63,16 @@ export const catchEntryImage = (entry, width = 0, height = 0, format = 'match')
return null;
};
export const catchDraftImage = (body, format = 'match') => {
export const catchDraftImage = (body, format = 'match', thumbnail = false) => {
const imgRegex = /(https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|PNG|GIF|JPG))/g;
format = whatOs === 'android' ? 'webp' : 'match';
if (body && imgRegex.test(body)) {
const imageMatch = body.match(imgRegex);
return proxifyImageSrc(imageMatch[0], 0, 0, format);
if (thumbnail) {
return proxifyImageSrc(imageMatch[0], 60, 50, format);
}
return proxifyImageSrc(imageMatch[0], 600, 500, format);
}
return null;
};

View File

@ -13,13 +13,13 @@ const webp = Platform.OS === 'ios' ? false : true;
export const parsePosts = (posts, currentUserName) => {
if (posts) {
const formattedPosts = posts.map((post) => parsePost(post, currentUserName));
const formattedPosts = posts.map((post) => parsePost(post, currentUserName, false, true));
return formattedPosts;
}
return null;
};
export const parsePost = (post, currentUserName, isPromoted) => {
export const parsePost = (post, currentUserName, isPromoted, isList = false) => {
if (!post) {
return null;
}
@ -33,10 +33,12 @@ export const parsePost = (post, currentUserName, isPromoted) => {
post.json_metadata = {};
}
post.image = catchPostImage(post.body, 600, 500, webp ? 'webp' : 'match');
post.thumbnail = catchPostImage(post.body, 60, 50, webp ? 'webp' : 'match');
post.author_reputation = getReputation(post.author_reputation);
post.avatar = getResizedAvatar(get(post, 'author'));
if (!isList) {
post.body = renderPostBody(post, true, webp);
}
post.summary = postBodySummary(post, 150);
post.is_declined_payout = parseAsset(post.max_accepted_payout).amount === 0;

View File

@ -81,11 +81,7 @@ export const groomingTransactionData = (transaction, steemPerMVests) => {
}
break;
case 'claim_reward_balance':
let {
reward_sbd: rewardSdb = opData.reward_hbd,
reward_steem: rewardSteem = opData.reward_hive,
reward_vests: rewardVests,
} = opData;
let { reward_hbd: rewardSdb, reward_hive: rewardSteem, reward_vests: rewardVests } = opData;
rewardSdb = parseToken(rewardSdb).toFixed(3).replace(',', '.');
rewardSteem = parseToken(rewardSteem).toFixed(3).replace(',', '.');
@ -192,15 +188,9 @@ export const groomingWalletData = async (user, globalProps, userCurrency) => {
const [userdata] = state;
// TODO: move them to utils these so big for a lifecycle function
walletData.rewardSteemBalance = parseToken(
userdata.reward_hive_balance || userdata.reward_steem_balance,
);
walletData.rewardSbdBalance = parseToken(
userdata.reward_hbd_balance || userdata.reward_sbd_balance,
);
walletData.rewardVestingSteem = parseToken(
userdata.reward_vesting_hive || userdata.reward_vesting_steem,
);
walletData.rewardSteemBalance = parseToken(userdata.reward_hive_balance);
walletData.rewardSbdBalance = parseToken(userdata.reward_hbd_balance);
walletData.rewardVestingSteem = parseToken(userdata.reward_vesting_hive);
walletData.hasUnclaimedRewards =
walletData.rewardSteemBalance > 0 ||
walletData.rewardSbdBalance > 0 ||
@ -211,11 +201,9 @@ export const groomingWalletData = async (user, globalProps, userCurrency) => {
walletData.vestingSharesReceived = parseToken(userdata.received_vesting_shares);
walletData.vestingSharesTotal =
walletData.vestingShares - walletData.vestingSharesDelegated + walletData.vestingSharesReceived;
walletData.sbdBalance = parseToken(userdata.hbd_balance || userdata.sbd_balance);
walletData.sbdBalance = parseToken(userdata.hbd_balance);
walletData.savingBalance = parseToken(userdata.savings_balance);
walletData.savingBalanceSbd = parseToken(
userdata.savings_hbd_balance || userdata.savings_sbd_balance,
);
walletData.savingBalanceSbd = parseToken(userdata.savings_hbd_balance);
const feedHistory = await getFeedHistory();
const base = parseToken(feedHistory.current_median_history.base);

View File

@ -698,10 +698,10 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@ecency/render-helper@^2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@ecency/render-helper/-/render-helper-2.0.11.tgz#faa140a318aa6c2a693cfb3d21f3e8b1821cdb8e"
integrity sha512-g9lDtdB3yrta3pHQodPQQbFIkg3kCrVvcKhrqUrl2JY0hYreKUlmRzGWobJFphyAoXnUubrTW/l8p4N3hUwteg==
"@ecency/render-helper@^2.0.15":
version "2.0.15"
resolved "https://registry.yarnpkg.com/@ecency/render-helper/-/render-helper-2.0.15.tgz#8e78a2d771d44dc6e77bf63bbf786589cf11a772"
integrity sha512-II3TAdSy3pCqcuWgL81ZJ8N8jVrQR5rosxA8kGc40/vHeoDPhgLmUUxmykJVZUubejOTtowyIpjSu8g7sJkXRg==
dependencies:
he "^1.2.0"
lru-cache "^5.1.1"
@ -7574,16 +7574,21 @@ react-native-iap@3.4.15:
dependencies:
dooboolab-welcome "^1.1.1"
react-native-image-crop-picker@^0.26.1:
version "0.26.2"
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.26.2.tgz#c70985ff6740e63569f90b185be0516d83f5933b"
integrity sha512-KnPtSJklwXr/BNdcyAlDp9xkDCQyJ5ZA4dM10elBc2aIXac/4CndbiPFZrwu4GlAYYYKlkiTTwTYW+h4RyGLaw==
react-native-image-crop-picker@^0.35.2:
version "0.35.2"
resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.35.2.tgz#21e085e40cb5724e1e986b8eba59ef23df19598c"
integrity sha512-4nHAYwnemFJLzVJPl5dhNB+WQgNp41MLJjyYCypatMZl2GJD+EVXnu5eSUQE+UOpaIEhSVtqK5Y3OQ93hMhosQ==
react-native-image-pan-zoom@^2.1.9:
version "2.1.12"
resolved "https://registry.yarnpkg.com/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz#eb98bf56fb5610379bdbfdb63219cc1baca98fd2"
integrity sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==
react-native-image-size@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/react-native-image-size/-/react-native-image-size-1.1.3.tgz#7d69c2cd4e1d1632947867e47643ed8cabb9de27"
integrity sha512-jJvN6CjXVAm69LAVZNV7m7r50Qk9vuPZwLyrbs/k31/3Xs8bZyVCdvfP44FuBisITn/yFsiOo6i8NPrFBPH20w==
react-native-image-zoom-viewer@^2.2.27:
version "2.2.27"
resolved "https://registry.yarnpkg.com/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-2.2.27.tgz#fb3314c5dc86ac33da48cb31bf4920d97eecb6eb"