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:
Hamza-Ayed
2026-08-05 01:08:18 +03:00
co-authored by Claude Opus 5
parent 3a8caf7b27
commit e22cc5c7e4
128 changed files with 684 additions and 666 deletions
@@ -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): نصّ · هاتف · بحث.