zafer-skills

安装量: 265
排名: #3349

安装

npx skills add https://github.com/zaferayan/skills --skill zafer-skills

IMPORTANT: This is a SKILL file, NOT a project. NEVER run npm/bun install in this folder. NEVER create code files here. When creating a new project, ALWAYS ask the user for the project path first or create it in a separate directory (e.g., ~/Projects/app-name).

This guide is created to provide context when working with Expo projects using Claude Code.

MANDATORY REQUIREMENTS

When creating a new Expo project, you MUST include ALL of the following:

Required Screens (ALWAYS CREATE)

src/app/onboarding.tsx - Swipe-based onboarding with fullscreen background video and gradient overlay src/app/paywall.tsx - RevenueCat paywall screen (shown after onboarding) src/app/settings.tsx - Settings screen with language, theme, notifications, and reset onboarding options

Onboarding Video Implementation (REQUIRED)

The onboarding screen MUST have a fullscreen background video. Use a URL, not a local file:

import { useVideoPlayer, VideoView } from "expo-video";

const VIDEO_URL =
  "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";

const player = useVideoPlayer(VIDEO_URL, (player) => {
  player.loop = true;
  player.muted = true;
  player.play();
});

// In render:
<VideoView
  player={player}
  style={StyleSheet.absoluteFill}
  contentFit="cover"
  nativeControls={false}
/>;

Do NOT just import expo-video without actually using the VideoView component.

Required Navigation (ALWAYS USE)

Use NativeTabs from expo-router/unstable-native-tabs for tab navigation - NEVER use @react-navigation/bottom-tabs or Tabs from expo-router

Required Context Providers (ALWAYS WRAP)

import { ThemeProvider } from "@/context/theme-context";
import {
  DarkTheme,
  DefaultTheme,
  ThemeProvider as NavigationThemeProvider,
} from "@react-navigation/native";

<ThemeProvider>
  <OnboardingProvider>
    <AdsProvider>
      <NavigationThemeProvider
        value={colorScheme === "dark" ? DarkTheme : DefaultTheme}
      >
        <Stack />
      </NavigationThemeProvider>
    </AdsProvider>
  </OnboardingProvider>
</ThemeProvider>;

Required Libraries (ALWAYS INSTALL)

Use npx expo install to install libraries (NOT npm/yarn/bun install):

npx expo install react-native-purchases react-native-google-mobile-ads expo-notifications i18next react-i18next expo-localization react-native-reanimated expo-video expo-audio expo-sqlite expo-linear-gradient

Libraries:

  • react-native-purchases (RevenueCat)

  • react-native-google-mobile-ads (AdMob)

  • expo-notifications

  • i18next + react-i18next + expo-localization

  • react-native-reanimated

  • expo-video + expo-audio

  • expo-sqlite (for localStorage)

  • expo-linear-gradient (for gradient overlays)

AdMob Configuration (REQUIRED in app.json)

You MUST add this to app.json for AdMob to work:

{
  "expo": {
    "plugins": [
      [
        "react-native-google-mobile-ads",
        {
          "androidAppId": "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy",
          "iosAppId": "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"
        }
      ]
    ]
  }
}

For development/testing, use test App IDs:

  • iOS: ca-app-pub-3940256099942544~1458002511

  • Android: ca-app-pub-3940256099942544~3347511713

Do NOT skip this configuration or the app will crash with GADInvalidInitializationException.

You MUST implement banner ads in the Tab layout. Use this pattern:

import { View, StyleSheet } from 'react-native';
import { NativeTabs } from 'expo-router/unstable-native-tabs';
import { useTranslation } from 'react-i18next';
import { BannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads';
import { useAds } from '@/context/ads-context';

const adUnitId = __DEV__
  ? TestIds.BANNER
  : 'ca-app-pub-xxxxxxxxxxxxxxxx/yyyyyyyyyy';

export default function TabLayout() {
  const { t } = useTranslation();
  const { shouldShowAds } = useAds();

  return (
    <View style={styles.container}>
      <NativeTabs>
        <NativeTabs.Trigger name="index">
          <NativeTabs.Trigger.Label>{t('tabs.home')}</NativeTabs.Trigger.Label>
          <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
        </NativeTabs.Trigger>
        <NativeTabs.Trigger name="settings">
          <NativeTabs.Trigger.Label>{t('tabs.settings')}</NativeTabs.Trigger.Label>
          <NativeTabs.Trigger.Icon sf="gear" md="settings" />
        </NativeTabs.Trigger>
      </NativeTabs>

      {shouldShowAds && (
        <View style={styles.adContainer}>
          <BannerAd
            unitId={adUnitId}
            size={BannerAdSize.ANCHORED_ADAPTIVE_BANNER}
            requestOptions={{
              requestNonPersonalizedAdsOnly: true,
            }}
          />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  adContainer: {
    alignItems: 'center',
    paddingBottom: 10,
  },
});
  • ALWAYS use TestIds.BANNER in development

  • Banner ad is placed below NativeTabs in the Tab layout

  • Use useAds context to check shouldShowAds (hides for premium users)

TURKISH LOCALIZATION (IMPORTANT)

When writing tr.json, you MUST use correct Turkish characters:

  • ı (lowercase dotless i) - NOT i

  • İ (uppercase dotted I) - NOT I

  • ü, Ü, ö, Ö, ç, Ç, ş, Ş, ğ, Ğ

Example:

  • ✅ "Ayarlar", "Giriş", "Çıkış", "Başla", "İleri", "Güncelle"

  • ❌ "Ayarlar", "Giris", "Cikis", "Basla", "Ileri", "Guncelle"

FORBIDDEN (NEVER USE)

  • ❌ AsyncStorage - Use expo-sqlite/localStorage/install instead

  • ❌ lineHeight style - Use padding/margin instead

  • Tabs from expo-router - Use NativeTabs instead

  • @react-navigation/bottom-tabs - Use NativeTabs instead

  • expo-av - Use expo-video for video, expo-audio for audio instead

  • expo-ads-admob - Use react-native-google-mobile-ads instead

  • ❌ Any other ads library - ONLY use react-native-google-mobile-ads

  • ❌ Reanimated hooks inside callbacks - Call at component top level

Reanimated Usage (IMPORTANT)

NEVER call useAnimatedStyle, useSharedValue, or other reanimated hooks inside callbacks, loops, or conditions.

❌ WRONG:

const renderItem = () => {
  const animatedStyle = useAnimatedStyle(() => ({ opacity: 1 })); // ERROR!
  return <Animated.View style={animatedStyle} />;
};

✅ CORRECT:

function MyComponent() {
  const animatedStyle = useAnimatedStyle(() => ({ opacity: 1 })); // Top level
  return <Animated.View style={animatedStyle} />;
}

For lists, create a separate component for each item:

function AnimatedItem({ item }) {
  const animatedStyle = useAnimatedStyle(() => ({ opacity: 1 }));
  return <Animated.View style={animatedStyle}>{item.name}</Animated.View>;
}

// In FlatList:
renderItem={({ item }) => <AnimatedItem item={item} />}

POST-CREATION CLEANUP (ALWAYS DO)

After creating a new Expo project, you MUST:

  • If using (tabs) folder, DELETE src/app/index.tsx to avoid route conflicts:
rm src/app/index.tsx
  • Check and remove lineHeight from these files:

  • src/components/themed-text.tsx (comes with lineHeight by default - REMOVE IT)

  • Any other component using lineHeight

Search and remove all lineHeight occurrences:

grep -r "lineHeight" src/

Replace with padding or margin instead.

AFTER COMPLETING CODE (ALWAYS RUN)

When you finish writing/modifying code, you MUST run these commands in order:

npx expo install --fix
npx expo prebuild --clean
  • install --fix fixes dependency version mismatches

  • prebuild --clean recreates ios and android folders

Do NOT skip these steps.

Project Creation

When user asks to create an app, you MUST:

  • FIRST ask for the bundle ID (e.g., "What is the bundle ID? Example: com.company.appname")

  • Create the project in the CURRENT directory using:

bunx create-expo -t default@next app-name
  • Update app.json with the bundle ID:
{
  "expo": {
    "ios": {
      "bundleIdentifier": "com.company.appname"
    },
    "android": {
      "package": "com.company.appname"
    }
  }
}
  • Then cd into the project and start implementing all required screens

  • Do NOT ask for project path - always use current directory

Technology Stack

  • Framework: Expo, React Native

  • Navigation: Expo Router (file-based routing), NativeTabs

  • State Management: React Context API

  • Translations: i18next, react-i18next

  • Purchases: RevenueCat (react-native-purchases)

  • Advertisements: Google AdMob (react-native-google-mobile-ads)

  • Notifications: expo-notifications

  • Animations: react-native-reanimated

  • Storage: localStorage via expo-sqlite polyfill

WARNING: DO NOT USE AsyncStorage! Use expo-sqlite polyfill instead.

  • Example usage
import "expo-sqlite/localStorage/install";

globalThis.localStorage.setItem("key", "value");
console.log(globalThis.localStorage.getItem("key")); // 'value'

WARNING: NEVER USE lineHeight! It causes layout issues in React Native. Use padding or margin instead.

Project Structure

project-root/
├── src/
│   ├── app/
│   │   ├── _layout.tsx
│   │   ├── index.tsx
│   │   ├── explore.tsx
│   │   ├── settings.tsx
│   │   ├── paywall.tsx
│   │   └── onboarding.tsx
│   ├── components/
│   │   ├── ui/
│   │   ├── themed-text.tsx
│   │   └── themed-view.tsx
│   ├── constants/
│   │   ├── theme.ts
│   │   └── [data-files].ts
│   ├── context/
│   │   ├── onboarding-context.tsx
│   │   └── ads-context.tsx
│   ├── hooks/
│   │   ├── use-notifications.ts
│   │   └── use-color-scheme.ts
│   ├── lib/
│   │   ├── notifications.ts
│   │   ├── purchases.ts
│   │   ├── ads.ts
│   │   └── i18n.ts
│   └── locales/
│       ├── tr.json
│       └── en.json
├── assets/
│   └── images/
├── ios/
├── android/
├── app.json
├── eas.json
├── package.json
└── tsconfig.json

Tab Navigation (NativeTabs)

Expo Router uses NativeTabs for native tab navigation:

import { NativeTabs } from "expo-router/unstable-native-tabs";

export default function TabLayout() {
  return (
    <NativeTabs>
      <NativeTabs.Trigger name="index">
        <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
        <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
      </NativeTabs.Trigger>
      <NativeTabs.Trigger name="explore">
        <NativeTabs.Trigger.Label>Explore</NativeTabs.Trigger.Label>
        <NativeTabs.Trigger.Icon sf="compass.fill" md="explore" />
      </NativeTabs.Trigger>
      <NativeTabs.Trigger name="settings">
        <NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
        <NativeTabs.Trigger.Icon sf="gear" md="settings" />
      </NativeTabs.Trigger>
    </NativeTabs>
  );
}

NativeTabs Properties

  • sf: SF Symbols icon name (iOS)

  • md: Material Design icon name (Android)

  • name: Route file name

  • Tab order follows trigger order

Common Icons

| Home | house.fill | home

| Explore | compass.fill | explore

| Settings | gear | settings

| Profile | person.fill | person

| Search | magnifyingglass | search

| Favorites | heart.fill | favorite

| Notifications | bell.fill | notifications

Development Commands

bun install
bun start
bun ios
bun android
bun lint
npx expo install --fix
npx expo prebuild --clean

EAS Build Commands

eas build --profile development --platform ios
eas build --profile development --platform android
eas build --profile production --platform ios
eas build --profile production --platform android
eas submit --platform ios
eas submit --platform android

Important Modules

RevenueCat

  • File: lib/purchases.ts

  • Used for premium access

  • Paywall: app/paywall.tsx

AdMob

  • File: src/lib/ads.ts

  • Ads disabled for premium users

  • Test IDs must be used in development

Notifications

  • Files: src/lib/notifications.ts, src/hooks/use-notifications.ts

  • iOS requires push notification entitlement

Onboarding & Paywall Flow (CRITICAL)

  • Files: src/app/onboarding.tsx, src/app/paywall.tsx

  • Swipe-based screens with fullscreen background video

  • Gradient overlay on video

  • IMPORTANT: Paywall MUST appear immediately after onboarding completes

// In onboarding.tsx - when user completes onboarding:
const handleComplete = async () => {
  await setOnboardingCompleted(true);
  router.replace('/paywall'); // Navigate to paywall immediately
};
// In paywall.tsx - after purchase or skip:
const handleContinue = () => {
  router.replace('/(tabs)'); // Navigate to main app
};

Flow: Onboarding → Paywall → Main App (tabs)

Paywall Subscription Options (REQUIRED)

Paywall MUST have two subscription options:

  • Weekly - Default option

  • Yearly - With "50% OFF" badge (recommended, should be highlighted)

// Subscription option component example:
const subscriptionOptions = [
  {
    id: 'weekly',
    title: t('paywall.weekly'),
    price: '$4.99/week',
    selected: selectedPlan === 'weekly',
  },
  {
    id: 'yearly',
    title: t('paywall.yearly'),
    price: '$129.99/year',
    badge: '50% OFF',
    selected: selectedPlan === 'yearly',
  },
];

// Yearly option should be visually highlighted as the best value
  • Yearly option should show the discount badge prominently

  • Default selection can be weekly, but yearly should be visually recommended

  • Use RevenueCat package identifiers to match these options

Settings Screen Options (REQUIRED)

Settings screen MUST include:

  • Language - Change app language

  • Theme - Light/Dark/System

  • Notifications - Enable/disable notifications

  • Remove Ads - Navigate to paywall (hidden if already premium)

  • Reset Onboarding - Restart onboarding flow (for testing/demo)

const { isPremium } = usePurchases();

// Remove Ads - navigates to paywall
const handleRemoveAds = () => {
  router.push('/paywall');
};

// Reset onboarding
const handleResetOnboarding = async () => {
  await setOnboardingCompleted(false);
  router.replace('/onboarding');
};

// In settings list:
{!isPremium && (
  <SettingsItem
    title={t('settings.removeAds')}
    icon="crown.fill"
    onPress={handleRemoveAds}
  />
)}

<SettingsItem
  title={t('settings.resetOnboarding')}
  icon="arrow.counterclockwise"
  onPress={handleResetOnboarding}
/>

Localization

  • File: lib/i18n.ts

  • Languages stored in locales/

  • App restarts on language change

Coding Standards

  • Use functional components

  • Strict TypeScript

  • Avoid hardcoded strings

  • Use padding instead of lineHeight

  • Use memoization when necessary

Context Providers

<ThemeProvider>
  <OnboardingProvider>
    <AdsProvider>
      <Stack />
    </AdsProvider>
  </OnboardingProvider>
</ThemeProvider>

useColorScheme Hook

File: src/hooks/use-color-scheme.ts

import { useThemeContext } from '@/context/theme-context';

export function useColorScheme(): 'light' | 'dark' | 'unspecified' {
  const { isDark } = useThemeContext();
  return isDark ? 'dark' : 'light';
}

Important Notes

  • iOS permissions are defined in app.json

  • Android permissions are defined in app.json

  • Enable new architecture via newArchEnabled: true

  • Enable typed routes via experiments.typedRoutes

App Store & Play Store Notes

  • iOS ATT permission required

  • Restore purchases must work correctly

  • Target SDK must be up to date

Testing Checklist

  • UI tested in all languages

  • Dark / Light mode

  • Notifications

  • Premium flow

  • Restore purchases

  • Offline support

  • Multiple screen sizes

After Development

npx expo prebuild --clean
bun ios
bun android

NOTE: prebuild --clean recreates ios and android folders. Run it after modifying native modules or app.json.

返回排行榜