First Commit
This commit is contained in:
66
lib/constants/theme.dart
Normal file
66
lib/constants/theme.dart
Normal 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
64
lib/firebase_options.dart
Normal 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
34
lib/main.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
156
lib/screens/admin_screen.dart
Normal file
156
lib/screens/admin_screen.dart
Normal 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: "حدث خطأ ما");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
245
lib/screens/dashboard_screen.dart
Normal file
245
lib/screens/dashboard_screen.dart
Normal 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;
|
||||
}
|
||||
}
|
136
lib/screens/login_screen.dart
Normal file
136
lib/screens/login_screen.dart
Normal 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: "حدث خطأ ما");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
244
lib/screens/report_details_screen.dart
Normal file
244
lib/screens/report_details_screen.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
54
lib/utils/file_classification.dart
Normal file
54
lib/utils/file_classification.dart
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
42
lib/widgets/app_toast.dart
Normal file
42
lib/widgets/app_toast.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
86
lib/widgets/custom_button.dart
Normal file
86
lib/widgets/custom_button.dart
Normal 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,
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
77
lib/widgets/custom_text_field.dart
Normal file
77
lib/widgets/custom_text_field.dart
Normal 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)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
127
lib/widgets/dashboard_tile.dart
Normal file
127
lib/widgets/dashboard_tile.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
94
lib/widgets/file_card.dart
Normal file
94
lib/widgets/file_card.dart
Normal 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,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
19
lib/widgets/small_spinner.dart
Normal file
19
lib/widgets/small_spinner.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user