Skip to content

Instantly share code, notes, and snippets.

@mukhtharcm
Created August 1, 2025 16:14
Show Gist options
  • Save mukhtharcm/a8082522c982a797f7939f87ce41f546 to your computer and use it in GitHub Desktop.
Save mukhtharcm/a8082522c982a797f7939f87ce41f546 to your computer and use it in GitHub Desktop.
// main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const ZakatCalculatorApp());
}
class ZakatCalculatorApp extends StatelessWidget {
const ZakatCalculatorApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Zakat Qualification',
theme: ThemeData(
primarySwatch: Colors.teal,
brightness: Brightness.light,
scaffoldBackgroundColor: Colors.grey[50],
appBarTheme: const AppBarTheme(
elevation: 1,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
),
filled: true,
fillColor: Colors.white,
),
cardTheme: CardThemeData( // Changed CardTheme to CardThemeData
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
home: const ZakatQualificationScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class ZakatQualificationScreen extends StatefulWidget {
const ZakatQualificationScreen({super.key});
@override
State<ZakatQualificationScreen> createState() => _ZakatQualificationScreenState();
}
class _ZakatQualificationScreenState extends State<ZakatQualificationScreen> {
// --- State Variables ---
int _currentStep = 0; // Controls which step of the wizard is shown
String _disqualificationMessage = '';
// Variables for the final calculation step
final _goldAmountController = TextEditingController();
bool _isPersonalJewelry = false;
bool _isForInvestment = false;
String _zakatResult = '';
// --- Logic Functions ---
/// Moves to the next step in the qualification wizard
void _nextStep() {
setState(() {
_currentStep++;
});
}
/// Handles when a user does not qualify for Zakat
void _disqualify(String message) {
setState(() {
_disqualificationMessage = message;
_currentStep = 100; // An arbitrary step number for the disqualification screen
});
}
/// Resets the entire wizard to the beginning
void _startOver() {
setState(() {
_currentStep = 0;
_disqualificationMessage = '';
_zakatResult = '';
_goldAmountController.clear();
_isPersonalJewelry = false;
_isForInvestment = false;
});
}
/// The final calculation logic
void _calculateZakat() {
const double nisabGold = 85.0; // ~20 Mithqal
const double zakatRate = 0.025; // 2.5%
final double? userAmount = double.tryParse(_goldAmountController.text);
if (userAmount == null || userAmount <= 0) {
setState(() {
_zakatResult = 'Please enter a valid amount of gold in grams.';
});
return;
}
// Jewelry exception check
if (_isPersonalJewelry && !_isForInvestment) {
setState(() {
_zakatResult = 'No Zakat is due on permissible jewelry intended for personal use.';
});
return;
}
// Nisab check
if (userAmount < nisabGold) {
setState(() {
_zakatResult = 'No Zakat is due as your holding of ${userAmount.toStringAsFixed(2)}g is below the Nisab of 85g.';
});
return;
}
// Final Calculation
final double zakatDue = userAmount * zakatRate;
setState(() {
_zakatResult = '''
Zakat is Obligatory.
Your Gold Holdings: ${userAmount.toStringAsFixed(2)}g
Nisab (Threshold): ${nisabGold.toStringAsFixed(2)}g
Zakat to be Paid: ${zakatDue.toStringAsFixed(3)}g of gold.
(or its equivalent cash value)
''';
});
}
// --- UI Building Functions ---
/// Builds a standard UI for a qualification question
Widget _buildQualificationStep({
required String question,
required String info,
required String disqualificationMessage,
}) {
return Center(
child: Card(
margin: const EdgeInsets.all(16.0),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
question,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Text(info, textAlign: TextAlign.center, style: TextStyle(color: Colors.grey[600])),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => _disqualify(disqualificationMessage),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red[700]),
child: const Text('No'),
),
ElevatedButton(
onPressed: _nextStep,
child: const Text('Yes'),
),
],
),
],
),
),
),
);
}
/// Builds the UI for the final step: entering the amount and calculating
Widget _buildCalculatorStep() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text("Congratulations, you've met the initial conditions for Zakat!", textAlign: TextAlign.center, style: TextStyle(color: Colors.green[800], fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Enter Gold Amount', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const SizedBox(height: 16),
TextFormField(
controller: _goldAmountController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(labelText: 'Amount in grams (جرام)', prefixIcon: Icon(Icons.balance)),
),
],
),
),
),
const SizedBox(height: 16),
Card(
child: Column(children: [
SwitchListTile(
title: const Text('Is this for personal use as jewelry?'),
value: _isPersonalJewelry,
onChanged: (bool value) {
setState(() {
_isPersonalJewelry = value;
if (!value) _isForInvestment = false;
});
},
),
if (_isPersonalJewelry)
SwitchListTile(
title: const Text('Do you intend to save it as an asset?'),
value: _isForInvestment,
onChanged: (bool value) => setState(() => _isForInvestment = value),
),
]),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _calculateZakat,
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 16), textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
child: const Text('Calculate Zakat (احسب الزكاة)'),
),
const SizedBox(height: 24),
if (_zakatResult.isNotEmpty)
Card(
color: Theme.of(context).primaryColor.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(_zakatResult, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1.5)),
),
),
if (_zakatResult.isNotEmpty) ...[const SizedBox(height: 20), TextButton(onPressed: _startOver, child: const Text("Start Over"))]
],
),
),
);
}
/// Builds the UI for a user who is disqualified
Widget _buildDisqualificationScreen() {
return Center(
child: Card(
color: Colors.red[50],
margin: const EdgeInsets.all(16.0),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Zakat Not Obligatory", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.red[800])),
const SizedBox(height: 16),
Text(_disqualificationMessage, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16)),
const SizedBox(height: 24),
ElevatedButton(onPressed: _startOver, child: const Text("Start Over"))
],
),
),
),
);
}
/// The main build method that routes to the correct step
@override
Widget build(BuildContext context) {
Widget currentWidget;
switch (_currentStep) {
case 0: // Welcome Screen
currentWidget = Center(child: ElevatedButton(onPressed: _nextStep, style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20)), child: const Text("Start Zakat Qualification", style: TextStyle(fontSize: 16))));
break;
case 1:
currentWidget = _buildQualificationStep(question: "Are you a Muslim?", info: "Zakat is a primary obligation for Muslims.", disqualificationMessage: "Zakat, as a pillar of Islam, is obligatory upon Muslims.");
break;
case 2:
currentWidget = _buildQualificationStep(question: "Are you a free person?", info: "The obligation is specified for a free person.", disqualificationMessage: "The specific conditions of Zakat discussed here apply to free individuals.");
break;
case 3:
currentWidget = _buildQualificationStep(question: "Is this wealth your personal property?", info: "i.e., not part of a public trust or general waqf.", disqualificationMessage: "Zakat on a principal amount is not required for wealth held in a general trust (e.g., for a mosque).");
break;
case 4:
currentWidget = _buildQualificationStep(question: "Has a full Hijri (lunar) year passed on this wealth?", info: "This is a required condition for Zakat on currency and trade goods.", disqualificationMessage: "A full lunar year must pass over the wealth (while it is above Nisab) for Zakat to be due.");
break;
case 5: // The final calculator
currentWidget = _buildCalculatorStep();
break;
default: // Disqualification screen
currentWidget = _buildDisqualificationScreen();
}
return Scaffold(
appBar: AppBar(title: const Text('Zakat Qualification')),
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: currentWidget,
)
);
}
@override
void dispose() {
_goldAmountController.dispose();
super.dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment