@umituz/react-native-gamification 1.4.4 → 1.4.5
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 +1 -1
- package/src/components/GamificationScreen/AchievementsList.tsx +84 -0
- package/src/components/GamificationScreen/Header.tsx +29 -0
- package/src/components/GamificationScreen/StatsGrid.tsx +51 -0
- package/src/components/GamificationScreen/index.tsx +111 -0
- package/src/components/GamificationScreen/styles.ts +43 -0
- package/src/components/GamificationScreen/types.ts +77 -0
- package/src/components/index.ts +1 -1
- package/src/components/GamificationScreen.tsx +0 -236
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-gamification",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"description": "Generic gamification system for React Native apps - achievements, points, levels, streaks with customizable UI components",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GamificationScreen AchievementsList Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { View, Text, type TextStyle } from "react-native";
|
|
7
|
+
import { AchievementItem } from "../AchievementItem";
|
|
8
|
+
import { styles } from "./styles";
|
|
9
|
+
import type { AchievementItemProps } from "../AchievementItem";
|
|
10
|
+
|
|
11
|
+
export interface AchievementsListProps {
|
|
12
|
+
achievementsTitle: string;
|
|
13
|
+
achievements: Array<
|
|
14
|
+
Omit<
|
|
15
|
+
AchievementItemProps,
|
|
16
|
+
| "accentColor"
|
|
17
|
+
| "backgroundColor"
|
|
18
|
+
| "textColor"
|
|
19
|
+
| "subtextColor"
|
|
20
|
+
| "lockedOpacity"
|
|
21
|
+
>
|
|
22
|
+
>;
|
|
23
|
+
emptyAchievementsText?: string;
|
|
24
|
+
accentColor: string;
|
|
25
|
+
cardBackgroundColor: string;
|
|
26
|
+
textColor: string;
|
|
27
|
+
subtextColor: string;
|
|
28
|
+
sectionTitleStyle?: TextStyle;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const AchievementsList: React.FC<AchievementsListProps> = ({
|
|
32
|
+
achievementsTitle,
|
|
33
|
+
achievements,
|
|
34
|
+
emptyAchievementsText,
|
|
35
|
+
accentColor,
|
|
36
|
+
cardBackgroundColor,
|
|
37
|
+
textColor,
|
|
38
|
+
subtextColor,
|
|
39
|
+
sectionTitleStyle,
|
|
40
|
+
}) => {
|
|
41
|
+
const unlockedAchievements = achievements.filter((a) => a.isUnlocked);
|
|
42
|
+
const lockedAchievements = achievements.filter((a) => !a.isUnlocked);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<View style={styles.section}>
|
|
46
|
+
<Text style={[styles.sectionTitle, { color: textColor }, sectionTitleStyle]}>
|
|
47
|
+
{achievementsTitle}
|
|
48
|
+
</Text>
|
|
49
|
+
|
|
50
|
+
{achievements.length === 0 && emptyAchievementsText ? (
|
|
51
|
+
<Text style={[styles.emptyText, { color: subtextColor }]}>
|
|
52
|
+
{emptyAchievementsText}
|
|
53
|
+
</Text>
|
|
54
|
+
) : (
|
|
55
|
+
<>
|
|
56
|
+
{/* Unlocked achievements first */}
|
|
57
|
+
{unlockedAchievements.map((achievement, index) => (
|
|
58
|
+
<AchievementItem
|
|
59
|
+
key={`unlocked-${index}`}
|
|
60
|
+
{...achievement}
|
|
61
|
+
accentColor={accentColor}
|
|
62
|
+
backgroundColor={cardBackgroundColor}
|
|
63
|
+
textColor={textColor}
|
|
64
|
+
subtextColor={subtextColor}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
|
|
68
|
+
{/* Locked achievements */}
|
|
69
|
+
{lockedAchievements.map((achievement, index) => (
|
|
70
|
+
<AchievementItem
|
|
71
|
+
key={`locked-${index}`}
|
|
72
|
+
{...achievement}
|
|
73
|
+
accentColor={accentColor}
|
|
74
|
+
backgroundColor={cardBackgroundColor}
|
|
75
|
+
textColor={textColor}
|
|
76
|
+
subtextColor={subtextColor}
|
|
77
|
+
lockedOpacity={0.6}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</>
|
|
81
|
+
)}
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GamificationScreen Header Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { View, Text, type ViewStyle, type TextStyle } from "react-native";
|
|
7
|
+
import { styles } from "./styles";
|
|
8
|
+
|
|
9
|
+
export interface HeaderProps {
|
|
10
|
+
title: string;
|
|
11
|
+
headerStyle?: ViewStyle;
|
|
12
|
+
titleStyle?: TextStyle;
|
|
13
|
+
textColor: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Header: React.FC<HeaderProps> = ({
|
|
17
|
+
title,
|
|
18
|
+
headerStyle,
|
|
19
|
+
titleStyle,
|
|
20
|
+
textColor,
|
|
21
|
+
}) => {
|
|
22
|
+
return (
|
|
23
|
+
<View style={[styles.header, headerStyle]}>
|
|
24
|
+
<Text style={[styles.title, { color: textColor }, titleStyle]}>
|
|
25
|
+
{title}
|
|
26
|
+
</Text>
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GamificationScreen StatsGrid Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { View, Text, type TextStyle } from "react-native";
|
|
7
|
+
import { StatsCard } from "../StatsCard";
|
|
8
|
+
import { styles } from "./styles";
|
|
9
|
+
import type { StatsCardProps } from "../StatsCard";
|
|
10
|
+
|
|
11
|
+
export interface StatsGridProps {
|
|
12
|
+
statsTitle: string;
|
|
13
|
+
stats: Array<Omit<StatsCardProps, "accentColor" | "backgroundColor" | "textColor" | "subtextColor">>;
|
|
14
|
+
accentColor: string;
|
|
15
|
+
cardBackgroundColor: string;
|
|
16
|
+
textColor: string;
|
|
17
|
+
subtextColor: string;
|
|
18
|
+
sectionTitleStyle?: TextStyle;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const StatsGrid: React.FC<StatsGridProps> = ({
|
|
22
|
+
statsTitle,
|
|
23
|
+
stats,
|
|
24
|
+
accentColor,
|
|
25
|
+
cardBackgroundColor,
|
|
26
|
+
textColor,
|
|
27
|
+
subtextColor,
|
|
28
|
+
sectionTitleStyle,
|
|
29
|
+
}) => {
|
|
30
|
+
if (stats.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.section}>
|
|
34
|
+
<Text style={[styles.sectionTitle, { color: textColor }, sectionTitleStyle]}>
|
|
35
|
+
{statsTitle}
|
|
36
|
+
</Text>
|
|
37
|
+
<View style={styles.statsGrid}>
|
|
38
|
+
{stats.map((stat, index) => (
|
|
39
|
+
<StatsCard
|
|
40
|
+
key={index}
|
|
41
|
+
{...stat}
|
|
42
|
+
accentColor={accentColor}
|
|
43
|
+
backgroundColor={cardBackgroundColor}
|
|
44
|
+
textColor={textColor}
|
|
45
|
+
subtextColor={subtextColor}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</View>
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GamificationScreen Component
|
|
3
|
+
* Full gamification screen - all text via props
|
|
4
|
+
* Generic for 100+ apps - NO hardcoded strings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { View, ScrollView, Text, StyleSheet } from "react-native";
|
|
9
|
+
import { LevelProgress } from "../LevelProgress";
|
|
10
|
+
import { StreakDisplay } from "../StreakDisplay";
|
|
11
|
+
import { Header } from "./Header";
|
|
12
|
+
import { StatsGrid } from "./StatsGrid";
|
|
13
|
+
import { AchievementsList } from "./AchievementsList";
|
|
14
|
+
import { styles } from "./styles";
|
|
15
|
+
import type { GamificationScreenProps } from "./types";
|
|
16
|
+
|
|
17
|
+
export type { GamificationScreenProps };
|
|
18
|
+
|
|
19
|
+
export const GamificationScreen: React.FC<GamificationScreenProps> = ({
|
|
20
|
+
title,
|
|
21
|
+
statsTitle,
|
|
22
|
+
achievementsTitle,
|
|
23
|
+
streakTitle,
|
|
24
|
+
levelProps,
|
|
25
|
+
stats,
|
|
26
|
+
achievements,
|
|
27
|
+
streakProps,
|
|
28
|
+
emptyAchievementsText,
|
|
29
|
+
containerStyle,
|
|
30
|
+
headerStyle,
|
|
31
|
+
titleStyle,
|
|
32
|
+
sectionTitleStyle,
|
|
33
|
+
accentColor = "#FFD700",
|
|
34
|
+
backgroundColor = "#0A0A0A",
|
|
35
|
+
cardBackgroundColor = "#1A1A1A",
|
|
36
|
+
textColor = "#FFFFFF",
|
|
37
|
+
subtextColor = "#888888",
|
|
38
|
+
headerComponent,
|
|
39
|
+
}) => {
|
|
40
|
+
return (
|
|
41
|
+
<View style={[styles.container, { backgroundColor }, containerStyle]}>
|
|
42
|
+
{headerComponent}
|
|
43
|
+
|
|
44
|
+
<ScrollView
|
|
45
|
+
style={styles.scrollView}
|
|
46
|
+
contentContainerStyle={styles.scrollContent}
|
|
47
|
+
showsVerticalScrollIndicator={false}
|
|
48
|
+
>
|
|
49
|
+
{/* Header */}
|
|
50
|
+
<Header
|
|
51
|
+
title={title}
|
|
52
|
+
headerStyle={headerStyle}
|
|
53
|
+
titleStyle={titleStyle}
|
|
54
|
+
textColor={textColor}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
{/* Level Progress */}
|
|
58
|
+
<View style={styles.section}>
|
|
59
|
+
<LevelProgress
|
|
60
|
+
{...levelProps}
|
|
61
|
+
primaryColor={accentColor}
|
|
62
|
+
backgroundColor={cardBackgroundColor}
|
|
63
|
+
textColor={textColor}
|
|
64
|
+
subtextColor={subtextColor}
|
|
65
|
+
/>
|
|
66
|
+
</View>
|
|
67
|
+
|
|
68
|
+
{/* Streak (if provided) */}
|
|
69
|
+
{streakProps && (
|
|
70
|
+
<View style={styles.section}>
|
|
71
|
+
<Text
|
|
72
|
+
style={[styles.sectionTitle, { color: textColor }, sectionTitleStyle]}
|
|
73
|
+
>
|
|
74
|
+
{streakTitle}
|
|
75
|
+
</Text>
|
|
76
|
+
<StreakDisplay
|
|
77
|
+
{...streakProps}
|
|
78
|
+
primaryColor={accentColor}
|
|
79
|
+
backgroundColor={cardBackgroundColor}
|
|
80
|
+
textColor={textColor}
|
|
81
|
+
subtextColor={subtextColor}
|
|
82
|
+
/>
|
|
83
|
+
</View>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{/* Stats Grid */}
|
|
87
|
+
<StatsGrid
|
|
88
|
+
statsTitle={statsTitle}
|
|
89
|
+
stats={stats}
|
|
90
|
+
accentColor={accentColor}
|
|
91
|
+
cardBackgroundColor={cardBackgroundColor}
|
|
92
|
+
textColor={textColor}
|
|
93
|
+
subtextColor={subtextColor}
|
|
94
|
+
sectionTitleStyle={sectionTitleStyle}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* Achievements */}
|
|
98
|
+
<AchievementsList
|
|
99
|
+
achievementsTitle={achievementsTitle}
|
|
100
|
+
achievements={achievements}
|
|
101
|
+
emptyAchievementsText={emptyAchievementsText}
|
|
102
|
+
accentColor={accentColor}
|
|
103
|
+
cardBackgroundColor={cardBackgroundColor}
|
|
104
|
+
textColor={textColor}
|
|
105
|
+
subtextColor={subtextColor}
|
|
106
|
+
sectionTitleStyle={sectionTitleStyle}
|
|
107
|
+
/>
|
|
108
|
+
</ScrollView>
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GamificationScreen Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleSheet } from "react-native";
|
|
6
|
+
|
|
7
|
+
export const styles = StyleSheet.create({
|
|
8
|
+
container: {
|
|
9
|
+
flex: 1,
|
|
10
|
+
},
|
|
11
|
+
scrollView: {
|
|
12
|
+
flex: 1,
|
|
13
|
+
},
|
|
14
|
+
scrollContent: {
|
|
15
|
+
padding: 16,
|
|
16
|
+
paddingBottom: 32,
|
|
17
|
+
},
|
|
18
|
+
header: {
|
|
19
|
+
marginBottom: 20,
|
|
20
|
+
},
|
|
21
|
+
title: {
|
|
22
|
+
fontSize: 28,
|
|
23
|
+
fontWeight: "bold",
|
|
24
|
+
},
|
|
25
|
+
section: {
|
|
26
|
+
marginBottom: 24,
|
|
27
|
+
},
|
|
28
|
+
sectionTitle: {
|
|
29
|
+
fontSize: 18,
|
|
30
|
+
fontWeight: "600",
|
|
31
|
+
marginBottom: 12,
|
|
32
|
+
},
|
|
33
|
+
statsGrid: {
|
|
34
|
+
flexDirection: "row",
|
|
35
|
+
flexWrap: "wrap",
|
|
36
|
+
gap: 12,
|
|
37
|
+
},
|
|
38
|
+
emptyText: {
|
|
39
|
+
fontSize: 14,
|
|
40
|
+
textAlign: "center",
|
|
41
|
+
paddingVertical: 20,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GamificationScreen Types
|
|
3
|
+
* Generic types for 100+ apps - NO app-specific code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { LevelProgressProps } from "../LevelProgress";
|
|
7
|
+
import type { StatsCardProps } from "../StatsCard";
|
|
8
|
+
import type { AchievementItemProps } from "../AchievementItem";
|
|
9
|
+
import type { StreakDisplayProps } from "../StreakDisplay";
|
|
10
|
+
import type { ViewStyle, TextStyle } from "react-native";
|
|
11
|
+
|
|
12
|
+
export interface GamificationScreenProps {
|
|
13
|
+
// Section titles (all via props)
|
|
14
|
+
title: string;
|
|
15
|
+
statsTitle: string;
|
|
16
|
+
achievementsTitle: string;
|
|
17
|
+
streakTitle: string;
|
|
18
|
+
|
|
19
|
+
// Level data
|
|
20
|
+
levelProps: Omit<
|
|
21
|
+
LevelProgressProps,
|
|
22
|
+
"primaryColor" | "backgroundColor" | "textColor" | "subtextColor"
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
// Stats data
|
|
26
|
+
stats: Array<
|
|
27
|
+
Omit<
|
|
28
|
+
StatsCardProps,
|
|
29
|
+
"accentColor" | "backgroundColor" | "textColor" | "subtextColor"
|
|
30
|
+
>
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
// Achievements data
|
|
34
|
+
achievements: Array<
|
|
35
|
+
Omit<
|
|
36
|
+
AchievementItemProps,
|
|
37
|
+
| "accentColor"
|
|
38
|
+
| "backgroundColor"
|
|
39
|
+
| "textColor"
|
|
40
|
+
| "subtextColor"
|
|
41
|
+
| "lockedOpacity"
|
|
42
|
+
>
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
// Streak data (optional)
|
|
46
|
+
streakProps?: Omit<
|
|
47
|
+
StreakDisplayProps,
|
|
48
|
+
"primaryColor" | "backgroundColor" | "textColor" | "subtextColor"
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
// Empty state
|
|
52
|
+
emptyAchievementsText?: string;
|
|
53
|
+
|
|
54
|
+
// Customization
|
|
55
|
+
containerStyle?: ViewStyle;
|
|
56
|
+
headerStyle?: ViewStyle;
|
|
57
|
+
titleStyle?: TextStyle;
|
|
58
|
+
sectionTitleStyle?: TextStyle;
|
|
59
|
+
|
|
60
|
+
// Colors (applied to all child components)
|
|
61
|
+
accentColor?: string;
|
|
62
|
+
backgroundColor?: string;
|
|
63
|
+
cardBackgroundColor?: string;
|
|
64
|
+
textColor?: string;
|
|
65
|
+
subtextColor?: string;
|
|
66
|
+
|
|
67
|
+
// Header component (optional - for back button etc)
|
|
68
|
+
headerComponent?: React.ReactNode;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface GamificationScreenStyleProps {
|
|
72
|
+
accentColor: string;
|
|
73
|
+
backgroundColor: string;
|
|
74
|
+
cardBackgroundColor: string;
|
|
75
|
+
textColor: string;
|
|
76
|
+
subtextColor: string;
|
|
77
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -10,4 +10,4 @@ export { AchievementToast, type AchievementToastProps } from "./AchievementToast
|
|
|
10
10
|
export { StreakDisplay, type StreakDisplayProps } from "./StreakDisplay";
|
|
11
11
|
export { StatsCard, type StatsCardProps } from "./StatsCard";
|
|
12
12
|
export { AchievementItem, type AchievementItemProps } from "./AchievementItem";
|
|
13
|
-
export { GamificationScreen, type GamificationScreenProps } from "./GamificationScreen";
|
|
13
|
+
export { GamificationScreen, type GamificationScreenProps } from "./GamificationScreen/index";
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GamificationScreen Component
|
|
3
|
-
* Full gamification screen - all text via props
|
|
4
|
-
* Generic for 100+ apps - NO hardcoded strings
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React from "react";
|
|
8
|
-
import {
|
|
9
|
-
View,
|
|
10
|
-
Text,
|
|
11
|
-
ScrollView,
|
|
12
|
-
StyleSheet,
|
|
13
|
-
type ViewStyle,
|
|
14
|
-
type TextStyle,
|
|
15
|
-
} from "react-native";
|
|
16
|
-
import { LevelProgress, type LevelProgressProps } from "./LevelProgress";
|
|
17
|
-
import { StatsCard, type StatsCardProps } from "./StatsCard";
|
|
18
|
-
import { AchievementItem, type AchievementItemProps } from "./AchievementItem";
|
|
19
|
-
import { StreakDisplay, type StreakDisplayProps } from "./StreakDisplay";
|
|
20
|
-
|
|
21
|
-
export interface GamificationScreenProps {
|
|
22
|
-
// Section titles (all via props)
|
|
23
|
-
title: string;
|
|
24
|
-
statsTitle: string;
|
|
25
|
-
achievementsTitle: string;
|
|
26
|
-
streakTitle: string;
|
|
27
|
-
|
|
28
|
-
// Level data
|
|
29
|
-
levelProps: Omit<LevelProgressProps, "primaryColor" | "backgroundColor" | "textColor" | "subtextColor">;
|
|
30
|
-
|
|
31
|
-
// Stats data
|
|
32
|
-
stats: Array<Omit<StatsCardProps, "accentColor" | "backgroundColor" | "textColor" | "subtextColor">>;
|
|
33
|
-
|
|
34
|
-
// Achievements data
|
|
35
|
-
achievements: Array<
|
|
36
|
-
Omit<AchievementItemProps, "accentColor" | "backgroundColor" | "textColor" | "subtextColor" | "lockedOpacity">
|
|
37
|
-
>;
|
|
38
|
-
|
|
39
|
-
// Streak data (optional)
|
|
40
|
-
streakProps?: Omit<StreakDisplayProps, "primaryColor" | "backgroundColor" | "textColor" | "subtextColor">;
|
|
41
|
-
|
|
42
|
-
// Empty state
|
|
43
|
-
emptyAchievementsText?: string;
|
|
44
|
-
|
|
45
|
-
// Customization
|
|
46
|
-
containerStyle?: ViewStyle;
|
|
47
|
-
headerStyle?: ViewStyle;
|
|
48
|
-
titleStyle?: TextStyle;
|
|
49
|
-
sectionTitleStyle?: TextStyle;
|
|
50
|
-
|
|
51
|
-
// Colors (applied to all child components)
|
|
52
|
-
accentColor?: string;
|
|
53
|
-
backgroundColor?: string;
|
|
54
|
-
cardBackgroundColor?: string;
|
|
55
|
-
textColor?: string;
|
|
56
|
-
subtextColor?: string;
|
|
57
|
-
|
|
58
|
-
// Header component (optional - for back button etc)
|
|
59
|
-
headerComponent?: React.ReactNode;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const GamificationScreen: React.FC<GamificationScreenProps> = ({
|
|
63
|
-
title,
|
|
64
|
-
statsTitle,
|
|
65
|
-
achievementsTitle,
|
|
66
|
-
streakTitle,
|
|
67
|
-
levelProps,
|
|
68
|
-
stats,
|
|
69
|
-
achievements,
|
|
70
|
-
streakProps,
|
|
71
|
-
emptyAchievementsText,
|
|
72
|
-
containerStyle,
|
|
73
|
-
headerStyle,
|
|
74
|
-
titleStyle,
|
|
75
|
-
sectionTitleStyle,
|
|
76
|
-
accentColor = "#FFD700",
|
|
77
|
-
backgroundColor = "#0A0A0A",
|
|
78
|
-
cardBackgroundColor = "#1A1A1A",
|
|
79
|
-
textColor = "#FFFFFF",
|
|
80
|
-
subtextColor = "#888888",
|
|
81
|
-
headerComponent,
|
|
82
|
-
}) => {
|
|
83
|
-
const unlockedAchievements = achievements.filter((a) => a.isUnlocked);
|
|
84
|
-
const lockedAchievements = achievements.filter((a) => !a.isUnlocked);
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<View style={[styles.container, { backgroundColor }, containerStyle]}>
|
|
88
|
-
{headerComponent}
|
|
89
|
-
|
|
90
|
-
<ScrollView
|
|
91
|
-
style={styles.scrollView}
|
|
92
|
-
contentContainerStyle={styles.scrollContent}
|
|
93
|
-
showsVerticalScrollIndicator={false}
|
|
94
|
-
>
|
|
95
|
-
{/* Header */}
|
|
96
|
-
<View style={[styles.header, headerStyle]}>
|
|
97
|
-
<Text style={[styles.title, { color: textColor }, titleStyle]}>
|
|
98
|
-
{title}
|
|
99
|
-
</Text>
|
|
100
|
-
</View>
|
|
101
|
-
|
|
102
|
-
{/* Level Progress */}
|
|
103
|
-
<View style={styles.section}>
|
|
104
|
-
<LevelProgress
|
|
105
|
-
{...levelProps}
|
|
106
|
-
primaryColor={accentColor}
|
|
107
|
-
backgroundColor={cardBackgroundColor}
|
|
108
|
-
textColor={textColor}
|
|
109
|
-
subtextColor={subtextColor}
|
|
110
|
-
/>
|
|
111
|
-
</View>
|
|
112
|
-
|
|
113
|
-
{/* Streak (if provided) */}
|
|
114
|
-
{streakProps && (
|
|
115
|
-
<View style={styles.section}>
|
|
116
|
-
<Text
|
|
117
|
-
style={[styles.sectionTitle, { color: textColor }, sectionTitleStyle]}
|
|
118
|
-
>
|
|
119
|
-
{streakTitle}
|
|
120
|
-
</Text>
|
|
121
|
-
<StreakDisplay
|
|
122
|
-
{...streakProps}
|
|
123
|
-
primaryColor={accentColor}
|
|
124
|
-
backgroundColor={cardBackgroundColor}
|
|
125
|
-
textColor={textColor}
|
|
126
|
-
subtextColor={subtextColor}
|
|
127
|
-
/>
|
|
128
|
-
</View>
|
|
129
|
-
)}
|
|
130
|
-
|
|
131
|
-
{/* Stats Grid */}
|
|
132
|
-
{stats.length > 0 && (
|
|
133
|
-
<View style={styles.section}>
|
|
134
|
-
<Text
|
|
135
|
-
style={[styles.sectionTitle, { color: textColor }, sectionTitleStyle]}
|
|
136
|
-
>
|
|
137
|
-
{statsTitle}
|
|
138
|
-
</Text>
|
|
139
|
-
<View style={styles.statsGrid}>
|
|
140
|
-
{stats.map((stat, index) => (
|
|
141
|
-
<StatsCard
|
|
142
|
-
key={index}
|
|
143
|
-
{...stat}
|
|
144
|
-
accentColor={accentColor}
|
|
145
|
-
backgroundColor={cardBackgroundColor}
|
|
146
|
-
textColor={textColor}
|
|
147
|
-
subtextColor={subtextColor}
|
|
148
|
-
/>
|
|
149
|
-
))}
|
|
150
|
-
</View>
|
|
151
|
-
</View>
|
|
152
|
-
)}
|
|
153
|
-
|
|
154
|
-
{/* Achievements */}
|
|
155
|
-
<View style={styles.section}>
|
|
156
|
-
<Text
|
|
157
|
-
style={[styles.sectionTitle, { color: textColor }, sectionTitleStyle]}
|
|
158
|
-
>
|
|
159
|
-
{achievementsTitle}
|
|
160
|
-
</Text>
|
|
161
|
-
|
|
162
|
-
{achievements.length === 0 && emptyAchievementsText ? (
|
|
163
|
-
<Text style={[styles.emptyText, { color: subtextColor }]}>
|
|
164
|
-
{emptyAchievementsText}
|
|
165
|
-
</Text>
|
|
166
|
-
) : (
|
|
167
|
-
<>
|
|
168
|
-
{/* Unlocked achievements first */}
|
|
169
|
-
{unlockedAchievements.map((achievement, index) => (
|
|
170
|
-
<AchievementItem
|
|
171
|
-
key={`unlocked-${index}`}
|
|
172
|
-
{...achievement}
|
|
173
|
-
accentColor={accentColor}
|
|
174
|
-
backgroundColor={cardBackgroundColor}
|
|
175
|
-
textColor={textColor}
|
|
176
|
-
subtextColor={subtextColor}
|
|
177
|
-
/>
|
|
178
|
-
))}
|
|
179
|
-
|
|
180
|
-
{/* Locked achievements */}
|
|
181
|
-
{lockedAchievements.map((achievement, index) => (
|
|
182
|
-
<AchievementItem
|
|
183
|
-
key={`locked-${index}`}
|
|
184
|
-
{...achievement}
|
|
185
|
-
accentColor={accentColor}
|
|
186
|
-
backgroundColor={cardBackgroundColor}
|
|
187
|
-
textColor={textColor}
|
|
188
|
-
subtextColor={subtextColor}
|
|
189
|
-
lockedOpacity={0.6}
|
|
190
|
-
/>
|
|
191
|
-
))}
|
|
192
|
-
</>
|
|
193
|
-
)}
|
|
194
|
-
</View>
|
|
195
|
-
</ScrollView>
|
|
196
|
-
</View>
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const styles = StyleSheet.create({
|
|
201
|
-
container: {
|
|
202
|
-
flex: 1,
|
|
203
|
-
},
|
|
204
|
-
scrollView: {
|
|
205
|
-
flex: 1,
|
|
206
|
-
},
|
|
207
|
-
scrollContent: {
|
|
208
|
-
padding: 16,
|
|
209
|
-
paddingBottom: 32,
|
|
210
|
-
},
|
|
211
|
-
header: {
|
|
212
|
-
marginBottom: 20,
|
|
213
|
-
},
|
|
214
|
-
title: {
|
|
215
|
-
fontSize: 28,
|
|
216
|
-
fontWeight: "bold",
|
|
217
|
-
},
|
|
218
|
-
section: {
|
|
219
|
-
marginBottom: 24,
|
|
220
|
-
},
|
|
221
|
-
sectionTitle: {
|
|
222
|
-
fontSize: 18,
|
|
223
|
-
fontWeight: "600",
|
|
224
|
-
marginBottom: 12,
|
|
225
|
-
},
|
|
226
|
-
statsGrid: {
|
|
227
|
-
flexDirection: "row",
|
|
228
|
-
flexWrap: "wrap",
|
|
229
|
-
gap: 12,
|
|
230
|
-
},
|
|
231
|
-
emptyText: {
|
|
232
|
-
fontSize: 14,
|
|
233
|
-
textAlign: "center",
|
|
234
|
-
paddingVertical: 20,
|
|
235
|
-
},
|
|
236
|
-
});
|