Commit 0ec281ad authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add Gate Pass (QR code) and About Academy (WebView) screens

- Gate Pass: shows QR code per participant for electronic gate scanning
- About Academy: WebView loading the academy's website
- Both accessible from dashboard quick actions grid
- Added qr_flutter and webview_flutter dependencies
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent c145991a
......@@ -12,6 +12,9 @@ PODS:
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
......@@ -20,6 +23,7 @@ DEPENDENCIES:
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
EXTERNAL SOURCES:
Flutter:
......@@ -34,6 +38,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
......@@ -42,6 +48,7 @@ SPEC CHECKSUMS:
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
......
......@@ -100,6 +100,8 @@ class AppLocalizations {
'next': 'التالي',
'type_message': 'اكتب رسالة...',
'attendance': 'الحضور',
'gate_pass': 'بطاقة الدخول',
'about_academy': 'عن الأكاديمية',
},
'en': {
'app_name': 'El Captain',
......@@ -187,6 +189,8 @@ class AppLocalizations {
'next': 'Next',
'type_message': 'Type a message...',
'attendance': 'Attendance',
'gate_pass': 'Gate Pass',
'about_academy': 'About Academy',
},
};
......
......@@ -19,6 +19,8 @@ import '../../features/shop/shop_screen.dart';
import '../../features/messages/messages_screen.dart';
import '../../features/service_requests/service_requests_screen.dart';
import '../../features/registration/registration_screen.dart';
import '../../features/gate_pass/gate_pass_screen.dart';
import '../../features/about/about_academy_screen.dart';
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authStoreProvider);
......@@ -155,6 +157,16 @@ final routerProvider = Provider<GoRouter>((ref) {
pageBuilder: (context, state) =>
PageTransitions.slide(const ServiceRequestsScreen(), key: 'requests'),
),
GoRoute(
path: '/gate-pass',
pageBuilder: (context, state) =>
PageTransitions.slide(const GatePassScreen(), key: 'gate-pass'),
),
GoRoute(
path: '/about-academy',
pageBuilder: (context, state) =>
PageTransitions.slide(const AboutAcademyScreen(), key: 'about-academy'),
),
],
);
});
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../config/instance.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
class AboutAcademyScreen extends StatefulWidget {
const AboutAcademyScreen({super.key});
@override
State<AboutAcademyScreen> createState() => _AboutAcademyScreenState();
}
class _AboutAcademyScreenState extends State<AboutAcademyScreen> {
late final WebViewController _controller;
bool _isLoading = true;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (_) {
if (mounted) setState(() => _isLoading = false);
},
onWebResourceError: (_) {
if (mounted) setState(() => _isLoading = false);
},
),
)
..loadRequest(Uri.parse(InstanceConfig.baseUrl));
}
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l.t('about_academy')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() => _isLoading = true);
_controller.reload();
},
),
],
),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading)
Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: AppTheme.primaryColor),
const SizedBox(height: 16),
Text(
'جارٍ التحميل...',
style: TextStyle(color: Colors.grey.shade500),
),
],
),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../core/auth/auth_store.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
class GatePassScreen extends ConsumerWidget {
const GatePassScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final auth = ref.watch(authStoreProvider);
final participants = auth.participants;
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('gate_pass')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
body: participants.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.qr_code_2, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text(
'لا يوجد مشتركين',
style: TextStyle(fontSize: 16, color: Colors.grey.shade500),
),
],
),
)
: PageView.builder(
itemCount: participants.length,
itemBuilder: (ctx, i) {
final p = participants[i];
return _PassCard(participant: p, index: i);
},
),
);
}
}
class _PassCard extends StatelessWidget {
final Map<String, dynamic> participant;
final int index;
const _PassCard({required this.participant, required this.index});
@override
Widget build(BuildContext context) {
final uuid = participant['uuid'] ?? '';
final name = participant['name_ar'] ?? participant['name'] ?? '';
final status = participant['status'] ?? 'active';
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: AppTheme.primaryColor.withValues(alpha: 0.08),
blurRadius: 24,
offset: const Offset(0, 8),
),
],
),
child: Column(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.person, size: 32, color: AppTheme.primaryColor),
)
.animate()
.scale(
begin: const Offset(0.7, 0.7),
end: const Offset(1, 1),
duration: 500.ms,
curve: Curves.easeOutBack,
),
const SizedBox(height: 16),
Text(
name,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
).animate(delay: 100.ms).fadeIn(duration: 400.ms),
const SizedBox(height: 6),
_StatusBadge(status: status),
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
),
child: QrImageView(
data: 'ELCAPTAIN:PASS:$uuid',
version: QrVersions.auto,
size: 200,
eyeStyle: const QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Color(0xFF1e40af),
),
dataModuleStyle: const QrDataModuleStyle(
dataModuleShape: QrDataModuleShape.square,
color: Color(0xFF1a1a2e),
),
),
)
.animate(delay: 200.ms)
.fadeIn(duration: 600.ms)
.scale(
begin: const Offset(0.85, 0.85),
end: const Offset(1, 1),
curve: Curves.easeOutBack,
),
const SizedBox(height: 20),
Text(
'امسح الكود عند البوابة',
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
).animate(delay: 400.ms).fadeIn(duration: 400.ms),
],
),
)
.animate(delay: Duration(milliseconds: 100 * index))
.fadeIn(duration: 500.ms)
.slideY(begin: 0.05, end: 0),
],
),
);
}
}
class _StatusBadge extends StatelessWidget {
final String status;
const _StatusBadge({required this.status});
@override
Widget build(BuildContext context) {
final (color, label) = switch (status) {
'active' => (Colors.green, 'نشط'),
'frozen' => (Colors.blue, 'مجمد'),
'suspended' => (Colors.red, 'موقوف'),
_ => (Colors.grey, status),
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(label, style: TextStyle(fontSize: 12, color: color, fontWeight: FontWeight.w600)),
);
}
}
......@@ -473,9 +473,11 @@ class _QuickActions extends StatelessWidget {
@override
Widget build(BuildContext context) {
final actions = [
(Icons.qr_code_2, l.t('gate_pass'), '/gate-pass', Colors.indigo),
(Icons.event, l.t('events'), '/events', AppTheme.primaryColor),
(Icons.shopping_bag_rounded, l.t('shop'), '/shop', AppTheme.accentColor),
(Icons.mail_rounded, l.t('messages'), '/messages', Colors.teal),
(Icons.language, l.t('about_academy'), '/about-academy', Colors.teal),
(Icons.mail_rounded, l.t('messages'), '/messages', Colors.deepOrange),
(
Icons.description_rounded,
l.t('service_requests'),
......@@ -490,12 +492,12 @@ class _QuickActions extends StatelessWidget {
_SectionHeader(title: l.t('more')),
const SizedBox(height: 12),
GridView.count(
crossAxisCount: 4,
crossAxisCount: 3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.85,
childAspectRatio: 0.9,
children: actions.asMap().entries.map((entry) {
final i = entry.key;
final (icon, label, route, color) = entry.value;
......
......@@ -893,6 +893,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
qr_flutter:
dependency: "direct main"
description:
name: qr_flutter
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
record_use:
dependency: transitive
description:
......@@ -1338,6 +1354,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
sha256: d53e1ccf5516f25017e3c9d44c39034db352d20fa34fe200674270242c2c5111
url: "https://pub.dev"
source: hosted
version: "4.14.1"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: a97db7a44f8e71af2f3971c45550a08cce1fb60059c1b8e534251e6cfb753490
url: "https://pub.dev"
source: hosted
version: "4.13.0"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04"
url: "https://pub.dev"
source: hosted
version: "2.15.1"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: c879dd64b87c452aa84381b244d5469da57ba7e8cca6884c7b1e0d406372c12d
url: "https://pub.dev"
source: hosted
version: "3.26.0"
win32:
dependency: transitive
description:
......
......@@ -40,6 +40,10 @@ dependencies:
# firebase_core: ^3.8.1
# firebase_messaging: ^15.1.6
# QR & WebView
qr_flutter: ^4.1.0
webview_flutter: ^4.10.0
# Utilities
intl: ^0.20.2
url_launcher: ^6.3.1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment