Wróć do projektów
Mobile Development · React Native · Education
2025 Projekt własny

QuizApp

Mobilna aplikacja edukacyjna do rozwiązywania testów wielokrotnego wyboru — timer na każde pytanie, ranking wyników, tryb offline z SQLite i synchronizacja z REST API. React Native 0.82, TypeScript 91%.

React Native
Framework
91% TypeScript
Język
SQLite + API
Storage
Offline-first
Tryb pracy

Nauka przez
quizy i ranking

Quiz App to mobilna aplikacja edukacyjna do rozwiązywania testów wielokrotnego wyboru z zewnętrznego API tgryl.pl. Każde pytanie ma własny timer, a wyniki trafiają do globalnego rankingu.

Aplikacja działa w pełni offline dzięki lokalnemu cache SQLite — testy są synchronizowane raz dziennie i dostępne bez połączenia z internetem.

📝
Testy wielokrotnego wyboru
4 opcje odpowiedzi (A/B/C/D), pytania i odpowiedzi losowane przy każdym uruchomieniu
⏱️
Timer na pytanie
Odliczanie czasu per pytanie, kolor czerwony gdy zostaje <5 sekund, auto-skip po upływie
📊
Globalny ranking
Top 20 wyników z serwera, wynik użytkownika podświetlony na żółto, pull-to-refresh
🌐
Offline-first
SQLite cache — testy dostępne bez internetu, synchronizacja automatyczna raz dziennie
🎲
Losowy quiz
Drawer z 5 losowymi testami i przyciskiem "Losowy Quiz" — nowe wyzwanie za każdym razem
📋
Onboarding
Regulamin przy pierwszym uruchomieniu, stan zapisywany w AsyncStorage — pokazuje się tylko raz

Stack technologiczny

⚛️
React Native 0.82
Cross-platform framework — jeden kod dla Androida i iOS. React 19.1.1.
🔷
TypeScript 5.9
91% projektu w TypeScript — statyczne typowanie, strict mode, pełne bezpieczeństwo.
🗄️
SQLite
react-native-sqlite-storage 6.0.1 — lokalna baza danych dla offline cache testów.
🧭
React Navigation 7
Stack Navigator — Onboarding → Home → Quiz → Results. Side Drawer menu.
📡
NetInfo
@react-native-community/netinfo 11.4.1 — detekcja stanu sieci, przełączanie offline/online.
💾
AsyncStorage
Preferencje użytkownika — stan onboardingu, lokalne wyniki i synchronizacja danych.

Jak to działa

01
Startup — initApp()

App.tsx inicjalizuje bazę danych, tworzy tabele SQLite, sprawdza onboarding w AsyncStorage i synchronizuje dane z API jeśli minął dzień od ostatniej synchronizacji.

// App.tsx
async function initApp() {
  const db = await getDBConnection();
  await createTables(db);
  await checkOnboarding();
  // Check lastFetchDate in meta table
  await fetchAndSaveTestsDaily();
  SplashScreen.hide();
}
02
Offline-first — quizService

Każde zapytanie o dane sprawdza najpierw lokalną bazę SQLite. Jeśli dane są — zwraca je bez wywołania API. NetInfo decyduje czy sieć jest dostępna.

// quizService.ts
async fetchTestDetails(id) {
  const local = await getTestFromDb(id);
  if (local) return local;

  const net = await NetInfo.fetch();
  if (!net.isConnected)
    throw new Error('Brak sieci i danych');

  const data = await fetch(API + id);
  await saveTest(data);
  return data;
}
03
QuizScreen — timer

Każde pytanie ma własny timer (np. 30s z API). Gdy zostaje <5 sekund — kolor czerwony. Po upływie czasu odpowiedzi blokowane, przejście dalej.

useEffect(() => {
  const interval = setInterval(() => {
    setTimeLeft(t => {
      if (t <= 1) {
        clearInterval(interval);
        nextQuestion();
        return 0;
      }
      return t - 1;
    });
  }, 1000);
  return () => clearInterval(interval);
}, [currentQuestionIndex]);
04
SQLite — schemat

Tabela tests przechowuje całe dane testu jako JSON, tabela meta przechowuje datę ostatniej synchronizacji.

CREATE TABLE tests (
  id        TEXT PRIMARY KEY,
  name      TEXT,
  description TEXT,
  tags      TEXT, -- JSON string
  tasks     TEXT, -- JSON string
  updatedAt TEXT
);

CREATE TABLE meta (
  key   TEXT PRIMARY KEY,
  value TEXT -- lastFetchDate
);
05
ResultsScreen — wyniki

Wynik wysyłany na serwer przez POST po zakończeniu quizu. Tabela top 20 z API, wynik użytkownika podświetlony na żółto. Pull-to-refresh aktualizuje ranking.

// POST wynik
await fetch('http://tgryl.pl/quiz/result', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    nick: 'Ty',
    score: 15,
    total: 20,
    type: 'Historia'
  })
});
06
Drawer — side menu

Animowany drawer z lewej strony — 5 losowych testów, przycisk "Losowy Quiz", synchronizacja i nawigacja. Obsługuje offline — wyświetla z SQLite gdy brak sieci.

// Drawer.tsx
// 5 losowych testów z lodash shuffle
const random = _.shuffle(allTests)
  .slice(0, 5);

// Losowy quiz
const pick = _.sample(allTests);
navigation.navigate('Quiz', {
  testId: pick.id
});

Struktura aplikacji

📋 OnboardingScreen Regulamin — pokazuje się tylko raz
🏠 HomeScreen Lista dostępnych testów z tagami
QuizScreen Pytania A/B/C/D z timerem i progress bar
🏆 ResultsScreen Top 20 globalny ranking z serwera
Drawer Side menu — losowe testy i sync

Tryb offline

🏠 HomeScreen
Online: pobiera z API, zapisuje do SQLite
Offline: wyświetla z lokalnej bazy
Offline + pusta baza: komunikat błędu
❓ QuizScreen
Online: pobiera szczegóły z API
Offline: pobiera pytania z SQLite
Offline + brak w bazie: błąd
🏆 ResultsScreen
Online: top 20 z API + lokalne
Offline: tylko lokalne wyniki
Wyniki cache'owane w AsyncStorage

Endpointy — tgryl.pl

📡 Quiz API https://tgryl.pl/quiz
GET /tests Lista dostępnych testów z tagami i liczbą pytań
GET /test/{testId} Szczegóły testu — pytania, odpowiedzi, czas (duration)
GET /results?last=20 Top 20 wyników globalnego rankingu
POST /result Wysłanie wyniku — nick, score, total, type
// Typy danych
type ApiTestListItem = {
  id: string;  name: string;
  description: string;
  tags: string[];  level: string;
  tasks: number;          // liczba pytań
};

type ApiTestTask = {
  id: string;  question: string;
  answers: ApiTaskAnswer[];
  duration: number;       // czas w sekundach
};

type ApiTaskAnswer = {
  id: string;  content: string;
  isCorrect: boolean;
};

type ResultItem = {
  id: string;  nick: string;
  score: number;  total: number;
  type: string;  date: string;
};

Skład projektu

TypeScript 91%
Kotlin 3.4%
Ruby 2.3%
Swift 2%
JavaScript 1.3%
🔷
TypeScript — logika aplikacji
Screens, services, database layer, typy danych — 91% całego kodu
🤖
Kotlin — natywny Android
Natywne moduły i konfiguracja Gradle dla warstwy Android
💎
Ruby — CocoaPods iOS
Gemfile i CocoaPods do zarządzania zależnościami iOS

Jak uruchomić

Wymagania
Node.js 20+ React Native CLI Android Studio Xcode (iOS) Ruby 2.3+
1
Klonuj i zainstaluj zależności
git clone https://github.com/Andrii418/Quiz.git
cd Quiz
npm install
2
Zainstaluj zależności iOS (tylko macOS)
cd ios && pod install && cd ..
3
Uruchom Metro i aplikację
npm start

# W osobnym terminalu:
npm run android   # Android
npm run ios       # iOS
Wszystkie projekty