features_tour 0.5.0-rc.8 copy "features_tour: ^0.5.0-rc.8" to clipboard
features_tour: ^0.5.0-rc.8 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'),
      ),
    ),
    doneConfig: DoneConfig(
      child: (onPressed) => FilledButton(
        onPressed: onPressed,
        child: const Text('DONE'),
      ),
    ),
    debugLog: true,
  );

  runApp(const ChangeableThemeMaterialApp());
}

abstract class MainTourIndex {
  static const drawer = 0.0;
  static const buttonOnDrawer = 1.0;
  static const settingAction = 2.0;
  static const list = 3.0;
  static const firstItem = 4.0;
  static const item90 = 5.0;
  static const dialogButton = 5.5;
  static const floatingButton = 6.0;
  static const restartTourButton = 7.0;
}

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>();

  @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,
          index: MainTourIndex.drawer,
          nextIndex: MainTourIndex.buttonOnDrawer,
          onAfterIntroduce: (result) {
            if (result != IntroduceResult.next &&
                result != IntroduceResult.done) {
              return;
            }

            scaffoldKey.currentState?.openDrawer();
          },
          introduce: const Text('Tap here to open the drawer'),
          child: IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () {
              scaffoldKey.currentState?.openDrawer();
            },
          ),
        ),
        actions: [
          FeaturesTour(
            controller: tourController,
            index: MainTourIndex.settingAction,
            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,
            index: MainTourIndex.buttonOnDrawer,
            introduce: const Text('Tap here to close the drawer'),
            onAfterIntroduce: (result) {
              if (result case IntroduceResult.next || IntroduceResult.done) {
                scaffoldKey.currentState?.closeDrawer();
              }
            },
            child: ElevatedButton(
              child: const Text('Close Drawer'),
              onPressed: () {
                scaffoldKey.currentState?.closeDrawer();
              },
            ),
          ),
        ),
      ),
      body: FeaturesTour(
        controller: tourController,
        index: 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: SingleChildScrollView(
          controller: scrollController,
          child: SizedBox(
            width: double.infinity,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                FeaturesTour(
                  controller: tourController,
                  index: MainTourIndex.firstItem,
                  nextIndex: MainTourIndex.item90,
                  introduce: const Text('This is the item 0'),
                  child: const Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Text('Item 0'),
                  ),
                ),
                for (var index = 1; index <= 100; index++)
                  FeaturesTour(
                    enabled: index == 98,
                    controller: tourController,
                    index: MainTourIndex.item90,
                    nextIndex: MainTourIndex.dialogButton,
                    introduce: Text('This is the item $index'),
                    onBeforeIntroduce: () async {
                      // 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,
                      );
                    },
                    onAfterIntroduce: (introduceResult) async {
                      if (introduceResult
                          case IntroduceResult.next || IntroduceResult.done) {
                        // Scroll to the first item when item 90 is tapped
                        await scrollController.animateTo(
                          0,
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut,
                        );

                        // Show a dialog after item 90 is tapped
                        if (context.mounted) {
                          showDialog(
                            context: context,
                            builder: (context) {
                              return AlertDialog(
                                title: const Text('A Dialog'),
                                actions: [
                                  FeaturesTour(
                                    controller: tourController,
                                    index: MainTourIndex.dialogButton,
                                    introduce: const Text(
                                      'Tap here to close the dialog',
                                    ),
                                    onAfterIntroduce: (result) {
                                      if (introduceResult !=
                                              IntroduceResult.next &&
                                          introduceResult !=
                                              IntroduceResult.done) {
                                        return;
                                      }

                                      Navigator.of(context).pop();
                                    },
                                    child: TextButton(
                                      onPressed: () {
                                        Navigator.of(context).pop();
                                      },
                                      child: const Text('Ok'),
                                    ),
                                  ),
                                ],
                              );
                            },
                          );
                        }
                      }
                    },
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Text('Item $index'),
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FeaturesTour(
            controller: tourController,
            index: MainTourIndex.floatingButton,
            introduce: const Text('Tap here to add a new item'),
            childConfig: ChildConfig(
              shapeBorder: const CircleBorder(),
              borderSizeInflate: 10.0,
            ),
            doneConfig: DoneConfig(alignment: Alignment.bottomLeft),
            child: FloatingActionButton(
              onPressed: () {},
              child: const Icon(Icons.add),
            ),
          ),
          const SizedBox(height: 16.0),
          FeaturesTour(
            controller: tourController,
            index: MainTourIndex.restartTourButton,
            introduce: const Text('Tap here to run the tour again'),
            childConfig: ChildConfig(
              shapeBorder: const CircleBorder(),
              borderSizeInflate: 10.0,
            ),
            doneConfig: DoneConfig(alignment: Alignment.bottomLeft),
            child: FloatingActionButton(
              onPressed: () {
                tourController.start(
                  context,
                  force: true,
                  preDialogConfig: PreDialogConfig(enabled: false),
                );
              },
              child: const Icon(Icons.restart_alt_rounded),
            ),
          ),
        ],
      ),
    );
  }
}
38
likes
0
points
1.51k
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, shared_preferences

More

Packages that depend on features_tour