@umituz/react-native-notifications 1.5.7 → 1.5.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-notifications",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
4
4
  "description": "Offline-first local notifications system for React Native apps using expo-notifications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -31,18 +31,19 @@
31
31
  "@react-native-community/datetimepicker": ">=8.0.0",
32
32
  "@umituz/react-native-design-system": "latest",
33
33
  "@umituz/react-native-haptics": "latest",
34
+ "@umituz/react-native-storage": "latest",
34
35
  "expo-device": ">=6.0.0",
35
36
  "expo-haptics": ">=15.0.0",
36
37
  "expo-linear-gradient": ">=14.0.0",
37
38
  "expo-notifications": ">=0.28.0",
38
39
  "react": ">=18.2.0",
39
40
  "react-native": ">=0.74.0",
40
- "react-native-safe-area-context": ">=4.0.0",
41
- "@umituz/react-native-storage": "latest"
41
+ "react-native-safe-area-context": ">=4.0.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@react-native-async-storage/async-storage": "^2.1.0",
45
45
  "@react-native-community/datetimepicker": "^8.2.0",
46
+ "@types/react": "~19.1.0",
46
47
  "@umituz/react-native-design-system": "latest",
47
48
  "@umituz/react-native-haptics": "latest",
48
49
  "@umituz/react-native-storage": "latest",
@@ -50,7 +51,7 @@
50
51
  "expo-haptics": "~14.0.0",
51
52
  "expo-linear-gradient": "~14.0.0",
52
53
  "expo-notifications": "~0.27.6",
53
- "@types/react": "~19.1.0",
54
+ "firebase": "^12.7.0",
54
55
  "typescript": "~5.9.2"
55
56
  },
56
57
  "publishConfig": {
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  import React, { useEffect, useMemo, useCallback } from 'react';
7
- import { View, FlatList, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
8
- import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
7
+ import { View, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
8
+ import { AtomicText, AtomicIcon, AtomicSpinner } from '@umituz/react-native-design-system';
9
9
  import { useAppDesignTokens } from '@umituz/react-native-design-system';
10
10
  import { ReminderItem } from '../components/ReminderItem';
11
11
  import { useRemindersStore, useReminders, useRemindersLoading } from '../../infrastructure/storage/RemindersStore';
@@ -77,7 +77,7 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
77
77
  if (isLoading) {
78
78
  return (
79
79
  <View style={[styles.loadingContainer, { backgroundColor: tokens.colors.surface }]}>
80
- <ActivityIndicator size="large" color={tokens.colors.primary} />
80
+ <AtomicSpinner size="lg" color="primary" fullContainer />
81
81
  </View>
82
82
  );
83
83
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * RemindersNavRow Styles
3
+ */
4
+
5
+ import { StyleSheet } from 'react-native';
6
+ import type { DesignTokens } from '@umituz/react-native-design-system';
7
+
8
+ export const createStyles = (tokens: DesignTokens) =>
9
+ StyleSheet.create({
10
+ navRow: {
11
+ flexDirection: 'row',
12
+ alignItems: 'center',
13
+ },
14
+ iconContainer: {
15
+ width: 44,
16
+ height: 44,
17
+ borderRadius: 22,
18
+ backgroundColor: tokens.colors.surfaceSecondary,
19
+ justifyContent: 'center',
20
+ alignItems: 'center',
21
+ marginRight: 12,
22
+ },
23
+ textContainer: {
24
+ flex: 1,
25
+ marginRight: 12,
26
+ },
27
+ badge: {
28
+ backgroundColor: tokens.colors.primary,
29
+ paddingHorizontal: 8,
30
+ paddingVertical: 2,
31
+ borderRadius: 10,
32
+ marginRight: 8,
33
+ },
34
+ badgeText: {
35
+ color: tokens.colors.surface,
36
+ fontWeight: '600',
37
+ },
38
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * RemindersNavRow Component
3
+ * Reusable navigation row for reminders section with badge
4
+ */
5
+
6
+ import React from 'react';
7
+ import { View, TouchableOpacity } from 'react-native';
8
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
9
+ import { createStyles } from './RemindersNavRow.styles';
10
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
11
+
12
+ export interface RemindersNavRowProps {
13
+ title: string;
14
+ description: string;
15
+ count: number;
16
+ onPress: () => void;
17
+ }
18
+
19
+ export const RemindersNavRow: React.FC<RemindersNavRowProps> = ({
20
+ title,
21
+ description,
22
+ count,
23
+ onPress,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+ const styles = createStyles(tokens);
27
+
28
+ return (
29
+ <TouchableOpacity style={styles.navRow} onPress={onPress} activeOpacity={0.7}>
30
+ <View style={styles.iconContainer}>
31
+ <AtomicIcon name="time" size="md" color="primary" />
32
+ </View>
33
+ <View style={styles.textContainer}>
34
+ <AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
35
+ {title}
36
+ </AtomicText>
37
+ <AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
38
+ {description}
39
+ </AtomicText>
40
+ </View>
41
+ {count > 0 && (
42
+ <View style={styles.badge}>
43
+ <AtomicText type="bodySmall" style={styles.badgeText}>
44
+ {count}
45
+ </AtomicText>
46
+ </View>
47
+ )}
48
+ <AtomicIcon name="chevron-forward" size="md" color="secondary" />
49
+ </TouchableOpacity>
50
+ );
51
+ };
@@ -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
+ });
@@ -3,21 +3,22 @@
3
3
  * Clean presentation-only screen for notification settings
4
4
  */
5
5
 
6
- import React, { useMemo, useState } from 'react';
7
- import { View, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
8
- import { AtomicText, AtomicIcon, AtomicCard, ScreenLayout } from '@umituz/react-native-design-system';
9
- import { useAppDesignTokens } from '@umituz/react-native-design-system';
6
+ import React from 'react';
7
+ import { View } from 'react-native';
8
+ import { AtomicCard, ScreenLayout, AtomicSpinner } from '@umituz/react-native-design-system';
10
9
  import { QuietHoursCard } from '../../domains/quietHours/presentation/components/QuietHoursCard';
11
10
  import { SettingRow } from '../components/SettingRow';
11
+ import { RemindersNavRow } from '../components/RemindersNavRow';
12
12
  import { useNotificationSettingsUI } from '../hooks/useNotificationSettingsUI';
13
+ import { useTimePicker } from '../hooks/useTimePicker';
13
14
  import { useReminders } from '../../domains/reminders/infrastructure/storage/RemindersStore';
14
15
  import { useQuietHoursActions } from '../../domains/quietHours/infrastructure/hooks/useQuietHoursActions';
15
16
  import type { NotificationSettingsTranslations, QuietHoursTranslations } from '../../infrastructure/services/types';
17
+ import { createStyles } from './NotificationSettingsScreen.styles';
18
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
16
19
  // @ts-ignore - Optional peer dependency
17
20
  import DateTimePicker from '@react-native-community/datetimepicker';
18
21
 
19
- type PickerMode = 'start' | 'end' | null;
20
-
21
22
  export interface NotificationSettingsScreenProps {
22
23
  translations: NotificationSettingsTranslations;
23
24
  quietHoursTranslations: QuietHoursTranslations;
@@ -30,10 +31,9 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
30
31
  onHapticFeedback,
31
32
  }) => {
32
33
  const tokens = useAppDesignTokens();
33
- const styles = useMemo(() => createStyles(tokens), [tokens]);
34
+ const styles = createStyles(tokens);
34
35
  const reminders = useReminders();
35
36
  const { setStartTime, setEndTime } = useQuietHoursActions();
36
- const [pickerMode, setPickerMode] = useState<PickerMode>(null);
37
37
 
38
38
  const {
39
39
  preferences,
@@ -45,48 +45,20 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
45
45
  handleQuietHoursToggle,
46
46
  } = useNotificationSettingsUI();
47
47
 
48
+ const timePicker = useTimePicker({
49
+ quietHours,
50
+ onStartTimeChange: setStartTime,
51
+ onEndTimeChange: setEndTime,
52
+ });
53
+
48
54
  const handleRemindersPress = () => {
49
55
  // Navigate to reminders screen when implemented
50
56
  };
51
57
 
52
- const handleStartTimePress = () => {
53
- setPickerMode('start');
54
- };
55
-
56
- const handleEndTimePress = () => {
57
- setPickerMode('end');
58
- };
59
-
60
- const handleTimeChange = (event: any, selectedDate?: Date) => {
61
- if (event.type === 'set' && selectedDate) {
62
- const hours = selectedDate.getHours();
63
- const minutes = selectedDate.getMinutes();
64
-
65
- if (pickerMode === 'start') {
66
- setStartTime(hours, minutes);
67
- } else if (pickerMode === 'end') {
68
- setEndTime(hours, minutes);
69
- }
70
- }
71
- setPickerMode(null);
72
- };
73
-
74
- const getPickerDate = () => {
75
- const date = new Date();
76
- if (pickerMode === 'start') {
77
- date.setHours(quietHours.startHour, quietHours.startMinute);
78
- } else if (pickerMode === 'end') {
79
- date.setHours(quietHours.endHour, quietHours.endMinute);
80
- }
81
- return date;
82
- };
83
-
84
58
  if (isLoading) {
85
59
  return (
86
60
  <ScreenLayout>
87
- <View style={styles.loadingContainer}>
88
- <ActivityIndicator size="large" color={tokens.colors.primary} />
89
- </View>
61
+ <AtomicSpinner size="lg" color="primary" fullContainer />
90
62
  </ScreenLayout>
91
63
  );
92
64
  }
@@ -128,98 +100,32 @@ export const NotificationSettingsScreen: React.FC<NotificationSettingsScreenProp
128
100
  </AtomicCard>
129
101
 
130
102
  <AtomicCard style={styles.card}>
131
- <TouchableOpacity style={styles.navRow} onPress={handleRemindersPress} activeOpacity={0.7}>
132
- <View style={styles.iconContainer}>
133
- <AtomicIcon name="time" size="md" color="primary" />
134
- </View>
135
- <View style={styles.textContainer}>
136
- <AtomicText type="bodyLarge" style={{ color: tokens.colors.textPrimary }}>
137
- {translations.remindersTitle}
138
- </AtomicText>
139
- <AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
140
- {translations.remindersDescription}
141
- </AtomicText>
142
- </View>
143
- {reminders.length > 0 && (
144
- <View style={styles.badge}>
145
- <AtomicText type="bodySmall" style={styles.badgeText}>
146
- {reminders.length}
147
- </AtomicText>
148
- </View>
149
- )}
150
- <AtomicIcon name="chevron-forward" size="md" color="secondary" />
151
- </TouchableOpacity>
103
+ <RemindersNavRow
104
+ title={translations.remindersTitle}
105
+ description={translations.remindersDescription}
106
+ count={reminders.length}
107
+ onPress={handleRemindersPress}
108
+ />
152
109
  </AtomicCard>
153
110
 
154
111
  <QuietHoursCard
155
112
  config={quietHours}
156
113
  translations={quietHoursTranslations}
157
114
  onToggle={handleQuietHoursToggle}
158
- onStartTimePress={handleStartTimePress}
159
- onEndTimePress={handleEndTimePress}
115
+ onStartTimePress={timePicker.handleStartTimePress}
116
+ onEndTimePress={timePicker.handleEndTimePress}
160
117
  />
161
118
  </>
162
119
  )}
163
120
  </View>
164
- {pickerMode && (
121
+ {timePicker.pickerMode && (
165
122
  <DateTimePicker
166
- value={getPickerDate()}
123
+ value={timePicker.getPickerDate()}
167
124
  mode="time"
168
125
  is24Hour={true}
169
- onChange={handleTimeChange}
126
+ onChange={timePicker.handleTimeChange}
170
127
  />
171
128
  )}
172
129
  </ScreenLayout>
173
130
  );
174
131
  };
175
-
176
- const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
177
- StyleSheet.create({
178
- container: {
179
- flex: 1,
180
- padding: 16,
181
- },
182
- loadingContainer: {
183
- flex: 1,
184
- justifyContent: 'center',
185
- alignItems: 'center',
186
- },
187
- card: {
188
- marginBottom: 16,
189
- padding: 16,
190
- backgroundColor: tokens.colors.surface,
191
- },
192
- divider: {
193
- height: 1,
194
- backgroundColor: tokens.colors.surfaceSecondary,
195
- marginVertical: 12,
196
- },
197
- navRow: {
198
- flexDirection: 'row',
199
- alignItems: 'center',
200
- },
201
- iconContainer: {
202
- width: 44,
203
- height: 44,
204
- borderRadius: 22,
205
- backgroundColor: tokens.colors.surfaceSecondary,
206
- justifyContent: 'center',
207
- alignItems: 'center',
208
- marginRight: 12,
209
- },
210
- textContainer: {
211
- flex: 1,
212
- marginRight: 12,
213
- },
214
- badge: {
215
- backgroundColor: tokens.colors.primary,
216
- paddingHorizontal: 8,
217
- paddingVertical: 2,
218
- borderRadius: 10,
219
- marginRight: 8,
220
- },
221
- badgeText: {
222
- color: tokens.colors.surface,
223
- fontWeight: '600',
224
- },
225
- });
@@ -6,8 +6,8 @@
6
6
  */
7
7
 
8
8
  import React, { useMemo } from 'react';
9
- import { View, StyleSheet, ActivityIndicator } from 'react-native';
10
- import { AtomicIcon, AtomicCard, AtomicText, ScreenLayout, STATIC_TOKENS } from '@umituz/react-native-design-system';
9
+ import { View, StyleSheet } from 'react-native';
10
+ import { AtomicIcon, AtomicCard, AtomicText, ScreenLayout, STATIC_TOKENS, AtomicSpinner } from '@umituz/react-native-design-system';
11
11
  import { Switch } from 'react-native';
12
12
  import { useAppDesignTokens } from '@umituz/react-native-design-system';
13
13
  import { useNotificationSettings } from '../../infrastructure/hooks/useNotificationSettings';
@@ -37,15 +37,12 @@ export const NotificationsScreen: React.FC<NotificationsScreenProps> = ({
37
37
  if (isLoading) {
38
38
  return (
39
39
  <ScreenLayout testID={testID}>
40
- <View style={styles.loadingContainer}>
41
- <ActivityIndicator size="large" color={tokens.colors.primary} />
42
- <AtomicText
43
- type="bodyMedium"
44
- style={{ color: tokens.colors.textSecondary, marginTop: STATIC_TOKENS.spacing.md }}
45
- >
46
- {translations.loadingText || 'Loading...'}
47
- </AtomicText>
48
- </View>
40
+ <AtomicSpinner
41
+ size="lg"
42
+ color="primary"
43
+ text={translations.loadingText || 'Loading...'}
44
+ fullContainer
45
+ />
49
46
  </ScreenLayout>
50
47
  );
51
48
  }