بناء تطبيقات الموبايل مع AI

إعداد تطبيق الموبايل مع Expo

5 دقيقة للقراءة

Project Goal: Build a cross-platform mobile app that works on iOS, Android, and web using Expo SDK 52 with AI assistance.

Why Expo for Vibe Coding?

Expo provides the fastest path from idea to mobile app. With Expo SDK 52+ and the new architecture:

  • Zero native setup - No Xcode/Android Studio initially required
  • Hot reloading - See changes instantly on your device
  • AI-friendly - Single JavaScript/TypeScript codebase is easier for AI to understand
  • Web support - Same code runs in browsers
  • EAS Build - Cloud builds without local native toolchains

Project Initialization Prompt

Create a new Expo SDK 52 mobile app with:

## Tech Stack
- Expo SDK 52 with New Architecture enabled
- Expo Router v4 for file-based navigation
- NativeWind v4 for Tailwind CSS styling
- Zustand for state management
- React Query for server state
- TypeScript strict mode

## Project Structure

/app # Expo Router screens /(tabs) # Tab navigation group /index.tsx # Home tab /explore.tsx # Explore tab /profile.tsx # Profile tab /(auth) # Auth screens (no tabs) /login.tsx /register.tsx /_layout.tsx # Root layout /components # Shared components /ui # Base UI components /lib # Utilities and helpers /hooks # Custom hooks /stores # Zustand stores /services # API services /constants # App constants and theme


## Initial Setup
1. Initialize with: npx create-expo-app@latest
2. Enable New Architecture in app.json
3. Configure NativeWind with CSS variables for theming
4. Set up path aliases (@/ for root)
5. Create base UI components: Button, Input, Card, Text

Understanding Expo Router v4

Expo Router uses file-based routing similar to Next.js:

// app/_layout.tsx - Root layout
import { Stack } from 'expo-router';
import { ThemeProvider } from '@/components/theme-provider';
import '../global.css';

export default function RootLayout() {
  return (
    <ThemeProvider>
      <Stack screenOptions={{ headerShown: false }}>
        <Stack.Screen name="(tabs)" />
        <Stack.Screen
          name="(auth)"
          options={{ presentation: 'modal' }}
        />
      </Stack>
    </ThemeProvider>
  );
}
// app/(tabs)/_layout.tsx - Tab navigation
import { Tabs } from 'expo-router';
import { Home, Search, User } from 'lucide-react-native';

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#6366f1',
        headerShown: true,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Home size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color, size }) => (
            <Search size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <User size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

NativeWind v4 Configuration

NativeWind brings Tailwind CSS to React Native:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,jsx,ts,tsx}',
    './components/**/*.{js,jsx,ts,tsx}',
  ],
  presets: [require('nativewind/preset')],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: 'var(--color-primary)',
          foreground: 'var(--color-primary-foreground)',
        },
        background: 'var(--color-background)',
        foreground: 'var(--color-foreground)',
        muted: {
          DEFAULT: 'var(--color-muted)',
          foreground: 'var(--color-muted-foreground)',
        },
        card: {
          DEFAULT: 'var(--color-card)',
          foreground: 'var(--color-card-foreground)',
        },
      },
    },
  },
  plugins: [],
};
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --color-primary: #6366f1;
  --color-primary-foreground: #ffffff;
  --color-background: #ffffff;
  --color-foreground: #09090b;
  --color-muted: #f4f4f5;
  --color-muted-foreground: #71717a;
  --color-card: #ffffff;
  --color-card-foreground: #09090b;
}

.dark {
  --color-primary: #818cf8;
  --color-primary-foreground: #09090b;
  --color-background: #09090b;
  --color-foreground: #fafafa;
  --color-muted: #27272a;
  --color-muted-foreground: #a1a1aa;
  --color-card: #18181b;
  --color-card-foreground: #fafafa;
}

Building Base UI Components

Create reusable components that work consistently across platforms:

// components/ui/button.tsx
import { Pressable, Text, View, ActivityIndicator } from 'react-native';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'flex-row items-center justify-center rounded-lg',
  {
    variants: {
      variant: {
        default: 'bg-primary',
        secondary: 'bg-muted',
        outline: 'border border-primary bg-transparent',
        ghost: 'bg-transparent',
        destructive: 'bg-red-500',
      },
      size: {
        default: 'h-12 px-6',
        sm: 'h-9 px-4',
        lg: 'h-14 px-8',
        icon: 'h-12 w-12',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

const textVariants = cva('font-semibold', {
  variants: {
    variant: {
      default: 'text-primary-foreground',
      secondary: 'text-foreground',
      outline: 'text-primary',
      ghost: 'text-foreground',
      destructive: 'text-white',
    },
    size: {
      default: 'text-base',
      sm: 'text-sm',
      lg: 'text-lg',
      icon: 'text-base',
    },
  },
  defaultVariants: {
    variant: 'default',
    size: 'default',
  },
});

interface ButtonProps extends VariantProps<typeof buttonVariants> {
  children: React.ReactNode;
  onPress?: () => void;
  disabled?: boolean;
  loading?: boolean;
  className?: string;
}

export function Button({
  children,
  variant,
  size,
  onPress,
  disabled,
  loading,
  className,
}: ButtonProps) {
  return (
    <Pressable
      onPress={onPress}
      disabled={disabled || loading}
      className={cn(
        buttonVariants({ variant, size }),
        disabled && 'opacity-50',
        className
      )}
    >
      {loading ? (
        <ActivityIndicator
          color={variant === 'default' ? '#fff' : '#6366f1'}
        />
      ) : (
        <Text className={cn(textVariants({ variant, size }))}>
          {children}
        </Text>
      )}
    </Pressable>
  );
}
// components/ui/input.tsx
import { TextInput, View, Text } from 'react-native';
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';

interface InputProps extends React.ComponentProps<typeof TextInput> {
  label?: string;
  error?: string;
  containerClassName?: string;
}

export const Input = forwardRef<TextInput, InputProps>(
  ({ label, error, containerClassName, className, ...props }, ref) => {
    return (
      <View className={cn('gap-1.5', containerClassName)}>
        {label && (
          <Text className="text-sm font-medium text-foreground">
            {label}
          </Text>
        )}
        <TextInput
          ref={ref}
          className={cn(
            'h-12 rounded-lg border border-muted bg-background px-4',
            'text-foreground placeholder:text-muted-foreground',
            'focus:border-primary',
            error && 'border-red-500',
            className
          )}
          placeholderTextColor="#71717a"
          {...props}
        />
        {error && (
          <Text className="text-sm text-red-500">{error}</Text>
        )}
      </View>
    );
  }
);

State Management with Zustand

// stores/auth-store.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
}

interface AuthState {
  user: User | null;
  token: string | null;
  isLoading: boolean;
  setUser: (user: User | null) => void;
  setToken: (token: string | null) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      isLoading: false,
      setUser: (user) => set({ user }),
      setToken: (token) => set({ token }),
      logout: () => set({ user: null, token: null }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

API Layer with React Query

// lib/api-client.ts
import { useAuthStore } from '@/stores/auth-store';

const API_URL = process.env.EXPO_PUBLIC_API_URL;

class ApiClient {
  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const token = useAuthStore.getState().token;

    const response = await fetch(`${API_URL}${endpoint}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...(token && { Authorization: `Bearer ${token}` }),
        ...options.headers,
      },
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || 'Request failed');
    }

    return response.json();
  }

  get<T>(endpoint: string) {
    return this.request<T>(endpoint);
  }

  post<T>(endpoint: string, data: unknown) {
    return this.request<T>(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

  put<T>(endpoint: string, data: unknown) {
    return this.request<T>(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
  }

  delete<T>(endpoint: string) {
    return this.request<T>(endpoint, { method: 'DELETE' });
  }
}

export const api = new ApiClient();
// services/posts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api-client';

interface Post {
  id: string;
  title: string;
  content: string;
  createdAt: string;
}

export function usePosts() {
  return useQuery({
    queryKey: ['posts'],
    queryFn: () => api.get<Post[]>('/posts'),
  });
}

export function useCreatePost() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: { title: string; content: string }) =>
      api.post<Post>('/posts', data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });
}

App Configuration

// app.json
{
  "expo": {
    "name": "MyApp",
    "slug": "myapp",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "automatic",
    "newArchEnabled": true,
    "splash": {
      "image": "./assets/splash-icon.png",
      "resizeMode": "contain",
      "backgroundColor": "#6366f1"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.myapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#6366f1"
      },
      "package": "com.yourcompany.myapp"
    },
    "web": {
      "bundler": "metro",
      "output": "static",
      "favicon": "./assets/favicon.png"
    },
    "plugins": [
      "expo-router",
      "expo-secure-store"
    ],
    "experiments": {
      "typedRoutes": true
    }
  }
}

AI Prompt: Debugging Mobile Issues

When you encounter mobile-specific issues, use this prompt template:

I'm getting this error in my Expo app:

[Paste the error message]

## Environment
- Expo SDK: 52
- Platform: iOS/Android/Web
- Device: Physical device / Simulator
- Running via: Expo Go / Development build

## Relevant code
[Paste the component or configuration causing issues]

## What I've tried
1. [List attempted solutions]

Please help me understand and fix this issue.

Testing on Devices

# Start development server
npx expo start

# Run on iOS simulator
npx expo run:ios

# Run on Android emulator
npx expo run:android

# Create development build for physical devices
eas build --profile development --platform ios
eas build --profile development --platform android

Key Takeaways

  1. Expo Router provides file-based navigation similar to Next.js
  2. NativeWind v4 enables Tailwind CSS with CSS variables for theming
  3. Zustand + React Query handle local and server state efficiently
  4. Component variants (using CVA) create consistent, reusable UI
  5. Development builds are needed for native modules not in Expo Go

إعداد تطبيق الموبايل مع Expo

هدف المشروع: بناء تطبيق موبايل متعدد المنصات يعمل على iOS وAndroid والويب باستخدام Expo SDK 52 بمساعدة AI.

لماذا Expo للـ Vibe Coding؟

Expo يوفر أسرع طريق من الفكرة إلى تطبيق الموبايل. مع Expo SDK 52+ والهندسة الجديدة:

  • صفر إعداد native - لا حاجة لـ Xcode/Android Studio في البداية
  • إعادة التحميل الفوري - شاهد التغييرات فوراً على جهازك
  • صديق للـ AI - قاعدة كود JavaScript/TypeScript واحدة أسهل للـ AI لفهمها
  • دعم الويب - نفس الكود يعمل في المتصفحات
  • EAS Build - بناء سحابي بدون أدوات native محلية

بروم�ت تهيئة المشروع

أنشئ تطبيق Expo SDK 52 موبايل جديد مع:

## التقنيات المستخدمة
- Expo SDK 52 مع تفعيل الهندسة الجديدة
- Expo Router v4 للتنقل المبني على الملفات
- NativeWind v4 لتنسيقات Tailwind CSS
- Zustand لإدارة الحالة
- React Query لحالة الخادم
- TypeScript وضع صارم

## هيكل المشروع

/app # شاشات Expo Router /(tabs) # مجموعة التنقل بالتابات /index.tsx # تاب الرئيسية /explore.tsx # تاب الاستكشاف /profile.tsx # تاب الملف الشخصي /(auth) # شاشات المصادقة (بدون تابات) /login.tsx /register.tsx /_layout.tsx # التخطيط الجذري /components # المكونات المشتركة /ui # مكونات UI الأساسية /lib # الأدوات المساعدة /hooks # الهوكس المخصصة /stores # مخازن Zustand /services # خدمات API /constants # الثوابت والثيم


## الإعداد الأولي
1. التهيئة بـ: npx create-expo-app@latest
2. تفعيل الهندسة الجديدة في app.json
3. تكوين NativeWind مع متغيرات CSS للثيمات
4. إعداد اختصارات المسارات (@/ للجذر)
5. إنشاء مكونات UI الأساسية: Button, Input, Card, Text

فهم Expo Router v4

Expo Router يستخدم التوجيه المبني على الملفات مشابهاً لـ Next.js:

// app/_layout.tsx - التخطيط الجذري
import { Stack } from 'expo-router';
import { ThemeProvider } from '@/components/theme-provider';
import '../global.css';

export default function RootLayout() {
  return (
    <ThemeProvider>
      <Stack screenOptions={{ headerShown: false }}>
        <Stack.Screen name="(tabs)" />
        <Stack.Screen
          name="(auth)"
          options={{ presentation: 'modal' }}
        />
      </Stack>
    </ThemeProvider>
  );
}
// app/(tabs)/_layout.tsx - التنقل بالتابات
import { Tabs } from 'expo-router';
import { Home, Search, User } from 'lucide-react-native';

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#6366f1',
        headerShown: true,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'الرئيسية',
          tabBarIcon: ({ color, size }) => (
            <Home size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'استكشاف',
          tabBarIcon: ({ color, size }) => (
            <Search size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'الملف الشخصي',
          tabBarIcon: ({ color, size }) => (
            <User size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

تكوين NativeWind v4

NativeWind يجلب Tailwind CSS إلى React Native:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,jsx,ts,tsx}',
    './components/**/*.{js,jsx,ts,tsx}',
  ],
  presets: [require('nativewind/preset')],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: 'var(--color-primary)',
          foreground: 'var(--color-primary-foreground)',
        },
        background: 'var(--color-background)',
        foreground: 'var(--color-foreground)',
        muted: {
          DEFAULT: 'var(--color-muted)',
          foreground: 'var(--color-muted-foreground)',
        },
        card: {
          DEFAULT: 'var(--color-card)',
          foreground: 'var(--color-card-foreground)',
        },
      },
    },
  },
  plugins: [],
};
/* global.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --color-primary: #6366f1;
  --color-primary-foreground: #ffffff;
  --color-background: #ffffff;
  --color-foreground: #09090b;
  --color-muted: #f4f4f5;
  --color-muted-foreground: #71717a;
  --color-card: #ffffff;
  --color-card-foreground: #09090b;
}

.dark {
  --color-primary: #818cf8;
  --color-primary-foreground: #09090b;
  --color-background: #09090b;
  --color-foreground: #fafafa;
  --color-muted: #27272a;
  --color-muted-foreground: #a1a1aa;
  --color-card: #18181b;
  --color-card-foreground: #fafafa;
}

بناء مكونات UI الأساسية

إنشاء مكونات قابلة لإعادة الاستخدام تعمل بشكل متسق عبر المنصات:

// components/ui/button.tsx
import { Pressable, Text, View, ActivityIndicator } from 'react-native';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'flex-row items-center justify-center rounded-lg',
  {
    variants: {
      variant: {
        default: 'bg-primary',
        secondary: 'bg-muted',
        outline: 'border border-primary bg-transparent',
        ghost: 'bg-transparent',
        destructive: 'bg-red-500',
      },
      size: {
        default: 'h-12 px-6',
        sm: 'h-9 px-4',
        lg: 'h-14 px-8',
        icon: 'h-12 w-12',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

const textVariants = cva('font-semibold', {
  variants: {
    variant: {
      default: 'text-primary-foreground',
      secondary: 'text-foreground',
      outline: 'text-primary',
      ghost: 'text-foreground',
      destructive: 'text-white',
    },
    size: {
      default: 'text-base',
      sm: 'text-sm',
      lg: 'text-lg',
      icon: 'text-base',
    },
  },
  defaultVariants: {
    variant: 'default',
    size: 'default',
  },
});

interface ButtonProps extends VariantProps<typeof buttonVariants> {
  children: React.ReactNode;
  onPress?: () => void;
  disabled?: boolean;
  loading?: boolean;
  className?: string;
}

export function Button({
  children,
  variant,
  size,
  onPress,
  disabled,
  loading,
  className,
}: ButtonProps) {
  return (
    <Pressable
      onPress={onPress}
      disabled={disabled || loading}
      className={cn(
        buttonVariants({ variant, size }),
        disabled && 'opacity-50',
        className
      )}
    >
      {loading ? (
        <ActivityIndicator
          color={variant === 'default' ? '#fff' : '#6366f1'}
        />
      ) : (
        <Text className={cn(textVariants({ variant, size }))}>
          {children}
        </Text>
      )}
    </Pressable>
  );
}
// components/ui/input.tsx
import { TextInput, View, Text } from 'react-native';
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';

interface InputProps extends React.ComponentProps<typeof TextInput> {
  label?: string;
  error?: string;
  containerClassName?: string;
}

export const Input = forwardRef<TextInput, InputProps>(
  ({ label, error, containerClassName, className, ...props }, ref) => {
    return (
      <View className={cn('gap-1.5', containerClassName)}>
        {label && (
          <Text className="text-sm font-medium text-foreground">
            {label}
          </Text>
        )}
        <TextInput
          ref={ref}
          className={cn(
            'h-12 rounded-lg border border-muted bg-background px-4',
            'text-foreground placeholder:text-muted-foreground',
            'focus:border-primary',
            error && 'border-red-500',
            className
          )}
          placeholderTextColor="#71717a"
          {...props}
        />
        {error && (
          <Text className="text-sm text-red-500">{error}</Text>
        )}
      </View>
    );
  }
);

إدارة الحالة مع Zustand

// stores/auth-store.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
}

interface AuthState {
  user: User | null;
  token: string | null;
  isLoading: boolean;
  setUser: (user: User | null) => void;
  setToken: (token: string | null) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      isLoading: false,
      setUser: (user) => set({ user }),
      setToken: (token) => set({ token }),
      logout: () => set({ user: null, token: null }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

طبقة API مع React Query

// lib/api-client.ts
import { useAuthStore } from '@/stores/auth-store';

const API_URL = process.env.EXPO_PUBLIC_API_URL;

class ApiClient {
  private async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const token = useAuthStore.getState().token;

    const response = await fetch(`${API_URL}${endpoint}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...(token && { Authorization: `Bearer ${token}` }),
        ...options.headers,
      },
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new Error(error.message || 'Request failed');
    }

    return response.json();
  }

  get<T>(endpoint: string) {
    return this.request<T>(endpoint);
  }

  post<T>(endpoint: string, data: unknown) {
    return this.request<T>(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

  put<T>(endpoint: string, data: unknown) {
    return this.request<T>(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
  }

  delete<T>(endpoint: string) {
    return this.request<T>(endpoint, { method: 'DELETE' });
  }
}

export const api = new ApiClient();
// services/posts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api-client';

interface Post {
  id: string;
  title: string;
  content: string;
  createdAt: string;
}

export function usePosts() {
  return useQuery({
    queryKey: ['posts'],
    queryFn: () => api.get<Post[]>('/posts'),
  });
}

export function useCreatePost() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: { title: string; content: string }) =>
      api.post<Post>('/posts', data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });
}

تكوين التطبيق

// app.json
{
  "expo": {
    "name": "MyApp",
    "slug": "myapp",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "automatic",
    "newArchEnabled": true,
    "splash": {
      "image": "./assets/splash-icon.png",
      "resizeMode": "contain",
      "backgroundColor": "#6366f1"
    },
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.myapp"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#6366f1"
      },
      "package": "com.yourcompany.myapp"
    },
    "web": {
      "bundler": "metro",
      "output": "static",
      "favicon": "./assets/favicon.png"
    },
    "plugins": [
      "expo-router",
      "expo-secure-store"
    ],
    "experiments": {
      "typedRoutes": true
    }
  }
}

برومبت AI: تصحيح مشاكل الموبايل

عندما تواجه مشاكل خاصة بالموبايل، استخدم قالب البرومبت هذا:

أحصل على هذا الخطأ في تطبيق Expo:

[الصق رسالة الخطأ]

## البيئة
- Expo SDK: 52
- المنصة: iOS/Android/Web
- الجهاز: جهاز فعلي / محاكي
- التشغيل عبر: Expo Go / Development build

## الكود ذو الصلة
[الصق المكون أو التكوين المسبب للمشكلة]

## ما جربته
1. [اذكر الحلول المجربة]

من فضلك ساعدني في فهم وإصلاح هذه المشكلة.

الاختبار على الأجهزة

# بدء خادم التطوير
npx expo start

# التشغيل على محاكي iOS
npx expo run:ios

# التشغيل على محاكي Android
npx expo run:android

# إنشاء development build للأجهزة الفعلية
eas build --profile development --platform ios
eas build --profile development --platform android

النقاط الرئيسية

  1. Expo Router يوفر تنقلاً مبنياً على الملفات مشابهاً لـ Next.js
  2. NativeWind v4 يُمكّن Tailwind CSS مع متغيرات CSS للثيمات
  3. Zustand + React Query يتعاملان مع الحالة المحلية وحالة الخادم بكفاءة
  4. متغيرات المكونات (باستخدام CVA) تنشئ UI متسق وقابل لإعادة الاستخدام
  5. Development builds مطلوبة للوحدات الأصلية غير الموجودة في Expo Go

اختبار

الوحدة 2: بناء تطبيقات الموبايل مع AI

خذ الاختبار