react-native#SectionList TypeScript Examples
The following examples show how to use
react-native#SectionList.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: HabitList.tsx From nyxo-app with GNU General Public License v3.0 | 6 votes |
List = styled(SectionList as new () => SectionList<Habit>).attrs(
({ theme }) => ({
contentContainerStyle: {
backgroundColor: theme.PRIMARY_BACKGROUND_COLOR
}
})
)`
background-color: ${colors.darkBlue};
`
Example #2
Source File: index.tsx From react-native-scroll-bottom-sheet with MIT License | 6 votes |
private getScrollComponent = () => {
switch (this.props.componentType) {
case 'FlatList':
return FlatList;
case 'ScrollView':
return ScrollView;
case 'SectionList':
return SectionList;
default:
throw new Error(
'Component type not supported: it should be one of `FlatList`, `ScrollView` or `SectionList`'
);
}
};
Example #3
Source File: HabitView.tsx From nyxo-app with GNU General Public License v3.0 | 5 votes |
HabitView: FC = () => {
const dispatch = useAppDispatch()
const renderItem = ({ item }: { item: Habit }) => {
return <HabitCard key={item.id} habit={item} />
}
const toggleModal = () => {
dispatch(toggleNewHabitModal(true))
}
const sections = [
{
title: 'HABIT.ACTIVE',
subtitle: 'HABIT.ACTIVE_SUBTITLE',
data: []
},
{
title: 'HABIT.ARCHIVED',
subtitle: 'HABIT.ARCHIVED_SUBTITLE',
data: []
}
]
const habitKeyExtractor = (item: Habit) => {
return item.id
}
return (
<SafeAreaView>
<SectionList
ListHeaderComponent={() => (
<>
<TitleRow>
<PageTitle adjustsFontSizeToFit>HABIT.HABIT_TITLE</PageTitle>
<NewHabitButton onPress={toggleModal}>
<IconBold
width={20}
height={20}
name="circleAdd"
fill={colors.darkBlue}
/>
</NewHabitButton>
</TitleRow>
<Container>
<P>HABIT.EXPLANATION_1</P>
<P>HABIT.EXPLANATION_2</P>
</Container>
</>
)}
renderSectionHeader={({ section: { title, data, subtitle } }) => (
<CoachingSectionHeader
data={data}
title={title}
subtitle={subtitle}
/>
)}
sections={sections}
ListEmptyComponent={<EmptyState />}
renderItem={renderItem}
keyExtractor={habitKeyExtractor}
/>
<EditHabitModal />
<NewHabitModal />
</SafeAreaView>
)
}
Example #4
Source File: Lessons.tsx From nyxo-app with GNU General Public License v3.0 | 5 votes |
AnimatedSectionList = Animated.createAnimatedComponent(SectionList)
Example #5
Source File: Mobility.tsx From wuhan2020-frontend-react-native-app with MIT License | 5 votes |
function MobilityScreen() {
const { data, loading, refresh } = useContext(MobilityDataContext);
const [refreshing, setRefreshing] = useState(false);
function keyExtractor(item: EntryType) {
return String(item.id);
}
function renderItem({ item }: { item: EntryType }) {
return <Mobility item={item} />;
}
const onRefresh = useCallback(() => {
setRefreshing(true);
refresh();
wait(2000).then(() => setRefreshing(false));
}, [refreshing, refresh]);
return (
<StatusBarSafeLayout>
<View>
<SectionList
keyExtractor={keyExtractor}
refreshing={loading}
refreshControl={
<RefreshControl
tintColor="pink"
refreshing={refreshing}
onRefresh={onRefresh}
/>
}
renderItem={renderItem}
sections={toSection(data)}
renderSectionHeader={({ section: { title } }) => (
<View style={{ backgroundColor: '#eee' }}>
<Text style={styles.subheader}>{title}</Text>
</View>
)}
ListEmptyComponent={<ActivityIndicator size="large" color="red" />}
ListHeaderComponent={
<View style={styles.header}>
<H1 title="确诊患者相同行程查询" />
</View>
}
/>
</View>
</StatusBarSafeLayout>
);
}
Example #6
Source File: index.tsx From react-native-section-alphabet-list with MIT License | 4 votes |
AlphabetList: React.FC<AlphabetListProps> = (props) => {
const {
data,
index = DEFAULT_CHAR_INDEX,
style,
indexContainerStyle,
indexLetterStyle,
indexLetterContainerStyle,
letterListContainerStyle,
getItemHeight: onGetItemHeight = () => sizes.itemHeight,
sectionHeaderHeight = sizes.itemHeight,
listHeaderHeight = sizes.listHeaderHeight,
uncategorizedAtTop = false,
renderCustomSectionHeader,
renderCustomItem,
renderCustomListHeader,
renderCustomIndexLetter,
...sectionListProps
} = props
const sectionListRef = useRef(null);
const [sectionData, setSectionData] = useState<ISectionData[]>([])
useEffect(() => {
setSectionData(getSectionData(data, index, uncategorizedAtTop))
}, [data])
const onScrollToSection = (sectionIndex: number) => {
const sectionList = sectionListRef.current! as SectionList;
if (!sectionList) return
sectionList.scrollToLocation({
sectionIndex,
itemIndex: 0,
});
}
const onGetItemLayout: any = sectionListGetItemLayout({
getItemHeight: (_rowData, sectionIndex: number, rowIndex: number) => {
return onGetItemHeight(sectionIndex, rowIndex)
},
getSectionHeaderHeight: () => sectionHeaderHeight,
getSectionFooterHeight: () => 0,
listHeaderHeight,
});
const onRenderSectionHeader = ({ section }: { section: SectionListData<IData> }) => {
if (renderCustomSectionHeader) return renderCustomSectionHeader(section);
return (
<View testID="header" style={styles.sectionHeaderContainer}>
<Text testID="header__label" style={styles.sectionHeaderLabel}>{section.title}</Text>
</View>
);
};
const onRenderItem = ({ item }: { item: IData }) => {
if (renderCustomItem) return renderCustomItem(item);
return (
<View testID="cell" style={styles.listItemContainer}>
<Text testID="cell__label" style={styles.listItemLabel}>{item.value}</Text>
</View>
);
};
return (
<View style={[styles.container, style]}>
<SectionList
{...sectionListProps}
testID="sectionList"
ref={sectionListRef}
sections={sectionData}
keyExtractor={(item: IData) => item.key}
renderItem={onRenderItem}
renderSectionHeader={onRenderSectionHeader}
ListHeaderComponent={renderCustomListHeader}
getItemLayout={onGetItemLayout}
/>
<ListLetterIndex
sectionData={sectionData}
onPressLetter={onScrollToSection}
indexContainerStyle={indexContainerStyle}
indexLetterStyle={indexLetterStyle}
indexLetterContainerStyle={indexLetterContainerStyle}
letterListContainerStyle={letterListContainerStyle}
renderCustomIndexLetter={renderCustomIndexLetter}
/>
</View>
);
}
Example #7
Source File: bankPayment.tsx From THUInfo with MIT License | 4 votes |
BankPaymentScreen = () => {
const [data, setData] = useState<BankPaymentByMonth[]>([]);
const [refreshing, setRefreshing] = useState(true);
const themeName = useColorScheme();
const theme = themes(themeName);
const fetchData = () => {
setRefreshing(true);
helper
.getBankPayment()
.then((r) => {
setData(r);
setRefreshing(false);
})
.catch(() => {
Snackbar.show({
text: getStr("networkRetry"),
duration: Snackbar.LENGTH_SHORT,
});
setRefreshing(false);
});
};
useEffect(fetchData, []);
return (
<>
<SectionList
sections={data.map(({month, payment}) => ({
month,
data: payment,
}))}
renderSectionHeader={({section}) => (
<View style={{marginTop: 12, paddingTop: 12}}>
<View
style={{
padding: 9,
paddingBottom: 0,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}>
<Text
numberOfLines={1}
style={{
fontSize: 20,
fontWeight: "bold",
flex: 1,
color: theme.colors.text,
}}>
{section.month}
</Text>
<Text
numberOfLines={1}
style={{
fontSize: 20,
fontWeight: "bold",
flex: 0,
color: theme.colors.text,
}}>
{section.data
.reduce((acc, payment) => acc + Number(payment.total), 0)
.toFixed(2)}
</Text>
</View>
</View>
)}
renderItem={({item}) => <PaymentItem payment={item} />}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={fetchData}
colors={[theme.colors.accent]}
/>
}
keyExtractor={(item) => `${item.time}`}
/>
</>
);
}
Example #8
Source File: report.tsx From THUInfo with MIT License | 4 votes |
ReportScreen = () => {
const [report, setReport] = useState<Course[]>();
const [refreshing, setRefreshing] = useState(true);
const [flag, setFlag] = useState<1 | 2 | 3>(1);
const [bx, setBx] = useState(false);
const themeName = useColorScheme();
const theme = themes(themeName);
const fetchData = () => {
setRefreshing(true);
helper
.getReport(bx && flag === 1, true, flag)
.then((res) => {
setReport(res);
setRefreshing(false);
})
.catch(() => {
Snackbar.show({
text: getStr("networkRetry"),
duration: Snackbar.LENGTH_SHORT,
});
setRefreshing(false);
});
};
useEffect(fetchData, [bx, flag]);
const {gpa, sections, allCredits, totalCredits, totalPoints} = prepareData(
report || [],
);
return (
<View style={{marginHorizontal: 20, flex: 1}}>
<View style={{flexDirection: "row", margin: 5}}>
<TouchableOpacity
style={{padding: 6, flex: 1}}
onPress={() => setFlag(1)}>
<Text
style={{
color: flag === 1 ? "blue" : theme.colors.text,
textAlign: "center",
}}>
{getStr("reportFlag1")}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{padding: 6, flex: 1}}
onPress={() => setFlag(2)}>
<Text
style={{
color: flag === 2 ? "blue" : theme.colors.text,
textAlign: "center",
}}>
{getStr("reportFlag2")}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{padding: 6, flex: 1}}
onPress={() => setFlag(3)}>
<Text
style={{
color: flag === 3 ? "blue" : theme.colors.text,
textAlign: "center",
}}>
{getStr("reportFlag3")}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{padding: 6, flex: 1}}
onPress={() => setBx((o) => !o)}>
<Text style={{color: "blue", textAlign: "center"}}>
{getStr(bx ? "bx" : "bxr")}
</Text>
</TouchableOpacity>
</View>
<SectionList
sections={sections}
stickySectionHeadersEnabled={false}
renderSectionHeader={({section}) => (
<ReportHeader
semester={section.semester}
gpa={section.gpa}
totalCredits={section.totalCredits}
allCredits={section.allCredits}
totalPoints={section.totalPoints}
/>
)}
renderItem={({item}) => (
<ReportItem
name={item.name}
credit={item.credit}
grade={item.grade}
point={item.point}
/>
)}
ListHeaderComponent={
<ReportSummary
gpa={gpa}
totalCredits={totalCredits}
allCredits={allCredits}
totalPoints={totalPoints}
/>
}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={fetchData}
colors={[theme.colors.accent]}
/>
}
keyExtractor={(item, index) => `${item.semester}${index}`}
/>
</View>
);
}
Example #9
Source File: settings.tsx From nyxo-app with GNU General Public License v3.0 | 4 votes |
SettingsScreen: FC<Props> = ({ navigation }) => {
const theme = useAppSelector((state) => state.theme.theme)
const rateApp = () => {
// FIXME
Rate.rate(options, () => undefined)
}
const displayTheme = (t: string) => (t === 'dark' ? 'Light' : 'Dark')
const settings = [
{
text: 'Select Tracking Source',
icon: 'smartWatchCircleGraph',
action: () => navigation.navigate('Sources')
},
{
text: 'Coaching settings',
icon: 'schoolPhysicalBold',
action: () => navigation.navigate('Coaching')
},
{
text: 'Manage Nyxo Subscription',
icon: 'receipt',
action: () => navigation.navigate('Subscription')
},
{
text: 'Sync to backend',
icon: 'syncCloud',
action: () => navigation.navigate('Cloud', { code: '' })
},
{
text: 'Switch mode',
icon: 'astronomyMoon',
theme: displayTheme(theme),
action: () => navigation.push('Theme')
},
{
text: 'RATE_APP',
icon: 'star',
action: rateApp
},
{
text: 'ONBOARDING.TITLE',
icon: 'compass',
action: () => navigation.push('Onboarding')
}
]
const socialActions = [
{
text: 'Send feedback',
icon: 'envelope',
action: () => Linking.openURL('mailto:[email protected]')
},
{
text: 'Visit site',
icon: 'browserDotCom',
action: () => Linking.openURL('https://nyxo.app/')
},
{
text: 'Follow us on Facebook',
icon: 'facebook',
action: () =>
Linking.openURL('https://www.facebook.com/Nyxo-467927177117033/')
},
{
text: 'Follow us on Twitter',
icon: 'twitter',
action: () => Linking.openURL('https://twitter.com/hellonyxo')
},
{
text: 'Follow us on Instagram',
icon: 'instagram',
action: () => Linking.openURL('https://www.instagram.com/hellonyxo/')
},
{
text: 'Terms of Service',
icon: 'handshake',
action: () => Linking.openURL(CONFIG.TERMS_LINK)
},
{
text: 'Privacy Policy',
icon: 'lockCircle',
action: () => Linking.openURL(CONFIG.PRIVACY_LINK)
}
]
const renderItem: ListRenderItem<SettingItem> = ({ item }) => {
if (!item) return null
return (
<SettingRow onPress={item.action} badge={item.badge} icon={item.icon}>
<Title>{`${item.text}`}</Title>
</SettingRow>
)
}
const renderSectionHeader = ({
section
}: {
section: SectionListData<SettingItem, { title: string }>
}) => {
if (section.title === 'Settings') return null
return <SectionHeader>{section.title}</SectionHeader>
}
const sections = [
{
title: 'Settings',
data: settings
},
{
title: 'Support',
data: socialActions
}
]
return (
<SafeAreaView>
<SectionList
ListHeaderComponent={<PageTitle>Settings</PageTitle>}
sections={sections}
renderSectionHeader={renderSectionHeader}
keyExtractor={keyExtractor}
renderItem={renderItem}
ListFooterComponent={<VersionInformation />}
/>
</SafeAreaView>
)
}
Example #10
Source File: Main.tsx From DoobooIAP with MIT License | 4 votes |
function Intro(): React.ReactElement {
const [sections, setSections] = useState<Section[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [totalPaidAmount, setTotalPaidAmount] = useState<number>(10000);
const { theme } = useThemeContext();
const getProducts = useCallback(async (): Promise<void> => {
RNIap.clearProductsIOS();
try {
const result = await RNIap.initConnection();
await RNIap.consumeAllItemsAndroid();
console.log('result', result);
} catch (err) {
console.warn(err.code, err.message);
}
purchaseUpdateSubscription = purchaseUpdatedListener(
async (purchase: InAppPurchase | SubscriptionPurchase) => {
const receipt = purchase.transactionReceipt;
if (receipt) {
try {
const ackResult = await finishTransaction(purchase);
console.log('ackResult', ackResult);
} catch (ackErr) {
console.warn('ackErr', ackErr);
}
Alert.alert('purchase error', JSON.stringify(receipt));
}
},
);
purchaseErrorSubscription = purchaseErrorListener(
(error: PurchaseError) => {
console.log('purchaseErrorListener', error);
Alert.alert('purchase error', JSON.stringify(error));
},
);
const products = await RNIap.getProducts(itemSkus);
products.forEach((product) => {
product.type = 'inapp';
});
// console.log('products', JSON.stringify(products));
const subscriptions = await RNIap.getSubscriptions(itemSubs);
subscriptions.forEach((subscription) => {
subscription.type = 'subs';
});
console.log('subscriptions', JSON.stringify(subscriptions));
const list = [
{
title: getString('ONE_TIME_PURCHASE'),
data: products,
},
{
title: getString('SUBSCRIPTION_PURCHASE'),
data: subscriptions,
},
];
setSections(list);
setLoading(false);
}, [sections]);
useEffect(() => {
getProducts();
return (): void => {
if (purchaseUpdateSubscription) {
purchaseUpdateSubscription.remove();
}
if (purchaseErrorSubscription) {
purchaseErrorSubscription.remove();
}
};
}, []);
const purchase = (item: Product | Subscription): void => {
if (getSkuType(item) === ITEM_TYPE.PRODUCT) {
RNIap.requestPurchase(item.productId);
} else {
RNIap.requestSubscription(item.productId);
}
};
const renderHeader = (): ReactElement => (
<Header>
<Text
style={{
fontSize: 28,
color: 'white',
}}
>
{totalPaidAmount.toLocaleString()}
</Text>
<Text
style={{
marginTop: 8,
fontSize: 16,
color: 'white',
}}
>
{getString('TOTAL_PURCHASE')}
</Text>
</Header>
);
const renderSectionHeader = (title: string): ReactElement => (
<View
style={{
height: 40,
flexDirection: 'row',
paddingHorizontal: 16,
backgroundColor: theme.placeholder,
}}
>
<Text
style={{
fontSize: 13,
color: theme.background,
marginTop: 12,
}}
>
{title}
</Text>
</View>
);
const renderItem = (item: Product | Subscription): ReactElement => (
<View
style={{
padding: 16,
flexDirection: 'row',
backgroundColor: theme.background,
borderBottomWidth: 1,
borderBottomColor: theme.placeholder,
}}
>
<Image
source={IC_COIN}
style={{
height: 80,
width: 80,
}}
/>
<View
style={{
flexDirection: 'column',
marginLeft: 32,
marginRight: 20,
}}
>
<Text
style={{
fontSize: 16,
color: theme.brandLight,
marginBottom: 4,
}}
>
{item.title}
</Text>
<Text
style={{
fontSize: 14,
color: theme.font,
}}
>
{item.productId}
</Text>
<Text
style={{
fontSize: 14,
fontWeight: 'bold',
color: theme.warning,
}}
>
{getSkuType(item) === ITEM_TYPE.SUBSCRIPTION &&
getTrialPeriod(item as Subscription) !== 0 &&
`FREE TRIAL DAYS: ${getTrialPeriod(item as Subscription)}`}
</Text>
<Button
containerStyle={{
width: 120,
marginTop: 16,
}}
style={{
backgroundColor: theme.background,
height: 40,
width: 120,
borderWidth: 1,
borderColor: theme.focused,
borderRadius: 8,
paddingHorizontal: 10,
}}
textStyle={{
color: theme.font,
}}
onPress={(): void => purchase(item)}
text={item.localizedPrice}
/>
</View>
</View>
);
return (
<Container>
<SectionList
style={{ width: '100%' }}
ListHeaderComponent={renderHeader}
refreshing={loading}
onRefresh={getProducts}
// @ts-ignore
sections={sections}
keyExtractor={(item, index): string => index.toString()}
renderItem={({ item }): ReactElement => renderItem(item)}
renderSectionHeader={({ section: { title } }): ReactElement =>
renderSectionHeader(title)
}
/>
</Container>
);
}
Example #11
Source File: Albums.tsx From jellyfin-audio-player with MIT License | 4 votes |
Albums: React.FC = () => {
// Retrieve data from store
const { entities: albums } = useTypedSelector((state) => state.music.albums);
const isLoading = useTypedSelector((state) => state.music.albums.isLoading);
const lastRefreshed = useTypedSelector((state) => state.music.albums.lastRefreshed);
const sections = useTypedSelector(selectAlbumsByAlphabet);
// Initialise helpers
const dispatch = useAppDispatch();
const navigation = useNavigation<MusicNavigationProp>();
const getImage = useGetImage();
const listRef = useRef<SectionList<EntityId>>(null);
const getItemLayout = useCallback((data: SectionedId[] | null, index: number): { offset: number, length: number, index: number } => {
// We must wait for the ref to become available before we can use the
// native item retriever in VirtualizedSectionList
if (!listRef.current) {
return { offset: 0, length: 0, index };
}
// Retrieve the right item info
// @ts-ignore
const wrapperListRef = (listRef.current?._wrapperListRef) as VirtualizedSectionList;
const info: VirtualizedItemInfo = wrapperListRef._subExtractor(index);
const { index: itemIndex, header, key } = info;
const sectionIndex = parseInt(key.split(':')[0]);
// We can then determine the "length" (=height) of this item. Header items
// end up with an itemIndex of -1, thus are easy to identify.
const length = header ? 50 : (itemIndex % 2 === 0 ? AlbumHeight : 0);
// We'll also need to account for any unevenly-ended lists up until the
// current item.
const previousRows = data?.filter((row, i) => i < sectionIndex)
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
// We must also calcuate the offset, total distance from the top of the
// screen. First off, we'll account for each sectionIndex that is shown up
// until now. This only includes the heading for the current section if the
// item is not the section header
const headingOffset = HeadingHeight * (header ? sectionIndex : sectionIndex + 1);
const currentRows = itemIndex > 1 ? Math.ceil((itemIndex + 1) / 2) : 0;
const itemOffset = AlbumHeight * (previousRows + currentRows);
const offset = headingOffset + itemOffset;
return { index, length, offset };
}, [listRef]);
// Set callbacks
const retrieveData = useCallback(() => dispatch(fetchAllAlbums()), [dispatch]);
const selectAlbum = useCallback((id: string) => navigation.navigate('Album', { id, album: albums[id] as Album }), [navigation, albums]);
const selectLetter = useCallback((sectionIndex: number) => {
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
}, [listRef]);
const generateItem = useCallback(({ item, index, section }: { item: EntityId, index: number, section: SectionedId }) => {
if (index % 2 === 1) {
return <View key={item} />;
}
const nextItem = section.data[index + 1];
return (
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
<GeneratedAlbumItem
id={item}
imageUrl={getImage(item as string)}
name={albums[item]?.Name || ''}
artist={albums[item]?.AlbumArtist || ''}
onPress={selectAlbum}
/>
{albums[nextItem] &&
<GeneratedAlbumItem
id={nextItem}
imageUrl={getImage(nextItem as string)}
name={albums[nextItem]?.Name || ''}
artist={albums[nextItem]?.AlbumArtist || ''}
onPress={selectAlbum}
/>
}
</View>
);
}, [albums, getImage, selectAlbum]);
// Retrieve data on mount
useEffect(() => {
// GUARD: Only refresh this API call every set amounts of days
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
retrieveData();
}
});
return (
<SafeAreaView>
<AlphabetScroller onSelect={selectLetter} />
<SectionList
sections={sections}
refreshing={isLoading}
onRefresh={retrieveData}
getItemLayout={getItemLayout}
ref={listRef}
keyExtractor={(item) => item as string}
renderSectionHeader={generateSection}
renderItem={generateItem}
/>
</SafeAreaView>
);
}
Example #12
Source File: index.tsx From krmanga with MIT License | 4 votes |
function Home({ dispatch, commendList, refreshing, navigation, loading, hasMore }: IProps) {
const headerHeight = useHeaderHeight();
const scrollY: Animated.Value = useRef(new Animated.Value(0)).current;
const [endReached, setEndReached] = useState<boolean>(false);
useEffect(() => {
SplashScreen.hide();//关闭启动屏
dispatch({
type: "home/setState",
payload: {
headerHeight
}
});
syncImmediate();
loadCarouselList();
loadCommendList(true);
}, []);
const syncImmediate = () => {
if (Platform.OS === "android") {
codePush.checkForUpdate().then((update) => {
if (update) {
navigation.navigate("AppUpdate");
}
});
}
};
const loadCarouselList = () => {
dispatch({
type: "home/fetchCarouselList"
});
};
const loadCommendList = (refreshing: boolean, callback?: () => void) => {
dispatch({
type: "home/fetchCommendList",
payload: {
refreshing
},
callback
});
};
const renderSectionHeader = ({ section: { title } }: any) => {
return (
<View style={styles.sectionHeader}>
<View style={styles.cell} />
<Text style={styles.classifyName}>{title}</Text>
</View>
);
};
const onRefresh = () => {
loadCarouselList();
loadCommendList(true);
};
const onEndReached = () => {
if (!hasMore || loading) {
return;
}
setEndReached(true);
loadCommendList(false, () => {
setEndReached(false);
});
};
const renderFooter = () => {
if (endReached) {
return <More />;
}
if (!hasMore) {
return <End />;
}
return null;
};
const goBrief = useCallback((data: IBook) => {
navigation.navigate("Brief", {
id: data.id
});
}, []);
const renderItem = ({ item }: SectionListRenderItemInfo<IBook[]>) => {
return (
<View style={styles.contentContainer}>
{item.map(data => {
return (
<BookCover data={data} goBrief={goBrief} key={data.id} />
);
})}
</View>
);
};
const getTopBarColor = useCallback(() => {
return scrollY.interpolate({
inputRange: [0, maxScroll],
outputRange: [0, 1],
extrapolate: "clamp"
});
}, []);
const TopBarColor = getTopBarColor();
return (
(loading && refreshing) ? <HomePlaceholder /> :
<View style={{ flex: 1 }}>
<CarouselBlurBackground />
<TopBarWrapper navigation={navigation} topBarColor={TopBarColor} />
<SectionList
keyExtractor={(item, index) => `item-${item["id"]}-key-${index}`}
ListHeaderComponent={() => <Carousel />}
renderSectionHeader={renderSectionHeader}
onRefresh={onRefresh}
refreshing={refreshing}
sections={commendList}
stickySectionHeadersEnabled={true}
scrollEventThrottle={1}
onScroll={Animated.event(
[{
nativeEvent: { contentOffset: { y: scrollY } }
}],
{
useNativeDriver: false
}
)}
onEndReached={onEndReached}
onEndReachedThreshold={0.1}
renderItem={renderItem}
extraData={endReached}
ListFooterComponent={renderFooter}
/>
</View>
);
}
Example #13
Source File: index.tsx From krmanga with MIT License | 4 votes |
function History({ dispatch, navigation, isLogin, isEdit, historyList, ids, loading, refreshing, hasMore, pages }: IProps) {
const translateX: Animated.Value = useRef(new Animated.Value(0)).current;
const [endReached, setEndReached] = useState<boolean>(false);
useFocusEffect(
React.useCallback(() => {
loadData(true);
return () => {
dispatch({
type: "history/setState",
payload: {
isEdit: false,
ids: []
}
});
};
}, [isLogin])
);
const loadData = (refreshing: boolean, callback?: () => void) => {
if (isLogin) {
dispatch({
type: "history/fetchHistoryList",
payload: {
refreshing: refreshing
},
callback
});
}
};
const renderSectionHeader = ({ section: { title } }: any) => {
return (
<View style={styles.headerView}>
<Text style={styles.headerTitle}>{title}</Text>
</View>
);
};
const getBeforeX = () => {
Animated.timing(translateX,
{
useNativeDriver: false,
toValue: wp(5),
duration: 150
}
).start();
};
const getAfterX = () => {
Animated.timing(translateX,
{
useNativeDriver: false,
toValue: 0,
duration: 150
}
).start();
};
const onClickItem = useCallback((item: IHistory[]) => {
if (isEdit) {
let i = ids.indexOf(item["book_id"]);
if (i > -1) {
ids.splice(i, 1);
dispatch({
type: "history/setState",
payload: {
ids: [...ids]
}
});
} else {
dispatch({
type: "history/setState",
payload: {
ids: [...ids, item["book_id"]]
}
});
}
} else {
navigation.navigate("Brief", {
id: item["book_id"]
});
}
}, [isEdit, ids]);
const goMangaView = useCallback((item: IHistory[]) => {
navigation.navigate("Brief", {
id: item["book_id"]
});
navigation.navigate("MangaView", {
chapter_num: item["chapter_num"],
markRoast: item["roast"],
book_id: item["book_id"]
});
}, []);
const renderItem = ({ item }: SectionListRenderItemInfo<IHistory[]>) => {
const selected = ids.indexOf(item["book_id"]) > -1;
return (
<Touchable
key={item["id"]}
onPress={() => onClickItem(item)}
>
<Animated.View
style={{ transform: [{ translateX: translateX }] }}
>
<Item
data={item}
isEdit={isEdit}
selected={selected}
goMangaView={goMangaView}
/>
</Animated.View>
</Touchable>
);
};
const onRefresh = () => {
dispatch({
type: "history/setScreenReload"
});
loadData(true);
};
const onEndReached = () => {
if (!hasMore || loading) {
return;
}
setEndReached(true);
loadData(false, () => {
setEndReached(false);
});
};
const renderFooter = () => {
if (endReached) {
return <More />;
}
if (!hasMore) {
return <End />;
}
return null;
};
const cancel = () => {
let newData: string[] = [];
historyList.forEach(items => {
items.data.forEach(item => {
newData = newData.concat(item["book_id"]);
});
}
);
if (newData.length === ids.length) {
dispatch({
type: "history/setState",
payload: {
ids: []
}
});
} else {
dispatch({
type: "history/setState",
payload: {
ids: newData
}
});
}
};
const destroy = () => {
dispatch({
type: "history/delUserHistory",
payload: {
ids
}
});
};
if (isEdit) {
getBeforeX();
} else {
getAfterX();
}
return (
!isLogin ? null :
(loading && refreshing) ? <ListPlaceholder /> :
<View style={styles.container}>
<SectionList
keyExtractor={(item, index) => `section-item-${index}`}
renderSectionHeader={renderSectionHeader}
onRefresh={onRefresh}
refreshing={refreshing}
sections={historyList}
style={styles.container}
stickySectionHeadersEnabled={true}
onEndReached={onEndReached}
onEndReachedThreshold={0.1}
renderItem={renderItem}
extraData={endReached}
ListFooterComponent={renderFooter}
/>
<EditView
data_length={pages.current_page * pages.page_size < pages.total ?
pages.current_page * pages.page_size : pages.total}
isEdit={isEdit}
ids={ids}
cancel={cancel}
destroy={destroy}
/>
</View>
);
}
Example #14
Source File: index.tsx From sharingan-rn-modal-dropdown with MIT License | 4 votes |
GroupDropdown: React.FC<IGroupDropdownProps> = props => {
const {
error,
value,
label,
required,
disabled,
data,
onChange,
floating,
enableSearch,
primaryColor,
elevation,
borderRadius,
activityIndicatorColor,
searchPlaceholder,
helperText,
errorColor,
itemTextStyle,
itemContainerStyle,
showLoader,
animationIn = 'fadeIn',
animationOut = 'fadeOut',
supportedOrientations = ['portrait', 'landscape'],
animationInTiming,
animationOutTiming,
headerTextStyle,
headerContainerStyle,
stickySectionHeadersEnabled,
parentDDContainerStyle,
rippleColor,
emptyListText,
enableAvatar,
avatarSize,
onBlur,
paperTheme,
textInputStyle,
mainContainerStyle,
underlineColor,
disableSelectionTick,
selectedItemTextStyle,
selectedItemViewStyle,
removeLabel,
mode = 'flat',
disabledItemTextStyle,
disabledItemViewStyle,
dropdownIcon = 'menu-down',
dropdownIconSize = 30,
itemSelectIcon,
itemSelectIconSize,
multiline = false,
searchInputTheme,
} = props;
const { colors } = useTheme();
const [selected, setSelected] = useState<string | number>();
const [labelv, setlabelV] = useState<string>('');
const [isVisible, setIsVisible] = useState<boolean>(false);
const [iconColor, setIconColor] = useState<string | undefined>('grey');
const [options, setOptions] = useState<IGroupDropdownData[]>([]);
const [hasError, setError] = useState<boolean>(false);
const [singluarData, setSingularData] = useState<IDropdownData[]>([]);
const [contMeasure, setConMeasure] = useState({
vx: 0,
vy: 0,
vWidth: 0,
vHeight: 0,
});
const [dimension, setDimension] = useState({
dw: deviceWidth,
dh: deviceHeight,
});
const [searchQuery, setSearchQuery] = useState('');
const pViewRef = useRef<View | any>();
const listRef = useRef<SectionList | any>();
useEffect(() => {
Dimensions.addEventListener('change', () => {
const { width, height } = Dimensions.get('window');
setDimension({ dw: width, dh: height });
setIsVisible(false);
setIconColor('grey');
});
return () => {
Dimensions.removeEventListener('change', () => {});
};
}, []);
useEffect(() => {
const destructuredData: IDropdownData[] = [];
Lo.forEach(data, d => {
Lo.forEach(d.data, dv => destructuredData.push(dv));
});
setSingularData(destructuredData);
setOptions(data);
}, [data]);
useEffect(() => {
if (!Lo.isEmpty(singluarData) && value) {
const lFilter = Lo.filter(singluarData, { value: value })[0];
if (!Lo.isEmpty(lFilter)) setlabelV(lFilter.label);
setSelected(value);
}
}, [value, singluarData]);
useEffect(() => {
if (isVisible && listRef) {
listRef.current.flashScrollIndicators();
}
}, [isVisible]);
useEffect(() => {
if (isVisible && selected) {
let secionIndex = 0;
let itemIndex = 0;
if (!Lo.isEmpty(options)) {
options.forEach((e, secIndex) => {
const itIndex = Lo.findIndex(e.data, { value: selected });
if (itIndex >= 0 && listRef) {
itemIndex = itIndex;
secionIndex = secIndex;
setTimeout(() => {
listRef.current.scrollToLocation({
animated: false,
sectionIndex: secionIndex,
itemIndex: itemIndex,
viewPosition: Platform.OS === 'android' ? 0 : 0.5,
});
}, 100);
}
});
}
}
}, [selected, options, isVisible]);
useEffect(() => {
if (disabled) {
setIconColor('lightgrey');
}
}, [disabled]);
useEffect(() => {
if (required && error) {
setError(true);
setIconColor(errorColor);
} else {
setError(false);
setIconColor('grey');
}
}, [required, error, errorColor]);
const onTextInputFocus = () => {
if (hasError) {
setIconColor('red');
} else {
setIconColor(primaryColor);
}
// if (Platform.OS === 'ios') {
pViewRef.current.measureInWindow(
(vx: number, vy: number, vWidth: number, vHeight: number) => {
const ddTop = vy + vHeight;
const bottomMetric = dimension.dh - vy;
if (bottomMetric < 300) {
setConMeasure({ vx, vy: ddTop - 217, vWidth, vHeight });
} else {
setConMeasure({ vx, vy: ddTop, vWidth, vHeight });
}
}
);
// }
setIsVisible(true);
};
const androidOnLayout = () => {
if (Platform.OS === 'android') {
pViewRef.current.measureInWindow(
(vx: number, vy: number, vWidth: number, vHeight: number) => {
const ddTop = vy + vHeight;
const bottomMetric = dimension.dh - vy;
// setPx(bottomMetric);
if (bottomMetric < 300) {
setConMeasure({ vx, vy: ddTop - 217, vWidth, vHeight });
} else {
setConMeasure({ vx, vy: ddTop, vWidth, vHeight });
}
}
);
}
};
const onModalBlur = () => {
setIsVisible(false);
if (hasError) {
setIconColor('red');
} else {
setIconColor('grey');
}
if (onBlur && typeof onBlur === 'function') onBlur();
};
const handleOptionSelect = (v: string | number) => {
const lFilter = Lo.filter(singluarData, { value: v })[0];
if (!Lo.isEmpty(lFilter)) setlabelV(lFilter.label);
setSelected(v);
if (onChange && typeof onChange === 'function') {
onChange(v);
setIsVisible(false);
}
if (hasError) {
setIconColor('red');
} else {
setIconColor('grey');
}
setSearchQuery('');
setOptions(data);
};
const onChangeSearch = (query: string) => {
setSearchQuery(query);
if (!Lo.isEmpty(data) && query) {
let matches: IGroupDropdownData[] = [];
data.forEach(e => {
const sF = e.data.filter(c =>
c.label
.toString()
.toLowerCase()
.trim()
.includes(query.toString().toLowerCase())
);
if (!Lo.isEmpty(sF))
matches = matches.concat([{ title: e.title, data: sF }]);
});
if (matches.length === 0) setOptions([]);
else setOptions(matches);
} else if (!Lo.isEmpty(data) && !query) {
setOptions(data);
}
};
const getEmptyComponent = () => {
if (typeof emptyListText === 'string')
return <EmptyList emptyItemMessage={emptyListText} />;
else return <>{emptyListText}</>;
};
const labelAction = () => {
if (removeLabel) {
return '';
} else {
return required ? `${label}*` : label;
}
};
return (
<PaperProvider theme={paperTheme}>
<View>
<View>
<PressableTouch
onPress={onTextInputFocus}
disabled={disabled}
rippleColor={rippleColor}
>
<View
style={[styles.fullWidth, mainContainerStyle]}
ref={pViewRef}
onLayout={androidOnLayout}
pointerEvents="none"
>
<TextInput
label={labelAction()}
value={labelv}
style={[styles.textInput, textInputStyle]}
underlineColor={underlineColor}
underlineColorAndroid={underlineColor}
editable={false}
error={hasError}
disabled={disabled}
multiline={multiline}
theme={{
...searchInputTheme,
colors: { primary: primaryColor, error: errorColor },
}}
right={
<TextInput.Icon
name={dropdownIcon}
size={dropdownIconSize}
color={iconColor}
/>
}
mode={mode}
/>
</View>
{required && hasError ? (
<HelperText
type="error"
theme={{ colors: { error: errorColor } }}
visible={hasError}
>
{helperText ? helperText : `${label} is required`}
</HelperText>
) : null}
</PressableTouch>
</View>
<View>
<Modal
isVisible={isVisible}
onBackdropPress={onModalBlur}
backdropColor={floating ? 'rgba(0,0,0,0.1)' : 'transparent'}
style={styles.modalStyle}
animationIn={animationIn}
animationOut={animationOut}
animationInTiming={animationInTiming}
animationOutTiming={animationOutTiming}
supportedOrientations={supportedOrientations}
>
<View
style={{
backgroundColor: 'transparent',
width: !floating ? contMeasure.vWidth : 'auto',
left: !floating ? contMeasure.vx : 0,
top: !floating ? contMeasure.vy : 100,
right: 0,
position: 'absolute',
padding: floating ? 20 : 0,
}}
>
<Surface
style={[
styles.surface,
parentDDContainerStyle,
{ elevation, borderRadius },
floating ? { maxHeight: dimension.dh / 2 } : null,
]}
>
{showLoader ? (
<View style={[styles.loader, { borderRadius }]}>
<ActivityIndicator
size="small"
color={activityIndicatorColor}
/>
</View>
) : null}
<SectionList
ref={listRef}
sections={options}
legacyImplementation
initialNumToRender={25}
maxToRenderPerBatch={25}
ListHeaderComponent={
enableSearch ? (
<View>
<Searchbar
placeholder={searchPlaceholder}
onChangeText={onChangeSearch}
value={searchQuery}
theme={{ colors: { primary: primaryColor } }}
style={{
elevation: 0,
backgroundColor: showLoader
? 'transparent'
: colors.background,
height: ITEMLAYOUT,
}}
/>
<Divider style={styles.divider} />
</View>
) : null
}
stickyHeaderIndices={enableSearch ? [0] : undefined}
renderItem={({ item }) => (
<Item
item={item}
onSelect={handleOptionSelect}
selected={value}
selectedColor={primaryColor}
itemTextStyle={itemTextStyle}
itemContainerStyle={itemContainerStyle}
rippleColor={rippleColor}
disabled={showLoader || item?.disabled}
enableAvatar={enableAvatar}
avatarSize={avatarSize}
disableSelectionTick={disableSelectionTick}
selectedItemTextStyle={selectedItemTextStyle}
selectedItemViewStyle={selectedItemViewStyle}
disabledItemTextStyle={disabledItemTextStyle}
disabledItemViewStyle={disabledItemViewStyle}
itemSelectIcon={itemSelectIcon}
itemSelectIconSize={itemSelectIconSize}
/>
)}
renderSectionHeader={({ section: { title } }) => (
<View
style={{
backgroundColor: showLoader
? 'transparent'
: colors.background,
borderRadius: 3,
}}
>
<Divider style={styles.divider} />
<View style={[styles.headerView, headerContainerStyle]}>
<Text style={[styles.headerText, headerTextStyle]}>
{title.trim()}
</Text>
</View>
<Divider style={styles.divider} />
</View>
)}
keyExtractor={() => Math.random().toString()}
ItemSeparatorComponent={() => (
<Divider style={styles.divider} />
)}
getItemLayout={(_d, index) => ({
length: ITEMLAYOUT,
offset: ITEMLAYOUT * index,
index,
})}
stickySectionHeadersEnabled={stickySectionHeadersEnabled}
ListEmptyComponent={getEmptyComponent()}
/>
</Surface>
</View>
</Modal>
</View>
</View>
</PaperProvider>
);
}
Example #15
Source File: invite-screen.tsx From beancount-mobile with MIT License | 4 votes |
export function InviteScreen(props: Props) {
React.useEffect(() => {
async function init() {
await analytics.track("page_view_invite", {});
}
init();
}, []);
const theme = useTheme().colorTheme;
const styles = getStyles(theme);
const contacts = useContacts();
const [selectedContacts, setSelectedContacts] = React.useState<RowItem[]>([]);
const [keyword, setKeyword] = React.useState("");
const sections = React.useMemo(() => {
// @ts-ignore
return Object.entries(
groupBy(
// Create one contact per phone number and email.
contacts.data.reduce((res, cur) => {
if (cur.phoneNumbers != null) {
for (const p of cur.phoneNumbers) {
res.push({
id: cur.id + p.number,
name: cur.name || "",
phoneNumber: p.number,
});
}
}
if (cur.emails != null) {
for (const e of cur.emails) {
res.push({
id: cur.id + e.email,
name: cur.name || "",
email: e.email,
});
}
}
return res;
}, [] as Array<RowItem>),
(c: RowItem) => {
const firstChar = (c.name.charAt(0) || "#").toLowerCase();
return firstChar.match(/[a-z]/) ? firstChar : "#";
}
)
)
.map(([key, value]: [string, RowItem[]]) => ({
key,
data: value.sort((a, b) =>
(a.name || a.name || "") < (b.name || b.name || "") ? -1 : 1
),
}))
.sort((a: { key: string }, b: { key: string }) =>
a.key < b.key ? -1 : 1
);
}, [contacts.data]);
const filteredSection = React.useMemo(() => {
if (keyword.length > 0) {
const filteredSections = new Array<SectionItem>();
for (const s of sections) {
const filteredData = s.data.filter(
(d) =>
d.name.indexOf(keyword) >= 0 ||
(d.email && d.email.indexOf(keyword) >= 0) ||
(d.phoneNumber && d.phoneNumber.indexOf(keyword) >= 0)
);
if (filteredData.length > 0) {
filteredSections.push({ key: s.key, data: filteredData });
}
}
return filteredSections;
}
return sections;
}, [sections, keyword]);
const onInvitePress = async () => {
const { shareLink } = props.route.params;
let didShare = false;
const message = `${i18n.t("recommend")} ${shareLink}`;
const emails = selectedContacts
.filter((c) => c.email != null)
.map((c) => c.email) as string[];
const phoneNumbers = selectedContacts
.filter((c) => c.phoneNumber != null)
.map((c) => c.phoneNumber) as string[];
if (emails.length > 0) {
try {
const result = await composeAsync({
recipients: emails,
subject: "beancount.io",
body: message,
isHtml: false,
});
didShare = didShare || result.status === "sent";
} catch (ex) {
Toast.fail(ex.message);
}
}
if (phoneNumbers.length > 0 && (await isAvailableAsync())) {
try {
const result = await sendSMSAsync(phoneNumbers, message);
didShare = didShare || result.result === "sent";
} catch (ex) {
Toast.fail(ex.message);
}
}
if (didShare) {
Toast.show(i18n.t("thanksShare"));
await analytics.track("tap_invite", { shareLink, selectedContacts });
}
};
const renderBody = () => {
if (contacts.loading) {
return (
<View style={styles.bodyContainer}>
<ActivityIndicator size="large" color={theme.primary} />
<CommonMargin />
<Text style={styles.loadingOrErrText}>{i18n.t("loading")}</Text>
</View>
);
}
if (contacts.error != null) {
const errMsg =
String(contacts.error.message).indexOf("permission") >= 0
? i18n.t("noContactPermission")
: String(contacts.error.message);
return (
<View style={styles.bodyContainer}>
<MaterialIcons name="error" size={48} color={theme.primary} />
<CommonMargin />
<Text style={styles.loadingOrErrText}>{errMsg}</Text>
</View>
);
}
return (
<View style={styles.flex1}>
<SearchBar
styles={{
wrapper: {
backgroundColor: theme.white,
},
}}
style={styles.searchInput}
placeholder={i18n.t("inputKeyword")}
value={keyword}
onCancel={() => {
setKeyword("");
}}
onChange={(val) => {
setKeyword(val);
}}
/>
<SectionList
showsVerticalScrollIndicator={false}
bounces={false}
sections={filteredSection}
renderSectionHeader={({ section }) => (
<View style={styles.sectionHeaderContainer}>
<Text style={styles.sectionHeaderText}>
{section.key!.toUpperCase()}
</Text>
</View>
)}
renderItem={({ item }: { item: RowItem }) => {
const selectedIndex = selectedContacts.findIndex(
(i) => i.id === item.id
);
const onPress = () => {
const newContacts = [...selectedContacts];
if (selectedIndex >= 0) {
newContacts.splice(selectedIndex, 1);
} else {
newContacts.push(item);
}
setSelectedContacts(newContacts);
};
return (
<ContactRow
name={item.name}
emailOrNumber={(item.email || item.phoneNumber)!}
selected={selectedIndex >= 0}
onPress={onPress}
/>
);
}}
extraData={selectedContacts}
contentContainerStyle={styles.contentContainerStyle}
/>
<SafeAreaView style={styles.bottomButtonContainer}>
<Button
style={styles.button}
onPress={onInvitePress}
disabled={selectedContacts.length === 0}
>
<Text style={styles.buttonText}>{`${i18n.t("invite")} (${
selectedContacts.length
})`}</Text>
</Button>
</SafeAreaView>
</View>
);
};
return (
<View style={styles.container}>
<NavigationBar
title={i18n.t("inviteFriends")}
showBack
navigation={props.navigation}
/>
{renderBody()}
</View>
);
}