Skip to content
Snippets Groups Projects
Commit 7ea6a683 authored by Julia Friesel's avatar Julia Friesel
Browse files

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
parents 4b971a1f 355bcbe8
No related branches found
No related tags found
No related merge requests found
......@@ -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";
}
}
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="Downloads" path="Download/" />
</paths>
\ No newline at end of file
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()}
......
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'
}
\ No newline at end of file
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>
)
}
}
\ No newline at end of file
......@@ -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;
......
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
}
\ No newline at end of file
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment