Merge branch '113-add-button-to-export-to-settings-page' into 'master'
Resolve "Add button to export to settings page" Closes #113 See merge request bloodyhealth/drip!46
This commit is contained in:
@@ -137,6 +137,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-share')
|
||||
compile project(':realm')
|
||||
compile project(':react-native-svg')
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.drip.provider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.drip;
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import cl.json.RNSharePackage;
|
||||
import cl.json.ShareApplication;
|
||||
import io.realm.react.RealmReactPackage;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
@@ -13,7 +15,7 @@ import com.facebook.soloader.SoLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
public class MainApplication extends Application implements ReactApplication, ShareApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
@@ -25,6 +27,7 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new RNSharePackage(),
|
||||
new RealmReactPackage(),
|
||||
new SvgPackage()
|
||||
);
|
||||
@@ -46,4 +49,9 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileProviderAuthority() {
|
||||
return "com.drip.provider";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="Downloads" path="Download/" />
|
||||
</paths>
|
||||
@@ -1,4 +1,6 @@
|
||||
rootProject.name = 'drip'
|
||||
include ':react-native-share'
|
||||
project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android')
|
||||
include ':realm'
|
||||
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
||||
include ':react-native-svg'
|
||||
|
||||
@@ -4,8 +4,10 @@ import Home from './components/home'
|
||||
import Calendar from './components/calendar'
|
||||
import CycleDay from './components/cycle-day'
|
||||
import Chart from './components/chart/chart'
|
||||
import Settings from './components/settings'
|
||||
|
||||
// this is until react native fixes this bug, see https://github.com/facebook/react-native/issues/18868#issuecomment-382671739
|
||||
// this is until react native fixes this bugg, see
|
||||
// https://github.com/facebook/react-native/issues/18868#issuecomment-382671739
|
||||
import { YellowBox } from 'react-native'
|
||||
YellowBox.ignoreWarnings(['Warning: isMounted(...) is deprecated'])
|
||||
|
||||
@@ -13,5 +15,6 @@ export default createStackNavigator({
|
||||
home: { screen: Home },
|
||||
calendar: { screen: Calendar },
|
||||
cycleDay: { screen: CycleDay },
|
||||
chart: { screen: Chart }
|
||||
chart: { screen: Chart },
|
||||
settings: { screen: Settings }
|
||||
})
|
||||
|
||||
@@ -69,6 +69,12 @@ export default class Home extends Component {
|
||||
title="Go to chart">
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.homeButton}>
|
||||
<Button
|
||||
onPress={() => navigate('settings')}
|
||||
title="Go to settings">
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.homeButton}>
|
||||
<Button
|
||||
onPress={() => fillWithDummyData()}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const settings = {
|
||||
errors: {
|
||||
noData: 'There is no data to export',
|
||||
couldNotConvert: 'Could not convert data to CSV',
|
||||
problemSharing: 'There was a problem sharing the data export file'
|
||||
},
|
||||
exportTitle: 'My Drip data export',
|
||||
exportSubject: 'My Drip data export',
|
||||
buttonLabel: 'Export data'
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { Component } from 'react'
|
||||
import {
|
||||
View,
|
||||
Button,
|
||||
Text,
|
||||
ScrollView,
|
||||
Alert
|
||||
} from 'react-native'
|
||||
import Share from 'react-native-share'
|
||||
import getDataAsCsvDataUri from '../lib/export-to-csv'
|
||||
import styles from '../styles/index'
|
||||
import { settings as labels } from './labels'
|
||||
|
||||
export default class Settings extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
pickerVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<Text style={styles.welcome}>{this.state.welcomeText}</Text>
|
||||
<View style={styles.homeButtons}>
|
||||
<View style={styles.homeButton}>
|
||||
<Button
|
||||
onPress={async () => {
|
||||
let data
|
||||
try {
|
||||
data = getDataAsCsvDataUri()
|
||||
if (!data) {
|
||||
return Alert.alert(labels.errors.noData)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return Alert.alert(labels.errors.couldNotConvert)
|
||||
}
|
||||
|
||||
try {
|
||||
await Share.open({
|
||||
title: labels.exportTitle,
|
||||
url: data,
|
||||
subject: labels.exportSubject,
|
||||
type: 'text/csv',
|
||||
showAppsToView: true
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return Alert.alert(labels.errors.problemSharing)
|
||||
}
|
||||
}}
|
||||
title={labels.buttonLabel}>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
}
|
||||
+21
-3
@@ -110,6 +110,7 @@ const db = new Realm(realmConfig)
|
||||
|
||||
const bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
|
||||
const temperatureDaysSortedByDate = db.objects('CycleDay').filtered('temperature != null').sorted('date', true)
|
||||
const cycleDaysSortedByDate = db.objects('CycleDay').sorted('date', true)
|
||||
|
||||
function saveSymptom(symptom, cycleDay, val) {
|
||||
db.write(() => {
|
||||
@@ -117,8 +118,6 @@ function saveSymptom(symptom, cycleDay, val) {
|
||||
})
|
||||
}
|
||||
|
||||
const cycleDaysSortedByDate = db.objects('CycleDay').sorted('date', true)
|
||||
|
||||
function getOrCreateCycleDay(localDate) {
|
||||
let result = db.objectForPrimaryKey('CycleDay', localDate)
|
||||
if (!result) {
|
||||
@@ -176,6 +175,24 @@ function getPreviousTemperature(cycleDay) {
|
||||
return winner.temperature.value
|
||||
}
|
||||
|
||||
function getColumnNamesForCsv() {
|
||||
return getPrefixedKeys('CycleDay')
|
||||
|
||||
function getPrefixedKeys(schemaName, prefix) {
|
||||
const schema = db.schema.find(x => x.name === schemaName).properties
|
||||
return Object.keys(schema).reduce((acc, key) => {
|
||||
const prefixedKey = prefix ? [prefix, key].join('.') : key
|
||||
const childSchemaName = schema[key].objectType
|
||||
if (!childSchemaName) {
|
||||
acc.push(prefixedKey)
|
||||
return acc
|
||||
}
|
||||
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
saveSymptom,
|
||||
getOrCreateCycleDay,
|
||||
@@ -185,5 +202,6 @@ export {
|
||||
fillWithDummyData,
|
||||
deleteAll,
|
||||
getPreviousTemperature,
|
||||
getCycleDay
|
||||
getCycleDay,
|
||||
getColumnNamesForCsv
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
089A8A31B3244EB381D3BA67 /* libRealmReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A5827160B914D2B99C47381 /* libRealmReact.a */; };
|
||||
62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEBF0735214455AAEDF56D5 /* libc++.tbd */; };
|
||||
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CD8C8B91E0A747B3883A0D56 /* libz.tbd */; };
|
||||
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8B59389C2FC4F19BD30ABC3 /* libRNShare.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -354,6 +355,8 @@
|
||||
7A5827160B914D2B99C47381 /* libRealmReact.a */ = {isa = PBXFileReference; name = "libRealmReact.a"; path = "libRealmReact.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
9AEBF0735214455AAEDF56D5 /* libc++.tbd */ = {isa = PBXFileReference; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
|
||||
CD8C8B91E0A747B3883A0D56 /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
|
||||
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */ = {isa = PBXFileReference; name = "RNShare.xcodeproj"; path = "../node_modules/react-native-share/ios/RNShare.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||
A8B59389C2FC4F19BD30ABC3 /* libRNShare.a */ = {isa = PBXFileReference; name = "libRNShare.a"; path = "libRNShare.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -386,6 +389,7 @@
|
||||
089A8A31B3244EB381D3BA67 /* libRealmReact.a in Frameworks */,
|
||||
62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */,
|
||||
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */,
|
||||
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -577,6 +581,7 @@
|
||||
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
|
||||
8316A5AD64274E6FBA6C9FFE /* RNSVG.xcodeproj */,
|
||||
7F6C9FA9B66B453CA602B334 /* RealmReact.xcodeproj */,
|
||||
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1203,11 +1208,13 @@
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/drip.app/drip";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1228,11 +1235,13 @@
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/drip.app/drip";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1256,6 +1265,7 @@
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1278,6 +1288,7 @@
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1307,11 +1318,13 @@
|
||||
TVOS_DEPLOYMENT_TARGET = 9.2;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1341,11 +1354,13 @@
|
||||
TVOS_DEPLOYMENT_TARGET = 9.2;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1374,11 +1389,13 @@
|
||||
TVOS_DEPLOYMENT_TARGET = 10.1;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1407,11 +1424,13 @@
|
||||
TVOS_DEPLOYMENT_TARGET = 10.1;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import objectPath from 'object-path'
|
||||
import { Base64 } from 'js-base64'
|
||||
|
||||
import { getColumnNamesForCsv, cycleDaysSortedByDate } from '../db'
|
||||
|
||||
export default function makeDataURI() {
|
||||
if (!cycleDaysSortedByDate.length) return null
|
||||
|
||||
const csv = transformToCsv(cycleDaysSortedByDate)
|
||||
const encoded = Base64.encodeURI(csv)
|
||||
return `data:text/csv;base64,${encoded}`
|
||||
}
|
||||
|
||||
function transformToCsv(cycleDays) {
|
||||
const columnNames = getColumnNamesForCsv()
|
||||
const rows = cycleDays
|
||||
.map(day => {
|
||||
return columnNames.map(column => {
|
||||
const val = objectPath.get(day, column)
|
||||
return typeof val === 'string' ? csvify(val) : val
|
||||
})
|
||||
})
|
||||
.map(row => row.join(','))
|
||||
|
||||
rows.unshift(columnNames.join(','))
|
||||
return rows.join('\n')
|
||||
}
|
||||
|
||||
function csvify (val) {
|
||||
// we wrap fields with special characters in quotes,
|
||||
// thus have to escape actual quotes
|
||||
val = val.replace(/"/g, '""')
|
||||
|
||||
val = val.toLowerCase()
|
||||
const hasSpecialChars = (
|
||||
val.includes('\n') ||
|
||||
val.includes('\t') ||
|
||||
val.includes(',') ||
|
||||
val.includes(';') ||
|
||||
val.includes('.') ||
|
||||
val.includes('\'')
|
||||
)
|
||||
|
||||
return hasSpecialChars ? `"${val}"` : val
|
||||
}
|
||||
Generated
+359
-281
File diff suppressed because it is too large
Load Diff
@@ -17,12 +17,15 @@
|
||||
"dependencies": {
|
||||
"assert": "^1.4.1",
|
||||
"date-range": "0.0.2",
|
||||
"js-base64": "^2.4.8",
|
||||
"js-joda": "^1.8.2",
|
||||
"moment": "^2.22.1",
|
||||
"object-path": "^0.11.4",
|
||||
"react": "16.4.1",
|
||||
"react-native": "^0.56.0",
|
||||
"react-native-calendars": "^1.19.3",
|
||||
"react-native-modal-datetime-picker-nevo": "^4.11.0",
|
||||
"react-native-share": "^1.1.0",
|
||||
"react-native-simple-radio-button": "^2.7.1",
|
||||
"react-native-svg": "^6.3.1",
|
||||
"react-navigation": "^2.0.4",
|
||||
|
||||
Reference in New Issue
Block a user