spotlight_tutorial 2.0.4
spotlight_tutorial: ^2.0.4 copied to clipboard
A lightweight Flutter package for interactive feature tutorials with spotlight overlays, coach marks, and sequential guided steps.
import 'package:flutter/material.dart';
import 'package:spotlight_tutorial/spotlight_tutorial.dart';
void main() {
runApp(const TutorialDemoApp());
}
class TutorialDemoApp extends StatelessWidget {
const TutorialDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Spotlight Tutorial Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
extensions: const [
SpotlightTutorialTheme(
tooltipBackgroundColor: Color(0xFFFFFFFF),
scrimColor: Color(0xCC121212),
),
],
),
home: const TutorialDemoPage(),
);
}
}
/// GlobalKeys for each UI element highlighted by the tutorial.
class DemoTargets {
final searchKey = GlobalKey();
final walletCardKey = GlobalKey();
final settingsKey = GlobalKey();
final fabKey = GlobalKey();
}
class TutorialDemoPage extends StatefulWidget {
const TutorialDemoPage({super.key});
@override
State<TutorialDemoPage> createState() => _TutorialDemoPageState();
}
class _TutorialDemoPageState extends State<TutorialDemoPage> {
final _targets = DemoTargets();
int _guideSession = 0;
bool _guideDismissed = false;
int _selectedTab = 0;
double _balance = 1240.50;
void _restartTutorial() {
setState(() {
_guideDismissed = false;
_guideSession++;
});
}
List<TutorialStep> _buildSteps(BuildContext context, DemoTargets? targets) {
final t = targets ?? _targets;
return [
TutorialStep(
targetKey: t.searchKey,
title: 'Quick search',
body: [
'Find accounts, transactions, or help articles from here.',
],
arrowEdge: TutorialArrowEdge.bottom,
arrowAlign: 0.15,
),
TutorialStep(
targetKey: t.walletCardKey,
title: 'Wallet overview',
body: [
'Your balance and recent activity appear on this card.',
'Swipe actions will be available in a future update.',
],
arrowEdge: TutorialArrowEdge.top,
),
TutorialStep(
targetKey: t.settingsKey,
title: 'Settings',
body: ['Open settings and notification controls.'],
arrowEdge: TutorialArrowEdge.bottom,
arrowAlign: 0.92,
),
TutorialStep(
targetKey: t.fabKey,
title: 'New transfer',
body: ['Tap here to send money or add funds.'],
arrowEdge: TutorialArrowEdge.bottom,
arrowAlign: 0.85,
),
];
}
@override
Widget build(BuildContext context) {
return TutorialScope<DemoTargets>(
key: ValueKey(_guideSession),
shouldStart: () => !_guideDismissed,
onDismiss: () => setState(() => _guideDismissed = true),
targets: _targets,
stepsBuilder: _buildSteps,
child: Scaffold(
appBar: AppBar(
title: const Text('Tutorial demo'),
actions: [
IconButton(
key: _targets.settingsKey,
icon: const Icon(Icons.settings_outlined),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.help_outline),
onPressed: _restartTutorial,
tooltip: 'Show tutorial again',
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
FilledButton.tonalIcon(
onPressed: _restartTutorial,
icon: const Icon(Icons.play_circle_outline),
label: const Text('Show tutorial'),
),
const SizedBox(height: 16),
TextField(
key: _targets.searchKey,
decoration: InputDecoration(
hintText: 'Search…',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
const SizedBox(height: 20),
Card(
key: _targets.walletCardKey,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Available balance',
style: Theme.of(context).textTheme.labelLarge,
),
const SizedBox(height: 8),
Text(
'\$${_balance.toStringAsFixed(2)}',
style: Theme.of(context).textTheme.headlineMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.arrow_upward),
label: const Text('Send'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
setState(() => _balance += 10);
},
icon: const Icon(Icons.add),
label: const Text('Add'),
),
),
],
),
],
),
),
),
const SizedBox(height: 16),
const ListTile(
leading: Icon(Icons.receipt_long),
title: Text('Recent transactions'),
subtitle: Text('3 pending · 12 completed'),
trailing: Icon(Icons.chevron_right),
),
const ListTile(
leading: Icon(Icons.insights_outlined),
title: Text('Spending insights'),
subtitle: Text('Updated today'),
trailing: Icon(Icons.chevron_right),
),
],
),
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedTab,
onDestinationSelected: (i) => setState(() => _selectedTab = i),
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.pie_chart_outline),
selectedIcon: Icon(Icons.pie_chart),
label: 'Stats',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Profile',
),
],
),
floatingActionButton: FloatingActionButton.extended(
key: _targets.fabKey,
onPressed: () {},
icon: const Icon(Icons.swap_horiz),
label: const Text('Transfer'),
),
),
);
}
}