feat(apps): ثيم سيرو ومفتاح الدولة والعراق
## الثيم مقرَّب من سيرو (قرار المالك 2026-08-05) - البذرة صارت كحليّ سيرو #1A2340 بدل الأزرق العام، والثانوي #8C9CF8 - fromSeed وحدها تنتج من بذرة داكنة أسطحاً باهتة، فثُبّت الأساسي والثانوي والأسطح صراحةً وتُرك الباقي للمشتقّ — شكل سيرو مع تباين M3 - الأدوار الدلالية بأرقام سيرو الميدانية: نجاح #22C55E · تحذير #F59E0B · خطأ #EF4444، وبطاقة الليلي #2C2C2E ## مفتاح الدولة عاد core/ui/phone_field.dart — النمط المجرَّب في سيرو، لكن بلا intl_phone_field: تلك الحزمة تحمل 240 دولة ونحتاج أربعاً. CountryCode صريح ومقصور على أسواقنا. - الاختيار من ورقة سفلية لا قائمة منسدلة: أهداف لمس أكبر وأربع دول لا مئتان - الترتيب يبدأ بحزمة دولة المستأجر — الأردنيّ يرى الأردن أولاً بلا بحث - الرقم يُرسل كاملاً بالمفتاح بلا + (962790000001) كما يقبله الخادم - التحقّق صار بطول الدولة لا برقم ثابت: jo/sy تسع خانات، eg/iq عشر ## العراق - CountryCode.iraq (+964) في قائمة الهاتف - قنوات دفعه: زين كاش · كي كارد · نقد ⚠️ الباك إند لا يعرف حزمة `iq`: default-tariffs.ts فيه jo/sy/eg فقط، فمستأجر عراقي يُزوَّد بلا أي تعرفة. موثَّق في الكود — وهو شغل خادم يسبق أي إطلاق عراقي. flutter analyze نظيف في التطبيقين. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3a8caf7b27
commit
e22cc5c7e4
@@ -1,23 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
import 'core/build_config.dart';
|
||||
|
||||
import 'core/design/theme.dart';
|
||||
|
||||
import 'core/di.dart';
|
||||
|
||||
import 'core/l10n/l10n.dart';
|
||||
|
||||
import 'core/router.dart';
|
||||
|
||||
import 'core/tenant/tenant_cubit.dart';
|
||||
|
||||
import 'features/settings/cubit/settings_cubit.dart';
|
||||
|
||||
import 'features/settings/cubit/settings_state.dart';
|
||||
|
||||
class TripzApp extends StatelessWidget {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
import 'api_exception.dart';
|
||||
|
||||
/// عميل map-saas — **منفصل تماماً** عن `ApiClient`.
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
import '../storage/token_store.dart';
|
||||
|
||||
import 'api_exception.dart';
|
||||
|
||||
import 'auth_interceptor.dart';
|
||||
|
||||
/// الواجهة الوحيدة للخادم. لا تُنشأ `Dio` في أي مكان آخر.
|
||||
|
||||
@@ -3,9 +3,7 @@ import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../build_config.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
import '../storage/token_store.dart';
|
||||
|
||||
/// يثبّت ترويسات كل طلب، ويجدّد التوكن **استباقياً** قبل انتهائه.
|
||||
|
||||
@@ -12,7 +12,13 @@ class BuildConfig {
|
||||
static const String tenantSlug = 'siro';
|
||||
|
||||
/// اللون البذرة — منه تُشتقّ `ColorScheme` للوضعين.
|
||||
static const Color seedColor = Color(0xFF1F6FEB);
|
||||
///
|
||||
/// كحليّ سيرو (`#1A2340`) بقرار المالك 2026-08-05: الهوية البصرية تقترب من
|
||||
/// سيرو المجرَّب ميدانياً بدل الأزرق العام.
|
||||
static const Color seedColor = Color(0xFF1A2340);
|
||||
|
||||
/// اللون الثانوي — بنفسجي فاتح، يُستعمل للتمييز والحالات النشطة.
|
||||
static const Color accentColor = Color(0xFF8C9CF8);
|
||||
|
||||
static const String logoAsset = 'assets/images/logo.png';
|
||||
static const String splashLogoAsset = 'assets/images/splash_logo.png';
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../build_config.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
import 'tripz_colors.dart';
|
||||
|
||||
import 'typography.dart';
|
||||
|
||||
/// دالة بناء الثيم الوحيدة (docs/26 §4): مدخلان لا ثالث لهما.
|
||||
@@ -13,9 +10,32 @@ import 'typography.dart';
|
||||
/// الاتجاه (RTL/LTR) **لا يُفرض هنا** — `MaterialApp` يشتقّه من الـlocale
|
||||
/// وحده. فرض `Directionality.rtl` يكسر الإنجليزية (docs/26 §4).
|
||||
ThemeData buildTheme(Brightness brightness, Locale locale) {
|
||||
// البذرة الكحلية داكنة جداً، و`fromSeed` وحدها تنتج منها أسطحاً باهتة.
|
||||
// نثبّت الأساسي والثانوي صراحةً ونترك للمشتقّ باقي الأدوار — فيبقى الشكل
|
||||
// قريباً من سيرو مع ضمان التباين الذي يوفّره M3.
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: BuildConfig.seedColor,
|
||||
brightness: brightness,
|
||||
).copyWith(
|
||||
primary: brightness == Brightness.dark
|
||||
? BuildConfig.accentColor
|
||||
: BuildConfig.seedColor,
|
||||
onPrimary: brightness == Brightness.dark
|
||||
? BuildConfig.seedColor
|
||||
: Colors.white,
|
||||
secondary: BuildConfig.accentColor,
|
||||
surface: brightness == Brightness.dark
|
||||
? const Color(0xFF161618)
|
||||
: const Color(0xFFF8FAFC),
|
||||
onSurface: brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: const Color(0xFF0F172A),
|
||||
onSurfaceVariant: brightness == Brightness.dark
|
||||
? const Color(0xFFB0B3B8)
|
||||
: const Color(0xFF64748B),
|
||||
outlineVariant: brightness == Brightness.dark
|
||||
? const Color(0xFF3A3A3C)
|
||||
: const Color(0xFFE2E8F0),
|
||||
);
|
||||
final isDark = brightness == Brightness.dark;
|
||||
final texts = buildTextTheme(brightness, locale);
|
||||
|
||||
@@ -40,35 +40,37 @@ class TripzColors extends ThemeExtension<TripzColors> {
|
||||
/// لون هيكل التحميل (skeleton) — لا spinner فارغ (docs/26 §8.4).
|
||||
final Color skeleton;
|
||||
|
||||
// الأرقام من نظام سيرو الميداني: نجاح `#22C55E` · تحذير `#F59E0B` ·
|
||||
// خطأ `#EF4444` — مألوفة لمستخدمي سيرو ومجرَّبة على شاشات الشمس.
|
||||
static const light = TripzColors(
|
||||
success: Color(0xFF1B8A5A),
|
||||
success: Color(0xFF22C55E),
|
||||
onSuccess: Color(0xFFFFFFFF),
|
||||
warning: Color(0xFFB4690E),
|
||||
onWarning: Color(0xFFFFFFFF),
|
||||
danger: Color(0xFFC5303B),
|
||||
warning: Color(0xFFF59E0B),
|
||||
onWarning: Color(0xFF2A1A00),
|
||||
danger: Color(0xFFEF4444),
|
||||
onDanger: Color(0xFFFFFFFF),
|
||||
info: Color(0xFF2563A8),
|
||||
info: Color(0xFF8C9CF8),
|
||||
surfaceRaised: Color(0xFFFFFFFF),
|
||||
mapRoute: Color(0xFF1F6FEB),
|
||||
mapOrigin: Color(0xFF1B8A5A),
|
||||
mapDestination: Color(0xFFC5303B),
|
||||
skeleton: Color(0xFFE6E8EC),
|
||||
mapRoute: Color(0xFF1A2340),
|
||||
mapOrigin: Color(0xFF22C55E),
|
||||
mapDestination: Color(0xFFEF4444),
|
||||
skeleton: Color(0xFFE2E8F0),
|
||||
);
|
||||
|
||||
static const dark = TripzColors(
|
||||
success: Color(0xFF4ECB8D),
|
||||
success: Color(0xFF4ADE80),
|
||||
onSuccess: Color(0xFF00281A),
|
||||
warning: Color(0xFFE9A63B),
|
||||
warning: Color(0xFFFBBF24),
|
||||
onWarning: Color(0xFF2A1A00),
|
||||
danger: Color(0xFFF2707A),
|
||||
danger: Color(0xFFF87171),
|
||||
onDanger: Color(0xFF33000A),
|
||||
info: Color(0xFF7FB2F0),
|
||||
// ليس أسود صرفاً — رمادي داكن متدرّج.
|
||||
surfaceRaised: Color(0xFF20232A),
|
||||
mapRoute: Color(0xFF5B9BFF),
|
||||
mapOrigin: Color(0xFF4ECB8D),
|
||||
mapDestination: Color(0xFFF2707A),
|
||||
skeleton: Color(0xFF2C3038),
|
||||
info: Color(0xFF8C9CF8),
|
||||
// ليس أسود صرفاً — رمادي داكن متدرّج (بطاقة سيرو `#2C2C2E`).
|
||||
surfaceRaised: Color(0xFF2C2C2E),
|
||||
mapRoute: Color(0xFF8C9CF8),
|
||||
mapOrigin: Color(0xFF4ADE80),
|
||||
mapDestination: Color(0xFFF87171),
|
||||
skeleton: Color(0xFF3A3A3C),
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../api/api_client.dart';
|
||||
|
||||
import '../api/api_exception.dart';
|
||||
|
||||
/// إشعارات FCM — **الطبقة الوحيدة التي تعرف Firebase**.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/// مفاتيح الدول المدعومة.
|
||||
///
|
||||
/// حزمة `intl_phone_field` التي يستعملها سيرو تحمل **240 دولة** ولا نحتاج
|
||||
/// منها إلا أربعاً، وتجرّ معها أصولاً وتبعيّات. القائمة هنا صريحة ومقصورة
|
||||
/// على أسواقنا — وتتوسّع بسطر واحد.
|
||||
///
|
||||
/// ترتيب العرض يبدأ بحزمة دولة المستأجر (`countryPack`)، فالمشغّل الأردني
|
||||
/// يرى الأردن أولاً بلا بحث.
|
||||
enum CountryCode {
|
||||
jordan('jo', '962', 'الأردن', 'Jordan', '🇯🇴', 9),
|
||||
syria('sy', '963', 'سوريا', 'Syria', '🇸🇾', 9),
|
||||
egypt('eg', '20', 'مصر', 'Egypt', '🇪🇬', 10),
|
||||
iraq('iq', '964', 'العراق', 'Iraq', '🇮🇶', 10);
|
||||
|
||||
const CountryCode(
|
||||
this.pack,
|
||||
this.dial,
|
||||
this.nameAr,
|
||||
this.nameEn,
|
||||
this.flag,
|
||||
this.nationalLength,
|
||||
);
|
||||
|
||||
/// يطابق `countryPack` في إعداد المستأجر.
|
||||
final String pack;
|
||||
|
||||
/// المفتاح الدولي بلا `+`.
|
||||
final String dial;
|
||||
|
||||
final String nameAr;
|
||||
final String nameEn;
|
||||
final String flag;
|
||||
|
||||
/// طول الرقم الوطني **بلا الصفر البادئ** — أساس التحقّق المحلّي.
|
||||
final int nationalLength;
|
||||
|
||||
String nameFor(String languageCode) =>
|
||||
languageCode == 'ar' ? nameAr : nameEn;
|
||||
|
||||
static CountryCode fromPack(String pack) =>
|
||||
values.firstWhere((c) => c.pack == pack, orElse: () => jordan);
|
||||
|
||||
/// المستأجر أولاً ثم الباقي — لا ترتيب أبجدي يدفن سوق المشغّل للأسفل.
|
||||
static List<CountryCode> orderedFor(String pack) {
|
||||
final first = fromPack(pack);
|
||||
return [first, ...values.where((c) => c != first)];
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ enum PaymentChannel {
|
||||
mtn('mtn_cash', 'MTN Cash', 'إم تي إن كاش'),
|
||||
shamCash('sham_cash', 'Sham Cash', 'شام كاش'),
|
||||
paymob('paymob', 'PayMob', 'باي موب'),
|
||||
zainCash('zain_cash', 'ZainCash', 'زين كاش'),
|
||||
qiCard('qi_card', 'Qi Card', 'كي كارد'),
|
||||
cash('cash', 'Cash', 'نقداً');
|
||||
|
||||
const PaymentChannel(this.wire, this.nameEn, this.nameAr);
|
||||
@@ -38,6 +40,14 @@ enum PaymentChannel {
|
||||
PaymentChannel.cash,
|
||||
],
|
||||
'eg' => const [PaymentChannel.paymob, PaymentChannel.cash],
|
||||
// العراق: المحافظ الأشيع محلياً. ⚠️ الباك إند لا يعرف حزمة `iq` بعد
|
||||
// (`default-tariffs.ts` فيه jo/sy/eg فقط) — مستأجر عراقي يُزوَّد بلا
|
||||
// تعرفة، وهذا شغل خادم يسبق أي إطلاق عراقي.
|
||||
'iq' => const [
|
||||
PaymentChannel.zainCash,
|
||||
PaymentChannel.qiCard,
|
||||
PaymentChannel.cash,
|
||||
],
|
||||
// حزمة غير معروفة: النقد وحده — لا نعرض قناة قد لا تعمل في سوقها.
|
||||
_ => const [PaymentChannel.cash],
|
||||
};
|
||||
|
||||
@@ -3,11 +3,8 @@ import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../api/api_exception.dart';
|
||||
|
||||
import 'app_user.dart';
|
||||
|
||||
import 'session_source.dart';
|
||||
|
||||
import 'session_state.dart';
|
||||
|
||||
/// حالة الجلسة على مستوى التطبيق — مصدر قرار التوجيه الوحيد
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'tenant_cubit.dart';
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../api/api_client.dart';
|
||||
|
||||
import '../api/api_exception.dart';
|
||||
|
||||
import '../build_config.dart';
|
||||
|
||||
import 'tenant_config.dart';
|
||||
|
||||
/// يجلب إعداد المستأجر مرة عند الإقلاع، ويخزّنه محلياً.
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../build_config.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
import '../router.dart';
|
||||
|
||||
import '../session/session_cubit.dart';
|
||||
|
||||
import '../session/session_state.dart';
|
||||
|
||||
import '../tenant/feature_gate.dart';
|
||||
|
||||
/// الدراور — نقطة تطبيق **طبقتَي الميزات** معاً (docs/23 §5 + docs/38 §10):
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
import 'tripz_button.dart';
|
||||
|
||||
/// حالة الفراغ: رسالة **تدلّ على الفعل التالي** لا مجرّد «لا يوجد»
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
import 'tripz_button.dart';
|
||||
|
||||
/// حالة الخطأ: رسالة **بالعربية** مع «أعد المحاولة» — لا شاشة صامتة
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../features/auth/cubit/phone_error.dart';
|
||||
|
||||
import '../api/api_failure.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
/// النقطة **الوحيدة** التي تُترجَم فيها رموز الفشل إلى نصّ.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
/// ترويسة موحّدة لشاشات الدخول: أيقونة ثم عنوان ثم سطر شارح.
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
import '../design/tripz_colors.dart';
|
||||
import '../design/typography.dart';
|
||||
import '../payments/country_codes.dart';
|
||||
|
||||
/// حقل الهاتف بمفتاح دولة — النمط المجرَّب في سيرو.
|
||||
///
|
||||
/// المفتاح يُختار من ورقة سفلية لا من قائمة منسدلة: الأصابع على الهاتف
|
||||
/// أدقّ مع أهداف كبيرة، والقائمة أربع دول لا مئتان.
|
||||
///
|
||||
/// يُخرج الرقم **كاملاً بالمفتاح بلا `+`** (`962790000001`) — وهو ما يقبله
|
||||
/// الخادم ويطبّعه (docs/38 §2).
|
||||
class PhoneField extends StatelessWidget {
|
||||
const PhoneField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.country,
|
||||
required this.onCountryChanged,
|
||||
this.label,
|
||||
this.hint,
|
||||
this.errorText,
|
||||
this.enabled = true,
|
||||
this.onSubmitted,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final CountryCode country;
|
||||
final ValueChanged<CountryCode> onCountryChanged;
|
||||
final String? label;
|
||||
final String? hint;
|
||||
final String? errorText;
|
||||
final bool enabled;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.phone,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: onSubmitted,
|
||||
maxLength: country.nationalLength + 1,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
autofillHints: const [AutofillHints.telephoneNumber],
|
||||
style: numericStyle(context.texts.bodyLarge),
|
||||
// الرقم يُقرأ يساراً-ليميناً حتى في واجهة عربية.
|
||||
textDirection: TextDirection.ltr,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
hintText: hint,
|
||||
errorText: errorText,
|
||||
counterText: '',
|
||||
prefixIcon: _DialPrefix(
|
||||
country: country,
|
||||
enabled: enabled,
|
||||
onTap: () => _pickCountry(context),
|
||||
),
|
||||
prefixIconConstraints: const BoxConstraints(minWidth: 0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pickCountry(BuildContext context) async {
|
||||
final lang = Localizations.localeOf(context).languageCode;
|
||||
final picked = await showModalBottomSheet<CountryCode>(
|
||||
context: context,
|
||||
builder: (context) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final c in CountryCode.values)
|
||||
ListTile(
|
||||
leading: Text(c.flag, style: const TextStyle(fontSize: 24)),
|
||||
title: Text(c.nameFor(lang)),
|
||||
trailing: Text(
|
||||
'+${c.dial}',
|
||||
textDirection: TextDirection.ltr,
|
||||
style: numericStyle(context.texts.bodyMedium),
|
||||
),
|
||||
selected: c == country,
|
||||
onTap: () => Navigator.of(context).pop(c),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (picked != null) onCountryChanged(picked);
|
||||
}
|
||||
}
|
||||
|
||||
class _DialPrefix extends StatelessWidget {
|
||||
const _DialPrefix({
|
||||
required this.country,
|
||||
required this.enabled,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final CountryCode country;
|
||||
final bool enabled;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: enabled ? onTap : null,
|
||||
borderRadius: Radii.field_,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Space.sm),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
textDirection: TextDirection.ltr,
|
||||
children: [
|
||||
Text(country.flag, style: const TextStyle(fontSize: 20)),
|
||||
const SizedBox(width: Space.xxs),
|
||||
Text(
|
||||
'+${country.dial}',
|
||||
style: numericStyle(context.texts.bodyLarge),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_drop_down_rounded,
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: Space.xxs),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
color: context.colors.outlineVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
/// هيكل تحميل — **لا spinner فارغ** (docs/26 §8.4).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/tripz_colors.dart';
|
||||
|
||||
enum BannerTone { info, success, warning, danger }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/typography.dart';
|
||||
|
||||
/// حقل رمز التحقّق — أربع خانات (طول رمز تريبز، docs/38 §2).
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import 'empty_view.dart';
|
||||
|
||||
import 'error_view.dart';
|
||||
|
||||
import 'skeleton.dart';
|
||||
|
||||
import 'view_status.dart';
|
||||
|
||||
/// نقطة التنظيم المركزية (docs/26 §6). **ممنوع `Scaffold` خام في
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../design/tokens.dart';
|
||||
|
||||
import '../design/typography.dart';
|
||||
|
||||
/// حقل الإدخال الموحّد (docs/26 §7): نصّ · هاتف · بحث.
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../core/api/api_exception.dart';
|
||||
|
||||
import '../../../core/api/api_failure.dart';
|
||||
|
||||
import '../../../core/config.dart';
|
||||
|
||||
import '../../../core/payments/country_codes.dart';
|
||||
import '../data/auth_repository.dart';
|
||||
|
||||
import 'login_state.dart';
|
||||
|
||||
import 'phone_error.dart';
|
||||
|
||||
/// تدفّق الدخول كاملاً: الشروط ← إذن الموقع ← الهاتف ← الرمز.
|
||||
@@ -121,6 +115,15 @@ class LoginCubit extends Cubit<LoginState> {
|
||||
|
||||
// ── الهاتف ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// يُنادى عند الإقلاع بحزمة دولة المستأجر — فيرى الأردنيُّ الأردن أولاً.
|
||||
void applyTenantCountry(String countryPack) {
|
||||
if (state.phone.isNotEmpty) return; // لا نغيّر اختياراً واعياً للمستخدم
|
||||
emit(state.copyWith(country: CountryCode.fromPack(countryPack)));
|
||||
}
|
||||
|
||||
void countryChanged(CountryCode country) =>
|
||||
emit(state.copyWith(country: country, clearPhoneError: true));
|
||||
|
||||
void phoneChanged(String value) {
|
||||
emit(state.copyWith(
|
||||
phone: value,
|
||||
@@ -130,14 +133,14 @@ class LoginCubit extends Cubit<LoginState> {
|
||||
}
|
||||
|
||||
Future<void> submitPhone() async {
|
||||
final error = validatePhone(state.phone);
|
||||
final error = validatePhone(state.phone, state.country);
|
||||
if (error != null) {
|
||||
emit(state.copyWith(phoneError: error));
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(status: LoginStatus.submitting, clearFailure: true));
|
||||
try {
|
||||
await _repo.sendOtp(state.phone.trim());
|
||||
await _repo.sendOtp(state.e164);
|
||||
emit(state.copyWith(status: LoginStatus.idle, step: LoginStep.otp));
|
||||
_startResendCountdown();
|
||||
} on ApiException catch (e) {
|
||||
@@ -164,7 +167,7 @@ class LoginCubit extends Cubit<LoginState> {
|
||||
if (!state.canResend) return;
|
||||
emit(state.copyWith(status: LoginStatus.submitting, clearFailure: true));
|
||||
try {
|
||||
await _repo.sendOtp(state.phone.trim());
|
||||
await _repo.sendOtp(state.e164);
|
||||
emit(state.copyWith(status: LoginStatus.idle));
|
||||
_startResendCountdown();
|
||||
} on ApiException catch (e) {
|
||||
@@ -182,7 +185,7 @@ class LoginCubit extends Cubit<LoginState> {
|
||||
if (code.length != AppConfig.otpLength) return;
|
||||
emit(state.copyWith(status: LoginStatus.submitting, clearFailure: true));
|
||||
try {
|
||||
final user = await _repo.verifyOtp(phone: state.phone.trim(), code: code);
|
||||
final user = await _repo.verifyOtp(phone: state.e164, code: code);
|
||||
_resendTimer?.cancel();
|
||||
emit(state.copyWith(
|
||||
status: LoginStatus.success,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
import '../../../core/api/api_failure.dart';
|
||||
|
||||
import '../../../core/payments/country_codes.dart';
|
||||
import '../../../core/session/app_user.dart';
|
||||
|
||||
import 'phone_error.dart';
|
||||
|
||||
/// خطوات الدخول بالترتيب الميداني المثبت في سيرو (docs/39 §2): بوابتان
|
||||
@@ -17,6 +16,7 @@ class LoginState extends Equatable {
|
||||
this.step = LoginStep.agreement,
|
||||
this.status = LoginStatus.idle,
|
||||
this.phone = '',
|
||||
this.country = CountryCode.jordan,
|
||||
this.agreed = false,
|
||||
this.failure,
|
||||
this.phoneError,
|
||||
@@ -31,6 +31,13 @@ class LoginState extends Equatable {
|
||||
/// الرقم كما أدخله المستخدم — الخادم يطبّعه، فلا نطبّعه نحن (docs/38 §2).
|
||||
final String phone;
|
||||
|
||||
/// الدولة المختارة — تحدّد المفتاح الدولي وطول التحقّق. تُبدأ من
|
||||
/// `countryPack` المستأجر لا من لغة الجهاز.
|
||||
final CountryCode country;
|
||||
|
||||
/// ما يُرسل للخادم: المفتاح + الرقم بلا `+` (`962790000001`).
|
||||
String get e164 => '${country.dial}${phone.trim()}';
|
||||
|
||||
final bool agreed;
|
||||
final ApiFailure? failure;
|
||||
final PhoneError? phoneError;
|
||||
@@ -51,6 +58,7 @@ class LoginState extends Equatable {
|
||||
LoginStep? step,
|
||||
LoginStatus? status,
|
||||
String? phone,
|
||||
CountryCode? country,
|
||||
bool? agreed,
|
||||
ApiFailure? failure,
|
||||
PhoneError? phoneError,
|
||||
@@ -64,6 +72,7 @@ class LoginState extends Equatable {
|
||||
step: step ?? this.step,
|
||||
status: status ?? this.status,
|
||||
phone: phone ?? this.phone,
|
||||
country: country ?? this.country,
|
||||
agreed: agreed ?? this.agreed,
|
||||
failure: clearFailure ? null : (failure ?? this.failure),
|
||||
phoneError: clearPhoneError ? null : (phoneError ?? this.phoneError),
|
||||
@@ -79,6 +88,7 @@ class LoginState extends Equatable {
|
||||
step,
|
||||
status,
|
||||
phone,
|
||||
country,
|
||||
agreed,
|
||||
failure,
|
||||
phoneError,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import '../../../core/payments/country_codes.dart';
|
||||
|
||||
/// أخطاء تحقّق **محليّة** لرقم الهاتف — تُكتشف قبل أي نداء شبكة.
|
||||
/// الشروط الثلاثة منقولة من التحقّق المطبَّق ميدانياً في سيرو (docs/39 §2).
|
||||
enum PhoneError { empty, leadingZero, tooShort }
|
||||
|
||||
PhoneError? validatePhone(String raw) {
|
||||
/// الطول المتوقّع من **الدولة المختارة** لا رقماً ثابتاً: الأردن وسوريا 9
|
||||
/// خانات، ومصر والعراق 10.
|
||||
PhoneError? validatePhone(String raw, CountryCode country) {
|
||||
final v = raw.trim();
|
||||
if (v.isEmpty) return PhoneError.empty;
|
||||
if (v.startsWith('0')) return PhoneError.leadingZero;
|
||||
if (v.length < 9) return PhoneError.tooShort;
|
||||
if (v.length < country.nationalLength) return PhoneError.tooShort;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import '../../../core/api/api_client.dart';
|
||||
|
||||
import '../../../core/session/app_user.dart';
|
||||
|
||||
import '../../../core/session/session_source.dart';
|
||||
|
||||
import '../../../core/storage/token_store.dart';
|
||||
|
||||
/// كل نداءات المصادقة (docs/38 §2). الـCubit لا ينادي Dio مباشرة أبداً
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../cubit/login_cubit.dart';
|
||||
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
import '../../../core/ui/form_header.dart';
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
import '../cubit/login_cubit.dart';
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
/// البوابة الأولى: شاشة كاملة لا حوار، وزر متابعة **معطّل حتى يُؤشَّر**
|
||||
/// (docs/39 §2). تُحفظ الموافقة فلا تتكرر.
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/di.dart';
|
||||
|
||||
import '../../../core/session/session_cubit.dart';
|
||||
|
||||
import '../cubit/login_cubit.dart';
|
||||
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
import 'agreement_view.dart';
|
||||
|
||||
import 'otp_view.dart';
|
||||
|
||||
import 'permission_view.dart';
|
||||
|
||||
import 'phone_view.dart';
|
||||
|
||||
/// مضيف تدفّق الدخول: خطوة واحدة معروضة في كل لحظة، والانتقال بينها من
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/failure_text.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_otp_field.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../cubit/login_cubit.dart';
|
||||
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
import '../../../core/ui/form_header.dart';
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
import '../../../core/ui/tripz_otp_field.dart';
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
import '../cubit/login_cubit.dart';
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
class OtpView extends StatefulWidget {
|
||||
const OtpView({super.key});
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/status_banner.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../cubit/login_cubit.dart';
|
||||
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
import '../../../core/ui/form_header.dart';
|
||||
import '../../../core/ui/status_banner.dart';
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
import '../cubit/login_cubit.dart';
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
/// البوابة الثانية: إذن الموقع. عند الرفض النهائي لا يبقى إلا فتح إعدادات
|
||||
/// النظام — والعودة منها يلتقطها `LoginPage` بمراقبة دورة الحياة.
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/tenant/tenant_cubit.dart';
|
||||
import '../../../core/ui/failure_text.dart';
|
||||
|
||||
import '../../../core/ui/form_header.dart';
|
||||
import '../../../core/ui/phone_field.dart';
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../../../core/ui/tripz_text_field.dart';
|
||||
|
||||
import '../cubit/login_cubit.dart';
|
||||
|
||||
import '../cubit/login_state.dart';
|
||||
|
||||
import '../../../core/ui/form_header.dart';
|
||||
|
||||
class PhoneView extends StatelessWidget {
|
||||
class PhoneView extends StatefulWidget {
|
||||
const PhoneView({super.key});
|
||||
|
||||
@override
|
||||
State<PhoneView> createState() => _PhoneViewState();
|
||||
}
|
||||
|
||||
class _PhoneViewState extends State<PhoneView> {
|
||||
final _phone = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_phone.addListener(
|
||||
() => context.read<LoginCubit>().phoneChanged(_phone.text),
|
||||
);
|
||||
// الدولة الافتراضية من المستأجر — يُطبَّق بعد أول إطار كي يتوفّر السياق.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
context
|
||||
.read<LoginCubit>()
|
||||
.applyTenantCountry(context.read<TenantCubit>().state.countryPack);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_phone.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -30,6 +50,7 @@ class PhoneView extends StatelessWidget {
|
||||
buildWhen: (p, n) =>
|
||||
p.status != n.status ||
|
||||
p.phoneError != n.phoneError ||
|
||||
p.country != n.country ||
|
||||
p.failure != n.failure,
|
||||
builder: (context, state) {
|
||||
final cubit = context.read<LoginCubit>();
|
||||
@@ -49,11 +70,13 @@ class PhoneView extends StatelessWidget {
|
||||
lead: l10n.phoneLead,
|
||||
),
|
||||
const SizedBox(height: Space.lg),
|
||||
TripzTextField.phone(
|
||||
PhoneField(
|
||||
controller: _phone,
|
||||
country: state.country,
|
||||
onCountryChanged: cubit.countryChanged,
|
||||
label: l10n.phoneLabel,
|
||||
hint: l10n.phoneHint,
|
||||
enabled: !state.isSubmitting,
|
||||
onChanged: cubit.phoneChanged,
|
||||
onSubmitted: (_) => cubit.submitPhone(),
|
||||
errorText: state.phoneError == null
|
||||
? null
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/api/api_exception.dart';
|
||||
|
||||
import '../../../core/api/api_failure.dart';
|
||||
|
||||
import '../../../core/session/app_user.dart';
|
||||
|
||||
import '../../../core/ui/view_status.dart';
|
||||
|
||||
import '../data/profile_repository.dart';
|
||||
|
||||
class ProfileState {
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/di.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/session/session_cubit.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../../../core/ui/tripz_text_field.dart';
|
||||
|
||||
import '../cubit/profile_cubit.dart';
|
||||
|
||||
/// تعديل الاسم. **الرقم لا يُعدَّل**: هو هوية الحساب على الخادم، وتغييره
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/di.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/session/session_cubit.dart';
|
||||
|
||||
import '../../../core/ui/failure_text.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../../../core/ui/tripz_text_field.dart';
|
||||
|
||||
import '../../../core/ui/form_header.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
import '../../../core/ui/tripz_text_field.dart';
|
||||
import '../cubit/profile_cubit.dart';
|
||||
|
||||
class ProfileSetupPage extends StatefulWidget {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/api/api_exception.dart';
|
||||
|
||||
import '../data/rating_repository.dart';
|
||||
|
||||
import 'rating_state.dart';
|
||||
|
||||
class RatingCubit extends Cubit<RatingState> {
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/di.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/tripz_button.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../../../core/ui/tripz_text_field.dart';
|
||||
|
||||
import '../cubit/rating_cubit.dart';
|
||||
|
||||
import '../cubit/rating_state.dart';
|
||||
|
||||
import '../data/rating_repository.dart';
|
||||
|
||||
import 'star_row.dart';
|
||||
|
||||
/// شاشة التقييم — **تُفرض عند فتح التطبيق** إن كانت هناك رحلة معلّقة
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
/// خمس نجوم — أهداف لمس ≥ 48 لأن الشاشة تُستعمل عادةً والمستخدم مستعجل.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'settings_state.dart';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// المظهر واللغة (docs/26 §4) — محفوظان محلياً، وليسا سرّاً فيذهبان إلى
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../cubit/settings_cubit.dart';
|
||||
|
||||
import '../cubit/settings_state.dart';
|
||||
|
||||
/// المظهر واللغة. تبديل اللغة **يقلب الاتجاه كاملاً** بلا إعادة تشغيل —
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/build_config.dart';
|
||||
|
||||
import '../../../core/config.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/session/session_cubit.dart';
|
||||
|
||||
import '../../../core/tenant/tenant_cubit.dart';
|
||||
|
||||
/// شاشة الإقلاع: تستعيد الجلسة ثم يتولّى الراوتر التوجيه. لا تنقّل من هنا —
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/build_config.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/tripz_card.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
/// المساعدة و«عن التطبيق». محتوى محلّي عمداً: الخادم لا يوفّر نقطة أسئلة
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/api/api_exception.dart';
|
||||
|
||||
import '../../../core/ui/view_status.dart';
|
||||
|
||||
import '../data/history_repository.dart';
|
||||
|
||||
import '../data/models/trip.dart';
|
||||
|
||||
class HistoryState {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '../../../core/api/api_client.dart';
|
||||
|
||||
import 'models/trip.dart';
|
||||
|
||||
/// سجلّ الرحلات. لا يستورد ميزة الرحلة كمنطق — يعيد استعمال نموذجها فقط،
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import '../../../core/api/antlaq_api.dart';
|
||||
|
||||
import 'models/geo_point.dart';
|
||||
|
||||
import 'models/place.dart';
|
||||
|
||||
import 'models/route_info.dart';
|
||||
|
||||
/// كل الخرائط من map-saas مباشرة (قرار 2026-07-20). نقاط `/maps/*` في باك إند
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import '../../../core/api/api_client.dart';
|
||||
|
||||
import 'models/fare_quote.dart';
|
||||
|
||||
import 'models/geo_point.dart';
|
||||
|
||||
import 'models/ride_type.dart';
|
||||
|
||||
import 'models/trip.dart';
|
||||
|
||||
class TripRepository {
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../core/design/typography.dart';
|
||||
|
||||
import '../../../core/di.dart';
|
||||
|
||||
import '../../../core/format/money.dart';
|
||||
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../../core/ui/empty_view.dart';
|
||||
|
||||
import '../../../core/ui/tripz_card.dart';
|
||||
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
|
||||
import '../cubit/history_cubit.dart';
|
||||
|
||||
import '../data/models/trip.dart';
|
||||
|
||||
class HistoryPage extends StatefulWidget {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../../core/design/typography.dart';
|
||||
|
||||
import '../../data/models/trip.dart';
|
||||
|
||||
/// بطاقة السائق ولوحة المركبة — أهم ما يبحث عنه الراكب في الشارع، فيأخذ
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../../core/design/typography.dart';
|
||||
|
||||
import '../../../../core/format/money.dart';
|
||||
|
||||
import '../../../../core/l10n/l10n.dart';
|
||||
|
||||
import '../../data/models/trip.dart';
|
||||
|
||||
/// ملخّص الأجرة عند الإنهاء.
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/api/api_exception.dart';
|
||||
|
||||
import '../../../core/ui/view_status.dart';
|
||||
|
||||
import '../data/wallet_repository.dart';
|
||||
|
||||
import 'wallet_state.dart';
|
||||
|
||||
class WalletCubit extends Cubit<WalletState> {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
import '../../../core/ui/view_status.dart';
|
||||
|
||||
import '../data/models/wallet.dart';
|
||||
|
||||
import '../data/models/wallet_txn.dart';
|
||||
|
||||
enum WalletAction { none, toppingUp, topupFailed, topupDone }
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import '../../../core/api/api_client.dart';
|
||||
|
||||
import 'models/wallet.dart';
|
||||
|
||||
import 'models/wallet_txn.dart';
|
||||
|
||||
class WalletRepository {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../../core/design/typography.dart';
|
||||
|
||||
import '../../../../core/format/money.dart';
|
||||
|
||||
/// بطاقة الرصيد — الرقم هو بطل الشاشة، فيأخذ أكبر حجم وأعلى تباين.
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/design/tokens.dart';
|
||||
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
|
||||
import '../../../../core/design/typography.dart';
|
||||
|
||||
import '../../../../core/format/money.dart';
|
||||
|
||||
import '../../data/models/wallet_txn.dart';
|
||||
|
||||
class TxnTile extends StatelessWidget {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'app.dart';
|
||||
|
||||
import 'core/di.dart';
|
||||
|
||||
import 'core/session/session_cubit.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
|
||||
Reference in New Issue
Block a user