diff --git a/android/app/build.gradle b/android/app/build.gradle index fa56b475b947fe38815dcf124054886df40ae966..6348b76bbed3ac2a079044c72c63b4ab7d68dc49 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -138,6 +138,7 @@ android { } dependencies { + compile project(':react-native-push-notification') compile project(':react-native-vector-icons') compile project(':react-native-fs') compile project(':react-native-document-picker') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4654ff4cc640a1d3c8ea52fa96976c184aed1060..207d90aa0bc07961fc8de65cd883b8580827c910 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,15 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + + <permission + android:name="${applicationId}.permission.C2D_MESSAGE" + android:protectionLevel="signature" /> + + <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> + <application android:name=".MainApplication" @@ -30,6 +39,46 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> + <meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_name" + android:value="drip-notification"/> + <meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_description" + android:value="notifications from drip"/> + <!-- Change the resource name to your App's accent color - or any other color you want --> + <meta-data android:name="com.dieam.reactnativepushnotification.notification_color" + android:resource="@android:color/white"/> + + <!-- < Only if you're using GCM or localNotificationSchedule() > --> + <receiver + android:name="com.google.android.gms.gcm.GcmReceiver" + android:exported="true" + android:permission="com.google.android.c2dm.permission.SEND" > + <intent-filter> + <action android:name="com.google.android.c2dm.intent.RECEIVE" /> + <category android:name="${applicationId}" /> + </intent-filter> + </receiver> + <!-- < Only if you're using GCM or localNotificationSchedule() > --> + + <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" /> + <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + </receiver> + <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/> + <service + android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService" + android:exported="false" > + <intent-filter> + <!-- < Only if you're using GCM or localNotificationSchedule() > --> + <action android:name="com.google.android.c2dm.intent.RECEIVE" /> + <!-- < Only if you're using GCM or localNotificationSchedule() > --> + + <!-- <Else> --> + <action android:name="com.google.firebase.MESSAGING_EVENT" /> + <!-- </Else> --> + </intent-filter> + </service> </application> diff --git a/android/app/src/main/java/com/drip/MainApplication.java b/android/app/src/main/java/com/drip/MainApplication.java index 4dc63a4e890ef14381f722b9a044c3114945f809..da1f79f0f32e9cadcd14ebc8a84617d867c3333e 100644 --- a/android/app/src/main/java/com/drip/MainApplication.java +++ b/android/app/src/main/java/com/drip/MainApplication.java @@ -3,6 +3,7 @@ package com.drip; import android.app.Application; import com.facebook.react.ReactApplication; +import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage; import com.oblador.vectoricons.VectorIconsPackage; import com.rnfs.RNFSPackage; import com.reactnativedocumentpicker.ReactNativeDocumentPicker; @@ -29,6 +30,7 @@ public class MainApplication extends Application implements ReactApplication, Sh protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), + new ReactNativePushNotificationPackage(), new VectorIconsPackage(), new RNFSPackage(), new ReactNativeDocumentPicker(), diff --git a/android/app/src/main/res/drawable-hdpi/ic_notification.png b/android/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..58405822bf648ee118a2416345c123b3fcd13c61 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_notification.png b/android/app/src/main/res/drawable-mdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..003156a561f7fb781c3a8e95d310147e5ca59505 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/android/app/src/main/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..1bdb4bd53e9e6b94bf8daf92b194331d50f6c9d0 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..9af70b030606f70a4d6666dd5434aa2d998ea239 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..45e66248aff96311c3ea623827fd3a1dd88669be Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index a2f5908281d070150700378b64a84c7db1f97aa1..bb93aa95047306fb661ac1066d8f15904844d961 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 1b523998081149a985cef0cdf89045b9ed29964a..0000000000000000000000000000000000000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_notification.png b/android/app/src/main/res/mipmap-hdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..58405822bf648ee118a2416345c123b3fcd13c61 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index ff10afd6e182edb2b1a63c8f984e9070d9f950ba..d221c4512e644c01bbe9649ef572f616b0943841 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 115a4c768a20c9e13185c17043f4c4d12dd4632a..0000000000000000000000000000000000000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_notification.png b/android/app/src/main/res/mipmap-mdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..003156a561f7fb781c3a8e95d310147e5ca59505 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index dcd3cd8083358269d6ed7894726283bb9bcbbfea..3be181b8b754e8eae778ed8cf0621c5553f57090 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca609d3ae0d3943ab44cdc27feef9256dc6d7..0000000000000000000000000000000000000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_notification.png b/android/app/src/main/res/mipmap-xhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..1bdb4bd53e9e6b94bf8daf92b194331d50f6c9d0 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 8ca12fe024be86e868d14e91120a6902f8e88ac6..05f21c421d2e2660ea24dfa641b8fc1e110360d2 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b410a1b15ff180f3dacac19395fe3046cdec..0000000000000000000000000000000000000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_notification.png b/android/app/src/main/res/mipmap-xxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..9af70b030606f70a4d6666dd5434aa2d998ea239 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_notification.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b824ebdd48db917eea2e67a82260a100371f8a24..3a12ebeb81c22802d2df7308408d3a63ba526c8b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13c239cb67b8a2134ddd5f325db1d2d5bee..0000000000000000000000000000000000000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_notification.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..45e66248aff96311c3ea623827fd3a1dd88669be Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_notification.png differ diff --git a/android/settings.gradle b/android/settings.gradle index 6a9c8c84200f3d0b63b9697454df468f2d5e1484..fe009fa9e1231ffd0c4187396c2d24a4a417a9e0 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'drip' +include ':react-native-push-notification' +project(':react-native-push-notification').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-push-notification/android') include ':react-native-vector-icons' project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') include ':react-native-fs' diff --git a/components/app.js b/components/app.js index b0924aa0b89b1baac2b7258e70fae8f431bb0c36..827b11e390b9eecdc7fb91bd1a2f7352470243de 100644 --- a/components/app.js +++ b/components/app.js @@ -10,6 +10,7 @@ import Chart from './chart/chart' import Settings from './settings' import Stats from './stats' import {headerTitles as titles} from './labels' +import setupNotifications from '../lib/notifications' const isSymptomView = name => Object.keys(symptomViews).indexOf(name) > -1 @@ -19,28 +20,28 @@ export default class App extends Component { this.state = { currentPage: 'Home' } - - const handleBackButtonPress = function() { - if (this.state.currentPage === 'Home') return false - if (isSymptomView(this.state.currentPage)) { - this.navigate('CycleDay', {cycleDay: this.state.currentProps.cycleDay}) - } else { - this.navigate('Home') - } - return true - }.bind(this) - - this.backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackButtonPress) + this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonPress) + setupNotifications(this.navigate) } componentWillUnmount() { this.backHandler.remove() } - navigate(pageName, props) { + navigate = (pageName, props) => { this.setState({currentPage: pageName, currentProps: props}) } + handleBackButtonPress = () => { + if (this.state.currentPage === 'Home') return false + if (isSymptomView(this.state.currentPage)) { + this.navigate('CycleDay', { cycleDay: this.state.currentProps.cycleDay }) + } else { + this.navigate('Home') + } + return true + } + render() { const page = { Home, Calendar, CycleDay, Chart, Settings, Stats, ...symptomViews @@ -51,14 +52,14 @@ export default class App extends Component { {this.state.currentPage != 'CycleDay' && <Header title={titles[this.state.currentPage]} />} {React.createElement(page, { - navigate: this.navigate.bind(this), + navigate: this.navigate, ...this.state.currentProps })} {!isSymptomView(this.state.currentPage) && - <Menu navigate={this.navigate.bind(this)} /> + <Menu navigate={this.navigate} /> } </View> ) } -} \ No newline at end of file +} diff --git a/components/calendar.js b/components/calendar.js index 72fb1babf89c61d4f213a53f7e0723eb00f5ea3f..fd85d3ab92bad98fdaf484883da96de2443f45ac 100644 --- a/components/calendar.js +++ b/components/calendar.js @@ -25,7 +25,7 @@ export default class CalendarView extends Component { bleedingDaysSortedByDate.removeListener(this.setStateWithCalFormattedDays) } - passDateToDayView(result) { + passDateToDayView = (result) => { const cycleDay = getOrCreateCycleDay(result.dateString) const navigate = this.props.navigate navigate('CycleDay', { cycleDay }) @@ -34,7 +34,7 @@ export default class CalendarView extends Component { render() { return ( <CalendarList - onDayPress={this.passDateToDayView.bind(this)} + onDayPress={this.passDateToDayView} markedDates={this.state.bleedingDaysInCalFormat} markingType={'period'} /> diff --git a/components/cycle-day/cycle-day-overview.js b/components/cycle-day/cycle-day-overview.js index d94453eb358959034ff7b482a213878db22fc93d..a75dda34a2aac17ae0340b03cd70fea5bd1b7eb3 100644 --- a/components/cycle-day/cycle-day-overview.js +++ b/components/cycle-day/cycle-day-overview.js @@ -33,7 +33,7 @@ export default class CycleDayOverView extends Component { } } - goToCycleDay(target) { + goToCycleDay = (target) => { const localDate = LocalDate.parse(this.state.cycleDay.date) const targetDate = target === 'before' ? localDate.minusDays(1).toString() : @@ -57,7 +57,7 @@ export default class CycleDayOverView extends Component { isCycleDayOverView={true} cycleDayNumber={cycleDayNumber} date={cycleDay.date} - goToCycleDay={this.goToCycleDay.bind(this)} + goToCycleDay={this.goToCycleDay} /> <ScrollView> <View style={styles.symptomBoxesView}> diff --git a/components/labels.js b/components/labels.js index 7608b62b28d34702a3878d28f1d884f7bbb77741..b398a4f418046e6e9fa294704bbb21726ff8d12a 100644 --- a/components/labels.js +++ b/components/labels.js @@ -42,6 +42,12 @@ export const settings = { max: 'Max', loadError: 'Could not load saved temperature scale settings', saveError: 'Could not save temperature scale settings' + }, + tempReminder: { + title: 'Temperature reminder', + noTimeSet: 'Set a time for a daily reminder to take your temperature', + timeSet: time => `Daily reminder set for ${time}`, + notification: 'Record your morning temperature' } } diff --git a/components/menu.js b/components/menu.js index 8a5518774db8bfb2e15ba652c5af17ab2779ac1c..737203baf8c94e29834457cac0a7489f763408a7 100644 --- a/components/menu.js +++ b/components/menu.js @@ -8,7 +8,7 @@ import styles, { iconStyles } from '../styles' import Icon from 'react-native-vector-icons/MaterialCommunityIcons' export default class Menu extends Component { - makeMenuItem({ title, icon, onPress}, i) { + makeMenuItem = ({ title, icon, onPress}, i) => { return ( <TouchableOpacity onPress={onPress} @@ -36,7 +36,7 @@ export default class Menu extends Component { { title: 'Chart', icon: 'chart-line', onPress: () => this.goTo('Chart') }, { title: 'Stats', icon: 'chart-pie', onPress: () => this.goTo('Stats') }, { title: 'Settings', icon: 'settings', onPress: () => this.goTo('Settings') }, - ].map(this.makeMenuItem.bind(this))} + ].map(this.makeMenuItem)} </View > ) } diff --git a/components/settings.js b/components/settings.js index f03aec91dc3dda283582677aad1b200a47b5aeae..aac1a65945bf6d5a91da9afe49c4b03bdca83fd3 100644 --- a/components/settings.js +++ b/components/settings.js @@ -4,53 +4,66 @@ import { TouchableOpacity, ScrollView, Alert, - Text + Text, + Switch } from 'react-native' +import DateTimePicker from 'react-native-modal-datetime-picker-nevo' import Slider from '@ptomasroos/react-native-multi-slider' import Share from 'react-native-share' import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker' import rnfs from 'react-native-fs' import styles, { secondaryColor } from '../styles/index' import config from '../config' -import { settings as settingsLabels, shared as sharedLabels } from './labels' +import { settings as labels, shared as sharedLabels } from './labels' import getDataAsCsvDataUri from '../lib/import-export/export-to-csv' import importCsv from '../lib/import-export/import-from-csv' -import { scaleObservable, saveTempScale } from '../local-storage' +import { + scaleObservable, + saveTempScale, + tempReminderObservable, + saveTempReminder +} from '../local-storage' export default class Settings extends Component { + constructor(props) { + super(props) + this.state = {} + } + render() { return ( <ScrollView> + <TempReminderPicker/> <View style={styles.settingsSegment}> <Text style={styles.settingsSegmentTitle}> - {settingsLabels.tempScale.segmentTitle} + {labels.tempScale.segmentTitle} </Text> - <Text>{settingsLabels.tempScale.segmentExplainer}</Text> + <Text>{labels.tempScale.segmentExplainer}</Text> <TempSlider/> </View> <View style={styles.settingsSegment}> <Text style={styles.settingsSegmentTitle}> - {settingsLabels.export.button} + {labels.export.button} </Text> - <Text>{settingsLabels.export.segmentExplainer}</Text> + <Text>{labels.export.segmentExplainer}</Text> <TouchableOpacity onPress={openShareDialogAndExport} style={styles.settingsButton}> <Text style={styles.settingsButtonText}> - {settingsLabels.export.button} + {labels.export.button} </Text> </TouchableOpacity> </View> <View style={styles.settingsSegment}> <Text style={styles.settingsSegmentTitle}> - {settingsLabels.import.button} + {labels.import.button} </Text> - <Text>{settingsLabels.import.segmentExplainer}</Text> + <Text>{labels.import.segmentExplainer}</Text> <TouchableOpacity onPress={openImportDialogAndImport} style={styles.settingsButton}> <Text style={styles.settingsButtonText}> - {settingsLabels.import.button} + {labels.import.button} </Text> </TouchableOpacity> </View> @@ -59,6 +72,66 @@ export default class Settings extends Component { } } +class TempReminderPicker extends Component { + constructor(props) { + super(props) + this.state = Object.assign({}, tempReminderObservable.value) + } + + render() { + return ( + <TouchableOpacity + style={styles.settingsSegment} + onPress={() => this.setState({ isTimePickerVisible: true })} + > + <Text style={styles.settingsSegmentTitle}> + {labels.tempReminder.title} + </Text> + <View style={{ flexDirection: 'row', alignItems: 'center' }}> + <View style={{ flex: 1 }}> + {this.state.time && this.state.enabled ? + <Text>{labels.tempReminder.timeSet(this.state.time)}</Text> + : + <Text>{labels.tempReminder.noTimeSet}</Text> + } + </View> + <Switch + value={this.state.enabled} + onValueChange={switchOn => { + this.setState({ enabled: switchOn }) + if (switchOn && !this.state.time) { + this.setState({ isTimePickerVisible: true }) + } + if (!switchOn) saveTempReminder({ enabled: false }) + }} + onTintColor={secondaryColor} + /> + <DateTimePicker + mode="time" + isVisible={this.state.isTimePickerVisible} + onConfirm={jsDate => { + const time = padWithZeros(`${jsDate.getHours()}:${jsDate.getMinutes()}`) + this.setState({ + time, + isTimePickerVisible: false, + enabled: true + }) + saveTempReminder({ + time, + enabled: true + }) + }} + onCancel={() => { + this.setState({ isTimePickerVisible: false }) + if (!this.state.time) this.setState({enabled: false}) + }} + /> + </View> + </TouchableOpacity> + ) + } +} + class TempSlider extends Component { constructor(props) { super(props) @@ -80,15 +153,15 @@ class TempSlider extends Component { try { saveTempScale(this.state) } catch(err) { - alertError(settingsLabels.tempScale.saveError) + alertError(labels.tempScale.saveError) } } render() { return ( - <View style={{alignItems: 'center'}}> - <Text>{`${settingsLabels.tempScale.min} ${this.state.min}`}</Text> - <Text>{`${settingsLabels.tempScale.max} ${this.state.max}`}</Text> + <View style={{ alignItems: 'center' }}> + <Text>{`${labels.tempScale.min} ${this.state.min}`}</Text> + <Text>{`${labels.tempScale.max} ${this.state.max}`}</Text> <Slider values={[this.state.min, this.state.max]} min={config.temperatureScale.min} @@ -103,7 +176,7 @@ class TempSlider extends Component { backgroundColor: 'silver', }} trackStyle={{ - height:10, + height: 10, }} markerStyle={{ backgroundColor: secondaryColor, @@ -123,36 +196,36 @@ async function openShareDialogAndExport() { try { data = getDataAsCsvDataUri() if (!data) { - return alertError(settingsLabels.errors.noData) + return alertError(labels.errors.noData) } } catch (err) { console.error(err) - return alertError(settingsLabels.errors.couldNotConvert) + return alertError(labels.errors.couldNotConvert) } try { await Share.open({ - title: settingsLabels.export.title, + title: labels.export.title, url: data, - subject: settingsLabels.export.subject, + subject: labels.export.subject, type: 'text/csv', showAppsToView: true }) } catch (err) { console.error(err) - return alertError(settingsLabels.export.errors.problemSharing) + return alertError(labels.export.errors.problemSharing) } } function openImportDialogAndImport() { Alert.alert( - settingsLabels.import.title, - settingsLabels.import.message, + labels.import.title, + labels.import.message, [{ - text: settingsLabels.import.replaceOption, + text: labels.import.replaceOption, onPress: () => getFileContentAndImport({ deleteExisting: false }) }, { - text: settingsLabels.import.deleteOption, + text: labels.import.deleteOption, onPress: () => getFileContentAndImport({ deleteExisting: true }) }, { text: sharedLabels.cancel, style: 'cancel', onPress: () => { } @@ -180,12 +253,12 @@ async function getFileContentAndImport({ deleteExisting }) { try { fileContent = await rnfs.readFile(fileInfo.uri, 'utf8') } catch (err) { - return importError(settingsLabels.import.errors.couldNotOpenFile) + return importError(labels.import.errors.couldNotOpenFile) } try { await importCsv(fileContent, deleteExisting) - Alert.alert(sharedLabels.successTitle, settingsLabels.import.success.message) + Alert.alert(sharedLabels.successTitle, labels.import.success.message) } catch(err) { importError(err.message) } @@ -196,6 +269,16 @@ function alertError(msg) { } function importError(msg) { - const postFixed = `${msg}\n\n${settingsLabels.import.errors.postFix}` + const postFixed = `${msg}\n\n${labels.import.errors.postFix}` alertError(postFixed) } + +function padWithZeros(time) { + const vals = time.split(':') + return vals.map(val => { + if (parseInt(val) < 10) { + val = `0${val}` + } + return val + }).join(':') +} \ No newline at end of file diff --git a/lib/notifications.js b/lib/notifications.js new file mode 100644 index 0000000000000000000000000000000000000000..dc1a7a52ce2c5f792eabca6b8166471566e20fdd --- /dev/null +++ b/lib/notifications.js @@ -0,0 +1,38 @@ +import {tempReminderObservable} from '../local-storage' +import Notification from 'react-native-push-notification' +import { LocalDate } from 'js-joda' +import Moment from 'moment' +import { settings as labels } from '../components/labels' +import { getOrCreateCycleDay } from '../db' + +export default function setupNotifications(navigate) { + Notification.configure({ + onNotification: () => { + const todayDateString = LocalDate.now().toString() + const cycleDay = getOrCreateCycleDay(todayDateString) + navigate('TemperatureEditView', { cycleDay }) + } + }) + + tempReminderObservable(reminder => { + Notification.cancelAllLocalNotifications() + if (reminder.enabled) { + const [hours, minutes] = reminder.time.split(':') + let target = new Moment() + .hours(parseInt(hours)) + .minutes(parseInt(minutes)) + .seconds(0) + + if(target.isBefore(new Moment())) { + target = target.add(1, 'd') + } + + Notification.localNotificationSchedule({ + message: labels.tempReminder.notification, + date: target.toDate(), + vibrate: false, + repeatType: 'day' + }) + } + }) +} \ No newline at end of file diff --git a/local-storage/index.js b/local-storage/index.js index 50d886fb6c9ada10b874a433dbe17359474f2a87..6296271101fdca17ba796fc7a0aef570f1e86dcf 100644 --- a/local-storage/index.js +++ b/local-storage/index.js @@ -3,20 +3,25 @@ import Observable from 'obv' import config from '../config' export const scaleObservable = Observable() -setTempScaleInitially() +setObvWithInitValue('tempScale', scaleObservable, { + min: config.temperatureScale.defaultLow, + max: config.temperatureScale.defaultHigh +}) -async function setTempScaleInitially() { - const result = await AsyncStorage.getItem('tempScale') +export const tempReminderObservable = Observable() +setObvWithInitValue('tempReminder', tempReminderObservable, { + enabled: false +}) + +async function setObvWithInitValue(key, obv, defaultValue) { + const result = await AsyncStorage.getItem(key) let value if (result) { value = JSON.parse(result) } else { - value = { - min: config.temperatureScale.defaultLow, - max: config.temperatureScale.defaultHigh - } + value = defaultValue } - scaleObservable.set(value) + obv.set(value) } export async function saveTempScale(scale) { @@ -24,3 +29,7 @@ export async function saveTempScale(scale) { scaleObservable.set(scale) } +export async function saveTempReminder(reminder) { + await AsyncStorage.setItem('tempReminder', JSON.stringify(reminder)) + tempReminderObservable.set(reminder) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4186e9cb19b66a37fe900a543b7e5f111f2ed6d3..036a5b79360ce679c7730cf67822f6bcd2df7778 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3768,8 +3768,7 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", @@ -3777,8 +3776,7 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -3881,8 +3879,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -3892,7 +3889,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4004,8 +4000,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -4120,7 +4115,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6679,6 +6673,11 @@ } } }, + "react-native-push-notification": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-3.1.1.tgz", + "integrity": "sha512-4+4yQXNPqh5IVvpSBmR4Cy/UeMjTcfE8KIJgEuT7pME97WK+aGPn6W3ybhOoXC1n+ZWKfrAlsHydLE4xfBZDJg==" + }, "react-native-safe-area-view": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.8.0.tgz", diff --git a/package.json b/package.json index 2c492c51a618b2257a136bf069e9554ad20e37bc..11c62a84faabc353e7cb333621998c7589b4e6a9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "isobject": "^3.0.1", "js-base64": "^2.4.8", "js-joda": "^1.8.2", - "moment": "^2.22.1", + "moment": "^2.22.2", "object-path": "^0.11.4", "obv": "0.0.1", "react": "16.4.1", @@ -31,6 +31,7 @@ "react-native-document-picker": "^2.1.0", "react-native-fs": "^2.10.14", "react-native-modal-datetime-picker-nevo": "^4.11.0", + "react-native-push-notification": "^3.1.1", "react-native-share": "^1.1.0", "react-native-simple-radio-button": "^2.7.1", "react-native-vector-icons": "^5.0.0",