Created
August 1, 2025 16:14
-
-
Save mukhtharcm/a8082522c982a797f7939f87ce41f546 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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