Menu
CoddyTech
flag Ar iconالعربيةdown icon

الاختبار القصير: رسالة النتائج

جزء من قسم المشاريع في رحلة Flutter على Coddy. الدرس 18 من 36.

تستحق عبارة "لقد حصلت على 5 من أصل 5" أكثر من مجرد سطر عادي. اختتم الاختبار بأيقونة كبيرة ورسالة تناسب النتيجة: كأس للجولة المثالية، وإشارة إعجاب لمن أجاب إجابات صحيحة في معظم الأسئلة، وبعض التشجيع فيما عدا ذلك.

ثلاث نتائج، يُتحقَّق منها بدءًا من الأفضل، تعني استخدام if / else if / else. الترتيب مهم: فالنتيجة المثالية تعني أيضًا أن أكثر من نصف الإجابات صحيحة، لذا اختبرها أولًا.

if (score == total) {
  message = 'Perfect!';
  icon = Icons.emoji_events;
} else if (score * 2 >= total) {
  message = 'Well done!';
  icon = Icons.thumb_up;
} else {
  message = 'Keep practicing!';
  icon = Icons.school;
}

يعني score * 2 >= total «نصف الإجابات على الأقل» من دون أي تقريب: اجتياز 3 من أصل 5 يفي بالشرط، أما 2 من أصل 5 فلا.

صرّح عن message وicon وcolor قبل if من دون إسناد قيمة إليها. يتحقق Dart من أن كل مسار يعيّن قيمة لكل منها قبل استخدامها.

هذا هو الدرس الأخير في الاختبار. تختبر الفحوص جولات كاملة: الألوان والنتيجة والنتائج وخيار اللعب مرة أخرى.

challenge icon

التحدي

متوسط

في شاشة النتائج، فوق النتيجة:

  • تُظهر النتيجة الكاملة Perfect! وأيقونة كهرمانية اللون Icons.emoji_events
  • تُظهر نتيجة النصف على الأقل Well done! وأيقونة خضراء Icons.thumb_up
  • تُظهر أي نتيجة أقل من ذلك Keep practicing! وأيقونة Icons.school بلون السمة الأساسي

حجم الأيقونة 96 بكسل، يليها فراغ بمقدار 16 بكسل، ثم الرسالة بتنسيق headlineMedium، ثم 8 بكسلات، ثم سطر النتيجة، بتنسيق titleMedium الآن.

جرّب بنفسك

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const QuizScreen(),
    );
  }
}

class Question {
  const Question(this.text, this.answers, this.correct);

  final String text;
  final List<String> answers;
  final int correct;
}

const List<Question> questions = [
  Question('Which widget stacks its children from top to bottom?', ['Row', 'Column', 'Stack', 'Wrap'], 1),
  Question('Which language do you write Flutter apps in?', ['Kotlin', 'Swift', 'Dart', 'JavaScript'], 2),
  Question('What does setState do?', ['Rebuilds the widget', 'Saves the app', 'Closes the screen', 'Plays a sound'], 0),
  Question('Which widget shows a list you can scroll?', ['Container', 'SizedBox', 'Padding', 'ListView'], 3),
  Question('Which widget adds space around its child?', ['Padding', 'Center', 'Icon', 'Text'], 0),
];

class QuizScreen extends StatefulWidget {
  const QuizScreen({super.key});

  @override
  State<QuizScreen> createState() => _QuizScreenState();
}

class _QuizScreenState extends State<QuizScreen> {
  int _index = 0;
  int? _picked;
  int _score = 0;

  Question get _question => questions[_index];
  bool get _answered => _picked != null;
  bool get _isLast => _index == questions.length - 1;

  void _pick(int answer) {
    if (_answered) return;
    setState(() {
      _picked = answer;
      if (answer == _question.correct) {
        _score++;
      }
    });
  }

  Future<void> _next() async {
    if (!_isLast) {
      setState(() {
        _index++;
        _picked = null;
      });
      return;
    }
    await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => ResultsScreen(score: _score, total: questions.length),
      ),
    );
    setState(() {
      _index = 0;
      _picked = null;
      _score = 0;
    });
  }

  Color? _colorFor(int answer) {
    if (!_answered) return null;
    if (answer == _question.correct) return Colors.green;
    if (answer == _picked) return Colors.red;
    return null;
  }

  @override
  Widget build(BuildContext context) {
    final question = _question;
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Quiz'),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Center(
              child: Text('Score: $_score', style: textTheme.titleMedium),
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              'Question ${_index + 1} of ${questions.length}',
              style: textTheme.labelLarge,
            ),
            const SizedBox(height: 8),
            LinearProgressIndicator(
              value: (_index + 1) / questions.length,
              minHeight: 8,
              borderRadius: BorderRadius.circular(4),
            ),
            const SizedBox(height: 24),
            Text(question.text, style: textTheme.headlineSmall),
            const SizedBox(height: 24),
            for (int i = 0; i < question.answers.length; i++)
              Padding(
                padding: const EdgeInsets.only(bottom: 12),
                child: FilledButton.tonal(
                  style: FilledButton.styleFrom(
                    backgroundColor: _colorFor(i),
                    foregroundColor: _colorFor(i) == null ? null : Colors.white,
                    padding: const EdgeInsets.all(16),
                  ),
                  onPressed: () => _pick(i),
                  child: Text(question.answers[i]),
                ),
              ),
            if (_answered) ...[
              const SizedBox(height: 12),
              Text(
                _picked == question.correct
                    ? 'Correct!'
                    : 'The answer is ${question.answers[question.correct]}',
                textAlign: TextAlign.center,
                style: textTheme.titleMedium,
              ),
              const SizedBox(height: 16),
              FilledButton(
                onPressed: _next,
                child: Text(_isLast ? 'See results' : 'Next'),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

class ResultsScreen extends StatelessWidget {
  const ResultsScreen({super.key, required this.score, required this.total});

  final int score;
  final int total;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Results')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                'You scored $score out of $total',
                style: Theme.of(context).textTheme.headlineSmall,
              ),
              const SizedBox(height: 32),
              FilledButton.icon(
                onPressed: () => Navigator.pop(context),
                icon: const Icon(Icons.replay),
                label: const Text('Play again'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس المشاريع

تدرّب بنفسك: مترجم Flutter عبر الإنترنت