feat(mobile): replace blocking modal with inline live inspector for instantaneous visual editing feedback

This commit is contained in:
AyrisAI
2026-08-21 01:04:03 +03:00
parent 2c3599839b
commit b0645c0ac3
2 changed files with 298 additions and 390 deletions
+298 -390
View File
@@ -410,44 +410,78 @@ export default function QrScreen() {
</ViewShot> </ViewShot>
</View> </View>
{/* Hızlı Katman Seçici (Tıklayınca Modal Açar) */} {/* Hızlı Katman Seçici (Canlı Seçim) */}
<View style={{ width: "100%", marginBottom: 16 }}> <View style={{ width: "100%", marginBottom: 12 }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700", marginBottom: 8, letterSpacing: 0.5 }}> <View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
DÜZENLEMEK VE KONUMUNU DEĞİŞTİRMEK İÇİN TIKLAYIN: <Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "800", letterSpacing: 0.5 }}>
</Text> CANLI DÜZENLENECEK ALANI SEÇİN:
</Text>
{selectedEditField && (
<Pressable
onPress={() => setSelectedEditField(null)}
style={{
backgroundColor: "rgba(255,255,255,0.08)",
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 6,
}}
>
<Text style={{ color: "#A1A1AA", fontSize: 11, fontWeight: "600" }}>Kapat</Text>
</Pressable>
)}
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}> <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 8 }}>
{FIELD_DEFINITIONS.map(({ key, label, icon }) => { {FIELD_DEFINITIONS.map(({ key, label, icon }) => {
const item = fields[key]; const item = fields[key];
const isSelected = selectedEditField === key;
return ( return (
<Pressable <Pressable
key={key} key={key}
onPress={() => setSelectedEditField(key)} onPress={() => setSelectedEditField(isSelected ? null : key)}
style={({ pressed }) => ({ style={({ pressed }) => ({
backgroundColor: item.visible ? "#242429" : "#1A1A1E", backgroundColor: isSelected ? "#D4AF37" : item.visible ? "#242429" : "#1A1A1E",
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 9, paddingVertical: 9,
borderRadius: 12, borderRadius: 12,
borderWidth: 1.5, borderWidth: 1.5,
borderColor: item.visible ? "rgba(212, 175, 55, 0.4)" : "#2B2B32", borderColor: isSelected ? "#D4AF37" : item.visible ? "rgba(212, 175, 55, 0.3)" : "#2B2B32",
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
gap: 6, gap: 6,
opacity: pressed ? 0.8 : 1, opacity: pressed ? 0.8 : 1,
})} })}
> >
<Ionicons name={icon as any} size={14} color={item.visible ? "#D4AF37" : "#71717A"} /> <Ionicons
name={icon as any}
size={14}
color={isSelected ? "#0C0C0E" : item.visible ? "#D4AF37" : "#71717A"}
/>
<Text <Text
style={{ style={{
color: item.visible ? "#FFFFFF" : "#71717A", color: isSelected ? "#0C0C0E" : item.visible ? "#FFFFFF" : "#71717A",
fontSize: 12, fontSize: 12,
fontWeight: "700", fontWeight: isSelected ? "800" : "700",
}} }}
> >
{label} {label}
</Text> </Text>
{item.offsetY ? ( {item.offsetY ? (
<View style={{ backgroundColor: "#D4AF37", borderRadius: 6, paddingHorizontal: 4, paddingVertical: 1 }}> <View
<Text style={{ color: "#0C0C0E", fontSize: 9, fontWeight: "800" }}> style={{
backgroundColor: isSelected ? "#0C0C0E" : "#D4AF37",
borderRadius: 6,
paddingHorizontal: 4,
paddingVertical: 1,
}}
>
<Text
style={{
color: isSelected ? "#D4AF37" : "#0C0C0E",
fontSize: 9,
fontWeight: "800",
}}
>
{item.offsetY > 0 ? `+${item.offsetY}` : item.offsetY}px {item.offsetY > 0 ? `+${item.offsetY}` : item.offsetY}px
</Text> </Text>
</View> </View>
@@ -458,421 +492,295 @@ export default function QrScreen() {
</ScrollView> </ScrollView>
</View> </View>
{/* Metin & Font & Boyut Özelleştirme Kartı (Açılır/Kapanır Liste) */} {/* CANLI DÜZENLEYİCİ KARTI (Sayfa İçinde Anında Görsel Geri Bildirim) */}
<View {selectedEditField && (() => {
style={{ const def = FIELD_DEFINITIONS.find((d) => d.key === selectedEditField)!;
backgroundColor: "#18181B", const item = fields[selectedEditField];
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: 34,
height: 34,
borderRadius: 10,
backgroundColor: "rgba(212, 175, 55, 0.15)",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="text-outline" size={19} color="#D4AF37" />
</View>
<View>
<Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "700" }}>
Tüm Katmanları Listele
</Text>
<Text style={{ color: "#A1A1AA", fontSize: 12 }}>
Tıkla & Düzenle, dikey konum kaydırma ve font ayarı
</Text>
</View>
</View>
<Ionicons
name={showCustomizePanel ? "chevron-up" : "chevron-down"}
size={20}
color="#A1A1AA"
/>
</Pressable>
{showCustomizePanel && ( return (
<View style={{ marginTop: 16, gap: 12, borderTopWidth: 1, borderTopColor: "rgba(255,255,255,0.06)", paddingTop: 16 }}> <View
{FIELD_DEFINITIONS.map(({ key, label, icon }) => { style={{
const item = fields[key]; width: "100%",
return ( backgroundColor: "#18181B",
<Pressable borderRadius: 20,
key={key} borderWidth: 1.5,
onPress={() => setSelectedEditField(key)} borderColor: "#D4AF37",
style={({ pressed }) => ({ padding: 16,
backgroundColor: "#141417", marginBottom: 20,
borderRadius: 14, gap: 14,
padding: 12, shadowColor: "#D4AF37",
borderWidth: 1, shadowOffset: { width: 0, height: 4 },
borderColor: "rgba(255,255,255,0.06)", shadowOpacity: 0.15,
flexDirection: "row", shadowRadius: 10,
elevation: 4,
}}
>
{/* Başlık & Görünürlük */}
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
<View
style={{
width: 28,
height: 28,
borderRadius: 8,
backgroundColor: "#D4AF37",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "center",
opacity: pressed ? 0.8 : 1, }}
})}
> >
<View style={{ flexDirection: "row", alignItems: "center", gap: 10, flex: 1 }}> <Ionicons name={def.icon as any} size={16} color="#0C0C0E" />
<Ionicons name={icon as any} size={18} color="#D4AF37" /> </View>
<View style={{ flex: 1 }}> <Text style={{ color: "#FFFFFF", fontSize: 15, fontWeight: "800" }}>
<Text style={{ color: "#FFFFFF", fontSize: 13, fontWeight: "700" }}> {def.label}
{label} </Text>
</Text> </View>
<Text style={{ color: "#A1A1AA", fontSize: 11 }} numberOfLines={1}>
{item.text || "Boş"}
</Text>
</View>
</View>
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
<View
style={{
backgroundColor: "#242429",
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
}}
>
<Text style={{ color: "#D4AF37", fontSize: 10, fontWeight: "700" }}>
{item.fontSize || 12}px
</Text>
</View>
<Ionicons name="create-outline" size={18} color="#D4AF37" />
</View>
</Pressable>
);
})}
</View>
)}
</View>
{/* Modal: Tıklanan Metin Katmanını Detaylı Düzenleme */} {/* Stantta Göster / Gizle */}
<Modal <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
visible={!!selectedEditField} <Text style={{ color: item.visible ? "#10B981" : "#71717A", fontSize: 11, fontWeight: "700" }}>
animationType="slide" {item.visible ? "Görünür" : "Gizli"}
transparent </Text>
onRequestClose={() => setSelectedEditField(null)} <Switch
> value={item.visible}
<View onValueChange={(v) =>
style={{ setFields((prev) => ({
flex: 1, ...prev,
backgroundColor: "rgba(0,0,0,0.75)", [selectedEditField]: { ...prev[selectedEditField], visible: v },
justifyContent: "flex-end", }))
}} }
> trackColor={{ false: "#27272A", true: "#D4AF37" }}
{selectedEditField && (() => { thumbColor="#FFFFFF"
const def = FIELD_DEFINITIONS.find((d) => d.key === selectedEditField)!; ios_backgroundColor="#27272A"
const item = fields[selectedEditField]; />
</View>
</View>
return ( {item.visible && (
<View <>
style={{ {/* Metin Girişi (Enter ile alt satır destekli) */}
backgroundColor: "#18181B", <View style={{ gap: 4 }}>
borderTopLeftRadius: 28, <Text style={{ color: "#D4AF37", fontSize: 11, fontWeight: "700" }}>
borderTopRightRadius: 28, METİN İÇERİĞİ (ENTER İLE YENİ SATIRA GEÇEBİLİR):
padding: 20, </Text>
maxHeight: "85%", <TextInput
gap: 16, value={item.text}
borderTopWidth: 1, onChangeText={(t) =>
borderColor: "rgba(212, 175, 55, 0.3)", setFields((prev) => ({
}} ...prev,
> [selectedEditField]: { ...prev[selectedEditField], text: t },
{/* Modal Başlığı & Kapat Butonu */} }))
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}> }
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}> placeholder={def.placeholder}
<Ionicons name={def.icon as any} size={20} color="#D4AF37" /> placeholderTextColor="#71717A"
<Text style={{ color: "#FFFFFF", fontSize: 17, fontWeight: "800" }}> multiline
{def.label}
</Text>
</View>
<Pressable
onPress={() => setSelectedEditField(null)}
style={{ style={{
width: 32, backgroundColor: "#121215",
height: 32, borderWidth: 1,
borderRadius: 16, borderColor: "rgba(255,255,255,0.12)",
backgroundColor: "#242429", borderRadius: 12,
alignItems: "center", paddingHorizontal: 12,
justifyContent: "center", paddingVertical: 10,
color: "#FFFFFF",
fontSize: 14,
minHeight: 46,
}} }}
> />
<Ionicons name="close" size={18} color="#FFFFFF" />
</Pressable>
</View> </View>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ gap: 16, paddingBottom: 20 }}> {/* DİKEY KONUM / YUKARI - AŞAĞI TAŞIMA (offsetY) */}
{/* Görünürlük Anahtarı */} <View style={{ gap: 6, backgroundColor: "#121215", padding: 12, borderRadius: 12 }}>
<View <View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
style={{ <Text style={{ color: "#D4AF37", fontSize: 11, fontWeight: "700" }}>
flexDirection: "row", DİKEY KONUM (YUKARI / AŞAĞI TAŞIMA)
justifyContent: "space-between", </Text>
alignItems: "center", <Text style={{ color: "#E5C378", fontSize: 11, fontWeight: "800" }}>
backgroundColor: "#121215", {item.offsetY ? `${item.offsetY > 0 ? "+" : ""}${item.offsetY} px` : "Orijinal (0px)"}
padding: 12,
borderRadius: 14,
}}
>
<Text style={{ color: "#FFFFFF", fontSize: 13, fontWeight: "700" }}>
Stantta Göster
</Text> </Text>
<Switch
value={item.visible}
onValueChange={(v) =>
setFields((prev) => ({
...prev,
[selectedEditField]: { ...prev[selectedEditField], visible: v },
}))
}
trackColor={{ false: "#27272A", true: "#D4AF37" }}
thumbColor="#FFFFFF"
ios_backgroundColor="#27272A"
/>
</View> </View>
<View style={{ flexDirection: "row", gap: 8 }}>
{/* Metin Girişi (Multiline / Enter Destekli) */} <Pressable
<View style={{ gap: 6 }}> onPress={() =>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
METİN İÇERİĞİ (ENTER İLE YENİ SATIR)
</Text>
<TextInput
value={item.text}
onChangeText={(t) =>
setFields((prev) => ({ setFields((prev) => ({
...prev, ...prev,
[selectedEditField]: { ...prev[selectedEditField], text: t }, [selectedEditField]: {
...prev[selectedEditField],
offsetY: (prev[selectedEditField].offsetY || 0) - 3,
},
})) }))
} }
placeholder={def.placeholder}
placeholderTextColor="#71717A"
multiline
style={{ style={{
backgroundColor: "#121215", flex: 1,
borderWidth: 1, backgroundColor: "#242429",
borderColor: "rgba(255,255,255,0.12)", paddingVertical: 8,
borderRadius: 14, borderRadius: 8,
paddingHorizontal: 14, alignItems: "center",
paddingVertical: 12, flexDirection: "row",
color: "#FFFFFF", justifyContent: "center",
fontSize: 14, gap: 4,
minHeight: 60,
}} }}
/> >
<Ionicons name="arrow-up" size={14} color="#D4AF37" />
<Text style={{ color: "#FFFFFF", fontSize: 12, fontWeight: "700" }}>Yukarı (-3px)</Text>
</Pressable>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: {
...prev[selectedEditField],
offsetY: (prev[selectedEditField].offsetY || 0) + 3,
},
}))
}
style={{
flex: 1,
backgroundColor: "#242429",
paddingVertical: 8,
borderRadius: 8,
alignItems: "center",
flexDirection: "row",
justifyContent: "center",
gap: 4,
}}
>
<Ionicons name="arrow-down" size={14} color="#D4AF37" />
<Text style={{ color: "#FFFFFF", fontSize: 12, fontWeight: "700" }}>Aşağı (+3px)</Text>
</Pressable>
{item.offsetY ? (
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: {
...prev[selectedEditField],
offsetY: 0,
},
}))
}
style={{
backgroundColor: "#3A2A2A",
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
}}
>
<Text style={{ color: "#EF4444", fontSize: 11, fontWeight: "700" }}>Sıfırla</Text>
</Pressable>
) : null}
</View> </View>
</View>
{/* DİKEY KONUM / YUKARI - AŞAĞI TAŞIMA (offsetY) */} {/* YAZI TİPİ (FONT) & BOYUT YAN YANA */}
<View style={{ gap: 8, backgroundColor: "#121215", padding: 12, borderRadius: 14 }}> <View style={{ gap: 6 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}> <Text style={{ color: "#D4AF37", fontSize: 11, fontWeight: "700" }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}> YAZI TİPİ (TÜRKÇE DESTEKLİ FONT)
DİKEY KONUM (YUKARI / AŞAĞI) </Text>
</Text> <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6 }}>
<Text style={{ color: "#E5C378", fontSize: 12, fontWeight: "800" }}> {FONT_OPTIONS.map((f) => {
{item.offsetY ? `${item.offsetY > 0 ? "+" : ""}${item.offsetY} px` : "Orijinal (0px)"} const isSelected = item.fontId === f.id;
</Text> return (
</View>
<View style={{ flexDirection: "row", gap: 8 }}>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: {
...prev[selectedEditField],
offsetY: (prev[selectedEditField].offsetY || 0) - 3,
},
}))
}
style={{
flex: 1,
backgroundColor: "#242429",
paddingVertical: 10,
borderRadius: 10,
alignItems: "center",
flexDirection: "row",
justifyContent: "center",
gap: 4,
}}
>
<Ionicons name="arrow-up" size={14} color="#D4AF37" />
<Text style={{ color: "#FFFFFF", fontSize: 12, fontWeight: "700" }}>Yukarı (-3px)</Text>
</Pressable>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: {
...prev[selectedEditField],
offsetY: (prev[selectedEditField].offsetY || 0) + 3,
},
}))
}
style={{
flex: 1,
backgroundColor: "#242429",
paddingVertical: 10,
borderRadius: 10,
alignItems: "center",
flexDirection: "row",
justifyContent: "center",
gap: 4,
}}
>
<Ionicons name="arrow-down" size={14} color="#D4AF37" />
<Text style={{ color: "#FFFFFF", fontSize: 12, fontWeight: "700" }}>Aşağı (+3px)</Text>
</Pressable>
{item.offsetY ? (
<Pressable <Pressable
key={f.id}
onPress={() => onPress={() =>
setFields((prev) => ({ setFields((prev) => ({
...prev, ...prev,
[selectedEditField]: { [selectedEditField]: { ...prev[selectedEditField], fontId: f.id },
...prev[selectedEditField],
offsetY: 0,
},
})) }))
} }
style={{ style={{
backgroundColor: "#3A2A2A", backgroundColor: isSelected ? "#D4AF37" : "#242429",
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 7,
borderRadius: 10, borderRadius: 10,
alignItems: "center", borderWidth: 1.5,
justifyContent: "center", borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.06)",
}} }}
> >
<Text style={{ color: "#EF4444", fontSize: 11, fontWeight: "700" }}>Sıfırla</Text> <Text
</Pressable>
) : null}
</View>
</View>
{/* YAZI TİPİ (FONT) SEÇİMİ */}
<View style={{ gap: 8 }}>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
YAZI TİPİ (TÜRKÇE DESTEKLİ FONT)
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
{FONT_OPTIONS.map((f) => {
const isSelected = item.fontId === f.id;
return (
<Pressable
key={f.id}
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: { ...prev[selectedEditField], fontId: f.id },
}))
}
style={{ style={{
backgroundColor: isSelected ? "#D4AF37" : "#242429", color: isSelected ? "#0C0C0E" : "#FFFFFF",
paddingHorizontal: 14, fontSize: 11.5,
paddingVertical: 10, fontWeight: isSelected ? "800" : "600",
borderRadius: 12,
borderWidth: 1.5,
borderColor: isSelected ? "#D4AF37" : "rgba(255,255,255,0.06)",
}} }}
> >
<Text {f.label}
style={{ </Text>
color: isSelected ? "#0C0C0E" : "#FFFFFF", </Pressable>
fontSize: 12, );
fontWeight: isSelected ? "800" : "500", })}
}} </ScrollView>
> </View>
{f.label}
</Text>
</Pressable>
);
})}
</View>
</View>
{/* YAZI BOYUTU (FONT SIZE) */} {/* YAZI BOYUTU (FONT SIZE) */}
<View style={{ gap: 8, backgroundColor: "#121215", padding: 12, borderRadius: 14 }}> <View
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}> style={{
YAZI BOYUTU flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "#121215",
padding: 10,
borderRadius: 12,
}}
>
<Text style={{ color: "#D4AF37", fontSize: 12, fontWeight: "700" }}>
YAZI BOYUTU:
</Text>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: {
...prev[selectedEditField],
fontSize: Math.max(def.min, (prev[selectedEditField].fontSize || 12) - 1),
},
}))
}
style={{
backgroundColor: "#242429",
paddingHorizontal: 14,
paddingVertical: 6,
borderRadius: 8,
}}
>
<Ionicons name="remove" size={16} color="#FFFFFF" />
</Pressable>
<Text style={{ color: "#D4AF37", fontSize: 16, fontWeight: "800", minWidth: 40, textAlign: "center" }}>
{item.fontSize || 12} px
</Text> </Text>
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
<Pressable
onPress={() =>
setFields((prev) => ({
...prev,
[selectedEditField]: {
...prev[selectedEditField],
fontSize: Math.max(def.min, (prev[selectedEditField].fontSize || 12) - 1),
},
}))
}
style={{
backgroundColor: "#242429",
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 10,
}}
>
<Ionicons name="remove" size={18} color="#FFFFFF" />
</Pressable>
<Text style={{ color: "#D4AF37", fontSize: 18, fontWeight: "800" }}> <Pressable
{item.fontSize || 12} px onPress={() =>
</Text> setFields((prev) => ({
...prev,
<Pressable [selectedEditField]: {
onPress={() => ...prev[selectedEditField],
setFields((prev) => ({ fontSize: Math.min(def.max, (prev[selectedEditField].fontSize || 12) + 1),
...prev, },
[selectedEditField]: { }))
...prev[selectedEditField], }
fontSize: Math.min(def.max, (prev[selectedEditField].fontSize || 12) + 1), style={{
}, backgroundColor: "#242429",
})) paddingHorizontal: 14,
} paddingVertical: 6,
style={{ borderRadius: 8,
backgroundColor: "#242429", }}
paddingHorizontal: 16, >
paddingVertical: 10, <Ionicons name="add" size={16} color="#FFFFFF" />
borderRadius: 10, </Pressable>
}}
>
<Ionicons name="add" size={18} color="#FFFFFF" />
</Pressable>
</View>
</View> </View>
</View>
</>
)}
</View>
);
})()}
{/* Kaydet & Tamamla Butonu */}
<Pressable
onPress={() => setSelectedEditField(null)}
style={{
backgroundColor: "#D4AF37",
borderRadius: 14,
paddingVertical: 14,
alignItems: "center",
marginTop: 6,
}}
>
<Text style={{ color: "#0C0C0E", fontSize: 15, fontWeight: "800" }}>
Tamamla & Uygula
</Text>
</Pressable>
</ScrollView>
</View>
);
})()}
</View>
</Modal>
View File