features_tour 0.7.0-rc.1 copy "features_tour: ^0.7.0-rc.1" to clipboard
features_tour: ^0.7.0-rc.1 copied to clipboard

Features Tour is a package that enables you to easily create tours to introduce your widget features in your app with order.

example/lib/main.dart

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

void main() {
  FeaturesTour.setGlobalConfig(
    preDialogConfig: PreDialogConfig(
      enabled: true,
      title: 'Welcome to Features Tour',
      content: 'This tour will guide you through the main features of the app.',
      applyToAllCheckboxLabel: 'Apply to all pages',
      acceptButtonLabel: 'Start Tour',
      laterButtonLabel: 'Later',
      dismissButtonLabel: 'Dismiss',
    ),
    introduceConfig: RoundedRectIntroduceConfig(),
    childConfig: ChildConfig(isAnimateChild: false),
    skipConfig: SkipConfig(
      builder: (context, onPressed) => ElevatedButton.icon(
        onPressed: onPressed,
        icon: const Icon(Icons.skip_next),
        label: const Text('SKIP'),
      ),
    ),
    nextConfig: NextConfig(
      builder: (context, onPressed) => FilledButton.icon(
        onPressed: onPressed,
        icon: const Icon(Icons.arrow_forward),
        label: const Text('NEXT'),
      ),
    ),
    previousConfig: PreviousConfig(
      builder: (context, onPressed) => FilledButton.icon(
        onPressed: onPressed,
        icon: const Icon(Icons.arrow_back),
        label: const Text('PREVIOUS'),
      ),
    ),
    doneConfig: DoneConfig(
      builder: (context, onPressed) => FilledButton.icon(
        onPressed: onPressed,
        icon: const Icon(Icons.done),
        label: const Text('DONE'),
      ),
    ),
    debugLog: true,
  );

  runApp(const ChangeableThemeMaterialApp());
}

enum MainTourIndex {
  drawer,
  buttonOnDrawer,
  settingAction,
  list,
  firstItem,
  item90,
  dialogButton,
  restartTourButton,
  floatingButton,
}

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

  @override
  State<ChangeableThemeMaterialApp> createState() =>
      _ChangeableThemeMaterialAppState();
}

class _ChangeableThemeMaterialAppState
    extends State<ChangeableThemeMaterialApp> {
  bool isDark = false;

  void toggleTheme() {
    setState(() {
      isDark = !isDark;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Features Tour Example',
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
      home: const App(),
    );
  }
}

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

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  final tourController = FeaturesTourController('App');
  final scrollController = ScrollController();
  final scaffoldKey = GlobalKey<ScaffoldState>();

  void _openDrawer() => scaffoldKey.currentState?.openDrawer();

  void _closeDrawer() => scaffoldKey.currentState?.closeDrawer();

  void _showTourDialogIfNeeded() {
    if (!mounted) return;

    showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (dialogContext) {
        return AlertDialog(
          title: const Text('A Dialog'),
          actions: [
            FeaturesTour(
              controller: tourController,
              step: MainTourIndex.dialogButton,
              introduce: const Text('Tap here to close the dialog'),
              onAfterAction: (action) {
                Navigator.of(dialogContext).pop();
              },
              child: TextButton(
                onPressed: () {
                  Navigator.of(dialogContext).pop();
                },
                child: const Text('Ok'),
              ),
            ),
          ],
        );
      },
    );
  }

  @override
  void initState() {
    tourController.start(context);

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    final changeableState =
        context.findAncestorStateOfType<_ChangeableThemeMaterialAppState>()!;
    return Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        title: const Text('App'),
        leading: FeaturesTour(
          controller: tourController,
          step: MainTourIndex.drawer,
          nextStep: MainTourIndex.buttonOnDrawer,
          onAfterAction: (action) {
            if (action != TourAction.next && action != TourAction.done) {
              return;
            }

            _openDrawer();
          },
          introduce: const Text('Tap here to open the drawer'),
          child: IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () {
              _openDrawer();
            },
          ),
        ),
        actions: [
          FeaturesTour(
            controller: tourController,
            step: MainTourIndex.settingAction,
            onAfterAction: (action) {
              if (action == TourAction.previous) {
                _openDrawer();
              }
            },
            introduce: const Text(
                'Tap here to change the brightness and reset the tour'),
            child: IconButton(
              icon: changeableState.isDark
                  ? const Icon(Icons.dark_mode)
                  : const Icon(Icons.light_mode),
              onPressed: () async {
                changeableState.toggleTheme();
                tourController.start(context, force: true);
              },
            ),
          )
        ],
      ),
      drawer: Drawer(
        child: Center(
          child: FeaturesTour(
            controller: tourController,
            step: MainTourIndex.buttonOnDrawer,
            introduce: const Text('Tap here to close the drawer'),
            onAfterAction: (action) {
              if (action
                  case TourAction.next ||
                      TourAction.done ||
                      TourAction.previous ||
                      TourAction.skip) {
                _closeDrawer();
              }
            },
            child: ElevatedButton(
              child: const Text('Close Drawer'),
              onPressed: () {
                _closeDrawer();
              },
            ),
          ),
        ),
      ),
      body: FeaturesTour(
        controller: tourController,
        step: MainTourIndex.list,
        introduce: Text(
          'This is a list of items',
          style: TextStyle(
            color: Theme.of(context).colorScheme.onPrimary,
          ),
        ),
        introduceConfig: IntroduceConfig(
          builder: (context, childRect, introduce) {
            return Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: ColorScheme.of(context).primary.withValues(alpha: 0.9),
                borderRadius: BorderRadius.circular(8),
              ),
              child: introduce,
            );
          },
          quadrantAlignment: QuadrantAlignment.inside,
        ),
        childConfig: ChildConfig(
          enableAnimation: false,
        ),
        child: Stack(
          children: [
            SingleChildScrollView(
              controller: scrollController,
              child: SizedBox(
                width: double.infinity,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    FeaturesTour(
                      controller: tourController,
                      step: MainTourIndex.firstItem,
                      nextStep: MainTourIndex.item90,
                      introduce: const Text('This is the item 0'),
                      onBeforeAction: (action) async {
                        if (action case TourAction.previous) {
                          await scrollController.animateTo(
                            0,
                            duration: const Duration(milliseconds: 300),
                            curve: Curves.easeInOut,
                          );
                        }
                      },
                      child: const Padding(
                        padding: EdgeInsets.all(16.0),
                        child: Text('Item 0'),
                      ),
                    ),
                    for (var index = 1; index <= 100; index++)
                      FeaturesTour(
                        enabled: index == 95,
                        controller: tourController,
                        step: MainTourIndex.item90,
                        nextStep: MainTourIndex.dialogButton,
                        introduce: Text('This is the item $index'),
                        onBeforeAction: (action) async {
                          if (action
                              case TourAction.next || TourAction.previous) {
                            // Scroll to the last item when the first item is tapped
                            await scrollController.animateTo(
                              scrollController.position.maxScrollExtent,
                              duration: const Duration(milliseconds: 300),
                              curve: Curves.easeInOut,
                            );
                          }
                        },
                        onAfterAction: (action) async {
                          if (action case TourAction.previous) {
                            // Scroll to the first item when item 90 is tapped
                            await scrollController.animateTo(
                              0,
                              duration: const Duration(milliseconds: 300),
                              curve: Curves.easeInOut,
                            );
                            return;
                          }

                          if (action case TourAction.next) {
                            _showTourDialogIfNeeded();
                          }
                        },
                        child: Padding(
                          padding: const EdgeInsets.all(16.0),
                          child: Text('Item $index'),
                        ),
                      ),
                  ],
                ),
              ),
            ),
            SafeArea(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        FeaturesTour(
                          controller: tourController,
                          step: MainTourIndex.restartTourButton,
                          onAfterAction: (action) {
                            if (action case TourAction.previous) {
                              _showTourDialogIfNeeded();
                            }
                          },
                          introduce:
                              const Text('Tap here to run the tour again'),
                          childConfig: ChildConfig(
                            shapeBorder: const CircleBorder(),
                            borderSizeInflate: 10.0,
                          ),
                          child: FloatingActionButton(
                            onPressed: () {
                              tourController.start(
                                context,
                                force: true,
                                preDialogConfig:
                                    PreDialogConfig(enabled: false),
                              );
                            },
                            child: const Icon(Icons.restart_alt_rounded),
                          ),
                        ),
                        FeaturesTour(
                          controller: tourController,
                          step: MainTourIndex.floatingButton,
                          introduce: const Text('Tap here to add a new item'),
                          childConfig: ChildConfig(
                            shapeBorder: const CircleBorder(),
                            borderSizeInflate: 10.0,
                          ),
                          child: FloatingActionButton(
                            onPressed: () {},
                            child: const Icon(Icons.add),
                          ),
                        ),
                      ],
                    ),
                    FeaturesTourPadding(
                      controller: tourController,
                      steps: const {
                        MainTourIndex.floatingButton,
                        MainTourIndex.restartTourButton
                      },
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
52
likes
0
points
1.74k
downloads

Publisher

verified publisherlamnhan.dev

Weekly Downloads

Features Tour is a package that enables you to easily create tours to introduce your widget features in your app with order.

Repository (GitHub)
View/report issues

Topics

#features #discovery #introduction #guide #onboarding

License

unknown (license)

Dependencies

flutter, lite_logger, shared_preferences

More

Packages that depend on features_tour