smart_review_prompter 1.1.0
smart_review_prompter: ^1.1.0 copied to clipboard
A smart way to show in-app review prompts based on user engagement, such as app open count and days since install.
import 'package:flutter/material.dart';
import 'package:smart_review_prompter/smart_review_prompter.dart';
void main() async {
// Required for asynchronous initialization
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialize the prompter package
await SmartReviewPrompter.instance.initialize();
// 2. Increment the app open count automatically on startup
await SmartReviewPrompter.instance.incrementAppOpenCount();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Smart Review Prompter',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF2CAC5C), // Brand Green Color
primary: const Color(0xFF2CAC5C),
secondary: const Color(0xFF2563EB), // Royal Blue
),
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _appOpenCount = 0;
int _daysSinceInstall = 0;
int _promptCount = 0;
bool _hasAlreadyRated = false;
String _lastPromptDate = 'Never';
// Controller inputs for customizable testing
final TextEditingController _minOpensController = TextEditingController(text: '3');
final TextEditingController _minDaysController = TextEditingController(text: '0');
final TextEditingController _maxPromptsController = TextEditingController(text: '3');
final TextEditingController _daysCooldownController = TextEditingController(text: '0');
final TextEditingController _opensCooldownController = TextEditingController(text: '2');
@override
void initState() {
super.initState();
_refreshStats();
}
@override
void dispose() {
_minOpensController.dispose();
_minDaysController.dispose();
_maxPromptsController.dispose();
_daysCooldownController.dispose();
_opensCooldownController.dispose();
super.dispose();
}
/// Re-fetch and refresh all prompter statistics
Future<void> _refreshStats() async {
final opens = await SmartReviewPrompter.instance.getAppOpenCount();
final days = await SmartReviewPrompter.instance.getDaysSinceInstall();
final prompts = await SmartReviewPrompter.instance.getPromptCount();
final rated = await SmartReviewPrompter.instance.hasUserAlreadyRated();
final lastDate = await SmartReviewPrompter.instance.getLastPromptDate();
setState(() {
_appOpenCount = opens;
_daysSinceInstall = days;
_promptCount = prompts;
_hasAlreadyRated = rated;
_lastPromptDate = lastDate != null ? lastDate.toLocal().toString().split('.')[0] : 'Never';
});
}
void _showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError ? Colors.red : const Color(0xFF2CAC5C),
behavior: SnackBarBehavior.floating,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC), // Off-white modern surface
appBar: AppBar(
title: const Text(
'Smart Review Playground',
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
),
backgroundColor: const Color(0xFF2CAC5C), // Brand green
centerTitle: true,
elevation: 2,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// --- STATS CARD (Glassmorphic / Aesthetic Feel) ---
Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.dashboard, color: Color(0xFF2CAC5C)),
SizedBox(width: 8),
Text(
'Prompter Status & Stats',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF0F172A)),
),
],
),
const Divider(height: 24),
_buildStatRow('App Open Count', '$_appOpenCount times', Colors.blue.shade700),
_buildStatRow('Days Since Install', '$_daysSinceInstall days', Colors.orange.shade700),
_buildStatRow('Total Prompt Count', '$_promptCount times', Colors.purple.shade700),
_buildStatRow('User Already Rated?', _hasAlreadyRated ? 'YES' : 'NO', _hasAlreadyRated ? Colors.green.shade700 : Colors.red.shade700),
_buildStatRow('Last Prompt Date', _lastPromptDate, const Color(0xFF64748B)),
],
),
),
),
const SizedBox(height: 16),
// --- CONTROLLER CARDS ---
Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.tune, color: Color(0xFF2563EB)),
SizedBox(width: 8),
Text(
'Test Parameters Configuration',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF0F172A)),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(child: _buildTextField('Min Opens', _minOpensController)),
const SizedBox(width: 12),
Expanded(child: _buildTextField('Min Days', _minDaysController)),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(child: _buildTextField('Max Prompts', _maxPromptsController)),
const SizedBox(width: 12),
Expanded(child: _buildTextField('Days Cooldown', _daysCooldownController)),
],
),
const SizedBox(height: 12),
_buildTextField('Opens Cooldown', _opensCooldownController),
],
),
),
),
const SizedBox(height: 20),
// --- INTERACTIVE ACTIONS ---
const Text(
'Interactive Actions',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF64748B)),
),
const SizedBox(height: 10),
// Simulate App Open
ElevatedButton.icon(
icon: const Icon(Icons.add_circle_outline),
label: const Text('Simulate App Open (Increment)'),
onPressed: () async {
await SmartReviewPrompter.instance.incrementAppOpenCount();
await _refreshStats();
_showSnackBar('App Open count incremented!');
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2563EB), // Royal Blue
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
const SizedBox(height: 12),
// Try Showing Review (With Limit / Reprompt - NEW)
ElevatedButton.icon(
icon: const Icon(Icons.replay_outlined),
label: const Text('Try Showing with Limits (New Method)'),
onPressed: () async {
final int minOpens = int.tryParse(_minOpensController.text) ?? 3;
final int minDays = int.tryParse(_minDaysController.text) ?? 0;
final int maxPrompts = int.tryParse(_maxPromptsController.text) ?? 3;
final int cooldownDays = int.tryParse(_daysCooldownController.text) ?? 0;
final int cooldownOpens = int.tryParse(_opensCooldownController.text) ?? 2;
_showSnackBar('Evaluating multiple-times conditions...');
await SmartReviewPrompter.instance.tryShowingReviewWithLimit(
conditions: SmartReviewConditions(
minAppOpens: minOpens,
minDaysSinceInstall: minDays,
),
maxPromptCount: maxPrompts,
minDaysBetweenPrompts: cooldownDays,
minOpensBetweenPrompts: cooldownOpens,
);
await _refreshStats();
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2CAC5C), // Brand green
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
const SizedBox(height: 12),
// Try Showing Review (Legacy One-time - OLD)
OutlinedButton.icon(
icon: const Icon(Icons.looks_one),
label: const Text('Try Showing One-Time Only (Legacy)'),
onPressed: () async {
final int minOpens = int.tryParse(_minOpensController.text) ?? 3;
final int minDays = int.tryParse(_minDaysController.text) ?? 0;
_showSnackBar('Evaluating legacy one-time conditions...');
await SmartReviewPrompter.instance.tryShowingReview(
SmartReviewConditions(
minAppOpens: minOpens,
minDaysSinceInstall: minDays,
),
);
await _refreshStats();
},
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF0F172A),
side: const BorderSide(color: Color(0xFF64748B)),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
const SizedBox(height: 12),
// Mark user as Rated
ElevatedButton.icon(
icon: const Icon(Icons.star),
label: const Text('Mark User as Rated (Permanently Disable)'),
onPressed: () async {
await SmartReviewPrompter.instance.markUserAsRated();
await _refreshStats();
_showSnackBar('User marked as already rated. Prompts disabled!');
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF10B981), // Emerald Accent
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
const SizedBox(height: 12),
// Reset All Cache
ElevatedButton.icon(
icon: const Icon(Icons.delete_forever),
label: const Text('Reset Prompter Storage Cache'),
onPressed: () async {
await SmartReviewPrompter.instance.reset();
await _refreshStats();
_showSnackBar('All local prompter storage has been reset!', isError: true);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFEF4444), // Error Red
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
],
),
),
);
}
Widget _buildStatRow(String label, String value, Color valueColor) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF64748B)),
),
Text(
value,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: valueColor),
),
],
),
);
}
Widget _buildTextField(String label, TextEditingController controller) {
return TextField(
controller: controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(fontSize: 12, color: Color(0xFF64748B)),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
);
}
}