Skip to content

Commit

Permalink
DMAPP-149: Integrate channel categories (#151)
Browse files Browse the repository at this point in the history
* DMAPP-149: Channel Categories

- Completed UI integration
- Completed API integration
- Work in progress: Handling search and category filter functionality

* DMAPP-149: Integrate channel categories

- Implemented handling of search and category filters.
- Ensured reset of the previous filter when a new one is applied.

* DMAPP-149: Integrate channel categories enhancements

- Disabled onPress event for the active category.
- Added a new message to display when data is unavailable.

* DMAPP-149: Minor fix in Pill component

* DMAPP-149: Code level changes

- Minor changes in Pill and channelsDisplayer component
  • Loading branch information
meKushdeepSingh authored Jan 9, 2025
1 parent 3e01dd4 commit 3d6d3d9
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 8 deletions.
9 changes: 9 additions & 0 deletions src/Globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default {
ENDPOINT_GET_FEEDS: '/feeds/get_feeds',
ENDPOINT_GET_SPAM_FEEDS: '/feeds/get_spam_feeds',
ENDPOINT_FETCH_CHANNELS: '/v1/channels',
ENDPOINT_FETCH_CHANNEL_CATEGORIES: '/v1/channels/tags/all',
ENDPOINT_SEARCH_CHANNELS: '/v1/channels/search',
ENDPOINT_FETCH_SUBSCRIPTION: '/channels/_is_user_subscribed',
ENDPOINT_SUBSCRIBE_OFFCHAIN: '/channels/subscribe_offchain',
Expand Down Expand Up @@ -82,6 +83,7 @@ export default {
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAvklEQVR4AcXBsW2FMBiF0Y8r3GQb6jeBxRauYRpo4yGQkMd4A7kg7Z/GUfSKe8703fKDkTATZsJsrr0RlZSJ9r4RLayMvLmJjnQS1d6IhJkwE2bT13U/DBzp5BN73xgRZsJMmM1HOolqb/yWiWpvjJSUiRZWopIykTATZsJs5g+1N6KSMiO1N/5DmAkzYTa9Lh6MhJkwE2ZzSZlo7xvRwson3txERzqJhJkwE2bT6+JhoKTMJ2pvjAgzYSbMfgDlXixqjH6gRgAAAABJRU5ErkJggg==',

PASSCODE_LENGTH: 6,
ALL_CATEGORIES: 'All',
},

ADJUSTMENTS: {
Expand Down Expand Up @@ -165,6 +167,13 @@ export default {

// Notification
IC_NOTIFICATION: '#e20880',

// Pill
PILL_BG_DEFAULT: '#F5F6F8',
PILL_TEXT_DEFAULT: '#17181B',

// Border
BORDER_DEFAULT: '#C4CBD5',
},
SCREENS: {
WELCOME: 'Welcome',
Expand Down
55 changes: 55 additions & 0 deletions src/components/pill/Pill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {FC} from 'react';
import {Pressable, StyleSheet, Text} from 'react-native';
import Globals from 'src/Globals';

import {PillProps} from '.';

const Pill: FC<PillProps> = ({data, value, onChange, disabled}) => {
const isActive = data.value === value;
return (
<Pressable
disabled={disabled}
onPress={() => onChange(data)}
style={[
styles.mainView,
isActive ? styles.activeView : styles.inactiveView,
]}>
<Text
style={[
styles.labelText,
isActive ? styles.activeText : styles.inactiveText,
]}>
{data.label}
</Text>
</Pressable>
);
};

export {Pill};

const styles = StyleSheet.create({
mainView: {
height: 40,
borderRadius: 48,
paddingHorizontal: 16,
paddingVertical: 8,
marginRight: 8,
},
activeView: {
backgroundColor: Globals.COLORS.BLACK,
},
inactiveView: {
backgroundColor: Globals.COLORS.PILL_BG_DEFAULT,
},
labelText: {
fontSize: 14,
fontWeight: '500',
lineHeight: 20,
},
activeText: {
color: Globals.COLORS.WHITE,
},
inactiveText: {
color: Globals.COLORS.PILL_TEXT_DEFAULT,
},
});
11 changes: 11 additions & 0 deletions src/components/pill/Pill.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type PillProps = {
data: PillData;
value: string | number;
onChange: (value: PillData) => void;
disabled?: boolean;
};

export type PillData = {
label: string;
value: string | number;
};
2 changes: 2 additions & 0 deletions src/components/pill/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Pill';
export * from './Pill.types';
52 changes: 52 additions & 0 deletions src/components/ui/ChannelCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, {FC, useState} from 'react';
import {ScrollView, StyleSheet, View} from 'react-native';
import Globals from 'src/Globals';
import {useChannelCategories} from 'src/hooks/channel/useChannelCategories';

import {Pill} from '../pill';

type ChannelCategoriesProps = {
onChangeCategory: (category: string) => void;
value: string;
disabled: boolean;
};

const ChannelCategories: FC<ChannelCategoriesProps> = ({
onChangeCategory,
value,
disabled,
}) => {
const {isLoading, channelCategories} = useChannelCategories();

if (!isLoading && channelCategories?.length > 0) {
return (
<View style={styles.mainView}>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
{channelCategories.map((item, index) => (
<Pill
disabled={disabled || item.value === value}
key={`${index}_pill_keys`}
value={value}
data={item}
onChange={category => {
onChangeCategory(category.value as string);
}}
/>
))}
</ScrollView>
</View>
);
}
return null;
};

export {ChannelCategories};

const styles = StyleSheet.create({
mainView: {
height: 40,
width: '100%',
flexDirection: 'row',
marginBottom: 16,
},
});
38 changes: 34 additions & 4 deletions src/components/ui/ChannelsDisplayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,30 @@ import {
} from 'src/redux/channelSlice';

import GLOBALS from '../../Globals';
import Globals from '../../Globals';
import {ChannelCategories} from './ChannelCategories';

const ChannelsDisplayer = () => {
const [searchTimer, setSearchTimer] = useState<NodeJS.Timeout>();

const DEBOUNCE_TIMEOUT = 500; //time in millisecond which we want to wait for then to finish typing
const [search, setSearch] = React.useState('');

const [search, setSearch] = React.useState('');
const [showSearchResults, setShowSearchResults] = useState(false);
const [selectedCategory, setSelectedCategory] = useState<string>(
Globals.CONSTANTS.ALL_CATEGORIES,
);

const channelResults = useSelector(selectChannels);
const channelsReachedEnd = useSelector(selectChannelsReachedEnd);
const {
loadMoreChannels,
loadSearchResults,
resetChannelData,
isLoadingChannels,
isLoadingSearchResults,
searchResults,
} = useChannels();
} = useChannels({tag: selectedCategory, showSearchResults});

const isLoadingSubscriptions = useSelector(selectIsLoadingSubscriptions);
const {refreshSubscriptions} = useSubscriptions();
Expand Down Expand Up @@ -74,6 +80,7 @@ const ChannelsDisplayer = () => {
return;
}
setShowSearchResults(true);
setSelectedCategory(Globals.CONSTANTS.ALL_CATEGORIES);
await loadSearchResults(channelName);
};

Expand All @@ -91,6 +98,15 @@ const ChannelsDisplayer = () => {
} catch (e) {}
};

const handleCategoryChange = (category: string) => {
if (search.length || showSearchResults) {
setSearch('');
setShowSearchResults(false);
}
resetChannelData();
setSelectedCategory(category as string);
};

return (
<>
<View style={styles.container}>
Expand All @@ -110,14 +126,24 @@ const ChannelsDisplayer = () => {
/>
</View>

<ChannelCategories
disabled={isLoadingChannels}
onChangeCategory={handleCategoryChange}
value={selectedCategory}
/>

{channels.length === 0 && (
<View style={[styles.infodisplay]}>
{!isLoading && !isLoadingSubscriptions ? (
// Show channel not found label
<StylishLabel
style={styles.infoText}
fontSize={16}
title="[dg:No channels match your query, please search for another name/address]"
title={
showSearchResults
? '[dg:No channels match your query, please search for another name/address]'
: '[dg:No results available.]'
}
/>
) : (
// Show channel fetching label
Expand All @@ -137,7 +163,7 @@ const ChannelsDisplayer = () => {
<FlatList
data={channels}
style={styles.channels}
contentContainerStyle={{paddingVertical: 10}}
contentContainerStyle={styles.channelListContentContainerStyle}
keyExtractor={item => item.channel.toString()}
initialNumToRender={20}
showsVerticalScrollIndicator={false}
Expand Down Expand Up @@ -175,6 +201,10 @@ const styles = StyleSheet.create({
flex: 1,
width: '100%',
},
channelListContentContainerStyle: {
paddingTop: 10,
paddingBottom: 80, // Add some padding to the bottom to display last item content
},
infodisplay: {
width: '100%',
justifyContent: 'center',
Expand Down
2 changes: 2 additions & 0 deletions src/env.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const {
ENDPOINT_GET_FEEDS,
ENDPOINT_GET_SPAM_FEEDS,
ENDPOINT_FETCH_CHANNELS,
ENDPOINT_FETCH_CHANNEL_CATEGORIES,
ENDPOINT_FETCH_SUBSCRIPTION,
ENDPOINT_SUBSCRIBE_OFFCHAIN,
ENDPOINT_UNSUBSCRIBE_OFFCHAIN,
Expand Down Expand Up @@ -60,6 +61,7 @@ export default {
ENDPOINT_GET_FEEDS,
ENDPOINT_GET_SPAM_FEEDS,
ENDPOINT_FETCH_CHANNELS,
ENDPOINT_FETCH_CHANNEL_CATEGORIES,
ENDPOINT_FETCH_SUBSCRIPTION,
ENDPOINT_SUBSCRIBE_OFFCHAIN,
ENDPOINT_UNSUBSCRIBE_OFFCHAIN,
Expand Down
46 changes: 46 additions & 0 deletions src/hooks/channel/useChannelCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {useEffect, useState} from 'react';
import Globals from 'src/Globals';
import {PillData} from 'src/components/pill';
import envConfig from 'src/env.config';

type ChannelCategoriesReturnType = {
isLoading: boolean;
channelCategories: PillData[];
};

const useChannelCategories = (): ChannelCategoriesReturnType => {
const [channelCategories, setChannelCategories] = useState<PillData[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);

useEffect(() => {
getChannelCategories();
}, []);

const getChannelCategories = async () => {
setIsLoading(true);
try {
const requestURL =
envConfig.EPNS_SERVER + envConfig.ENDPOINT_FETCH_CHANNEL_CATEGORIES;
const resJson = await fetch(requestURL).then(response => response.json());
// Modify data for pill component
const modifiedData = resJson?.tags?.tags.map((category: string) => ({
label: category,
value: category,
}));
setChannelCategories([
{
label: Globals.CONSTANTS.ALL_CATEGORIES,
value: Globals.CONSTANTS.ALL_CATEGORIES,
},
...modifiedData,
]);
} catch (e) {
} finally {
setIsLoading(false);
}
};

return {isLoading, channelCategories};
};

export {useChannelCategories};
Loading

0 comments on commit 3d6d3d9

Please sign in to comment.