@umituz/react-native-settings 4.20.62 → 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 +6 -61
- 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 +4 -4
- package/src/domains/gamification/components/GamificationSettingsItem.tsx +1 -1
- 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 +1 -1
- package/src/domains/gamification/hooks/useGamification.ts +91 -0
- package/src/domains/gamification/index.ts +46 -19
- package/src/domains/gamification/store/gamificationStore.ts +162 -0
- package/src/domains/gamification/types/index.ts +95 -23
- package/src/domains/gamification/types/settings.ts +28 -0
- package/src/domains/gamification/utils/calculations.ts +85 -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,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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGamification Hook
|
|
3
|
+
* Main hook for gamification operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
7
|
+
import { useGamificationStore } from "../store/gamificationStore";
|
|
8
|
+
import { calculateLevel } from "../utils/calculations";
|
|
9
|
+
import type { GamificationConfig, LevelState, Achievement } from "../types";
|
|
10
|
+
|
|
11
|
+
export interface UseGamificationReturn {
|
|
12
|
+
// State
|
|
13
|
+
points: number;
|
|
14
|
+
totalTasksCompleted: number;
|
|
15
|
+
level: LevelState;
|
|
16
|
+
streak: { current: number; longest: number };
|
|
17
|
+
achievements: Achievement[];
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
isInitialized: boolean;
|
|
20
|
+
|
|
21
|
+
// Actions
|
|
22
|
+
initialize: (config: GamificationConfig) => Promise<void>;
|
|
23
|
+
completeTask: () => void;
|
|
24
|
+
addPoints: (amount: number) => void;
|
|
25
|
+
reset: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useGamification = (
|
|
29
|
+
config?: GamificationConfig
|
|
30
|
+
): UseGamificationReturn => {
|
|
31
|
+
const store = useGamificationStore();
|
|
32
|
+
|
|
33
|
+
// Auto-initialize if config provided
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (config && !store.isInitialized) {
|
|
36
|
+
store.initialize(config);
|
|
37
|
+
}
|
|
38
|
+
}, [config, store.isInitialized]);
|
|
39
|
+
|
|
40
|
+
// Calculate level from config
|
|
41
|
+
const level = useMemo((): LevelState => {
|
|
42
|
+
if (!config?.levels.length) {
|
|
43
|
+
return {
|
|
44
|
+
currentLevel: 1,
|
|
45
|
+
currentPoints: store.points,
|
|
46
|
+
pointsToNext: 50,
|
|
47
|
+
progress: Math.min(100, (store.points / 50) * 100),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return calculateLevel(store.points, config.levels);
|
|
51
|
+
}, [store.points, config?.levels]);
|
|
52
|
+
|
|
53
|
+
const completeTask = useCallback(() => {
|
|
54
|
+
store.completeTask();
|
|
55
|
+
}, [store.completeTask]);
|
|
56
|
+
|
|
57
|
+
const addPoints = useCallback(
|
|
58
|
+
(amount: number) => {
|
|
59
|
+
store.addPoints(amount);
|
|
60
|
+
},
|
|
61
|
+
[store.addPoints]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const initialize = useCallback(
|
|
65
|
+
async (cfg: GamificationConfig) => {
|
|
66
|
+
await store.initialize(cfg);
|
|
67
|
+
},
|
|
68
|
+
[store.initialize]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const reset = useCallback(async () => {
|
|
72
|
+
await store.reset();
|
|
73
|
+
}, [store.reset]);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
points: store.points,
|
|
77
|
+
totalTasksCompleted: store.totalTasksCompleted,
|
|
78
|
+
level,
|
|
79
|
+
streak: {
|
|
80
|
+
current: store.streak.current,
|
|
81
|
+
longest: store.streak.longest,
|
|
82
|
+
},
|
|
83
|
+
achievements: store.achievements,
|
|
84
|
+
isLoading: store.isLoading,
|
|
85
|
+
isInitialized: store.isInitialized,
|
|
86
|
+
initialize,
|
|
87
|
+
completeTask,
|
|
88
|
+
addPoints,
|
|
89
|
+
reset,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -1,38 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @umituz/react-native-gamification
|
|
3
|
+
*
|
|
4
|
+
* Generic gamification system for React Native apps
|
|
5
|
+
* All text via props - NO hardcoded strings
|
|
6
|
+
* Designed for 100+ apps
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
9
|
// Types
|
|
10
|
+
export type {
|
|
11
|
+
AchievementDefinition,
|
|
12
|
+
Achievement,
|
|
13
|
+
LevelDefinition,
|
|
14
|
+
LevelState,
|
|
15
|
+
StreakState,
|
|
16
|
+
GamificationConfig,
|
|
17
|
+
GamificationState,
|
|
18
|
+
GamificationActions,
|
|
19
|
+
GamificationStore,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
// Settings Integration Types
|
|
6
23
|
export type {
|
|
7
24
|
GamificationSettingsConfig,
|
|
8
25
|
GamificationMenuConfig,
|
|
9
|
-
} from "./types";
|
|
26
|
+
} from "./types/settings";
|
|
10
27
|
|
|
11
|
-
//
|
|
12
|
-
export {
|
|
13
|
-
export type { GamificationScreenWrapperProps } from "./components/GamificationScreenWrapper";
|
|
28
|
+
// Store
|
|
29
|
+
export { useGamificationStore } from "./store/gamificationStore";
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
export type
|
|
31
|
+
// Hooks
|
|
32
|
+
export { useGamification, type UseGamificationReturn } from "./hooks/useGamification";
|
|
17
33
|
|
|
18
|
-
//
|
|
34
|
+
// Utils
|
|
35
|
+
export {
|
|
36
|
+
calculateLevel,
|
|
37
|
+
checkAchievementUnlock,
|
|
38
|
+
updateAchievementProgress,
|
|
39
|
+
isStreakActive,
|
|
40
|
+
isSameDay,
|
|
41
|
+
} from "./utils/calculations";
|
|
42
|
+
|
|
43
|
+
// Components
|
|
19
44
|
export {
|
|
20
|
-
useGamification,
|
|
21
|
-
useGamificationStore,
|
|
22
45
|
LevelProgress,
|
|
46
|
+
type LevelProgressProps,
|
|
23
47
|
PointsBadge,
|
|
48
|
+
type PointsBadgeProps,
|
|
24
49
|
AchievementCard,
|
|
50
|
+
type AchievementCardProps,
|
|
25
51
|
AchievementToast,
|
|
52
|
+
type AchievementToastProps,
|
|
26
53
|
StreakDisplay,
|
|
54
|
+
type StreakDisplayProps,
|
|
27
55
|
StatsCard,
|
|
56
|
+
type StatsCardProps,
|
|
28
57
|
AchievementItem,
|
|
29
|
-
|
|
58
|
+
type AchievementItemProps,
|
|
59
|
+
GamificationScreen,
|
|
60
|
+
type GamificationScreenProps,
|
|
61
|
+
} from "./components";
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Achievement,
|
|
35
|
-
LevelState,
|
|
36
|
-
StreakState,
|
|
37
|
-
UseGamificationReturn,
|
|
38
|
-
} from "@umituz/react-native-gamification";
|
|
63
|
+
// Settings Integration Components
|
|
64
|
+
export { GamificationScreenWrapper } from "./components/GamificationScreenWrapper";
|
|
65
|
+
export { GamificationSettingsItem } from "./components/GamificationSettingsItem";
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Store
|
|
3
|
+
* Zustand store with persist middleware
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createStore } from "@umituz/react-native-storage";
|
|
7
|
+
import type {
|
|
8
|
+
GamificationState,
|
|
9
|
+
GamificationActions,
|
|
10
|
+
GamificationConfig,
|
|
11
|
+
Achievement,
|
|
12
|
+
} from "../types";
|
|
13
|
+
import {
|
|
14
|
+
checkAchievementUnlock,
|
|
15
|
+
updateAchievementProgress,
|
|
16
|
+
isStreakActive,
|
|
17
|
+
isSameDay,
|
|
18
|
+
} from "../utils/calculations";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_STATE: GamificationState = {
|
|
21
|
+
points: 0,
|
|
22
|
+
totalTasksCompleted: 0,
|
|
23
|
+
achievements: [],
|
|
24
|
+
streak: {
|
|
25
|
+
current: 0,
|
|
26
|
+
longest: 0,
|
|
27
|
+
lastActivityDate: null,
|
|
28
|
+
},
|
|
29
|
+
isLoading: false,
|
|
30
|
+
isInitialized: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let currentConfig: GamificationConfig | null = null;
|
|
34
|
+
|
|
35
|
+
export const useGamificationStore = createStore<GamificationState, GamificationActions>({
|
|
36
|
+
name: "gamification-storage",
|
|
37
|
+
initialState: DEFAULT_STATE,
|
|
38
|
+
persist: true,
|
|
39
|
+
version: 1,
|
|
40
|
+
partialize: (state) => ({
|
|
41
|
+
points: state.points,
|
|
42
|
+
totalTasksCompleted: state.totalTasksCompleted,
|
|
43
|
+
achievements: state.achievements,
|
|
44
|
+
streak: state.streak,
|
|
45
|
+
isLoading: false,
|
|
46
|
+
isInitialized: false,
|
|
47
|
+
}),
|
|
48
|
+
actions: (set, get) => ({
|
|
49
|
+
initialize: async (config: GamificationConfig) => {
|
|
50
|
+
currentConfig = config;
|
|
51
|
+
const state = get();
|
|
52
|
+
|
|
53
|
+
// Initialize achievements from config
|
|
54
|
+
const achievements: Achievement[] = config.achievements.map((def) => ({
|
|
55
|
+
...def,
|
|
56
|
+
isUnlocked: false,
|
|
57
|
+
progress: 0,
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Merge with existing unlocked achievements
|
|
61
|
+
const mergedAchievements = achievements.map((ach: Achievement) => {
|
|
62
|
+
const existing = state.achievements.find((a: Achievement) => a.id === ach.id);
|
|
63
|
+
if (existing) {
|
|
64
|
+
return { ...ach, isUnlocked: existing.isUnlocked, progress: existing.progress };
|
|
65
|
+
}
|
|
66
|
+
return ach;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
set({ achievements: mergedAchievements, isInitialized: true });
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
addPoints: (amount: number) => {
|
|
73
|
+
const state = get();
|
|
74
|
+
set({ points: state.points + amount });
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
completeTask: () => {
|
|
78
|
+
const state = get();
|
|
79
|
+
const pointsToAdd = currentConfig?.pointsPerAction ?? 15;
|
|
80
|
+
|
|
81
|
+
set({
|
|
82
|
+
totalTasksCompleted: state.totalTasksCompleted + 1,
|
|
83
|
+
points: state.points + pointsToAdd,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Update streak
|
|
87
|
+
get().updateStreak();
|
|
88
|
+
|
|
89
|
+
// Check achievements
|
|
90
|
+
get().checkAchievements();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
updateStreak: () => {
|
|
94
|
+
const state = get();
|
|
95
|
+
const now = new Date();
|
|
96
|
+
const lastDate = state.streak.lastActivityDate
|
|
97
|
+
? new Date(state.streak.lastActivityDate)
|
|
98
|
+
: null;
|
|
99
|
+
|
|
100
|
+
let newStreak = state.streak.current;
|
|
101
|
+
|
|
102
|
+
if (!lastDate || !isSameDay(lastDate, now)) {
|
|
103
|
+
if (isStreakActive(state.streak.lastActivityDate)) {
|
|
104
|
+
newStreak = state.streak.current + 1;
|
|
105
|
+
} else {
|
|
106
|
+
newStreak = 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
set({
|
|
111
|
+
streak: {
|
|
112
|
+
current: newStreak,
|
|
113
|
+
longest: Math.max(state.streak.longest, newStreak),
|
|
114
|
+
lastActivityDate: now.toISOString(),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
checkAchievements: () => {
|
|
120
|
+
if (!currentConfig) return [];
|
|
121
|
+
|
|
122
|
+
const state = get();
|
|
123
|
+
const newlyUnlocked: Achievement[] = [];
|
|
124
|
+
|
|
125
|
+
const updatedAchievements = state.achievements.map((ach: Achievement) => {
|
|
126
|
+
if (ach.isUnlocked) return ach;
|
|
127
|
+
|
|
128
|
+
const progress = updateAchievementProgress(
|
|
129
|
+
ach,
|
|
130
|
+
state.totalTasksCompleted,
|
|
131
|
+
state.streak.current
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const shouldUnlock = checkAchievementUnlock(
|
|
135
|
+
ach,
|
|
136
|
+
state.totalTasksCompleted,
|
|
137
|
+
state.streak.current
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (shouldUnlock && !ach.isUnlocked) {
|
|
141
|
+
const unlocked = {
|
|
142
|
+
...ach,
|
|
143
|
+
isUnlocked: true,
|
|
144
|
+
unlockedAt: new Date().toISOString(),
|
|
145
|
+
progress: 100,
|
|
146
|
+
};
|
|
147
|
+
newlyUnlocked.push(unlocked);
|
|
148
|
+
return unlocked;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { ...ach, progress };
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
set({ achievements: updatedAchievements });
|
|
155
|
+
return newlyUnlocked;
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
reset: async () => {
|
|
159
|
+
set(DEFAULT_STATE);
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
@@ -1,31 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gamification
|
|
3
|
-
*
|
|
2
|
+
* Gamification Types
|
|
3
|
+
* Generic types for 100+ apps - NO app-specific code
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// Achievement Definition (provided by app)
|
|
7
|
+
export interface AchievementDefinition {
|
|
8
|
+
id: string;
|
|
9
|
+
threshold: number;
|
|
10
|
+
type: "count" | "streak" | "milestone";
|
|
11
|
+
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
enabled: boolean;
|
|
17
|
-
config: GamificationConfig;
|
|
18
|
-
screenProps: Omit<GamificationScreenProps, "levelProps" | "stats" | "achievements">;
|
|
19
|
-
onNavigate?: () => void;
|
|
13
|
+
// Achievement State (internal)
|
|
14
|
+
export interface Achievement extends AchievementDefinition {
|
|
15
|
+
isUnlocked: boolean;
|
|
16
|
+
unlockedAt?: string;
|
|
17
|
+
progress: number;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
// Level Definition (provided by app)
|
|
21
|
+
export interface LevelDefinition {
|
|
22
|
+
level: number;
|
|
23
|
+
minPoints: number;
|
|
24
|
+
maxPoints: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Level State (internal)
|
|
28
|
+
export interface LevelState {
|
|
29
|
+
currentLevel: number;
|
|
30
|
+
currentPoints: number;
|
|
31
|
+
pointsToNext: number;
|
|
32
|
+
progress: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Streak State
|
|
36
|
+
export interface StreakState {
|
|
37
|
+
current: number;
|
|
38
|
+
longest: number;
|
|
39
|
+
lastActivityDate: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Gamification Config (provided by app via props)
|
|
43
|
+
export interface GamificationConfig {
|
|
44
|
+
storageKey: string;
|
|
45
|
+
achievements: AchievementDefinition[];
|
|
46
|
+
levels: LevelDefinition[];
|
|
47
|
+
pointsPerAction?: number;
|
|
48
|
+
streakBonusMultiplier?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Store State
|
|
52
|
+
export interface GamificationState {
|
|
53
|
+
points: number;
|
|
54
|
+
totalTasksCompleted: number;
|
|
55
|
+
achievements: Achievement[];
|
|
56
|
+
streak: StreakState;
|
|
57
|
+
isLoading: boolean;
|
|
58
|
+
isInitialized: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Store Actions
|
|
62
|
+
export interface GamificationActions {
|
|
63
|
+
initialize: (config: GamificationConfig) => Promise<void>;
|
|
64
|
+
addPoints: (amount: number) => void;
|
|
65
|
+
completeTask: () => void;
|
|
66
|
+
updateStreak: () => void;
|
|
67
|
+
checkAchievements: () => Achievement[];
|
|
68
|
+
reset: () => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Combined Store
|
|
72
|
+
export type GamificationStore = GamificationState & GamificationActions;
|
|
73
|
+
|
|
74
|
+
// UI Component Props (all text via props - NO hardcoded strings)
|
|
75
|
+
export interface LevelProgressProps {
|
|
76
|
+
level: number;
|
|
77
|
+
points: number;
|
|
78
|
+
levelTitle: string;
|
|
79
|
+
pointsToNext: number;
|
|
80
|
+
progress: number;
|
|
81
|
+
showPoints?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface AchievementCardProps {
|
|
27
85
|
title: string;
|
|
28
|
-
|
|
29
|
-
icon
|
|
30
|
-
|
|
86
|
+
description: string;
|
|
87
|
+
icon: string;
|
|
88
|
+
isUnlocked: boolean;
|
|
89
|
+
progress: number;
|
|
90
|
+
threshold: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface StreakDisplayProps {
|
|
94
|
+
current: number;
|
|
95
|
+
longest: number;
|
|
96
|
+
streakLabel: string;
|
|
97
|
+
bestLabel: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PointsBadgeProps {
|
|
101
|
+
points: number;
|
|
102
|
+
label?: string;
|
|
31
103
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Settings Integration Types
|
|
3
|
+
* Wrapper types for integrating gamification into settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GamificationConfig } from "./index";
|
|
7
|
+
import type { GamificationScreenProps } from "../components/GamificationScreen/types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for gamification in settings
|
|
11
|
+
*/
|
|
12
|
+
export interface GamificationSettingsConfig {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
config: GamificationConfig;
|
|
15
|
+
screenProps: Omit<GamificationScreenProps, "levelProps" | "stats" | "achievements">;
|
|
16
|
+
onNavigate?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configuration for gamification menu item
|
|
21
|
+
*/
|
|
22
|
+
export interface GamificationMenuConfig {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
title: string;
|
|
25
|
+
subtitle?: string;
|
|
26
|
+
icon?: string;
|
|
27
|
+
onPress?: () => void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gamification Calculations
|
|
3
|
+
* Pure utility functions - NO side effects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { LevelDefinition, LevelState, Achievement, AchievementDefinition } from "../types";
|
|
7
|
+
|
|
8
|
+
export const calculateLevel = (
|
|
9
|
+
points: number,
|
|
10
|
+
levels: LevelDefinition[]
|
|
11
|
+
): LevelState => {
|
|
12
|
+
const sortedLevels = [...levels].sort((a, b) => a.minPoints - b.minPoints);
|
|
13
|
+
|
|
14
|
+
let currentLevelDef = sortedLevels[0];
|
|
15
|
+
let nextLevelDef: LevelDefinition | null = null;
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < sortedLevels.length; i++) {
|
|
18
|
+
if (points >= sortedLevels[i].minPoints) {
|
|
19
|
+
currentLevelDef = sortedLevels[i];
|
|
20
|
+
nextLevelDef = sortedLevels[i + 1] || null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pointsInLevel = points - currentLevelDef.minPoints;
|
|
25
|
+
const levelRange = nextLevelDef
|
|
26
|
+
? nextLevelDef.minPoints - currentLevelDef.minPoints
|
|
27
|
+
: 100;
|
|
28
|
+
const progress = Math.min(100, (pointsInLevel / levelRange) * 100);
|
|
29
|
+
const pointsToNext = nextLevelDef
|
|
30
|
+
? nextLevelDef.minPoints - points
|
|
31
|
+
: 0;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
currentLevel: currentLevelDef.level,
|
|
35
|
+
currentPoints: points,
|
|
36
|
+
pointsToNext,
|
|
37
|
+
progress,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const checkAchievementUnlock = (
|
|
42
|
+
definition: AchievementDefinition,
|
|
43
|
+
tasksCompleted: number,
|
|
44
|
+
currentStreak: number
|
|
45
|
+
): boolean => {
|
|
46
|
+
switch (definition.type) {
|
|
47
|
+
case "count":
|
|
48
|
+
return tasksCompleted >= definition.threshold;
|
|
49
|
+
case "streak":
|
|
50
|
+
return currentStreak >= definition.threshold;
|
|
51
|
+
case "milestone":
|
|
52
|
+
return tasksCompleted >= definition.threshold;
|
|
53
|
+
default:
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const updateAchievementProgress = (
|
|
59
|
+
definition: AchievementDefinition,
|
|
60
|
+
tasksCompleted: number,
|
|
61
|
+
currentStreak: number
|
|
62
|
+
): number => {
|
|
63
|
+
const value = definition.type === "streak" ? currentStreak : tasksCompleted;
|
|
64
|
+
return Math.min(100, (value / definition.threshold) * 100);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const isStreakActive = (lastActivityDate: string | null): boolean => {
|
|
68
|
+
if (!lastActivityDate) return false;
|
|
69
|
+
|
|
70
|
+
const last = new Date(lastActivityDate);
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const diffDays = Math.floor(
|
|
73
|
+
(now.getTime() - last.getTime()) / (1000 * 60 * 60 * 24)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return diffDays <= 1;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const isSameDay = (date1: Date, date2: Date): boolean => {
|
|
80
|
+
return (
|
|
81
|
+
date1.getFullYear() === date2.getFullYear() &&
|
|
82
|
+
date1.getMonth() === date2.getMonth() &&
|
|
83
|
+
date1.getDate() === date2.getDate()
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Bug report
|
|
3
|
-
about: Create a report to help us improve
|
|
4
|
-
title: '[BUG] '
|
|
5
|
-
labels: bug
|
|
6
|
-
assignees: ''
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Bug Description
|
|
10
|
-
|
|
11
|
-
A clear and concise description of what the bug is.
|
|
12
|
-
|
|
13
|
-
## Reproduction Steps
|
|
14
|
-
|
|
15
|
-
1. Go to '...'
|
|
16
|
-
2. Click on '....'
|
|
17
|
-
3. Scroll down to '....'
|
|
18
|
-
4. See error
|
|
19
|
-
|
|
20
|
-
## Expected Behavior
|
|
21
|
-
|
|
22
|
-
A clear and concise description of what you expected to happen.
|
|
23
|
-
|
|
24
|
-
## Actual Behavior
|
|
25
|
-
|
|
26
|
-
A clear and concise description of what actually happened.
|
|
27
|
-
|
|
28
|
-
## Screenshots
|
|
29
|
-
|
|
30
|
-
If applicable, add screenshots to help explain your problem.
|
|
31
|
-
|
|
32
|
-
## Environment
|
|
33
|
-
|
|
34
|
-
| Software | Version |
|
|
35
|
-
|----------|---------|
|
|
36
|
-
| React Native | |
|
|
37
|
-
| iOS | |
|
|
38
|
-
| Android | |
|
|
39
|
-
| @umituz/react-native-settings | |
|
|
40
|
-
| Node | |
|
|
41
|
-
| npm/yarn | |
|
|
42
|
-
|
|
43
|
-
## Additional Context
|
|
44
|
-
|
|
45
|
-
Add any other context about the problem here.
|
|
46
|
-
|
|
47
|
-
## Console Logs
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
Paste your console logs here
|
|
51
|
-
```
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Documentation
|
|
3
|
-
about: Report documentation issues or suggest improvements
|
|
4
|
-
title: '[DOCS] '
|
|
5
|
-
labels: documentation
|
|
6
|
-
assignees: ''
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Documentation Issue
|
|
10
|
-
|
|
11
|
-
Please describe the documentation issue:
|
|
12
|
-
|
|
13
|
-
- [ ] Inaccurate information
|
|
14
|
-
- [ ] Missing information
|
|
15
|
-
- [ ] Unclear or confusing
|
|
16
|
-
- [ ] Outdated content
|
|
17
|
-
- [ ] Typo or grammatical error
|
|
18
|
-
- [ ] Suggestion for improvement
|
|
19
|
-
|
|
20
|
-
## Affected Documentation
|
|
21
|
-
|
|
22
|
-
Which documentation file(s) are affected?
|
|
23
|
-
- [ ] README.md
|
|
24
|
-
- [ ] ARCHITECTURE.md
|
|
25
|
-
- [ ] TESTING.md
|
|
26
|
-
- [ ] AI_AGENT_GUIDELINES.md
|
|
27
|
-
- [ ] SETTINGS_SCREEN_GUIDE.md
|
|
28
|
-
- [ ] Specific component README
|
|
29
|
-
- [ ] Other (please specify)
|
|
30
|
-
|
|
31
|
-
## Current Documentation
|
|
32
|
-
|
|
33
|
-
Paste or describe the current documentation content that needs improvement:
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
Paste current documentation here
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Suggested Change
|
|
40
|
-
|
|
41
|
-
Please describe what should be changed or added:
|
|
42
|
-
|
|
43
|
-
## Additional Context
|
|
44
|
-
|
|
45
|
-
Add any other context, screenshots, or examples about the documentation issue.
|
|
46
|
-
|
|
47
|
-
## AI Agent Guidelines
|
|
48
|
-
|
|
49
|
-
If this is about AI agent guidelines, please specify:
|
|
50
|
-
- Which file needs updating
|
|
51
|
-
- What guidelines are missing or unclear
|
|
52
|
-
- How the guidelines could be improved
|