Skip to content

Commit

Permalink
show nft list in choose nft for moment screen
Browse files Browse the repository at this point in the history
  • Loading branch information
aldhosutra committed Nov 22, 2022
1 parent f6ddff1 commit b66aace
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 36 deletions.
25 changes: 25 additions & 0 deletions src/components/atoms/form/AppRadioButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Theme } from 'enevti-app/theme/default';
import React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { RadioButton, useTheme } from 'react-native-paper';

interface AppRadioButtonProps {
value: string;
checked: string;
onPress?: (value: string) => void;
style?: StyleProp<ViewStyle>;
}

export default function AppRadioButton({ value, checked, onPress, style }: AppRadioButtonProps) {
const theme = useTheme() as Theme;

return (
<RadioButton.Android
value={value}
status={checked === value ? 'checked' : 'unchecked'}
onPress={onPress ? () => onPress(value) : undefined}
color={theme.colors.primary}
style={style}
/>
);
}
180 changes: 164 additions & 16 deletions src/screen/createMoment/ChooseNFTforMoment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, Text, ScrollView, StyleSheet, Platform } from 'react-native';
import { View, StyleSheet, FlatList, FlatListProps, RefreshControl } from 'react-native';
import React from 'react';
import { StackScreenProps } from '@react-navigation/stack';
import { RootStackParamList } from 'enevti-app/navigation';
Expand All @@ -12,15 +12,129 @@ import { hp, SafeAreaInsets, wp } from 'enevti-app/utils/layout/imageRatio';
import AppPrimaryButton from 'enevti-app/components/atoms/button/AppPrimaryButton';
import { useTranslation } from 'react-i18next';
import AppHeaderWizard from 'enevti-app/components/molecules/view/AppHeaderWizard';
import { getProfileInitialMomentSlot, getProfileMomentSlot } from 'enevti-app/service/enevti/profile';
import AppActivityIndicator from 'enevti-app/components/atoms/loading/AppActivityIndicator';
import { NFTBase } from 'enevti-app/types/core/chain/nft';
import { PaginationStore } from 'enevti-app/types/ui/store/PaginationStore';
import Animated, { useAnimatedRef } from 'react-native-reanimated';
import AppListItem, { LIST_ITEM_VERTICAL_MARGIN_PERCENTAGE } from 'enevti-app/components/molecules/list/AppListItem';
import AppNFTRenderer from 'enevti-app/components/molecules/nft/AppNFTRenderer';
import AppTextHeading3 from 'enevti-app/components/atoms/text/AppTextHeading3';
import AppTextBody4 from 'enevti-app/components/atoms/text/AppTextBody4';
import utilityToLabel from 'enevti-app/utils/format/utilityToLabel';
import { useDispatch, useSelector } from 'react-redux';
import { hideModalLoader, showModalLoader } from 'enevti-app/store/slices/ui/global/modalLoader';
import { selectMyPersonaCache } from 'enevti-app/store/slices/entities/cache/myPersona';
import { PROFILE_MOMENT_SLOT_RESPONSE_LIMIT } from 'enevti-app/utils/constant/limit';
import AppRadioButton from 'enevti-app/components/atoms/form/AppRadioButton';

const MOMENT_SLOT_ITEM_HEIGHT = 9;
type Props = StackScreenProps<RootStackParamList, 'ChooseNFTforMoment'>;
const AnimatedFlatList = Animated.createAnimatedComponent<FlatListProps<NFTBase>>(FlatList);

export default function ChooseNFTforMoment({ navigation, route }: Props) {
export default function ChooseNFTforMoment({ navigation }: Props) {
const dispatch = useDispatch();
const { t } = useTranslation();
const theme = useTheme() as Theme;
const insets = useSafeAreaInsets();
const styles = React.useMemo(() => makeStyles(theme, insets), [theme, insets]);

const nftListRef = useAnimatedRef<FlatList>();
const myPersona = useSelector(selectMyPersonaCache);
const abortController = React.useRef<AbortController>();
const [selectedNFT, setSelectedNFT] = React.useState<string>('');
const [momentSlot, setMomentSlot] = React.useState<NFTBase[]>();
const [momentSlotPagination, setMomentSlotPagination] = React.useState<PaginationStore>();

const itemHeight = React.useMemo(() => hp(MOMENT_SLOT_ITEM_HEIGHT + LIST_ITEM_VERTICAL_MARGIN_PERCENTAGE), []);

const keyExtractor = React.useCallback((item: NFTBase) => item.id, []);

const getItemLayout = React.useCallback(
(_, index) => ({
length: itemHeight,
offset: itemHeight * index,
index,
}),
[itemHeight],
);

const renderItem = React.useCallback(
({ item }: { item: NFTBase }) => (
<AppListItem
style={styles.momentSlotItem}
leftContent={<AppNFTRenderer imageSize={'s'} nft={item} width={wp('13%')} style={styles.nftRenderer} />}
rightContent={
<View style={styles.momentSlotRightContent}>
<AppRadioButton value={item.id} checked={selectedNFT} onPress={id => setSelectedNFT(id)} />
</View>
}
onPress={() => setSelectedNFT(item.id)}>
<AppTextHeading3 numberOfLines={1}>{`${item.symbol}#${item.serial}`}</AppTextHeading3>
<AppTextBody4 style={{ color: theme.colors.placeholder }} numberOfLines={1}>
{utilityToLabel(item.utility)}
</AppTextBody4>
</AppListItem>
),
[styles.momentSlotItem, styles.nftRenderer, styles.momentSlotRightContent, selectedNFT, theme.colors.placeholder],
);

const listFooter = React.useMemo(
() => (
<View>
{momentSlot &&
momentSlotPagination !== undefined &&
momentSlotPagination.version !== momentSlot.length &&
momentSlot.length !== 0 ? (
<AppActivityIndicator style={{ marginVertical: hp('3%') }} />
) : null}
</View>
),
[momentSlot, momentSlotPagination],
);

const onLoad = React.useCallback(async () => {
const momentSlotResponse = await getProfileInitialMomentSlot(myPersona.address);
if (momentSlotResponse.status === 200) {
setMomentSlot(momentSlotResponse.data.data);
setMomentSlotPagination({
checkpoint: momentSlotResponse.data.checkpoint,
version: momentSlotResponse.data.version,
});
}
}, [myPersona.address]);

const onLoadMore = React.useCallback(async () => {
if (momentSlot && momentSlotPagination && momentSlot.length !== momentSlotPagination.version) {
const momentSlotResponse = await getProfileMomentSlot(
myPersona.address,
momentSlotPagination.checkpoint,
PROFILE_MOMENT_SLOT_RESPONSE_LIMIT,
momentSlotPagination.version,
abortController.current?.signal,
);
if (momentSlotResponse.status === 200) {
setMomentSlot(old => (old !== undefined ? [...old, ...momentSlotResponse.data.data] : undefined));
}
}
}, [momentSlot, momentSlotPagination, myPersona.address]);

const onRefresh = React.useCallback(async () => {
dispatch(showModalLoader());
await onLoad();
dispatch(hideModalLoader());
}, [dispatch, onLoad]);

const refreshControl = React.useMemo(() => <RefreshControl refreshing={false} onRefresh={onRefresh} />, [onRefresh]);

React.useEffect(() => {
abortController.current = new AbortController();
onLoad();
return () => {
abortController.current && abortController.current.abort();
};
}, [onLoad]);

return (
<AppView
withModal
Expand All @@ -31,28 +145,64 @@ export default function ChooseNFTforMoment({ navigation, route }: Props) {
header={
<AppHeader compact back backIcon={iconMap.close} backIconSize={23} navigation={navigation} title={' '} />
}>
<AppHeaderWizard
title={t('createMoment:addMoment')}
description={t('createMoment:addMomentDescription')}
style={styles.header}
memoKey={[]}
/>
<ScrollView style={styles.scrollContainer}>
<Text>ChooseNFTforMoment</Text>
</ScrollView>
{momentSlot !== undefined ? (
<AnimatedFlatList
ref={nftListRef}
keyExtractor={keyExtractor}
scrollEventThrottle={16}
showsVerticalScrollIndicator={false}
getItemLayout={getItemLayout}
data={momentSlot}
renderItem={renderItem}
refreshControl={refreshControl}
ListHeaderComponent={
<AppHeaderWizard
title={t('createMoment:addMoment')}
description={t('createMoment:addMomentDescription')}
style={styles.header}
memoKey={[]}
/>
}
ListFooterComponent={listFooter}
onEndReachedThreshold={0.1}
onEndReached={onLoadMore}
/>
) : (
<View style={styles.loaderContainer}>
<AppActivityIndicator animating />
</View>
)}
<View style={styles.actionContainer}>
<View style={{ height: hp('2%', insets) }} />
<AppPrimaryButton onPress={() => {}} style={styles.actionButton}>
{t('createNFT:createButton')}
<AppPrimaryButton disabled={selectedNFT === ''} onPress={() => {}} style={styles.actionButton}>
{selectedNFT === '' ? t('createMoment:pleaseSelectNFT') : t('createMoment:attachToThis')}
</AppPrimaryButton>
<View style={{ height: Platform.OS === 'ios' ? insets.bottom : hp('2%', insets) }} />
</View>
</AppView>
);
}

const makeStyles = (theme: Theme, insets: SafeAreaInsets) =>
StyleSheet.create({
momentSlotRightContent: {
justifyContent: 'center',
},
nftRenderer: {
width: wp('13%'),
marginRight: wp('2%'),
alignSelf: 'center',
borderRadius: wp('13%'),
overflow: 'hidden',
},
momentSlotItem: {
height: hp(MOMENT_SLOT_ITEM_HEIGHT),
},
loaderContainer: {
justifyContent: 'center',
alignItems: 'center',
width: '100%',
flex: 1,
},
header: {
flex: 0,
marginLeft: wp('3%', insets),
Expand All @@ -63,10 +213,8 @@ const makeStyles = (theme: Theme, insets: SafeAreaInsets) =>
zIndex: -9,
},
actionContainer: {
position: 'absolute',
backgroundColor: theme.colors.background,
width: '100%',
bottom: 0,
},
actionButton: {
marginLeft: wp('5%', insets),
Expand Down
6 changes: 5 additions & 1 deletion src/service/enevti/feed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ProfileAPIVersion } from 'enevti-app/types/core/account/profile';
import { APIResponse, APIResponseVersionRoot } from 'enevti-app/types/core/service/api';
import { Feeds, HomeFeeds } from 'enevti-app/types/core/service/feed';
import { Feeds, HomeFeeds, Moments } from 'enevti-app/types/core/service/feed';
import { HOME_FEED_LIMIT } from 'enevti-app/utils/constant/limit';
import { urlGetFeeds, urlGetHome } from 'enevti-app/utils/constant/URLCreator';
import { apiFetch, apiFetchVersionRoot } from 'enevti-app/utils/app/network';
Expand Down Expand Up @@ -42,6 +42,10 @@ export function parseFeedCache(feeds: Feeds) {
return feeds.slice(0, FEED_CACHE_MAX_LENGTH);
}

export function parseMomentCache(moments: Moments) {
return moments.slice(0, 10);
}

export async function getHome(
signal?: AbortController['signal'],
silent?: boolean,
Expand Down
16 changes: 0 additions & 16 deletions src/service/enevti/moment.ts

This file was deleted.

34 changes: 33 additions & 1 deletion src/service/enevti/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
urlGetProfile,
urlGetProfileBalance,
urlGetProfileCollection,
urlGetProfileMomentSlot,
urlGetProfileNonce,
urlGetProfileOwned,
urlGetProfilePendingDelivery,
Expand All @@ -26,7 +27,11 @@ import { APIResponse, APIResponseVersioned, APIResponseVersionRoot } from 'enevt
import { NFTSecret } from 'enevti-app/types/core/chain/nft/NFTSecret';
import { RootStackParamList } from 'enevti-app/navigation';
import { StackScreenProps } from '@react-navigation/stack';
import { PROFILE_OWNED_INITIAL_LENGTH, PROFILE_COLLECTION_INITIAL_LENGTH } from 'enevti-app/utils/constant/limit';
import {
PROFILE_OWNED_INITIAL_LENGTH,
PROFILE_COLLECTION_INITIAL_LENGTH,
PROFILE_MOMENT_SLOT_INITIAL_LENGTH,
} from 'enevti-app/utils/constant/limit';
import { NFTBase } from 'enevti-app/types/core/chain/nft';
import { selectMyPersonaCache } from 'enevti-app/store/slices/entities/cache/myPersona';

Expand Down Expand Up @@ -77,6 +82,16 @@ async function fetchProfileAddressFromUsername(
return await apiFetch<string>(urlGetUsernameToAddress(username), signal);
}

async function fetchProfileMomentSlot(
address: string,
offset: number,
limit: number,
version: number,
signal?: AbortController['signal'],
): Promise<APIResponseVersioned<NFTBase[]>> {
return await apiFetchVersioned<NFTBase[]>(urlGetProfileMomentSlot(address, offset, limit, version), signal);
}

async function fetchProfileOwned(
address: string,
offset: number,
Expand Down Expand Up @@ -114,6 +129,23 @@ export async function getProfileInitialCollection(
return await fetchProfileCollection(address, 0, PROFILE_COLLECTION_INITIAL_LENGTH, 0, signal);
}

export async function getProfileInitialMomentSlot(
address: string,
signal?: AbortController['signal'],
): Promise<APIResponseVersioned<NFTBase[]>> {
return await fetchProfileMomentSlot(address, 0, PROFILE_MOMENT_SLOT_INITIAL_LENGTH, 0, signal);
}

export async function getProfileMomentSlot(
address: string,
offset: number,
limit: number,
version: number,
signal?: AbortController['signal'],
): Promise<APIResponseVersioned<NFTBase[]>> {
return await fetchProfileMomentSlot(address, offset, limit, version, signal);
}

export async function getProfileOwned(
address: string,
offset: number,
Expand Down
4 changes: 2 additions & 2 deletions src/store/middleware/thunk/ui/view/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
setFeedViewState,
} from 'enevti-app/store/slices/ui/view/feed';
import { lastFetchTimeout } from 'enevti-app/utils/constant/lastFetch';
import { getHome, getMoreFeeds, parseFeedCache } from 'enevti-app/service/enevti/feed';
import { getHome, getMoreFeeds, parseFeedCache, parseMomentCache } from 'enevti-app/service/enevti/feed';
import i18n from 'enevti-app/translations/i18n';
import {
HOME_FEED_LIMIT,
Expand Down Expand Up @@ -95,7 +95,7 @@ export const loadFeeds = createAsyncThunk<void, loadFeedsArgs, AsyncThunkAPI>(
reqVersion: homeResponse.version.feed,
}),
);
dispatch(setMomentItemsCache(homeResponse.data.moment));
dispatch(setMomentItemsCache(parseMomentCache(homeResponse.data.moment)));
dispatch(
setMyProfileCache({
...parseProfileCache(homeResponse.data.profile as Profile),
Expand Down
10 changes: 10 additions & 0 deletions src/utils/constant/URLCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,16 @@ export function urlGetProfilePendingDelivery(address: string, host: string = ENE
return encodeURI(`${host}/profile/${address}/pending`);
}

export function urlGetProfileMomentSlot(
address: string,
offset: number = 0,
limit: number = 10,
version: number = 0,
host: string = ENEVTI_DEFAULT_API,
) {
return encodeURI(`${host}/profile/${address}/moment/slot?offset=${offset}&limit=${limit}&version=${version}`);
}

export function urlGetStakePoolByAddress(
address: string,
withInitialData: boolean = false,
Expand Down
2 changes: 2 additions & 0 deletions src/utils/constant/limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const PROFILE_ONSALE_INITIAL_LENGTH = 6;
export const PROFILE_ONSALE_RESPONSE_LIMIT = 6;
export const PROFILE_COLLECTION_INITIAL_LENGTH = 8;
export const PROFILE_COLLECTION_RESPONSE_LIMIT = 8;
export const PROFILE_MOMENT_SLOT_INITIAL_LENGTH = 5;
export const PROFILE_MOMENT_SLOT_RESPONSE_LIMIT = 5;
export const COLLECTION_MINTED_INITIAL_LENGTH = 6;
export const COLLECTION_MINTED_RESPONSE_LIMIT = 6;
export const COLLECTION_ACTIVITY_INITIAL_LENGTH = 8;
Expand Down

0 comments on commit b66aace

Please sign in to comment.