commit cf9784f4687cd1ce94b618a459d7fae689a3b98d Author: Abdullah Salah Date: Thu Dec 26 09:10:35 2024 +0300 First Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..bf94537 --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "4cf269e36de2573851eaef3c763994f8f9be494d" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: android + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: ios + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: linux + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: macos + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: web + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: windows + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8c3eca --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# baligh_dashboard + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/assets/fonts/Cairo-Bold.ttf b/assets/fonts/Cairo-Bold.ttf new file mode 100644 index 0000000..098d2fb Binary files /dev/null and b/assets/fonts/Cairo-Bold.ttf differ diff --git a/assets/fonts/Cairo-Light.ttf b/assets/fonts/Cairo-Light.ttf new file mode 100644 index 0000000..3c40958 Binary files /dev/null and b/assets/fonts/Cairo-Light.ttf differ diff --git a/assets/fonts/Cairo-Medium.ttf b/assets/fonts/Cairo-Medium.ttf new file mode 100644 index 0000000..38d2834 Binary files /dev/null and b/assets/fonts/Cairo-Medium.ttf differ diff --git a/assets/fonts/Cairo-Regular.ttf b/assets/fonts/Cairo-Regular.ttf new file mode 100644 index 0000000..fb9e6cc Binary files /dev/null and b/assets/fonts/Cairo-Regular.ttf differ diff --git a/assets/fonts/Cairo-SemiBold.ttf b/assets/fonts/Cairo-SemiBold.ttf new file mode 100644 index 0000000..e2b82ba Binary files /dev/null and b/assets/fonts/Cairo-SemiBold.ttf differ diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..b72f85f --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"baligh-fbef7","configurations":{"web":"1:580708075572:web:72a4af92dc560d72702261"}}}}}} \ No newline at end of file diff --git a/lib/constants/theme.dart b/lib/constants/theme.dart new file mode 100644 index 0000000..784fb6a --- /dev/null +++ b/lib/constants/theme.dart @@ -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), + )), + )), + ); +} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..e11b3ff --- /dev/null +++ b/lib/firebase_options.dart @@ -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', + ); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..806a615 --- /dev/null +++ b/lib/main.dart @@ -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(), + ); + } +} + diff --git a/lib/screens/admin_screen.dart b/lib/screens/admin_screen.dart new file mode 100644 index 0000000..4236b92 --- /dev/null +++ b/lib/screens/admin_screen.dart @@ -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 createState() => _AdminScreenState(); +} + +class _AdminScreenState extends State { + TextEditingController usernameController = TextEditingController(); + TextEditingController passwordController = TextEditingController(); + + String selectedRole = "التحرش"; + List 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( + initialSelection: "التحرش", + label: const Text("نوع البلاغ"), + onSelected: (String? value) { + setState(() { + selectedRole = value ?? "التحرش"; + }); + }, + dropdownMenuEntries: availableRoles + .map>((value) { + return DropdownMenuEntry( + value: value, + label: value, + ); + }).toList(), + ), + ], + ), + const SizedBox( + height: 15, + ), + CustomButton( + label: "اضافة المشرف", + isElevated: true, + onPressed: handleCreateAdmin, + isLoading: isLoading, + ), + ], + ), + ), + ), + ), + ), + ); + } + + Future 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: "حدث خطأ ما"); + }); + } + } +} diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..b3c141e --- /dev/null +++ b/lib/screens/dashboard_screen.dart @@ -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 createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + bool isLoading = false; + String? error; + + List> 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 getInitialData() async { + try { + setState(() { + isLoading = true; + }); + + var temp = await getReportsWithUsers(); + + setState(() { + isLoading = false; + reports = temp; + }); + } catch (e, s) { + setState(() { + isLoading = false; + error = "حدث خطأ ما"; + }); + } + } + + Future>> 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> reportsWithUsers = []; + Set userRefs = {}; + + // Collect unique user references + for (var reportDoc in reportsSnapshot.docs) { + var reportData = reportDoc.data() as Map; + 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; + } +} diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..1eff7b0 --- /dev/null +++ b/lib/screens/login_screen.dart @@ -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 createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + 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 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; + + 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: "حدث خطأ ما"); + }); + } + } +} diff --git a/lib/screens/report_details_screen.dart b/lib/screens/report_details_screen.dart new file mode 100644 index 0000000..47da2b4 --- /dev/null +++ b/lib/screens/report_details_screen.dart @@ -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 createState() => _ReportDetailsScreenState(); +} + +class _ReportDetailsScreenState extends State { + String selectedStatus = "قيد الانتظار"; + List 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( + 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>((value) { + return DropdownMenuEntry( + 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, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/utils/file_classification.dart b/lib/utils/file_classification.dart new file mode 100644 index 0000000..ac5b8dd --- /dev/null +++ b/lib/utils/file_classification.dart @@ -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 + } + } + +} \ No newline at end of file diff --git a/lib/widgets/app_toast.dart b/lib/widgets/app_toast.dart new file mode 100644 index 0000000..a7cbf8f --- /dev/null +++ b/lib/widgets/app_toast.dart @@ -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, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/custom_button.dart b/lib/widgets/custom_button.dart new file mode 100644 index 0000000..b41baed --- /dev/null +++ b/lib/widgets/custom_button.dart @@ -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, + ) + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/custom_text_field.dart b/lib/widgets/custom_text_field.dart new file mode 100644 index 0000000..a247340 --- /dev/null +++ b/lib/widgets/custom_text_field.dart @@ -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 createState() => _CustomTextFieldState(); +} + +class _CustomTextFieldState extends State { + + @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) + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/dashboard_tile.dart b/lib/widgets/dashboard_tile.dart new file mode 100644 index 0000000..4029a0c --- /dev/null +++ b/lib/widgets/dashboard_tile.dart @@ -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, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/file_card.dart b/lib/widgets/file_card.dart new file mode 100644 index 0000000..230b19a --- /dev/null +++ b/lib/widgets/file_card.dart @@ -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, + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/small_spinner.dart b/lib/widgets/small_spinner.dart new file mode 100644 index 0000000..0b48ab4 --- /dev/null +++ b/lib/widgets/small_spinner.dart @@ -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, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..79c8d44 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,490 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "5534e701a2c505fed1f0799e652dd6ae23bd4d2c4cf797220e5ced5764a7c1c2" + url: "https://pub.dev" + source: hosted + version: "1.3.44" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + bot_toast: + dependency: "direct main" + description: + name: bot_toast + sha256: "6b93030a99a98335b8827ecd83021e92e885ffc61d261d3825ffdecdd17f3bdf" + url: "https://pub.dev" + source: hosted + version: "4.1.3" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: bdc7607e9169ee3ce736bbbe6a81c2a6cb15c41379346b74f77f8e641211a17f + url: "https://pub.dev" + source: hosted + version: "5.4.4" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "884fa34c6be2d9c7c1f4af86f90f36c0a3b3afef585a12b350a5d15368e7ec7a" + url: "https://pub.dev" + source: hosted + version: "6.4.3" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: "6e621bbcc999f32db0bc6bfcb18d9991617ec20f8d6bf51b6a1571f5c324fafd" + url: "https://pub.dev" + source: hosted + version: "4.3.2" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dio: + dependency: transitive + description: + name: dio + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: "017a127de686af2d2fbbd64afea97052d95f2a0f87d19d25b87e097407bf9c1e" + url: "https://pub.dev" + source: hosted + version: "0.2.14" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: d453acec0d958ba0e25d41a9901b32cb77d1535766903dea7a61b2788c304596 + url: "https://pub.dev" + source: hosted + version: "5.3.1" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "78966c2ef774f5bf2a8381a307222867e9ece3509110500f7a138c115926aa65" + url: "https://pub.dev" + source: hosted + version: "7.4.7" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "77ad3b252badedd3f08dfa21a4c7fe244be96c6da3a4067f253b13ea5d32424c" + url: "https://pub.dev" + source: hosted + version: "5.13.2" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "51dfe2fbf3a984787a2e7b8592f2f05c986bfedd6fdacea3f9e0a7beb334de96" + url: "https://pub.dev" + source: hosted + version: "3.6.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 + url: "https://pub.dev" + source: hosted + version: "5.3.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 + url: "https://pub.dev" + source: hosted + version: "2.18.1" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: "8a8a21f3a359129a1257e2e77ece1de9678f40e43876635b3d411388ee388729" + url: "https://pub.dev" + source: hosted + version: "12.3.2" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: "462621bbdb5ab496518aa0f4785cb6db87763d5f1063aa228e1f65562937af1d" + url: "https://pub.dev" + source: hosted + version: "5.1.31" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: "40c52d5585ce63659b4b698fee0d47412ce499392ae3edf69c8e6141c22daf9a" + url: "https://pub.dev" + source: hosted + version: "3.10.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f" + url: "https://pub.dev" + source: hosted + version: "10.7.0" + html: + dependency: "direct main" + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f7544c346a0742aee1450f9e5c0f5269d7c602b9c95fdbcd9fb8f5b1df13b1cc + url: "https://pub.dev" + source: hosted + version: "2.2.11" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.5.2 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..91f0015 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,108 @@ +name: baligh_dashboard +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.5.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + auto_size_text: ^3.0.0 + font_awesome_flutter: ^10.7.0 + firebase_core: ^3.6.0 + firebase_auth: ^5.3.1 + cloud_firestore: ^5.4.3 + bot_toast: ^4.1.3 + firebase_storage: ^12.3.2 + file_saver: ^0.2.14 + html: ^0.15.4 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + fonts: + - family: Cairo + fonts: + - asset: assets/fonts/Cairo-Light.ttf + weight: 300 + - family: Cairo + fonts: + - asset: assets/fonts/Cairo-Regular.ttf + weight: 400 + - family: Cairo + fonts: + - asset: assets/fonts/Cairo-Medium.ttf + weight: 500 + - family: Cairo + fonts: + - asset: assets/fonts/Cairo-SemiBold.ttf + weight: 600 + - family: Cairo + fonts: + - asset: assets/fonts/Cairo-Bold.ttf + weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..c74cf80 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:baligh_dashboard/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..a49aed8 --- /dev/null +++ b/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + baligh_dashboard + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..4f896f7 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "baligh_dashboard", + "short_name": "baligh_dashboard", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +}