abtasty_qa_assistant 1.0.0
abtasty_qa_assistant: ^1.0.0 copied to clipboard
Flutter package providing quality assurance tools and utilities for ABTasty testing workflows.
import 'package:flagship/hits/event.dart';
import 'package:flagship/hits/screen.dart';
import 'package:flagship/hits/transaction.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:abtasty_qa_assistant/abtasty_qa_assistant.dart';
import 'package:flagship/flagship.dart';
void main() {
runApp(const ABTastyDemo());
}
class ABTastyDemo extends StatelessWidget {
const ABTastyDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ABTasty QA Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF2196F3),
brightness: Brightness.light,
),
useMaterial3: true,
appBarTheme: const AppBarTheme(centerTitle: true, elevation: 2),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF2196F3),
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const DemoHomePage(),
);
}
}
class DemoHomePage extends StatefulWidget {
const DemoHomePage({super.key});
@override
State<DemoHomePage> createState() => _DemoHomePageState();
}
class _DemoHomePageState extends State<DemoHomePage> {
ABTastyQAAssistant? _qaAssistant;
String _btnTitleValue = "Loading...";
String _btnColorValue = "Loading...";
String _flag1Value = "Loading...";
int _flag2Value = 0;
String _key1Value = "Loading...";
bool _isVIPMode = false;
String _vipValue = "Loading...";
@override
void initState() {
super.initState();
_initFlagship();
// Ne pas appeler _setupFlagUpdateListener() ici
}
Future<void> _initFlagship() async {
try {
// Init flagship sdk
await Flagship.start(
"bkk9glocmjcg0vtmdlng",
"DxAcxlnRB9yFBZYtLDue1q01dcXZCw6aM49CQB23",
);
Flagship.newVisitor(visitorId: "qaUser", hasConsented: true).withContext({
"isQA": true,
"country": "FR",
"customer": "customer",
"accountID": "1234",
"isVip": _isVIPMode
}).build();
print("Flagship SDK initialized successfully");
// Fetch Flags
await Flagship.getCurrentVisitor()?.fetchFlags();
// Récupérer les valeurs initiales des flags
_updateFlagValues();
// ✅ Configurer le listener APRÈS que le visitor soit créé
_setupFlagUpdateListener();
} catch (e) {
print("Error initializing Flagship SDK: $e");
setState(() {
_btnTitleValue = "Error loading flag";
});
}
}
/// Setup listener for live flag updates from QA Assistant
void _setupFlagUpdateListener() {
final visitor = Flagship.getCurrentVisitor();
if (visitor != null) {
visitor.onFlagUpdate = (changedKeys) {
print('🔔 Live update received for flags: $changedKeys');
// Show a snackbar to indicate live update
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('🔄 Live update: ${changedKeys.join(", ")}'),
backgroundColor: Colors.orange,
duration: const Duration(seconds: 2),
),
);
}
// Update the flag values
_updateFlagValues();
};
print('✅ Flag update listener registered');
}
}
/// Update all flag values and refresh UI
void _updateFlagValues() {
final visitor = Flagship.getCurrentVisitor();
if (visitor != null) {
setState(() {
_btnTitleValue = visitor.getFlag("btnTitle").value("Default Button");
_btnColorValue = visitor.getFlag("btnColor").value("Default Color");
_flag1Value = visitor.getFlag("payKey1").value("Default Flag1");
_flag2Value = visitor.getFlag("payKey2").value(0);
_key1Value = visitor.getFlag("rejectedKey").value("Rejected Key");
_vipValue = visitor.getFlag("Delivery cost").value("25 €").toString();
});
print("Flag values updated:");
print(" btnTitle: $_btnTitleValue");
print(" btnColor: $_btnColorValue");
print(" payKey1: $_flag1Value");
print(" payKey2: $_flag2Value");
print(" rejectedKey: $_key1Value");
print(" Delivery cost: $_vipValue");
}
}
@override
void dispose() {
// Clean up the flag update listener
final visitor = Flagship.getCurrentVisitor();
if (visitor != null) {
visitor.onFlagUpdate = null;
}
_destroyQAAssistant();
super.dispose();
}
void _initializeQAAssistant() {
if (_qaAssistant == null) {
_qaAssistant = ABTastyQAAssistant(
"bkk9glocmjcg0vtmdlng",
"DxAcxlnRB9yFBZYtLDue1q01dcXZCw6aM49CQB23",
onClose: () {
// Automatically refresh flags when QA Assistant is closed
print('🔄 QA Assistant closed, fetching updated flags...');
_fetchFlagsFromSDK();
},
);
print('✅ QA Assistant initialized and bucketing preloading started');
}
}
void _destroyQAAssistant() {
if (_qaAssistant != null) {
_qaAssistant!.hideOverlayButton();
_qaAssistant!.dispose();
_qaAssistant = null;
print('🗑️ QA Assistant destroyed');
}
}
void _toggleQAAssistant() {
if (_qaAssistant?.isOverlayVisible ?? false) {
_destroyQAAssistant();
} else {
_initializeQAAssistant();
_qaAssistant?.showOverlayButton(context);
}
setState(() {});
}
Future<void> _refreshFlags() async {
try {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Refreshing flag values...'),
duration: Duration(seconds: 1),
),
);
_updateFlagValues();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Flag values refreshed successfully!'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
} catch (e) {
print("Error refreshing flags: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error refreshing flags: $e'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
),
);
}
}
/// Fetch flags from SDK and update UI
Future<void> _fetchFlagsFromSDK() async {
try {
final visitor = Flagship.getCurrentVisitor();
if (visitor != null) {
// Fetch fresh flags from SDK
await visitor.fetchFlags();
print('✅ Flags fetched from SDK successfully');
// Update UI with new values
_updateFlagValues();
// Show notification if context is available
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('🔄 Flags automatically refreshed'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
}
}
} catch (e) {
print('❌ Error fetching flags from SDK: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: _toggleQAAssistant,
icon: Icon(
_qaAssistant?.isOverlayVisible ?? false
? Icons.visibility_off
: Icons.bug_report,
),
tooltip: _qaAssistant?.isOverlayVisible ?? false
? 'Hide QA Assistant'
: 'Show QA Assistant',
),
title: const Text('ABTasty QA Demo'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
IconButton(
onPressed: () async {
await Flagship.getCurrentVisitor()?.fetchFlags();
_refreshFlags();
},
icon: const Icon(Icons.sync),
tooltip: 'Fetch Flags',
),
IconButton(
onPressed: _refreshFlags,
icon: const Icon(Icons.refresh),
tooltip: 'Refresh Flags',
),
],
),
body: _buildOverviewTab(),
);
}
Widget _buildOverviewTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 10),
// Flagship Flag Display
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.flag, color: Theme.of(context).primaryColor),
const SizedBox(width: 8),
const Text(
'Flagship Flags',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
_buildFlagRow('btnTitle - Login A/B', _btnTitleValue),
const Divider(),
_buildFlagRow('btnColor - Login A/B', _btnColorValue),
const Divider(),
_buildFlagRow('payKey1 - Payment', _flag1Value),
const Divider(),
_buildFlagRow('payKey2 - Payment', _flag2Value.toString()),
const Divider(),
_buildFlagRow('rejectedKey', _key1Value),
const Divider(),
_buildFlagRow('Delivery cost - VIP Mode', _vipValue)
],
),
),
),
const SizedBox(height: 20),
// Send Screen Event Button
ElevatedButton.icon(
onPressed: _sendEvents,
icon: const Icon(Icons.send),
label: const Text('Send Events'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 20),
// VIP Mode Toggle
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'VIP Mode',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
_isVIPMode ? 'Enabled' : 'Disabled',
style: TextStyle(
fontSize: 12,
color: _isVIPMode ? Colors.green : Colors.grey,
),
),
],
),
Switch(
value: _isVIPMode,
onChanged: (bool newValue) async {
setState(() {
_isVIPMode = newValue;
});
// Update context
Flagship.getCurrentVisitor()
?.updateContext("isVip", newValue);
// Fetch flags with the new context
await Flagship.getCurrentVisitor()?.fetchFlags();
// Update UI
_updateFlagValues();
// Show feedback
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
newValue
? '✅ VIP Mode enabled'
: '❌ VIP Mode disabled',
),
backgroundColor:
newValue ? Colors.green : Colors.orange,
duration: const Duration(seconds: 2),
),
);
}
},
),
],
),
),
)
],
),
);
}
void _sendEvents() {
try {
final visitor = Flagship.getCurrentVisitor();
if (visitor != null) {
// Send a screen hit
visitor.sendHit(Screen(location: "HomeScreen"));
// Send Event
visitor.sendHit(
Event(action: "eventQA", category: EventCategory.Action_Tracking),
);
visitor.sendHit(
Event(action: "eventQA", category: EventCategory.User_Engagement),
);
// Send Transaction Event
visitor.sendHit(
Transaction(
transactionId: "transactionId",
affiliation: "affiliationQA",
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('📤 Events sent successfully!'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
print('✅ Events sent successfully!');
} else {
throw Exception('Visitor not initialized');
}
} catch (e) {
print('❌ Error sending events: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error sending events: $e'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
),
);
}
}
Widget _buildFlagRow(String flagName, String flagValue) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Flag Name:',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(height: 4),
Text(
flagName,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue[700],
),
),
],
),
),
Container(width: 1, height: 40, color: Colors.grey[300]),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'Flag Value:',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(height: 4),
Text(
flagValue,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.green[700],
),
textAlign: TextAlign.right,
),
],
),
),
],
),
);
}
}