diff --git a/assets/images/backend.png b/assets/images/backend.png new file mode 100644 index 0000000..2ab04c2 Binary files /dev/null and b/assets/images/backend.png differ diff --git a/assets/images/cybersecurity.png b/assets/images/cybersecurity.png new file mode 100644 index 0000000..53776bc Binary files /dev/null and b/assets/images/cybersecurity.png differ diff --git a/assets/images/dataEngineering.png b/assets/images/dataEngineering.png new file mode 100644 index 0000000..541bf6e Binary files /dev/null and b/assets/images/dataEngineering.png differ diff --git a/assets/images/datascience.png b/assets/images/datascience.png new file mode 100644 index 0000000..580e56b Binary files /dev/null and b/assets/images/datascience.png differ diff --git a/assets/images/embeded.png b/assets/images/embeded.png new file mode 100644 index 0000000..1518af0 Binary files /dev/null and b/assets/images/embeded.png differ diff --git a/assets/images/flutter.png b/assets/images/flutter.png new file mode 100644 index 0000000..07c0e00 Binary files /dev/null and b/assets/images/flutter.png differ diff --git a/assets/images/frontend.png b/assets/images/frontend.png new file mode 100644 index 0000000..fafd968 Binary files /dev/null and b/assets/images/frontend.png differ diff --git a/assets/images/ui.png b/assets/images/ui.png new file mode 100644 index 0000000..20ea142 Binary files /dev/null and b/assets/images/ui.png differ diff --git a/lib/core/routing/app_router.dart b/lib/core/routing/app_router.dart index be5165b..c551fb1 100644 --- a/lib/core/routing/app_router.dart +++ b/lib/core/routing/app_router.dart @@ -1,6 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:intermediate_final_project/core/routing/routes.dart'; +import 'package:intermediate_final_project/features/login/presentation/views/track_view.dart'; import 'package:intermediate_final_project/features/nav_bar/presentation/manager/nav_bar_cubit/nav_bar_cubit.dart'; import 'package:intermediate_final_project/features/nav_bar/presentation/widgets/bottom_nav_bar.dart'; @@ -9,7 +10,7 @@ abstract class AppRouter { static void initRouter() { router = GoRouter( - initialLocation: Routes.navbar, + initialLocation: Routes.track, routes: [ GoRoute( path: Routes.navbar, @@ -18,6 +19,10 @@ abstract class AppRouter { child: CustomBottomNavBar(), ), ), + GoRoute( + path: Routes.track, + builder: (context, state) => const TrackLevelScreen(), + ), ], ); } diff --git a/lib/core/routing/routes.dart b/lib/core/routing/routes.dart index 67d0521..f694fd6 100644 --- a/lib/core/routing/routes.dart +++ b/lib/core/routing/routes.dart @@ -1,3 +1,4 @@ class Routes { static const String navbar = '/navbar'; + static const String track='/track'; } diff --git a/lib/features/login/presentation/views/track_view.dart b/lib/features/login/presentation/views/track_view.dart new file mode 100644 index 0000000..c16e53d --- /dev/null +++ b/lib/features/login/presentation/views/track_view.dart @@ -0,0 +1,488 @@ +import 'package:flutter/material.dart'; + +class TrackLevelScreen extends StatefulWidget { + const TrackLevelScreen({super.key}); + + @override + State createState() => _TrackLevelScreenState(); +} + +class _TrackLevelScreenState extends State { + int selectedTrackIndex = 1; + String selectedLevel = 'Beginner'; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + const orange = Color(0xFFF6A100); + const bg = Color(0xFFF8F8F8); + + final tracks = <_TrackItem>[ + _TrackItem('UI/UX', "assets/images/ui.png"), + _TrackItem('Frontend', "assets/images/frontend.png"), + _TrackItem('Flutter', "assets/images/flutter.png"), + _TrackItem('Backend', "assets/images/backend.png"), + _TrackItem('Data Science', "assets/images/datascience.png"), + _TrackItem('Data Engineering', "assets/images/dataEngineering.png"), + _TrackItem('Embedded Systems', "assets/images/embeded.png"), + _TrackItem('Cyber Security', "assets/images/cybersecurity.png"), + ]; + + return Scaffold( + backgroundColor: bg, + body: Stack( + children: [ + const Positioned( + top: 0, + left: 0, + right: 0, + height: 100, + child: CurveBackground(), + ), + + // Content + SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 60), + // Title + Text( + 'Which track are you interested in?', + textAlign: TextAlign.center, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: const Color(0xFF333333), + fontSize: 18, + ), + ), + + const SizedBox(height: 20), + + // Grid of track cards + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: tracks.length, + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 1.1, + ), + itemBuilder: (context, index) { + final item = tracks[index]; + final isSelected = index == selectedTrackIndex; + return _TrackCard( + title: item.title, + image: item.image, + isSelected: isSelected, + onTap: () => + setState(() => selectedTrackIndex = index), + ); + }, + ), + + const SizedBox(height: 24), + + // Subtitle + Align( + alignment: Alignment.centerLeft, + child: Text( + 'What is your current level in Programming Basics', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: const Color(0xFF333333), + fontSize: 16, + ), + ), + ), + + const SizedBox(height: 16), + + // Radio options horizontally + Wrap( + spacing: 16, + runSpacing: 8, + children: [ + _LevelOption( + label: 'Beginner', + value: 'Beginner', + groupValue: selectedLevel, + onChanged: (v) => setState(() => selectedLevel = v), + ), + _LevelOption( + label: 'Intermediate', + value: 'Intermediate', + groupValue: selectedLevel, + onChanged: (v) => setState(() => selectedLevel = v), + ), + _LevelOption( + label: 'Advanced', + value: 'Advanced', + groupValue: selectedLevel, + onChanged: (v) => setState(() => selectedLevel = v), + ), + ], + ), + + const SizedBox(height: 24), + + // Bottom Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: orange, + foregroundColor: Colors.white, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + onPressed: () {}, + child: const Text( + 'Login', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + ), + ), + + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +// class CurvedPainter extends CustomPainter { +// @override +// void paint(Canvas canvas, Size size) { +// +// final paint = Paint() +// ..color = const Color(0xFFFBF3E3) +// ..style = PaintingStyle.fill; +// +// final path = Path(); +// +// +// path.moveTo(0, size.height * 0.2); +// +// +// path.cubicTo( +// size.width * 0.025, size.height * 0.015, +// size.width * 0.075, size.height * 0.55, +// size.width, size.height * -0.045, +// ); +// +// path.lineTo(size.width, 0); +// path.lineTo(0, 0); +// path.close(); +// +// canvas.drawPath(path, paint); +// } +// +// @override +// bool shouldRepaint(CustomPainter oldDelegate) => false; +// +// } +// class CurvedPainter extends CustomPainter { +// final Color color; // 👈 اللون +// final double height; // 👈 الارتفاع النسبي للكيرف (0.0 إلى 1.0) +// +// CurvedPainter({ +// required this.color, +// this.height = 0.2, +// }); +// +// @override +// void paint(Canvas canvas, Size size) { +// final paint = Paint() +// ..color = color +// ..style = PaintingStyle.fill; +// +// final path = Path(); +// +// +// path.moveTo(0, size.height * height); +// +// path.cubicTo( +// size.width * 0.025, size.height * 0.015, +// size.width * 0.075, size.height * 0.55, +// size.width, size.height * -0.045, +// ); +// +// path.lineTo(size.width, 0); +// path.lineTo(0, 0); +// path.close(); +// +// canvas.drawPath(path, paint); +// } +// +// @override +// bool shouldRepaint(covariant CurvedPainter oldDelegate) { +// return oldDelegate.color != color || oldDelegate.height != height; +// } +// } +// class CurvedPainter extends CustomPainter { +// final Color color; +// final double curveScale; +// final double startOffset; // نسبة بداية الكيرف من اليسار (0-1) +// final double endOffset; // نسبة نهاية الكيرف قبل الحافة اليمنى (0-1) +// +// CurvedPainter({ +// required this.color, +// this.curveScale = 1.0, +// this.startOffset = 0.0, +// this.endOffset = 1.0, +// }); +// +// @override +// void paint(Canvas canvas, Size size) { +// final paint = Paint() +// ..color = color +// ..style = PaintingStyle.fill; +// +// final path = Path(); +// +// double startX = size.width * startOffset; +// double endX = size.width * endOffset; +// +// path.moveTo(startX, size.height * 0.25 * curveScale); +// +// path.cubicTo( +// startX + (endX - startX) * 0.25, size.height * 0.05 * curveScale, +// startX + (endX - startX) * 0.55, size.height * 0.7 * curveScale, +// endX, size.height * 0.25 * curveScale, +// ); +// path.lineTo(endX, 0); +// path.lineTo(startX, 0); +// path.close(); +// +// canvas.drawPath(path, paint); +// } +// +// @override +// bool shouldRepaint(covariant CurvedPainter oldDelegate) { +// return oldDelegate.color != color || +// oldDelegate.curveScale != curveScale || +// oldDelegate.startOffset != startOffset || +// oldDelegate.endOffset != endOffset; +// } +// } + +//================================ +class CurveBackground extends StatelessWidget { + const CurveBackground({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + height: 200, // ممكن تغيري الارتفاع حسب التصميم + child: Stack( + children: [ + // الموجة الخلفية (الأفتح) + CustomPaint( + size: const Size(double.infinity, double.infinity), + painter: _CurvePainter( + // اللون الأفتح + color: const Color(0xFFFBF3E3), + yFactor: 0.6, + ), + ), + // الموجة الأمامية (الأغمق شوية) + CustomPaint( + size: const Size(double.infinity, double.infinity), + painter: _CurvePainter( + color: const Color(0xFFFFE7B7), // اللون الأغمق + yFactor: 0.3, + ), + ), + ], + ), + ); + } +} + +class _CurvePainter extends CustomPainter { + final Color color; + final double yFactor; + + _CurvePainter({required this.color, required this.yFactor}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + final path = Path(); + path.lineTo(0, size.height * yFactor); + path.quadraticBezierTo( + size.width * 0.25, + size.height * (yFactor + 0.1), + size.width * 0.5, + size.height * yFactor, + ); + path.quadraticBezierTo( + size.width * 0.75, + size.height * (yFactor - 0.15), + size.width, + size.height * yFactor, + ); + path.lineTo(size.width, 0); + path.close(); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class _TrackItem { + final String title; + final String image; + + _TrackItem(this.title, this.image); +} + +class _TrackCard extends StatelessWidget { + final String title; + final String image; + final bool isSelected; + final VoidCallback onTap; + + const _TrackCard({ + required this.title, + required this.image, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + const orange = Color(0xFFF6A100); + + return Material( + color: isSelected ? orange : Colors.white, + elevation: isSelected ? 2 : 1, + borderRadius: BorderRadius.circular(16), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: isSelected + ? null + : Border.all(color: const Color(0xFFEDEDED)), + boxShadow: [ + if (!isSelected) + BoxShadow( + color: const Color(0xFF000000).withOpacity(0.04), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + image,width: 48,height: 48, + + color: isSelected ? Colors.white : orange, + ), + const SizedBox(height: 12), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isSelected ? Colors.white : orange, + ), + ), + ], + ), + ), + ), + ); + } +} + +class _LevelOption extends StatelessWidget { + final String label; + final String value; + final String groupValue; + final ValueChanged onChanged; + + const _LevelOption({ + required this.label, + required this.value, + required this.groupValue, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + const orange = Color(0xFFF6A100); + final isSelected = value == groupValue; + return InkWell( + borderRadius: BorderRadius.circular(24), + onTap: () => onChanged(value), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? orange : const Color(0xFFBDBDBD), + width: 2, + ), + ), + alignment: Alignment.center, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 10, + height: 10, + decoration: BoxDecoration( + color: isSelected ? orange : Colors.transparent, + shape: BoxShape.circle, + ), + ), + ), + const SizedBox(width: 8), + Text( + label, + style: TextStyle( + color: const Color(0xFF333333), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } +} + + diff --git a/pubspec.lock b/pubspec.lock index 7f5dfb0..6f1e8b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -361,7 +361,7 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted version: "1.16.0" @@ -582,7 +582,7 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted version: "0.7.6"