@fadyshawky/react-native-magic 1.0.7 → 1.0.8
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/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/template/src/common/localization/translations/homeLocalization.ts +1 -0
- package/template/src/core/store/user/userSlice.ts +1 -0
- package/template/src/navigation/MainStack.tsx +7 -3
- package/template/src/screens/home/HomeScreen.tsx +107 -0
- package/template/src/screens/home/components/CarouselSection.tsx +80 -0
- package/template/src/screens/home/components/FeaturedCarousel.tsx +129 -0
- package/template/src/screens/home/hooks/useHomeData.ts +60 -0
- package/template/src/screens/home/types.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2024-01-24
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Improved FeaturedCarousel component:
|
|
9
|
+
- Centered carousel items on screen
|
|
10
|
+
- Adjusted item width to 85% of screen width
|
|
11
|
+
- Added proper item layout calculations
|
|
12
|
+
- Enhanced snapping behavior
|
|
13
|
+
|
|
5
14
|
## [1.0.7] - 2024-12-27
|
|
6
15
|
|
|
7
16
|
### Changed
|
package/package.json
CHANGED
|
@@ -12,13 +12,17 @@ import {Settings} from '../screens/Settings/Settings';
|
|
|
12
12
|
import {Header, HeaderBack} from './HeaderComponents';
|
|
13
13
|
import {TabBar} from './TabBar';
|
|
14
14
|
import {RootStackParamList} from './types';
|
|
15
|
+
import {HomeScreen} from '../screens/home/HomeScreen';
|
|
15
16
|
|
|
16
17
|
const MainScreens = [
|
|
17
18
|
{
|
|
18
|
-
id: '
|
|
19
|
-
component:
|
|
19
|
+
id: 'Home',
|
|
20
|
+
component: HomeScreen,
|
|
20
21
|
options: {
|
|
21
|
-
|
|
22
|
+
tabBarLabel: 'Profile',
|
|
23
|
+
header: () => {
|
|
24
|
+
return <Header title={localization.home.home} />;
|
|
25
|
+
},
|
|
22
26
|
},
|
|
23
27
|
},
|
|
24
28
|
{
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
ScrollView,
|
|
6
|
+
RefreshControl,
|
|
7
|
+
Dimensions,
|
|
8
|
+
Text,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {Colors} from '../../core/theme/colors';
|
|
11
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
12
|
+
import {CommonStyles} from '../../core/theme/commonStyles';
|
|
13
|
+
import {CarouselSection} from './components/CarouselSection';
|
|
14
|
+
import {FeaturedCarousel} from './components/FeaturedCarousel';
|
|
15
|
+
import {useHomeData} from './hooks/useHomeData';
|
|
16
|
+
|
|
17
|
+
const {width} = Dimensions.get('window');
|
|
18
|
+
|
|
19
|
+
export function HomeScreen(): JSX.Element {
|
|
20
|
+
const {
|
|
21
|
+
featuredItems,
|
|
22
|
+
trendingItems,
|
|
23
|
+
newItems,
|
|
24
|
+
recommendedItems,
|
|
25
|
+
isLoading,
|
|
26
|
+
refreshData,
|
|
27
|
+
} = useHomeData();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<ScrollView
|
|
31
|
+
style={styles.container}
|
|
32
|
+
refreshControl={
|
|
33
|
+
<RefreshControl refreshing={isLoading} onRefresh={refreshData} />
|
|
34
|
+
}>
|
|
35
|
+
<View style={styles.content}>
|
|
36
|
+
{/* Featured Section */}
|
|
37
|
+
<View style={styles.featuredSection}>
|
|
38
|
+
<Text style={styles.sectionTitle}>Featured</Text>
|
|
39
|
+
<FeaturedCarousel items={featuredItems} />
|
|
40
|
+
</View>
|
|
41
|
+
|
|
42
|
+
{/* Trending Section */}
|
|
43
|
+
<View style={styles.carouselSection}>
|
|
44
|
+
<Text style={styles.sectionTitle}>Trending Now</Text>
|
|
45
|
+
<CarouselSection
|
|
46
|
+
items={trendingItems}
|
|
47
|
+
imageStyle={styles.trendingImage}
|
|
48
|
+
/>
|
|
49
|
+
</View>
|
|
50
|
+
|
|
51
|
+
{/* New Arrivals Section */}
|
|
52
|
+
<View style={styles.carouselSection}>
|
|
53
|
+
<Text style={styles.sectionTitle}>New Arrivals</Text>
|
|
54
|
+
<CarouselSection
|
|
55
|
+
items={newItems}
|
|
56
|
+
imageStyle={styles.newArrivalsImage}
|
|
57
|
+
/>
|
|
58
|
+
</View>
|
|
59
|
+
|
|
60
|
+
{/* Recommended Section */}
|
|
61
|
+
<View style={styles.carouselSection}>
|
|
62
|
+
<Text style={styles.sectionTitle}>Recommended for You</Text>
|
|
63
|
+
<CarouselSection
|
|
64
|
+
items={recommendedItems}
|
|
65
|
+
imageStyle={styles.recommendedImage}
|
|
66
|
+
/>
|
|
67
|
+
</View>
|
|
68
|
+
</View>
|
|
69
|
+
</ScrollView>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const styles = StyleSheet.create({
|
|
74
|
+
container: {
|
|
75
|
+
flex: 1,
|
|
76
|
+
backgroundColor: Colors.background,
|
|
77
|
+
},
|
|
78
|
+
content: {
|
|
79
|
+
paddingVertical: CommonSizes.spacing.medium,
|
|
80
|
+
},
|
|
81
|
+
featuredSection: {
|
|
82
|
+
marginBottom: CommonSizes.spacing.large,
|
|
83
|
+
},
|
|
84
|
+
carouselSection: {
|
|
85
|
+
marginBottom: CommonSizes.spacing.large,
|
|
86
|
+
},
|
|
87
|
+
sectionTitle: {
|
|
88
|
+
...CommonStyles.h2_semiBold,
|
|
89
|
+
marginHorizontal: CommonSizes.spacing.large,
|
|
90
|
+
marginBottom: CommonSizes.spacing.medium,
|
|
91
|
+
},
|
|
92
|
+
trendingImage: {
|
|
93
|
+
width: width * 0.7,
|
|
94
|
+
height: 200,
|
|
95
|
+
borderRadius: CommonSizes.borderRadius.medium,
|
|
96
|
+
},
|
|
97
|
+
newArrivalsImage: {
|
|
98
|
+
width: width * 0.5,
|
|
99
|
+
height: 180,
|
|
100
|
+
borderRadius: CommonSizes.borderRadius.medium,
|
|
101
|
+
},
|
|
102
|
+
recommendedImage: {
|
|
103
|
+
width: width * 0.6,
|
|
104
|
+
height: 160,
|
|
105
|
+
borderRadius: CommonSizes.borderRadius.medium,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
ScrollView,
|
|
6
|
+
Image,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
ImageStyle,
|
|
9
|
+
Text,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import {CommonSizes} from '../../../core/theme/commonSizes';
|
|
12
|
+
import {CommonStyles} from '../../../core/theme/commonStyles';
|
|
13
|
+
import {CarouselItem} from '../types';
|
|
14
|
+
import {Colors} from '../../../core/theme/colors';
|
|
15
|
+
|
|
16
|
+
interface CarouselSectionProps {
|
|
17
|
+
items: CarouselItem[];
|
|
18
|
+
imageStyle?: ImageStyle;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CarouselSection({
|
|
22
|
+
items,
|
|
23
|
+
imageStyle,
|
|
24
|
+
}: CarouselSectionProps): JSX.Element {
|
|
25
|
+
return (
|
|
26
|
+
<ScrollView
|
|
27
|
+
horizontal
|
|
28
|
+
showsHorizontalScrollIndicator={false}
|
|
29
|
+
contentContainerStyle={styles.container}>
|
|
30
|
+
{items.map((item, index) => (
|
|
31
|
+
<TouchableOpacity
|
|
32
|
+
key={item.id}
|
|
33
|
+
style={styles.itemContainer}
|
|
34
|
+
onPress={() => item.onPress?.(item)}>
|
|
35
|
+
<Image
|
|
36
|
+
source={{uri: item.imageUrl}}
|
|
37
|
+
style={[styles.image, imageStyle]}
|
|
38
|
+
/>
|
|
39
|
+
<View style={styles.textContainer}>
|
|
40
|
+
<Text style={styles.title} numberOfLines={1}>
|
|
41
|
+
{item.title}
|
|
42
|
+
</Text>
|
|
43
|
+
{item.subtitle && (
|
|
44
|
+
<Text style={styles.subtitle} numberOfLines={1}>
|
|
45
|
+
{item.subtitle}
|
|
46
|
+
</Text>
|
|
47
|
+
)}
|
|
48
|
+
</View>
|
|
49
|
+
</TouchableOpacity>
|
|
50
|
+
))}
|
|
51
|
+
</ScrollView>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const styles = StyleSheet.create({
|
|
56
|
+
container: {
|
|
57
|
+
paddingHorizontal: CommonSizes.spacing.large,
|
|
58
|
+
},
|
|
59
|
+
itemContainer: {
|
|
60
|
+
marginRight: CommonSizes.spacing.medium,
|
|
61
|
+
},
|
|
62
|
+
image: {
|
|
63
|
+
width: 200,
|
|
64
|
+
height: 150,
|
|
65
|
+
borderRadius: CommonSizes.borderRadius.medium,
|
|
66
|
+
},
|
|
67
|
+
textContainer: {
|
|
68
|
+
marginTop: CommonSizes.spacing.small,
|
|
69
|
+
},
|
|
70
|
+
title: {
|
|
71
|
+
fontSize: 16,
|
|
72
|
+
fontWeight: '600',
|
|
73
|
+
color: Colors.blue100,
|
|
74
|
+
},
|
|
75
|
+
subtitle: {
|
|
76
|
+
fontSize: 14,
|
|
77
|
+
color: Colors.blue100,
|
|
78
|
+
marginTop: 2,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React, {useState, useRef} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Dimensions,
|
|
6
|
+
FlatList,
|
|
7
|
+
Image,
|
|
8
|
+
TouchableOpacity,
|
|
9
|
+
Text,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import {Colors} from '../../../core/theme/colors';
|
|
12
|
+
import {CommonSizes} from '../../../core/theme/commonSizes';
|
|
13
|
+
import {CommonStyles} from '../../../core/theme/commonStyles';
|
|
14
|
+
import {CarouselItem} from '../types';
|
|
15
|
+
|
|
16
|
+
const {width} = Dimensions.get('window');
|
|
17
|
+
const ITEM_WIDTH = width * 0.85;
|
|
18
|
+
|
|
19
|
+
interface FeaturedCarouselProps {
|
|
20
|
+
items: CarouselItem[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function FeaturedCarousel({items}: FeaturedCarouselProps): JSX.Element {
|
|
24
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
25
|
+
const flatListRef = useRef<FlatList>(null);
|
|
26
|
+
|
|
27
|
+
const renderItem = ({item}: {item: CarouselItem}) => (
|
|
28
|
+
<TouchableOpacity
|
|
29
|
+
style={styles.itemContainer}
|
|
30
|
+
onPress={() => item.onPress?.(item)}>
|
|
31
|
+
<Image source={{uri: item.imageUrl}} style={styles.image} />
|
|
32
|
+
<View style={styles.overlay}>
|
|
33
|
+
<Text style={styles.title}>{item.title}</Text>
|
|
34
|
+
{item.subtitle && <Text style={styles.subtitle}>{item.subtitle}</Text>}
|
|
35
|
+
</View>
|
|
36
|
+
</TouchableOpacity>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const renderDot = (index: number) => (
|
|
40
|
+
<View
|
|
41
|
+
key={index}
|
|
42
|
+
style={[styles.dot, index === activeIndex && styles.activeDot]}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const handleScroll = (event: any) => {
|
|
47
|
+
const slideSize = event.nativeEvent.layoutMeasurement.width;
|
|
48
|
+
const index = event.nativeEvent.contentOffset.x / slideSize;
|
|
49
|
+
const roundIndex = Math.round(index);
|
|
50
|
+
setActiveIndex(roundIndex);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<View>
|
|
55
|
+
<FlatList
|
|
56
|
+
ref={flatListRef}
|
|
57
|
+
data={items}
|
|
58
|
+
renderItem={renderItem}
|
|
59
|
+
horizontal
|
|
60
|
+
pagingEnabled
|
|
61
|
+
showsHorizontalScrollIndicator={false}
|
|
62
|
+
onScroll={handleScroll}
|
|
63
|
+
snapToAlignment="center"
|
|
64
|
+
decelerationRate="fast"
|
|
65
|
+
snapToInterval={ITEM_WIDTH}
|
|
66
|
+
contentContainerStyle={styles.listContainer}
|
|
67
|
+
getItemLayout={(_, index) => ({
|
|
68
|
+
length: ITEM_WIDTH,
|
|
69
|
+
offset: ITEM_WIDTH * index,
|
|
70
|
+
index,
|
|
71
|
+
})}
|
|
72
|
+
/>
|
|
73
|
+
<View style={styles.pagination}>
|
|
74
|
+
{items.map((_, index) => renderDot(index))}
|
|
75
|
+
</View>
|
|
76
|
+
</View>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const styles = StyleSheet.create({
|
|
81
|
+
listContainer: {
|
|
82
|
+
paddingHorizontal: (width - ITEM_WIDTH) / 2,
|
|
83
|
+
},
|
|
84
|
+
itemContainer: {
|
|
85
|
+
width: ITEM_WIDTH,
|
|
86
|
+
height: 250,
|
|
87
|
+
},
|
|
88
|
+
image: {
|
|
89
|
+
width: '100%',
|
|
90
|
+
height: '100%',
|
|
91
|
+
borderRadius: CommonSizes.borderRadius.large,
|
|
92
|
+
},
|
|
93
|
+
overlay: {
|
|
94
|
+
...StyleSheet.absoluteFillObject,
|
|
95
|
+
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
96
|
+
borderRadius: CommonSizes.borderRadius.large,
|
|
97
|
+
padding: CommonSizes.spacing.large,
|
|
98
|
+
justifyContent: 'flex-end',
|
|
99
|
+
},
|
|
100
|
+
title: {
|
|
101
|
+
fontSize: 20,
|
|
102
|
+
fontWeight: '600',
|
|
103
|
+
color: Colors.white,
|
|
104
|
+
},
|
|
105
|
+
subtitle: {
|
|
106
|
+
fontSize: 16,
|
|
107
|
+
color: Colors.white,
|
|
108
|
+
marginTop: CommonSizes.spacing.small,
|
|
109
|
+
},
|
|
110
|
+
pagination: {
|
|
111
|
+
flexDirection: 'row',
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
marginTop: CommonSizes.spacing.medium,
|
|
115
|
+
},
|
|
116
|
+
dot: {
|
|
117
|
+
width: 8,
|
|
118
|
+
height: 8,
|
|
119
|
+
borderRadius: 4,
|
|
120
|
+
backgroundColor: Colors.gray,
|
|
121
|
+
marginHorizontal: 4,
|
|
122
|
+
},
|
|
123
|
+
activeDot: {
|
|
124
|
+
backgroundColor: Colors.blue100,
|
|
125
|
+
width: 12,
|
|
126
|
+
height: 12,
|
|
127
|
+
borderRadius: 6,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {useState, useEffect} from 'react';
|
|
2
|
+
import {CarouselItem} from '../types';
|
|
3
|
+
|
|
4
|
+
export function useHomeData() {
|
|
5
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
6
|
+
const [featuredItems, setFeaturedItems] = useState<CarouselItem[]>([]);
|
|
7
|
+
const [trendingItems, setTrendingItems] = useState<CarouselItem[]>([]);
|
|
8
|
+
const [newItems, setNewItems] = useState<CarouselItem[]>([]);
|
|
9
|
+
const [recommendedItems, setRecommendedItems] = useState<CarouselItem[]>([]);
|
|
10
|
+
|
|
11
|
+
const fetchData = async () => {
|
|
12
|
+
setIsLoading(true);
|
|
13
|
+
try {
|
|
14
|
+
// Simulate API calls
|
|
15
|
+
// Replace with actual API calls in production
|
|
16
|
+
setFeaturedItems([
|
|
17
|
+
{
|
|
18
|
+
id: '1',
|
|
19
|
+
title: 'Featured Item 1',
|
|
20
|
+
subtitle: 'Discover amazing features',
|
|
21
|
+
imageUrl: 'https://picsum.photos/800/400',
|
|
22
|
+
},
|
|
23
|
+
// Add more items...
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
setTrendingItems([
|
|
27
|
+
{
|
|
28
|
+
id: '1',
|
|
29
|
+
title: 'Trending Item 1',
|
|
30
|
+
subtitle: 'Hot right now',
|
|
31
|
+
imageUrl: 'https://picsum.photos/700/400',
|
|
32
|
+
},
|
|
33
|
+
// Add more items...
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// Similar for newItems and recommendedItems...
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Error fetching home data:', error);
|
|
39
|
+
} finally {
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const refreshData = () => {
|
|
45
|
+
fetchData();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
fetchData();
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
isLoading,
|
|
54
|
+
featuredItems,
|
|
55
|
+
trendingItems,
|
|
56
|
+
newItems,
|
|
57
|
+
recommendedItems,
|
|
58
|
+
refreshData,
|
|
59
|
+
};
|
|
60
|
+
}
|