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,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): نصّ · هاتف · بحث.
|
||||
|
||||
Reference in New Issue
Block a user