This commit is contained in:
2026-03-14 19:45:08 -03:00
parent 9ac83ad823
commit 74adc35b53
23 changed files with 1082 additions and 150 deletions
+78 -4
View File
@@ -5,12 +5,15 @@ import {
import { useLocalSearchParams, useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';
import * as FileSystem from 'expo-file-system/legacy';
import * as Sharing from 'expo-sharing';
import { useFilamentStore } from '@store/filamentStore';
import { usePresetStore } from '@store/presetStore';
import { calcFilamentPercentage } from '@domain/Filament';
import { colors, typography, spacing, radius } from '@shared/theme';
import { formatWeight } from '@shared/utils/filament';
import { Button } from '@presentation/components/ui/Button';
import { httpClient } from '@adapters/remote/httpClient';
type LabelSize = '50x30' | '62x29' | '38x25';
@@ -44,6 +47,7 @@ export default function LabelScreen(): React.ReactElement {
const preset = filament ? presets.find((p) => p.id === filament.spoolPresetId) : undefined;
const [selectedSize, setSelectedSize] = useState<LabelSize>('50x30');
const [selectedFormat, setSelectedFormat] = useState<'pdf' | 'svg'>('pdf');
const [enabledContent, setEnabledContent] = useState<Set<ContentOption>>(
new Set(['color', 'name', 'material_brand', 'net_weight', 'print_temp', 'qrcode']),
);
@@ -68,9 +72,47 @@ export default function LabelScreen(): React.ReactElement {
});
}
function handleExport(): void {
// TODO: Generate SVG and share via expo-sharing
Alert.alert('Exportar SVG', 'Funcionalidade de exportação SVG será implementada na integração com o backend.');
async function handleExport(): Promise<void> {
const [widthStr, heightStr] = selectedSize.split('x');
const width_mm = Number(widthStr);
const height_mm = Number(heightStr);
const isPdf = selectedFormat === 'pdf';
const fields = Array.from(enabledContent).join(',');
try {
if (isPdf) {
const response = await httpClient.get<ArrayBuffer>(
`/filaments/${id}/label.pdf`,
{ params: { width_mm, height_mm, fields }, responseType: 'arraybuffer' },
);
const base64 = btoa(
String.fromCharCode(...new Uint8Array(response.data)),
);
const fileUri = `${FileSystem.cacheDirectory}meowspool-label-${id}.pdf`;
await FileSystem.writeAsStringAsync(fileUri, base64, {
encoding: FileSystem.EncodingType.Base64,
});
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(fileUri, { mimeType: 'application/pdf', UTI: 'com.adobe.pdf' });
} else {
Alert.alert('Compartilhamento indisponível', 'Este dispositivo não suporta compartilhamento de arquivos.');
}
} else {
const response = await httpClient.get<string>(
`/filaments/${id}/label.svg`,
{ params: { width_mm, height_mm, fields }, responseType: 'text' },
);
const fileUri = `${FileSystem.cacheDirectory}meowspool-label-${id}.svg`;
await FileSystem.writeAsStringAsync(fileUri, response.data, { encoding: 'utf8' });
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(fileUri, { mimeType: 'image/svg+xml', UTI: 'public.svg-image' });
} else {
Alert.alert('Compartilhamento indisponível', 'Este dispositivo não suporta compartilhamento de arquivos.');
}
}
} catch (err) {
Alert.alert('Erro', String(err));
}
}
const sizeLabel = LABEL_SIZES.find((s) => s.id === selectedSize);
@@ -174,8 +216,21 @@ export default function LabelScreen(): React.ReactElement {
{/* CTA */}
<View style={styles.footer}>
<View style={styles.formatRow}>
{(['pdf', 'svg'] as const).map((fmt) => (
<TouchableOpacity
key={fmt}
onPress={() => setSelectedFormat(fmt)}
style={[styles.formatChip, selectedFormat === fmt && styles.formatChipActive]}
>
<Text style={[styles.formatChipText, selectedFormat === fmt && styles.formatChipTextActive]}>
{fmt.toUpperCase()}
</Text>
</TouchableOpacity>
))}
</View>
<Button
label="Exportar SVG"
label={`Exportar ${selectedFormat.toUpperCase()}`}
leftIcon={<Ionicons name="download-outline" size={18} color={colors.bgBase} />}
onPress={handleExport}
/>
@@ -300,8 +355,27 @@ const styles = StyleSheet.create({
contentChipTextActive: { color: colors.accent },
footer: {
padding: spacing[5],
gap: spacing[3],
borderTopWidth: 1,
borderTopColor: colors.border,
backgroundColor: colors.bgBase,
},
formatRow: { flexDirection: 'row', gap: spacing[2] },
formatChip: {
flex: 1,
alignItems: 'center',
paddingVertical: spacing[2],
borderRadius: radius.lg,
backgroundColor: colors.bgSurface,
borderWidth: 1,
borderColor: colors.border,
},
formatChipActive: { borderColor: colors.accent, backgroundColor: colors.accentMuted },
formatChipText: {
fontFamily: typography.fontFamily.ui,
fontSize: typography.fontSize.sm,
fontWeight: typography.fontWeight.semibold,
color: colors.textSecondary,
},
formatChipTextActive: { color: colors.accent },
});