mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-12-23 21:35:04 +03:00
Merge pull request #1717 from ecency/feature/community
Feature/community
This commit is contained in:
commit
5f65339ac3
@ -20,6 +20,7 @@ import WalletUnclaimedPlaceHolder from './view/placeHolder/walletUnclaimedPlaceH
|
||||
import ListPlaceHolder from './view/placeHolder/listPlaceHolderView';
|
||||
import BoostPlaceHolder from './view/placeHolder/boostPlaceHolderView';
|
||||
import CommentPlaceHolder from './view/placeHolder/commentPlaceHolderView';
|
||||
import CommunitiesPlaceHolder from './view/placeHolder/communitiesPlaceHolder';
|
||||
|
||||
export {
|
||||
Card,
|
||||
@ -42,4 +43,5 @@ export {
|
||||
WalletDetailsPlaceHolder,
|
||||
WalletLineItem,
|
||||
WalletUnclaimedPlaceHolder,
|
||||
CommunitiesPlaceHolder,
|
||||
};
|
||||
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import Placeholder from 'rn-placeholder';
|
||||
|
||||
import { ThemeContainer } from '../../../../containers';
|
||||
|
||||
import styles from './postCardPlaceHolderStyles';
|
||||
// TODO: make container for place holder wrapper after alpha
|
||||
const PostCardPlaceHolder = () => {
|
||||
return (
|
||||
<ThemeContainer>
|
||||
{({ isDarkTheme }) => {
|
||||
const color = isDarkTheme ? '#2e3d51' : '#f5f5f5';
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.paragraphWrapper}>
|
||||
<Placeholder.Paragraph
|
||||
lineNumber={4}
|
||||
color={color}
|
||||
textSize={16}
|
||||
lineSpacing={5}
|
||||
width="100%"
|
||||
lastLineWidth="70%"
|
||||
firstLineWidth="20%"
|
||||
animate="fade"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
</ThemeContainer>
|
||||
);
|
||||
};
|
||||
export default PostCardPlaceHolder;
|
@ -0,0 +1,15 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
padding: 20,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
borderTopWidth: 1,
|
||||
borderColor: '$primaryLightBackground',
|
||||
marginRight: 0,
|
||||
marginLeft: 0,
|
||||
marginTop: 0,
|
||||
},
|
||||
});
|
@ -48,7 +48,7 @@ class TagContainer extends PureComponent {
|
||||
onPress();
|
||||
} else {
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.SEARCH_RESULT,
|
||||
routeName: ROUTES.SCREENS.TAG_RESULT,
|
||||
params: {
|
||||
tag: value,
|
||||
},
|
||||
@ -57,7 +57,7 @@ class TagContainer extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isPin, value, isPostCardTag, isFilter } = this.props;
|
||||
const { isPin, value, isPostCardTag, isFilter, style, textStyle } = this.props;
|
||||
const { label } = this.state;
|
||||
|
||||
return (
|
||||
@ -68,6 +68,8 @@ class TagContainer extends PureComponent {
|
||||
isPostCardTag={isPostCardTag}
|
||||
onPress={this._handleOnTagPress}
|
||||
isFilter={isFilter}
|
||||
style={style}
|
||||
textStyle={textStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -2,14 +2,18 @@ import React from 'react';
|
||||
import { Text, View, TouchableOpacity } from 'react-native';
|
||||
import styles from './tagStyles';
|
||||
|
||||
const Tag = ({ onPress, isPin, value, label, isPostCardTag, isFilter }) => (
|
||||
<TouchableOpacity onPress={() => onPress && onPress(value)}>
|
||||
const Tag = ({ onPress, isPin, value, label, isPostCardTag, isFilter, style, textStyle }) => (
|
||||
<TouchableOpacity
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
onPress={() => onPress && onPress(value)}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.textWrapper,
|
||||
isFilter && styles.isFilter,
|
||||
isPin && styles.isPin,
|
||||
isPostCardTag && styles.isPostCardTag,
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
@ -17,6 +21,7 @@ const Tag = ({ onPress, isPin, value, label, isPostCardTag, isFilter }) => (
|
||||
styles.text,
|
||||
!isPin && isFilter && styles.isFilterTextUnPin,
|
||||
isPin && isFilter && styles.isFilterTextPin,
|
||||
textStyle,
|
||||
]}
|
||||
>
|
||||
{isPostCardTag ? label : value}
|
||||
|
@ -37,7 +37,7 @@ const TextWithIcon = ({
|
||||
name={iconName}
|
||||
iconType={iconType}
|
||||
/>
|
||||
<Text style={styles.text}>{text}</Text>
|
||||
<Text style={[styles.text, textStyle]}>{text}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
@ -8,7 +8,7 @@ import HeaderView from '../view/headerView';
|
||||
|
||||
import { AccountContainer, ThemeContainer } from '../../../containers';
|
||||
|
||||
const HeaderContainer = ({ selectedUser, isReverse, navigation, handleOnBackPress }) => {
|
||||
const HeaderContainer = ({ selectedUser, isReverse, navigation, handleOnBackPress, hideUser }) => {
|
||||
const _handleOpenDrawer = () => {
|
||||
if (has(navigation, 'openDrawer') && typeof get(navigation, 'openDrawer') === 'function') {
|
||||
navigation.openDrawer();
|
||||
@ -41,6 +41,7 @@ const HeaderContainer = ({ selectedUser, isReverse, navigation, handleOnBackPres
|
||||
isReverse={isReverse}
|
||||
reputation={get(_user, 'reputation')}
|
||||
username={get(_user, 'name')}
|
||||
hideUser={hideUser}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
@ -2,12 +2,16 @@ import React, { useState } from 'react';
|
||||
import { View, Text, SafeAreaView, TouchableOpacity } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { withNavigation } from 'react-navigation';
|
||||
|
||||
// Components
|
||||
import { SearchModal } from '../../searchModal';
|
||||
import { IconButton } from '../../iconButton';
|
||||
import { UserAvatar } from '../../userAvatar';
|
||||
|
||||
// Constants
|
||||
import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
// Styles
|
||||
import styles from './headerStyles';
|
||||
|
||||
@ -21,6 +25,8 @@ const HeaderView = ({
|
||||
isReverse,
|
||||
reputation,
|
||||
username,
|
||||
navigation,
|
||||
hideUser,
|
||||
}) => {
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
|
||||
const intl = useIntl();
|
||||
@ -32,54 +38,64 @@ const HeaderView = ({
|
||||
gradientColor = isDarkTheme ? ['#081c36', '#43638e'] : ['#2d5aa0', '#357ce6'];
|
||||
}
|
||||
|
||||
const _onPressSearchButton = () => {
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.SEARCH_RESULT,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, isReverse && styles.containerReverse]}>
|
||||
<SearchModal
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'header.search',
|
||||
})}
|
||||
isOpen={isSearchModalOpen}
|
||||
handleOnClose={() => setIsSearchModalOpen(false)}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={styles.avatarWrapper}
|
||||
onPress={handleOpenDrawer}
|
||||
disabled={isReverse}
|
||||
>
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
colors={gradientColor}
|
||||
style={[
|
||||
styles.avatarButtonWrapper,
|
||||
isReverse ? styles.avatarButtonWrapperReverse : styles.avatarDefault,
|
||||
]}
|
||||
>
|
||||
<UserAvatar
|
||||
noAction
|
||||
style={isReverse ? styles.reverseAvatar : styles.avatar}
|
||||
username={username}
|
||||
{!hideUser && (
|
||||
<>
|
||||
<SearchModal
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'header.search',
|
||||
})}
|
||||
isOpen={isSearchModalOpen}
|
||||
handleOnClose={() => setIsSearchModalOpen(false)}
|
||||
/>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
{displayName || username ? (
|
||||
<View style={styles.titleWrapper}>
|
||||
{displayName && <Text style={styles.title}>{displayName}</Text>}
|
||||
<Text style={styles.subTitle}>
|
||||
{`@${username}`}
|
||||
{reputation && ` (${reputation})`}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.titleWrapper}>
|
||||
{isLoginDone && !isLoggedIn && (
|
||||
<Text style={styles.noAuthTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'header.title',
|
||||
})}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.avatarWrapper}
|
||||
onPress={handleOpenDrawer}
|
||||
disabled={isReverse}
|
||||
>
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
colors={gradientColor}
|
||||
style={[
|
||||
styles.avatarButtonWrapper,
|
||||
isReverse ? styles.avatarButtonWrapperReverse : styles.avatarDefault,
|
||||
]}
|
||||
>
|
||||
<UserAvatar
|
||||
noAction
|
||||
style={isReverse ? styles.reverseAvatar : styles.avatar}
|
||||
username={username}
|
||||
/>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
{displayName || username ? (
|
||||
<View style={styles.titleWrapper}>
|
||||
{displayName && <Text style={styles.title}>{displayName}</Text>}
|
||||
<Text style={styles.subTitle}>
|
||||
{`@${username}`}
|
||||
{reputation && ` (${reputation})`}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.titleWrapper}>
|
||||
{isLoginDone && !isLoggedIn && (
|
||||
<Text style={styles.noAuthTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'header.title',
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isReverse ? (
|
||||
@ -93,15 +109,11 @@ const HeaderView = ({
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.backButtonWrapper}>
|
||||
<IconButton
|
||||
iconStyle={styles.backIcon}
|
||||
name="md-search"
|
||||
onPress={() => setIsSearchModalOpen(true)}
|
||||
/>
|
||||
<IconButton iconStyle={styles.backIcon} name="md-search" onPress={_onPressSearchButton} />
|
||||
</View>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderView;
|
||||
export default withNavigation(HeaderView);
|
||||
|
@ -180,7 +180,7 @@ const CommentBody = ({
|
||||
const __handleTagPress = (tag) => {
|
||||
if (tag) {
|
||||
navigate({
|
||||
routeName: ROUTES.SCREENS.SEARCH_RESULT,
|
||||
routeName: ROUTES.SCREENS.TAG_RESULT,
|
||||
params: {
|
||||
tag,
|
||||
},
|
||||
|
@ -166,7 +166,7 @@ const PostBody = ({
|
||||
const _handleTagPress = (tag) => {
|
||||
if (tag) {
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.SEARCH_RESULT,
|
||||
routeName: ROUTES.SCREENS.TAG_RESULT,
|
||||
params: {
|
||||
tag,
|
||||
},
|
||||
|
@ -65,9 +65,6 @@ const PostsView = ({
|
||||
fetchPromotePost();
|
||||
_loadPosts();
|
||||
}
|
||||
return () => {
|
||||
//unmounting
|
||||
};
|
||||
}, [
|
||||
_getPromotePosts,
|
||||
_loadPosts,
|
||||
|
@ -503,5 +503,33 @@
|
||||
"reveal_comment": "Reveal comment",
|
||||
"read_more": "Read more comments",
|
||||
"more_replies": "more replies"
|
||||
},
|
||||
"search_result": {
|
||||
"others": "Others",
|
||||
"communities": {
|
||||
"title": "Communities",
|
||||
"subscribe": "Subscribe",
|
||||
"unsubscribe": "Unsubscribe",
|
||||
"subscribers": "Subscribers",
|
||||
"posters": "Posters",
|
||||
"posts": "Posts"
|
||||
},
|
||||
"communities_filter": {
|
||||
"rank": "Rank",
|
||||
"subs": "Subscribers",
|
||||
"new": "New"
|
||||
},
|
||||
"post_result_filter": {
|
||||
"popularity": "Popularity",
|
||||
"newest": "Newest",
|
||||
"relevance": "Relevance"
|
||||
},
|
||||
"other_result_filter": {
|
||||
"user": "User",
|
||||
"tag": "Tag"
|
||||
}
|
||||
},
|
||||
"community": {
|
||||
"new_post": "New Post"
|
||||
}
|
||||
}
|
||||
|
@ -21,12 +21,14 @@ export default {
|
||||
REDEEM: `Redeem${SCREEN_SUFFIX}`,
|
||||
REGISTER: `Register${SCREEN_SUFFIX}`,
|
||||
SEARCH_RESULT: `SearchResult${SCREEN_SUFFIX}`,
|
||||
TAG_RESULT: `TagResult${SCREEN_SUFFIX}`,
|
||||
SETTINGS: `Settings${SCREEN_SUFFIX}`,
|
||||
STEEM_CONNECT: `SteemConnect${SCREEN_SUFFIX}`,
|
||||
TRANSFER: `Transfer${SCREEN_SUFFIX}`,
|
||||
VOTERS: `Voters${SCREEN_SUFFIX}`,
|
||||
COMMENTS: `Comments${SCREEN_SUFFIX}`,
|
||||
ACCOUNT_BOOST: `AccountBoost${SCREEN_SUFFIX}`,
|
||||
COMMUNITY: `Community${SCREEN_SUFFIX}`,
|
||||
},
|
||||
DRAWER: {
|
||||
MAIN: `Main${DRAWER_SUFFIX}`,
|
||||
|
@ -35,6 +35,8 @@ import {
|
||||
Voters,
|
||||
Wallet,
|
||||
AccountBoost,
|
||||
TagResult,
|
||||
Community,
|
||||
} from '../screens';
|
||||
|
||||
const bottomTabNavigator = createBottomTabNavigator(
|
||||
@ -124,12 +126,14 @@ const stackNavigator = createStackNavigator(
|
||||
[ROUTES.SCREENS.DRAFTS]: { screen: Drafts },
|
||||
[ROUTES.SCREENS.BOOKMARKS]: { screen: Bookmarks },
|
||||
[ROUTES.SCREENS.SEARCH_RESULT]: { screen: SearchResult },
|
||||
[ROUTES.SCREENS.TAG_RESULT]: { screen: TagResult },
|
||||
[ROUTES.SCREENS.TRANSFER]: { screen: Transfer },
|
||||
[ROUTES.SCREENS.BOOST]: { screen: Boost },
|
||||
[ROUTES.SCREENS.REDEEM]: { screen: Redeem },
|
||||
[ROUTES.SCREENS.REBLOGS]: { screen: Reblogs },
|
||||
[ROUTES.SCREENS.SPIN_GAME]: { screen: SpinGame },
|
||||
[ROUTES.SCREENS.ACCOUNT_BOOST]: { screen: AccountBoost },
|
||||
[ROUTES.SCREENS.COMMUNITY]: { screen: Community },
|
||||
},
|
||||
{
|
||||
headerMode: 'none',
|
||||
|
@ -6,8 +6,6 @@ import { Client, PrivateKey, cryptoUtils } from '@esteemapp/dhive';
|
||||
import hivesigner from 'hivesigner';
|
||||
import Config from 'react-native-config';
|
||||
import { get, has } from 'lodash';
|
||||
import axios from 'axios';
|
||||
import { getInputRangeFromIndexes } from 'react-native-snap-carousel';
|
||||
import { getServer } from '../../realm/realm';
|
||||
import { getUnreadActivityCount } from '../esteem/esteem';
|
||||
import { userActivity } from '../esteem/ePoint';
|
||||
@ -1484,7 +1482,7 @@ export const profileUpdate = async (params, pin, currentAccount) => {
|
||||
.then((resp) => resp.result)
|
||||
.catch((error) => console.log(error));
|
||||
}
|
||||
console.log('priv key', key);
|
||||
|
||||
if (key) {
|
||||
const opArray = [
|
||||
[
|
||||
@ -1521,6 +1519,34 @@ export const profileUpdate = async (params, pin, currentAccount) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const subscribeCommunity = (currentAccount, pinCode, data) => {
|
||||
const pin = getDigitPinCode(pinCode);
|
||||
const key = getActiveKey(get(currentAccount, 'local'), pin);
|
||||
const username = get(currentAccount, 'name');
|
||||
|
||||
const json = JSON.stringify([
|
||||
data.isSubscribed ? 'unsubscribe' : 'subscribe',
|
||||
{ community: data.communityId },
|
||||
]);
|
||||
|
||||
if (key) {
|
||||
const privateKey = PrivateKey.fromString(key);
|
||||
|
||||
const op = {
|
||||
id: 'community',
|
||||
json,
|
||||
required_auths: [username],
|
||||
required_posting_auths: [],
|
||||
};
|
||||
|
||||
return client.broadcast.json(op, privateKey);
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
new Error('Check private key permission! Required private active key or above.'),
|
||||
);
|
||||
};
|
||||
|
||||
export const getBtcAddress = (pin, currentAccount) => {
|
||||
/*const digitPinCode = getDigitPinCode(pin);
|
||||
const key = getActiveKey(get(currentAccount, 'local'), digitPinCode);
|
||||
|
38
src/providers/steem/steem.js
Normal file
38
src/providers/steem/steem.js
Normal file
@ -0,0 +1,38 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const DEFAULT_SERVER = [
|
||||
'https://rpc.esteem.app',
|
||||
'https://anyx.io',
|
||||
'https://api.pharesim.me',
|
||||
'https://api.hive.blog',
|
||||
'https://api.hivekings.com',
|
||||
];
|
||||
|
||||
const pickAServer = () => DEFAULT_SERVER.sort(() => 0.5 - Math.random())[0];
|
||||
|
||||
const bridgeApiCall = (endpoint, params) =>
|
||||
axios
|
||||
.post(pickAServer(), {
|
||||
jsonrpc: '2.0',
|
||||
method: endpoint,
|
||||
params: params,
|
||||
id: 1,
|
||||
})
|
||||
.then((resp) => {
|
||||
return resp.data.result || null;
|
||||
});
|
||||
|
||||
export const getCommunity = (name, observer = '') =>
|
||||
bridgeApiCall('bridge.get_community', { name, observer });
|
||||
|
||||
export const getCommunities = (last = '', limit = 100, query = '', sort = 'rank', observer = '') =>
|
||||
bridgeApiCall('bridge.list_communities', {
|
||||
last,
|
||||
limit,
|
||||
query,
|
||||
sort,
|
||||
observer,
|
||||
});
|
||||
|
||||
export const getSubscriptions = (account = '') =>
|
||||
bridgeApiCall('bridge.list_all_subscriptions', { account });
|
67
src/screens/community/container/communityContainer.js
Normal file
67
src/screens/community/container/communityContainer.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { withNavigation } from 'react-navigation';
|
||||
import get from 'lodash/get';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getCommunity, getSubscriptions } from '../../../providers/steem/steem';
|
||||
import { subscribeCommunity } from '../../../providers/steem/dsteem';
|
||||
|
||||
import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
const CommunityContainer = ({ children, navigation, currentAccount, pinCode }) => {
|
||||
const [data, setData] = useState(null);
|
||||
const [isSubscribed, setIsSubscribed] = useState(false);
|
||||
const tag = get(navigation, 'state.params.tag');
|
||||
|
||||
useEffect(() => {
|
||||
getCommunity(tag).then((res) => {
|
||||
setData(res);
|
||||
});
|
||||
}, [tag]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
getSubscriptions(currentAccount.username).then((result) => {
|
||||
const _isSubscribed = result.some((item) => item[0] === data.name);
|
||||
setIsSubscribed(_isSubscribed);
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const _handleSubscribeButtonPress = () => {
|
||||
const _data = {
|
||||
isSubscribed: !isSubscribed,
|
||||
communityId: data.name,
|
||||
};
|
||||
|
||||
subscribeCommunity(currentAccount, pinCode, _data).then((result) => {
|
||||
setIsSubscribed(!isSubscribed);
|
||||
});
|
||||
};
|
||||
|
||||
const _handleNewPostButtonPress = () => {
|
||||
navigation.navigate({
|
||||
routeName: ROUTES.SCREENS.EDITOR,
|
||||
params: {
|
||||
tags: [tag],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
children &&
|
||||
children({
|
||||
data,
|
||||
handleSubscribeButtonPress: _handleSubscribeButtonPress,
|
||||
handleNewPostButtonPress: _handleNewPostButtonPress,
|
||||
isSubscribed: isSubscribed,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
currentAccount: state.account.currentAccount,
|
||||
pinCode: state.application.pin,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withNavigation(CommunityContainer));
|
4
src/screens/community/index.js
Normal file
4
src/screens/community/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Community from './screen/communityScreen';
|
||||
|
||||
export { Community };
|
||||
export default Community;
|
99
src/screens/community/screen/communityScreen.js
Normal file
99
src/screens/community/screen/communityScreen.js
Normal file
@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
// Components
|
||||
import { Posts, CollapsibleCard, Header } from '../../../components';
|
||||
import { Tag, ProfileSummaryPlaceHolder } from '../../../components/basicUIElements';
|
||||
|
||||
import CommunityContainer from '../container/communityContainer';
|
||||
|
||||
// Styles
|
||||
import styles from './communityStyles';
|
||||
|
||||
import { GLOBAL_POST_FILTERS, GLOBAL_POST_FILTERS_VALUE } from '../../../constants/options/filters';
|
||||
|
||||
const TagResultScreen = ({ navigation }) => {
|
||||
const tag = navigation.getParam('tag', '');
|
||||
const filter = navigation.getParam('filter', '');
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const _getSelectedIndex = () => {
|
||||
if (filter) {
|
||||
const selectedIndex = GLOBAL_POST_FILTERS_VALUE.indexOf(filter);
|
||||
if (selectedIndex > 0) {
|
||||
return selectedIndex;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<CommunityContainer>
|
||||
{({ data, handleSubscribeButtonPress, handleNewPostButtonPress, isSubscribed }) => (
|
||||
<View style={styles.container}>
|
||||
<Header isReverse hideUser />
|
||||
{data ? (
|
||||
<CollapsibleCard title={data.title} isTitleCenter defaultTitle="">
|
||||
<View style={styles.collapsibleCard}>
|
||||
<Text style={styles.description}>{data.description}</Text>
|
||||
<View style={styles.separator} />
|
||||
<Text style={styles.stats}>
|
||||
{`${data.subscribers} ${intl.formatMessage({
|
||||
id: 'search_result.communities.subscribers',
|
||||
})} • ${data.num_authors} ${intl.formatMessage({
|
||||
id: 'search_result.communities.posters',
|
||||
})} • ${data.num_pending} ${intl.formatMessage({
|
||||
id: 'search_result.communities.posts',
|
||||
})}`}
|
||||
</Text>
|
||||
<View style={styles.separator} />
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
<Tag
|
||||
style={styles.subscribeButton}
|
||||
textStyle={!isSubscribed && styles.subscribeButtonText}
|
||||
value={
|
||||
isSubscribed
|
||||
? intl.formatMessage({
|
||||
id: 'search_result.communities.subscribe',
|
||||
})
|
||||
: intl.formatMessage({
|
||||
id: 'search_result.communities.unsubscribe',
|
||||
})
|
||||
}
|
||||
isPin={isSubscribed}
|
||||
isFilter
|
||||
onPress={handleSubscribeButtonPress}
|
||||
/>
|
||||
<Tag
|
||||
style={styles.subscribeButton}
|
||||
value={intl.formatMessage({
|
||||
id: 'community.new_post',
|
||||
})}
|
||||
isFilter
|
||||
isPin
|
||||
onPress={handleNewPostButtonPress}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</CollapsibleCard>
|
||||
) : (
|
||||
<ProfileSummaryPlaceHolder />
|
||||
)}
|
||||
<View tabLabel={intl.formatMessage({ id: 'search.posts' })} style={styles.tabbarItem}>
|
||||
<Posts
|
||||
key={tag}
|
||||
filterOptions={GLOBAL_POST_FILTERS}
|
||||
filterOptionsValue={GLOBAL_POST_FILTERS_VALUE}
|
||||
selectedOptionIndex={_getSelectedIndex()}
|
||||
tag={tag}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</CommunityContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagResultScreen;
|
62
src/screens/community/screen/communityStyles.js
Normal file
62
src/screens/community/screen/communityStyles.js
Normal file
@ -0,0 +1,62 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '$primaryLightBackground',
|
||||
},
|
||||
buttonContainer: {
|
||||
width: '50%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tabbar: {
|
||||
alignSelf: 'center',
|
||||
height: 40,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
shadowOpacity: 0.2,
|
||||
shadowColor: '$shadowColor',
|
||||
shadowOffset: { height: 4 },
|
||||
zIndex: 99,
|
||||
borderBottomColor: '$shadowColor',
|
||||
borderBottomWidth: 0.1,
|
||||
marginTop: 8,
|
||||
},
|
||||
tabbarItem: {
|
||||
flex: 1,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
minWidth: '$deviceWidth',
|
||||
},
|
||||
tabs: {
|
||||
flex: 1,
|
||||
},
|
||||
tabView: {
|
||||
backgroundColor: '$primaryGrayBackground',
|
||||
},
|
||||
description: {
|
||||
fontSize: 14,
|
||||
fontFamily: '$primaryFont',
|
||||
marginTop: 5,
|
||||
color: '$primaryBlack',
|
||||
textAlign: 'center',
|
||||
},
|
||||
separator: {
|
||||
width: 100,
|
||||
alignSelf: 'center',
|
||||
backgroundColor: '$primaryDarkGray',
|
||||
height: 0.5,
|
||||
marginVertical: 10,
|
||||
},
|
||||
stats: {
|
||||
fontSize: 14,
|
||||
fontFamily: '$primaryFont',
|
||||
color: '$primaryDarkGray',
|
||||
},
|
||||
subscribeButton: {
|
||||
borderWidth: 1,
|
||||
borderColor: '$primaryBlue',
|
||||
},
|
||||
collapsibleCard: { alignItems: 'center', marginBottom: 20 },
|
||||
subscribeButtonText: {
|
||||
color: '$primaryBlue',
|
||||
},
|
||||
});
|
@ -716,7 +716,7 @@ class EditorContainer extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLoggedIn, isDarkTheme } = this.props;
|
||||
const { isLoggedIn, isDarkTheme, navigation } = this.props;
|
||||
const {
|
||||
autoFocusText,
|
||||
draftPost,
|
||||
@ -731,6 +731,8 @@ class EditorContainer extends Component {
|
||||
uploadedImage,
|
||||
} = this.state;
|
||||
|
||||
const tags = navigation.state.params.tags;
|
||||
|
||||
return (
|
||||
<EditorScreen
|
||||
autoFocusText={autoFocusText}
|
||||
@ -754,6 +756,7 @@ class EditorContainer extends Component {
|
||||
saveCurrentDraft={this._saveCurrentDraft}
|
||||
saveDraftToDB={this._saveDraftToDB}
|
||||
uploadedImage={uploadedImage}
|
||||
tags={tags}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class EditorScreen extends Component {
|
||||
fields: {
|
||||
title: (props.draftPost && props.draftPost.title) || '',
|
||||
body: (props.draftPost && props.draftPost.body) || '',
|
||||
tags: (props.draftPost && props.draftPost.tags) || [],
|
||||
tags: (props.draftPost && props.draftPost.tags) || props.tags || [],
|
||||
isValid: false,
|
||||
},
|
||||
};
|
||||
@ -50,6 +50,7 @@ class EditorScreen extends Component {
|
||||
fields: {
|
||||
...prevState.fields,
|
||||
...nextProps.draftPost,
|
||||
tags: prevState.fields.tags,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import Transfer from './transfer';
|
||||
import Voters from './voters';
|
||||
import AccountBoost from './accountBoost/screen/accountBoostScreen';
|
||||
import Register from './register/registerScreen';
|
||||
import TagResult from './tagResult';
|
||||
import { Community } from './community';
|
||||
|
||||
export {
|
||||
Bookmarks,
|
||||
@ -48,4 +50,6 @@ export {
|
||||
Transfer,
|
||||
Voters,
|
||||
Wallet,
|
||||
TagResult,
|
||||
Community,
|
||||
};
|
||||
|
76
src/screens/searchResult/container/communitiesContainer.js
Normal file
76
src/screens/searchResult/container/communitiesContainer.js
Normal file
@ -0,0 +1,76 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { withNavigation } from 'react-navigation';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ROUTES from '../../../constants/routeNames';
|
||||
|
||||
import { getCommunities, getSubscriptions } from '../../../providers/steem/steem';
|
||||
import { subscribeCommunity } from '../../../providers/steem/dsteem';
|
||||
|
||||
const CommunitiesContainer = ({ children, navigation, searchValue, currentAccount, pinCode }) => {
|
||||
const [data, setData] = useState();
|
||||
const [filterIndex, setFilterIndex] = useState(0);
|
||||
const [query, setQuery] = useState('');
|
||||
const [sort, setSort] = useState('rank');
|
||||
const [allSubscriptions, setAllSubscriptions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setData([]);
|
||||
getCommunities('', 100, query, sort).then((res) => {
|
||||
if (res) {
|
||||
setData(res);
|
||||
}
|
||||
});
|
||||
}, [query, sort]);
|
||||
|
||||
useEffect(() => {
|
||||
setData([]);
|
||||
setQuery(searchValue);
|
||||
}, [searchValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
getSubscriptions(currentAccount.username).then((result) => {
|
||||
setAllSubscriptions(result);
|
||||
});
|
||||
}
|
||||
}, [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,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
currentAccount: state.account.currentAccount,
|
||||
pinCode: state.application.pin,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withNavigation(CommunitiesContainer));
|
83
src/screens/searchResult/container/otherResultContainer.js
Normal file
83
src/screens/searchResult/container/otherResultContainer.js
Normal file
@ -0,0 +1,83 @@
|
||||
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/steem/dsteem';
|
||||
import { getLeaderboard } from '../../../providers/esteem/esteem';
|
||||
|
||||
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).then((res) => {
|
||||
setUsers(res);
|
||||
});
|
||||
getTrendingTags(searchValue).then((res) => {
|
||||
setTags(res);
|
||||
});
|
||||
} else {
|
||||
getLeaderboard().then((result) => {
|
||||
setUsers(result.map((item) => item._id));
|
||||
});
|
||||
}
|
||||
}, [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));
|
90
src/screens/searchResult/container/postResultContainer.js
Normal file
90
src/screens/searchResult/container/postResultContainer.js
Normal file
@ -0,0 +1,90 @@
|
||||
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/esteem/esteem';
|
||||
import { getPost } from '../../../providers/steem/dsteem';
|
||||
|
||||
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;
|
||||
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));
|
77
src/screens/searchResult/screen/CommunitiesListItem.js
Normal file
77
src/screens/searchResult/screen/CommunitiesListItem.js
Normal file
@ -0,0 +1,77 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import styles from './communitiesListItemStyles';
|
||||
|
||||
import { Tag } from '../../../components/basicUIElements';
|
||||
|
||||
const UserListItem = ({
|
||||
index,
|
||||
handleOnPress,
|
||||
handleOnLongPress,
|
||||
title,
|
||||
about,
|
||||
admins,
|
||||
id,
|
||||
authors,
|
||||
posts,
|
||||
subscribers,
|
||||
isNsfw,
|
||||
name,
|
||||
handleSubscribeButtonPress,
|
||||
isSubscribed,
|
||||
}) => {
|
||||
const [subscribed, setSubscribed] = useState(isSubscribed);
|
||||
const intl = useIntl();
|
||||
|
||||
const _handleSubscribeButtonPress = () => {
|
||||
handleSubscribeButtonPress({ subscribed: !subscribed, communityId: name }).then(() => {
|
||||
setSubscribed(!subscribed);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onLongPress={() => handleOnLongPress && handleOnLongPress()}
|
||||
onPress={() => handleOnPress && handleOnPress(name)}
|
||||
>
|
||||
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Tag
|
||||
style={styles.subscribeButton}
|
||||
textStyle={!subscribed && styles.subscribeButtonText}
|
||||
value={
|
||||
subscribed
|
||||
? intl.formatMessage({
|
||||
id: 'search_result.communities.subscribe',
|
||||
})
|
||||
: intl.formatMessage({
|
||||
id: 'search_result.communities.unsubscribe',
|
||||
})
|
||||
}
|
||||
isPin={subscribed}
|
||||
isFilter
|
||||
onPress={_handleSubscribeButtonPress}
|
||||
/>
|
||||
</View>
|
||||
{!!about && <Text style={styles.about}>{about}</Text>}
|
||||
<View style={styles.separator} />
|
||||
<Text style={styles.stats}>
|
||||
{`${subscribers.toString()} ${intl.formatMessage({
|
||||
id: 'search_result.communities.subscribers',
|
||||
})} • ${authors.toString()} ${intl.formatMessage({
|
||||
id: 'search_result.communities.posters',
|
||||
})} • ${posts} ${intl.formatMessage({
|
||||
id: 'search_result.communities.posts',
|
||||
})}`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserListItem;
|
54
src/screens/searchResult/screen/communities.js
Normal file
54
src/screens/searchResult/screen/communities.js
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import get from 'lodash/get';
|
||||
|
||||
// Components
|
||||
import { FilterBar } from '../../../components';
|
||||
import CommunitiesList from './communitiesList';
|
||||
|
||||
import CommunitiesContainer from '../container/communitiesContainer';
|
||||
|
||||
const filterOptions = ['rank', 'subs', 'new'];
|
||||
|
||||
const CommunitiesScreen = ({ navigation, searchValue }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const activeVotes = get(navigation, 'state.params.activeVotes');
|
||||
|
||||
return (
|
||||
<CommunitiesContainer data={activeVotes} searchValue={searchValue}>
|
||||
{({
|
||||
data,
|
||||
filterIndex,
|
||||
allSubscriptions,
|
||||
handleOnVotersDropdownSelect,
|
||||
handleOnPress,
|
||||
handleSubscribeButtonPress,
|
||||
}) => (
|
||||
<>
|
||||
<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])}
|
||||
/>
|
||||
<CommunitiesList
|
||||
votes={data}
|
||||
allSubscriptions={allSubscriptions}
|
||||
handleOnPress={handleOnPress}
|
||||
handleSubscribeButtonPress={handleSubscribeButtonPress}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CommunitiesContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommunitiesScreen;
|
65
src/screens/searchResult/screen/communitiesList.js
Normal file
65
src/screens/searchResult/screen/communitiesList.js
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, FlatList } from 'react-native';
|
||||
|
||||
// Components
|
||||
import CommunitiesListItem from './CommunitiesListItem';
|
||||
import { CommunitiesPlaceHolder } from '../../../components/basicUIElements';
|
||||
|
||||
// Styles
|
||||
import styles from './communitiesListStyles';
|
||||
|
||||
const VotersDisplayView = ({
|
||||
votes,
|
||||
handleOnPress,
|
||||
handleSubscribeButtonPress,
|
||||
allSubscriptions,
|
||||
}) => {
|
||||
const _renderItem = (item, index) => {
|
||||
const isSubscribed = allSubscriptions.some((sub) => sub[0] === item.name);
|
||||
|
||||
return (
|
||||
<CommunitiesListItem
|
||||
index={index}
|
||||
title={item.title}
|
||||
about={item.about}
|
||||
admins={item.admins}
|
||||
id={item.id}
|
||||
authors={item.num_authors}
|
||||
posts={item.num_pending}
|
||||
subscribers={item.subscribers}
|
||||
isNsfw={item.is_nsfw}
|
||||
name={item.name}
|
||||
handleOnPress={handleOnPress}
|
||||
handleSubscribeButtonPress={handleSubscribeButtonPress}
|
||||
isSubscribed={isSubscribed}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderEmptyContent = () => {
|
||||
return (
|
||||
<>
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<FlatList
|
||||
data={votes}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
renderItem={({ item, index }) => _renderItem(item, index)}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default VotersDisplayView;
|
58
src/screens/searchResult/screen/communitiesListItemStyles.js
Normal file
58
src/screens/searchResult/screen/communitiesListItemStyles.js
Normal file
@ -0,0 +1,58 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
padding: 8,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
content: {
|
||||
flexDirection: 'column',
|
||||
marginLeft: 8,
|
||||
width: '100%',
|
||||
},
|
||||
itemWrapper: {
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
itemWrapperGray: {
|
||||
backgroundColor: '$primaryLightBackground',
|
||||
},
|
||||
title: {
|
||||
color: '$primaryBlue',
|
||||
fontSize: 17,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: '$primaryFont',
|
||||
},
|
||||
about: {
|
||||
fontSize: 14,
|
||||
fontFamily: '$primaryFont',
|
||||
marginTop: 5,
|
||||
color: '$primaryBlack',
|
||||
},
|
||||
separator: {
|
||||
width: 100,
|
||||
alignSelf: 'center',
|
||||
backgroundColor: '$primaryDarkGray',
|
||||
height: 0.5,
|
||||
marginVertical: 5,
|
||||
},
|
||||
stats: {
|
||||
fontSize: 14,
|
||||
fontFamily: '$primaryFont',
|
||||
color: '$primaryDarkGray',
|
||||
},
|
||||
subscribeButton: {
|
||||
borderWidth: 1,
|
||||
borderColor: '$primaryBlue',
|
||||
},
|
||||
subscribeButtonText: {
|
||||
color: '$primaryBlue',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
});
|
16
src/screens/searchResult/screen/communitiesListStyles.js
Normal file
16
src/screens/searchResult/screen/communitiesListStyles.js
Normal file
@ -0,0 +1,16 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
marginBottom: 40,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
text: {
|
||||
color: '$iconColor',
|
||||
fontSize: 12,
|
||||
fontFamily: '$primaryFont',
|
||||
},
|
||||
});
|
104
src/screens/searchResult/screen/otherResults.js
Normal file
104
src/screens/searchResult/screen/otherResults.js
Normal file
@ -0,0 +1,104 @@
|
||||
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:
|
||||
return (
|
||||
<FlatList
|
||||
data={users}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item, index }) => (
|
||||
<TouchableOpacity onPress={() => handleOnPress(item)}>
|
||||
{_renderUserItem(item, index)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
/>
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<FlatList
|
||||
data={tags}
|
||||
keyExtractor={(item) => item.id}
|
||||
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;
|
24
src/screens/searchResult/screen/otherResultsStyles.js
Normal file
24
src/screens/searchResult/screen/otherResultsStyles.js
Normal file
@ -0,0 +1,24 @@
|
||||
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',
|
||||
},
|
||||
});
|
110
src/screens/searchResult/screen/postResult.js
Normal file
110
src/screens/searchResult/screen/postResult.js
Normal file
@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { SafeAreaView, FlatList, View, Text, TouchableOpacity } from 'react-native';
|
||||
import get from 'lodash/get';
|
||||
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 { getTimeFromNow } from '../../../utils/time';
|
||||
|
||||
import styles from './postResultStyles';
|
||||
|
||||
import DEFAULT_IMAGE from '../../../assets/no_image.png';
|
||||
|
||||
const filterOptions = ['relevance', 'popularity', 'newest'];
|
||||
|
||||
const PostResult = ({ navigation, searchValue }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const _renderItem = (item, index) => {
|
||||
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'))}
|
||||
size={36}
|
||||
tag={item.category}
|
||||
/>
|
||||
<FastImage source={item.img_url} style={styles.thumbnail} defaultSource={DEFAULT_IMAGE} />
|
||||
<View style={[styles.postDescription]}>
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
<Text style={styles.summary} numberOfLines={2}>
|
||||
{item.body}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.stats}>
|
||||
{item.payout && <Text style={styles.postIconText}>{`$ ${item.payout}`}</Text>}
|
||||
<TextWithIcon
|
||||
iconName="heart-outline"
|
||||
textStyle={styles.postIconText}
|
||||
iconStyle={styles.postIcon}
|
||||
iconType="MaterialCommunityIcons"
|
||||
text={get(item, 'up_votes', 0)}
|
||||
/>
|
||||
<TextWithIcon
|
||||
iconName="comment-outline"
|
||||
iconStyle={styles.postIcon}
|
||||
iconType="MaterialCommunityIcons"
|
||||
text={get(item, 'children', 0)}
|
||||
textStyle={styles.postIconText}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderEmptyContent = () => {
|
||||
return (
|
||||
<>
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
<CommunitiesPlaceHolder />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PostResultContainer searchValue={searchValue}>
|
||||
{({ data, filterIndex, handleFilterChanged, handleOnPress, loadMore }) => (
|
||||
<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])}
|
||||
/>
|
||||
<FlatList
|
||||
data={data}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
renderItem={({ item, index }) => (
|
||||
<TouchableOpacity onPress={() => handleOnPress(item)}>
|
||||
{_renderItem(item, index)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
onEndReached={loadMore}
|
||||
ListEmptyComponent={_renderEmptyContent}
|
||||
ListFooterComponent={<CommunitiesPlaceHolder />}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)}
|
||||
</PostResultContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostResult;
|
44
src/screens/searchResult/screen/postResultStyles.js
Normal file
44
src/screens/searchResult/screen/postResultStyles.js
Normal file
@ -0,0 +1,44 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
title: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginVertical: 5,
|
||||
color: '$primaryBlack',
|
||||
},
|
||||
summary: {
|
||||
fontSize: 13,
|
||||
color: '$primaryDarkGray',
|
||||
},
|
||||
itemWrapper: {
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
itemWrapperGray: {
|
||||
backgroundColor: '$primaryLightBackground',
|
||||
},
|
||||
stats: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
postIcon: {
|
||||
alignSelf: 'flex-start',
|
||||
fontSize: 20,
|
||||
color: '$iconColor',
|
||||
margin: 0,
|
||||
width: 20,
|
||||
marginLeft: 25,
|
||||
},
|
||||
postIconText: {
|
||||
color: '$primaryDarkGray',
|
||||
fontSize: 13,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
});
|
@ -1,23 +1,31 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
// Components
|
||||
import { SearchInput, Posts, TabBar } from '../../../components';
|
||||
import { SearchInput, TabBar } from '../../../components';
|
||||
import Communities from './communities';
|
||||
import PostResult from './postResult';
|
||||
import OtherResult from './otherResults';
|
||||
|
||||
// Styles
|
||||
import styles from './searchResultStyles';
|
||||
import globalStyles from '../../../globalStyles';
|
||||
|
||||
import { GLOBAL_POST_FILTERS, GLOBAL_POST_FILTERS_VALUE } from '../../../constants/options/filters';
|
||||
|
||||
const SearchResultScreen = ({ navigation }) => {
|
||||
const tag = navigation.getParam('tag', '');
|
||||
const filter = navigation.getParam('filter', '');
|
||||
|
||||
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();
|
||||
};
|
||||
@ -31,34 +39,34 @@ const SearchResultScreen = ({ navigation }) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const _getSelectedIndex = () => {
|
||||
if (filter) {
|
||||
const selectedIndex = GLOBAL_POST_FILTERS_VALUE.indexOf(filter);
|
||||
if (selectedIndex > 0) {
|
||||
return selectedIndex;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<SafeAreaView>
|
||||
<SearchInput
|
||||
handleOnModalClose={_navigationGoBack}
|
||||
placeholder={`#${tag}`}
|
||||
editable={false}
|
||||
placeholder={intl.formatMessage({ id: 'header.search' })}
|
||||
onChangeText={setText}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
<ScrollableTabView style={globalStyles.tabView} renderTabBar={_renderTabbar}>
|
||||
<ScrollableTabView
|
||||
style={globalStyles.tabView}
|
||||
renderTabBar={_renderTabbar}
|
||||
prerenderingSiblingsNumber={Infinity}
|
||||
>
|
||||
<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}>
|
||||
<Posts
|
||||
key={tag}
|
||||
filterOptions={GLOBAL_POST_FILTERS}
|
||||
filterOptionsValue={GLOBAL_POST_FILTERS_VALUE}
|
||||
selectedOptionIndex={_getSelectedIndex()}
|
||||
tag={tag}
|
||||
/>
|
||||
<PostResult searchValue={searchValue} />
|
||||
</View>
|
||||
<View
|
||||
tabLabel={intl.formatMessage({ id: 'search_result.others' })}
|
||||
style={styles.tabbarItem}
|
||||
>
|
||||
<OtherResult searchValue={searchValue} />
|
||||
</View>
|
||||
</ScrollableTabView>
|
||||
</View>
|
||||
|
4
src/screens/tagResult/index.js
Normal file
4
src/screens/tagResult/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import SearchResult from './screen/tagResultScreen';
|
||||
|
||||
export { SearchResult };
|
||||
export default SearchResult;
|
68
src/screens/tagResult/screen/tagResultScreen.js
Normal file
68
src/screens/tagResult/screen/tagResultScreen.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { View, SafeAreaView } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
// Components
|
||||
import { SearchInput, Posts, TabBar } from '../../../components';
|
||||
|
||||
// Styles
|
||||
import styles from './tagResultStyles';
|
||||
import globalStyles from '../../../globalStyles';
|
||||
|
||||
import { GLOBAL_POST_FILTERS, GLOBAL_POST_FILTERS_VALUE } from '../../../constants/options/filters';
|
||||
|
||||
const TagResultScreen = ({ navigation }) => {
|
||||
const tag = navigation.getParam('tag', '');
|
||||
const filter = navigation.getParam('filter', '');
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const _navigationGoBack = () => {
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
const _renderTabbar = () => (
|
||||
<TabBar
|
||||
style={styles.tabbar}
|
||||
tabUnderlineDefaultWidth={80}
|
||||
tabUnderlineScaleX={2}
|
||||
tabBarPosition="overlayTop"
|
||||
/>
|
||||
);
|
||||
|
||||
const _getSelectedIndex = () => {
|
||||
if (filter) {
|
||||
const selectedIndex = GLOBAL_POST_FILTERS_VALUE.indexOf(filter);
|
||||
if (selectedIndex > 0) {
|
||||
return selectedIndex;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<SafeAreaView>
|
||||
<SearchInput
|
||||
handleOnModalClose={_navigationGoBack}
|
||||
placeholder={`#${tag}`}
|
||||
editable={false}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
<ScrollableTabView style={globalStyles.tabView} renderTabBar={_renderTabbar}>
|
||||
<View tabLabel={intl.formatMessage({ id: 'search.posts' })} style={styles.tabbarItem}>
|
||||
<Posts
|
||||
key={tag}
|
||||
filterOptions={GLOBAL_POST_FILTERS}
|
||||
filterOptionsValue={GLOBAL_POST_FILTERS_VALUE}
|
||||
selectedOptionIndex={_getSelectedIndex()}
|
||||
tag={tag}
|
||||
/>
|
||||
</View>
|
||||
</ScrollableTabView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagResultScreen;
|
31
src/screens/tagResult/screen/tagResultStyles.js
Normal file
31
src/screens/tagResult/screen/tagResultStyles.js
Normal file
@ -0,0 +1,31 @@
|
||||
import EStyleSheet from 'react-native-extended-stylesheet';
|
||||
|
||||
export default EStyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
},
|
||||
buttonContainer: {
|
||||
width: '50%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
tabbar: {
|
||||
alignSelf: 'center',
|
||||
height: 40,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
shadowOpacity: 0.2,
|
||||
shadowColor: '$shadowColor',
|
||||
shadowOffset: { height: 4 },
|
||||
zIndex: 99,
|
||||
borderBottomColor: '$shadowColor',
|
||||
borderBottomWidth: 0.1,
|
||||
},
|
||||
tabbarItem: {
|
||||
flex: 1,
|
||||
backgroundColor: '$primaryBackgroundColor',
|
||||
minWidth: '$deviceWidth',
|
||||
},
|
||||
tabs: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
@ -24,7 +24,7 @@ const VotersScreen = ({ navigation }) => {
|
||||
return (
|
||||
<AccountListContainer data={activeVotes}>
|
||||
{({ data, filterResult, filterIndex, handleOnVotersDropdownSelect, handleSearch }) => (
|
||||
<SafeAreaView style={globalStyles.container}>
|
||||
<>
|
||||
<BasicHeader
|
||||
title={`${headerTitle} (${data && data.length})`}
|
||||
isHasSearch
|
||||
@ -44,7 +44,7 @@ const VotersScreen = ({ navigation }) => {
|
||||
onDropdownSelect={handleOnVotersDropdownSelect}
|
||||
/>
|
||||
<VotersDisplay votes={filterResult || data} />
|
||||
</SafeAreaView>
|
||||
</>
|
||||
)}
|
||||
</AccountListContainer>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user