Update: 2026-05-07 13:47:48

This commit is contained in:
Hamza-Ayed
2026-05-07 13:47:48 +03:00
parent b8d9b3343e
commit f7aee80553
54 changed files with 714 additions and 53 deletions

View File

@@ -14,6 +14,8 @@ import '../../features/settings/controllers/settings_controller.dart';
import '../../features/subscription/views/subscription_view.dart';
import '../../features/subscription/views/payment_receipt_view.dart';
import '../../features/invoices/views/invoice_detail_view.dart';
import '../../features/onboarding/views/onboarding_view.dart';
import '../../features/onboarding/controllers/onboarding_controller.dart';
import '../../core/storage/secure_storage.dart';
part 'app_routes.dart';
@@ -32,18 +34,32 @@ class AppPages {
// User is already logged in, request Biometric unlock before dashboard
Get.offAllNamed(AppRoutes.BIOMETRIC_AUTH);
} else {
// New user, go to login
Get.offAllNamed(AppRoutes.PHONE_INPUT);
// Check if user has seen onboarding
final hasSeenOnboarding =
await SecureStorage().read('has_seen_onboarding');
if (hasSeenOnboarding == 'true') {
Get.offAllNamed(AppRoutes.PHONE_INPUT);
} else {
Get.offAllNamed(AppRoutes.ONBOARDING);
}
}
});
return const Scaffold(
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.security, size: 100, color: Color(0xFF0F4C81)),
SizedBox(height: 24),
CircularProgressIndicator(),
Image.asset(
'assets/images/logo.jpg',
height: 120,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.security,
size: 100,
color: Color(0xFF0F4C81),
),
),
const SizedBox(height: 24),
const CircularProgressIndicator(),
],
),
),
@@ -105,5 +121,12 @@ class AppPages {
name: AppRoutes.INVOICE_DETAIL,
page: () => const InvoiceDetailView(),
),
GetPage(
name: AppRoutes.ONBOARDING,
page: () => const OnboardingView(),
binding: BindingsBuilder(() {
Get.put(OnboardingController());
}),
),
];
}

View File

@@ -16,4 +16,5 @@ abstract class AppRoutes {
static const SUBSCRIPTION = '/subscription';
static const PAYMENT_RECEIPT = '/payment-receipt';
static const INVOICE_DETAIL = '/invoice-detail';
static const ONBOARDING = '/onboarding';
}

View File

@@ -19,7 +19,15 @@ class PhoneInputView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Icon(Icons.security, size: 80, color: Color(0xFF0F4C81)),
Image.asset(
'assets/images/logo.jpg',
height: 100,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.security,
size: 80,
color: Color(0xFF0F4C81),
),
),
const SizedBox(height: 32),
const Text(
'أهلاً بك في مُصادَق',

View File

@@ -0,0 +1,49 @@
import 'package:get/get.dart';
import '../../../core/storage/secure_storage.dart';
import '../../../app/routes/app_pages.dart';
class OnboardingController extends GetxController {
var currentPage = 0.obs;
final List<OnboardingModel> items = [
OnboardingModel(
title: 'مرحباً بك في مُصادَق',
description:
'أول منصة أردنية ذكية لأتمتة الفواتير الضريبية والربط مع جوفوترا بكل سهولة.',
imageAsset: 'assets/images/onboarding_1.png',
),
OnboardingModel(
title: 'مسح ضوئي ذكي',
description:
'التقط صورة للفاتورة وسيقوم الذكاء الاصطناعي باستخراج كافة البيانات الضريبية في ثوانٍ.',
imageAsset: 'assets/images/onboarding_2.png',
),
OnboardingModel(
title: 'مدفوعات فورية آمنة',
description:
'قم بشحن محفظتك وتفعيل اشتراكك عبر نظام كليك (CliQ) بكل سرعة وأمان.',
imageAsset: 'assets/images/onboarding_3.png',
),
];
void onPageChanged(int index) {
currentPage.value = index;
}
Future<void> completeOnboarding() async {
await SecureStorage().write('has_seen_onboarding', 'true');
Get.offAllNamed(AppRoutes.PHONE_INPUT);
}
}
class OnboardingModel {
final String title;
final String description;
final String imageAsset;
OnboardingModel({
required this.title,
required this.description,
required this.imageAsset,
});
}

View File

@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import '../controllers/onboarding_controller.dart';
class OnboardingView extends GetView<OnboardingController> {
const OnboardingView({super.key});
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDark ? const Color(0xFF0F172A) : Colors.white,
body: SafeArea(
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TextButton(
onPressed: () => controller.completeOnboarding(),
child: Text(
'تخطي',
style: TextStyle(
color: isDark ? Colors.white54 : Colors.grey.shade600,
fontWeight: FontWeight.bold,
),
),
),
),
),
Expanded(
child: PageView.builder(
controller: PageController(),
onPageChanged: controller.onPageChanged,
itemCount: controller.items.length,
itemBuilder: (context, index) {
final item = controller.items[index];
return Padding(
padding: const EdgeInsets.all(40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 300,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: Image.asset(
item.imageAsset,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Icon(
index == 0
? Icons.rocket_launch
: (index == 1
? Icons.document_scanner
: Icons.account_balance_wallet),
size: 150,
color: const Color(0xFF0F4C81),
),
),
),
),
const SizedBox(height: 40),
Text(
item.title,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
color: isDark ? Colors.white : const Color(0xFF0F172A),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Text(
item.description,
style: TextStyle(
fontSize: 16,
color: isDark ? Colors.white70 : Colors.grey.shade600,
height: 1.5,
),
textAlign: TextAlign.center,
),
],
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(40.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(() => Row(
children: List.generate(
controller.items.length,
(index) => AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.only(right: 8),
height: 8,
width: controller.currentPage.value == index ? 24 : 8,
decoration: BoxDecoration(
color: controller.currentPage.value == index
? const Color(0xFF0F4C81)
: Colors.grey.withOpacity(0.3),
borderRadius: BorderRadius.circular(4),
),
),
),
)),
Obx(() => ElevatedButton(
onPressed: () {
if (controller.currentPage.value == controller.items.length - 1) {
controller.completeOnboarding();
} else {
// PageView controller would be needed here for "Next" button
// For simplicity, let's just make it "Start" on the last page
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0F4C81),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 0,
),
child: Text(
controller.currentPage.value == controller.items.length - 1 ? 'ابدأ الآن' : 'التالي',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
)),
],
),
),
],
),
),
);
}
}