@umituz/react-native-settings 4.21.10 → 4.21.12

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.
Files changed (46) hide show
  1. package/package.json +11 -2
  2. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +42 -87
  3. package/src/domains/gamification/components/index.ts +1 -0
  4. package/src/domains/gamification/index.ts +3 -11
  5. package/src/index.ts +3 -0
  6. package/src/notifications/domains/quietHours/infrastructure/hooks/useQuietHoursActions.ts +52 -0
  7. package/src/notifications/domains/quietHours/presentation/components/QuietHoursCard.tsx +112 -0
  8. package/src/notifications/domains/reminders/infrastructure/config/reminderPresets.ts +120 -0
  9. package/src/notifications/domains/reminders/infrastructure/hooks/useReminderActions.ts +106 -0
  10. package/src/notifications/domains/reminders/infrastructure/storage/RemindersStore.ts +148 -0
  11. package/src/notifications/domains/reminders/presentation/components/FormButton.tsx +66 -0
  12. package/src/notifications/domains/reminders/presentation/components/FrequencySelector.tsx +72 -0
  13. package/src/notifications/domains/reminders/presentation/components/ReminderForm.tsx +169 -0
  14. package/src/notifications/domains/reminders/presentation/components/ReminderItem.tsx +130 -0
  15. package/src/notifications/domains/reminders/presentation/components/TimePresetSelector.tsx +100 -0
  16. package/src/notifications/domains/reminders/presentation/components/WeekdaySelector.tsx +61 -0
  17. package/src/notifications/domains/reminders/presentation/screens/ReminderListScreen.tsx +131 -0
  18. package/src/notifications/index.ts +139 -0
  19. package/src/notifications/infrastructure/config/notificationsConfig.ts +98 -0
  20. package/src/notifications/infrastructure/hooks/useNotificationSettings.ts +37 -0
  21. package/src/notifications/infrastructure/services/NotificationBadgeManager.ts +28 -0
  22. package/src/notifications/infrastructure/services/NotificationManager.ts +138 -0
  23. package/src/notifications/infrastructure/services/NotificationPermissions.ts +80 -0
  24. package/src/notifications/infrastructure/services/NotificationScheduler.ts +77 -0
  25. package/src/notifications/infrastructure/services/NotificationService.ts +50 -0
  26. package/src/notifications/infrastructure/services/types.ts +176 -0
  27. package/src/notifications/infrastructure/storage/NotificationsStore.ts +45 -0
  28. package/src/notifications/infrastructure/utils/dev.ts +25 -0
  29. package/src/notifications/infrastructure/utils/idGenerator.ts +14 -0
  30. package/src/notifications/infrastructure/utils/triggerBuilder.ts +45 -0
  31. package/src/notifications/presentation/components/NotificationsSection.tsx +84 -0
  32. package/src/notifications/presentation/components/RemindersNavRow.styles.ts +38 -0
  33. package/src/notifications/presentation/components/RemindersNavRow.tsx +51 -0
  34. package/src/notifications/presentation/components/SettingRow.tsx +86 -0
  35. package/src/notifications/presentation/hooks/useNotificationSettingsUI.ts +52 -0
  36. package/src/notifications/presentation/hooks/useTimePicker.ts +71 -0
  37. package/src/notifications/presentation/screens/NotificationSettingsScreen.styles.ts +30 -0
  38. package/src/notifications/presentation/screens/NotificationSettingsScreen.tsx +131 -0
  39. package/src/notifications/presentation/screens/NotificationsScreen.tsx +107 -0
  40. package/src/presentation/navigation/SettingsStackNavigator.tsx +21 -11
  41. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +1 -1
  42. package/src/domains/gamification/README.md +0 -343
  43. package/src/domains/gamification/components/GamificationSettingsItem.tsx +0 -33
  44. package/src/domains/gamification/examples/gamification.config.example.ts +0 -70
  45. package/src/domains/gamification/examples/localization.example.json +0 -71
  46. package/src/domains/gamification/types/settings.ts +0 -28
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Setting Row Component
3
+ * Reusable toggle row for settings
4
+ */
5
+
6
+ import React, { useMemo, useCallback } from 'react';
7
+ import { View, StyleSheet, Switch } from 'react-native';
8
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
9
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
10
+
11
+ export interface SettingRowProps {
12
+ iconName: string;
13
+ title: string;
14
+ description: string;
15
+ value: boolean;
16
+ onToggle: (value: boolean) => void;
17
+ onHapticFeedback?: () => void;
18
+ }
19
+
20
+ export const SettingRow: React.FC<SettingRowProps> = ({
21
+ iconName,
22
+ title,
23
+ description,
24
+ value,
25
+ onToggle,
26
+ onHapticFeedback,
27
+ }) => {
28
+ const tokens = useAppDesignTokens();
29
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
30
+
31
+ const handleToggle = useCallback((newValue: boolean) => {
32
+ onHapticFeedback?.();
33
+ onToggle(newValue);
34
+ }, [onToggle, onHapticFeedback]);
35
+
36
+ return (
37
+ <View style={styles.container}>
38
+ <View style={styles.iconContainer}>
39
+ <AtomicIcon name={iconName} size="md" color="primary" />
40
+ </View>
41
+ <View style={styles.textContainer}>
42
+ <AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
43
+ {title}
44
+ </AtomicText>
45
+ <AtomicText type="bodySmall" style={styles.description}>
46
+ {description}
47
+ </AtomicText>
48
+ </View>
49
+ <Switch
50
+ value={value}
51
+ onValueChange={handleToggle}
52
+ trackColor={{
53
+ false: tokens.colors.surfaceSecondary,
54
+ true: tokens.colors.primary
55
+ }}
56
+ thumbColor={tokens.colors.surface}
57
+ ios_backgroundColor={tokens.colors.surfaceSecondary}
58
+ />
59
+ </View>
60
+ );
61
+ };
62
+
63
+ const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
64
+ StyleSheet.create({
65
+ container: {
66
+ flexDirection: 'row',
67
+ alignItems: 'center',
68
+ },
69
+ iconContainer: {
70
+ width: 44,
71
+ height: 44,
72
+ borderRadius: 22,
73
+ backgroundColor: tokens.colors.surfaceSecondary,
74
+ justifyContent: 'center',
75
+ alignItems: 'center',
76
+ marginRight: 12,
77
+ },
78
+ textContainer: {
79
+ flex: 1,
80
+ marginRight: 12,
81
+ },
82
+ description: {
83
+ color: tokens.colors.textSecondary,
84
+ marginTop: 2,
85
+ },
86
+ });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * useNotificationSettingsUI Hook
3
+ * Handles all business logic for notification settings screen
4
+ */
5
+
6
+ import { useEffect, useCallback } from 'react';
7
+ import { useNotificationPreferences, useQuietHours, usePreferencesStore, useRemindersLoading } from '../../domains/reminders/infrastructure/storage/RemindersStore';
8
+ import { notificationService } from '../../infrastructure/services/NotificationService';
9
+
10
+ export const useNotificationSettingsUI = () => {
11
+ const preferences = useNotificationPreferences();
12
+ const quietHours = useQuietHours();
13
+ const { initialize, updatePreferences, updateQuietHours } = usePreferencesStore();
14
+ const isLoading = useRemindersLoading();
15
+
16
+ useEffect(() => {
17
+ initialize();
18
+ }, [initialize]);
19
+
20
+ const handleMasterToggle = useCallback(async (value: boolean) => {
21
+ if (value) {
22
+ const hasPermission = await notificationService.hasPermissions();
23
+ if (!hasPermission) {
24
+ const granted = await notificationService.requestPermissions();
25
+ if (!granted) return;
26
+ }
27
+ }
28
+ await updatePreferences({ enabled: value });
29
+ }, [updatePreferences]);
30
+
31
+ const handleSoundToggle = useCallback(async (value: boolean) => {
32
+ await updatePreferences({ sound: value });
33
+ }, [updatePreferences]);
34
+
35
+ const handleVibrationToggle = useCallback(async (value: boolean) => {
36
+ await updatePreferences({ vibration: value });
37
+ }, [updatePreferences]);
38
+
39
+ const handleQuietHoursToggle = useCallback(async (value: boolean) => {
40
+ await updateQuietHours({ ...quietHours, enabled: value });
41
+ }, [quietHours, updateQuietHours]);
42
+
43
+ return {
44
+ preferences,
45
+ quietHours,
46
+ isLoading,
47
+ handleMasterToggle,
48
+ handleSoundToggle,
49
+ handleVibrationToggle,
50
+ handleQuietHoursToggle,
51
+ };
52
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * useTimePicker Hook
3
+ * Encapsulates DateTimePicker logic for notification settings
4
+ */
5
+
6
+ import { useState, useCallback } from 'react';
7
+ import type { QuietHoursConfig } from '../../infrastructure/services/types';
8
+
9
+ export type PickerMode = 'start' | 'end' | null;
10
+
11
+ export interface UseTimePickerParams {
12
+ quietHours: QuietHoursConfig;
13
+ onStartTimeChange: (hours: number, minutes: number) => void;
14
+ onEndTimeChange: (hours: number, minutes: number) => void;
15
+ }
16
+
17
+ export interface TimePickerHandlers {
18
+ pickerMode: PickerMode;
19
+ handleStartTimePress: () => void;
20
+ handleEndTimePress: () => void;
21
+ handleTimeChange: (event: any, selectedDate?: Date) => void;
22
+ getPickerDate: () => Date;
23
+ }
24
+
25
+ export const useTimePicker = ({
26
+ quietHours,
27
+ onStartTimeChange,
28
+ onEndTimeChange,
29
+ }: UseTimePickerParams): TimePickerHandlers => {
30
+ const [pickerMode, setPickerMode] = useState<PickerMode>(null);
31
+
32
+ const handleStartTimePress = useCallback(() => {
33
+ setPickerMode('start');
34
+ }, []);
35
+
36
+ const handleEndTimePress = useCallback(() => {
37
+ setPickerMode('end');
38
+ }, []);
39
+
40
+ const handleTimeChange = useCallback((event: any, selectedDate?: Date) => {
41
+ if (event.type === 'set' && selectedDate) {
42
+ const hours = selectedDate.getHours();
43
+ const minutes = selectedDate.getMinutes();
44
+
45
+ if (pickerMode === 'start') {
46
+ onStartTimeChange(hours, minutes);
47
+ } else if (pickerMode === 'end') {
48
+ onEndTimeChange(hours, minutes);
49
+ }
50
+ }
51
+ setPickerMode(null);
52
+ }, [pickerMode, onStartTimeChange, onEndTimeChange]);
53
+
54
+ const getPickerDate = useCallback((): Date => {
55
+ const date = new Date();
56
+ if (pickerMode === 'start') {
57
+ date.setHours(quietHours.startHour, quietHours.startMinute);
58
+ } else if (pickerMode === 'end') {
59
+ date.setHours(quietHours.endHour, quietHours.endMinute);
60
+ }
61
+ return date;
62
+ }, [pickerMode, quietHours]);
63
+
64
+ return {
65
+ pickerMode,
66
+ handleStartTimePress,
67
+ handleEndTimePress,
68
+ handleTimeChange,
69
+ getPickerDate,
70
+ };
71
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * NotificationSettingsScreen Styles
3
+ * Extracted styles for better organization and maintainability
4
+ */
5
+
6
+ import { StyleSheet } from 'react-native';
7
+ import type { DesignTokens } from '@umituz/react-native-design-system';
8
+
9
+ export const createStyles = (tokens: DesignTokens) =>
10
+ StyleSheet.create({
11
+ container: {
12
+ flex: 1,
13
+ padding: 16,
14
+ },
15
+ loadingContainer: {
16
+ flex: 1,
17
+ justifyContent: 'center',
18
+ alignItems: 'center',
19
+ },
20
+ card: {
21
+ marginBottom: 16,
22
+ padding: 16,
23
+ backgroundColor: tokens.colors.surface,
24
+ },
25
+ divider: {
26
+ height: 1,
27
+ backgroundColor: tokens.colors.surfaceSecondary,
28
+ marginVertical: 12,
29
+ },
30
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * NotificationSettingsScreen
3
+ * Clean presentation-only screen for notification settings
4
+ */
5
+
6
+ import React from 'react';
7
+ import { View } from 'react-native';
8
+ import { AtomicCard, ScreenLayout, AtomicSpinner } from '@umituz/react-native-design-system';
9
+ import { QuietHoursCard } from '../../domains/quietHours/presentation/components/QuietHoursCard';
10
+ import { SettingRow } from '../components/SettingRow';
11
+ import { RemindersNavRow } from '../components/RemindersNavRow';
12
+ import { useNotificationSettingsUI } from '../hooks/useNotificationSettingsUI';
13
+ import { useTimePicker } from '../hooks/useTimePicker';
14
+ import { useReminders } from '../../domains/reminders/infrastructure/storage/RemindersStore';
15
+ import { useQuietHoursActions } from '../../domains/quietHours/infrastructure/hooks/useQuietHoursActions';
16
+ import type { NotificationSettingsTranslations, QuietHoursTranslations } from '../../infrastructure/services/types';
17
+ import { createStyles } from './NotificationSettingsScreen.styles';
18
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
19
+ // @ts-ignore - Optional peer dependency
20
+ import DateTimePicker from '@react-native-community/datetimepicker';
21
+
22
+ export interface NotificationSettingsScreenProps {
23
+ translations: NotificationSettingsTranslations;
24
+ quietHoursTranslations: QuietHoursTranslations;
25
+ onHapticFeedback?: () => void;
26
+ }
27
+
28
+ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProps> = ({
29
+ translations,
30
+ quietHoursTranslations,
31
+ onHapticFeedback,
32
+ }) => {
33
+ const tokens = useAppDesignTokens();
34
+ const styles = createStyles(tokens);
35
+ const reminders = useReminders();
36
+ const { setStartTime, setEndTime } = useQuietHoursActions();
37
+
38
+ const {
39
+ preferences,
40
+ quietHours,
41
+ isLoading,
42
+ handleMasterToggle,
43
+ handleSoundToggle,
44
+ handleVibrationToggle,
45
+ handleQuietHoursToggle,
46
+ } = useNotificationSettingsUI();
47
+
48
+ const timePicker = useTimePicker({
49
+ quietHours,
50
+ onStartTimeChange: setStartTime,
51
+ onEndTimeChange: setEndTime,
52
+ });
53
+
54
+ const handleRemindersPress = () => {
55
+ // Navigate to reminders screen when implemented
56
+ };
57
+
58
+ if (isLoading) {
59
+ return (
60
+ <ScreenLayout>
61
+ <AtomicSpinner size="lg" color="primary" fullContainer />
62
+ </ScreenLayout>
63
+ );
64
+ }
65
+
66
+ return (
67
+ <ScreenLayout hideScrollIndicator>
68
+ <View style={styles.container}>
69
+ <AtomicCard style={styles.card}>
70
+ <SettingRow
71
+ iconName="notifications"
72
+ title={translations.masterToggleTitle}
73
+ description={translations.masterToggleDescription}
74
+ value={preferences.enabled}
75
+ onToggle={handleMasterToggle}
76
+ onHapticFeedback={onHapticFeedback}
77
+ />
78
+ </AtomicCard>
79
+
80
+ {preferences.enabled && (
81
+ <>
82
+ <AtomicCard style={styles.card}>
83
+ <SettingRow
84
+ iconName="volume-high"
85
+ title={translations.soundTitle}
86
+ description={translations.soundDescription}
87
+ value={preferences.sound}
88
+ onToggle={handleSoundToggle}
89
+ onHapticFeedback={onHapticFeedback}
90
+ />
91
+ <View style={styles.divider} />
92
+ <SettingRow
93
+ iconName="phone-portrait"
94
+ title={translations.vibrationTitle}
95
+ description={translations.vibrationDescription}
96
+ value={preferences.vibration}
97
+ onToggle={handleVibrationToggle}
98
+ onHapticFeedback={onHapticFeedback}
99
+ />
100
+ </AtomicCard>
101
+
102
+ <AtomicCard style={styles.card}>
103
+ <RemindersNavRow
104
+ title={translations.remindersTitle}
105
+ description={translations.remindersDescription}
106
+ count={reminders.length}
107
+ onPress={handleRemindersPress}
108
+ />
109
+ </AtomicCard>
110
+
111
+ <QuietHoursCard
112
+ config={quietHours}
113
+ translations={quietHoursTranslations}
114
+ onToggle={handleQuietHoursToggle}
115
+ onStartTimePress={timePicker.handleStartTimePress}
116
+ onEndTimePress={timePicker.handleEndTimePress}
117
+ />
118
+ </>
119
+ )}
120
+ </View>
121
+ {timePicker.pickerMode && (
122
+ <DateTimePicker
123
+ value={timePicker.getPickerDate()}
124
+ mode="time"
125
+ is24Hour={true}
126
+ onChange={timePicker.handleTimeChange}
127
+ />
128
+ )}
129
+ </ScreenLayout>
130
+ );
131
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Notifications Screen - Dynamic and Reusable
3
+ *
4
+ * A clean notification settings screen that accepts all text and configuration
5
+ * as props to make it completely reusable across different applications.
6
+ */
7
+
8
+ import React, { useMemo } from 'react';
9
+ import { View, StyleSheet } from 'react-native';
10
+ import { AtomicIcon, AtomicCard, AtomicText, ScreenLayout, STATIC_TOKENS, AtomicSpinner } from '@umituz/react-native-design-system';
11
+ import { Switch } from 'react-native';
12
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
13
+ import { useNotificationSettings } from '../../infrastructure/hooks/useNotificationSettings';
14
+ import type { DesignTokens } from '@umituz/react-native-design-system';
15
+
16
+ export interface NotificationsScreenProps {
17
+ translations: {
18
+ title: string;
19
+ description: string;
20
+ loadingText?: string;
21
+ };
22
+ iconName?: string;
23
+ iconColor?: string;
24
+ testID?: string;
25
+ }
26
+
27
+ export const NotificationsScreen: React.FC<NotificationsScreenProps> = ({
28
+ translations,
29
+ iconName = 'notifications',
30
+ iconColor = 'primary',
31
+ testID = 'notifications-screen',
32
+ }) => {
33
+ const tokens = useAppDesignTokens();
34
+ const styles = useMemo(() => getStyles(tokens), [tokens]);
35
+ const { notificationsEnabled, setNotificationsEnabled, isLoading } = useNotificationSettings();
36
+
37
+ if (isLoading) {
38
+ return (
39
+ <ScreenLayout testID={testID}>
40
+ <AtomicSpinner
41
+ size="lg"
42
+ color="primary"
43
+ text={translations.loadingText || 'Loading...'}
44
+ fullContainer
45
+ />
46
+ </ScreenLayout>
47
+ );
48
+ }
49
+
50
+ return (
51
+ <ScreenLayout testID={testID} hideScrollIndicator>
52
+ <AtomicCard style={styles.card}>
53
+ <View style={styles.settingItem}>
54
+ <View style={styles.iconContainer}>
55
+ <AtomicIcon name={iconName} size="lg" color={iconColor as any} />
56
+ </View>
57
+ <View style={styles.textContainer}>
58
+ <AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
59
+ {translations.title}
60
+ </AtomicText>
61
+ <AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: STATIC_TOKENS.spacing.xs }}>
62
+ {translations.description}
63
+ </AtomicText>
64
+ </View>
65
+ <Switch
66
+ value={notificationsEnabled}
67
+ onValueChange={setNotificationsEnabled}
68
+ trackColor={{ false: tokens.colors.surfaceSecondary, true: tokens.colors.primary }}
69
+ thumbColor={tokens.colors.surface}
70
+ testID="notifications-toggle"
71
+ />
72
+ </View>
73
+ </AtomicCard>
74
+ </ScreenLayout>
75
+ );
76
+ };
77
+
78
+ const getStyles = (tokens: DesignTokens) => StyleSheet.create({
79
+ loadingContainer: {
80
+ flex: 1,
81
+ justifyContent: 'center',
82
+ alignItems: 'center',
83
+ },
84
+ card: {
85
+ padding: STATIC_TOKENS.spacing.lg,
86
+ backgroundColor: tokens.colors.surface,
87
+ },
88
+ settingItem: {
89
+ flexDirection: 'row',
90
+ alignItems: 'center',
91
+ },
92
+ iconContainer: {
93
+ width: 48,
94
+ height: 48,
95
+ borderRadius: 24,
96
+ backgroundColor: tokens.colors.surfaceSecondary,
97
+ justifyContent: 'center',
98
+ alignItems: 'center',
99
+ marginRight: STATIC_TOKENS.spacing.md,
100
+ },
101
+ textContainer: {
102
+ flex: 1,
103
+ marginRight: STATIC_TOKENS.spacing.md,
104
+ },
105
+ });
106
+
107
+ export default NotificationsScreen;
@@ -8,9 +8,13 @@
8
8
  import React from "react";
9
9
  import { createStackNavigator } from "@react-navigation/stack";
10
10
  import { useLocalization, LanguageSelectionScreen } from "@umituz/react-native-localization";
11
- import { NotificationSettingsScreen } from "@umituz/react-native-notifications";
11
+ import { NotificationSettingsScreen } from "@notifications";
12
12
  import { AccountScreen } from "@umituz/react-native-auth";
13
- import { useAppDesignTokens, createScreenOptions } from "@umituz/react-native-design-system";
13
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
14
+
15
+ // ...
16
+
17
+
14
18
  import { AppearanceScreen } from "../screens/AppearanceScreen";
15
19
  import { FAQScreen } from "../../domains/faqs";
16
20
  import { useNavigationHandlers } from "./hooks";
@@ -49,15 +53,21 @@ export const SettingsStackNavigator: React.FC<SettingsStackNavigatorProps> = ({
49
53
  useNavigationHandlers(appInfo, legalUrls);
50
54
 
51
55
  const screenOptions = React.useMemo(
52
- () =>
53
- createScreenOptions({
54
- colors: {
55
- surface: tokens.colors.surface,
56
- textPrimary: tokens.colors.textPrimary,
57
- borderLight: tokens.colors.borderLight,
58
- },
59
- backTitle: t("settings.title"),
60
- }),
56
+ () => ({
57
+ headerStyle: {
58
+ backgroundColor: tokens.colors.surface,
59
+ borderBottomColor: tokens.colors.borderLight,
60
+ borderBottomWidth: 1,
61
+ elevation: 0,
62
+ shadowOpacity: 0,
63
+ },
64
+ headerTintColor: tokens.colors.textPrimary,
65
+ headerTitleStyle: {
66
+ color: tokens.colors.textPrimary,
67
+ fontWeight: "600" as const,
68
+ },
69
+ headerBackTitle: t("settings.title"),
70
+ }),
61
71
  [tokens, t]
62
72
  );
63
73
  const notificationTranslations = React.useMemo(() => createNotificationTranslations(t), [t]);
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { useNavigation } from "@react-navigation/native";
3
3
  import { AppearanceSection } from "../../../../domains/appearance/presentation/components/AppearanceSection";
4
- import { NotificationsSection } from "@umituz/react-native-notifications";
4
+ import { NotificationsSection } from "@notifications";
5
5
  import { useLocalization, getLanguageByCode } from "@umituz/react-native-localization";
6
6
  import { SettingsItemCard } from "../../../components/SettingsItemCard";
7
7
  import type { NormalizedConfig } from "../../utils/normalizeConfig";