@umituz/react-native-settings 5.3.76 → 5.3.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.3.76",
3
+ "version": "5.3.77",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification - expo-store-review and expo-device now lazy loaded",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -1,9 +1,9 @@
1
- import React, { useState, useCallback } from "react";
1
+ import React, { useState, useCallback, useMemo } from "react";
2
2
  import {
3
3
  View,
4
4
  StyleSheet,
5
5
  TouchableOpacity,
6
- ScrollView,
6
+ FlatList,
7
7
  ActivityIndicator,
8
8
  } from "react-native";
9
9
  import {
@@ -83,7 +83,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
83
83
  }
84
84
  };
85
85
 
86
- const filteredRequests = (() => {
86
+ const filteredRequests = useMemo(() => {
87
87
  switch (activeTab) {
88
88
  case 'my':
89
89
  return requests.filter(r => r.createdBy === userId);
@@ -92,9 +92,9 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
92
92
  default:
93
93
  return requests;
94
94
  }
95
- })();
95
+ }, [requests, activeTab, userId]);
96
96
 
97
- const renderRequestCard = (item: FeatureRequestItem) => {
97
+ const renderRequestCard = useCallback(({ item }: { item: FeatureRequestItem }) => {
98
98
  const voted = userVotes[item.id] || null;
99
99
 
100
100
  return (
@@ -144,9 +144,13 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
144
144
  </View>
145
145
  </View>
146
146
  );
147
- };
147
+ }, [userVotes, vote, tokens.colors, getStatusColor, statusLabels, t]);
148
+
149
+ const keyExtractor = useCallback((item: FeatureRequestItem) => item.id, []);
148
150
 
149
- const header = (
151
+ const tabs = useMemo(() => (['all', 'my', 'roadmap'] as const), []);
152
+
153
+ const header = useMemo(() => (
150
154
  <View style={styles.header}>
151
155
  <AtomicText style={styles.headerTitle}>{screenTitle}</AtomicText>
152
156
  <TouchableOpacity
@@ -160,7 +164,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
160
164
  />
161
165
  </TouchableOpacity>
162
166
  </View>
163
- );
167
+ ), [screenTitle, tokens.colors.primary, tokens.colors.onPrimary]);
164
168
 
165
169
  if (isLoading) {
166
170
  return (
@@ -175,7 +179,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
175
179
  return (
176
180
  <ScreenLayout header={header} edges={['top', 'bottom']}>
177
181
  <View style={styles.tabsContainer}>
178
- {(['all', 'my', 'roadmap'] as const).map((tab) => (
182
+ {tabs.map((tab) => (
179
183
  <TouchableOpacity
180
184
  key={tab}
181
185
  onPress={() => setActiveTab(tab)}
@@ -188,28 +192,34 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
188
192
  ))}
189
193
  </View>
190
194
 
191
- <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
192
- <View style={[styles.banner, { backgroundColor: tokens.colors.primary + '10', borderColor: tokens.colors.primary + '20' }]}>
193
- <View style={styles.bannerIconContainer}>
194
- <AtomicIcon
195
- svgPath={ICON_PATHS['users']}
196
- customSize={24}
197
- customColor={tokens.colors.primary}
198
- />
199
- <View style={styles.pulseDot} />
200
- </View>
201
- <View>
202
- <AtomicText style={styles.bannerTitle}>{bannerTitle}</AtomicText>
203
- <AtomicText style={[styles.bannerSub, { color: tokens.colors.textSecondary }]}>
204
- {bannerSub}
205
- </AtomicText>
206
- </View>
207
- </View>
208
-
209
- <AtomicText style={styles.sectionTitle}>{trendingTitle}</AtomicText>
195
+ <FlatList
196
+ data={filteredRequests}
197
+ renderItem={renderRequestCard}
198
+ keyExtractor={(item) => item.id}
199
+ ListHeaderComponent={
200
+ <View style={styles.container}>
201
+ <View style={[styles.banner, { backgroundColor: tokens.colors.primary + '10', borderColor: tokens.colors.primary + '20' }]}>
202
+ <View style={styles.bannerIconContainer}>
203
+ <AtomicIcon
204
+ svgPath={ICON_PATHS['users']}
205
+ customSize={24}
206
+ customColor={tokens.colors.primary}
207
+ />
208
+ <View style={styles.pulseDot} />
209
+ </View>
210
+ <View>
211
+ <AtomicText style={styles.bannerTitle}>{bannerTitle}</AtomicText>
212
+ <AtomicText style={[styles.bannerSub, { color: tokens.colors.textSecondary }]}>
213
+ {bannerSub}
214
+ </AtomicText>
215
+ </View>
216
+ </View>
210
217
 
211
- {filteredRequests.length === 0 ? (
212
- <View style={styles.emptyState}>
218
+ <AtomicText style={styles.sectionTitle}>{trendingTitle}</AtomicText>
219
+ </View>
220
+ }
221
+ ListEmptyComponent={
222
+ <View style={[styles.emptyState, { paddingHorizontal: tokens.spacing.md }]}>
213
223
  <AtomicIcon
214
224
  svgPath={ICON_PATHS['chatbubble-outline']}
215
225
  customSize={32}
@@ -219,12 +229,13 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
219
229
  {t.empty || "No requests yet. Be the first!"}
220
230
  </AtomicText>
221
231
  </View>
222
- ) : (
223
- <View style={styles.listContent}>
224
- {filteredRequests.map(renderRequestCard)}
225
- </View>
226
- )}
227
- </ScrollView>
232
+ }
233
+ contentContainerStyle={styles.listContent}
234
+ showsVerticalScrollIndicator={false}
235
+ removeClippedSubviews={true}
236
+ maxToRenderPerBatch={10}
237
+ windowSize={5}
238
+ />
228
239
 
229
240
  {isModalVisible && (
230
241
  <FeedbackModal
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import React, { useMemo, useCallback } from 'react';
7
- import { View, StyleSheet, TouchableOpacity } from 'react-native';
7
+ import { View, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
8
8
  import { AtomicText, AtomicIcon, AtomicSpinner } from '@umituz/react-native-design-system/atoms';
9
9
  import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
10
10
  import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
@@ -54,10 +54,45 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
54
54
 
55
55
  const fab = canAddMore ? (
56
56
  <TouchableOpacity style={styles.fab} onPress={onAddPress} activeOpacity={0.8}>
57
- <AtomicIcon name="add" size="md" color="onPrimary" />
57
+ <AtomicIcon
58
+ svgPath="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
59
+ customSize={20}
60
+ customColor={tokens.colors.onPrimary}
61
+ />
58
62
  <AtomicText type="bodyMedium" style={styles.fabText}>{translations.addButtonLabel}</AtomicText>
59
63
  </TouchableOpacity>
60
- ) : undefined;
64
+ ) : null;
65
+
66
+ const renderEmptyState = useCallback(() => (
67
+ <View style={styles.emptyContainer}>
68
+ <View style={styles.emptyIconContainer}>
69
+ <AtomicIcon
70
+ svgPath="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-14c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 18c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
71
+ customSize={48}
72
+ customColor={tokens.colors.secondary}
73
+ />
74
+ </View>
75
+ <AtomicText type="bodyLarge" style={styles.emptyTitle}>{translations.emptyTitle}</AtomicText>
76
+ <AtomicText type="bodySmall" style={styles.emptyDescription}>{translations.emptyDescription}</AtomicText>
77
+ </View>
78
+ ), [translations.emptyTitle, translations.emptyDescription, tokens.colors]);
79
+
80
+ const renderItem = useCallback(({ item }: { item: Reminder }) => (
81
+ <ReminderItem
82
+ reminder={item}
83
+ translations={{
84
+ frequencyOnce: translations.frequencyOnce,
85
+ frequencyDaily: translations.frequencyDaily,
86
+ frequencyWeekly: translations.frequencyWeekly,
87
+ frequencyMonthly: translations.frequencyMonthly,
88
+ }}
89
+ onToggle={handleToggle}
90
+ onEdit={onEditPress}
91
+ onDelete={handleDelete}
92
+ />
93
+ ), [translations, handleToggle, onEditPress, handleDelete]);
94
+
95
+ const keyExtractor = useCallback((item: Reminder) => item.id, []);
61
96
 
62
97
  if (isLoading) {
63
98
  return (
@@ -68,32 +103,20 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
68
103
  }
69
104
 
70
105
  return (
71
- <ScreenLayout footer={fab} contentContainerStyle={styles.listContent}>
72
- {reminders.length === 0 ? (
73
- <View style={styles.emptyContainer}>
74
- <View style={styles.emptyIconContainer}>
75
- <AtomicIcon name="notifications-off" size="xl" color="secondary" />
76
- </View>
77
- <AtomicText type="bodyLarge" style={styles.emptyTitle}>{translations.emptyTitle}</AtomicText>
78
- <AtomicText type="bodySmall" style={styles.emptyDescription}>{translations.emptyDescription}</AtomicText>
79
- </View>
80
- ) : (
81
- reminders.map((reminder) => (
82
- <ReminderItem
83
- key={reminder.id}
84
- reminder={reminder}
85
- translations={{
86
- frequencyOnce: translations.frequencyOnce,
87
- frequencyDaily: translations.frequencyDaily,
88
- frequencyWeekly: translations.frequencyWeekly,
89
- frequencyMonthly: translations.frequencyMonthly,
90
- }}
91
- onToggle={handleToggle}
92
- onEdit={onEditPress}
93
- onDelete={handleDelete}
94
- />
95
- ))
96
- )}
106
+ <ScreenLayout footer={fab} scrollable={false}>
107
+ <FlatList
108
+ data={reminders}
109
+ renderItem={renderItem}
110
+ keyExtractor={keyExtractor}
111
+ ListEmptyComponent={renderEmptyState}
112
+ contentContainerStyle={reminders.length === 0 ? styles.listEmptyContent : styles.listContent}
113
+ showsVerticalScrollIndicator={false}
114
+ removeClippedSubviews={true}
115
+ maxToRenderPerBatch={10}
116
+ updateCellsBatchingPeriod={50}
117
+ initialNumToRender={8}
118
+ windowSize={5}
119
+ />
97
120
  </ScreenLayout>
98
121
  );
99
122
  };
@@ -101,6 +124,7 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
101
124
  const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
102
125
  StyleSheet.create({
103
126
  listContent: { padding: 16, flexGrow: 1 },
127
+ listEmptyContent: { flexGrow: 1 },
104
128
  emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, paddingTop: 100 },
105
129
  emptyIconContainer: {
106
130
  width: 80,