First Commit

This commit is contained in:
Abdullah Salah
2024-12-26 09:10:35 +03:00
commit cf9784f468
34 changed files with 2278 additions and 0 deletions

66
lib/constants/theme.dart Normal file
View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class MyCustomTheme {
static Color primaryColor = const Color.fromARGB(255, 42, 61, 171);
static Color cardColor = const Color(0xFFECECEC);
static Color textColor = const Color(0xFF181818);
static Color bgColor = const Color(0xFFF8FAFC);
static Color dividerColor = const Color(0xFF4C4C4C);
static ThemeData theme = ThemeData(
primaryColor: primaryColor,
useMaterial3: true,
cardColor: cardColor,
scaffoldBackgroundColor: bgColor,
dividerColor: dividerColor,
textTheme: TextTheme(
headlineLarge: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
fontFamily: "Cairo",
color: textColor,
),
titleLarge: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
fontFamily: "Cairo",
color: textColor,
),
bodyLarge: TextStyle(
fontSize: 18,
fontWeight: FontWeight.normal,
fontFamily: "Cairo",
color: textColor,
),
bodyMedium: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: "Cairo",
color: textColor,
),
bodySmall: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
fontFamily: "Cairo",
color: textColor,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
elevation: const WidgetStatePropertyAll(0),
backgroundColor: WidgetStatePropertyAll(primaryColor),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
)),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
elevation: const WidgetStatePropertyAll(0),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
)),
)),
);
}

64
lib/firebase_options.dart Normal file
View File

@ -0,0 +1,64 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for android - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.iOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for ios - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyBwuk1s6fLiPK0DjEpjYK-s9aZby8z-FiM',
appId: '1:580708075572:web:72a4af92dc560d72702261',
messagingSenderId: '580708075572',
projectId: 'baligh-fbef7',
authDomain: 'baligh-fbef7.firebaseapp.com',
storageBucket: 'baligh-fbef7.appspot.com',
measurementId: 'G-KQX9WQ9Y8M',
);
}

34
lib/main.dart Normal file
View File

@ -0,0 +1,34 @@
import 'package:baligh_dashboard/constants/theme.dart';
import 'package:baligh_dashboard/firebase_options.dart';
import 'package:baligh_dashboard/screens/login_screen.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'بلغ',
debugShowCheckedModeBanner: false,
theme: MyCustomTheme.theme,
locale: const Locale("ar"),
builder: BotToastInit(),
home: const LoginScreen(),
);
}
}

View File

@ -0,0 +1,156 @@
import 'package:baligh_dashboard/widgets/app_toast.dart';
import 'package:baligh_dashboard/widgets/custom_button.dart';
import 'package:baligh_dashboard/widgets/custom_text_field.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class AdminScreen extends StatefulWidget {
const AdminScreen({super.key});
@override
State<AdminScreen> createState() => _AdminScreenState();
}
class _AdminScreenState extends State<AdminScreen> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
String selectedRole = "التحرش";
List<String> availableRoles = [
"التحرش",
"التعنيف",
"الابتزاز",
"المخدرات",
"الرشوة",
];
bool isLoading = false;
@override
void dispose() {
usernameController.dispose();
passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 500,
minWidth: 100,
),
child: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(15),
children: [
Text(
"اضافى مشرف",
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(
height: 50,
),
CustomTextField(
controller: usernameController,
labelText: "اسم المشرف",
inputType: TextInputType.text),
const SizedBox(
height: 10,
),
CustomTextField(
controller: passwordController,
labelText: "كلمة المرور",
inputType: TextInputType.text,
isPassword: true,
),
const SizedBox(
height: 10,
),
Row(
children: [
DropdownMenu<String>(
initialSelection: "التحرش",
label: const Text("نوع البلاغ"),
onSelected: (String? value) {
setState(() {
selectedRole = value ?? "التحرش";
});
},
dropdownMenuEntries: availableRoles
.map<DropdownMenuEntry<String>>((value) {
return DropdownMenuEntry<String>(
value: value,
label: value,
);
}).toList(),
),
],
),
const SizedBox(
height: 15,
),
CustomButton(
label: "اضافة المشرف",
isElevated: true,
onPressed: handleCreateAdmin,
isLoading: isLoading,
),
],
),
),
),
),
),
);
}
Future<void> handleCreateAdmin() async {
if (passwordController.text.trim().isEmpty ||
usernameController.text.trim().isEmpty) {
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "يجب ملئ جميع الحقول");
});
return;
}
setState(() {
isLoading = true;
});
try {
await FirebaseFirestore.instance.collection("admins").add({
"role": selectedRole,
"username": usernameController.text.trim(),
"password": passwordController.text.trim(),
"createdAt": FieldValue.serverTimestamp(),
});
setState(() {
isLoading = false;
usernameController.clear();
passwordController.clear();
});
// if (!mounted) return;
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "تم اضافة مشرف");
});
} catch (e) {
print(e);
setState(() {
isLoading = false;
});
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "حدث خطأ ما");
});
}
}
}

View File

@ -0,0 +1,245 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:baligh_dashboard/screens/admin_screen.dart';
import 'package:baligh_dashboard/widgets/custom_button.dart';
import 'package:baligh_dashboard/widgets/dashboard_tile.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key, required this.adminRole});
final String adminRole;
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
bool isLoading = false;
String? error;
List<Map<String, dynamic>> reports = [];
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
getInitialData();
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 50,
),
AutoSizeText(
"البلاغات",
style: Theme.of(context).textTheme.titleLarge,
minFontSize: 12,
),
if (isLoading) ...[
const SizedBox(
height: 50,
),
Text("جار التحميل",
style: Theme.of(context).textTheme.bodyLarge),
] else if (error != null) ...[
const SizedBox(
height: 50,
),
Text(error!, style: Theme.of(context).textTheme.bodyLarge),
] else ...[
if (widget.adminRole == "admin") ...[
const SizedBox(
height: 10,
),
SizedBox(
width: 200,
child: CustomButton(
label: "اضافة مشرف",
isElevated: true,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AdminScreen()));
},
),
),
],
const SizedBox(
height: 30,
),
Row(
children: [
Expanded(
child: AutoSizeText(
"اسم صاحب البلاغ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"عنوان صاحب البلاغ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"المدرسة",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"نوع البلاغ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"الحالة",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
],
),
const SizedBox(
height: 10,
),
Expanded(
child: ListView.separated(
shrinkWrap: true,
itemCount: reports.length,
separatorBuilder: (context, index) => const SizedBox(
height: 10,
),
itemBuilder: (context, index) => DashboardTile(
id: reports[index]["id"] ?? "",
name: reports[index]["name"] ?? "",
age: reports[index]["age"] ?? "",
phone: reports[index]["phone"] ?? "",
address: reports[index]["address"] ?? "",
school: reports[index]["schoolName"] ?? "",
nationalIdNumber:
reports[index]["nationalIdNumber"] ?? "",
description: reports[index]["description"] ?? "",
attachments: reports[index]["attachments"] ?? "",
type: reports[index]["type"] ?? "",
createdAt: reports[index]["createdAt"] ?? "",
status: reports[index]["status"] ?? ""),
),
)
]
],
),
),
),
),
);
}
Future<void> getInitialData() async {
try {
setState(() {
isLoading = true;
});
var temp = await getReportsWithUsers();
setState(() {
isLoading = false;
reports = temp;
});
} catch (e, s) {
setState(() {
isLoading = false;
error = "حدث خطأ ما";
});
}
}
Future<List<Map<String, dynamic>>> getReportsWithUsers() async {
QuerySnapshot reportsSnapshot;
if (widget.adminRole == "admin") {
reportsSnapshot = await FirebaseFirestore.instance
.collection('reports')
.orderBy('createdAt', descending: true)
.get();
} else {
reportsSnapshot = await FirebaseFirestore.instance
.collection('reports')
.where('type', isEqualTo: widget.adminRole)
.orderBy('createdAt', descending: true)
.get();
}
List<Map<String, dynamic>> reportsWithUsers = [];
Set<DocumentReference> userRefs = {};
// Collect unique user references
for (var reportDoc in reportsSnapshot.docs) {
var reportData = reportDoc.data() as Map<String, dynamic>;
if (reportData['userRef'] == null) continue;
userRefs.add(reportData['userRef'] as DocumentReference);
reportsWithUsers.add({
'id': reportDoc.id,
'type': reportData["type"],
'description': reportData["description"],
'attachments': reportData["attachments"],
'status': reportData["status"],
'createdAt': reportData["createdAt"],
'userId': reportData["userId"],
});
}
// Fetch all user data in one go
var userDocs = await Future.wait(userRefs.map((ref) => ref.get()));
for (var el in userDocs) {
int index = reportsWithUsers.indexWhere((r) => r["userId"] == el["uid"]);
reportsWithUsers[index]["name"] = el["name"];
reportsWithUsers[index]["phone"] = el["phone"];
reportsWithUsers[index]["age"] = el["age"];
reportsWithUsers[index]["schoolName"] = el["schoolName"];
reportsWithUsers[index]["stage"] = el["stage"];
reportsWithUsers[index]["address"] = el["address"];
reportsWithUsers[index]["nationalIdNumber"] = el["nationalIdNumber"];
}
return reportsWithUsers;
}
}

View File

@ -0,0 +1,136 @@
import 'package:baligh_dashboard/screens/dashboard_screen.dart';
import 'package:baligh_dashboard/widgets/app_toast.dart';
import 'package:baligh_dashboard/widgets/custom_button.dart';
import 'package:baligh_dashboard/widgets/custom_text_field.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool isLoading = false;
@override
void dispose() {
usernameController.dispose();
passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 500,
minWidth: 100,
),
child: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(15),
children: [
Text(
"تسجيل الدخول",
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(
height: 50,
),
CustomTextField(
controller: usernameController,
labelText: "اسم المشرف",
inputType: TextInputType.text),
const SizedBox(
height: 10,
),
CustomTextField(
controller: passwordController,
labelText: "كلمة المرور",
inputType: TextInputType.text,
isPassword: true,
),
const SizedBox(
height: 15,
),
CustomButton(
label: "تسجيل الدخول",
isElevated: true,
onPressed: handleLogIn,
isLoading: isLoading,
),
],
),
),
),
),
),
);
}
Future<void> handleLogIn() async {
if (usernameController.text.trim().isEmpty ||
passwordController.text.trim().isEmpty) {
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "يجب ملئ جميع الحقول");
});
return;
}
setState(() {
isLoading = true;
});
try {
final QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection('admins')
.where('username', isEqualTo: usernameController.text.trim())
.where('password', isEqualTo: passwordController.text.trim())
.get();
if (snapshot.docs.isEmpty) {
setState(() {
isLoading = false;
});
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "اسم المشرف او كلمة المرور خطأ");
});
return;
}
var admin = snapshot.docs.first.data() as Map<String, dynamic>;
setState(() {
isLoading = false;
});
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DashboardScreen(adminRole: admin["role"])),
);
} catch (e) {
print(e);
setState(() {
isLoading = false;
});
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "حدث خطأ ما");
});
}
}
}

View File

@ -0,0 +1,244 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:baligh_dashboard/utils/file_classification.dart';
import 'package:baligh_dashboard/widgets/app_toast.dart';
import 'package:baligh_dashboard/widgets/dashboard_tile.dart';
import 'package:baligh_dashboard/widgets/file_card.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class ReportDetailsScreen extends StatefulWidget {
const ReportDetailsScreen({
super.key,
required this.id,
required this.name,
required this.address,
required this.school,
required this.type,
required this.status,
required this.age,
required this.attachments,
required this.createdAt,
required this.description,
required this.nationalIdNumber,
required this.phone,
});
final String id;
final String name;
final String address;
final String school;
final String type;
final String status;
final Timestamp createdAt;
final int age;
final String phone;
final int nationalIdNumber;
final String description;
final List attachments;
@override
State<ReportDetailsScreen> createState() => _ReportDetailsScreenState();
}
class _ReportDetailsScreenState extends State<ReportDetailsScreen> {
String selectedStatus = "قيد الانتظار";
List<String> availableStatus = [
"قيد الانتظار",
"قيد المعالجة",
"مكتمل",
];
@override
void initState() {
setState(() {
selectedStatus = widget.status;
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: SafeArea(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 15),
children: [
const SizedBox(
height: 50,
),
AutoSizeText(
"تفاصيل البلاغ",
style: Theme.of(context).textTheme.titleLarge,
minFontSize: 12,
),
const SizedBox(
height: 30,
),
Wrap(
children: [
Text(
"حالة البلاغ:",
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(
width: 5,
),
DropdownMenu<String>(
initialSelection: "قيد الانتظار",
label: const Text("الحالة"),
onSelected: (String? value) async {
var old = selectedStatus;
setState(() {
selectedStatus = value ?? "قيد الانتظار";
});
try {
await FirebaseFirestore.instance
.collection("reports")
.doc(widget.id)
.update({
'status': selectedStatus,
});
} catch (e) {
setState(() {
selectedStatus = old;
});
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "حدث خطأ ما");
});
}
},
dropdownMenuEntries:
availableStatus.map<DropdownMenuEntry<String>>((value) {
return DropdownMenuEntry<String>(
value: value,
label: value,
);
}).toList(),
),
],
),
const SizedBox(
height: 15,
),
Row(
children: [
Expanded(
child: AutoSizeText(
"اسم صاحب البلاغ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"عنوان صاحب البلاغ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"المدرسة",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"نوع البلاغ",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
"الحالة",
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
],
),
const SizedBox(
height: 10,
),
DashboardTile(
id: widget.id,
name: widget.name,
age: widget.age,
phone: widget.phone,
address: widget.address,
school: widget.school,
nationalIdNumber: widget.nationalIdNumber,
description: widget.description,
attachments: widget.attachments,
type: widget.type,
createdAt: widget.createdAt,
status: widget.status,
),
const SizedBox(
height: 20,
),
AutoSizeText(
"وصف البلاغ",
style: Theme.of(context).textTheme.bodyLarge,
minFontSize: 12,
),
const SizedBox(
height: 10,
),
Text(
widget.description,
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(
height: 20,
),
AutoSizeText(
"الملفات المرفقة",
style: Theme.of(context).textTheme.bodyLarge,
minFontSize: 12,
),
const SizedBox(
height: 10,
),
Wrap(
children: widget.attachments
.map(
(el) => FileCard(
type: FileClassification.getFileClass(el),
filename: FileClassification.getFileName(el) ?? "",
fileUrl: el,),
)
.toList(),
),
const SizedBox(
height: 30,
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,54 @@
import 'package:file_saver/file_saver.dart';
class FileClassification {
static String? getFileName(String url) {
Uri uri = Uri.parse(url);
return uri.pathSegments.isNotEmpty ? uri.pathSegments.last : null;
}
static String getFileClass(String url) {
String? fileName = getFileName(url);
if (fileName == null) return 'unknown';
// Get the file extension
String extension = fileName.split('.').last.toLowerCase();
// Determine the class based on the file extension
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].contains(extension)) {
return 'image';
} else if (['mp3', 'wav', 'ogg', 'aac'].contains(extension)) {
return 'audio';
} else if (['mp4', 'mkv', 'webm', 'avi', 'mov'].contains(extension)) {
return 'video';
} else {
return 'unknown';
}
}
static MimeType getMimeType(String filename) {
String extension = filename.split('.').last.toLowerCase();
switch (extension.toLowerCase()) {
case 'jpg':
case 'jpeg':
return MimeType.jpeg;
case 'png':
return MimeType.png;
case 'gif':
return MimeType.gif;
case 'bmp':
return MimeType.bmp;
case 'mp3':
return MimeType.mp3;
case 'aac':
return MimeType.aac;
case 'mp4':
return MimeType.mp4Video;
case 'avi':
return MimeType.avi;
default:
return MimeType.other; // Fallback for unknown types
}
}
}

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
class AppToast extends StatelessWidget {
const AppToast({
super.key,
required this.text,
this.bottomPadding,
this.color,
this.textStyle,
});
final String text;
final double? bottomPadding;
final Color? color;
final TextStyle? textStyle;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: bottomPadding ?? 100),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: color ?? Theme.of(context).scaffoldBackgroundColor,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.4),
offset: const Offset(2, 2),
blurRadius: 5,
spreadRadius: 1
)
]
),
child: Text(
text,
style: textStyle ?? Theme.of(context).textTheme.bodyMedium,
),
),
);
}
}

View File

@ -0,0 +1,86 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:baligh_dashboard/constants/theme.dart';
import 'package:baligh_dashboard/widgets/small_spinner.dart';
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
const CustomButton({
super.key,
required this.onPressed,
required this.label,
required this.isElevated,
this.isLoading = false,
this.textStyle,
this.color,
});
final void Function() onPressed;
final String label;
final bool isLoading;
final bool isElevated;
final TextStyle? textStyle;
final Color? color;
@override
Widget build(BuildContext context) {
return isElevated
? ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: Theme.of(context).elevatedButtonTheme.style?.copyWith(
backgroundColor: WidgetStatePropertyAll(color ?? MyCustomTheme.primaryColor)
),
child: isLoading
? const SmallSpinner()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Row(
children: [
Expanded(
child: AutoSizeText(
label,
minFontSize: 8,
maxFontSize: 14,
maxLines: 1,
textAlign: TextAlign.center,
style: textStyle ?? Theme.of(context).textTheme.bodyLarge?.copyWith(
color: MyCustomTheme.bgColor,
)
),
),
],
),
),
)
: OutlinedButton(
onPressed: isLoading ? null : onPressed,
style: Theme.of(context).elevatedButtonTheme.style?.copyWith(
backgroundColor: WidgetStatePropertyAll(Theme.of(context).scaffoldBackgroundColor),
side: WidgetStatePropertyAll(BorderSide(
width: 1.5,
color: color ?? MyCustomTheme.primaryColor,
))
),
child: isLoading
? SmallSpinner(color: MyCustomTheme.primaryColor,)
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Row(
children: [
Expanded(
child: AutoSizeText(
label,
minFontSize: 8,
maxFontSize: 14,
maxLines: 1,
textAlign: TextAlign.center,
style: textStyle ?? Theme.of(context).textTheme.bodyLarge?.copyWith(
color: color ?? MyCustomTheme.primaryColor,
)
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
class CustomTextField extends StatefulWidget {
const CustomTextField({
super.key,
required this.controller,
required this.labelText,
required this.inputType,
this.errorText,
this.isPassword = false,
this.maxLines = 1,
this.focusNode,
});
final TextEditingController controller;
final String labelText;
final TextInputType inputType;
final String? errorText;
final bool isPassword;
final int maxLines;
final FocusNode? focusNode;
@override
State<CustomTextField> createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
focusNode: widget.focusNode,
keyboardType: widget.inputType,
obscureText: widget.isPassword,
enableSuggestions: !widget.isPassword,
autocorrect: false,
maxLines: widget.maxLines,
textDirection: TextDirection.rtl,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
labelText: widget.labelText,
alignLabelWithHint: true,
labelStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).dividerColor),
floatingLabelStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).dividerColor),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: Theme.of(context).cardColor, width: 1.5)
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: Theme.of(context).dividerColor, width: 1)
),
filled: true,
fillColor: Theme.of(context).cardColor,
errorText: widget.errorText,
errorStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.red
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.red, width: 1.5)
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Colors.red, width: 1.5)
),
),
);
}
}

View File

@ -0,0 +1,127 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:baligh_dashboard/screens/report_details_screen.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class DashboardTile extends StatelessWidget {
const DashboardTile({
super.key,
required this.id,
required this.name,
required this.address,
required this.school,
required this.type,
required this.status,
required this.age,
required this.attachments,
required this.createdAt,
required this.description,
required this.nationalIdNumber,
required this.phone,
});
final String id;
final String name;
final String address;
final String school;
final String type;
final String status;
final Timestamp createdAt;
final int age;
final String phone;
final int nationalIdNumber;
final String description;
final List attachments;
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: const BorderRadius.all(Radius.circular(12)),
hoverColor: Colors.transparent,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ReportDetailsScreen(
id: id,
name: name,
age: age,
phone: phone,
address: address,
school: school,
nationalIdNumber: nationalIdNumber,
description: description,
attachments: attachments,
type: type,
createdAt: createdAt,
status: status,
)));
},
child: Container(
width: double.infinity,
clipBehavior: Clip.hardEdge,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Row(
children: [
Expanded(
child: AutoSizeText(
name,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
address,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
school,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
type,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
status,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: 3,
minFontSize: 8,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,94 @@
import 'dart:html' as html;
import 'package:auto_size_text/auto_size_text.dart';
import 'package:baligh_dashboard/widgets/app_toast.dart';
import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class FileCard extends StatelessWidget {
const FileCard(
{super.key,
required this.type,
required this.filename,
required this.fileUrl});
final String type;
final String filename;
final String fileUrl;
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: const BorderRadius.all(Radius.circular(12)),
hoverColor: Colors.transparent,
onTap: () async {
try {
html.AnchorElement(href: fileUrl)
..setAttribute('target', '_blank')
..setAttribute('download', '$filename.pdf') // Specify the filename
..click();
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "تم التحميل");
});
} catch (e) {
print(e);
BotToast.showCustomText(toastBuilder: (_) {
return const AppToast(text: "حدث خطأ ما في التحميل");
});
}
},
child: Container(
width: 180,
clipBehavior: Clip.hardEdge,
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: type == "image"
? Colors.red.withOpacity(0.2)
: type == "video"
? Colors.blue.withOpacity(0.2)
: Colors.grey.withOpacity(0.2),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Center(
child: Icon(
type == "image"
? FontAwesomeIcons.image
: type == "video"
? FontAwesomeIcons.video
: FontAwesomeIcons.microphone,
color: type == "image"
? Colors.red
: type == "video"
? Colors.blue
: Colors.grey,
size: 18,
),
),
),
const SizedBox(
width: 5,
),
Expanded(
child: AutoSizeText(
filename,
style: Theme.of(context).textTheme.bodySmall,
minFontSize: 6,
),
)
],
),
),
);
}
}

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class SmallSpinner extends StatelessWidget {
final Color? color;
const SmallSpinner({super.key, this.color});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
color: color ?? Colors.white,
),
);
}
}