# القصص (Stories) في تطبيق الموبايل: ملف التسليم لمطوّر Flutter

الهدف: أن يطابق التطبيق الموقع تماماً في الشكل والسلوك. المرجع الحي هو الموقع نفسه (الصفحة الرئيسية)، والكود المرجعي هو `resources/views/frontend/partials/stories_strip.blade.php`، وعقد الـ API في `docs/api/stories.md`.

> لا تغيير على الـ API لأجل هذا الملف. كل ما يلي موجود ويعمل اليوم.

---

## 1. ما هي القصة

صورة أو فيديو قصير، مع نص اختياري ورابط اختياري. تنشرها **كابيتانو** أو **متجر بائع**. القصة حيّة **24 ساعة** من وقت النشر ثم تنتهي (صاحبها يستطيع إعادة نشرها).

في الصفحة الرئيسية يظهر **شريط دوائر**: دائرة واحدة لكل حساب (كابيتانو أو متجر)، وتضغط الدائرة فتفتح شاشة المشاهدة بملء الشاشة.

---

## 2. قراءة البيانات

`GET api/v2/stories`

- لا يحتاج توكن. الجواب واحد للجميع ومخزّن 60 ثانية لكل لغة (`App-Language`).
- نحتاج الترويسة المعتادة `System-Key`.

```json
{
  "result": true,
  "lifetime_hours": 24,
  "data": [
    {
      "account": { "type": "kapitano|shop", "shop_id": null, "name": "...", "slug": null, "logo": "https://..." },
      "stories": [
        {
          "id": 12,
          "media_type": "image|video",
          "media_url": "https://...",
          "text": "...",
          "published_at": "ISO-8601",
          "expires_at": "ISO-8601",
          "link": { "type": "shop|category|brand|product|section", "id": 5, "name": "...", "slug": "...", "path": "/product/x", "url": "https://..." }
        }
      ]
    }
  ]
}
```

قواعد:

- `data` مرتّبة بترتيب الدوائر الذي يحدده الموظفون. **ارسم الدوائر بنفس الترتيب بدون إعادة ترتيب.**
- `stories` داخل الحساب مرتّبة بترتيب الحساب نفسه. تُعرض بالترتيب.
- لا توجد حسابات فارغة: كل عنصر في `data` فيه قصة واحدة على الأقل. إذا كانت `data` فارغة **لا تعرض الشريط إطلاقاً** (لا مساحة ولا عنوان).
- `link` تكون `null` عندما لا تشير القصة إلى شيء، أو عندما صار هدفها غير معروض للبيع. في هذه الحالة لا يظهر زر الرابط.
- الرابط العميق = `kapitano://` + `path` بدون الشرطة الأولى، مثلاً `kapitano://product/x`. روابط `section` تفتح `/section/<slug>` (انتبه لملاحظة الأقسام في `docs/api/deep-links/README.md`: صفحة `section` تعمل فقط بعد أن يضيف التطبيق شاشتها).
- أعد جلب الشريط عند فتح الصفحة الرئيسية وعند السحب للتحديث. لا تخزّنه على الجهاز أكثر من دقيقة.

---

## 3. مكان الشريط في الصفحة الرئيسية

- **الافتراضي: أعلى الصفحة**، فوق السلايدر / أول قسم، وهو كذلك على الموقع.
- على الموقع صار موضع القصص قابلاً للتحكم من الأدمن (يُسحب مع أقسام الصفحة الرئيسية). **التطبيق لا يعرف هذا الموضع**: القصص لا تدخل في `layout` الذي يرسله `v2/home-sections` عمداً حتى لا تظهر الأقسام القديمة في التطبيق بشكل خاطئ. لذلك ثبّتها في التطبيق في الأعلى. إذا أردتم لاحقاً أن يتبع التطبيق موضع الموقع، نضيف مفتاحاً للـ API ونخبرك.

---

## 4. شكل الدائرة (مطابق للموقع)

القياسات بالنقاط المنطقية (dp):

| العنصر | الجوال | قياس الموقع |
|---|---|---|
| عرض خلية الحساب | 70 | 78 |
| قطر الحلقة | 68 | 76 |
| قطر الشعار داخل الحلقة | 56 | 62 |
| الفراغ بين الحلقة والشعار | 6 | 7 |
| سماكة الحلقة | 3.5 | 3.5 |
| المسافة بين الخلايا | 12 | 16 |

استخدم قياسات **الجوال** في التطبيق.

- **الحلقة مقاطع** (Segmented ring): **قوس واحد لكل قصة**. تبدأ من أعلى الدائرة (زاوية -90°) وتتوزع حول الدائرة بالتساوي.
  - إذا كان للحساب قصة واحدة فالحلقة دائرة كاملة بلا فراغات.
  - إذا كان أكثر، بين كل قوسين فراغ صغير ثابت، حوالي 7 من 80 وحدة في تصميم الموقع (viewBox 80، نصف القطر 37). أي نحو 3% من محيط الدائرة. أطراف الأقواس دائرية (`StrokeCap.round`).
  - ارسمها بـ `CustomPainter` (`canvas.drawArc`).
- **لون القوس:**
  - قصة **لم تُشاهد** بعد: تدرج أصفر. على الموقع اليوم هو أصفر `#FFC519` إلى برتقالي `#F0641E` (من أسفل اليسار إلى أعلى اليمين). **قد يتغير البرتقالي إلى أصفر فقط**، لأن البرتقالي ليس من ألوان الهوية. ضع اللونين في ثابت واحد في التطبيق ليسهل تغييرهما.
  - قصة **شوهدت**: رمادي `#D9DCE3`.
- **الشعار:** دائري، تغطيته `cover`، خلفية بيضاء، حدّ أبيض خارجي 3dp، وظل خفيف (لون كحلي `#10164C` بشفافية 18%، blur 10، انزياح 3 للأسفل).
- **شارة العدد:** تظهر فقط إذا كان للحساب **أكثر من قصة**، في الزاوية السفلية (اليمنى في LTR، اليسرى في RTL)، خلفية كحلي `#10164C`، رقم أصفر `#FFC519`، خط 11 بولد، ارتفاع 20، شكل كبسولة، وحدّ أبيض 2dp حولها.
- **الاسم** تحت الحلقة، سطر واحد مع `ellipsis`، خط 12:
  - غير مشاهد: كحلي `#10164C`، وزن 600.
  - مشاهد: رمادي `#8A8F9C`، وزن 500، وصورة الشعار بإشباع أقل (0.7) وشفافية 0.88.
- **حالة المشاهدة لكل قوس على حدة:** القوس يكون رمادياً عندما `story.id <= آخر id شوهد` لهذا الحساب. الحساب كله "مشاهد" (للاسم والشعار) عندما يكون آخر id شوهد >= id آخر قصة فيه.
- **الضغط على الدائرة:** يكبر قليلاً عند اللمس (انكماش إلى 0.95 أثناء الضغط)، ثم تفتح شاشة المشاهدة على **أول قصة** في هذا الحساب.
- الشريط أفقي قابل للسحب، بدون شريط تمرير، يبدأ من حافة اللغة (RTL في العربية).

### حفظ المشاهدة

خزّن على الجهاز خريطة: `{ "<shop_id أو 0 لكابيتانو>": <آخر id شوهد> }` (مثلاً في `SharedPreferences`). عند **عرض** أي قصة، حدّث القيمة إلى `max(القديم, story.id)`. لا يُرسل شيء إلى السيرفر.

---

## 5. شاشة المشاهدة (مطابقة للموقع)

شاشة كاملة، خلفية سوداء. على الأجهزة العريضة (تابلت) يكون الصندوق بعرض أقصى 420 وارتفاع أقصى 760 في المنتصف. على الجوال ملء الشاشة.

### العناصر من الأعلى إلى الأسفل

1. **أشرطة التقدم** (أعلى الشاشة، 8dp من الحافة والجوانب): شريط لكل قصة في الحساب، بينها 4dp، ارتفاع كل شريط 3dp وزواياه مستديرة. الخلفية أبيض بشفافية 35%، والتعبئة أبيض.
   - القصص **السابقة** للحالية: ممتلئة 100%.
   - **الحالية**: تمتلئ تدريجياً.
   - **التالية**: فارغة.
2. **الرأس** (تحت الأشرطة): شعار الحساب دائري 32dp، الاسم بخط 14 وزن 600 أبيض، وزر إغلاق `×` (حجم 30) في الطرف.
3. **الوسائط** تملأ الشاشة بنمط `contain` (بدون قص). الصورة أو الفيديو.
4. **التذييل** أسفل الشاشة: تدرج من شفاف إلى أسود بشفافية 75%، مع حشوة 48 من الأعلى و16 جوانب و20 من الأسفل، ومحتواه في الوسط:
   - **النص** (إن وُجد): أبيض، خط 15، يحافظ على الأسطر الجديدة، وهامش 10 تحته. إذا كان فارغاً لا يظهر.
   - **زر الرابط** (إن وُجد `link`): انظر أدناه.

### زر الرابط

كبسولة **صفراء `#FFC519`** مع نص **كحلي `#10164C`** بخط 14 وزن بولد، حشوة أفقية 22 وعمودية 11، وظل أسود بشفافية 35% (blur 18، انزياح 6 للأسفل). بعد النص سهم صغير (شيفرون) باتجاه القراءة: يشير لليمين في LTR، ولليسار في RTL.

- النص داخل الزر: `link.name` إن وُجد، وإلا كلمة "عرض" (`View`) المترجمة.
- عند الضغط: ينكمش إلى 0.97 ثم يفتح الهدف مباشرة عبر الرابط العميق من `link.path`.
- **يغلق** القصة قبل الانتقال ولا يعود تلقائياً.

### التنقل بالضغط

- منطقتا ضغط على الجانبين، **كل منهما 35% من العرض**، وتمتدان من 70dp تحت أعلى الشاشة إلى 130dp فوق أسفلها (حتى لا تغطيا الرأس ولا زر الرابط).
- **LTR:** الجانب الأيسر = السابق، الأيمن = التالي.
- **RTL:** معكوس: الأيمن = السابق، الأيسر = التالي (كما في الموقع).
- **التالي:** القصة التالية في الحساب. في آخر قصة ينتقل إلى **أول قصة في الحساب التالي**، وإذا لم يوجد حساب تالي **يُغلق المشاهد**.
- **السابق:** القصة السابقة في الحساب. في أول قصة ينتقل إلى **أول قصة في الحساب السابق**، وإذا لم يوجد يبقى على أول قصة (أو يعيدها من البداية).
- الانتقال التلقائي عند انتهاء المدة يتبع قاعدة **التالي** نفسها.

### التوقف بالضغط المطوّل (Press and hold)

- اضغط مطوّلاً على أي مكان في القصة (**بعد 220 ملّي ثانية** من اللمس) فتتوقف: تتجمد العدّادات وأشرطة التقدم، **ويتوقف الفيديو**. عند رفع الإصبع تكمل من نفس النقطة.
- أثناء التوقف **يختفي الرأس والتذييل** (شفافية 0 خلال 0.2 ثانية)، وتبقى أشرطة التقدم ظاهرة. ويعودان عند رفع الإصبع.
- **مهم:** إذا حدث توقف مطوّل فإن رفع الإصبع **لا يُحتسب ضغطة "تالي/سابق"**. أي أن الضغطة التي انتهت بتوقف تُهمل ولا تنقل القصة. (هذا كان خللاً أصلحناه على الموقع: كانت القصة تبدأ من جديد.)
- الضغط على زر الإغلاق أو زر الرابط **لا يبدأ** التوقف.
- ألغِ التوقف أيضاً عند خروج الإصبع من المنطقة أو عند إلغاء اللمس.

### مدة القصة وشريط التقدم

- **الصورة: 5 ثوانٍ.** المدة تُحسب بعدّاد يُحدَّث **مع كل إطار رسم** (نحو 60 مرة في الثانية) ليتحرك الشريط **بسلاسة ومستمر**، وليس بقفزات. استخدم `AnimationController` بمدة 5 ثوانٍ، وعند التوقف المطوّل أوقفه (`stop`/`forward` عند الاستئناف). لا تستخدم `Timer.periodic` بفترات طويلة.
- **الفيديو:** يعمل حتى النهاية. عرض الشريط = `position / duration` ويُقرأ **كل إطار** (استمع بـ `AnimationController`/`Ticker` وليس بالاعتماد على مستمع موضع الفيديو وحده، لأنه يحدّث نحو 4 مرات في الثانية فقط وهذا سبب التقطّع). عند انتهاء الفيديو: القصة التالية.
- فشل تحميل الفيديو (`error`): انتقل مباشرة إلى القصة التالية.
- تشغيل الفيديو تلقائي. إذا رفض النظام التشغيل بصوت، شغّله **كاتماً** (muted).
- عند فتح قصة جديدة: أوقف عدّاد القصة السابقة وأفرغ الوسائط وأعد التعبئة (لا تتراكم مؤقتات).

### الإغلاق

- زر `×`، أو الرجوع في النظام، أو انتهاء آخر قصة في آخر حساب.
- بعد الإغلاق ارسم الشريط من جديد (حالات المشاهدة تغيّرت: بعض الحلقات صارت رمادية).

### منع التداخل

- لا تسمح بتحديد النص ولا بالسحب على الوسائط، ولا بقائمة النقر المطوّل الخاصة بالنظام على الصورة.
- حالة الجهاز الأفقي: يكفي الإبقاء على الصندوق بعرض أقصى 420.

---

## 6. الألوان (تطابق هوية كابيتانو)

| الاستخدام | اللون |
|---|---|
| كحلي الهوية (نص، شارة العدد، نص زر الرابط) | `#10164C` |
| أصفر الهوية (شارة العدد، خلفية زر الرابط، بداية التدرج) | `#FFC519` |
| نهاية تدرج الحلقة (قد تُلغى) | `#F0641E` |
| حلقة مشاهَدة | `#D9DCE3` |
| اسم حساب مشاهَد | `#8A8F9C` |

ضعها كلها في ملف ثوابت واحد.

---

## 7. نص الإتاحة (Accessibility)

- كل دائرة: وصف دلالي باسم الحساب وعدد قصصه.
- أزرار الإغلاق والسابق والتالي: لها وصف دلالي.
- إذا كان النظام يطلب تقليل الحركة: ألغِ تكبير الدائرة عند الضغط.

---

## 8. جانب البائع (إن كان تطبيق البائع منفصلاً)

الإدارة موجودة بالفعل ضمن `api/v2/seller/stories` ومشروحة في `docs/api/stories.md`:
- إنشاء وتعديل وإعادة نشر وإخفاء وحذف وترتيب قصص المتجر، ورفع الملف أولاً عبر `v2/seller/file/upload`.
- الصور ≤ 5 ميغابايت (jpg/png/webp/gif)، الفيديو ≤ 20 ميغابايت (mp4/webm).
- ملاحظة جودة: الرفع من مكتبة الملفات في الموقع يُصغّر الصور (الجودة 75 وحدّ أقصى للأبعاد). إذا أردتم أعلى جودة للقصص، يُنفَّذ إصلاح لاحق على السيرفر، ولا يغيّر شيئاً في التطبيق.

---

## 9. قائمة اختبار القبول

1. دائرة لكل حساب بترتيب `data`، والشريط مخفي تماماً عندما `data` فارغة.
2. حلقة مقاطع بعدد القصص، قصة واحدة = دائرة كاملة، وقصتان أو أكثر = أقواس بفراغات.
3. القوس يتحول إلى رمادي بعد مشاهدة قصته فقط (لا الحساب كله دفعة واحدة).
4. شارة العدد تظهر فقط مع أكثر من قصة.
5. ضغط جانبي: سابق/تالي بالاتجاه الصحيح في العربية والإنجليزية.
6. الضغط المطوّل يوقف الصورة والفيديو وأشرطة التقدم ويخفي الرأس والتذييل، ورفع الإصبع بعده لا ينقل القصة.
7. شريط التقدم يتحرك بسلاسة للصور والفيديو.
8. آخر قصة في آخر حساب تُغلق المشاهد، وأول قصة في حساب تالٍ يُفتح عند انتهاء السابق.
9. زر الرابط أصفر بنص كحلي وسهم، يفتح الهدف الصحيح، ويختفي عندما `link = null`.
10. فيديو يفشل تحميله ينتقل للتالي، وصوت مرفوض يُشغَّل كاتماً.
11. المقارنة جنباً إلى جنب مع الموقع على نفس الحسابات: الشكل والتوقيت متطابقان.
