Files
menulio/apps/mobile/src/app/menu/qr.tsx
T

799 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from "react";
import { router } from "expo-router";
import {
ActivityIndicator,
Alert,
Pressable,
ScrollView,
Share,
Switch,
Text,
TextInput,
View,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard";
import ViewShot from "react-native-view-shot";
import * as Sharing from "expo-sharing";
import * as Print from "expo-print";
import { QR_STAND_PRESETS, type QrStandPreset } from "@menulio/shared";
import { api } from "@/lib/api";
import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant";
import {
COMPONENT_TEMPLATES,
TEMPLATE_ASPECT_RATIOS,
FONT_OPTIONS,
type QrCustomTextConfig,
} from "@/components/qr-templates";
interface QrResponse {
id: string;
redirectUrl: string;
targetUrl: string;
pngBase64: string;
}
export default function QrScreen() {
const [active, setActive] = useState<ActiveRestaurant | null>(null);
const [restaurantName, setRestaurantName] = useState<string>("Gourmet Bistro");
const [qr, setQr] = useState<QrResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const availablePresets = Object.values(QR_STAND_PRESETS).filter(
(p) => !!COMPONENT_TEMPLATES[p.key]
);
const [selectedKey, setSelectedKey] = useState<string>(
availablePresets[0]?.key || "qr-lumiere"
);
const [exportingPng, setExportingPng] = useState(false);
const [exportingPdf, setExportingPdf] = useState(false);
const [showCustomizePanel, setShowCustomizePanel] = useState(false);
const [selectedFontId, setSelectedFontId] = useState<string>("didot");
const [customText, setCustomText] = useState<QrCustomTextConfig>({
restaurantName: "Gourmet Bistro",
subtitle: "RESTAURANT & BAR | MODERN CUISINE",
tableNo: "MASA NO: 01",
ctaText: "MENÜYÜ GÖRMEK İÇİN OKUTUN",
wifiText: "WiFi: Gourmet_Guest | Şifre: lezzetli01",
socialText: "@gourmetbistro",
showTableNo: true,
showSubtitle: true,
showCta: true,
showWifi: true,
showSocial: true,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const viewShotRef = useRef<any>(null);
const activePreset: QrStandPreset =
QR_STAND_PRESETS[selectedKey] || availablePresets[0] || Object.values(QR_STAND_PRESETS)[0]!;
const activeFont =
FONT_OPTIONS.find((f) => f.id === selectedFontId)?.fontFamily ||
"'Playfair Display', Georgia, serif";
// Component-based template sistemi
const TemplateComponent =
COMPONENT_TEMPLATES[selectedKey] ?? Object.values(COMPONENT_TEMPLATES)[0];
const aspectRatio = TEMPLATE_ASPECT_RATIOS[selectedKey] ?? 1;
const displayWidth = 300;
const displayHeight = Math.round(displayWidth / aspectRatio);
useEffect(() => {
(async () => {
const activeRest = await getActiveRestaurant();
if (!activeRest) return;
setActive(activeRest);
try {
const data = await api.post<QrResponse>(`/restaurants/${activeRest.restaurantId}/qr`);
setQr(data);
// Fetch detailed restaurant name
try {
const restDetail = await api.get<{ name?: string }>(`/restaurants/${activeRest.restaurantId}`);
if (restDetail?.name) {
setRestaurantName(restDetail.name);
setCustomText((prev) => ({
...prev,
restaurantName: restDetail.name,
}));
}
} catch {
// fallback
}
} catch (err) {
setError(err instanceof Error ? err.message : "QR oluşturulamadı");
} finally {
setLoading(false);
}
})();
}, []);
async function handleCopy() {
if (!qr?.targetUrl) return;
await Clipboard.setStringAsync(qr.targetUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
async function handleShareQuick() {
if (!qr?.targetUrl) return;
await Share.share({
title: "Dijital QR Menü",
message: `${qr.targetUrl}\nMenümüzü incelemek için QR kodu taratın veya linke tıklayın.`,
url: qr.targetUrl,
});
}
async function handleExportPng() {
if (!viewShotRef.current) return;
setExportingPng(true);
try {
const uri = await viewShotRef.current.capture?.();
if (uri) {
await Sharing.shareAsync(uri, {
mimeType: "image/png",
dialogTitle: "Masa Stant QR Görselini Paylaş / Kaydet",
});
}
} catch {
Alert.alert("Hata", "Görsel oluşturulamadı.");
} finally {
setExportingPng(false);
}
}
async function handleExportPdf() {
if (!qr || !active) return;
setExportingPdf(true);
try {
// ViewShot ile önce PNG al, sonra A5 HTML içine göm
const pngUri = await viewShotRef.current?.capture();
if (!pngUri) throw new Error("Görsel alınamadı");
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<style>
@page { size: A5 portrait; margin: 0; }
* { box-sizing: border-box; }
body {
margin: 0; padding: 0; width: 148mm; height: 210mm;
display: flex; align-items: center; justify-content: center;
background: #000000;
-webkit-print-color-adjust: exact;
}
.img-wrap {
width: 140mm;
height: 200mm;
display: flex;
align-items: center;
justify-content: center;
}
img {
width: 100%;
height: 100%;
object-fit: contain;
}
</style>
</head>
<body>
<div class="img-wrap">
<img src="${pngUri}" />
</div>
</body>
</html>
`;
const { uri } = await Print.printToFileAsync({
html: htmlContent,
width: 420,
height: 595,
});
await Sharing.shareAsync(uri, {
mimeType: "application/pdf",
dialogTitle: "A5 Baskıya Hazır PDF İndir / Paylaş",
});
} catch {
Alert.alert("Hata", "PDF oluşturulamadı.");
} finally {
setExportingPdf(false);
}
}
if (loading) {
return (
<View style={{ flex: 1, backgroundColor: "#FAF8F5", alignItems: "center", justifyContent: "center" }}>
<ActivityIndicator size="large" color="#C8A96B" />
<Text style={{ marginTop: 12, color: "#78716C", fontSize: 14 }}>QR Kod ve Şablonlar Hazırlanıyor...</Text>
</View>
);
}
if (error || !qr) {
return (
<View style={{ flex: 1, backgroundColor: "#FAF8F5", alignItems: "center", justifyContent: "center", padding: 24 }}>
<Ionicons name="alert-circle-outline" size={48} color="#DC2626" style={{ marginBottom: 12 }} />
<Text style={{ color: "#DC2626", fontSize: 16, fontWeight: "600", textAlign: "center" }}>
{error ?? "QR kod yüklenemedi"}
</Text>
<Pressable
onPress={() => router.back()}
style={{ marginTop: 20, backgroundColor: "#1C1917", paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8 }}
>
<Text style={{ color: "#FFF", fontWeight: "600" }}>Geri Dön</Text>
</Pressable>
</View>
);
}
return (
<ScrollView contentContainerStyle={{ flexGrow: 1, backgroundColor: "#0C0C0E", paddingVertical: 20 }}>
<View style={{ width: "100%", maxWidth: 440, alignSelf: "center", paddingHorizontal: 20 }}>
{/* Header Badge */}
<View
style={{
alignSelf: "center",
backgroundColor: "rgba(212, 175, 55, 0.15)",
paddingHorizontal: 14,
paddingVertical: 6,
borderRadius: 99,
marginBottom: 12,
flexDirection: "row",
alignItems: "center",
gap: 6,
borderWidth: 1,
borderColor: "rgba(212, 175, 55, 0.3)",
}}
>
<Ionicons name="qr-code-outline" size={15} color="#D4AF37" />
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
MASA QR STANT JENERATÖRÜ
</Text>
</View>
<Text style={{ fontSize: 24, fontWeight: "800", color: "#FFFFFF", marginBottom: 4, textAlign: "center" }}>
Masa Stant Tasarımları
</Text>
<Text style={{ fontSize: 13, color: "#A1A1AA", textAlign: "center", marginBottom: 20, lineHeight: 18 }}>
Şablon seçin; QR kodunuz otomatik yerleşsin. Baskıya hazır A5 PDF veya PNG olarak indirin.
</Text>
{/* Horizontal Theme Selector */}
{availablePresets.length > 1 && (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ gap: 10, paddingHorizontal: 4, marginBottom: 20 }}
>
{availablePresets.map((preset) => {
const isSelected = preset.key === selectedKey;
return (
<Pressable
key={preset.key}
onPress={() => setSelectedKey(preset.key)}
style={{
backgroundColor: isSelected ? "#D4AF37" : "#1A1A1E",
borderWidth: 1.5,
borderColor: isSelected ? "#D4AF37" : "#2B2B32",
borderRadius: 14,
paddingHorizontal: 16,
paddingVertical: 10,
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
<View
style={{
width: 14,
height: 14,
borderRadius: 7,
backgroundColor: preset.plaqueColor || "#1A1A1E",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.4)",
}}
/>
<Text
style={{
fontSize: 13,
fontWeight: "700",
color: isSelected ? "#0C0C0E" : "#FFFFFF",
}}
>
{preset.name}
</Text>
</Pressable>
);
})}
</ScrollView>
)}
{/* Live Stand Preview Wrapped in ViewShot */}
<View
style={{
alignItems: "center",
justifyContent: "center",
padding: 16,
borderRadius: 28,
backgroundColor: "#18181B",
marginBottom: 20,
shadowColor: "#000",
shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.5,
shadowRadius: 20,
elevation: 8,
borderWidth: 1,
borderColor: "rgba(255,255,255,0.08)",
}}
>
<ViewShot ref={viewShotRef} options={{ format: "png", quality: 1.0 }}>
{/* Component-based template — no XML parsing, pixel-perfect */}
<View
style={{
width: displayWidth,
height: displayHeight,
borderRadius: 24,
overflow: "hidden",
backgroundColor: "#0C0C0E",
}}
>
{TemplateComponent ? (
<TemplateComponent
qrBase64={qr.pngBase64}
width={displayWidth}
height={displayHeight}
restaurantName={customText.restaurantName}
subtitle={customText.subtitle}
tableNo={customText.tableNo}
ctaText={customText.ctaText}
wifiText={customText.wifiText}
socialText={customText.socialText}
fontFamily={activeFont}
showTableNo={customText.showTableNo}
showSubtitle={customText.showSubtitle}
showCta={customText.showCta}
showWifi={customText.showWifi}
showSocial={customText.showSocial}
/>
) : null}
</View>
</ViewShot>
</View>
{/* Metin & Font Özelleştirme Kartı (Açılır/Kapanır) */}
<View
style={{
backgroundColor: "#18181B",
borderRadius: 20,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.08)",
padding: 16,
marginBottom: 20,
}}
>
<Pressable
onPress={() => setShowCustomizePanel(!showCustomizePanel)}
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<View
style={{
width: 32,
height: 32,
borderRadius: 8,
backgroundColor: "rgba(212, 175, 55, 0.15)",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="text-outline" size={18} color="#D4AF37" />
</View>
<View>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
Metin ve Yazı Tipi Özelleştir
</Text>
<Text style={{ color: "#A1A1AA", fontSize: 12 }}>
İstediğiniz alanları düzenleyin veya kaldırın
</Text>
</View>
</View>
<Ionicons
name={showCustomizePanel ? "chevron-up" : "chevron-down"}
size={20}
color="#A1A1AA"
/>
</Pressable>
{showCustomizePanel && (
<View style={{ marginTop: 16, gap: 16, borderTopWidth: 1, borderTopColor: "rgba(255,255,255,0.06)", paddingTop: 16 }}>
{/* Yazı Tipi (Font) Seçici */}
<View>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700", marginBottom: 8, letterSpacing: 0.5 }}>
YAZI TİPİ (FONT)
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}>
{FONT_OPTIONS.map((f) => {
const isSelected = f.id === selectedFontId;
return (
<Pressable
key={f.id}
onPress={() => setSelectedFontId(f.id)}
style={{
backgroundColor: isSelected ? "#D4AF37" : "#242429",
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 10,
borderWidth: 1,
borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.08)",
}}
>
<Text
style={{
color: isSelected ? "#0C0C0E" : "#FFFFFF",
fontSize: 12,
fontWeight: isSelected ? "700" : "500",
}}
>
{f.label}
</Text>
</Pressable>
);
})}
</ScrollView>
</View>
{/* Alan 1: Restoran Başlığı */}
<View style={{ gap: 6 }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
RESTORAN BAŞLIĞI
</Text>
<TextInput
value={customText.restaurantName}
onChangeText={(t) => setCustomText((p) => ({ ...p, restaurantName: t }))}
placeholder="Restoran Adı"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
</View>
{/* Alan 2: Slogan / Alt Başlık */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
SLOGAN / ALT BAŞLIK
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showSubtitle ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showSubtitle}
onValueChange={(v) => setCustomText((p) => ({ ...p, showSubtitle: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showSubtitle && (
<TextInput
value={customText.subtitle}
onChangeText={(t) => setCustomText((p) => ({ ...p, subtitle: t }))}
placeholder="Slogan / Açıklama"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 3: Masa Numarası */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
MASA NUMARASI
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showTableNo ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showTableNo}
onValueChange={(v) => setCustomText((p) => ({ ...p, showTableNo: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showTableNo && (
<TextInput
value={customText.tableNo}
onChangeText={(t) => setCustomText((p) => ({ ...p, tableNo: t }))}
placeholder="Örn: MASA NO: 01"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 4: Yönlendirme (CTA) */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
YÖNLENDİRME (CTA) METNİ
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showCta ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showCta}
onValueChange={(v) => setCustomText((p) => ({ ...p, showCta: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showCta && (
<TextInput
value={customText.ctaText}
onChangeText={(t) => setCustomText((p) => ({ ...p, ctaText: t }))}
placeholder="Örn: MENÜYÜ GÖRMEK İÇİN OKUTUN"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 5: WiFi Bilgileri */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
WIFI BİLGİSİ
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showWifi ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showWifi}
onValueChange={(v) => setCustomText((p) => ({ ...p, showWifi: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showWifi && (
<TextInput
value={customText.wifiText}
onChangeText={(t) => setCustomText((p) => ({ ...p, wifiText: t }))}
placeholder="Örn: WiFi: Bistro | Şifre: 12345"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
{/* Alan 6: Sosyal Medya / İletişim */}
<View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
SOSYAL MEDYA / İLETİŞİM
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
<Text style={{ color: "#71717A", fontSize: 11 }}>
{customText.showSocial ? "Göster" : "Gizle"}
</Text>
<Switch
value={customText.showSocial}
onValueChange={(v) => setCustomText((p) => ({ ...p, showSocial: v }))}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View>
</View>
{customText.showSocial && (
<TextInput
value={customText.socialText}
onChangeText={(t) => setCustomText((p) => ({ ...p, socialText: t }))}
placeholder="Örn: @menulio"
placeholderTextColor="#71717A"
style={{
backgroundColor: "#121215",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.1)",
borderRadius: 12,
paddingHorizontal: 14,
paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
}}
/>
)}
</View>
</View>
)}
</View>
{/* Output Action Buttons */}
<View style={{ width: "100%", gap: 10 }}>
{/* PDF Download Button */}
<Pressable
onPress={handleExportPdf}
disabled={exportingPdf}
style={({ pressed }) => ({
backgroundColor: "#D4AF37",
borderRadius: 14,
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
opacity: pressed || exportingPdf ? 0.85 : 1,
shadowColor: "#D4AF37",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 3,
})}
>
{exportingPdf ? (
<ActivityIndicator size="small" color="#0C0C0E" />
) : (
<>
<Ionicons name="document-text-outline" size={20} color="#0C0C0E" />
<Text style={{ color: "#0C0C0E", fontSize: 15, fontWeight: "800" }}>
PDF Olarak İndir (A5 Matbaa Baskı)
</Text>
</>
)}
</Pressable>
{/* PNG Download Button */}
<Pressable
onPress={handleExportPng}
disabled={exportingPng}
style={({ pressed }) => ({
backgroundColor: "#1A1A1E",
borderWidth: 1.5,
borderColor: "#2B2B32",
borderRadius: 14,
padding: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
opacity: pressed || exportingPng ? 0.85 : 1,
})}
>
{exportingPng ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons name="image-outline" size={20} color="#FFFFFF" />
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
PNG Olarak Kaydet / Paylaş
</Text>
</>
)}
</Pressable>
{/* Direct Link Share Button */}
<Pressable
onPress={handleShareQuick}
style={({ pressed }) => ({
backgroundColor: "#1A1A1E",
borderWidth: 1,
borderColor: "#2B2B32",
borderRadius: 14,
padding: 14,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
opacity: pressed ? 0.85 : 1,
})}
>
<Ionicons name="share-outline" size={18} color="#D4AF37" />
<Text style={{ color: "#D4AF37", fontSize: 14, fontWeight: "700" }}>
Menü Linkini Paylaş
</Text>
</Pressable>
{/* Quick Copy Link Box */}
<Pressable
onPress={handleCopy}
style={{
backgroundColor: "#141417",
borderWidth: 1,
borderColor: "#242429",
borderRadius: 14,
padding: 14,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginTop: 4,
}}
>
<Text style={{ color: "#A1A1AA", fontSize: 12, flex: 1 }} numberOfLines={1}>
{qr.targetUrl}
</Text>
<Ionicons
name={copied ? "checkmark-circle" : "copy-outline"}
size={18}
color={copied ? "#10B981" : "#D4AF37"}
style={{ marginLeft: 8 }}
/>
</Pressable>
</View>
</View>
</ScrollView>
);
}