@umituz/react-native-settings 4.20.61 → 4.21.1
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 +8 -60
- package/src/domains/gamification/README.md +343 -0
- package/src/domains/gamification/components/AchievementCard.tsx +142 -0
- package/src/domains/gamification/components/AchievementItem.tsx +182 -0
- package/src/domains/gamification/components/AchievementToast.tsx +122 -0
- package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +84 -0
- package/src/domains/gamification/components/GamificationScreen/Header.tsx +29 -0
- package/src/domains/gamification/components/GamificationScreen/StatsGrid.tsx +51 -0
- package/src/domains/gamification/components/GamificationScreen/index.tsx +111 -0
- package/src/domains/gamification/components/GamificationScreen/styles.ts +43 -0
- package/src/domains/gamification/components/GamificationScreen/types.ts +77 -0
- package/src/domains/gamification/components/GamificationScreenWrapper.tsx +91 -0
- package/src/domains/gamification/components/GamificationSettingsItem.tsx +33 -0
- package/src/domains/gamification/components/LevelProgress.tsx +129 -0
- package/src/domains/gamification/components/PointsBadge.tsx +60 -0
- package/src/domains/gamification/components/StatsCard.tsx +89 -0
- package/src/domains/gamification/components/StreakDisplay.tsx +119 -0
- package/src/domains/gamification/components/index.ts +13 -0
- package/src/domains/gamification/examples/gamification.config.example.ts +70 -0
- package/src/domains/gamification/examples/localization.example.json +71 -0
- package/src/domains/gamification/hooks/useGamification.ts +91 -0
- package/src/domains/gamification/index.ts +65 -0
- package/src/domains/gamification/store/gamificationStore.ts +162 -0
- package/src/domains/gamification/types/index.ts +103 -0
- package/src/domains/gamification/types/settings.ts +28 -0
- package/src/domains/gamification/utils/calculations.ts +85 -0
- package/src/index.ts +18 -8
- package/src/presentation/navigation/SettingsStackNavigator.tsx +12 -0
- package/src/presentation/navigation/types.ts +2 -0
- package/src/presentation/navigation/utils/navigationScreenOptions.ts +7 -0
- package/src/presentation/screens/types/UserFeatureConfig.ts +2 -0
- package/src/presentation/utils/configCreators.ts +147 -0
- package/src/presentation/utils/index.ts +5 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -51
- package/.github/ISSUE_TEMPLATE/documentation.md +0 -52
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -63
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -84
- package/AI_AGENT_GUIDELINES.md +0 -367
- package/ARCHITECTURE.md +0 -246
- package/CHANGELOG.md +0 -67
- package/CODE_OF_CONDUCT.md +0 -75
- package/CONTRIBUTING.md +0 -107
- package/DOCUMENTATION_MIGRATION.md +0 -319
- package/DOCUMENTATION_TEMPLATE.md +0 -155
- package/SECURITY.md +0 -98
- package/SETTINGS_SCREEN_GUIDE.md +0 -185
- package/TESTING.md +0 -358
- package/src/__tests__/integration.test.tsx +0 -371
- package/src/__tests__/performance.test.tsx +0 -369
- package/src/__tests__/setup.test.tsx +0 -20
- package/src/__tests__/setup.ts +0 -154
- package/src/domains/about/__tests__/integration.test.tsx +0 -328
- package/src/domains/about/__tests__/types.d.ts +0 -5
- package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +0 -93
- package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +0 -153
- package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +0 -178
- package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +0 -293
- package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +0 -201
- package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +0 -71
- package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +0 -229
- package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +0 -240
- package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +0 -199
- package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +0 -366
- package/src/domains/about/utils/__tests__/index.test.ts +0 -408
- package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +0 -195
- package/src/domains/appearance/__tests__/hooks/index.test.tsx +0 -232
- package/src/domains/appearance/__tests__/integration/index.test.tsx +0 -207
- package/src/domains/appearance/__tests__/services/appearanceService.test.ts +0 -299
- package/src/domains/appearance/__tests__/setup.ts +0 -88
- package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +0 -175
- package/src/domains/cloud-sync/presentation/components/__tests__/CloudSyncSetting.test.tsx +0 -78
- package/src/domains/legal/__tests__/ContentValidationService.test.ts +0 -195
- package/src/domains/legal/__tests__/StyleCacheService.test.ts +0 -110
- package/src/domains/legal/__tests__/UrlHandlerService.test.ts +0 -71
- package/src/domains/legal/__tests__/setup.ts +0 -82
- package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +0 -186
- package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +0 -322
- package/src/presentation/screens/hooks/__tests__/useFeatureDetection.test.tsx +0 -261
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Screen Component
|
|
3
|
+
* Wrapper for gamification screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { GamificationScreen as BaseGamificationScreen } from "./GamificationScreen";
|
|
8
|
+
import { useGamification } from "../hooks/useGamification";
|
|
9
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
|
+
import type { GamificationSettingsConfig } from "../types/settings";
|
|
11
|
+
|
|
12
|
+
export interface GamificationScreenWrapperProps {
|
|
13
|
+
config: GamificationSettingsConfig;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Gamification Screen for Settings
|
|
18
|
+
* Displays achievements, levels, streaks, and stats
|
|
19
|
+
*/
|
|
20
|
+
export const GamificationScreenWrapper: React.FC<GamificationScreenWrapperProps> = ({
|
|
21
|
+
config,
|
|
22
|
+
}) => {
|
|
23
|
+
const { t } = useLocalization();
|
|
24
|
+
const gamification = useGamification(config.config);
|
|
25
|
+
|
|
26
|
+
const screenProps = useMemo(() => {
|
|
27
|
+
// Map achievements to screen format
|
|
28
|
+
const achievements = gamification.achievements.map((achievement) => ({
|
|
29
|
+
id: achievement.id,
|
|
30
|
+
title: t(`gamification.achievements.${achievement.id}.title`),
|
|
31
|
+
description: t(`gamification.achievements.${achievement.id}.description`),
|
|
32
|
+
icon: "trophy",
|
|
33
|
+
isUnlocked: achievement.isUnlocked,
|
|
34
|
+
progress: achievement.progress,
|
|
35
|
+
threshold: achievement.threshold,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Map stats
|
|
39
|
+
const stats = [
|
|
40
|
+
{
|
|
41
|
+
icon: "star",
|
|
42
|
+
value: gamification.points,
|
|
43
|
+
label: t("gamification.stats.totalPoints"),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
icon: "check-circle",
|
|
47
|
+
value: gamification.totalTasksCompleted,
|
|
48
|
+
label: t("gamification.stats.tasksCompleted"),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
icon: "award",
|
|
52
|
+
value: gamification.achievements.filter((a) => a.isUnlocked).length,
|
|
53
|
+
label: t("gamification.stats.achievementsUnlocked"),
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// Level props
|
|
58
|
+
const levelProps = {
|
|
59
|
+
level: gamification.level.currentLevel,
|
|
60
|
+
points: gamification.level.currentPoints,
|
|
61
|
+
levelTitle: t("gamification.level.title", {
|
|
62
|
+
level: gamification.level.currentLevel,
|
|
63
|
+
}),
|
|
64
|
+
pointsToNext: gamification.level.pointsToNext,
|
|
65
|
+
progress: gamification.level.progress,
|
|
66
|
+
showPoints: true,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Streak props
|
|
70
|
+
const streakProps = gamification.streak.current > 0
|
|
71
|
+
? {
|
|
72
|
+
current: gamification.streak.current,
|
|
73
|
+
longest: gamification.streak.longest,
|
|
74
|
+
currentLabel: t("gamification.streak.current"),
|
|
75
|
+
bestLabel: t("gamification.streak.best"),
|
|
76
|
+
daysLabel: t("gamification.streak.days"),
|
|
77
|
+
}
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...config.screenProps,
|
|
82
|
+
levelProps,
|
|
83
|
+
stats,
|
|
84
|
+
achievements,
|
|
85
|
+
streakProps,
|
|
86
|
+
emptyAchievementsText: t("gamification.achievements.empty"),
|
|
87
|
+
};
|
|
88
|
+
}, [gamification, config.screenProps, t]);
|
|
89
|
+
|
|
90
|
+
return <BaseGamificationScreen {...screenProps} />;
|
|
91
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Settings Item Component
|
|
3
|
+
* Menu item to navigate to gamification screen
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { SettingsItemCard } from "../../../presentation/components/SettingsItemCard";
|
|
8
|
+
import type { GamificationMenuConfig } from "../types/settings";
|
|
9
|
+
|
|
10
|
+
export interface GamificationSettingsItemProps {
|
|
11
|
+
config: GamificationMenuConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gamification menu item for settings screen
|
|
16
|
+
*/
|
|
17
|
+
export const GamificationSettingsItem: React.FC<GamificationSettingsItemProps> = ({
|
|
18
|
+
config,
|
|
19
|
+
}) => {
|
|
20
|
+
if (!config.enabled) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<SettingsItemCard
|
|
26
|
+
title={config.title}
|
|
27
|
+
description={config.subtitle}
|
|
28
|
+
icon={config.icon || "trophy"}
|
|
29
|
+
onPress={config.onPress}
|
|
30
|
+
showChevron
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LevelProgress Component
|
|
3
|
+
* Displays level and progress - all text via props
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
+
|
|
9
|
+
export interface LevelProgressProps {
|
|
10
|
+
level: number;
|
|
11
|
+
points: number;
|
|
12
|
+
levelTitle: string;
|
|
13
|
+
pointsToNext: number;
|
|
14
|
+
progress: number;
|
|
15
|
+
showPoints?: boolean;
|
|
16
|
+
// Customization
|
|
17
|
+
containerStyle?: ViewStyle;
|
|
18
|
+
titleStyle?: TextStyle;
|
|
19
|
+
subtitleStyle?: TextStyle;
|
|
20
|
+
progressBarStyle?: ViewStyle;
|
|
21
|
+
progressFillStyle?: ViewStyle;
|
|
22
|
+
badgeStyle?: ViewStyle;
|
|
23
|
+
badgeTextStyle?: TextStyle;
|
|
24
|
+
// Colors (design system integration)
|
|
25
|
+
primaryColor?: string;
|
|
26
|
+
secondaryColor?: string;
|
|
27
|
+
backgroundColor?: string;
|
|
28
|
+
textColor?: string;
|
|
29
|
+
subtextColor?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const LevelProgress: React.FC<LevelProgressProps> = ({
|
|
33
|
+
level,
|
|
34
|
+
points,
|
|
35
|
+
levelTitle,
|
|
36
|
+
pointsToNext,
|
|
37
|
+
progress,
|
|
38
|
+
showPoints = true,
|
|
39
|
+
containerStyle,
|
|
40
|
+
titleStyle,
|
|
41
|
+
subtitleStyle,
|
|
42
|
+
progressBarStyle,
|
|
43
|
+
progressFillStyle,
|
|
44
|
+
badgeStyle,
|
|
45
|
+
badgeTextStyle,
|
|
46
|
+
primaryColor = "#FFD700",
|
|
47
|
+
secondaryColor = "#2A2A2A",
|
|
48
|
+
backgroundColor = "#1A1A1A",
|
|
49
|
+
textColor = "#FFFFFF",
|
|
50
|
+
subtextColor = "#888888",
|
|
51
|
+
}) => {
|
|
52
|
+
return (
|
|
53
|
+
<View style={[styles.container, { backgroundColor }, containerStyle]}>
|
|
54
|
+
<View style={styles.header}>
|
|
55
|
+
<View style={styles.titleSection}>
|
|
56
|
+
<Text style={[styles.levelTitle, { color: textColor }, titleStyle]}>
|
|
57
|
+
{levelTitle}
|
|
58
|
+
</Text>
|
|
59
|
+
{showPoints && (
|
|
60
|
+
<Text style={[styles.subtitle, { color: subtextColor }, subtitleStyle]}>
|
|
61
|
+
{points} / {points + pointsToNext}
|
|
62
|
+
</Text>
|
|
63
|
+
)}
|
|
64
|
+
</View>
|
|
65
|
+
|
|
66
|
+
<View style={[styles.badge, { backgroundColor: `${primaryColor}20`, borderColor: `${primaryColor}40` }, badgeStyle]}>
|
|
67
|
+
<Text style={[styles.badgeText, { color: primaryColor }, badgeTextStyle]}>
|
|
68
|
+
{level}
|
|
69
|
+
</Text>
|
|
70
|
+
</View>
|
|
71
|
+
</View>
|
|
72
|
+
|
|
73
|
+
<View style={[styles.progressBar, { backgroundColor: secondaryColor }, progressBarStyle]}>
|
|
74
|
+
<View
|
|
75
|
+
style={[
|
|
76
|
+
styles.progressFill,
|
|
77
|
+
{ width: `${Math.min(100, progress)}%`, backgroundColor: primaryColor },
|
|
78
|
+
progressFillStyle,
|
|
79
|
+
]}
|
|
80
|
+
/>
|
|
81
|
+
</View>
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const styles = StyleSheet.create({
|
|
87
|
+
container: {
|
|
88
|
+
borderRadius: 16,
|
|
89
|
+
padding: 16,
|
|
90
|
+
},
|
|
91
|
+
header: {
|
|
92
|
+
flexDirection: "row",
|
|
93
|
+
justifyContent: "space-between",
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
marginBottom: 12,
|
|
96
|
+
},
|
|
97
|
+
titleSection: {
|
|
98
|
+
flex: 1,
|
|
99
|
+
},
|
|
100
|
+
levelTitle: {
|
|
101
|
+
fontSize: 18,
|
|
102
|
+
fontWeight: "700",
|
|
103
|
+
},
|
|
104
|
+
subtitle: {
|
|
105
|
+
fontSize: 14,
|
|
106
|
+
marginTop: 2,
|
|
107
|
+
},
|
|
108
|
+
badge: {
|
|
109
|
+
width: 48,
|
|
110
|
+
height: 48,
|
|
111
|
+
borderRadius: 24,
|
|
112
|
+
justifyContent: "center",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
borderWidth: 2,
|
|
115
|
+
},
|
|
116
|
+
badgeText: {
|
|
117
|
+
fontSize: 18,
|
|
118
|
+
fontWeight: "bold",
|
|
119
|
+
},
|
|
120
|
+
progressBar: {
|
|
121
|
+
height: 8,
|
|
122
|
+
borderRadius: 4,
|
|
123
|
+
overflow: "hidden",
|
|
124
|
+
},
|
|
125
|
+
progressFill: {
|
|
126
|
+
height: "100%",
|
|
127
|
+
borderRadius: 4,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PointsBadge Component
|
|
3
|
+
* Displays points with optional icon - all text via props
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
+
|
|
9
|
+
export interface PointsBadgeProps {
|
|
10
|
+
points: number;
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
// Customization
|
|
13
|
+
containerStyle?: ViewStyle;
|
|
14
|
+
textStyle?: TextStyle;
|
|
15
|
+
// Colors
|
|
16
|
+
backgroundColor?: string;
|
|
17
|
+
textColor?: string;
|
|
18
|
+
borderColor?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const PointsBadge: React.FC<PointsBadgeProps> = ({
|
|
22
|
+
points,
|
|
23
|
+
icon,
|
|
24
|
+
containerStyle,
|
|
25
|
+
textStyle,
|
|
26
|
+
backgroundColor = "#FFD70020",
|
|
27
|
+
textColor = "#FFD700",
|
|
28
|
+
borderColor = "#FFD70040",
|
|
29
|
+
}) => {
|
|
30
|
+
return (
|
|
31
|
+
<View
|
|
32
|
+
style={[
|
|
33
|
+
styles.container,
|
|
34
|
+
{ backgroundColor, borderColor },
|
|
35
|
+
containerStyle,
|
|
36
|
+
]}
|
|
37
|
+
>
|
|
38
|
+
{icon}
|
|
39
|
+
<Text style={[styles.text, { color: textColor }, textStyle]}>
|
|
40
|
+
{points}
|
|
41
|
+
</Text>
|
|
42
|
+
</View>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const styles = StyleSheet.create({
|
|
47
|
+
container: {
|
|
48
|
+
flexDirection: "row",
|
|
49
|
+
alignItems: "center",
|
|
50
|
+
gap: 6,
|
|
51
|
+
paddingHorizontal: 12,
|
|
52
|
+
paddingVertical: 6,
|
|
53
|
+
borderRadius: 20,
|
|
54
|
+
borderWidth: 1,
|
|
55
|
+
},
|
|
56
|
+
text: {
|
|
57
|
+
fontSize: 16,
|
|
58
|
+
fontWeight: "bold",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsCard Component
|
|
3
|
+
* Displays a stat with icon - all text via props
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
+
|
|
9
|
+
export interface StatsCardProps {
|
|
10
|
+
value: number;
|
|
11
|
+
label: string;
|
|
12
|
+
icon: React.ReactNode;
|
|
13
|
+
suffix?: string;
|
|
14
|
+
// Customization
|
|
15
|
+
containerStyle?: ViewStyle;
|
|
16
|
+
valueStyle?: TextStyle;
|
|
17
|
+
labelStyle?: TextStyle;
|
|
18
|
+
// Colors
|
|
19
|
+
accentColor?: string;
|
|
20
|
+
backgroundColor?: string;
|
|
21
|
+
textColor?: string;
|
|
22
|
+
subtextColor?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const StatsCard: React.FC<StatsCardProps> = ({
|
|
26
|
+
value,
|
|
27
|
+
label,
|
|
28
|
+
icon,
|
|
29
|
+
suffix,
|
|
30
|
+
containerStyle,
|
|
31
|
+
valueStyle,
|
|
32
|
+
labelStyle,
|
|
33
|
+
accentColor = "#FFD700",
|
|
34
|
+
backgroundColor = "#1A1A1A",
|
|
35
|
+
textColor = "#FFFFFF",
|
|
36
|
+
subtextColor = "#888888",
|
|
37
|
+
}) => {
|
|
38
|
+
return (
|
|
39
|
+
<View style={[styles.container, { backgroundColor }, containerStyle]}>
|
|
40
|
+
<View style={[styles.iconContainer, { backgroundColor: `${accentColor}15` }]}>
|
|
41
|
+
{icon}
|
|
42
|
+
</View>
|
|
43
|
+
<View style={styles.valueRow}>
|
|
44
|
+
<Text style={[styles.value, { color: textColor }, valueStyle]}>
|
|
45
|
+
{value}
|
|
46
|
+
</Text>
|
|
47
|
+
{suffix && (
|
|
48
|
+
<Text style={[styles.suffix, { color: subtextColor }]}>{suffix}</Text>
|
|
49
|
+
)}
|
|
50
|
+
</View>
|
|
51
|
+
<Text style={[styles.label, { color: subtextColor }, labelStyle]}>
|
|
52
|
+
{label}
|
|
53
|
+
</Text>
|
|
54
|
+
</View>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const styles = StyleSheet.create({
|
|
59
|
+
container: {
|
|
60
|
+
flex: 1,
|
|
61
|
+
minWidth: "45%",
|
|
62
|
+
borderRadius: 12,
|
|
63
|
+
padding: 12,
|
|
64
|
+
},
|
|
65
|
+
iconContainer: {
|
|
66
|
+
width: 36,
|
|
67
|
+
height: 36,
|
|
68
|
+
borderRadius: 18,
|
|
69
|
+
alignItems: "center",
|
|
70
|
+
justifyContent: "center",
|
|
71
|
+
marginBottom: 8,
|
|
72
|
+
},
|
|
73
|
+
valueRow: {
|
|
74
|
+
flexDirection: "row",
|
|
75
|
+
alignItems: "baseline",
|
|
76
|
+
gap: 4,
|
|
77
|
+
},
|
|
78
|
+
value: {
|
|
79
|
+
fontSize: 24,
|
|
80
|
+
fontWeight: "bold",
|
|
81
|
+
},
|
|
82
|
+
suffix: {
|
|
83
|
+
fontSize: 12,
|
|
84
|
+
},
|
|
85
|
+
label: {
|
|
86
|
+
fontSize: 12,
|
|
87
|
+
marginTop: 2,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreakDisplay Component
|
|
3
|
+
* Displays streak information - all text via props
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, Text, StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
8
|
+
|
|
9
|
+
export interface StreakDisplayProps {
|
|
10
|
+
current: number;
|
|
11
|
+
longest: number;
|
|
12
|
+
currentLabel: string; // e.g., "Current Streak"
|
|
13
|
+
bestLabel: string; // e.g., "Best"
|
|
14
|
+
daysLabel: string; // e.g., "days"
|
|
15
|
+
icon?: React.ReactNode;
|
|
16
|
+
// Customization
|
|
17
|
+
containerStyle?: ViewStyle;
|
|
18
|
+
numberStyle?: TextStyle;
|
|
19
|
+
labelStyle?: TextStyle;
|
|
20
|
+
// Colors
|
|
21
|
+
primaryColor?: string;
|
|
22
|
+
backgroundColor?: string;
|
|
23
|
+
textColor?: string;
|
|
24
|
+
subtextColor?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const StreakDisplay: React.FC<StreakDisplayProps> = ({
|
|
28
|
+
current,
|
|
29
|
+
longest,
|
|
30
|
+
currentLabel,
|
|
31
|
+
bestLabel,
|
|
32
|
+
daysLabel,
|
|
33
|
+
icon,
|
|
34
|
+
containerStyle,
|
|
35
|
+
numberStyle,
|
|
36
|
+
labelStyle,
|
|
37
|
+
primaryColor = "#FF6B35",
|
|
38
|
+
backgroundColor = "#1A1A1A",
|
|
39
|
+
textColor = "#FFFFFF",
|
|
40
|
+
subtextColor = "#888888",
|
|
41
|
+
}) => {
|
|
42
|
+
return (
|
|
43
|
+
<View style={[styles.container, { backgroundColor }, containerStyle]}>
|
|
44
|
+
<View style={styles.mainStreak}>
|
|
45
|
+
{icon && <View style={styles.iconContainer}>{icon}</View>}
|
|
46
|
+
<View style={styles.streakInfo}>
|
|
47
|
+
<Text style={[styles.number, { color: primaryColor }, numberStyle]}>
|
|
48
|
+
{current}
|
|
49
|
+
</Text>
|
|
50
|
+
<Text style={[styles.label, { color: subtextColor }, labelStyle]}>
|
|
51
|
+
{daysLabel}
|
|
52
|
+
</Text>
|
|
53
|
+
</View>
|
|
54
|
+
<Text style={[styles.currentLabel, { color: textColor }]}>
|
|
55
|
+
{currentLabel}
|
|
56
|
+
</Text>
|
|
57
|
+
</View>
|
|
58
|
+
|
|
59
|
+
<View style={[styles.bestStreak, { backgroundColor: `${primaryColor}20` }]}>
|
|
60
|
+
<Text style={[styles.bestLabel, { color: subtextColor }]}>
|
|
61
|
+
{bestLabel}
|
|
62
|
+
</Text>
|
|
63
|
+
<Text style={[styles.bestNumber, { color: primaryColor }]}>
|
|
64
|
+
{longest}
|
|
65
|
+
</Text>
|
|
66
|
+
</View>
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const styles = StyleSheet.create({
|
|
72
|
+
container: {
|
|
73
|
+
borderRadius: 16,
|
|
74
|
+
padding: 16,
|
|
75
|
+
flexDirection: "row",
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
justifyContent: "space-between",
|
|
78
|
+
},
|
|
79
|
+
mainStreak: {
|
|
80
|
+
flexDirection: "row",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
gap: 12,
|
|
83
|
+
},
|
|
84
|
+
iconContainer: {
|
|
85
|
+
width: 40,
|
|
86
|
+
height: 40,
|
|
87
|
+
justifyContent: "center",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
},
|
|
90
|
+
streakInfo: {
|
|
91
|
+
alignItems: "center",
|
|
92
|
+
},
|
|
93
|
+
number: {
|
|
94
|
+
fontSize: 32,
|
|
95
|
+
fontWeight: "bold",
|
|
96
|
+
},
|
|
97
|
+
label: {
|
|
98
|
+
fontSize: 12,
|
|
99
|
+
textTransform: "uppercase",
|
|
100
|
+
},
|
|
101
|
+
currentLabel: {
|
|
102
|
+
fontSize: 14,
|
|
103
|
+
fontWeight: "500",
|
|
104
|
+
},
|
|
105
|
+
bestStreak: {
|
|
106
|
+
paddingHorizontal: 12,
|
|
107
|
+
paddingVertical: 8,
|
|
108
|
+
borderRadius: 12,
|
|
109
|
+
alignItems: "center",
|
|
110
|
+
},
|
|
111
|
+
bestLabel: {
|
|
112
|
+
fontSize: 11,
|
|
113
|
+
textTransform: "uppercase",
|
|
114
|
+
},
|
|
115
|
+
bestNumber: {
|
|
116
|
+
fontSize: 18,
|
|
117
|
+
fontWeight: "bold",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Components
|
|
3
|
+
* All text via props - NO hardcoded strings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { LevelProgress, type LevelProgressProps } from "./LevelProgress";
|
|
7
|
+
export { PointsBadge, type PointsBadgeProps } from "./PointsBadge";
|
|
8
|
+
export { AchievementCard, type AchievementCardProps } from "./AchievementCard";
|
|
9
|
+
export { AchievementToast, type AchievementToastProps } from "./AchievementToast";
|
|
10
|
+
export { StreakDisplay, type StreakDisplayProps } from "./StreakDisplay";
|
|
11
|
+
export { StatsCard, type StatsCardProps } from "./StatsCard";
|
|
12
|
+
export { AchievementItem, type AchievementItemProps } from "./AchievementItem";
|
|
13
|
+
export { GamificationScreen, type GamificationScreenProps } from "./GamificationScreen/index";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Configuration Example
|
|
3
|
+
*
|
|
4
|
+
* Copy this file to your app's settings config directory:
|
|
5
|
+
* src/domains/settings/config/gamification.config.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { GamificationSettingsConfig } from "../types/settings";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates gamification configuration for settings
|
|
12
|
+
*
|
|
13
|
+
* @param t - Translation function from useLocalization
|
|
14
|
+
* @returns Gamification settings configuration
|
|
15
|
+
*/
|
|
16
|
+
export const createGamificationConfig = ({ t }): GamificationSettingsConfig => ({
|
|
17
|
+
enabled: true,
|
|
18
|
+
|
|
19
|
+
// Gamification system configuration
|
|
20
|
+
config: {
|
|
21
|
+
storageKey: "app_gamification_data",
|
|
22
|
+
|
|
23
|
+
// Define achievements
|
|
24
|
+
achievements: [
|
|
25
|
+
// Count-based achievements
|
|
26
|
+
{ id: "first_task", threshold: 1, type: "count" },
|
|
27
|
+
{ id: "five_tasks", threshold: 5, type: "count" },
|
|
28
|
+
{ id: "ten_tasks", threshold: 10, type: "count" },
|
|
29
|
+
{ id: "fifty_tasks", threshold: 50, type: "count" },
|
|
30
|
+
{ id: "hundred_tasks", threshold: 100, type: "count" },
|
|
31
|
+
|
|
32
|
+
// Streak-based achievements
|
|
33
|
+
{ id: "three_day_streak", threshold: 3, type: "streak" },
|
|
34
|
+
{ id: "week_streak", threshold: 7, type: "streak" },
|
|
35
|
+
{ id: "month_streak", threshold: 30, type: "streak" },
|
|
36
|
+
|
|
37
|
+
// Milestone achievements
|
|
38
|
+
{ id: "first_login", threshold: 1, type: "milestone" },
|
|
39
|
+
{ id: "power_user", threshold: 1, type: "milestone" },
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
// Define level progression
|
|
43
|
+
levels: [
|
|
44
|
+
{ level: 1, minPoints: 0, maxPoints: 50 },
|
|
45
|
+
{ level: 2, minPoints: 50, maxPoints: 150 },
|
|
46
|
+
{ level: 3, minPoints: 150, maxPoints: 300 },
|
|
47
|
+
{ level: 4, minPoints: 300, maxPoints: 500 },
|
|
48
|
+
{ level: 5, minPoints: 500, maxPoints: 800 },
|
|
49
|
+
{ level: 6, minPoints: 800, maxPoints: 1200 },
|
|
50
|
+
{ level: 7, minPoints: 1200, maxPoints: 1700 },
|
|
51
|
+
{ level: 8, minPoints: 1700, maxPoints: 2300 },
|
|
52
|
+
{ level: 9, minPoints: 2300, maxPoints: 3000 },
|
|
53
|
+
{ level: 10, minPoints: 3000, maxPoints: 999999 },
|
|
54
|
+
],
|
|
55
|
+
|
|
56
|
+
// Points awarded per action
|
|
57
|
+
pointsPerAction: 10,
|
|
58
|
+
|
|
59
|
+
// Streak bonus multiplier (1.5 = 50% bonus)
|
|
60
|
+
streakBonusMultiplier: 1.5,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Screen display configuration
|
|
64
|
+
screenProps: {
|
|
65
|
+
title: t("gamification.title"),
|
|
66
|
+
statsTitle: t("gamification.stats.title"),
|
|
67
|
+
achievementsTitle: t("gamification.achievements.title"),
|
|
68
|
+
streakTitle: t("gamification.streak.title"),
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"settings": {
|
|
3
|
+
"gamification": {
|
|
4
|
+
"title": "Achievements",
|
|
5
|
+
"menuTitle": "Achievements & Progress",
|
|
6
|
+
"menuSubtitle": "View your achievements and stats"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"gamification": {
|
|
10
|
+
"title": "Your Progress",
|
|
11
|
+
"level": {
|
|
12
|
+
"title": "Level {{level}}"
|
|
13
|
+
},
|
|
14
|
+
"stats": {
|
|
15
|
+
"title": "Statistics",
|
|
16
|
+
"totalPoints": "Total Points",
|
|
17
|
+
"tasksCompleted": "Tasks Completed",
|
|
18
|
+
"achievementsUnlocked": "Achievements Unlocked"
|
|
19
|
+
},
|
|
20
|
+
"achievements": {
|
|
21
|
+
"title": "Achievements",
|
|
22
|
+
"empty": "No achievements yet. Keep going!",
|
|
23
|
+
"first_task": {
|
|
24
|
+
"title": "First Steps",
|
|
25
|
+
"description": "Complete your first task"
|
|
26
|
+
},
|
|
27
|
+
"five_tasks": {
|
|
28
|
+
"title": "Getting Started",
|
|
29
|
+
"description": "Complete 5 tasks"
|
|
30
|
+
},
|
|
31
|
+
"ten_tasks": {
|
|
32
|
+
"title": "On a Roll",
|
|
33
|
+
"description": "Complete 10 tasks"
|
|
34
|
+
},
|
|
35
|
+
"fifty_tasks": {
|
|
36
|
+
"title": "Dedicated",
|
|
37
|
+
"description": "Complete 50 tasks"
|
|
38
|
+
},
|
|
39
|
+
"hundred_tasks": {
|
|
40
|
+
"title": "Century Club",
|
|
41
|
+
"description": "Complete 100 tasks"
|
|
42
|
+
},
|
|
43
|
+
"three_day_streak": {
|
|
44
|
+
"title": "Consistent",
|
|
45
|
+
"description": "Maintain a 3-day streak"
|
|
46
|
+
},
|
|
47
|
+
"week_streak": {
|
|
48
|
+
"title": "Week Warrior",
|
|
49
|
+
"description": "Maintain a 7-day streak"
|
|
50
|
+
},
|
|
51
|
+
"month_streak": {
|
|
52
|
+
"title": "Monthly Master",
|
|
53
|
+
"description": "Maintain a 30-day streak"
|
|
54
|
+
},
|
|
55
|
+
"first_login": {
|
|
56
|
+
"title": "Welcome!",
|
|
57
|
+
"description": "Join the community"
|
|
58
|
+
},
|
|
59
|
+
"power_user": {
|
|
60
|
+
"title": "Power User",
|
|
61
|
+
"description": "Unlock all features"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"streak": {
|
|
65
|
+
"title": "Daily Streak",
|
|
66
|
+
"current": "Current Streak",
|
|
67
|
+
"best": "Best Streak",
|
|
68
|
+
"days": "days"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|