Merge pull request #1815 from ecency/feature/reorganize-search-screen

Feature/reorganize search screen
This commit is contained in:
Feruz M 2021-01-18 22:21:57 +02:00 committed by GitHub
commit e5822fd831
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1590 additions and 691 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.+'

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,24 +40,29 @@ const UserListItem = ({
<View style={styles.content}>
<View style={styles.header}>
<Text style={styles.title}>{title}</Text>
{isLoggedIn && (
<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}
/>
)}
{isLoggedIn &&
(loading ? (
<View style={styles.indicatorView}>
<ActivityIndicator />
</View>
) : (
<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}
/>
))}
</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

@ -64,6 +64,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 +106,7 @@ import {
WalletLineItem,
WalletUnclaimedPlaceHolder,
Separator,
EmptyScreen,
} from './basicUIElements';
export {
@ -206,6 +209,9 @@ export {
WalletLineItem,
WalletUnclaimedPlaceHolder,
Separator,
EmptyScreen,
HorizontalIconList,
PopoverWrapper,
CommunitiesList,
SubscribedCommunitiesList,
};

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,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

@ -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

@ -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

@ -314,7 +314,8 @@ export const getCommunities = async (
resolve({});
}
} catch (error) {
reject(error);
console.log(error);
resolve({});
}
});
@ -1138,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,77 +111,323 @@ export default function (state = initialState, action) {
},
};
case SUBSCRIBE_COMMUNITY:
return {
...state,
subscribingCommunitiesInFeedScreen: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: true,
error: false,
},
},
};
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: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: true,
error: false,
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: true,
error: false,
},
},
};
default:
return state;
}
case SUBSCRIBE_COMMUNITY_SUCCESS:
return {
...state,
subscribingCommunitiesInFeedScreen: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: false,
},
},
};
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: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: false,
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: false,
},
},
};
default:
return state;
}
case SUBSCRIBE_COMMUNITY_FAIL:
return {
...state,
subscribingCommunitiesInFeedScreen: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: true,
},
},
};
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: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: true,
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: true,
},
},
};
default:
return state;
}
case LEAVE_COMMUNITY:
return {
...state,
subscribingCommunitiesInFeedScreen: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: true,
error: false,
},
},
};
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: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: true,
error: false,
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: true,
error: false,
},
},
};
default:
return state;
}
case LEAVE_COMMUNITY_SUCCESS:
return {
...state,
subscribingCommunitiesInFeedScreen: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: false,
},
},
};
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: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: false,
},
},
};
case 'searchResultsScreen':
return {
...state,
subscribingCommunitiesInSearchResultsScreen: {
...state.subscribingCommunitiesInSearchResultsScreen,
[action.payload.communityId]: {
isSubscribed: false,
loading: false,
error: false,
},
},
};
default:
return state;
}
case LEAVE_COMMUNITY_FAIL:
return {
...state,
subscribingCommunitiesInFeedScreen: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: true,
},
},
};
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: {
...state.subscribingCommunitiesInFeedScreen,
[action.payload.communityId]: {
isSubscribed: true,
loading: false,
error: true,
},
},
};
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

@ -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>
<SearchInput
handleOnModalClose={_navigationGoBack}
placeholder={intl.formatMessage({ id: 'header.search' })}
onChangeText={setText}
/>
<View style={styles.headerContainer}>
<View style={{ flex: 11 }}>
<SearchInput
//handleOnModalClose={_navigationGoBack}
placeholder={intl.formatMessage({ id: 'header.search' })}
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,38 +85,29 @@ 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])}
/>
<FlatList
data={data}
keyExtractor={(item) => item.id && item.id.toString()}
renderItem={({ item, index }) => (
<TouchableOpacity onPress={() => handleOnPress(item)}>
{_renderItem(item, index)}
</TouchableOpacity>
)}
onEndReached={loadMore}
ListEmptyComponent={_renderEmptyContent}
ListFooterComponent={<CommunitiesPlaceHolder />}
/>
{noResult ? (
<EmptyScreen />
) : (
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<TouchableOpacity onPress={() => handleOnPress(item)}>
{_renderItem(item, index)}
</TouchableOpacity>
)}
onEndReached={loadMore}
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;
}