flog_flow 0.1.1
flog_flow: ^0.1.1 copied to clipboard
Graba el flujo de navegación real del usuario (pantallas, taps, red, logs y errores) y lo exporta en un archivo cifrado .flowx que se reproduce en un visor web.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flog_flow/flog_flow.dart';
void main() {
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlogFlow demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true, colorSchemeSeed: const Color(0xFF5B54E6)),
// 1) Observa la navegación.
navigatorObservers: [FlogFlow.instance.navigatorObserver],
// 2) Envuelve toda la app para capturar taps + screenshots.
builder: (context, child) => FlogFlogScope(child: child!),
initialRoute: '/',
routes: {
'/': (_) => const StartPage(),
'/password': (_) => const PasswordPage(),
'/dashboard': (_) => const DashboardPage(),
'/details': (_) => const DetailsPage(),
'/signup': (_) => const SignUpPage(),
},
);
}
}
class StartPage extends StatefulWidget {
const StartPage({super.key});
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
@override
void initState() {
super.initState();
// 3) Empieza a grabar al arrancar.
WidgetsBinding.instance.addPostFrameCallback((_) {
FlogFlow.instance.start(
appName: 'Demo App',
initialRouteName: '/',
// El export siempre va cifrado: define una clave.
config: const CaptureConfig(encryptionPassphrase: 'demo-clave'),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlutterLogo(size: 96),
const SizedBox(height: 24),
const Text('Start page', style: TextStyle(fontSize: 22)),
const SizedBox(height: 24),
FilledButton(
onPressed: () {
// 4) Etiqueta la acción ANTES de navegar.
FlogFlow.instance.tag('User clicks "Sign in"');
Navigator.pushNamed(context, '/password');
},
child: const Text('Sign in'),
),
const SizedBox(height: 12),
TextButton(
onPressed: () {
FlogFlow.instance.tag('User clicks "Sign up"');
Navigator.pushNamed(context, '/signup');
},
child: const Text('Sign up'),
),
],
),
),
);
}
}
class PasswordPage extends StatelessWidget {
const PasswordPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Password')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const TextField(decoration: InputDecoration(labelText: 'Email')),
const TextField(
obscureText: true,
decoration: InputDecoration(labelText: 'Password')),
const SizedBox(height: 24),
FilledButton(
onPressed: () {
FlogFlow.instance.tag('Submit credentials');
Navigator.pushNamed(context, '/dashboard');
},
child: const Text('Continue'),
),
],
),
),
);
}
}
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dashboard')),
body: ListView(
children: [
SizedBox(
height: 90,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(12),
children: List.generate(
8,
(i) => const Padding(
padding: EdgeInsets.all(8),
child: CircleAvatar(radius: 28, child: Icon(Icons.person)),
),
),
),
),
for (var i = 0; i < 6; i++)
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
leading: const Icon(Icons.image),
title: Text('Card #$i'),
onTap: () {
FlogFlow.instance.tag('Open details #$i');
Navigator.pushNamed(context, '/details');
},
),
),
],
),
);
}
}
class DetailsPage extends StatelessWidget {
const DetailsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
floatingActionButton: FloatingActionButton.extended(
icon: const Icon(Icons.download),
label: const Text('Export'),
onPressed: () async {
await FlogFlow.instance.stop();
final file = await FlogFlow.instance.export();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Exportado: ${file.path}')),
);
}
},
),
body: const Center(
child: Icon(Icons.donut_large, size: 160, color: Color(0xFF5B54E6)),
),
);
}
}
class SignUpPage extends StatelessWidget {
const SignUpPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign up form')),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
for (final f in ['Name', 'Email', 'Password', 'Confirm'])
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(decoration: InputDecoration(labelText: f)),
),
const SizedBox(height: 16),
FilledButton(
onPressed: () => Navigator.pop(context),
child: const Text('Create account'),
),
],
),
);
}
}