flutter_feature_hint 0.0.6
flutter_feature_hint: ^0.0.6 copied to clipboard
Create interactive feature tutorials with gesture-driven overlays. Users must perform actual gestures on your UI to dismiss hints - perfect for onboarding.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_feature_hint/flutter_feature_hint.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Feature Hint Demo',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
appBarTheme: const AppBarTheme(centerTitle: true, elevation: 0),
),
home: const DemoHomePage(),
);
}
}
class DemoHomePage extends StatefulWidget {
const DemoHomePage({Key? key}) : super(key: key);
@override
State<DemoHomePage> createState() => _DemoHomePageState();
}
class _DemoHomePageState extends State<DemoHomePage> {
final List<String> items = List.generate(12, (i) => 'Task ${i + 1}');
@override
Widget build(BuildContext context) {
// Build the ListView with hints wrapped around it
Widget listView = ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (context, index) {
return Dismissible(
key: Key(items[index]),
direction: DismissDirection.horizontal,
onDismissed: (direction) {
final dismissedItem = items[index];
setState(() {
items.removeAt(index);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
direction == DismissDirection.endToStart
? '✓ Deleted: $dismissedItem'
: '✓ Completed: $dismissedItem',
),
backgroundColor: direction == DismissDirection.endToStart
? Colors.red
: Colors.green,
duration: const Duration(seconds: 2),
),
);
},
background: Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 20),
child: const Icon(Icons.check, color: Colors.white, size: 28),
),
secondaryBackground: Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(Icons.delete, color: Colors.white, size: 28),
),
child: Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.indigo.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.indigo, Colors.purple],
),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
title: Text(
items[index],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
'Swipe to ${index % 2 == 0 ? 'complete' : 'delete'}',
style: TextStyle(fontSize: 13, color: Colors.grey[500]),
),
trailing: Icon(Icons.drag_indicator, color: Colors.grey[400]),
),
),
);
},
);
// Wrap listview with FeatureHintOverlay
listView = FeatureHintOverlay(
message: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: Colors.indigo[100],
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.touch_app, size: 18, color: Colors.indigo),
),
const SizedBox(height: 12),
const Text(
"Try swiping items!",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
"Swipe left to delete or right to complete",
style: TextStyle(fontSize: 13, color: Colors.grey[600]),
textAlign: TextAlign.center,
),
],
),
),
gesture: GestureType.swipeRight,
duration: const Duration(seconds: 6),
shouldPlay: true,
playOnceInLifetime: true,
uniqueKey: 'swipe_right_hint3',
customIcon: const Icon(Icons.swipe_right, size: 48, color: Colors.white),
onCompleted: () {
print("Feature hint animation completed!");
},
child: listView,
);
return Scaffold(
appBar: AppBar(
title: const Text(
'Task Manager',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
elevation: 0,
backgroundColor: Colors.transparent,
),
body: Column(
children: [
// Header section with gradient
FeatureHintOverlay(
gesture: GestureType.swipeRight,
duration: const Duration(seconds: 6),
shouldPlay: true,
playOnceInLifetime: true,
limitToChildBounds: true,
uniqueKey: 'header_hint',
customIcon: const Icon(
Icons.swipe_right,
size: 48,
color: Colors.white,
),
onCompleted: () {
print("Feature hint animation completed!");
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.indigo, Colors.indigo[700]!],
),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Welcome Back!',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'${items.length} Tasks',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
// Example with limitToChildBounds - overlay constrained to widget bounds
Padding(
padding: const EdgeInsets.all(16.0),
child: FeatureHintOverlay(
uniqueKey: 'bounded_card_hint2',
gesture: GestureType.tap,
duration: const Duration(seconds: 4),
playOnceInLifetime: true,
limitToChildBounds:
true, // New feature: limit overlay to child bounds
message: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.shade700,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'👆 Tap this card\n(overlay limited to bounds)',
style: TextStyle(color: Colors.white, fontSize: 13),
textAlign: TextAlign.center,
),
),
customIcon: Icon(
Icons.touch_app,
size: 48,
color: Colors.blue.shade700,
),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
border: Border.all(color: Colors.blue.shade200),
borderRadius: BorderRadius.circular(12),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bounded Hint Example',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
SizedBox(height: 8),
Text(
'The overlay animation and message are constrained within this card\'s bounds when limitToChildBounds is enabled.',
style: TextStyle(fontSize: 13, color: Colors.grey),
),
],
),
),
),
),
// ListView with height 400
Expanded(child: listView),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
setState(() {
items.add('New Task ${items.length + 1}');
});
},
label: const Text('Add Task'),
icon: const Icon(Icons.add),
),
);
}
}