Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div align="center">

<img src="docs/screenshots/icon.png" width="120" alt="AppFromAI logo" />

# AppFromAI

### *Describe it. Build it. Run it. Instantly.*
Expand Down Expand Up @@ -200,6 +202,13 @@ The game engine is declarative: the AI describes the scene as an array of object

AppFromAI generates the game loop, the renderer, the gamepad layout, the score state, and the collision logic — all in one shot.

**Recent game engine additions:**
- Per-object physics (`vx`, `vy`, `gravity`) applied automatically every tick — no manual physics code needed
- `fps` property on `gameView` to set frame rate declaratively (10–60 fps)
- `onCollideAction` — fired automatically on AABB overlap between any two identified objects
- `onOutOfBoundsAction` — fired when an object leaves the canvas bounds
- Global `gravity` on the `gameView` node, applied to all objects at once

**Gamepad layouts:**
- `row` — horizontal button row (left / right / jump)
- `dpad` — cross layout (↑ ← → ↓) for 4-directional movement
Expand Down Expand Up @@ -403,6 +412,7 @@ src/
## Roadmap

### 🧩 New UI components
- [x] **WebView** — embed any website or web content inline inside a generated module; the AI can generate a `webview` node that opens in an in-app browser with a single tap
- [ ] **Charts** — bar, line, and pie chart components in the declarative UI tree; generated modules can visualize data without a single line of chart code
- [ ] **Map view** — display GPS coordinates and routes on an interactive map, directly inside any generated module
- [ ] **Rich list items** — list rows with avatar, subtitle, badge, and swipe-to-delete, so the AI can generate beautiful data-driven apps instead of plain text lists
Expand All @@ -413,6 +423,7 @@ src/
- [ ] **Biometric lock** — protect individual modules with Face ID or fingerprint; one setting, zero code

### 🤝 Sharing & community
- [x] **Module rename** — tap the module name to rename it directly from the library screen, without regenerating
- [ ] **Module export & import** — export any module as a single JSON file and share it via AirDrop, link, or QR code; anyone with AppFromAI can import and run it instantly
- [ ] **Module marketplace** — a curated feed of community-built modules; browse, preview, and install in one tap

Expand Down
9 changes: 7 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
"NSAppTransportSecurity": {
"NSAllowsLocalNetworking": true
},
"LSApplicationQueriesSchemes": ["mailto", "tel", "sms"]
"LSApplicationQueriesSchemes": [
"mailto",
"tel",
"sms"
]
},
"bundleIdentifier": "com.afi.appfromai"
},
Expand Down Expand Up @@ -77,7 +81,8 @@
"icon": "./assets/icon.png",
"color": "#111827"
}
]
],
"expo-web-browser"
],
"extra": {
"router": {}
Expand Down
21 changes: 9 additions & 12 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { StatusBar } from 'expo-status-bar';
import { Ionicons } from '@expo/vector-icons';
import { generateModule } from '../../src/ai/aiClient';
import { saveModule } from '../../src/modules/moduleStore';
import { getJsonResponseRetryHint } from '../../src/ai/modulePrompt';
import { useSettings } from '../../src/settings/SettingsContext';
import { DinoGame } from '../../src/components/DinoGame';
import { useI18n } from '../../src/i18n/useI18n';
import { useDeviceLayout } from '../../src/utils/deviceLayout';

/* ── Design tokens ── */
const C = {
Expand All @@ -44,6 +44,7 @@ const C = {
export default function GeneraScreen() {
const { settings } = useSettings();
const { t } = useI18n();
const { isTablet } = useDeviceLayout();
const [prompt, setPrompt] = useState('');
const [focused, setFocused] = useState(false);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -76,7 +77,7 @@ export default function GeneraScreen() {
startPulse();
try {
const res = await generateModule(prompt.trim(), {
useMock: settings.useMock,
useMock: false,
...(settings.provider === 'ollama'
? {
ollamaBaseUrl: settings.ollamaUrl || undefined,
Expand Down Expand Up @@ -109,9 +110,7 @@ export default function GeneraScreen() {
}
};

const providerLabel = settings.useMock
? 'Mock'
: settings.provider === 'ollama'
const providerLabel = settings.provider === 'ollama'
? settings.ollamaModel || 'Ollama'
: settings.provider === 'claude'
? settings.claudeModel || 'Claude'
Expand All @@ -127,7 +126,7 @@ export default function GeneraScreen() {
>
<ScrollView
style={s.flex}
contentContainerStyle={s.scrollContent}
contentContainerStyle={[s.scrollContent, isTablet && s.scrollTablet]}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
Expand All @@ -138,7 +137,7 @@ export default function GeneraScreen() {
<Text style={s.brand}>AppFromAI</Text>
</View>
<View style={s.modelPill}>
<View style={[s.modelDot, settings.useMock && s.modelDotMock]} />
<View style={s.modelDot} />
<Text style={s.modelText}>{providerLabel}</Text>
</View>
</View>
Expand Down Expand Up @@ -215,9 +214,6 @@ export default function GeneraScreen() {
<View style={[s.feedbackInner, s.feedbackError]}>
<Ionicons name="alert-circle" size={15} color={C.error} />
<View style={s.feedbackText}>
{error !== t.errorEmpty ? (
<Text style={s.feedbackHint}>{getJsonResponseRetryHint()}</Text>
) : null}
<Text style={s.feedbackMsg}>{error}</Text>
</View>
</View>
Expand All @@ -238,7 +234,7 @@ export default function GeneraScreen() {
</ScrollView>

{/* ── CTA — always visible, outside ScrollView ── */}
<View style={s.footer}>
<View style={[s.footer, isTablet && s.footerTablet]}>
<Pressable
style={({ pressed }) => [
s.btn,
Expand Down Expand Up @@ -273,6 +269,7 @@ const s = StyleSheet.create({
safe: { flex: 1, backgroundColor: C.bg },
flex: { flex: 1 },
scrollContent: { flexGrow: 1 },
scrollTablet: { maxWidth: 640, width: '100%', alignSelf: 'center' },

/* Top bar */
topBar: {
Expand Down Expand Up @@ -308,7 +305,6 @@ const s = StyleSheet.create({
borderColor: C.border,
},
modelDot: { width: 6, height: 6, borderRadius: 3, backgroundColor: C.primary },
modelDotMock: { backgroundColor: '#f59e0b' },
modelText: { color: C.muted, fontSize: 12, fontWeight: '600' },

/* Hero */
Expand Down Expand Up @@ -483,6 +479,7 @@ const s = StyleSheet.create({
padding: 20,
paddingBottom: Platform.OS === 'ios' ? 8 : 16,
},
footerTablet: { maxWidth: 640, width: '100%', alignSelf: 'center' },
btn: {
backgroundColor: C.primary,
borderRadius: 18,
Expand Down
Loading
Loading