tutorial_overlay 1.1.0 copy "tutorial_overlay: ^1.1.0" to clipboard
tutorial_overlay: ^1.1.0 copied to clipboard

A Flutter package for building interactive, step-by-step tutorials that highlight and point to widgets across multiple screens with customizable indicators.

example/lib/main.dart

import 'package:example/translations.dart';
import 'package:flutter/material.dart';
import 'package:tutorial_overlay/tutorial_overlay.dart';

// Initialize with navigator key (required for navigation between steps)
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

// Global keys for tutorial target widgets
final GlobalKey navigateButtonKey = GlobalKey();
final GlobalKey returnButtonKey = GlobalKey();
final GlobalKey helpButtonKey = GlobalKey();

enum TutorialID { home, secondary } // Unique identifiers for each tutorial

// Tutorial instance with predefined steps
final tutorial = Tutorial<TutorialID>(buildTutorials());

void main() {
  // Wrap your app with the tutorial provider
  runApp(Tutorial.provide(tutorial: tutorial, child: const MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Tutorial Overlay Demo',
      navigatorKey: navigatorKey,
      initialRoute: '/',
      routes: {
        '/':
            (context) => TutorialOverlay<TutorialID>(
              tutorialId: TutorialID.home,
              width: 320, // Set overlay width
              // Default styling for all tutorial steps with this tutorialId
              padding: const EdgeInsets.all(16), // Padding around content
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(12),
              ),
              focusOverlayPadding: const EdgeInsets.all(
                12,
              ), // Padding around highlighted widget
              indicator: Column(
                children: [
                  Expanded(
                    child: Container(color: Colors.greenAccent, width: 5),
                  ),
                  Container(
                    width: 10,
                    height: 10,
                    decoration: const BoxDecoration(
                      shape: BoxShape.circle,
                      color: Colors.greenAccent,
                    ),
                  ),
                ],
              ),
              indicatorHeight: 50,
              indicatorWidth: 10,
              child: const HomePage(),
            ),
        '/secondary':
            (context) => TutorialOverlay<TutorialID>(
              tutorialId: TutorialID.secondary,
              width: 320,
              dismissOnTap: false, // prevent dismiss when tapping outside
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(12),
              ),
              child: const SecondaryPage(),
            ),
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Welcome to the App',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () => tutorial.startTutorial(TutorialID.home),
              child: const Text(
                'Click me to start tutorial!',
                style: TextStyle(fontSize: 16),
              ),
            ),
            const SizedBox(height: 16),
            TextButton(
              key: navigateButtonKey, // Target for tutorial step
              onPressed: () => Navigator.pushNamed(context, '/secondary'),
              child: const Text('Go to Secondary Page'),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Secondary Page'),
        actions: [
          IconButton(
            key: helpButtonKey,
            icon: const Icon(Icons.help_outline),
            onPressed:
                () => tutorial.startTutorial(
                  TutorialID.secondary,
                ), // Start tutorial
          ),
        ],
      ),
      body: Center(
        child: ElevatedButton(
          key: returnButtonKey, // Target for tutorial step
          onPressed: () => Navigator.pop(context),
          child: const Text('Return to Welcome'),
        ),
      ),
    );
  }
}

// Define tutorial steps for each tutorial ID
Map<TutorialID, List<TutorialStep>> buildTutorials({bool isStyled = false}) {
  return {
    // Home page tutorial
    TutorialID.home: [
      TutorialStep(
        widgetKey: navigateButtonKey,
        child: _tutorialCard(
          step: 1,
          text: t("navigateButton"),
          buttons: [
            _backButton(TutorialID.home),
            _nextButton(TutorialID.home, route: '/secondary'),
          ],
        ),
      ),
      TutorialStep(
        // No widgetKey: shows general instruction
        child: _tutorialCard(
          step: 2,
          text: "${t("secondaryPage")} (${t("noTargetWidget")})",
          buttons: [
            _backButton(TutorialID.home, backToPreviousPage: true),
            _nextButton(TutorialID.home),
          ],
        ),
      ),
      TutorialStep(
        widgetKey: helpButtonKey,
        // Custom indicator for this step
        indicator: Container(color: Colors.lightBlue),
        indicatorWidth: 5,
        child: _tutorialCard(
          step: 3,
          text: "${t("helpButton")} (${t("customIndicator")})",
          buttons: [_backButton(TutorialID.home), _doneButton(TutorialID.home)],
        ),
      ),
    ],
    // Secondary page tutorial
    TutorialID.secondary: [
      TutorialStep(
        widgetKey: returnButtonKey,
        child: _tutorialCard(
          text: "${t("returnButton")} (${t("dismissNote")})",
          buttons: [_doneButton(TutorialID.secondary)],
        ),
      ),
    ],
  };
}

// Helper widget for tutorial step content
Widget _tutorialCard({
  int? step,
  required String text,
  required List<Widget> buttons,
}) {
  return Container(
    padding: const EdgeInsets.all(16),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            if (step != null) Expanded(child: Text("${t("step")} $step")),
            TextButton(
              onPressed: () {
                currentLang = currentLang == "en" ? "zh" : "en";
                tutorial.updateTutorial(buildTutorials());
              },
              child: Text(
                t("languageToggle"),
                style: const TextStyle(
                  decoration: TextDecoration.underline,
                  color: Colors.grey,
                ),
              ),
            ),
          ],
        ),
        Text(text, style: const TextStyle(fontSize: 16)),
        const SizedBox(height: 12),
        Row(mainAxisAlignment: MainAxisAlignment.end, children: buttons),
      ],
    ),
  );
}

// Next button for advancing tutorial steps
Widget _nextButton(
  TutorialID tutorialId, {
  String? route,
  bool backToPreviousPage = false,
}) {
  return Padding(
    padding: const EdgeInsets.only(left: 8),
    child: ElevatedButton(
      onPressed: () {
        tutorial.nextStep(
          tutorialId: tutorialId,
          context: navigatorKey.currentContext,
          route: route,
          backToPreviousPage: backToPreviousPage,
        );
      },
      child: Text(t('next')),
    ),
  );
}

// Back button for returning to previous tutorial steps
Widget _backButton(
  TutorialID tutorialId, {
  String? route,
  bool backToPreviousPage = false,
}) {
  return Padding(
    padding: const EdgeInsets.only(right: 8),
    child: OutlinedButton(
      onPressed: () {
        tutorial.previousStep(
          tutorialId: tutorialId,
          context: navigatorKey.currentContext,
          route: route,
          backToPreviousPage: backToPreviousPage,
        );
      },
      child: Text(t('back')),
    ),
  );
}

// Done button to end the tutorial
Widget _doneButton(TutorialID tutorialId) {
  return ElevatedButton(
    onPressed: () => tutorial.endTutorial(tutorialId),
    child: Text(t('done')),
  );
}
0
likes
160
points
143
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

A Flutter package for building interactive, step-by-step tutorials that highlight and point to widgets across multiple screens with customizable indicators.

Repository (GitHub)

Topics

#tutorial #tooltip #overlay #onboarding #guide

Documentation

API reference

License

MIT (license)

Dependencies

flutter, provider

More

Packages that depend on tutorial_overlay