Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"pushDesc": "Receive notifications directly on this device.",
"telegram": "Telegram",
"telegramDesc": "Receive automated reminders via the MyTaskly Telegram bot.",
"weeklySummary": "Weekly Summary",
"weeklySummaryDesc": "Receive a weekly summary of your tasks.",
"timezone": "Timezone",
"timezoneDesc": "Reminders are sent according to this timezone. Keep it in sync with your device.",
"info": "Information"
Expand All @@ -76,6 +78,21 @@
"title": "Reminder advance notice",
"desc": "How many minutes before the task deadline should the reminder be sent."
},
"weeklySummary": {
"enable": "Enable summary",
"nextScheduled": "Next: {{date}} at {{time}}",
"day": "Day of week",
"hour": "Time",
"days": {
"0": "Sun",
"1": "Mon",
"2": "Tue",
"3": "Wed",
"4": "Thu",
"5": "Fri",
"6": "Sat"
}
},
"timezone": {
"server": "Server",
"device": "This device",
Expand Down
17 changes: 17 additions & 0 deletions src/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"pushDesc": "Ricevi notifiche direttamente su questo dispositivo.",
"telegram": "Telegram",
"telegramDesc": "Ricevi promemoria automatici tramite il bot Telegram di MyTaskly.",
"weeklySummary": "Riepilogo Settimanale",
"weeklySummaryDesc": "Ricevi un riepilogo dei tuoi task ogni settimana.",
"timezone": "Fuso Orario",
"timezoneDesc": "I promemoria vengono inviati in base a questo fuso orario. Mantienilo sincronizzato con il tuo dispositivo.",
"info": "Informazioni"
Expand All @@ -77,6 +79,21 @@
"title": "Anticipo promemoria",
"desc": "Quanti minuti prima della scadenza del task deve essere inviato il promemoria."
},
"weeklySummary": {
"enable": "Abilita riepilogo",
"nextScheduled": "Prossimo: {{date}} alle {{time}}",
"day": "Giorno della settimana",
"hour": "Orario",
"days": {
"0": "Dom",
"1": "Lun",
"2": "Mar",
"3": "Mer",
"4": "Gio",
"5": "Ven",
"6": "Sab"
}
},
"timezone": {
"server": "Server",
"device": "Questo dispositivo",
Expand Down
221 changes: 220 additions & 1 deletion src/navigation/screens/NotificationSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,29 @@ import {
NotificationSettings,
TELEGRAM_REMINDER_OPTIONS,
} from '../../services/notificationSettingsService';
import {
getWeeklySummarySettings,
updateWeeklySummarySettings,
WeeklySummarySettings,
} from '../../services/weeklySummaryService';
import {
registerForPushNotificationsAsync,
sendTokenToBackend,
} from '../../services/notificationService';

// Costanti per orari
const HOURS = Array.from({ length: 24 }, (_, i) => ({
value: i,
label: `${i.toString().padStart(2, '0')}:00`,
}));

// Costanti per giorni della settimana (valori 0-6)
const WEEK_DAYS = [0, 1, 2, 3, 4, 5, 6];

export default function NotificationSettingsScreen() {
const { t } = useTranslation();
const [settings, setSettings] = useState<NotificationSettings | null>(null);
const [weeklySummarySettings, setWeeklySummarySettings] = useState<WeeklySummarySettings | null>(null);
const [loading, setLoading] = useState(true);
const [syncing, setSyncing] = useState(false);
const [syncDone, setSyncDone] = useState(false);
Expand Down Expand Up @@ -73,8 +88,12 @@ export default function NotificationSettingsScreen() {
const loadSettings = useCallback(async () => {
try {
setLoading(true);
const data = await getNotificationSettings();
const [data, weeklyData] = await Promise.all([
getNotificationSettings(),
getWeeklySummarySettings(),
]);
setSettings(data);
setWeeklySummarySettings(weeklyData);
} catch (error) {
console.error('[NotificationSettings] Errore caricamento:', error);
Alert.alert(
Expand Down Expand Up @@ -164,6 +183,64 @@ export default function NotificationSettingsScreen() {
}
};

// Funzioni per riepilogo settimanale
const handleWeeklySummaryToggle = async (enabled: boolean) => {
if (!weeklySummarySettings) return;

const previous = weeklySummarySettings.enabled;
setWeeklySummarySettings((prev) => prev ? { ...prev, enabled } : prev);
setSavingField('weekly_enabled');

try {
const updated = await updateWeeklySummarySettings({ enabled });
setWeeklySummarySettings(updated);
} catch (error: any) {
setWeeklySummarySettings((prev) => prev ? { ...prev, enabled: previous } : prev);
const detail = error?.response?.data?.detail ?? t('notificationSettings.error.saveFailed');
Alert.alert(t('notificationSettings.error.title'), detail);
} finally {
setSavingField(null);
}
};

const handleWeeklySummaryDay = async (day: number) => {
if (!weeklySummarySettings || weeklySummarySettings.day === day) return;

const previous = weeklySummarySettings.day;
setWeeklySummarySettings((prev) => prev ? { ...prev, day } : prev);
setSavingField('weekly_day');

try {
const updated = await updateWeeklySummarySettings({ day });
setWeeklySummarySettings(updated);
} catch (error: any) {
setWeeklySummarySettings((prev) => prev ? { ...prev, day: previous } : prev);
const detail = error?.response?.data?.detail ?? t('notificationSettings.error.saveFailed');
Alert.alert(t('notificationSettings.error.title'), detail);
} finally {
setSavingField(null);
}
};

const handleWeeklySummaryHour = async (hour: number) => {
if (!weeklySummarySettings || weeklySummarySettings.hour === hour) return;

const previous = weeklySummarySettings.hour;
setWeeklySummarySettings((prev) => prev ? { ...prev, hour } : prev);
setSavingField('weekly_hour');

try {
const updated = await updateWeeklySummarySettings({ hour });
setWeeklySummarySettings(updated);
} catch (error: any) {
setWeeklySummarySettings((prev) => prev ? { ...prev, hour: previous } : prev);
const detail = error?.response?.data?.detail ?? t('notificationSettings.error.saveFailed');
Alert.alert(t('notificationSettings.error.title'), detail);
} finally {
setSavingField(null);
}
};

if (loading) {
return (
<SafeAreaView style={styles.container}>
Expand Down Expand Up @@ -346,6 +423,101 @@ export default function NotificationSettingsScreen() {
</Text>
</TouchableOpacity>

{/* ───────────────── WEEKLY SUMMARY ───────────────── */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.weeklySummary')}</Text>
<Text style={styles.sectionDescription}>
{t('notificationSettings.sections.weeklySummaryDesc')}
</Text>
</View>

{/* Toggle abilitazione */}
<View style={styles.row}>
<View style={styles.rowLeft}>
<Ionicons name="calendar-outline" size={22} color="#000000" />
<View style={styles.rowTextWrap}>
<Text style={styles.rowLabel}>{t('notificationSettings.weeklySummary.enable')}</Text>
{weeklySummarySettings?.enabled && weeklySummarySettings.next_scheduled_date && (
<Text style={styles.rowHint}>
{t('notificationSettings.weeklySummary.nextScheduled', {
date: new Date(weeklySummarySettings.next_scheduled_date).toLocaleDateString('it-IT', {
weekday: 'long',
day: 'numeric',
month: 'short',
}),
time: `${weeklySummarySettings.hour}:00`
})}
</Text>
)}
</View>
</View>
<Switch
value={weeklySummarySettings?.enabled ?? false}
onValueChange={handleWeeklySummaryToggle}
disabled={savingField === 'weekly_enabled'}
trackColor={{ false: '#dee2e6', true: '#000000' }}
thumbColor="#ffffff"
/>
</View>

{/* Selettore giorno - visibile solo se abilitato */}
{weeklySummarySettings?.enabled && (
<>
<View style={styles.subSectionHeader}>
<Text style={styles.subSectionTitle}>{t('notificationSettings.weeklySummary.day')}</Text>
</View>

<View style={styles.pillsRow}>
{WEEK_DAYS.map((day) => {
const isSelected = weeklySummarySettings.day === day;
const isSaving = savingField === 'weekly_day';
return (
<TouchableOpacity
key={day}
style={[styles.pill, isSelected && styles.pillSelected]}
onPress={() => handleWeeklySummaryDay(day)}
disabled={isSaving}
activeOpacity={0.7}
>
<Text style={[styles.pillText, isSelected && styles.pillTextSelected]}>
{t(`notificationSettings.weeklySummary.days.${day}`)}
</Text>
</TouchableOpacity>
);
})}
</View>

<View style={styles.subSectionHeader}>
<Text style={styles.subSectionTitle}>{t('notificationSettings.weeklySummary.hour')}</Text>
</View>

<ScrollView
style={styles.hoursScroll}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.hoursScrollContent}
>
{HOURS.map((hour) => {
const isSelected = weeklySummarySettings.hour === hour.value;
const isSaving = savingField === 'weekly_hour';
return (
<TouchableOpacity
key={hour.value}
style={[styles.hourPill, isSelected && styles.hourPillSelected]}
onPress={() => handleWeeklySummaryHour(hour.value)}
disabled={isSaving}
activeOpacity={0.7}
>
<Text style={[styles.hourPillText, isSelected && styles.hourPillTextSelected]}>
{hour.label}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
</>
)}

{/* ───────────────── INFO ───────────────── */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{t('notificationSettings.sections.info')}</Text>
Expand Down Expand Up @@ -492,6 +664,53 @@ const styles = StyleSheet.create({
color: '#ffffff',
fontWeight: '600',
},
// Sottosezioni
subSectionHeader: {
paddingHorizontal: 20,
paddingTop: 16,
paddingBottom: 8,
backgroundColor: '#ffffff',
},
subSectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#000000',
fontFamily: 'System',
},
// Hours scroll
hoursScroll: {
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
hoursScrollContent: {
paddingHorizontal: 16,
paddingVertical: 16,
gap: 8,
},
hourPill: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 16,
borderWidth: 1.5,
borderColor: '#dee2e6',
backgroundColor: '#ffffff',
minWidth: 60,
alignItems: 'center',
},
hourPillSelected: {
backgroundColor: '#000000',
borderColor: '#000000',
},
hourPillText: {
fontSize: 14,
color: '#495057',
fontWeight: '500',
fontFamily: 'System',
},
hourPillTextSelected: {
color: '#ffffff',
fontWeight: '600',
},
// Timezone
timezoneRow: {
flexDirection: 'row',
Expand Down
27 changes: 27 additions & 0 deletions src/services/weeklySummaryService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import axiosInstance from './axiosInstance';

export interface WeeklySummarySettings {
enabled: boolean;
day: number; // 0-6, dove 0 = Domenica
hour: number; // 0-23
last_sent_date?: string;
next_scheduled_date?: string;
}

/**
* Ottiene le impostazioni correnti del riepilogo settimanale
*/
export async function getWeeklySummarySettings(): Promise<WeeklySummarySettings> {
const response = await axiosInstance.get('/notifications/weekly-summary-settings');
return response.data;
}

/**
* Aggiorna le impostazioni del riepilogo settimanale
*/
export async function updateWeeklySummarySettings(
settings: Partial<WeeklySummarySettings>
): Promise<WeeklySummarySettings> {
const response = await axiosInstance.put('/notifications/weekly-summary-settings', settings);
return response.data;
}
Loading