diff --git a/components/settings/customization/advance-notice-days-slider.js b/components/settings/customization/advance-notice-days-slider.js
new file mode 100644
index 0000000..ece11f0
--- /dev/null
+++ b/components/settings/customization/advance-notice-days-slider.js
@@ -0,0 +1,56 @@
+import React from 'react'
+import { View } from 'react-native'
+import PropTypes from 'prop-types'
+import Slider from '@ptomasroos/react-native-multi-slider'
+import SliderLabel from './slider-label'
+
+import { styles } from './slider-styles'
+import {
+ ADVANCE_PERIOD_NOTICE_DAYS_MIN,
+ ADVANCE_PERIOD_NOTICE_DAYS_MAX,
+} from '../../../config'
+
+const AdvanceNoticeDaysSlider = ({
+ disabled,
+ advanceNoticeDays,
+ onAdvanceNoticeDaysChange,
+}) => {
+ const sliderAccentBackground = disabled
+ ? styles.disabledSliderAccentBackground
+ : styles.sliderAccentBackground
+
+ const sliderBackground = disabled
+ ? styles.disabledSliderBackground
+ : styles.sliderBackground
+
+ return (
+
+
+
+ )
+}
+
+export default AdvanceNoticeDaysSlider
+
+AdvanceNoticeDaysSlider.propTypes = {
+ disabled: PropTypes.bool,
+ advanceNoticeDays: PropTypes.number,
+ onAdvanceNoticeDaysChange: PropTypes.func,
+}
diff --git a/components/settings/customization/slider-styles.js b/components/settings/customization/slider-styles.js
new file mode 100644
index 0000000..e28f31e
--- /dev/null
+++ b/components/settings/customization/slider-styles.js
@@ -0,0 +1,35 @@
+import { StyleSheet } from 'react-native'
+import { Colors, Sizes } from '../../../styles'
+
+export const styles = StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ paddingTop: Sizes.base,
+ },
+ marker: {
+ backgroundColor: Colors.turquoiseDark,
+
+ borderRadius: 50,
+ elevation: 4,
+ height: Sizes.subtitle,
+ width: Sizes.subtitle,
+ },
+ slider: {
+ borderRadius: 25,
+ height: Sizes.small,
+ paddingTop: Sizes.base,
+ },
+ sliderAccentBackground: {
+ backgroundColor: Colors.turquoiseDark,
+ },
+ disabledSliderAccentBackground: {
+ backgroundColor: Colors.grey,
+ },
+ sliderBackground: {
+ backgroundColor: Colors.turquoise,
+ },
+ disabledSliderBackground: {
+ backgroundColor: Colors.greyLight,
+ },
+ markerOffsetY: Sizes.tiny,
+})
diff --git a/components/settings/customization/temperature-slider.js b/components/settings/customization/temperature-slider.js
index 7d91f7a..463b001 100644
--- a/components/settings/customization/temperature-slider.js
+++ b/components/settings/customization/temperature-slider.js
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { StyleSheet, View } from 'react-native'
+import { View } from 'react-native'
import PropTypes from 'prop-types'
import Slider from '@ptomasroos/react-native-multi-slider'
@@ -7,9 +7,9 @@ import alertError from '../common/alert-error'
import SliderLabel from './slider-label'
import { scaleObservable, saveTempScale } from '../../../local-storage'
-import { Colors, Sizes } from '../../../styles'
import labels from '../../../i18n/en/settings'
import { TEMP_MIN, TEMP_MAX, TEMP_SLIDER_STEP } from '../../../config'
+import { styles } from './slider-styles'
const TemperatureSlider = ({ disabled }) => {
const savedValue = scaleObservable.value
@@ -40,7 +40,7 @@ const TemperatureSlider = ({ disabled }) => {
customLabel={SliderLabel}
enableLabel={true}
markerStyle={styles.marker}
- markerOffsetY={Sizes.tiny}
+ markerOffsetY={styles.markerOffsetY}
max={TEMP_MAX}
min={TEMP_MIN}
onValuesChange={onTemperatureSliderChange}
@@ -61,34 +61,3 @@ export default TemperatureSlider
TemperatureSlider.propTypes = {
disabled: PropTypes.bool,
}
-
-const styles = StyleSheet.create({
- container: {
- alignItems: 'center',
- paddingTop: Sizes.base,
- },
- marker: {
- backgroundColor: Colors.turquoiseDark,
-
- borderRadius: 50,
- elevation: 4,
- height: Sizes.subtitle,
- width: Sizes.subtitle,
- },
- slider: {
- borderRadius: 25,
- height: Sizes.small,
- },
- sliderAccentBackground: {
- backgroundColor: Colors.turquoiseDark,
- },
- disabledSliderAccentBackground: {
- backgroundColor: Colors.grey,
- },
- sliderBackground: {
- backgroundColor: Colors.turquoise,
- },
- disabledSliderBackground: {
- backgroundColor: Colors.greyLight,
- },
-})
diff --git a/components/settings/reminders/period-reminder.js b/components/settings/reminders/period-reminder.js
new file mode 100644
index 0000000..b0bf81f
--- /dev/null
+++ b/components/settings/reminders/period-reminder.js
@@ -0,0 +1,54 @@
+import React, { useState } from 'react'
+import AppSwitch from '../../common/app-switch'
+import AdvanceNoticeDaysSlider from '../customization/advance-notice-days-slider'
+
+import {
+ periodReminderObservable,
+ savePeriodReminder,
+ periodPredictionObservable,
+ saveAdvanceNoticeDays,
+ advanceNoticeDaysObservable,
+} from '../../../local-storage'
+import labels from '../../../i18n/en/settings'
+
+const PeriodReminder = () => {
+ const isPeriodPredictionDisabled = !periodPredictionObservable.value
+
+ const [isPeriodReminderEnabled, setIsPeriodReminderEnabled] = useState(
+ periodReminderObservable.value.enabled
+ )
+
+ const [advanceNoticeDays, setAdvanceNoticeDays] = useState(
+ advanceNoticeDaysObservable.value
+ )
+
+ const periodReminderToggle = (isEnabled) => {
+ setIsPeriodReminderEnabled(isEnabled)
+ savePeriodReminder({ enabled: isEnabled })
+ }
+
+ const handleAdvanceNoticeDaysChange = (days) => {
+ setAdvanceNoticeDays(days)
+ saveAdvanceNoticeDays(days)
+ }
+
+ return (
+ <>
+
+ {isPeriodReminderEnabled && (
+
+ )}
+ >
+ )
+}
+
+export default PeriodReminder
diff --git a/components/settings/reminders/reminders.js b/components/settings/reminders/reminders.js
index 1279d29..63f2cbb 100644
--- a/components/settings/reminders/reminders.js
+++ b/components/settings/reminders/reminders.js
@@ -1,13 +1,11 @@
-import React, { useState } from 'react'
+import React from 'react'
import AppPage from '../../common/app-page'
-import AppSwitch from '../../common/app-switch'
import Segment from '../../common/segment'
import TemperatureReminder from './temperature-reminder'
+import PeriodReminder from './period-reminder'
import {
- periodReminderObservable,
- savePeriodReminder,
periodPredictionObservable,
temperatureTrackingCategoryObservable,
} from '../../../local-storage'
@@ -16,17 +14,7 @@ import labels from '../../../i18n/en/settings'
import { Alert, Pressable } from 'react-native'
const Reminders = () => {
- const isPeriodPredictionDisabled = !periodPredictionObservable.value
-
- const [isPeriodReminderEnabled, setIsPeriodReminderEnabled] = useState(
- periodReminderObservable.value.enabled
- )
- const periodReminderToggle = (isEnabled) => {
- setIsPeriodReminderEnabled(isEnabled)
- savePeriodReminder({ enabled: isEnabled })
- }
-
- const reminderDisabledPrompt = () => {
+ const periodReminderDisabledPrompt = () => {
if (!periodPredictionObservable.value) {
Alert.alert(
labels.periodReminder.alertNoPeriodReminder.title,
@@ -43,16 +31,12 @@ const Reminders = () => {
)
}
}
+
return (
-
+
-
+
diff --git a/config.js b/config.js
index 5d11e5d..fa854f6 100644
--- a/config.js
+++ b/config.js
@@ -33,6 +33,10 @@ export const TEMP_MAX = 39
export const TEMP_MIN = 35
export const TEMP_SLIDER_STEP = 0.5
+export const ADVANCE_PERIOD_NOTICE_DAYS_MIN = 1
+export const ADVANCE_PERIOD_NOTICE_DAYS_MAX = 7
+export const ADVANCE_PERIOD_NOTICE_DAYS_INIT_VALUE = 3
+
export const HIT_SLOP = {
top: verticalScale(20),
bottom: verticalScale(20),
diff --git a/i18n/en/settings.js b/i18n/en/settings.js
index 7f00368..7c09c14 100644
--- a/i18n/en/settings.js
+++ b/i18n/en/settings.js
@@ -60,10 +60,13 @@ export default {
},
periodReminder: {
title: 'Next period reminder',
- reminderText:
- 'Get a notification 3 days before your next period is likely to start.',
- notification: (daysToEndOfPrediction) =>
- `Your next period is likely to start in 3 to ${daysToEndOfPrediction} days.`,
+ reminderText: (days) => {
+ const dayCount = parseInt(days, 10)
+ const dayText = dayCount === 1 ? '1 day' : `${dayCount} days`
+ return `Get a notification ${dayText} before your next period is likely to start.`
+ },
+ notification: (advanceNoticeDays, daysToEndOfPrediction) =>
+ `Your next period is likely to start in ${advanceNoticeDays} to ${daysToEndOfPrediction} days.`,
alertNoPeriodReminder: {
title: 'Period predictions turned off',
message:
diff --git a/lib/notifications.js b/lib/notifications.js
index d34d752..566ac0d 100644
--- a/lib/notifications.js
+++ b/lib/notifications.js
@@ -2,9 +2,9 @@ import { Platform } from 'react-native'
import {
tempReminderObservable,
periodReminderObservable,
+ advanceNoticeDaysObservable,
} from '../local-storage'
import * as PN from 'react-native-push-notification'
-import { requestNotifications } from 'react-native-permissions'
import Moment from 'moment'
import { LocalDate } from '@js-joda/core'
@@ -13,12 +13,14 @@ import { getBleedingDaysSortedByDate } from '../db'
import cycleModule from './cycle'
import nothingChanged from '../db/db-unchanged'
-export default function setupNotifications(navigate, setDate) {
- Platform.OS === 'android' ? requestNotifications() : null
- const PushNotification = Platform.OS === 'ios' ? PN : PN.default
+const DRIP_CHANNEL_ID = 'drip-channel-id'
+const TEMPERATURE_REMINDER_ID = '1'
+const PERIOD_REMINDER_ID = '2'
+const PushNotification = Platform.OS === 'ios' ? PN : PN.default
+export default function setupNotifications(navigate, setDate) {
PushNotification.createChannel({
- channelId: 'drip-channel-id', // (required)
+ channelId: DRIP_CHANNEL_ID, // (required)
channelName: 'drip reminder', // (required)
playSound: false, // (optional) default: true
})
@@ -26,7 +28,10 @@ export default function setupNotifications(navigate, setDate) {
PushNotification.configure({
onNotification: (notification) => {
// https://github.com/zo0r/react-native-push-notification/issues/966#issuecomment-479069106
- if (notification.data?.id === '1' || notification.id === '1') {
+ if (
+ notification.data?.id === TEMPERATURE_REMINDER_ID ||
+ notification.id === TEMPERATURE_REMINDER_ID
+ ) {
const todayDate = LocalDate.now().toString()
setDate(todayDate)
navigate('TemperatureEditView')
@@ -37,7 +42,7 @@ export default function setupNotifications(navigate, setDate) {
})
tempReminderObservable((reminder) => {
- PushNotification.cancelLocalNotification({ id: '1' })
+ PushNotification.cancelLocalNotification({ id: TEMPERATURE_REMINDER_ID })
if (reminder.enabled) {
const [hours, minutes] = reminder.time.split(':')
let target = new Moment()
@@ -50,57 +55,74 @@ export default function setupNotifications(navigate, setDate) {
}
PushNotification.localNotificationSchedule({
- id: '1',
- userInfo: { id: '1' },
+ id: TEMPERATURE_REMINDER_ID,
+ userInfo: { id: TEMPERATURE_REMINDER_ID },
message: labels.tempReminder.notification,
date: target.toDate(),
vibrate: false,
repeatType: 'day',
- channelId: 'drip-channel-id',
+ channelId: DRIP_CHANNEL_ID,
allowWhileIdle: true,
})
}
}, false)
- periodReminderObservable((reminder) => {
- PushNotification.cancelLocalNotification({ id: '2' })
- if (reminder.enabled) setupPeriodReminder()
- }, false)
+ periodReminderObservable(() => updatePeriodNotification(), false)
+ advanceNoticeDaysObservable(() => updatePeriodNotification(), false)
getBleedingDaysSortedByDate().addListener((_, changes) => {
// the listener fires on setup, so we check if there were actually any changes
- if (nothingChanged(changes)) return
- PushNotification.cancelLocalNotification({ id: '2' })
- if (periodReminderObservable.value.enabled) setupPeriodReminder()
+ if (nothingChanged(changes)) {
+ return
+ }
+
+ updatePeriodNotification()
})
}
-function setupPeriodReminder() {
- const PushNotification = Platform.OS === 'ios' ? PN : PN.default
+const updatePeriodNotification = () => {
+ // Cancel any existing period reminder
+ PushNotification.cancelLocalNotification({ id: PERIOD_REMINDER_ID })
+
+ // Set up a new period reminder if enabled
+ if (periodReminderObservable.value.enabled) {
+ schedulePeriodNotification()
+ }
+}
+
+function schedulePeriodNotification() {
const bleedingPrediction = cycleModule().getPredictedMenses()
+
if (bleedingPrediction.length > 0) {
const predictedBleedingStart = Moment(
bleedingPrediction[0][0],
'YYYY-MM-DD'
)
- // 3 days before and at 6 am
+
+ const advanceNoticeDays = parseInt(advanceNoticeDaysObservable.value, 10)
+
+ // ${advanceNoticeDays} days before and at 6 am
const reminderDate = predictedBleedingStart
- .subtract(3, 'days')
+ .subtract(advanceNoticeDays, 'days')
.hours(6)
.minutes(0)
.seconds(0)
if (reminderDate.isAfter()) {
- // period is likely to start in 3 to 3 + (length of prediction - 1) days
- const daysToEndOfPrediction = bleedingPrediction[0].length + 2
+ // period is likely to start in advanceNoticeDays to advanceNoticeDays + (length of prediction - 1) days
+ const daysToEndOfPrediction =
+ advanceNoticeDays + bleedingPrediction[0].length - 1
PushNotification.localNotificationSchedule({
- id: '2',
- userInfo: { id: '2' },
- message: labels.periodReminder.notification(daysToEndOfPrediction),
+ id: PERIOD_REMINDER_ID,
+ userInfo: { id: PERIOD_REMINDER_ID },
+ message: labels.periodReminder.notification(
+ advanceNoticeDays,
+ daysToEndOfPrediction
+ ),
date: reminderDate.toDate(),
vibrate: false,
- channelId: 'drip-channel-id',
+ channelId: DRIP_CHANNEL_ID,
allowWhileIdle: true,
})
}
diff --git a/local-storage.js b/local-storage.js
index 66ce305..63bd55a 100644
--- a/local-storage.js
+++ b/local-storage.js
@@ -2,6 +2,8 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
import Observable from 'obv'
import { TEMP_SCALE_MIN, TEMP_SCALE_MAX, TEMP_SCALE_UNITS } from './config'
+import { ADVANCE_PERIOD_NOTICE_DAYS_INIT_VALUE } from './config'
+
export const scaleObservable = Observable()
setObvWithInitValue('tempScale', scaleObservable, {
min: TEMP_SCALE_MIN,
@@ -59,6 +61,18 @@ export async function savePeriodPrediction(bool) {
}
}
+export const advanceNoticeDaysObservable = Observable()
+setObvWithInitValue(
+ 'advanceNoticeDays',
+ advanceNoticeDaysObservable,
+ parseInt(ADVANCE_PERIOD_NOTICE_DAYS_INIT_VALUE, 10)
+)
+
+export async function saveAdvanceNoticeDays(days) {
+ await AsyncStorage.setItem('advanceNoticeDays', JSON.stringify(days))
+ advanceNoticeDaysObservable.set(days)
+}
+
export const useCervixAsSecondarySymptomObservable = Observable()
setObvWithInitValue(
'useCervixAsSecondarySymptom',
@@ -109,7 +123,7 @@ export async function saveTemperatureTrackingCategory(bool) {
if (!temperatureTrackingCategoryObservable.value) {
// if temperature tracking is turned off, the temperature reminder gets disabled
const tempReminderResult = await AsyncStorage.getItem('tempReminder')
- if (tempReminderResult && JSON.parse(tempReminderResult).enabled) {
+ if (tempReminderResult && JSON.parse(tempReminderResult).enabled) {
tempReminderObservable.set(false)
}
}