واجهة JavaScript bridge API
ودجت الشاشة الرئيسية
بطاقة Android صغيرة على الشاشة الرئيسية تملؤها صفحتك بعنوان وقيمة وبضعة سطور - AppMint.widget.
AppMint.widget دالة مساعدة (helper)#
AppMint.widget.set(card) / update(partial) / clear() / placedCount() / requestPin()
مثال
يملأ ودجت الشاشة الرئيسية لتطبيقك: بطاقة أصلية بعنوان وقيمة كبيرة واحدة وتعليق وحتى اثني عشر سطرًا (لكل سطر لونه وعلامته ومسار النقر إن شئت) وألوانك ومسار يفتحه النقر. عندما يكبّر المستخدم الودجت يعرض سطورًا أكثر، ومع textSize: 'auto' يكبر النص ليملأه - ما دام كل سطر يتسع، فالسطر الطويل يأخذ صفين بدل أن يُقطع. لا يستطيع الودجت عرض HTML - أنت ترسل له النص.
القيمة المُرجَعة: كل دالة تُرجع Promise. set(card) ← {ok: true, placed} (placed = عدد النسخ على الشاشة الرئيسية) أو {ok: false, error}. clear() ← undefined. placedCount() ← رقم. requestPin() ← true إن فُتحت نافذة «إضافة» في شاشة التشغيل. المتطلبات: لا شيء - كل بناء يحتوي على الودجت.
اضبط البطاقة - مرة عند البدء، ومجددًا كلما تغيّرت البيانات التي تعرضها:
async function updateWidget(events) {
if (!(window.AppMint && AppMint.widget)) return; // in a browser
var r = await AppMint.widget.set({
title: 'Today',
value: events.length + ' events',
lines: events.slice(0, 6).map(function (ev) { return ev.time + ' ' + ev.name; }),
route: '/calendar', // a tap opens the app at this route
bg: '#0E7C86', // background
ink: '#FFFFFF', // title and lines
accent: '#FFE08A' // the big value
});
if (!r.ok) console.log('Widget not updated: ' + r.error);
}جدول أعمال يملأ الودجت - خلفية شفافة ونص يكبر عند تغيير حجم الودجت وسطور ملونة بعلامة وسطر متأخر بالأحمر، والنقر على سطر يفتح ذلك اليوم:
AppMint.widget.set({
title: 'Agenda',
value: 'Sun 18 Oct',
caption: '5 events, 1 overdue', // small text under the value
bg: 'rgba(20, 38, 74, 0.6)', // 60 % opaque — the wallpaper shows through
ink: '#FFFFFF', accent: '#FFE08A',
textSize: 'auto', // 'auto' | 'small' | 'normal' | 'large' | 'xl' | a number 0.6–3
align: 'start', // 'start' (left) | 'center' | 'end'
lines: [
{ text: '09:00 Stand-up', marker: '#29B6F6', route: '/#/day/2026-10-18' },
{ text: '10:30 Dentist, Dr Rao', marker: '#43A047', route: '/#/day/2026-10-18' },
{ text: 'Overdue: renew the car insurance', color: '#FF5252', marker: '#FF5252', bold: true, route: '/#/tasks/overdue' },
{ text: 'Tue 20: Team lunch', route: '/#/day/2026-10-20' },
'Wed 21: Gym' // a plain string is still a line
],
route: '/#/today' // a tap anywhere else on the card
});السطر نص أو { text, color?, marker?, bold?, wrap?, route? }؛ marker تضع ● ملونة قبل النص، وwrap: true على سطر (أو على البطاقة) تجعل السطر الطويل يأخذ صفين بأي حجم نص؛ ومع textSize: 'auto' تفعل السطور ذلك أصلًا، وwrap: false تبقي السطر في صف واحد. تظهر دائمًا حتى ست سطور؛ والسطور من السابع إلى الثاني عشر تظهر عندما يكون الودجت طويلًا بما يكفي.
ودجتات أغنى - قائمة ورقم كبير مع شريط تقدم وصورة خلفية وأزرار. تُرسم البطاقة بهذه الطريقة فقط عندما تحتوي على أحد هذه المفاتيح؛ والبطاقة بدونها تبدو تمامًا كما كانت:
AppMint.widget.set({
layout: 'list', // 'list' | 'progress' | 'card'
title: 'Today', subtitle: 'Tue 30 Sep',
items: [ // up to 5 rows; a row tap opens its own route
{ text: '09:00 Dentist', secondary: 'Dr Rao, 2nd floor', image: 'img/tooth.png', route: '/event/41' },
{ text: '13:30 Lunch with Sam', image: 'https://example.com/sam.jpg', route: '/event/42' }
],
buttons: [{ label: 'Add', route: '/new' }, { label: 'Week', route: '/week' }], // up to 2
bgImage: 'img/widget-bg.jpg', bg: '#101820', ink: '#FFFFFF', accent: '#FFB000'
});
AppMint.widget.set({
layout: 'progress', title: 'Steps', value: '8 214',
progress: { value: 8214, max: 10000, label: '82% of today's goal' }
});
AppMint.widget.update({ value: '8 950', progress: { value: 8950, max: 10000 } }); // change only these keysخطك الخاص - ضع ملف الخط في تطبيقك (.ttf أو .otf، مثلًا من «Download family» في Google Fonts) وأعطِ مساره كما تفعل مع <img>؛ فيُرسم كل نص على البطاقة به:
AppMint.widget.set({
title: 'Score', value: '1 250',
font: 'fonts/RocknRollOne-Regular.ttf', // a file in your app, an https address, or a data: URL
lines: ['Level 7', { text: 'New record!', bold: true }]
}).then(function (r) { if (!r.ok) console.log(r.error); }); // e.g. "font file not found in the app: …"اسم خط مثل 'RocknRoll One' لا يمكن أن يعمل: الشاشة الرئيسية هي التي ترسم الودجتات، ولا تملك إلا خطوط الهاتف نفسه - وset() تُجيب {ok: false, error} موضحةً ذلك. هذه العائلات المدمجة لا تحتاج ملفًا: 'serif' و'monospace' و'sans-serif-condensed' و'sans-serif-light' و'sans-serif-medium' و'sans-serif-black' و'casual' و'cursive' و'sans-serif-smallcaps'. ملفات WOFF / WOFF2 تُرفَض (لا يستطيع Android قراءتها) - استخدم .ttf. مع ملف خط تُرسم البطاقة كصورة بحجم الودجت وتُعاد رسمها عند تغيير حجمه؛ وتعمل النقرات على السطور والصفوف والأزرار وقارئات الشاشة كما في أي بطاقة.
التحديث والتطبيق مغلق - أعطِ عنوان https يُرجع نفس JSON؛ يجلبه Android كل refreshMinutes (30 أو أكثر، والافتراضي 60) ما دام ودجت على الشاشة الرئيسية، حتى بعد إعادة التشغيل:
AppMint.widget.set({
title: 'Orders', value: '…',
refreshUrl: 'https://api.example.com/widget.json?user=42',
refreshMinutes: 30
}).then(function (r) { console.log('refreshing every ' + r.refreshMinutes + ' min'); });
// widget.json answers e.g. {"title":"Orders","value":"12 new","layout":"list","items":[{"text":"#1042 paid"}]}الصور: مسار داخل تطبيقك (يُحلّ مثل <img src> في الصفحة)، أو عنوان https، أو رابط data:image/…؛ تُصغَّر وتُحفظ على الهاتف. في JSON التحديث يعني المسار النسبي ملفًا من التطبيق. refreshUrl: '' توقف التحديث؛ وclear() توقفه أيضًا. يقرر Android اللحظة الدقيقة لكل تحديث (فهو يجمّع أعمال الخلفية لتوفير البطارية).
زر «إضافة إلى الشاشة الرئيسية»:
async function addWidget() {
if (!(window.AppMint && AppMint.widget)) return;
if ((await AppMint.widget.placedCount()) > 0) { alert('The widget is already on your home screen'); return; }
var opened = await AppMint.widget.requestPin();
if (!opened) alert('Touch and hold your home screen, tap Widgets, and pick this app.');
}أفرغه (مثلًا بعد تسجيل خروج المستخدم):
if (window.AppMint && AppMint.widget) AppMint.widget.clear(); // shows "Nothing to show yet"النقر على الودجت يفتح التطبيق مع appmint-shortcut://<package><route> كرابط تشغيل - اقرأه بـ WebToApk.getLaunchUrl() عند البدء وبحدث appmint:deep-link أثناء التشغيل:
window.addEventListener('appmint:deep-link', function (e) {
var m = /^appmint-shortcut:\/\/[^\/?#]*([^#]*)/i.exec(e.detail.url);
if (m) goTo(m[1] || '/');
});الألوان: #RRGGBB و#RGB وrgb(20, 38, 74) وrgba(20, 38, 74, 0.6) (وأيضًا rgb(20 38 74 / 60%)) وtransparent وأسماء بسيطة مثل red. الست عشري المكوّن من ثمانية أرقام بترتيب Android، وقناة الشفافية أولًا: #AARRGGBB. #9914264A شفافيته 60 %؛ أما #14264A99 في CSS فتُقرأ هنا بقناة شفافية 14 (8 %)، أي بطاقة شبه غير مرئية. للخلفيات الشفافة يتجنب rgba() هذا الالتباس. ألوان الخطوط (color وmarker) تقبل الصيغ نفسها؛ لون خط خاطئ يُرفَض بالخطأ {ok: false, error: 'lines[2].color is not a colour'}، ولون بطاقة خاطئ يستخدم اللون الافتراضي.
ملاحظات: جميع حقول البطاقة اختيارية: title الفارغ يعرض اسم التطبيق؛ وvalue أو caption أو السطور الفارغة تُخفى؛ والسطور بعد الثاني عشر تُحذف. textSize وalign يسريان على كل نص في البطاقة؛ وقيمة غير معروفة تُرفَض بـ {ok: false, error}. البطاقة بدون المفاتيح الجديدة (caption وtextSize وalign وwrap والسطور المنسّقة) تبدو تمامًا كما كانت. يحتفظ الودجت بآخر بطاقة حتى بعد إعادة التشغيل، ولا يتغير إلا عندما تستدعي صفحتك set() مجددًا. requestPin() تحتاج Android 8+ وشاشة تشغيل تدعمها. يمكن للتطبيقات المبنية بالذكاء الاصطناعي استخدام widget من @/lib/appmintNative. السطور المنسّقة وcaption وtextSize وalign وwrap وrgba() تحتاج تطبيقًا مبنيًا بـ AppMint 1.11.38 أو أحدث - أعد بناء التطبيق قبل إرسال كائنات السطور، لأن البناء الأقدم سيعرضها كنص خام. font تحتاج تطبيقًا مبنيًا بـ AppMint 1.11.41 أو أحدث (البناء الأقدم يتجاهلها). تطبيقات وضع الموقع: النقر يفتح المسار على موقعك - /#/today يحمّل https://<your site>/#/today - وما زالت getLaunchUrl() تُرجع رابط appmint-shortcut://.
widgetClear الجسر (bridge)#
window.WebToApk.widgetClear()
يزيل البطاقة؛ فتعود الودجتات الموضوعة إلى حالتها الفارغة.
مثال
طبقة داخلية خلف AppMint.widget.clear() - تزيل بطاقة الودجت؛ فيعود كل ودجت موضوع إلى «لا شيء لعرضه بعد».
يعيد: لا شيء. يتطلب: لا شيء.
الواجهة العامة أولًا:
async function onSignOut() {
if (window.AppMint && AppMint.widget) await AppMint.widget.clear();
}الاستدعاء الخام:
if (window.WebToApk && typeof window.WebToApk.widgetClear === 'function') {
window.WebToApk.widgetClear();
}اربط الودجت بمفتاح «إظهار على الشاشة الرئيسية» في إعداداتك - إيقافه يفرّغ الودجت وتشغيله يملؤه من جديد:
document.getElementById('widget-toggle').onchange = async function (e) {
if (!(window.AppMint && AppMint.widget)) return;
if (e.target.checked) {
await AppMint.widget.set(buildWidgetCard()); // your own card builder
} else {
await AppMint.widget.clear();
}
};بعد حذف الحساب، أفرغ الودجت وأخبر المستخدم إن كانت نسخة ما لا تزال على شاشته الرئيسية:
function afterAccountDeleted() {
var W = window.WebToApk;
if (!W || typeof W.widgetClear !== 'function') return;
W.widgetClear();
if (W.widgetPlacedCount() > 0) {
showMessage('Your widget is now empty. You can remove it from your home screen.');
}
}ملاحظات: يبقى الودجت على الشاشة الرئيسية؛ ويُزال محتواه فقط. استدعِ AppMint.widget.set() لملئه من جديد.
widgetPlacedCount الجسر (bridge)#
window.WebToApk.widgetPlacedCount(): Int
عدد نسخ الودجت التي وضعها المستخدم على الشاشة الرئيسية.
مثال
طبقة داخلية خلف AppMint.widget.placedCount() - عدد نسخ الودجت الخاص بك التي وضعها المستخدم على الشاشة الرئيسية.
القيمة المُرجَعة: رقم، بشكل متزامن (0 عند عدم وجود أي نسخة). المتطلبات: لا شيء.
الواجهة العامة أولًا - اعرض زر «إضافة ودجت» فقط عندما لا يكون الودجت موضوعًا بعد:
async function showAddWidgetButton() {
var btn = document.getElementById('add-widget');
if (!(window.AppMint && AppMint.widget)) { btn.hidden = true; return; }
btn.hidden = (await AppMint.widget.placedCount()) > 0;
}الاستدعاء الخام:
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
var n = window.WebToApk.widgetPlacedCount();
console.log('Widgets on the home screen: ' + n);
}افحص مجددًا في كل مرة يعود فيها المستخدم من شاشة التشغيل، واشكره عندما يصبح الودجت موجودًا:
document.addEventListener('visibilitychange', async function () {
if (document.visibilityState !== 'visible') return;
if (!(window.AppMint && AppMint.widget)) return;
var n = await AppMint.widget.placedCount();
document.getElementById('widget-promo').hidden = n > 0;
if (n > 0) document.getElementById('widget-thanks').hidden = false;
});اعرض حالة الودجت في صفحة الإعدادات بالاستدعاء الخام المتزامن:
var status = document.getElementById('widget-status');
if (window.WebToApk && typeof window.WebToApk.widgetPlacedCount === 'function') {
var n = window.WebToApk.widgetPlacedCount();
status.textContent = n === 0 ? 'Not on your home screen'
: n === 1 ? 'On your home screen' : 'On your home screen ' + n + ' times';
} else {
status.textContent = 'Available in the installed app';
}ملاحظات: افحصه مجددًا عندما يعود التطبيق إلى المقدمة (visibilitychange)، لأن المستخدم يضيف الودجت من شاشة التشغيل خارج تطبيقك.
widgetRequestPin الجسر (bridge)#
window.WebToApk.widgetRequestPin(): Boolean
يفتح نافذة «إضافة إلى الشاشة الرئيسية» في شاشة التشغيل للودجت (Android 8+). تُرجع false حيث لا تستطيع شاشة التشغيل ذلك.
مثال
طبقة داخلية خلف AppMint.widget.requestPin() - تفتح نافذة «إضافة إلى الشاشة الرئيسية» في شاشة التشغيل للودجت الخاص بك.
القيمة المُرجَعة: true إن فُتحت النافذة، وfalse حيث لا تستطيع شاشة التشغيل ذلك (بشكل متزامن). المتطلبات: Android 8+ وشاشة تشغيل تدعم تثبيت الودجتات.
الواجهة العامة أولًا، من نقرة على زر:
document.getElementById('add-widget').onclick = async function () {
if (!(window.AppMint && AppMint.widget)) return;
var opened = await AppMint.widget.requestPin();
if (!opened) {
alert('Touch and hold your home screen, tap Widgets, then pick this app.');
}
};الاستدعاء الخام:
if (window.WebToApk && typeof window.WebToApk.widgetRequestPin === 'function') {
if (!window.WebToApk.widgetRequestPin()) showManualSteps();
}املأ البطاقة أولًا ثم افتح النافذة، ليعرض الودجت محتوى حقيقيًا لحظة وصوله:
document.getElementById('add-widget').onclick = async function () {
if (!(window.AppMint && AppMint.widget)) return;
await AppMint.widget.set({ title: 'Streak', value: '12 days', lines: ['Best 30'], route: '/streak' });
if (!(await AppMint.widget.requestPin())) {
document.getElementById('widget-help').hidden = false; // manual steps
}
};اعرف هل وافق المستخدم: قارن عدد النسخ الموضوعة قبل النافذة وبعد عودته:
document.getElementById('add-widget').onclick = function () {
var W = window.WebToApk;
if (!W || typeof W.widgetRequestPin !== 'function') return;
var before = W.widgetPlacedCount();
if (!W.widgetRequestPin()) { showManualSteps(); return; }
document.addEventListener('visibilitychange', function check() {
if (document.visibilityState !== 'visible') return;
document.removeEventListener('visibilitychange', check);
if (W.widgetPlacedCount() > before) showToast('Widget added');
});
};ملاحظات: true تعني فقط أن النافذة فُتحت - ما زال بإمكان المستخدم إلغاؤها. استخدم AppMint.widget.placedCount() لاحقًا لمعرفة هل أُضيف. املأ البطاقة بـ AppMint.widget.set() أولًا، كي لا يكون الودجت الجديد فارغًا.
widgetSet الجسر (bridge)#
window.WebToApk.widgetSet(cardJson: String): String
يحفظ بطاقة الودجت [cardJson] `{title?, value?, lines?, route?, bg?, ink?, accent?}` ويعيد رسم كل ودجت موضوع. يُرجع `{ok, placed}`.
مثال
طبقة داخلية خلف AppMint.widget.set(card) - تستخدم الصفحات تلك الدالة المساعدة لملء ودجت الشاشة الرئيسية.
القيمة المُرجَعة: نص JSON هو {"ok":true,"placed":<number>} أو {"ok":false,"error":"…"} (بشكل متزامن). المتطلبات: لا شيء - كل بناء يحتوي على الودجت.
الواجهة العامة أولًا:
if (window.AppMint && AppMint.widget) {
AppMint.widget.set({ title: 'Steps', value: '8 214', lines: ['Goal 10 000'], route: '/' })
.then(function (r) { if (!r.ok) console.log(r.error); });
}يأخذ الاستدعاء الخام البطاقة كنص JSON ويُرجع نص JSON:
if (window.WebToApk && typeof window.WebToApk.widgetSet === 'function') {
var card = { title: 'Steps', value: '8 214', lines: ['Goal 10 000'], route: '/',
bg: '#15161A', ink: '#FFFFFF', accent: '#22D3EE' };
var r = JSON.parse(window.WebToApk.widgetSet(JSON.stringify(card)));
console.log(r.ok ? 'on ' + r.placed + ' home screen(s)' : r.error);
}أبقِ الودجت متوافقًا مع قائمة مهام - عدد المهام المفتوحة كقيمة، وأول ست مهام كسطور، والنقر يفتح شاشة المهام:
async function syncTaskWidget(tasks) {
if (!(window.AppMint && AppMint.widget)) return;
var open = tasks.filter(function (t) { return !t.done; });
var r = await AppMint.widget.set({
title: 'To do',
value: open.length ? open.length + ' left' : 'All done',
lines: open.slice(0, 6).map(function (t) { return '• ' + t.title; }),
route: '/tasks'
});
if (!r.ok) console.warn('Widget not updated: ' + r.error);
}اكتب أحدث حالة عندما يغادر المستخدم التطبيق؛ الاستدعاء الخام متزامن، فينتهي قبل إيقاف الصفحة مؤقتًا:
document.addEventListener('visibilitychange', function () {
if (document.visibilityState !== 'hidden') return;
if (!window.WebToApk || typeof window.WebToApk.widgetSet !== 'function') return;
var s = getTodayStats(); // your app's own numbers
window.WebToApk.widgetSet(JSON.stringify({
title: 'Today', value: s.spent + ' spent',
lines: ['Budget ' + s.budget, 'Left ' + (s.budget - s.spent)], route: '/budget'
}));
});ملاحظات: مفاتيح البطاقة: title وvalue وcaption وlines (حتى اثني عشر؛ والسطر نص أو {text, color, marker, bold, wrap, route}) وroute (تُضاف / إن غابت) وbg وink وaccent وtextSize وalign وwrap وfont (ملف .ttf/.otf في تطبيقك أو عائلة نظام مثل serif)، إضافة إلى مفاتيح التخطيط الغني - راجع AppMint.widget. الألوان: #RRGGBB و#AARRGGBB (الشفافية أولًا) وrgba(). يُعاد رسم كل ودجت موضوع فورًا. قيمة placed هي 0 عندما لم يضف المستخدم الودجت بعد - وتُحفظ البطاقة مع ذلك وتظهر عندما يضيفه.
widgetUpdate الجسر (bridge)#
window.WebToApk.widgetUpdate(partialJson: String): String
يدمج [partialJson] في بطاقة الودجت المحفوظة (المفاتيح المذكورة تستبدل والبقية تبقى) ويعيد الرسم. يُرجع `{ok, placed}`.
مثال
يغيّر جزءًا من ودجت الشاشة الرئيسية: المفاتيح التي تمررها تستبدل المحفوظة وكل ما عداها يبقى. تستدعي الصفحات عادةً AppMint.widget.update(partial).
القيمة المُرجَعة: نص JSON هو {"ok":true,"placed":<n>} أو {"ok":false,"error":"…"}. المتطلبات: لا شيء - كل بناء يحتوي على الودجت.
// Only the number and the progress bar change; title, rows and buttons stay as they were.
if (window.AppMint && AppMint.widget) {
AppMint.widget.update({ value: '7 420', progress: { value: 7420, max: 10000, label: '74% of goal' } });
}
// Raw form:
if (window.WebToApk && typeof window.WebToApk.widgetUpdate === 'function') {
var r = JSON.parse(window.WebToApk.widgetUpdate(JSON.stringify({ subtitle: 'Updated ' + new Date().toLocaleTimeString() })));
if (!r.ok) console.warn(r.error);
}امسح البطاقة عند تسجيل الخروج وأبلغ عن خطأ من update:
async function onSignOut() {
if (!(window.AppMint && AppMint.widget)) return;
await AppMint.widget.clear();
}
async function bumpCount(n) {
if (!(window.AppMint && AppMint.widget)) return;
const r = await AppMint.widget.update({ value: String(n) });
if (!r.ok) console.warn('Widget not updated: ' + r.error);
}اجمعه مع فحص الوضع: حدّث البطاقة فقط ما دامت نسخة موجودة على الشاشة الرئيسية.
async function refreshWidget(stats) {
if (!(window.AppMint && AppMint.widget)) return;
if (await AppMint.widget.placedCount() === 0) return;
AppMint.widget.update({ value: stats.total + ' today', subtitle: 'Updated ' + new Date().toLocaleTimeString() });
}ملاحظات: عند عدم وجود بطاقة محفوظة، تتصرف update() مثل set(). راجع AppMint.widget لجميع مفاتيح البطاقة.