react-native-sop-docs/04-design-integration/assets.md

6.3 KiB

Asset Management

Folder Structure

src/assets/
├── images/
│   ├── logo.png
│   ├── logo@2x.png
│   └── logo@3x.png
├── icons/
│   └── index.ts
└── fonts/
    ├── Montserrat-Regular.ttf
    └── Montserrat-Bold.ttf

Image Management

Create image index file:

// src/assets/images/index.ts
export const Images = {
  logo: require('./logo.png'),
  splash: require('./splash.png'),
  placeholder: require('./placeholder.png'),
  // Add more images here
};

Usage in components:

import {Images} from '@assets/images';
import {Image} from 'react-native';

const MyComponent = () => (
  <Image source={Images.logo} style={{width: 100, height: 100}} />
);

Icon Management

Install react-native-vector-icons:

yarn add react-native-vector-icons
yarn add -D @types/react-native-vector-icons

# iOS setup
cd ios && pod install

Create icon component:

// src/components/common/Icon/Icon.tsx
import React from 'react';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import Ionicons from 'react-native-vector-icons/Ionicons';
import FontAwesome from 'react-native-vector-icons/FontAwesome';

type IconFamily = 'MaterialIcons' | 'Ionicons' | 'FontAwesome';

interface IconProps {
  name: string;
  size?: number;
  color?: string;
  family?: IconFamily;
}

const IconComponents = {
  MaterialIcons,
  Ionicons,
  FontAwesome,
};

export const Icon: React.FC<IconProps> = ({
  name,
  size = 24,
  color = '#000',
  family = 'MaterialIcons',
}) => {
  const IconComponent = IconComponents[family];
  return <IconComponent name={name} size={size} color={color} />;
};

SVG Icons

Install react-native-svg:

yarn add react-native-svg
yarn add -D @types/react-native-svg

# iOS setup
cd ios && pod install

Create SVG icon component:

// src/components/common/SvgIcon/SvgIcon.tsx
import React from 'react';
import Svg, {Path, Circle, Rect} from 'react-native-svg';

interface SvgIconProps {
  name: string;
  size?: number;
  color?: string;
}

const iconPaths = {
  home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
  user: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
  // Add more icon paths
};

export const SvgIcon: React.FC<SvgIconProps> = ({
  name,
  size = 24,
  color = '#000',
}) => {
  const path = iconPaths[name as keyof typeof iconPaths];
  
  if (!path) {
    console.warn(`Icon "${name}" not found`);
    return null;
  }

  return (
    <Svg width={size} height={size} viewBox="0 0 24 24" fill={color}>
      <Path d={path} />
    </Svg>
  );
};

Image Optimization

Install react-native-fast-image:

yarn add react-native-fast-image

# iOS setup
cd ios && pod install

Optimized image component:

// src/components/common/OptimizedImage/OptimizedImage.tsx
import React from 'react';
import FastImage, {FastImageProps} from 'react-native-fast-image';
import {StyleSheet, View, ActivityIndicator} from 'react-native';

interface OptimizedImageProps extends FastImageProps {
  showLoader?: boolean;
  loaderColor?: string;
}

export const OptimizedImage: React.FC<OptimizedImageProps> = ({
  showLoader = true,
  loaderColor = '#007AFF',
  style,
  ...props
}) => {
  const [loading, setLoading] = React.useState(true);

  return (
    <View style={style}>
      <FastImage
        {...props}
        style={[StyleSheet.absoluteFillObject, style]}
        onLoadStart={() => setLoading(true)}
        onLoadEnd={() => setLoading(false)}
        resizeMode={FastImage.resizeMode.cover}
      />
      {loading && showLoader && (
        <View style={styles.loader}>
          <ActivityIndicator color={loaderColor} />
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  loader: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.1)',
  },
});

Asset Preloading

// src/utils/assetPreloader.ts
import {Image} from 'react-native';
import {Images} from '@assets/images';

export const preloadImages = async (): Promise<void> => {
  const imagePromises = Object.values(Images).map(image => {
    return new Promise<void>((resolve, reject) => {
      Image.prefetch(Image.resolveAssetSource(image).uri)
        .then(() => resolve())
        .catch(reject);
    });
  });

  try {
    await Promise.all(imagePromises);
    console.log('All images preloaded successfully');
  } catch (error) {
    console.error('Error preloading images:', error);
  }
};

Image Caching Strategy

// src/utils/imageCache.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import RNFS from 'react-native-fs';

const CACHE_DIR = `${RNFS.CachesDirectoryPath}/images`;
const CACHE_KEY_PREFIX = 'image_cache_';

export class ImageCache {
  static async getCachedImagePath(url: string): Promise<string | null> {
    try {
      const cacheKey = `${CACHE_KEY_PREFIX}${this.hashUrl(url)}`;
      const cachedPath = await AsyncStorage.getItem(cacheKey);
      
      if (cachedPath && await RNFS.exists(cachedPath)) {
        return cachedPath;
      }
      
      return null;
    } catch (error) {
      console.error('Error getting cached image:', error);
      return null;
    }
  }

  static async cacheImage(url: string): Promise<string> {
    try {
      const fileName = this.hashUrl(url);
      const filePath = `${CACHE_DIR}/${fileName}`;
      const cacheKey = `${CACHE_KEY_PREFIX}${fileName}`;

      // Ensure cache directory exists
      await RNFS.mkdir(CACHE_DIR);

      // Download and cache the image
      await RNFS.downloadFile({
        fromUrl: url,
        toFile: filePath,
      }).promise;

      // Store the path in AsyncStorage
      await AsyncStorage.setItem(cacheKey, filePath);

      return filePath;
    } catch (error) {
      console.error('Error caching image:', error);
      throw error;
    }
  }

  private static hashUrl(url: string): string {
    // Simple hash function for URL
    let hash = 0;
    for (let i = 0; i < url.length; i++) {
      const char = url.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash).toString();
  }
}