flutter_app_tour 0.0.2 copy "flutter_app_tour: ^0.0.2" to clipboard
flutter_app_tour: ^0.0.2 copied to clipboard

A lightweight, customizable guided tour overlay for Flutter apps.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_app_tour/flutter_app_tour.dart';

void main() => runApp(const TourExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter App Tour',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xff176b87)),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  static const _tourId = 'home-tour-v2';

  final _scaffoldKey = GlobalKey<ScaffoldState>();
  final _fabKey = GlobalKey();
  final _menuKey = GlobalKey();
  final _drawerOverviewKey = GlobalKey();
  final _searchKey = GlobalKey();
  final _profileKey = GlobalKey();
  final _insightsKey = GlobalKey();
  final _storage = const SharedPreferencesTourStorage();

  late final _tourController = TourController(
    storage: _storage,
    theme: const TourTheme(
      overlayColor: Color(0xff102a43),
      overlayOpacity: 0.72,
      spotlightPadding: 10,
      spotlightRadius: 16,
      spotlightShape: SpotlightShape.roundedRectangle,
      progressIndicatorType: TourProgressIndicatorType.fraction,
    ),
    steps: [
      TourStep(
        id: TourId('search'),
        targetKey: _searchKey,
        title: 'Search',
        description: 'Use search to find projects, notes, and teammates.',
        tooltipPosition: TooltipPosition.bottom,
        spotlightShape: SpotlightShape.circle,
      ),
      TourStep(
        id: TourId('menu'),
        targetKey: _drawerOverviewKey,
        title: 'Open navigation',
        description: 'Choose a workspace section from the navigation drawer.',
        tooltipPosition: TooltipPosition.right,
        animation: TourAnimation.glow,
        animationConfig: TourAnimationConfig(
          duration: Duration(milliseconds: 900),
          repeat: true,
        ),
        onBeforeShow: () async {
          _scaffoldKey.currentState?.openDrawer();
          await WidgetsBinding.instance.endOfFrame;
        },
      ),
      TourStep(
        id: TourId('insights'),
        targetKey: _insightsKey,
        title: 'Your weekly insights',
        description:
            'This card appears farther down the page to demonstrate automatic scrolling.',
        tooltipPosition: TooltipPosition.auto,
        scrollToTarget: true,
        onBeforeShow: () async {
          _scaffoldKey.currentState?.closeDrawer();
          await WidgetsBinding.instance.endOfFrame;
        },
      ),
      TourStep(
        id: TourId('profile'),
        targetKey: _profileKey,
        title: 'Your Profile',
        description: 'Update your preferences and account details here.',
        tooltipPosition: TooltipPosition.left,
        spotlightShape: SpotlightShape.circle,
      ),
      TourStep(
        id: TourId('create'),
        targetKey: _fabKey,
        title: 'Create a project',
        description: 'Start something new whenever inspiration strikes.',
        tooltipPosition: TooltipPosition.top,
      ),
    ],
    onCompleted: () => _storage.markSeen(_tourId),
    onSkipped: () => _storage.markSeen(_tourId),
  );

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

  void _showMessage(String message) {
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text(message)));
  }

  void _openNavigation(BuildContext context) {
    Scaffold.of(context).openDrawer();
  }

  @override
  Widget build(BuildContext context) {
    return TourScope(
      controller: _tourController,
      child: Scaffold(
        key: _scaffoldKey,
        appBar: AppBar(
          leading: TourTarget(
            id: TourId('menu'),
            controller: _tourController,
            targetKey: _menuKey,
            child: Builder(
              builder: (context) => IconButton(
                tooltip: 'Open navigation',
                onPressed: () => _openNavigation(context),
                icon: const Icon(Icons.menu),
              ),
            ),
          ),
          title: const Text('Workspace'),
          actions: [
            TourTarget(
              id: TourId('search'),
              controller: _tourController,
              targetKey: _searchKey,
              child: IconButton(
                tooltip: 'Search',
                onPressed: () {},
                icon: const Icon(Icons.search),
              ),
            ),
            TourTarget(
              id: TourId('profile'),
              controller: _tourController,
              targetKey: _profileKey,
              child: IconButton(
                tooltip: 'Profile',
                onPressed: () {},
                icon: const Icon(Icons.account_circle_outlined),
              ),
            ),
            const SizedBox(width: 8),
          ],
        ),
        drawer: Drawer(
          child: SafeArea(
            child: ListView(
              padding: EdgeInsets.zero,
              children: [
                DrawerHeader(
                  decoration: BoxDecoration(
                    color: Theme.of(context).colorScheme.primaryContainer,
                  ),
                  child: Align(
                    alignment: Alignment.bottomLeft,
                    child: Text(
                      'Workspace',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                  ),
                ),
                TourTarget(
                  id: TourId('menu'),
                  controller: _tourController,
                  targetKey: _drawerOverviewKey,
                  child: ListTile(
                    leading: const Icon(Icons.dashboard_outlined),
                    title: const Text('Overview'),
                    selected: true,
                    onTap: () => Navigator.of(context).pop(),
                  ),
                ),
                ListTile(
                  leading: const Icon(Icons.folder_outlined),
                  title: const Text('Projects'),
                  onTap: () => Navigator.of(context).pop(),
                ),
                ListTile(
                  leading: const Icon(Icons.settings_outlined),
                  title: const Text('Settings'),
                  onTap: () => Navigator.of(context).pop(),
                ),
              ],
            ),
          ),
        ),
        body: ListView(
          padding: const EdgeInsets.fromLTRB(20, 24, 20, 120),
          children: [
            Text(
              'Good morning, Alex',
              style: Theme.of(
                context,
              ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Text(
              'Here is what is happening across your workspace.',
              style: Theme.of(context).textTheme.bodyLarge,
            ),
            const SizedBox(height: 24),
            Card(
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'This week',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 16),
                    const Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        _Metric(label: 'Tasks done', value: '24'),
                        _Metric(label: 'Projects', value: '08'),
                        _Metric(label: 'Focus hours', value: '18h'),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 420),
            Card(
              key: _insightsKey,
              color: Theme.of(context).colorScheme.primaryContainer,
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Icon(Icons.insights_outlined),
                    const SizedBox(height: 16),
                    Text(
                      'Weekly insights',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    const Text(
                      'You are most productive on Tuesday mornings. Keep that momentum going.',
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
        floatingActionButton: TourTarget(
          id: TourId('create'),
          controller: _tourController,
          targetKey: _fabKey,
          child: FloatingActionButton(
            onPressed: () => _showMessage('New project action'),
            tooltip: 'Create project',
            child: const Icon(Icons.add),
          ),
        ),
        bottomNavigationBar: SafeArea(
          child: Padding(
            padding: const EdgeInsets.fromLTRB(20, 8, 20, 12),
            child: FilledButton.icon(
              onPressed: _tourController.start,
              icon: const Icon(Icons.play_arrow),
              label: const Text('Start product tour'),
            ),
          ),
        ),
      ),
    );
  }
}

class _Metric extends StatelessWidget {
  const _Metric({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(value, style: Theme.of(context).textTheme.headlineSmall),
        const SizedBox(height: 4),
        Text(label),
      ],
    );
  }
}
1
likes
160
points
150
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A lightweight, customizable guided tour overlay for Flutter apps.

Repository (GitHub)
View/report issues

License

BSD-3-Clause (license)

Dependencies

flutter, shared_preferences

More

Packages that depend on flutter_app_tour