از اینجا شروع کنید
نوشتن HTML برای اپلیکیشن شما
آنچه runtime اپلیکیشن در اختیار صفحهٔ شما میگذارد - مخاطبان، SMS، اعلانها، اطلاعات دستگاه، بیومتریک - و قواعد فراخوانی درست آن
1قانون ۱ - همیشه ابتدا بررسی کنید#
window.WebToApk فقط داخل اپلیکیشن ساختهشدهٔ شما وجود دارد. در مرورگر وجود ندارد.
پس همیشه پیش از فراخوانی بررسی کنید. اگر نکنید، صفحهٔ شما همهجا جز داخل اپلیکیشن خراب میشود.
بعضی چیزها اصلاً به بررسی نیاز ندارند: navigator.vibrate()، speechSynthesis و Notification از قبل کار میکنند - اپلیکیشن آنها را برایتان وصل میکند. فقط از کد معمول وب استفاده کنید.
// GOOD — safe everywhere
if (window.WebToApk) {
WebToApk.playClick();
}
// BAD — this breaks your page in a browser
WebToApk.playClick();2قانون ۲ - بعضی پاسخها دیرتر میرسند#
خواندن مخاطبان، SMS یا تاریخچهٔ تماس زمان میبرد. پس اینها فوراً جواب نمیدهند. اگر میدادند، اپلیکیشن شما قفل میکرد.
به فراخوانی یک نام (id) میدهید. پاسخ بعداً بهصورت یک رویداد میرسد. شما به آن رویداد گوش میدهید.
نام رویدادها: appmint:contacts, appmint:calllog, appmint:sms, appmint:sms-received, appmint:notification-action.
// 1. Listen for the answer
window.addEventListener('appmint:contacts', function (e) {
if (e.detail.error) {
alert('Problem: ' + e.detail.error);
return;
}
console.log(e.detail.contacts);
});
// 2. Ask the question ('my1' is any name you choose)
WebToApk.listContacts('my1', 50, 0);3قانون ۳ - دو کلید باید روشن باشند#
یک قابلیت گوشی به دو چیز نیاز دارد:
- هنگام ساخت اپلیکیشن آن را در مرحلهٔ ۴ (Access) روشن کردهاید. اگر نه، خطای 'not_enabled' میگیرید و کاربر چیزی نمیبیند.
- کاربر روی گوشی بله گفت. اگر نه گفت، خطای 'permission_denied' میگیرید.
همیشه هر دو خطا را مدیریت کنید تا کاربر بداند چه شد.
راه آسانتر: pickContact() و composeSms() اصلاً به مجوز نیاز ندارند. وقتی به کارتان میآیند از آنها استفاده کنید - همان کار را با دردسر کمتر انجام میدهند.
window.addEventListener('appmint:contacts', function (e) {
if (e.detail.error === 'not_enabled') {
alert('Turn on Contacts when you build the app');
} else if (e.detail.error === 'permission_denied') {
alert('Please allow contacts access');
} else {
showContacts(e.detail.contacts);
}
});4اعلانهایی با تصویر و دکمه#
notify() فهرستی از گزینهها میگیرد. میتوانید تصویر بزرگ، آیکون، دکمه و نوار پیشرفت اضافه کنید.
channel میتواند باشد: urgent، default، quiet یا ongoing.
برای اعلانی که کاربر نتواند کنار بزند از ongoing:true استفاده کنید. توجه: کاربر همچنان میتواند آن را از تنظیمات Android حذف کند. هیچ اپلیکیشنی نمیتواند اعلانی بسازد که حذفش غیرممکن باشد.
با tag به آن نام بدهید. ارسال دوباره با همان tag بهجای افزودن اعلان جدید، قبلی را جایگزین میکند.
WebToApk.notify(JSON.stringify({
title: 'Order shipped',
body: 'Arriving Tuesday',
channel: 'urgent',
image: 'https://mysite.com/box.jpg',
actions: [{ id: 'track', label: 'Track' }],
tag: 'order-482'
}));
// Know which button the user pressed
window.addEventListener('appmint:notification-action', function (e) {
if (e.detail.actionId === 'track') showTracking();
});5اطلاعات گوشی و اثر انگشت#
getDeviceInfo() به هیچ مجوزی نیاز ندارد. نسخهٔ Android، شناسهٔ نسخه (buildId)، مدل گوشی، اندازهٔ صفحه، سطح باتری و موارد دیگر را میدهد.
دربارهٔ شناسهٔ دستگاه: IMEI یا شمارهٔ سریال وجود ندارد. Android از Android 10 این را برای همهٔ اپلیکیشنها مسدود کرده است. بهجای آن از getInstallId() استفاده کنید - یک شناسهٔ ثابت برای همین نصب که پس از بهروزرسانی هم تغییر نمیکند.
var info = JSON.parse(WebToApk.getDeviceInfo());
info.android.release; // "14"
info.android.buildId; // "TQ3A.230805.001" <- version ID
info.hardware.model; // "Pixel 7"
info.runtime.batteryLevel; // 82
// Fingerprint
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth['unlock'] = function (json) {
var r = JSON.parse(json);
if (r.ok) showMyApp();
};
WebToApk.authenticateBiometricEx('unlock', JSON.stringify({
title: 'Unlock',
allowDeviceCredential: true
}));6صفحهتان را تمامصفحه کنید#
وقتی Fullscreen را روشن میکنید، اپلیکیشن نوارهای Android را برایتان پنهان میکند. اما این CSS را اضافه کنید تا محتوایتان زیر ناچ گوشی پنهان نشود.
مهم: Fullscreen نوارهای ANDROID (ساعت، باتری، دکمههای بازگشت) را پنهان میکند. نوار رنگی بالای خود اپلیکیشن کلید دیگری به نام «Show top bar» است. اگر پس از روشن کردن Fullscreen هنوز نواری میبینید، آن را خاموش کنید.
همچنین: اپلیکیشن خود را چرخانده هم آزمایش کنید. ناچ در حالت افقی به کنار میرود.
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
min-height: 100vh; /* old phones */
min-height: 100dvh; /* correct when bars are hidden */
}7دانلودها نام فایلی را که انتخاب کردهاید نگه میدارند#
کد دانلود معمولی شما از قبل کار میکند. وقتی صفحه فایلی را ذخیره میکند - گزارش PDF، خروجی CSV، پشتیبان - اپلیکیشن آن را میگیرد و برگهٔ «Save as…» در Android را با نام فایل شما از قبل پر باز میکند.
این همهٔ روشهای رایج را پوشش میدهد: <a download="...">، a.click() از کد، html2pdf / jsPDF، FileSaver.js saveAs() و window.open() روی blob URL. یک شیء File نام خودش را همراه دارد.
اگر ترجیح میدهید بهجای ساخت تگ <a> مستقیم بخواهید، از این استفاده کنید AppMint.downloadFile().
یک نکته: نام فقط در جاوااسکریپت صفحهٔ شما وجود دارد - Android خودش آن را نمیبیند. پس هر بار download= را تنظیم کنید (یا نام بدهید)، وگرنه فایل با نام «download» ذخیره میشود.
// The usual way — saves as Site_Report_2026-08.pdf
const blob = await html2pdf().from(el).outputPdf('blob');
const a = document.createElement('a');
a.download = 'Site_Report_2026-08.pdf';
a.href = URL.createObjectURL(blob);
a.click();
// Or ask directly
AppMint.downloadFile(base64String, 'Ledger_Q3.csv', 'text/csv');
AppMint.saveBlob(myBlob, 'Backup.json');8دکمهٔ Back را مطابق انتظار اپلیکیشن خودتان کنید#
اگر اپلیکیشن شما صفحهها را با نمایش و پنهان کردن عناصر عوض میکند (بدون تغییر URL، بدون history.pushState)، دکمهٔ Back در Android این صفحهها را نمیبیند - تاریخچهاش خالی است، پس Back از هر جایی اپلیکیشن را میبندد.
یک back handler ثبت کنید و خودتان تصمیم بگیرید: وقتی خودتان کلید را مدیریت کردید (منو را بستید، یک صفحه عقب رفتید) true برگردانید، و برای اجرای رفتار عادی false برگردانید - بازگشت در تاریخچهٔ صفحه، سپس تأیید خروج، سپس خروج.
اپلیکیشنهایی که برای هر صفحه از history.pushState استفاده میکنند به این نیاز ندارند: Back از قبل تاریخچهٔ آنها را طی میکند. و نگران قفل شدن اپلیکیشن نباشید - اگر handler شما هنگ کند، Back گوشی پس از لحظهای کوتاه بهصورت بومی کار میکند.
AppMint.setBackHandler(() => {
if (isMenuOpen) { closeMenu(); return true; } // consumed
if (screen !== 'home') { goTo('home'); return true; }
return false; // nothing open — normal exit behaviour
});
// Or listen instead: e.preventDefault() consumes the press
window.addEventListener('appmint:back', e => {
if (closeTopmost()) e.preventDefault();
});9دریافت آنچه مردم به اپلیکیشن شما میفرستند - پیوندها، عکسها، ویدیوها، فایلها#
در ویزارد ساخت «Receive shares from other apps» را روشن کنید و آنچه اپلیکیشن شما میپذیرد را تیک بزنید: متن و پیوند، فایلهای داده (JSON، CSV، TXT، XML، Markdown، GPX، TCX، FIT)، عکس، ویدیو، صدا، سند (PDF، Word، Excel، PowerPoint). اپلیکیشن شما سپس دقیقاً برای همان نوعها در منوی Share هر اپلیکیشن دیگر ظاهر میشود - مثلاً اپلیکیشن یادداشت برای متن، پخشکننده برای ویدیو.
یک پیوند یا متن (اشتراک ویدیوی YouTube از اپلیکیشن YouTube پیوند آن را میفرستد) با WebToApk.getSharedText() خوانده و بهصورت appmint:shared اعلام میشود. یک فایل با AppMint.getOpenedFile() درخواست میشود - هر وقت آماده بودید بخواهید، فایل منتظر شما میماند. این برای اپلیکیشنهای React، Vue و Angular مهم است: آنها بعد از بارگذاری صفحه شروع به کار میکنند، پس اپلیکیشنی که فقط به رویداد گوش میداد فایل را از دست میداد و فقط صفحهٔ اصلی را نشان میداد.
f.kind میگوید چه رسیده: image، video، audio، document یا data. عکس، ویدیو و صدا بهصورت f.url میآیند - نشانی قابلپخش و قابلجستوجو که مستقیم در <video src>، <img src> یا <audio src> میگذارید، پس کلیپ ۳۰۰ مگابایتی هیچوقت لازم نیست در حافظه جا شود؛ f.getFile() وقتی واقعاً بایتها را میخواهید File را میگیرد. فایلهای داده و سند هم f.file دارند، بهعلاوهٔ f.text برای قالبهای متنی و f.base64 برای باینریهای کوچک بهعنوان میانبر. عکسی که با توضیح اشتراکگذاری شود توضیح را هم بهعنوان متن اشتراکی میرساند.
// A link or text someone shared
const shared = JSON.parse(WebToApk.getSharedText() || 'null');
if (shared) saveLink(shared.url || shared.text);
window.addEventListener('appmint:shared', e => saveLink(e.detail.url || e.detail.text));
// A photo, video, audio clip, document or data file — ask on startup, however late
const f = await AppMint.getOpenedFile(); // null if opened normally
if (f) {
if (f.kind === 'video') player.src = f.url; // streams, seekable
else if (f.kind === 'image') img.src = f.url;
else if (f.kind === 'document') await upload(await f.getFile()); // a real File
else importActivity(f.text ?? await f.file.text()); // data files
}
// Or listen, if you prefer
window.addEventListener('appmint:fileopen', e => handle(e.detail));10حسگرهای Bluetooth - Web Bluetooth استاندارد#
بند ضربان قلب، حسگرهای ریتم و سرعت، توانسنجها، تریینرهای هوشمند، ترازوها - هر چیزی که Bluetooth LE بفهمد.
از navigator.bluetooth استفاده کنید، دقیقاً همان کدی که برای Chrome روی دسکتاپ مینویسید. AppMint آن را داخل اپلیکیشن فراهم میکند؛ WebView ساده در Android چنین چیزی ندارد، به همین دلیل این کد در سازندگان اپلیکیشن دیگر کاری نمیکند.
انتخابگر دستگاه را خود اپلیکیشن رسم میکند، مثل مرورگر - صفحهٔ شما فقط به دستگاهی که کاربر انتخاب کرده دسترسی دارد.
مجوز Bluetooth را در مرحلهٔ ۴ (Access) روشن کنید. اگر کد شما navigator.bluetooth را ذکر کند، AppMint هنگام انتخاب ZIP آن را برایتان روشن میکند.
در دسترس نیست: getDevices()، watchAdvertisements() و requestLEScan() - با NotSupportedError رد میشوند، پس پیش از استفاده بررسی کنید.
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }]
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService('heart_rate');
const chr = await service.getCharacteristic('heart_rate_measurement');
chr.addEventListener('characteristicvaluechanged', e => {
const v = e.target.value; // a DataView
const bpm = (v.getUint8(0) & 1) ? v.getUint16(1, true) : v.getUint8(1);
document.getElementById('bpm').textContent = bpm;
});
await chr.startNotifications();
device.addEventListener('gattserverdisconnected', () => showReconnect());11بارگذاری عکس - انتخابگر عکس Android، بدون مجوز#
از یک file input معمولی استفاده کنید. چیز دیگری لازم نیست.
در اپلیکیشن، <input type="file" accept="image/*"> انتخابگر عکس Android را باز میکند: عکسهای کاربر در یک شبکه و صفحهٔ شما فقط همانهایی را میگیرد که انتخاب کرده. بدون مجوز ذخیرهسازی و بدون پنجرهٔ «Allow access to photos?». روی گوشیهای قدیمی هم کار میکند - Google Play services انتخابگر را آنجا نصب میکند.
- accept="image/*" - فقط عکس
- accept="video/*" - فقط ویدیو
- accept="image/*,video/*" - هر دو
- add multiple - کاربر میتواند چند مورد انتخاب کند
- capture="environment" اضافه کنید - بهجای آن دوربین را باز میکند (به Camera تیکخورده در مرحلهٔ ۴ (Access) نیاز دارد)
فایلهای دیگر را هم اضافه کنید (accept="image/*,.pdf") تا بهجای آن انتخابگر معمولی فایل باز شود و PDF هم قابل انتخاب باشد.
<input type="file" id="photos" accept="image/*" multiple>
<script>
document.getElementById('photos').onchange = function (e) {
for (const file of e.target.files) {
const img = document.createElement('img');
img.src = URL.createObjectURL(file);
document.body.appendChild(img);
}
};
</script>12اسکن QR code یا بارکد - با یک خط#
AppMint.scanCode() صفحهٔ اسکنر Google را باز میکند. کاربر دوربین را میگیرد و کد را پس میگیرید.
بدون مجوز دوربین. بدون عنصر video. چیزی برای ترسیم نیست. اسکنر برای کدهای کوچک خودش زوم میکند. اولین بار روی یک گوشی، Google Play services اسکنر را دانلود میکند (چند ثانیه).
پاسخ شامل value (متن داخل کد) و format (qr_code، ean_13، …) است. برای QR codeهای رایج، بخشها هم آماده میآیند: url، wifi (ssid، password)، phone، email، sms، geo، contact.
اگر ok برابر false باشد، code دلیلش را میگوید: cancelled (کاربر بست)، unavailable (Google Play services نیست یا دانلود شکست خورد)، busy (یک اسکن از قبل باز است).
AppMint.scanCode فقط داخل اپلیکیشن وجود دارد - ابتدا بررسی کنید.
async function scan() {
if (!window.AppMint || !AppMint.scanCode) return; // in a browser
const r = await AppMint.scanCode({ formats: ['qr_code'] });
if (!r.ok) return; // closed, or no scanner
console.log(r.value); // the text inside the code
if (r.url) location.href = r.url.url;
}
// Any code type: AppMint.scanCode()
// Let the user type it when it will not scan: AppMint.scanCode({ manualInput: true })13Passkeyها - ورود با اثر انگشت یا چهره، بدون رمز عبور#
اگر وبسایت شما از قبل passkey دارد، اکنون داخل اپلیکیشن هم کار میکند. WebView معمولی Android نمیتواند passkey را مدیریت کند؛ AppMint navigator.credentials را به مدیر passkey در Android وصل میکند، پس کتابخانههایی مثل SimpleWebAuthn، Hanko، Corbado یا Auth0 بدون تغییر کار میکنند.
به سروری نیاز دارید که گزینههای passkey را بسازد و پاسخ را بررسی کند (مثلاً SimpleWebAuthn در بکاند شما). passkey داخل صفحه قابل بررسی نیست.
دو چیز که یک بار باید تنظیم شود:
- وبسایت شما باید بگوید به اپلیکیشن اعتماد دارد. فایل
AppMint.passkey.assetLinks() را در https://YOUR-DOMAIN/.well-known/assetlinks.json قرار دهید. آن را از اپلیکیشنی بگیرید که از Google Play نصب شده - Play اپلیکیشن شما را با کلید خودش امضا میکند، پس فایل APK آزمایشی با کاربران Play نمیخواند. دامنه باید دقیقاً rpID سرور شما باشد (www و بدون www متفاوتاند).
- سرور شما باید origin اپلیکیشن را بپذیرد. پاسخ اپلیکیشن بهجای https://… مبدأ 'android:apk-key-hash:…' را میگوید - آن مقدار را از
AppMint.passkey.origin() بگیرید و به originهای مجاز سرورتان اضافه کنید.
خطاها: NotAllowedError = کاربر لغو کرد یا روی این گوشی passkey ندارد. InvalidStateError = این passkey از قبل وجود دارد. SecurityError = assetlinks.json اپلیکیشن را فهرست نکرده است.
Passkeyها به Android 9 یا جدیدتر با Google Play services نیاز دارند - AppMint.passkey.isAvailable() به شما میگوید. تکمیل خودکار passkey در فیلد متنی (mediation: 'conditional') در اپلیکیشنها در دسترس نیست؛ ورود را از یک دکمه شروع کنید.
// Your normal passkey code works as it is:
const cred = await navigator.credentials.get({ publicKey: options });
// Or with your server's JSON options (SimpleWebAuthn style):
const options = await (await fetch('/passkey/login-options')).json();
const answer = await AppMint.passkey.get(options);
await fetch('/passkey/login-verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(answer)
});
// Once, on the Play-installed app — copy these to your server setup:
console.log(JSON.stringify(AppMint.passkey.assetLinks(), null, 2));
console.log(AppMint.passkey.origin());14ویجت صفحهٔ اصلی - دادههایتان را روی صفحهٔ اصلی نشان دهید#
اپلیکیشن شما یک ویجت صفحهٔ اصلی دارد. کاربر آن را از فهرست ویجتهای گوشی اضافه میکند (صفحهٔ اصلی را نگه دارید، سپس Widgets)، یا شما برگهٔ Add را با AppMint.widget.requestPin() باز میکنید.
ویجت تا وقتی صفحهٔ شما آن را پر نکند خالی میماند («Nothing to show yet»). یک کارت کوچک Android است، نه صفحهٔ وب، پس نمیتواند HTML شما را نشان دهد. شما یک عنوان، یک مقدار بزرگ، تا شش خط کوتاه و رنگها میدهید.
هنگام باز شدن اپلیکیشن و هر بار که دادههای نمایشدادهشده تغییر میکند، AppMint.widget.set() را صدا بزنید. ویجت آخرین کارت را حتی پس از راهاندازی مجدد نگه میدارد. route جایی است که زدن روی ویجت اپلیکیشن شما را باز میکند.
function updateWidget() {
if (!window.AppMint || !AppMint.widget) return; // in a browser
AppMint.widget.set({
title: 'Today',
value: '3 events',
lines: ['09:00 Dentist', '13:00 Lunch with Sam', '18:30 Gym'],
route: '/',
bg: '#0E7C86', ink: '#FFFFFF', accent: '#FFE08A'
});
}
updateWidget(); // on start-up, and after every change
// An "Add to home screen" button:
async function addWidget() {
if ((await AppMint.widget.placedCount()) > 0) return;
await AppMint.widget.requestPin();
}15محافظت از کپی و منوی فشار طولانی#
دو کلید در مرحلهٔ ۳ (Integrate)، زیر Display Options. هر دو تا وقتی روشن نکنید خاموشاند.
- منوی فشار طولانی روی پیوندها و تصاویر: روی پیوند یا تصویر فشار دهید و نگه دارید تا باز، کپی، اشتراک یا ذخیره کنید. بهطور پیشفرض خاموش است، پس فشار طولانی روی پیوند کاری نمیکند. یک صفحه همچنان میتواند با لغو رویداد contextmenu منو را روی یک عنصر رد کند.
- محافظت از کپی: هیچکس نمیتواند متن اپلیکیشن شما را انتخاب، کپی یا برش کند. تایپ در فرمها همچنان کار میکند. منوی فشار طولانی را هم خاموش نگه میدارد.
صفحهٔ شما میتواند بفهمد: AppMint.copyProtection وقتی محافظت روشن است true است و وقتی خاموش است (و در مرورگر) وجود ندارد. فقطخواندنی است. دکمههای Copy خودتان را وقتی روشن است پنهان کنید، چون چیزی کپی نمیکنند.
const protectedApp = !!(window.AppMint && window.AppMint.copyProtection === true);
// A Copy button would copy nothing, so hide it
document.querySelectorAll('[data-copy-button]').forEach(btn => {
btn.hidden = protectedApp;
});16{exampleCount} مثال آماده - اینجا بازشان کنید#
کد کاری که میتوانید کپی کنید: ارسال SMS، خواندن مخاطبان، اعلان با تصویر، قفل اثر انگشت، اطلاعات گوشی، ذخیرهٔ فایلها، حسگرهای Bluetooth و بیشتر. هرکدام کوتاه و با کلمات ساده توضیح داده شده است.
داخل AppMint باز میشود. میتوانید در آن جستوجو کنید و هر بلوک کد دکمهٔ Copy دارد.
همین صفحه «All methods» را هم دارد - همهٔ {methodCount} کاری که اپلیکیشن شما میتواند صدا بزند و هرکدام با یک مثال کارا که میتوانید کپی کنید. این فهرست از خود runtime اپلیکیشن ساخته میشود، پس همیشه دقیقاً با تواناییهای اپلیکیشن شما میخواند.
17مثالها را روی گوشی یا کامپیوترتان ذخیره کنید#
سه فایل را در Downloads/AppMint/Guides ذخیره میکند:
- Examples - {exampleCount} مثال بالا
- فهرست متدها - همهٔ {methodCount} متد
- index.html - یک صفحهٔ آزمایش آماده
صفحهٔ آزمایش دکمههایی برای اطلاعات دستگاه، ارتعاش، اعلانها و انتخابگر مخاطب دارد. آن را بهصورت اپلیکیشن بسازید تا همهچیز را در حال کار ببینید، سپس آن را به اپلیکیشن خودتان تبدیل کنید. در مرورگر عادی هم بدون مشکل باز میشود - چیزی خراب نمیشود، فقط میگوید داخل اپلیکیشن نیست.
در اپلیکیشن، دکمه این مرحله اینها را روی گوشی شما ذخیره میکند. اینجا دانلود مستقیم هستند.
AppMint-examples.md AppMint-all-methods.md index.html18ساعت زنگدار که روی صفحهٔ قفل زنگ میزند#
Alarm clock را در مرحلهٔ Access تیک بزنید. scheduleNotificationEx با alarm: true با صدای شما زنگ میزند تا کاربر آن را ببندد یا به تعویق بیندازد، حتی وقتی اپلیکیشن بسته است. تکرار میتواند روزانه، هفتگی، روزهای کاری، ماهانه یا سالانه باشد، یا روزهای هفته را انتخاب کنید.
پیش از زمانبندی از getAlarmSupport() بپرسید. پاسخ میدهد 'ready'، 'not_enabled'، 'needs_exact_alarm' یا 'needs_notification_permission'. سپس stopAlarm(id)، snoozeAlarm(id, minutes) و isAlarmRinging(id) یک زنگ در حال نواختن را کنترل میکنند.
const B = window.WebToApk;
if (B && B.getAlarmSupport && B.getAlarmSupport() === 'ready') {
B.scheduleNotificationEx('wake', JSON.stringify({
at: firstAtMs, alarm: true, title: 'Wake up', repeat: 'weekdays', sound: '/audio/alarm.mp3'
}));
}
document.getElementById('snooze').onclick = () => B.snoozeAlarm('wake', 10);19صدای سیستم#
getVolume(stream) صدای رسانه، زنگ هشدار، زنگ تماس، اعلان یا تماس را بهصورت JSON میخواند. setVolume(stream, percent, showUi) آن را تغییر میدهد. watchVolume(true) با هر تغییر رویداد appmint:volume میفرستد. بیصدا کردن صدای زنگ به دسترسی Do Not Disturb نیاز دارد: requestDndAccess().
if (window.WebToApk && WebToApk.getVolume) {
const v = JSON.parse(WebToApk.getVolume('alarm'));
if (v.percent === 0) alert('Alarm volume is off');
window.addEventListener('appmint:volume', (e) => console.log(e.detail.stream, e.detail.percent));
WebToApk.watchVolume(true);
}20سرویس پسزمینه، موقعیت پسزمینه، geofence و وظایف#
startBackgroundService صفحه، صدا و تایمرهای شما را با یک اعلان در حالی که کاربر دور است زنده نگه میدارد. requestBackgroundLocation «Allow all the time» را میخواهد. addGeofence ورود، خروج یا توقف را با اپلیکیشن بسته اعلام میکند. AppMint.background.register کد صفحهٔ خودتان را هر ۱۵ دقیقه یا بیشتر اجرا میکند. همه پس از راهاندازی مجدد گوشی دوباره شروع میشوند.
اینها به کلیدشان در ویزارد (مرحلهٔ Access) نیاز دارند؛ بدون آن فراخوانی { ok: false, reason: 'not_enabled' } پاسخ میدهد.
const r = JSON.parse(WebToApk.startBackgroundService(JSON.stringify({ title: 'Syncing', text: 'Keeping data fresh' })));
if (!r.ok) console.warn(r.reason);
if (AppMint.background.isBackgroundRun()) {
window.addEventListener('appmint:background', async (e) => {
await refreshData();
AppMint.background.done(e.detail.runId, { ok: true });
});
} else {
AppMint.background.register('refresh', { intervalMinutes: 60 });
}21گزارشهای خرابی#
Crash reports را در مرحلهٔ Integrate تیک بزنید. خطاهای مدیریتنشده برایتان ثبت میشوند. logError(message, stack) خطایی را که خودتان گرفتهاید ثبت میکند، setCrashUserId(id) گزارشها را با شناسهٔ کاربری خودتان برچسب میزند (هرگز ایمیل)، و getLastCrash() جدیدترین خرابی را بهصورت JSON یا رشتهٔ خالی برمیگرداند. گزارشها را در Update the App، سپس اپلیکیشن خود، سپس Crash reports بخوانید.
if (window.WebToApk && WebToApk.logError) {
try { risky(); } catch (err) { WebToApk.logError(err.message, err.stack || ''); }
WebToApk.setCrashUserId('u_42');
const last = WebToApk.getLastCrash(); // '' when none
}22یکپارچگی دستگاه و Play Integrity#
Device integrity checks را در مرحلهٔ Integrate تیک بزنید. getDeviceIntegrity() نشانههای root، شبیهساز و دیباگر را بهصورت JSON برمیگرداند؛ این یک هشدار است نه مدرک. برای حکمی قابلاعتماد، requestIntegrityToken(nonce, '') را صدا بزنید و توکن رویداد appmint:integrity را به سرور خودتان بفرستید تا از Google Play حکم را بپرسد.
const d = JSON.parse(WebToApk.getDeviceIntegrity());
if (d.rooted || d.emulator) alert('This phone is modified');
window.addEventListener('appmint:integrity', (e) => sendToServer(e.detail.token));
WebToApk.requestIntegrityToken(serverNonceBase64, '');23آیکونهای جایگزین اپلیکیشن و TV#
در مرحلهٔ Basic تا ۴ آیکون جایگزین اضافه کنید. setAppIcon(name) آیکون لانچر را عوض میکند ('default' برمیگرداند)، getAppIcon() آیکون فعلی را نام میبرد و getAvailableIcons() آنها را فهرست میکند. isTv() میگوید اپلیکیشن روی تلویزیون است تا چیدمان مناسب کنترل از راه دور نشان دهید.
if (window.WebToApk && WebToApk.setAppIcon) WebToApk.setAppIcon('dark');
if (window.WebToApk && WebToApk.isTv && WebToApk.isTv()) {
document.documentElement.classList.add('tv');
}24ورود با Facebook، آنالیتیکس و RevenueCat#
ورود با Facebook پشتیبانی میشود. در مرحلهٔ Integrate شناسهٔ Facebook App و Client Token را وارد کنید؛ ورود در یک تب مرورگر باز میشود و به اپلیکیشن برمیگردد. AppMint.facebook.login(['public_profile', 'email']) را صدا بزنید و توکن را به سرور خود بفرستید. AppMint.analytics (Firebase) و AppMint.facebook.logEvent (Meta App Events) فقط پس از موافقت کاربر ثبت میکنند. AppMint.revenuecat اشتراکها را با کلید عمومی Android شما مدیریت میکند.
const r = await AppMint.facebook.login(['public_profile', 'email']);
if (r.ok) await fetch('/api/login/facebook', { method: 'POST', body: JSON.stringify({ token: r.accessToken }) });
AppMint.analytics.log('share', { method: 'whatsapp' });
const offerings = await AppMint.revenuecat.getOfferings();25متن از عکسها، PDFها و ویجتها#
AppMint.textRecognition.recognize(image, { script }) متن را به خطهای لاتین، دوناگری، چینی، ژاپنی یا کرهای میخواند. AppMint.pdf.fromPage() و fromHtml(html, options) یک PDF واقعی بدون پنجرهٔ چاپ میسازند. AppMint.widget.set(card) ویجت صفحهٔ اصلی را پر میکند.
const r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
const pdf = await AppMint.pdf.fromHtml('<h1>Invoice</h1>', { fileName: 'invoice', save: 'share' });
await AppMint.widget.set({ title: 'Today', value: '3 events', route: '/calendar' });26دادههای سلامت و بیکنهای Bluetooth#
AppMint.health قدمها، ضربان قلب، خواب، وزن و ورزش را از طریق Health Connect میخواند و مینویسد؛ Health data و نوعها را در مرحلهٔ Access تیک بزنید. AppMint.beacons.startScan() بیکنهای iBeacon و Eddystone را پیدا میکند و رویدادهای appmint:beacon میفرستد.
const a = await AppMint.health.isAvailable();
if (a.available) {
await AppMint.health.requestPermissions({ read: ['steps'] });
const steps = await AppMint.health.read('steps', { start: new Date(Date.now() - 864e5), end: new Date(), aggregate: true });
}
window.addEventListener('appmint:beacon', (e) => console.log(e.detail));
await AppMint.beacons.startScan();27پلاگینهای آماده: SQLite، اعلانها، push، اسکنر، مخاطبان#
در صفحههایی که خود اپلیکیشن ارائه میدهد، window.Capacitor.Plugins شامل CapacitorSQLite (یک پایگاه دادهٔ خصوصی)، LocalNotifications، PushNotifications، CapacitorBarcodeScanner و Contacts است. ابتدا بررسی کنید پلاگین وجود دارد: در اپلیکیشن حالت Website، window.Capacitor فقط در صفحههای خود سایت هست، نه دامنههای دیگر.
const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.CapacitorBarcodeScanner) {
const s = await P.CapacitorBarcodeScanner.scanBarcode({ hint: 17 });
console.log(s.ScanResult);
}
if (P && P.LocalNotifications) await P.LocalNotifications.requestPermissions();