React Native / Апп зөвшөөрөл

Апп зөвшөөрөл

Өмнөх хичээлүүдэд камер, байршил, notification тус тусдаа зөвшөөрөл хэрхэн авахыг сурсан. Гэвч жинхэнэ аппликейшн олон зөвшөөрөл удирдах шаардлагатай байдаг. Энэ хичээлд зөвшөөрлийн ерөнхий зарчим, expo-modules-core болон тус бүрийн package-н hook-уудыг нэгтгэн системтэй удирдах аргыг сурна.

Зөвшөөрлийн гурван байдал

iOS болон Android хоёулан зөвшөөрлийн гурван байдал байдаг:

| Байдал | Утга | | -------------- | ------------------------------------------------------ | | undetermined | Хэрэглэгч хариулаагүй — анх удаа асуух боломжтой | | granted | Зөвшөөрсөн — feature ажиллуулж болно | | denied | Татгалзсан — системийн Settings рүү чиглүүлэх хэрэгтэй |

Хамгийн чухал дүрэм: татгалзсан бол дахин асуух боломжгүй — хэрэглэгчийг Settings рүү гарна.

Зөвшөөрлийг системтэй удирдах

Бүх зөвшөөрлийн логикийг нэг custom hook-т цуглуулна:

jsx
import * as ImagePicker from "expo-image-picker";
import * as Location from "expo-location";
import * as Notifications from "expo-notifications";
import { Alert, Linking, Platform } from "react-native";

// Settings нээх — татгалзсан зөвшөөрлийн үед
const openSettings = () => {
  if (Platform.OS === "ios") {
    Linking.openURL("app-settings:");
  } else {
    Linking.openSettings();
  }
};

// Зөвшөөрөл татгалзсан үед стандарт мэдэгдэл
const showDeniedAlert = (featureName) => {
  Alert.alert(
    "Зөвшөөрөл шаардлагатай",
    `${featureName} ашиглахын тулд утасны тохиргоонд зөвшөөрнө үү.`,
    [
      { text: "Болих", style: "cancel" },
      { text: "Тохиргоо нээх", onPress: openSettings },
    ],
  );
};

// Камерын зөвшөөрөл
export async function requestCamera() {
  const { status } = await ImagePicker.requestCameraPermissionsAsync();
  if (status === "denied") {
    showDeniedAlert("Камер");
    return false;
  }
  return status === "granted";
}

// Галерейн зөвшөөрөл
export async function requestGallery() {
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
  if (status === "denied") {
    showDeniedAlert("Зургийн цомог");
    return false;
  }
  return status === "granted";
}

// Байршлын зөвшөөрөл
export async function requestLocation() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  if (status === "denied") {
    showDeniedAlert("Байршил");
    return false;
  }
  return status === "granted";
}

// Notification-н зөвшөөрөл
export async function requestNotifications() {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status === "denied") {
    showDeniedAlert("Мэдэгдэл");
    return false;
  }
  return status === "granted";
}

Зөвшөөрлийн dashboard харуулах

Аппликейшн нь хэрэглэгчид ямар зөвшөөрлүүд идэвхтэй байгааг нэг дэлгэцэнд харуулах нь сайн UX:

jsx
import { useState, useEffect, useCallback } from "react";
import * as ImagePicker from "expo-image-picker";
import * as Location from "expo-location";
import * as Notifications from "expo-notifications";
import {
  View,
  Text,
  TouchableOpacity,
  ScrollView,
  StyleSheet,
  Linking,
  Platform,
} from "react-native";

const PERMISSIONS = [
  {
    key: "camera",
    label: "Камер",
    icon: "📷",
    description: "Зураг авах, QR код уншихад хэрэгтэй",
    check: () => ImagePicker.getCameraPermissionsAsync(),
    request: () => ImagePicker.requestCameraPermissionsAsync(),
  },
  {
    key: "gallery",
    label: "Зургийн цомог",
    icon: "🖼️",
    description: "Галерейгаас зураг сонгоход хэрэгтэй",
    check: () => ImagePicker.getMediaLibraryPermissionsAsync(),
    request: () => ImagePicker.requestMediaLibraryPermissionsAsync(),
  },
  {
    key: "location",
    label: "Байршил",
    icon: "📍",
    description: "Ойролцоох газруудыг харуулахад хэрэгтэй",
    check: () => Location.getForegroundPermissionsAsync(),
    request: () => Location.requestForegroundPermissionsAsync(),
  },
  {
    key: "notifications",
    label: "Мэдэгдэл",
    icon: "🔔",
    description: "Захиалга, мессеж мэдэгдлийг хүлээн авахад",
    check: () => Notifications.getPermissionsAsync(),
    request: () => Notifications.requestPermissionsAsync(),
  },
];

function PermissionRow({ perm, status, onRequest }) {
  const isGranted = status === "granted";
  const isDenied = status === "denied";

  return (
    <View style={styles.row}>
      <Text style={styles.icon}>{perm.icon}</Text>
      <View style={styles.rowInfo}>
        <Text style={styles.rowLabel}>{perm.label}</Text>
        <Text style={styles.rowDesc}>{perm.description}</Text>
      </View>
      <TouchableOpacity
        style={[
          styles.badge,
          isGranted ? styles.badgeGranted : styles.badgeDenied,
        ]}
        onPress={isGranted ? undefined : onRequest}
      >
        <Text
          style={[
            styles.badgeText,
            isGranted ? styles.badgeTextGranted : styles.badgeTextDenied,
          ]}
        >
          {isGranted ? "Зөвшөөрсөн" : isDenied ? "Татгалзсан" : "Асуух"}
        </Text>
      </TouchableOpacity>
    </View>
  );
}

export default function PermissionsScreen() {
  const [statuses, setStatuses] = useState({});

  const checkAll = useCallback(async () => {
    const results = await Promise.all(
      PERMISSIONS.map(async (p) => {
        const { status } = await p.check();
        return [p.key, status];
      }),
    );
    setStatuses(Object.fromEntries(results));
  }, []);

  useEffect(() => {
    checkAll();
  }, []);

  const handleRequest = async (perm) => {
    if (statuses[perm.key] === "denied") {
      Linking.openSettings();
      return;
    }
    await perm.request();
    await checkAll();
  };

  return (
    <ScrollView style={styles.scroll} contentContainerStyle={styles.content}>
      <Text style={styles.heading}>Апп зөвшөөрлүүд</Text>
      <Text style={styles.subtitle}>
        Аппликейшн бүрэн ажиллахын тулд дараах зөвшөөрлүүд шаардлагатай
      </Text>

      {PERMISSIONS.map((perm) => (
        <PermissionRow
          key={perm.key}
          perm={perm}
          status={statuses[perm.key]}
          onRequest={() => handleRequest(perm)}
        />
      ))}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  scroll: { flex: 1, backgroundColor: "#0b1120" },
  content: { padding: 24, gap: 12 },
  heading: {
    fontSize: 24,
    fontWeight: "bold",
    color: "#f1f5f9",
    marginBottom: 4,
  },
  subtitle: {
    color: "#94a3b8",
    fontSize: 14,
    lineHeight: 20,
    marginBottom: 12,
  },
  row: {
    flexDirection: "row",
    alignItems: "center",
    backgroundColor: "#0f172a",
    borderRadius: 12,
    padding: 14,
    gap: 12,
    borderWidth: 1,
    borderColor: "#1e293b",
  },
  icon: { fontSize: 24, width: 32, textAlign: "center" },
  rowInfo: { flex: 1 },
  rowLabel: { color: "#f1f5f9", fontSize: 15, fontWeight: "600" },
  rowDesc: { color: "#475569", fontSize: 12, marginTop: 2, lineHeight: 16 },
  badge: {
    paddingHorizontal: 10,
    paddingVertical: 5,
    borderRadius: 6,
  },
  badgeGranted: { backgroundColor: "#052e16" },
  badgeDenied: {
    backgroundColor: "#1e293b",
    borderWidth: 1,
    borderColor: "#475569",
  },
  badgeText: { fontSize: 12, fontWeight: "600" },
  badgeTextGranted: { color: "#4ade80" },
  badgeTextDenied: { color: "#94a3b8" },
});

app.json дахь зөвшөөрлийн тайлбар

EAS Build-р App Store/Play Store-д нийтлэхэд зөвшөөрл бүрт тайлбар нэмэх шаардлагатай. iOS-д infoPlist, Android-д permissions ашиглана:

json
{
  "expo": {
    "ios": {
      "infoPlist": {
        "NSCameraUsageDescription": "Профайл зураг авахад камер ашиглана",
        "NSPhotoLibraryUsageDescription": "Зургийн цомгоос профайл зураг сонгоход ашиглана",
        "NSLocationWhenInUseUsageDescription": "Ойролцоох дэлгүүр харуулахад байршил ашиглана",
        "NSMicrophoneUsageDescription": "Видео бичихэд микрофон ашиглана"
      }
    },
    "android": {
      "permissions": [
        "android.permission.CAMERA",
        "android.permission.READ_MEDIA_IMAGES",
        "android.permission.ACCESS_FINE_LOCATION",
        "android.permission.RECORD_AUDIO"
      ]
    }
  }
}

Зөвшөөрлийн тайлбарыг Монгол хэлээр бичихийг мартуузай. App Store шалгах үед энэ тайлбарыг уншиж баталгаажуулдаг — тодорхой, үнэн мэдээлэл бичих нь апп-г зөвшөөрүүлэхэд тусална.

Дараагийн хичээлд:

Supabase + React Native — бэлэн backend ашиглан өгөгдлийн сан, authentication, файл хадгалалтыг мобайл апп-тай холбоно.