feat: share knop op dashboard kaart — deelt kaart als PNG via expo-sharing
This commit is contained in:
parent
86d6cde478
commit
dc85cd3ff0
3 changed files with 129 additions and 27 deletions
73
package-lock.json
generated
73
package-lock.json
generated
|
|
@ -13,6 +13,7 @@
|
||||||
"@react-navigation/native-stack": "^7.17.6",
|
"@react-navigation/native-stack": "^7.17.6",
|
||||||
"expo": "~54.0.0",
|
"expo": "~54.0.0",
|
||||||
"expo-secure-store": "~15.0.8",
|
"expo-secure-store": "~15.0.8",
|
||||||
|
"expo-sharing": "~14.0.8",
|
||||||
"expo-sqlite": "~16.0.10",
|
"expo-sqlite": "~16.0.10",
|
||||||
"expo-status-bar": "~3.0.9",
|
"expo-status-bar": "~3.0.9",
|
||||||
"expo-updates": "~29.0.18",
|
"expo-updates": "~29.0.18",
|
||||||
|
|
@ -23,6 +24,7 @@
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-svg": "15.12.1",
|
"react-native-svg": "15.12.1",
|
||||||
|
"react-native-view-shot": "4.0.3",
|
||||||
"react-native-web": "^0.21.0"
|
"react-native-web": "^0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -3728,6 +3730,15 @@
|
||||||
"node": "18 || 20 || >=22"
|
"node": "18 || 20 || >=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-arraybuffer": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
|
@ -4403,6 +4414,15 @@
|
||||||
"hyphenate-style-name": "^1.0.3"
|
"hyphenate-style-name": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-line-break": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-select": {
|
"node_modules/css-select": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
|
@ -5212,6 +5232,15 @@
|
||||||
"node": ">=20.16.0"
|
"node": ">=20.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-sharing": {
|
||||||
|
"version": "14.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-14.0.8.tgz",
|
||||||
|
"integrity": "sha512-A1pPr2iBrxypFDCWVAESk532HK+db7MFXbvO2sCV9ienaFXAk7lIBm6bkqgE6vzRd9O3RGdEGzYx80cYlc089Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-sqlite": {
|
"node_modules/expo-sqlite": {
|
||||||
"version": "16.0.10",
|
"version": "16.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-16.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/expo-sqlite/-/expo-sqlite-16.0.10.tgz",
|
||||||
|
|
@ -6144,6 +6173,19 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/html2canvas": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-line-break": "^2.1.0",
|
||||||
|
"text-segmentation": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
|
|
@ -9611,6 +9653,19 @@
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-view-shot": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"html2canvas": "^1.4.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-web": {
|
"node_modules/react-native-web": {
|
||||||
"version": "0.21.2",
|
"version": "0.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||||
|
|
@ -10753,6 +10808,15 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/text-segmentation": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/thenify": {
|
"node_modules/thenify": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
|
|
@ -11087,6 +11151,15 @@
|
||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/utrie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-arraybuffer": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"@react-navigation/native-stack": "^7.17.6",
|
"@react-navigation/native-stack": "^7.17.6",
|
||||||
"expo": "~54.0.0",
|
"expo": "~54.0.0",
|
||||||
"expo-secure-store": "~15.0.8",
|
"expo-secure-store": "~15.0.8",
|
||||||
|
"expo-sharing": "~14.0.8",
|
||||||
"expo-sqlite": "~16.0.10",
|
"expo-sqlite": "~16.0.10",
|
||||||
"expo-status-bar": "~3.0.9",
|
"expo-status-bar": "~3.0.9",
|
||||||
"expo-updates": "~29.0.18",
|
"expo-updates": "~29.0.18",
|
||||||
|
|
@ -18,6 +19,7 @@
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "~4.16.0",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-svg": "15.12.1",
|
"react-native-svg": "15.12.1",
|
||||||
|
"react-native-view-shot": "4.0.3",
|
||||||
"react-native-web": "^0.21.0"
|
"react-native-web": "^0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react'
|
import React, { useRef } from 'react'
|
||||||
import { View, Text, StyleSheet } from 'react-native'
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
|
import ViewShot from 'react-native-view-shot'
|
||||||
|
import * as Sharing from 'expo-sharing'
|
||||||
import { Measurement } from '../types'
|
import { Measurement } from '../types'
|
||||||
|
|
||||||
type Props = { measurements: Measurement[] }
|
type Props = { measurements: Measurement[] }
|
||||||
|
|
@ -16,6 +18,8 @@ const rows: { label: string; key: keyof Measurement; suffix: string }[] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function LatestMeasurementCard({ measurements }: Props) {
|
export default function LatestMeasurementCard({ measurements }: Props) {
|
||||||
|
const viewShotRef = useRef<ViewShot>(null)
|
||||||
|
|
||||||
if (measurements.length === 0) {
|
if (measurements.length === 0) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
|
|
@ -28,8 +32,26 @@ export default function LatestMeasurementCard({ measurements }: Props) {
|
||||||
const previous = measurements[1]
|
const previous = measurements[1]
|
||||||
const diff = previous ? (latest.weight - previous.weight) : null
|
const diff = previous ? (latest.weight - previous.weight) : null
|
||||||
|
|
||||||
|
const handleShare = async () => {
|
||||||
|
try {
|
||||||
|
const uri = await viewShotRef.current?.capture?.()
|
||||||
|
if (uri) {
|
||||||
|
await Sharing.shareAsync(uri, { mimeType: 'image/png' })
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.card}>
|
<View style={styles.card}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View style={{ flex: 1 }} />
|
||||||
|
<TouchableOpacity onPress={handleShare} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
||||||
|
<Text style={styles.shareIcon}>↗</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ViewShot ref={viewShotRef} options={{ format: 'png', quality: 1 }}>
|
||||||
|
<View style={styles.shotContent}>
|
||||||
{previous && (
|
{previous && (
|
||||||
<View style={styles.previous}>
|
<View style={styles.previous}>
|
||||||
<Text style={styles.previousTitle}>Voorlaatste meting</Text>
|
<Text style={styles.previousTitle}>Voorlaatste meting</Text>
|
||||||
|
|
@ -57,18 +79,23 @@ export default function LatestMeasurementCard({ measurements }: Props) {
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
</ViewShot>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
card: { backgroundColor: '#fff', borderRadius: 12, padding: 16, margin: 16, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 8, elevation: 3 },
|
card: { backgroundColor: '#fff', borderRadius: 12, padding: 16, margin: 16, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 8, elevation: 3 },
|
||||||
|
header: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 },
|
||||||
|
shareIcon: { fontSize: 22, color: '#2196F3' },
|
||||||
|
shotContent: { backgroundColor: '#fff', padding: 16 },
|
||||||
title: { fontSize: 18, fontWeight: '700', marginBottom: 4 },
|
title: { fontSize: 18, fontWeight: '700', marginBottom: 4 },
|
||||||
date: { fontSize: 14, color: '#666', marginBottom: 12 },
|
date: { fontSize: 14, color: '#666', marginBottom: 12 },
|
||||||
row: { flexDirection: 'row', paddingVertical: 4 },
|
row: { flexDirection: 'row', paddingVertical: 4 },
|
||||||
rowLabel: { fontSize: 14, color: '#555', flex: 1 },
|
rowLabel: { fontSize: 14, color: '#555', flex: 1 },
|
||||||
rowValue: { fontSize: 14, fontWeight: '600' },
|
rowValue: { fontSize: 14, fontWeight: '600' },
|
||||||
emptyText: { fontSize: 16, color: '#999', textAlign: 'center', padding: 20 },
|
emptyText: { fontSize: 16, color: '#999', textAlign: 'center', padding: 20 },
|
||||||
previous: { marginTop: 4 },
|
previous: { marginBottom: 4 },
|
||||||
divider: { height: 1, backgroundColor: '#eee', marginVertical: 12 },
|
divider: { height: 1, backgroundColor: '#eee', marginVertical: 12 },
|
||||||
previousTitle: { fontSize: 18, fontWeight: '700', marginBottom: 4 },
|
previousTitle: { fontSize: 18, fontWeight: '700', marginBottom: 4 },
|
||||||
previousDate: { fontSize: 14, color: '#666', marginBottom: 4 },
|
previousDate: { fontSize: 14, color: '#666', marginBottom: 4 },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue