feat(apps): الدراور على نمط سيرو — المكافآت والدعوات والمساعدة

جُرد دراور سيرو: 14 مدخلاً للراكب (map_menu_widget) و23 للسائق
(drawer_captain). بُني ما له نقطة فعلية على NestJS، والباقي مذكور صراحةً.

## مبنيّ
- **المكافآت** (features/rewards): GET /rewards/referrals و /rewards/coupons —
  كلاهما مُتحقَّق حيّاً. كود الدعوة يُنسخ ويُشارك، وعدّادات دعوتَهم/قيد
  التأكيد/مدفوعة. يقابل Promos + Invite Driver في سيرو
- **شارك التطبيق · قيّم التطبيق · سياسة الخصوصية · تطبيق الطرف الآخر** —
  أفعال محليّة بلا نقطة خادم (share_plus + url_launcher)
- **المساعدة** وُسّعت: أسئلة شائعة قابلة للطيّ · واتساب الدعم ·
  **فحص توافق الجهاز** (نمط سيرو) يفحص ما يُعطّل التطبيق فعلاً: خدمة الموقع
  وإذنها وإذن الإشعارات — أسباب حقيقية لشكوى «التطبيق لا يعمل»
- روابط المتجر والخصوصية والدعم صارت قيم بناء لكل مستأجر في BuildConfig

## غير مبنيّ — ولا نقطة خادم له
الشكاوى (R3 في الباكلوج) · الإحصاءات · التحدّيات ولوحة المتصدّرين · مركز
الصيانة · جدولي · التأمين الصحي (مؤجَّل بقرار المالك) · الطعام والمواصلات
(خارج النطاق، docs/39 §1). بناء واجهة بلا خادم يعني شاشة تكذب على المستخدم.

## سجلّ الإشعارات
سيرو يعرض قائمة إشعارات؛ الخادم يوفّر POST /notifications/token فقط بلا نقطة
سرد. لم تُبنَ شاشة قائمة على فراغ.

flutter analyze نظيف في التطبيقين.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-08-05 01:34:23 +03:00
co-authored by Claude Opus 5
parent 6a33cc7241
commit ca766a2d4e
27 changed files with 1406 additions and 48 deletions
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../build_config.dart';
import '../config.dart';
@@ -52,6 +54,13 @@ class AppDrawer extends StatelessWidget {
label: isDriver ? l10n.menuEarnings : l10n.menuWallet,
route: Routes.wallet,
),
if (Features.coupons && context.tenantHas('coupons'))
_Item(
icon: Icons.card_giftcard_rounded,
label: l10n.menuRewards,
route: Routes.rewards,
),
const Divider(indent: Space.md, endIndent: Space.md),
_Item(
icon: Icons.settings_outlined,
label: l10n.menuSettings,
@@ -62,6 +71,29 @@ class AppDrawer extends StatelessWidget {
label: l10n.menuSupport,
route: Routes.support,
),
// مداخل محليّة بلا نقطة خادم — تُنفَّذ في الجهاز مباشرة.
_Action(
icon: Icons.share_outlined,
label: l10n.menuShare,
onTap: () => SharePlus.instance
.share(ShareParams(text: l10n.shareMessage(''))),
),
_Action(
icon: Icons.star_outline_rounded,
label: l10n.menuRateApp,
onTap: () => _open(BuildConfig.storeUrl),
),
_Action(
icon: Icons.shield_outlined,
label: l10n.menuPrivacy,
onTap: () => _open(BuildConfig.privacyUrl),
),
if (!isDriver)
_Action(
icon: Icons.local_taxi_outlined,
label: l10n.menuDriverApp,
onTap: () => _open(BuildConfig.driverAppUrl),
),
],
),
),
@@ -83,6 +115,13 @@ class AppDrawer extends StatelessWidget {
);
}
static Future<void> _open(String url) async {
final uri = Uri.tryParse(url);
if (uri == null) return;
// فشل الفتح لا يُسقط شيئاً — رابط خارجي ليس جزءاً من التطبيق.
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
Future<void> _confirmLogout(BuildContext context) async {
final l10n = context.l10n;
final session = context.read<SessionCubit>();
@@ -155,6 +194,31 @@ class _Header extends StatelessWidget {
}
}
/// مدخل ينفّذ فعلاً محلياً (مشاركة · رابط خارجي) لا تنقّلاً.
class _Action extends StatelessWidget {
const _Action({
required this.icon,
required this.label,
required this.onTap,
});
final IconData icon;
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon),
title: Text(label),
onTap: () {
Navigator.of(context).pop();
onTap();
},
);
}
}
class _Item extends StatelessWidget {
const _Item({required this.icon, required this.label, required this.route});