Skip to content

Implement media display components and feedback overlay for quiz feature#8

Merged
adewuyito merged 1 commit intomasterfrom
copilot/fix-ebdb8296-62e3-4298-b79f-d95c3f08a616
Jul 8, 2025
Merged

Implement media display components and feedback overlay for quiz feature#8
adewuyito merged 1 commit intomasterfrom
copilot/fix-ebdb8296-62e3-4298-b79f-d95c3f08a616

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jul 8, 2025

Overview

This PR implements the media display components and feedback overlay system for the quiz feature, enabling support for image and video questions with interactive feedback.

Changes Made

1. Media Display Components (lib/features/quiz/widgets/media_display.dart)

Created a comprehensive media display system with three main components:

  • MediaDisplay: Unified wrapper component that automatically renders the appropriate media type
  • VideoDisplay: Video player with play/pause controls and proper lifecycle management
  • ImageDisplay: Image display with loading states and error handling
// Usage example
MediaDisplay(
  mediaUrl: question.mediaUrl,
  type: question.type,
  height: 200,
)

Key Features:

  • Support for both network URLs and local assets
  • Robust error handling with fallback UI
  • Loading indicators for better UX
  • Configurable dimensions
  • Proper video controller disposal to prevent memory leaks

2. Quiz Feedback Overlay (lib/features/quiz/widgets/feedback_overlay.dart)

Implemented QuizFeedbackOverlay component providing immediate feedback after answer selection:

QuizFeedbackOverlay(
  isCorrect: isCorrect,
  explanation: question.explanation,
  onNext: handleNext,
  onRetry: handleRetry,
)

Features:

  • Visual feedback with appropriate colors and icons
  • Contextual action buttons (Next for correct, Try Again for incorrect)
  • Clean modal overlay design
  • Explanation text display

3. Enhanced Question Display (lib/features/quiz/views/question_display.dart)

Updated the existing question display to use the new media components:

  • Replaced placeholder media containers with MediaDisplay component
  • Maintained compatibility with existing quiz controller
  • Improved layout and spacing

4. Documentation and Demo

  • README.md: Comprehensive usage guide with examples
  • media_display_demo.dart: Interactive demo showcasing all components

Technical Details

Compatibility & Integration

  • Uses existing QuestionType enum (text, image, video)
  • Compatible with current mediaUrl property from quiz models
  • Integrates seamlessly with existing quiz controller
  • No breaking changes to existing functionality

Dependencies

  • Leverages existing video_player: ^2.8.2 (no new dependencies added)
  • Uses standard Flutter widgets for maximum compatibility

Performance Considerations

  • Proper video controller initialization and disposal
  • Efficient network image loading with caching
  • Minimal rebuilds through careful state management

Testing

The implementation includes:

  • Error handling for failed media loading
  • Loading states for network content
  • Graceful fallbacks for unsupported media types
  • Interactive demo for manual testing

Example Usage

// In a quiz screen
Stack(
  children: [
    Column(
      children: [
        Text(question.question),
        MediaDisplay(
          mediaUrl: question.mediaUrl,
          type: question.type,
          height: 200,
        ),
        // Answer options...
      ],
    ),
    if (showFeedback)
      QuizFeedbackOverlay(
        isCorrect: isCorrect,
        explanation: question.explanation,
        onNext: moveToNextQuestion,
        onRetry: retryQuestion,
      ),
  ],
)

Impact

This implementation provides:

  • ✅ Full support for image and video quiz questions
  • ✅ Enhanced user experience with immediate feedback
  • ✅ Robust error handling and loading states
  • ✅ Foundation for future quiz enhancements
  • ✅ Comprehensive documentation for maintainability

The changes are minimal, focused, and maintain full backward compatibility while significantly enhancing the quiz functionality.

This pull request was created as a result of the following prompt from Copilot chat.

Implement media display components and feedback overlay for the quiz feature:

  1. Create Media Display Components:
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import '../domain/entities/quiz_entities.dart';

class MediaDisplay extends StatelessWidget {
  final String? mediaPath;
  final QuestionType type;
  final double? height;
  final double? width;

  const MediaDisplay({
    super.key,
    required this.mediaPath,
    required this.type,
    this.height,
    this.width,
  });

  @override
  Widget build(BuildContext context) {
    if (mediaPath == null) return const SizedBox.shrink();

    switch (type) {
      case QuestionType.video:
        return VideoDisplay(
          videoPath: mediaPath!,
          height: height,
          width: width,
        );
      case QuestionType.image:
        return ImageDisplay(
          imagePath: mediaPath!,
          height: height,
          width: width,
        );
      default:
        return const SizedBox.shrink();
    }
  }
}

class VideoDisplay extends StatefulWidget {
  final String videoPath;
  final double? height;
  final double? width;

  const VideoDisplay({
    super.key,
    required this.videoPath,
    this.height,
    this.width,
  });

  @override
  State<VideoDisplay> createState() => _VideoDisplayState();
}

class _VideoDisplayState extends State<VideoDisplay> {
  late VideoPlayerController _controller;
  bool _isInitialized = false;

  @override
  void initState() {
    super.initState();
    _initializeVideoPlayer();
  }

  Future<void> _initializeVideoPlayer() async {
    // For testing with asset files
    _controller = VideoPlayerController.asset(widget.videoPath)
      ..initialize().then((_) {
        setState(() {
          _isInitialized = true;
        });
        _controller.setLooping(true);
      });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (!_isInitialized) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    return Stack(
      alignment: Alignment.center,
      children: [
        AspectRatio(
          aspectRatio: _controller.value.aspectRatio,
          child: VideoPlayer(_controller),
        ),
        IconButton(
          icon: Icon(
            _controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
            size: 48,
            color: Colors.white.withOpacity(0.8),
          ),
          onPressed: () {
            setState(() {
              _controller.value.isPlaying
                  ? _controller.pause()
                  : _controller.play();
            });
          },
        ),
      ],
    );
  }
}

class ImageDisplay extends StatelessWidget {
  final String imagePath;
  final double? height;
  final double? width;

  const ImageDisplay({
    super.key,
    required this.imagePath,
    this.height,
    this.width,
  });

  @override
  Widget build(BuildContext context) {
    return Image.asset(
      imagePath,
      height: height,
      width: width,
      fit: BoxFit.contain,
    );
  }
}
  1. Add Feedback Overlay:
import 'package:flutter/material.dart';

class QuizFeedbackOverlay extends StatelessWidget {
  final bool isCorrect;
  final String explanation;
  final VoidCallback onNext;
  final VoidCallback onRetry;

  const QuizFeedbackOverlay({
    super.key,
    required this.isCorrect,
    required this.explanation,
    required this.onNext,
    required this.onRetry,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black.withOpacity(0.7),
      child: Center(
        child: Container(
          margin: const EdgeInsets.all(32),
          padding: const EdgeInsets.all(24),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(16),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                isCorrect ? Icons.check_circle : Icons.cancel,
                color: isCorrect ? Colors.green : Colors.red,
                size: 64,
              ),
              const SizedBox(height: 16),
              Text(
                isCorrect ? 'Correct!' : 'Try Again',
                style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                  color: isCorrect ? Colors.green : Colors.red,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 16),
              Text(
                explanation,
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.bodyLarge,
              ),
              const SizedBox(height: 24),
              Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (!isCorrect)
                    ElevatedButton(
                      onPressed: onRetry,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.red,
                      ),
                      child: const Text('Try Again'),
                    ),
                  if (!isCorrect)
                    const SizedBox(width: 16),
                  if (isCorrect)
                    ElevatedButton(
                      onPressed: onNext,
                      style: ElevatedButton.styleFrom(
                        backgroundColor: Colors.green,
                      ),
                      child: const Text('Next Question'),
                    ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}
  1. Update Question Display to Use New Components:
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../domain/entities/quiz_entities.dart';
import '../widgets/media_display.dart';
import '../widgets/feedback_overlay.dart';
import '../controllers/quiz_controller.dart';

class QuestionDisplay extends ConsumerStatefulWidget {
  const QuestionDisplay({super.key});

  @override
  ConsumerState<QuestionDisplay> createState() => _QuestionDisplayState();
}

class _QuestionDisplayState extends ConsumerState<QuestionDisplay> {
  bool _showFeedback = false;
  bool? _isCorrect;

  void _handleAnswer(int selectedIndex) {
    final session = ref.read(quizControllerProvider).value;
    if (session == null) return;

    final isCorrect = session.currentQuestion.isAnswerCorrect(selectedIndex);
    setState(() {
      _showFeedback = true;
      _isCorrect = isCorrect;
    });

    ref.read(quizControllerProvider.notifier).answerQuestion(selectedIndex);
  }

  void _handleNext() {
    setState(() {
      _showFeedback = false;
      _isCorrect = null;
    });
    ref.read(quizControllerProvider.notifier).moveToNext();
  }

  void _handleRetry() {
    setState(() {
      _showFeedback = false;
      _isCorrect = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ref.watch(quizControllerProvider).when(
      data: (session) {
        if (session == null) {
          return const Center(child: Text('No quiz loaded'));
        }

        final question = session.currentQuestion;
        
        return Stack(
          children: [
            Column(
              children: [
                Text(
                  question.question,
                  style: Theme.of(context).textTheme.titleLarge,
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 24),
                if (question.type != QuestionType.text)
                  MediaDisplay(
                    mediaPath: question.mediaPath,
                    type: question.type,
                    height: 200,
                  ),
                const SizedBox(height: 24),
                GridView.builder(
                  shrinkWrap: true,
                  physics: const NeverScrollableScrollPhysics(),
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    childAspectRatio: 1.5,
                    crossAxisSpacing: 12,
                    mainAxisSpacing: 12,
                  ),
                  itemCount: question.options.length,
                  itemBuilder: (context, index) {
                    return Card(
                      child: InkWell(
                        onTap: () => _handleAnswer(index),
                        child: Center(
                          child: Text(
                            question.options[index],
                            textAlign: TextAlign.center,
                            style: Theme.of(context).textTheme.titleMedium,
                          ),
                        ),
                      ),
                    );
                  },
                ),
              ],
            ),
            if (_showFeedback)
              QuizFeedbackOverlay(
                isCorrect: _isCorrect!,
                explanation: question.explanation,
                onNext: _handleNext,
                onRetry: _handleRetry,
              ),
          ],
        );
      },
      loading: () => const Center(
        child: CircularProgressIndicator(),
      ),
      error: (error, stack) => Center(
        child: Text('Error: $error'),
      ),
    );
  }
}
  1. Add Required Dependencies to pubspec.yaml:
dependencies:
  video_player: ^2.7.0

This implementation:

  1. Adds video and image display components
  2. Creates a feedback overlay for correct/incorrect answers
  3. Updates the question display to use the new components
  4. Handles loading states and errors
  5. Supports video playback controls

Next steps:

  1. Create the score screen
  2. Add animations and transitions
  3. Implement Firebase Storage integration
  4. Add loading states for media content

Would you like me to proceed with any of these next steps?


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@adewuyito adewuyito marked this pull request as ready for review July 8, 2025 22:48
@adewuyito adewuyito merged commit 118988d into master Jul 8, 2025
2 checks passed
@adewuyito adewuyito deleted the copilot/fix-ebdb8296-62e3-4298-b79f-d95c3f08a616 branch July 8, 2025 22:49
Copilot AI changed the title [WIP] Add Media Display and Feedback Components Implement media display components and feedback overlay for quiz feature Jul 8, 2025
Copilot AI requested a review from adewuyito July 8, 2025 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants