मुफ़्त APK बनाएँ

JavaScript ब्रिज API

appmintNative (AI से बने ऐप)

AI से बना हर प्रोजेक्ट @/lib/appmintNative से जो टाइप्ड रैपर इम्पोर्ट करता है। हर रैपर ऐप के अंदर नेटिव रास्ता चुनता है, और ब्राउज़र में वेब रास्ता या सीधा none - एक कॉल हर जगह चलती है।

appmintNative.actionSheet appmintNative#

actionSheet(title: string, options: { title: string; destructive?: boolean }[]): Promise<number | null>

उदाहरण

"इनमें से एक चुनें" वाली नीचे की शीट दिखाता है - किसी पंक्ति के एक्शन मेन्यू के लिए नेटिव कंट्रोल।

लौटाता है: Promise<number | null> - चुने गए विकल्प का इंडेक्स, या यूज़र के शीट बंद करने पर null (बैकड्रॉप पर टैप, Escape, सिस्टम का कैंसल)। शीट बंद करना सामान्य है, त्रुटि नहीं। ज़रूरत: कुछ नहीं।

import { actionSheet, haptic } from '@/lib/appmintNative';

export async function onNoteMenu(note: { id: string }, api: {
  edit(id: string): void; share(id: string): void; remove(id: string): void;
}) {
  const i = await actionSheet('Note', [
    { title: 'Edit' },
    { title: 'Share' },
    { title: 'Delete', destructive: true },
  ]);
  if (i === null) return;                 // dismissed
  if (i === 0) api.edit(note.id);
  if (i === 1) api.share(note.id);
  if (i === 2) { haptic('warning'); api.remove(note.id); }
}

इसे एक दिखाई देने वाले बटन से खोलें, और पंक्ति पर लंबे दबाव से भी:

import { useEffect, useRef } from 'react';
import { actionSheet, longPress } from '@/lib/appmintNative';

export function Row({ label }: { label: string }) {
  const ref = useRef<HTMLLIElement>(null);
  const openMenu = () => actionSheet(label, [{ title: 'Rename' }, { title: 'Delete', destructive: true }]);
  useEffect(() => (ref.current ? longPress(ref.current, () => void openMenu()) : undefined), []);
  return <li ref={ref}>{label} <button aria-label="More actions" onClick={() => void openMenu()}>⋯</button></li>;
}

Capacitor वाला रूप ActionSheet.showActions({ title?, options }) वही विकल्प लेता है और { index } देता है, खारिज करने पर -1 के साथ:

import { ActionSheet } from '@/lib/appmintNative';

const { index } = await ActionSheet.showActions({
  title: 'Photo',
  options: [{ title: 'Save' }, { title: 'Delete', destructive: true }],
});
if (index === 1) console.log('delete');   // -1 = dismissed; never test with `if (index)`

ध्यान दें:

  • Android ऐप में Capacitor ActionSheet प्लगइन सिस्टम शीट बनाता है; destructive इसकी destructive स्टाइल इस्तेमाल करता है।
  • बाकी जगह यह ऐप के पैलेट (--surface, --ink, --danger) में असली शीट है, बैकड्रॉप, Escape, शीट के अंदर फ़ोकस और सिलेक्शन हैप्टिक के साथ।
  • खाली options सूची तुरंत null के साथ resolve होती है। एक्शन की सूची के अलावा किसी भी कंटेंट के लिए इस्तेमाल करें sheet().

appmintNative.ActionSheet appmintNative#

import { ActionSheet } from '@/lib/appmintNative'

उदाहरण

appmintNative.actionSheet के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

"इनमें से एक चुनें" वाली नीचे की शीट दिखाता है - किसी पंक्ति के एक्शन मेन्यू के लिए नेटिव कंट्रोल।

लौटाता है: Promise<number | null> - चुने गए विकल्प का इंडेक्स, या यूज़र के शीट बंद करने पर null (बैकड्रॉप पर टैप, Escape, सिस्टम का कैंसल)। शीट बंद करना सामान्य है, त्रुटि नहीं। ज़रूरत: कुछ नहीं।

import { actionSheet, haptic } from '@/lib/appmintNative';

export async function onNoteMenu(note: { id: string }, api: {
  edit(id: string): void; share(id: string): void; remove(id: string): void;
}) {
  const i = await actionSheet('Note', [
    { title: 'Edit' },
    { title: 'Share' },
    { title: 'Delete', destructive: true },
  ]);
  if (i === null) return;                 // dismissed
  if (i === 0) api.edit(note.id);
  if (i === 1) api.share(note.id);
  if (i === 2) { haptic('warning'); api.remove(note.id); }
}

इसे एक दिखाई देने वाले बटन से खोलें, और पंक्ति पर लंबे दबाव से भी:

import { useEffect, useRef } from 'react';
import { actionSheet, longPress } from '@/lib/appmintNative';

export function Row({ label }: { label: string }) {
  const ref = useRef<HTMLLIElement>(null);
  const openMenu = () => actionSheet(label, [{ title: 'Rename' }, { title: 'Delete', destructive: true }]);
  useEffect(() => (ref.current ? longPress(ref.current, () => void openMenu()) : undefined), []);
  return <li ref={ref}>{label} <button aria-label="More actions" onClick={() => void openMenu()}>⋯</button></li>;
}

Capacitor वाला रूप ActionSheet.showActions({ title?, options }) वही विकल्प लेता है और { index } देता है, खारिज करने पर -1 के साथ:

import { ActionSheet } from '@/lib/appmintNative';

const { index } = await ActionSheet.showActions({
  title: 'Photo',
  options: [{ title: 'Save' }, { title: 'Delete', destructive: true }],
});
if (index === 1) console.log('delete');   // -1 = dismissed; never test with `if (index)`

ध्यान दें:

  • Android ऐप में Capacitor ActionSheet प्लगइन सिस्टम शीट बनाता है; destructive इसकी destructive स्टाइल इस्तेमाल करता है।
  • बाकी जगह यह ऐप के पैलेट (--surface, --ink, --danger) में असली शीट है, बैकड्रॉप, Escape, शीट के अंदर फ़ोकस और सिलेक्शन हैप्टिक के साथ।
  • खाली options सूची तुरंत null के साथ resolve होती है। एक्शन की सूची के अलावा किसी भी कंटेंट के लिए इस्तेमाल करें sheet().

appmintNative.addContact appmintNative#

addContact(contact: Contact): Promise<boolean>

उदाहरण

फ़ोन की एड्रेस बुक में नया संपर्क सेव करता है। सिर्फ़ Android ऐप: शेल का ब्रिज इसे सीधे सेव करता है (कोई Contacts स्क्रीन नहीं खुलती)।

लौटाता है: Promise<boolean> - सेव होने पर true, यूज़र के कॉन्टैक्ट्स की अनुमति न देने या सेव विफल होने पर false। यह Error के साथ रिजेक्ट होता है जिसका code 'not_enabled' (बिल्ड ने कॉन्टैक्ट लिखना चालू नहीं किया) या 'foreign_origin' है, और बिना जवाब के 5 मिनट बाद भी। ज़रूरत: चरण 4 (Access) में Edit Contacts स्विच - आपका कोड इसे कॉल करे तो बिल्ड इसे चालू कर देता है - और पहली बार इस्तेमाल पर यूज़र की अनुमति।

फ़ॉर्म से ग्राहक को सेव करें।

import { addContact, can } from '@/lib/appmintNative';

export async function saveCustomer(name: string, phone: string, email: string): Promise<string> {
  if (can('contactAdd') === 'none') return 'Saving contacts works in the app only.';
  try {
    const ok = await addContact({ name, phones: [phone], emails: email ? [email] : [] });
    return ok ? 'Saved to contacts' : 'Allow contacts access to save it';
  } catch (e) {
    return (e as { code?: string }).code === 'not_enabled'
      ? 'Saving contacts is not switched on in this app.'
      : 'No answer from the phone. Try again.';     // 5-minute timeout
  }
}

पहले से सेव किए गए संपर्क को छोड़ दें। searchContacts एड्रेस बुक पढ़ता है, इसलिए जोड़ने से पहले जाँचें:

import { addContact, searchContacts, can } from '@/lib/appmintNative';

export async function addIfNew(name: string, phone: string): Promise<'added' | 'exists' | 'refused' | 'app_only'> {
  if (can('contactAdd') === 'none') return 'app_only';
  const found = await searchContacts(name, 5);
  if (found.some((c) => c.phones?.includes(phone))) return 'exists';
  return (await addContact({ name, phones: [phone] })) ? 'added' : 'refused';
}

बिल्ड की गलती और मना किए जाने में फ़र्क करें। code 'foreign_origin' या 'not_enabled' के साथ आया रिजेक्शन बिल्ड की समस्या है, false यूज़र की अपनी पसंद है:

import { useState } from 'react';
import { addContact } from '@/lib/appmintNative';

export function SaveContactButton({ name, phone }: { name: string; phone: string }) {
  const [msg, setMsg] = useState('');
  async function onTap() {
    try {
      setMsg((await addContact({ name, phones: [phone] })) ? 'Saved.' : 'Contacts access was declined.');
    } catch (e) {
      const code = (e as { code?: string }).code;
      setMsg(code === 'foreign_origin' || code === 'not_enabled' ? 'This build cannot save contacts.' : 'Try again.');
    }
  }
  return <button onClick={onTap}>{msg || 'Save to contacts'}</button>;
}

ध्यान दें: यह तुरंत सेव करता है - कोई कॉन्टैक्ट एडिटर नहीं खुलता - इसलिए पहले अपनी स्क्रीन पर यूज़र से पुष्टि करवा लें। यह फ़ोन नंबर मोबाइल नंबर के रूप में और कॉन्टैक्ट को डिवाइस पर सेव करता है (किसी अकाउंट में नहीं)। कॉन्टैक्ट लिखना संवेदनशील है: इसे अपने Play Data safety फ़ॉर्म और प्राइवेसी पॉलिसी में बताएँ।

appmintNative.alarmSupport appmintNative#

alarmSupport() — @/lib/appmintNative

उदाहरण

क्या scheduleAlarm अभी अलार्म सेट कर सकता है, और अगर नहीं तो क्या कमी है।

लौटाता है: 'ready', 'not_enabled' (अलार्म क्लॉक के बिना बना), 'needs_exact_alarm' (Android 12+: सटीक अलार्म बंद हैं), 'needs_notification_permission' (Android 13+), या 'unavailable' (वेब और iOS - वहाँ अलार्म कंट्रोल न बनाएँ)। ज़रूरत: बिल्ड करते समय Access चरण (चरण 4) में 🔔 अलार्म क्लॉक।

import { alarmSupport } from '@/lib/appmintNative';

export function AlarmSetupBanner() {
  const s = alarmSupport();
  if (s === 'ready' || s === 'unavailable') return null;
  const text = {
    not_enabled: 'This app was built without alarm clocks.',
    needs_exact_alarm: 'Allow "Alarms & reminders" for this app in Settings.',
    needs_notification_permission: 'Allow notifications so you can stop the alarm.',
  }[s];
  return <p role="alert">{text}</p>;
}

रोज़ का अलार्म तभी लगाएँ जब फ़ोन तैयार हो, और न हो तो कारण बताएँ:

import { alarmSupport, scheduleAlarm } from '@/lib/appmintNative';

export function setMorningAlarm(hour: number, minute: number): string {
  const s = alarmSupport();
  if (s !== 'ready') return `Alarm not set (${s}).`;       // 'not_enabled', 'needs_exact_alarm', ...
  const first = new Date();
  first.setHours(hour, minute, 0, 0);
  if (first.getTime() <= Date.now()) first.setDate(first.getDate() + 1);
  const ok = scheduleAlarm('morning', { at: first.getTime(), title: 'Wake up', repeat: 'daily', snoozeMinutes: 5 });
  return ok ? 'Alarm set.' : 'The phone did not accept the alarm.';
}

वेब पर अलार्म स्क्रीन छिपाएँ और अलार्म खत्म होने पर प्रतिक्रिया दें, इसे इसके साथ मिलाकर onAlarm:

import { useEffect, useState } from 'react';
import { alarmSupport, onAlarm, snoozeAlarm, stopAlarm } from '@/lib/appmintNative';

export function AlarmPanel() {
  const [ringing, setRinging] = useState<string | null>(null);
  useEffect(() => onAlarm((e) => {
    if (e.action === 'open') setRinging(e.ringing ? e.id : null);
    else setRinging(null);                                  // dismiss, snooze, missed, error
  }), []);
  if (alarmSupport() === 'unavailable') return null;        // web and iOS: draw nothing
  if (!ringing) return null;
  return (
    <div role="alertdialog">
      <button onClick={() => stopAlarm(ringing)}>Stop</button>
      <button onClick={() => snoozeAlarm(ringing, 10)}>Snooze 10 min</button>
    </div>
  );
}

appmintNative.App appmintNative#

App.getInfo(): Promise<{ name, id, build, version }> / App.exitApp(): void

उदाहरण

ऐप का नाम, पैकेज id और वर्ज़न, और ऐप बंद करने का तरीका - Capacitor के App प्लगइन के रूप में।

लौटाता है: getInfo() → Promise<{ name: string; id: string; build: string; version: string }>। exitApp() → void। ज़रूरत: कुछ नहीं।

import { App } from '@/lib/appmintNative';

const info = await App.getInfo();
aboutLine.textContent = `${info.name} ${info.version}${info.build ? ` (${info.build})` : ''}`;

“बाहर निकलें” आइटम से ऐप बंद करें, जो आगे बढ़ने का इकलौता रास्ता न हो:

import { App, Dialog } from '@/lib/appmintNative';

async function quit() {
  const { value } = await Dialog.confirm({ title: 'Quit?', message: 'Unsaved changes will be lost.' });
  if (value) App.exitApp();
}

React स्क्रीन में ऐप का वर्ज़न दिखाएँ।

import { useEffect, useState } from 'react';
import { App } from '@/lib/appmintNative';

export function VersionLabel() {
  const [text, setText] = useState('');
  useEffect(() => {
    void App.getInfo().then((i) => setText(`${i.name} v${i.version}`));
  }, []);
  return <small>{text}</small>;
}

नोट: ऐप में getInfo Capacitor App प्लगइन से पढ़ता है; वह न हो तो यह शेल ब्रिज का नाम और वर्ज़न इस्तेमाल करता है (तब id और build '' होते हैं), और वेब पर खाली वर्ज़न के साथ document.title। exitApp ऐप में शेल का closeWindow इस्तेमाल करता है और वेब पर कुछ नहीं करता, इसलिए Quit को आगे बढ़ने का इकलौता रास्ता कभी न बनाएँ। pause/resume इवेंट और डीप लिंक के लिए Capacitor.Plugins.App, launchUrl() और देखें onDeepLink().

appmintNative.appLauncher appmintNative#

appLauncher.canOpen(url): Promise<boolean> / appLauncher.open(url): Promise<boolean>

उदाहरण

लिंक से दूसरा ऐप खोलता है - whatsapp://send?text=…, tel:, mailto:, geo:, या ऐसा https लिंक जिसे OS उसके ऐप को देता है - और पहले पूछ लेता है कि यह चल सकेगा या नहीं।

लौटाता है: canOpen() → Promise<boolean>। open() → Promise<boolean> (लिंक किसी ऐप ने न लिया तो false)। ज़रूरत: कुछ नहीं।

import { appLauncher } from '@/lib/appmintNative';

async function chat(phone: string, text: string) {
  const url = `whatsapp://send?phone=${phone}&text=${encodeURIComponent(text)}`;
  if (!(await appLauncher.canOpen(url))) {
    showMessage('WhatsApp is not installed on this phone.');
    return;
  }
  await appLauncher.open(url);
}

कॉल या ईमेल (जाँच की ज़रूरत नहीं; फ़ोन में इनके लिए हमेशा ऐप होता है):

import { appLauncher } from '@/lib/appmintNative';

await appLauncher.open('tel:+15551234567');
await appLauncher.open('mailto:[email protected]?subject=Order%2042');

geo: लिंक से मैप्स ऐप खोलें, और किसी ने न लिया तो बताएँ:

import { appLauncher } from '@/lib/appmintNative';

export async function showOnMap(lat: number, lng: number, label: string) {
  const ok = await appLauncher.open(`geo:${lat},${lng}?q=${lat},${lng}(${encodeURIComponent(label)})`);
  if (!ok) showMessage('No maps app could open this place.');
}

ध्यान दें: ऐप में यह Capacitor AppLauncher प्लगइन इस्तेमाल करता है। Android 11+ पर canOpen सिर्फ़ वही स्कीम देख सकता है जो ऐप घोषित करता है (https, geo, google.navigation, tel, mailto, sms, whatsapp, intent, upi); बाकी के लिए, ऐप इंस्टॉल होने पर भी यह false जवाब देता है - इसकी जगह open() कॉल करें और उसका नतीजा देखें। वेब पर कस्टम स्कीम जाँची नहीं जा सकती, इसलिए canOpen सिर्फ़ http(s) के लिए true होता है, और open लिंक पर चला जाता है।

appmintNative.appLock appmintNative#

appLock({ timeoutSec?, title?, onLocked? }) — from '@/lib/appmintNative'

उदाहरण

ऐप कुछ देर बैकग्राउंड में रहने के बाद यूज़र के लौटने पर उसे फिर लॉक कर देता है: यह फ़िंगरप्रिंट / चेहरा / PIN (unlock()) माँगता है, और प्राइवेसी स्क्रीन चालू करता है ताकि ऐप स्विचर आपका कंटेंट न दिखाए।

लौटाता है: एक फ़ंक्शन जो लॉक को बंद करता है (साइन-आउट या अनमाउंट पर बुलाएँ)। ऐप लॉक होने पर onLocked(true) चलता है, सफल अनलॉक के बाद onLocked(false)। ज़रूरत: वही जो unlock() की है - Android: बिल्ड करते समय Fingerprint चुना हुआ हो।

import { useEffect, useState } from 'react';
import { appLock } from '@/lib/appmintNative';

/** Call once in your root component; render a lock screen while it returns true. */
export function useAppLock(): boolean {
  const [locked, setLocked] = useState(false);
  useEffect(() => appLock({
    timeoutSec: 60,                 // lock after 1 minute in the background (default 30)
    title: 'Unlock MyNotes',
    onLocked: setLocked,            // true → cover the screen, false → show the app again
  }), []);                          // appLock returns its own stop function = the cleanup
  return locked;
}

रूट कंपोनेंट में: const locked = useAppLock(); if (locked) return <LockScreen />;.

साइन-आउट पर लॉक बंद करें, ताकि साइन-आउट यूज़र से अनलॉक करने को न कहा जाए:

import { appLock } from '@/lib/appmintNative';

let stopLock: (() => void) | null = null;

export function onSignedIn() {
  stopLock = appLock({ timeoutSec: 120, title: 'Unlock your wallet' });
}

export function onSignedOut() {
  stopLock?.();            // removes the listener and turns the privacy screen off
  stopLock = null;
}

लॉक सिर्फ़ वहीं दें जहाँ डिवाइस उसे खोल सके, और मैन्युअल “अभी लॉक करें” बटन के लिए इसे unlock() के साथ मिलाएँ:

import { appLock, can, unlock } from '@/lib/appmintNative';

export function LockSettings({ onLockedChange }: { onLockedChange: (locked: boolean) => void }) {
  if (can('unlock') === 'none') return <p>This device has no screen lock to use.</p>;   // appLock would do nothing here
  return (
    <>
      <button onClick={() => appLock({ onLocked: onLockedChange })}>Turn on app lock</button>
      <button onClick={async () => { onLockedChange(true); if (await unlock('Unlock')) onLockedChange(false); }}>
        Lock now
      </button>
    </>
  );
}

ध्यान दें: अगर डिवाइस में अनलॉक करने का कोई तरीका नहीं है (can('unlock') === 'none'), तो यह कुछ नहीं करता और no-op लौटाता है - ऐसा ऐप लॉक जो कभी खुल न सके, यूज़र को फँसा देगा। यूज़र रद्द करे तो यह पाँच बार तक फिर से पूछता है, फिर ऐप के बैकग्राउंड में जाकर लौटने तक लॉक रहता है (अपनी लॉक स्क्रीन बनाए रखें)। यह बिल्ड विज़ार्ड के PIN Lock विकल्प से अलग है, जिसे ऐप लॉन्च पर नेटिव रूप से दिखाता है।

appmintNative.appUpdate appmintNative#

appUpdate.check(): Promise<AppUpdateInfo | null> / appUpdate.start(): Promise<boolean>

उदाहरण

Google Play में नया वर्ज़न देखता है और Play का इन-ऐप अपडेट शुरू करता है (डाउनलोड और इंस्टॉल वहीं हो जाता है)।

लौटाता है: check() → Promise<{ currentVersion, availableVersion?, currentVersionCode?, updateAvailable, inPlace } | null> (वेब पर, Play services के बिना, या Play के फ़ेल होने पर null)। updateAvailable स्टोर का अपना जवाब है। start() → Promise<boolean>: true सिर्फ़ तब जब Play का अपडेट पूरा हुआ, false जब यूज़र ने रद्द किया, वह फ़ेल हुआ या कोई उपलब्ध नहीं था। ज़रूरत: कुछ नहीं; ऐप Google Play से इंस्टॉल होना चाहिए।

import { appUpdate } from '@/lib/appmintNative';

async function offerUpdate() {
  const info = await appUpdate.check();
  if (!info?.inPlace) return;                    // Play can install an update now
  showBanner('A new version is ready', async () => {
    const done = await appUpdate.start();        // Play's full-screen flow / the App Store page
    if (!done) showBanner('Update later from the store.', null);
  });
}

Play के वर्ज़न कोड की इंस्टॉल किए हुए से तुलना करें, और null को "यहाँ जाँच नहीं सकते" मानें:

import { appUpdate } from '@/lib/appmintNative';

export async function updateStatus(): Promise<string> {
  const info = await appUpdate.check();
  if (!info) return 'Update check is not available here.';     // web, no Play services, or Play failed
  if (!info.updateAvailable) return `You have the latest version (${info.currentVersion}).`;
  return `Update available: version code ${info.availableVersion} (you have ${info.currentVersionCode}).`;
}

React बैनर जो स्क्रीन खुलने पर एक बार जाँचता है:

import { useEffect, useState } from 'react';
import { appUpdate, type AppUpdateInfo } from '@/lib/appmintNative';

export function UpdateBanner() {
  const [info, setInfo] = useState<AppUpdateInfo | null>(null);
  useEffect(() => { void appUpdate.check().then(setInfo); }, []);
  if (!info?.updateAvailable) return null;
  return <button onClick={() => void appUpdate.start()}>Update now</button>;
}

ध्यान दें: updateAvailable Play का अपना जवाब है; inPlace जोड़ता है कि Play अभी अपना इन-ऐप अपडेट चला सकता है। availableVersion Play का वर्ज़न CODE है (Play नाम नहीं देता) - इसकी तुलना currentVersionCode से करें, कभी currentVersion से नहीं, जो वर्ज़न का नाम है।

appmintNative.askForReview appmintNative#

askForReview(options): Promise<ReviewOutcome>

उदाहरण

Google Play से ऐप के अंदर अपनी रेटिंग शीट दिखाने को कहता है और बताता है कि क्या हुआ।

लौटाता है: Promise<ReviewOutcome> - { result, openedStore, message? } जिसमें result 'shown' | 'not_from_play' | 'quota' | 'error' | 'unavailable' होता है (वेब पर 'unavailable')। जब शीट नहीं दिखी तो { openStore: true } Play लिस्टिंग खोलता है। ज़रूरत: कुछ नहीं; शीट सिर्फ़ Google Play से इंस्टॉल किए ऐप के लिए दिखती है।

import { askForReview } from '@/lib/appmintNative';

export async function onRateTap() {
  const r = await askForReview({ openStore: true });   // listing if Play shows no sheet
  if (r.result === 'shown' || r.openedStore) toast('Thanks for rating!');
}

हर नतीजे के लिए उपयोगकर्ता को बताएँ कि क्या हुआ:

import { askForReview } from '@/lib/appmintNative';

export async function rateMessage(): Promise<string> {
  const r = await askForReview();
  switch (r.result) {
    case 'shown': return 'Thanks for rating!';
    case 'not_from_play': return 'Rating works for installs from Google Play.';
    case 'quota': return 'Google limits how often the sheet shows. Try again later.';
    case 'unavailable': return '';                       // the web: draw no rating button at all
    default: return `Could not open the rating sheet${r.message ? `: ${r.message}` : ''}.`;
  }
}

यूज़र का तीसरा काम पूरा होने के बाद एक ही बार पूछें, और याद रखें कि आपने पूछ लिया है:

import { askForReview } from '@/lib/appmintNative';

export async function maybeAskAfterTask(completed: number) {
  if (completed !== 3 || localStorage.getItem('asked-review')) return;
  localStorage.setItem('asked-review', '1');
  await askForReview();
}

नोट: सफलता के पल के बाद और कभी-कभार ही पूछें। साइडलोड किया हुआ टेस्ट इंस्टॉल हमेशा जवाब देता है not_from_play.

appmintNative.backgroundLocation appmintNative#

import { backgroundLocation } from '@/lib/appmintNative'

उदाहरण

ऐसी लोकेशन जो ऐप के बैकग्राउंड में रहने पर भी आती रहती है: start, stop, onFix, buffered, clearBuffered, requestAlways, permissionState, available.

लौटाता है: start(options) → Promise<BgResult> ({ ok, background, backgroundPermission, precise } या { ok: false, reason })। stop / clearBuffered → Promise<boolean>। buffered() → Promise<LocationFix[]>। requestAlways() → Promise<{ foreground, background, precise, error? }>। onFix(cb) → एक अनसब्सक्राइब; cb को हर appmint:location डिटेल मिलती है। ज़रूरत: बिल्ड में Background location; लोकेशन की अनुमति (पहले navigator.geolocation से पूछें)।

import { backgroundLocation, type LocationFix } from '@/lib/appmintNative';

export async function startRun(onPoint: (p: LocationFix) => void) {
  const r = await backgroundLocation.start({ intervalMs: 5000, minDistanceM: 5, accuracy: 'high',
    uploadUrl: 'https://api.example.com/run-points' });
  if (!r.ok) throw new Error(r.reason);
  const off = backgroundLocation.onFix((d) => { if (d.type === 'fix') onPoint(d); });
  // Points recorded while the screen was off:
  (await backgroundLocation.buffered()).forEach(onPoint);
  await backgroundLocation.clearBuffered();
  return async () => { off(); await backgroundLocation.stop(); };
}

शुरू करने से पहले "हमेशा अनुमति दें" माँगें, और अस्वीकार होने पर समझाएँ:

import { backgroundLocation } from '@/lib/appmintNative';

export async function beginTracking(): Promise<string> {
  if (backgroundLocation.available() === 'none') return 'Background location works in the app only.';
  const p = await backgroundLocation.requestAlways();
  if (p.error) return `Could not ask for permission (${p.error}).`;
  if (p.background !== 'granted') return 'Choose "Allow all the time" to keep tracking with the screen off.';
  const r = await backgroundLocation.start({ intervalMs: 10000, accuracy: 'balanced' });
  return r.ok ? 'Tracking.' : `Could not start (${r.reason}).`;
}

React हुक जो लाइव पॉइंट दिखाता है और स्क्रीन बंद होने पर सुनना बंद कर देता है:

import { useEffect, useState } from 'react';
import { backgroundLocation, type LocationFix } from '@/lib/appmintNative';

export function useLastFix(): LocationFix | null {
  const [fix, setFix] = useState<LocationFix | null>(null);
  useEffect(() => backgroundLocation.onFix((d) => {
    if (d.type === 'fix') setFix(d);          // { type: 'error', reason } events are skipped here
  }), []);                                    // onFix returns its unsubscribe
  return fix;
}

appmintNative.backgroundSync appmintNative#

import { backgroundSync } from '@/lib/appmintNative'

उदाहरण

नेटिव HTTP रिक्वेस्ट जो ऐप बंद होने पर भी चलती हैं: enqueue (एक बार की, टिकाऊ ऑटो-सेव जो ऑनलाइन होने पर भेजा जाता है), schedule (दोहराने वाली रिक्वेस्ट, कम से कम हर 15 मिनट में), cancel और list.

लौटाता है: enqueue / schedule → Promise<boolean>; cancel → Promise<void>; list → Promise<BackgroundJob[]> ({ id, url, periodic, createdAt?, pendingDelivery? } - Android पर pendingDelivery: एक बार वाला जॉब जो अभी पहुँचा नहीं)। ज़रूरत: Android ऐप में कुछ नहीं (यह WorkManager इस्तेमाल करता है)। वेब पर can('backgroundSync') का मान 'none' है और enqueue / schedule पूरे होते हैं false.

टिकाऊ ऑटो-सेव: वही id उस सेव को बदल देता है जो अभी भेजा नहीं गया है।

import { backgroundSync, can } from '@/lib/appmintNative';

export async function saveDraft(draft: object, token: string) {
  if (can('backgroundSync') === 'none') return false;   // web: save normally instead
  return backgroundSync.enqueue({
    id: 'draft',
    url: 'https://api.example.com/drafts',
    method: 'PUT',
    headers: { Authorization: `Bearer ${token}` },
    body: JSON.stringify(draft),
  });
}

दोहराने वाला सिंक, सूची और रद्द करना:

await backgroundSync.schedule({
  id: 'sync', url: 'https://api.example.com/sync', body: '{}', intervalMinutes: 60,
});

const jobs = await backgroundSync.list();
console.log(jobs.map((j) => `${j.id} ${j.periodic ? 'repeating' : 'one-off'}`));

await backgroundSync.cancel('sync');

रॉ इवेंट (Android पर रैपर जिसका इंतज़ार करता है):

window.addEventListener('appmint:work', function (e) {
  // { id, status: 'enqueued' | 'scheduled' | 'cancelled' | 'failed', reason?, intervalMinutes? }
  // or { requestId, jobs: [...] } for a list
  console.log('work', e.detail);
});

ध्यान दें: true का मतलब है कि जॉब कतार में लगा, डिलीवर नहीं हुआ। false का मतलब है कि वह कतार में नहीं लगा: शेल ने status: 'failed' जवाब दिया (URL http से शुरू नहीं होता, id खाली है, या सिस्टम ने मना किया) या 10 सेकंड में जवाब नहीं दिया। पेज का JavaScript बैकग्राउंड में कभी नहीं चलता; सिर्फ़ यही रिक्वेस्ट चलती है। 4xx जवाब पर दोबारा कोशिश नहीं होती; नेटवर्क त्रुटियों और 5xx पर 8 बार तक कोशिश होती है।

appmintNative.backgroundTasks appmintNative#

import { backgroundTasks } from '@/lib/appmintNative'

उदाहरण

पेज का अपना कोड, ऐप बंद होने पर समय-समय पर चलता है (इसके ऊपर टाइप किया हुआ रैपर AppMint.background): register, unregister, list, runNow, done, isBackgroundRun, currentRun, onRun, available.

लौटाता है: register / runNow → Promise<BgResult>; unregister / done → Promise<boolean>; list() → रजिस्टर किए गए टास्क, lastRunAt / lastResult के साथ; isBackgroundRun() / currentRun() सिंक्रोनस हैं। ज़रूरत: बिल्ड में Run page code while closed।

import { backgroundTasks, notify } from '@/lib/appmintNative';

// In the app's entry file, before rendering:
if (backgroundTasks.isBackgroundRun()) {
  backgroundTasks.onRun(async (run) => {
    try {
      const unread = await fetch('/api/unread').then((r) => r.json());
      if (unread.count > 0) await notify({ title: 'New messages', body: `${unread.count} unread` });
      await backgroundTasks.done(run.runId, { ok: true });
    } catch {
      await backgroundTasks.done(run.runId, { ok: false, retry: true });
    }
  });
} else {
  void backgroundTasks.register('unread', { intervalMinutes: 30, requiresNetwork: true });
  // renderApp();
}

साइन इन पर रजिस्टर करें, साइन-आउट पर हटाएँ, और जो रजिस्टर है उसकी सूची दिखाएँ:

import { backgroundTasks } from '@/lib/appmintNative';

export async function onSignedIn() {
  const r = await backgroundTasks.register('sync', { intervalMinutes: 60, requiresNetwork: true });
  if (!r.ok) console.warn('Background sync not set up:', r.reason);   // 'not_enabled' or 'unsupported'
}

export async function onSignedOut() {
  await backgroundTasks.unregister('sync');
}

export async function taskCountText(): Promise<string> {
  const tasks = await backgroundTasks.list();
  return tasks.length ? `Registered tasks: ${tasks.length}` : 'No background tasks.';
}

“अभी सिंक करें” बटन से एक टास्क अभी चलाएँ और जवाब जाँचें:

import { backgroundTasks } from '@/lib/appmintNative';

export function SyncNowButton() {
  return (
    <button
      disabled={backgroundTasks.available() === 'none'}
      onClick={async () => {
        const r = await backgroundTasks.runNow('sync');
        if (!r.ok) alert(`Could not run it (${r.reason}).`);
      }}
    >
      Sync now
    </button>
  );
}

appmintNative.badge appmintNative#

import { badge } from '@/lib/appmintNative'

उदाहरण

ऐप के लॉन्चर आइकन पर अनपढ़े की गिनती सेट या साफ़ करता है।

लौटाता है: badge.set(count) → Promise<boolean> (जब Badge प्लगइन न हो, जैसे वेब पर, तब false); badge.clear() → Promise<void>। ज़रूरत: Android पर कुछ नहीं (यह बिल्ट-इन Badge प्लगइन इस्तेमाल करता है)।

import { badge, pushInbox } from '@/lib/appmintNative';

export async function syncBadge() {
  const unread = pushInbox().filter((m) => !m.read).length;
  await badge.set(unread);        // 0 or less clears it
}

export async function onInboxOpened() {
  await badge.clear();
}

ऐप खुला होने पर पुश मैसेज आए तो अपठित गिनती को मिलाएँ:

import { badge, pushInbox } from '@/lib/appmintNative';

export function watchInbox(): () => void {
  const refresh = () => void badge.set(pushInbox().filter((m) => !m.read).length);
  window.addEventListener('appmint:push', refresh);
  refresh();
  return () => window.removeEventListener('appmint:push', refresh);
}

सब कुछ पढ़ा हुआ मार्क करें और गिनती साथ में साफ़ करें; जहाँ बैज नहीं है वहाँ उसे छोड़ दें:

import { badge, can, markPushRead } from '@/lib/appmintNative';

export async function markAllRead(): Promise<boolean> {
  markPushRead();                              // no id = every message
  if (can('badge') === 'none') return false;   // the web: nothing to clear
  await badge.clear();
  return true;
}

ध्यान दें: Android पर दिखावट लॉन्चर पर निर्भर है: कई नंबर दिखाते हैं, कुछ सिर्फ़ बिंदु, कुछ कुछ नहीं - जानकारी सिर्फ़ बैज में न रखें। वेब पर can('badge') है 'none'.

appmintNative.barcodeFormats appmintNative#

barcodeFormats(): Promise<string[]>

उदाहरण

वे बारकोड और QR फ़ॉर्मैट दिखाता है जिन्हें इस डिवाइस का BarcodeDetector पढ़ सकता है।

लौटाता है: Promise<string[]> - फ़ॉर्मैट के नाम जैसे 'qr_code', 'ean_13', 'code_128'; यहाँ बारकोड पढ़ना उपलब्ध न हो तो खाली ऐरे। यह कभी रिजेक्ट नहीं होता। ज़रूरत: कुछ नहीं।

"Scan a product barcode" विकल्प तभी दिखाएँ जब डिवाइस वह फ़ॉर्मैट पढ़ता हो:

import { useEffect, useState } from 'react';
import { barcodeFormats } from '@/lib/appmintNative';

export function useCanReadEan(): boolean {
  const [ok, setOk] = useState(false);
  useEffect(() => {
    void barcodeFormats().then((f) => setOk(f.includes('ean_13')));
  }, []);
  return ok;
}

स्कैन को उस तक सीमित करें जो डिवाइस सपोर्ट करता है:

import { barcodeFormats, readBarcodeFrom, type BarcodeFormat } from '@/lib/appmintNative';

declare const photo: File;
const wanted: BarcodeFormat[] = ['qr_code', 'ean_13'];
const have = await barcodeFormats();
const codes = await readBarcodeFrom(photo, wanted.filter((f) => have.includes(f)));

ऐसा फ़ॉर्मैट पिकर बनाएँ जो सिर्फ़ वही दिखाए जो यह डिवाइस पढ़ सकता है:

import { useEffect, useState } from 'react';
import { barcodeFormats } from '@/lib/appmintNative';

export function FormatPicker({ onPick }: { onPick: (format: string) => void }) {
  const [formats, setFormats] = useState<string[]>([]);
  useEffect(() => { void barcodeFormats().then(setFormats); }, []);
  if (formats.length === 0) return <p>Barcode reading is not available on this device.</p>;   // [] = none
  return (
    <select onChange={(e) => onPick(e.target.value)}>
      {formats.map((f) => <option key={f} value={f}>{f}</option>)}
    </select>
  );
}

ध्यान दें:

  • यह ब्राउज़र इंजन के BarcodeDetector.getSupportedFormats() से पूछता है। Android ऐप के WebView में यह है (नापा गया: aztec, code_128, code_39, code_93, codabar, data_matrix, ean_13, ean_8, itf, pdf417, qr_code, upc_a, upc_e); कई डेस्कटॉप ब्राउज़र और Safari में नहीं है, और जवाब देते हैं [].
  • यह सूची readBarcodeFrom() / scanBarcode() के बारे में है। ऐप में scanCode() इसकी जगह Google का स्कैनर इस्तेमाल करता है और इस पर निर्भर नहीं है।

appmintNative.batteryOptimization appmintNative#

import { batteryOptimization } from '@/lib/appmintNative'

उदाहरण

बैटरी-ऑप्टिमाइज़ेशन की छूट: isIgnoring() और request().

लौटाता है: isIgnoring() → boolean। request() → Promise<string>: 'already_ignoring', 'dialog_opened' (बिल्ड विकल्प "Ask to skip battery optimisation directly"), 'settings_opened', 'unavailable', या Android ऐप के बाहर 'unsupported'। ज़रूरत: सेटिंग्स की सूची के लिए कुछ नहीं।

import { batteryOptimization } from '@/lib/appmintNative';

export async function makeReliable() {
  if (batteryOptimization.isIgnoring()) return true;
  const how = await batteryOptimization.request();
  if (how === 'settings_opened') alert('Choose this app in the list and set it to "Not optimised".');
  return false;
}

सेटिंग्स की पंक्ति पर मौजूदा स्थिति दिखाएँ:

import { useState } from 'react';
import { batteryOptimization } from '@/lib/appmintNative';

export function BatteryRow() {
  const [ignoring, setIgnoring] = useState(batteryOptimization.isIgnoring());
  return (
    <button
      disabled={ignoring}
      onClick={async () => {
        const how = await batteryOptimization.request();
        if (how === 'unsupported' || how === 'unavailable') alert('This phone has no setting for it.');
        setIgnoring(batteryOptimization.isIgnoring());   // reads the current answer after the dialog or settings screen
      }}
    >
      {ignoring ? 'Battery limits are off' : 'Allow background work'}
    </button>
  );
}

लंबा बैकग्राउंड काम शुरू करने से पहले छूट माँगें, साथ में keepAlive:

import { batteryOptimization, keepAlive } from '@/lib/appmintNative';

export async function startUpload() {
  if (!batteryOptimization.isIgnoring()) await batteryOptimization.request();
  const r = await keepAlive.start({ title: 'Uploading photos', type: 'dataSync' });
  if (!r.ok) throw new Error(r.reason);
}

appmintNative.bleSensors appmintNative#

import { bleSensors } from '@/lib/appmintNative'

उदाहरण

स्टैंडर्ड Bluetooth फ़िटनेस सेंसर - हार्ट रेट, साइक्लिंग स्पीड/कैडेंस, पावर, रनिंग स्पीड/कैडेंस और सेंसर बैटरी - पहले से पार्स किए गए नंबरों के साथ, Android ऐप में।

लौटाता है: हर तरीका Promise<void> लौटाता है (setWheelCircumference void लौटाता है)। बाकी सब appmint:ble window इवेंट के रूप में आता है, जिसमें detail = {kind:'device', address, name, sensors, rssi} | {kind:'state', state, address?, services?} | {kind:'data', type, data} | {kind:'error', error} होता है। ज़रूरत: ब्लूटूथ, जिसे बिल्ड खुद चालू कर देता है जब कोड bleSensors इस्तेमाल करता है। ब्राउज़र (वेब प्रीव्यू) में कोई सेंसर ब्रिज नहीं है: startScan() जवाब में {kind:'error', error:'not_enabled'} देता है, इसलिए उस एरर को संभालें। पहले से जानने के लिए can('bluetooth') === 'native' जाँचें - !== 'none' नहीं: Chrome में can('bluetooth') का मान 'web' (Web Bluetooth) होता है, जिसे bleSensors इस्तेमाल नहीं करता।

import { useEffect, useState } from 'react';
import { bleSensors } from '@/lib/appmintNative';

type BleDetail = {
  kind: 'device' | 'state' | 'data' | 'error';
  address?: string; name?: string | null; sensors?: string[]; rssi?: number;
  state?: 'scanning' | 'scan_stopped' | 'connecting' | 'connected' | 'ready' | 'disconnected';
  type?: 'heart_rate' | 'speed_cadence' | 'power' | 'running' | 'battery';
  data?: { bpm?: number; speedKmh?: number; cadenceRpm?: number; watts?: number; cadenceSpm?: number; percent?: number };
  error?: string;
};

export function SensorPanel() {
  const [devices, setDevices] = useState<BleDetail[]>([]);
  const [bpm, setBpm] = useState<number | null>(null);
  const [status, setStatus] = useState('');

  useEffect(() => {
    const onBle = (e: Event) => {
      const d = (e as CustomEvent<BleDetail>).detail;
      if (d.kind === 'device') setDevices((list) => [...list, d]);
      else if (d.kind === 'state') setStatus(d.state ?? '');
      else if (d.kind === 'data' && d.type === 'heart_rate') setBpm(d.data?.bpm ?? null);
      else if (d.kind === 'error') {
        setStatus(d.error === 'not_enabled' ? 'Sensors work in the installed app.' : `Bluetooth: ${d.error}`);
      }
    };
    window.addEventListener('appmint:ble', onBle);
    return () => { window.removeEventListener('appmint:ble', onBle); void bleSensors.disconnect(); };
  }, []);

  return (
    <div>
      <button onClick={() => { setDevices([]); bleSensors.setWheelCircumference(2105); void bleSensors.startScan(); }}>
        Find sensors
      </button>
      {devices.map((d) => (
        <button key={d.address} onClick={() => void bleSensors.connect(d.address!)}>
          {d.name ?? 'Sensor'} ({d.sensors?.join(', ')})
        </button>
      ))}
      <p>{status}</p>
      {bpm !== null && <p>{bpm} bpm</p>}
    </div>
  );
}

इंस्टॉल किए गए ऐप के हिसाब से स्क्रीन को गेट करें।

import { bleSensors, can } from '@/lib/appmintNative';

export function SensorsEntry() {
  // 'native' only: in Chrome can('bluetooth') is 'web', which bleSensors does not use.
  if (can('bluetooth') !== 'native') return <p>Heart-rate sensors work in the installed app.</p>;
  return <button onClick={() => void bleSensors.startScan()}>Find sensors</button>;
}

पहले सेंसर पर स्कैन रोकें और साइकिल की रफ़्तार देखें।

import { useEffect, useState } from 'react';
import { bleSensors } from '@/lib/appmintNative';

type BikeEvent = { kind: string; address?: string; type?: string; data?: { speedKmh?: number } };

export function BikeSpeed() {
  const [kmh, setKmh] = useState<number | null>(null);

  useEffect(() => {
    bleSensors.setWheelCircumference(2096);   // 700x23c
    const onBle = (e: Event) => {
      const d = (e as CustomEvent<BikeEvent>).detail;
      if (d.kind === 'device' && d.address) {
        void bleSensors.stopScan().then(() => bleSensors.connect(d.address!));
      } else if (d.kind === 'data' && d.type === 'speed_cadence') {
        setKmh(d.data?.speedKmh ?? null);
      }
    };
    window.addEventListener('appmint:ble', onBle);
    void bleSensors.startScan();
    return () => { window.removeEventListener('appmint:ble', onBle); void bleSensors.disconnect(); };
  }, []);

  return <p>{kmh === null ? 'Looking for a sensor...' : `${kmh.toFixed(1)} km/h`}</p>;
}

ध्यान दें: स्कैन 15 सेकंड चलते हैं (stopScan() एक को जल्दी ख़त्म करता है); एक समय में एक सेंसर जुड़ता है। त्रुटि कोड: not_enabled, permission_denied, bluetooth_unavailable, bluetooth_off, scan_failed, invalid_address। स्पीड के लिए दो पैकेट और पहिये का साइज़ चाहिए (डिफ़ॉल्ट 2096 mm)। यही संदेश window.onAppMintBle को भी जाते हैं; इवेंट सुनना ज़्यादा आसान है। किसी भी दूसरे Bluetooth डिवाइस के लिए इस्तेमाल करें navigator.bluetooth.

appmintNative.brightness appmintNative#

brightness.get() / brightness.set(value) / brightness.restore()

उदाहरण

बोर्डिंग पास या QR कोड के लिए स्क्रीन की चमक बढ़ाता है, फिर नियंत्रण सिस्टम को लौटा देता है। सिर्फ़ नेटिव - वेब पेज बैकलाइट नहीं चला सकता।

लौटाता है: get() → Promise<number | null> (जब ऐप स्तर को ओवरराइड करे तब 0-1; जब ऐप सिस्टम स्तर का पालन करे तब -1; वेब पर या जब पढ़ा न जा सके तब null)। set(value) → Promise<boolean> (मान 0-1 में सीमित; वेब पर false)। restore() → Promise<void>। ज़रूरत: कुछ नहीं।

import { brightness, can } from '@/lib/appmintNative';
import { useEffect } from 'react';

function TicketScreen() {
  useEffect(() => {
    if (can('brightness') === 'none') return;
    void brightness.set(1);
    return () => { void brightness.restore(); };
  }, []);
  return <QrCode />;
}

स्तर याद रखें, चमक बढ़ाएँ, फिर वापस कर दें।

import { brightness } from '@/lib/appmintNative';

export async function showBoardingPass(render: () => Promise<void>) {
  const before = await brightness.get();   // -1 = following the system, null = not readable here
  const ok = await brightness.set(1);
  try {
    await render();
  } finally {
    if (ok) await brightness.restore();    // returns to the system level
  }
  return before;
}

कोड दिखाते समय स्क्रीन को तेज़ और जागता रखें।

import { brightness, keepAwake, can } from '@/lib/appmintNative';
import { useEffect } from 'react';

export function useQrPresenting(active: boolean) {
  useEffect(() => {
    if (!active) return;
    if (can('brightness') !== 'none') void brightness.set(0.95);
    void keepAwake(true);
    return () => { void brightness.restore(); void keepAwake(false); };
  }, [active]);
}

ध्यान दें: यह सिर्फ़ इस ऐप की विंडो बदलता है, फ़ोन की ब्राइटनेस सेटिंग नहीं, और ऐप बंद होते ही यह बदलाव खत्म हो जाता है। restore() से -1 सेट होता है (सिस्टम के हिसाब से चलें)। get() विंडो का बदलाव पढ़ता है, इसलिए जब तक आप set() नहीं बुलाते तब तक यह -1 रहता है - यह सिस्टम का स्तर नहीं है।

appmintNative.browser appmintNative#

browser.open(url, { toolbarColor? }) / browser.close() / browser.onClosed(cb)

उदाहरण

ऐप के अंदर वाले ब्राउज़र टैब (Chrome Custom Tabs) में पेज खोलता है - शर्तों, हेल्प पेज या साइन-इन पेज के लिए - और वेब पर नया टैब।

लौटाता है: open() → Promise<boolean> (टैब न खुल सके तो false)। close() → Promise<void>। onClosed(cb) → अनसब्सक्राइब फ़ंक्शन; यूज़र के टैब बंद करने पर cb चलता है (सिर्फ़ ऐप में)। ज़रूरत: कुछ नहीं।

import { browser } from '@/lib/appmintNative';

async function openHelp() {
  const off = browser.onClosed(() => {
    off();
    void refreshAccount();       // the user may have changed something on the site
  });
  const ok = await browser.open('https://example.com/help', { toolbarColor: '#0B0F1A' });
  if (!ok) { off(); showMessage('Could not open the help page.'); }
}

React कंपोनेंट में शर्तों का लिंक।

import { browser, toast } from '@/lib/appmintNative';

export function TermsLink() {
  return (
    <button
      onClick={async () => {
        if (!(await browser.open('https://example.com/terms'))) toast('Could not open the terms page');
      }}
    >
      Terms of use
    </button>
  );
}

कोड से टैब बंद करें और उपयोगकर्ता के लौटने पर रिफ़्रेश करें।

import { useEffect } from 'react';
import { browser } from '@/lib/appmintNative';

export function usePaymentTab(onBack: () => void) {
  useEffect(() => browser.onClosed(onBack), [onBack]);   // the returned function unsubscribes
  return {
    start: (url: string) => browser.open(url),
    cancel: () => browser.close(),   // closes the tab in the app; does nothing on the web
  };
}

नोट: toolbarColor ऐप के --bg टोकन पर डिफ़ॉल्ट होता है। टैब एक अलग ब्राउज़र है - वह ऐप की कुकीज़ या localStorage साझा नहीं करता। ऐप में close() कोड से टैब बंद करता है; वेब पर कुछ नहीं करता। onClosed वेब पर कभी नहीं चलता।

appmintNative.calendar appmintNative#

calendar.requestPermission / createEvent / listEvents / deleteEvent / openCalendar

उदाहरण

यूज़र के अपने फ़ोन कैलेंडर (Android का कैलेंडर प्रोवाइडर) में इवेंट जोड़ता, सूचीबद्ध करता और हटाता है, और किसी तारीख पर कैलेंडर ऐप खोलता है।

लौटाता है: requestPermission() → Promise<'granted' | 'denied'>। createEvent(input) → Promise<string | null> (इवेंट id; नकारे या फ़ेल होने पर null)। listEvents({ from, to }) → Promise<{ id, title, start, end, location?, notes?, allDay? }[]> (समय epoch ms में)। deleteEvent(id) / openCalendar(date?) → Promise<boolean>। कुछ भी reject नहीं होता। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Calendar चालू करें - जब बिल्ड को कोड में calendar.createEvent(, calendar.listEvents( या calendar.requestPermission( दिखता है तो वह इसे खुद टिक कर देता है।

import { calendar } from '@/lib/appmintNative';

async function addBooking(b: { name: string; at: Date; minutes: number; place: string }) {
  if ((await calendar.requestPermission()) !== 'granted') {
    showMessage('Allow calendar access to add the booking.');
    return;
  }
  const id = await calendar.createEvent({
    title: `Booking: ${b.name}`,
    start: b.at,
    end: new Date(b.at.getTime() + b.minutes * 60_000),
    location: b.place,
    notes: 'Added by the app',
  });
  if (id) saveEventId(b, id);
}

इस हफ़्ते की सूची बनाएँ, हटाएँ, खोलें:

import { calendar, can } from '@/lib/appmintNative';

if (can('calendarRead') !== 'none') {
  const week = await calendar.listEvents({ from: Date.now(), to: Date.now() + 7 * 86_400_000 });
  if (week[0]) await calendar.deleteEvent(week[0].id);
}
await calendar.openCalendar(new Date());

कैलेंडर में तभी जोड़ें जब लिखना संभव हो।

import { calendar, can, toast } from '@/lib/appmintNative';

export async function addReminder(title: string, when: Date) {
  if (can('calendarWrite') === 'none') { toast('Calendar is not available here'); return null; }
  const id = await calendar.createEvent({ title, start: when, end: new Date(when.getTime() + 30 * 60_000) });
  toast(id ? 'Added to your calendar' : 'Calendar access was refused');
  return id;
}

ध्यान दें: वेब पर createEvent एक .ics फ़ाइल डाउनलोड करता है जिसे यूज़र अपने कैलेंडर में खोलता है (उसकी UID लौटाता है), listEvents [] लौटाता है (can('calendarRead') है 'none'), और deleteEvent / openCalendar false लौटाते हैं। रिमाइंडर और दोहराने वाले इवेंट Capacitor.Plugins.Calendar पर उपलब्ध हैं, इस रैपर से नहीं।

appmintNative.camera appmintNative#

camera.take(opts?) / camera.pick(opts?)

उदाहरण

कैमरे से फ़ोटो लेता है या गैलरी से फ़ोटो चुनता है: ऐप में Capacitor Camera प्लगइन, वेब पर capture वाला फ़ाइल इनपुट।

लौटाता है: take() → Promise<Photo | null> जिसमें { dataUrl, mime }; रद्द करने या अनुमति अस्वीकार होने पर null। pick() → Promise<Photo[]>, रद्द करने पर खाली। ज़रूरत: चरण 4 (Access) में कैमरा - जब आपका कोड कॉल करता है तो बिल्ड इसे चालू कर देता है camera.take.

फ़्रंट कैमरे से प्रोफ़ाइल फ़ोटो लें।

import { camera } from '@/lib/appmintNative';

export async function takeSelfie(img: HTMLImageElement): Promise<boolean> {
  const photo = await camera.take({ source: 'camera', direction: 'front', quality: 70 });
  if (!photo) return false;                 // cancelled or permission refused
  img.src = photo.dataUrl;                  // "data:image/jpeg;base64,..."
  return true;
}

take से गैलरी से एक फ़ोटो चुनें, या pick से कई।

import { camera } from '@/lib/appmintNative';

const one = await camera.take({ source: 'photos' });          // Photo | null
const many = await camera.pick({ limit: 5, quality: 80 });    // Photo[] (limit 0 = no limit)
console.log(one?.mime, many.length);

फ़ोटो अपलोड करें, data URL को Blob में बदलकर।

import { camera } from '@/lib/appmintNative';

const p = await camera.take();
if (p) {
  const blob = await (await fetch(p.dataUrl)).blob();
  const form = new FormData();
  form.append('file', blob, 'photo.jpg');
  await fetch('https://api.example.com/upload', { method: 'POST', body: form });
}

नोट: विकल्प: source ('camera' डिफ़ॉल्ट, या 'photos'), quality 0-100 (डिफ़ॉल्ट 80), direction ('rear' डिफ़ॉल्ट, या 'front'), allowEditing। फ़ोटो गैलरी में सेव नहीं होतीं। अनुमति न मिलने पर null / [] मिलता है, एरर नहीं आता - एक छोटा संकेत दिखाएँ ताकि उपयोगकर्ता जाने कि कुछ क्यों नहीं हुआ। कैमरा संवेदनशील है: इसे अपने Play Data safety फ़ॉर्म में घोषित करें।

appmintNative.can appmintNative#

can(cap: Capability): 'native' | 'web' | 'none'

उदाहरण

बताता है कि मौजूदा सतह पर कोई फ़ीचर असल में कहाँ चलेगा, ताकि ऐप सिर्फ़ वही कंट्रोल दिखाए जो काम करते हैं।

लौटाता है: एक स्ट्रिंग, तुरंत: 'native' (ऐप का अपना कार्यान्वयन - शेल ब्रिज या ऐप में बना Capacitor प्लगइन), 'web' (इस ब्राउज़र में असली वेब मानक) या 'none' (यहाँ उपलब्ध नहीं - कंट्रोल न बनाएँ)। ज़रूरत: कुछ नहीं।

ऐसा कंट्रोल छिपाएँ जो इस प्लेटफ़ॉर्म पर काम नहीं कर सकता:

import { can, share } from '@/lib/appmintNative';

export function ShareButton({ url }: { url: string }) {
  if (can('share') === 'none') return null;   // never ship a dead button
  return <button onClick={() => void share({ title: 'Have a look', url })}>Share</button>;
}

सतह के हिसाब से शब्द बदलें:

import { can } from '@/lib/appmintNative';

const contacts = can('contactList') !== 'none'; // 'none' on the web
const lockLabel = can('unlock') === 'native' ? 'Unlock with fingerprint' : 'Unlock with a passkey';

क्षमता के नाम: haptic share clipboardWrite clipboardRead speak fullscreen notify deviceInfo · filePick fileSave unlock · contactPick contactList contactAdd · smsCompose smsSend smsList callLog · toast dialog actionSheet keyboardInsets orientationLock network statusBar · swipe backGesture · folderAccess secureKeys mediaControls cast bluetooth · shortcuts · appLaunch browser camera photos location screenReader textZoom privacyScreen fileViewer fileTransfer calendarRead calendarWrite backgroundSync motion · keepAwake brightness review appUpdate badge speech hapticPatterns docScanner systemPalette liveProgress widget nfc pip shareTarget notificationActions · barcodeScan codeScanner passkeys.

बेकार कंट्रोल की जगह संकेत दिखाएँ, और फ़ीचर को सादे हाँ/नहीं में बदलें, इसके साथ has():

import { can, has, keepAwake } from '@/lib/appmintNative';

export function KeepScreenOn({ on }: { on: boolean }) {
  if (!has('keepAwake')) return <p>Keep-awake is not available on this device.</p>;
  return <label><input type="checkbox" checked={on} onChange={(e) => void keepAwake(e.target.checked)} /> Keep screen on</label>;
}

export const scansInApp = can('codeScanner') === 'native';

ध्यान दें:

  • ऐप में बना Capacitor प्लगइन हमेशा 'native' जवाब देता है; वरना शेल ब्रिज जाँचा जाता है, फिर वेब मानक।
  • toast, actionSheet, dialog, statusBar और swipe का असली वेब संस्करण है, इसलिए ब्राउज़र में वे 'none' नहीं हैं।
  • contactList, contactAdd, smsSend, smsList, callLog, shortcuts, shareTarget और widget वेब पर 'none' हैं।
  • कुछ फ़ीचर तभी मौजूद होते हैं जब बिल्ड ने उन्हें चालू किया हो (फ़ोल्डर एक्सेस, सिक्योर कुंजियाँ, मीडिया कंट्रोल, SMS, कॉन्टैक्ट)। पूछें can() से, इससे नहीं isNativeApp().
  • अज्ञात नाम पर 'none' मिलता है। सीधे हाँ/नहीं के लिए इस्तेमाल करें has(cap).

appmintNative.clearPushInbox appmintNative#

import { clearPushInbox } from '@/lib/appmintNative'

उदाहरण

इस फ़ोन पर AppMint Push इनबॉक्स खाली करता है। हमेशा के लिए - इसे वापस नहीं किया जा सकता।

लौटाता है: तुरंत boolean - साफ़ होने पर true; वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { clearPushInbox, pushInbox } from '@/lib/appmintNative';

export async function onClearAll(confirmDelete: () => Promise<boolean>) {
  if (!(await confirmDelete())) return pushInbox();
  clearPushInbox();
  return pushInbox();   // []
}

Clear तभी दिखाएँ जब साफ़ करने को कुछ हो, और नतीजा बताएँ:

import { clearPushInbox, pushInbox, toast } from '@/lib/appmintNative';

export function InboxToolbar({ onChange }: { onChange: () => void }) {
  const count = pushInbox().length;
  if (count === 0) return null;
  return (
    <button
      onClick={() => {
        // false on the web, or in an app built without AppMint Push
        if (clearPushInbox()) { toast('Inbox cleared'); onChange(); }
        else toast('Could not clear the inbox here');
      }}
    >
      Clear all ({count})
    </button>
  );
}

सबको मिटाने की जगह पढ़ा हुआ चिह्नित करें, इससे कुछ नहीं खोता:

import { markPushRead, pushInbox } from '@/lib/appmintNative';

export function markAllRead(): number {
  const unread = pushInbox().filter((m) => !m.read).length;
  markPushRead();   // no argument = every message
  return unread;
}

नोट: ट्रे नोटिफ़िकेशन और AppMint में आपका भेजने का इतिहास छुए नहीं जाते; नए संदेश फिर भी आते हैं।

appmintNative.clearShared appmintNative#

clearShared(): void

उदाहरण

किसी दूसरे ऐप से इस ऐप में शेयर किया गया टेक्स्ट या लिंक भुला देता है, जब आपका ऐप उसे सेव कर ले।

लौटाता है: कुछ नहीं। ज़रूरत: कुछ नहीं (वेब पर कुछ नहीं करता)।

import { sharedText, clearShared } from '@/lib/appmintNative';

export async function importPendingShare(addNote: (text: string) => Promise<void>): Promise<boolean> {
  const p = sharedText();
  if (!p) return false;
  await addNote(p.text);
  clearShared();          // otherwise sharedText() returns the same share on the next mount
  return true;
}

शेयर तभी साफ़ करें जब वह सेव हो जाए, और सेव फ़ेल होने पर उसे रखें:

import { sharedText, clearShared, toast } from '@/lib/appmintNative';

export async function saveSharedLink(save: (url: string) => Promise<void>) {
  const p = sharedText();
  if (!p?.url) return;
  try {
    await save(p.url);
    clearShared();
    toast('Saved');
  } catch {
    toast('Could not save, the shared link is still waiting');   // not cleared, so it is not lost
  }
}

ऐप खुला रहने पर आने वाले शेयर को संभालें:

import { useEffect } from 'react';
import { onShared, clearShared } from '@/lib/appmintNative';

export function useIncomingShare(addNote: (text: string) => void) {
  useEffect(() => onShared((p) => {
    addNote(p.subject ? `${p.subject}\n${p.text}` : p.text);
    clearShared();
  }), [addNote]);
}

ध्यान दें: जान-बूझकर, जब तक यह नहीं बुलाया जाता तब तक sharedText() शेयर को रखता है, ताकि देर से खुलने वाली स्क्रीन भी उसे देख सके। clearShared() सिर्फ़ तभी बुलाएँ जब शेयर सुरक्षित सेव हो चुका हो - सेव न हो पाए तो उसे रहने दें ताकि यूज़र का डेटा न जाए।

appmintNative.click appmintNative#

click() — @/lib/appmintNative

उदाहरण

आपके अपने बटनों के लिए ऐप की टैप ध्वनि एक बार बजाता है।

लौटाता है: void। ज़रूरत: बिल्ड करते समय चरण 3 (Integrate) में Tap Sound चालू। वेब पर, और बिना Tap Sound बने ऐप में, यह कुछ नहीं करता।

import { click, haptic } from '@/lib/appmintNative';

export function KeypadKey({ label, onPress }: { label: string; onPress: () => void }) {
  return (
    <button
      onClick={() => {
        click();     // sound
        haptic();    // feel
        onPress();
      }}
    >
      {label}
    </button>
  );
}

यूज़र को अपनी सेटिंग्स में आवाज़ बंद करने दें:

import { click } from '@/lib/appmintNative';

export function useTapSound(enabled: boolean) {
  return () => { if (enabled) click(); };   // a no-op on the web and in apps built without Tap Sound
}

इसे लिस्ट पंक्ति से टोस्ट के साथ चलाएँ:

import { click, toast } from '@/lib/appmintNative';

export function Row({ name, onPick }: { name: string; onPick: () => void }) {
  return <li onClick={() => { click(); onPick(); toast(`${name} selected`); }}>{name}</li>;
}

ध्यान दें: Follow phone setting (recommended) के साथ, यूज़र ने फ़ोन में “Touch sounds” बंद किया हो तो क्लिक चुप रहता है। Always play on every tap के साथ हर टैप पहले से क्लिक करता है, इसलिए अतिरिक्त click() कॉल की ज़रूरत नहीं। सिर्फ़ Android (शेल का WebToApk.playClick).

appmintNative.Clipboard appmintNative#

Clipboard.write({ string?, url? }) / Clipboard.read(): Promise<{ value, type }>

उदाहरण

क्लिपबोर्ड, Capacitor के Clipboard प्लगइन के रूप में - वही कोड जो copy() / paste().

लौटाता है: write() → Promise<void>। read() → Promise<{ value: string; type: 'text/plain' }> (क्लिपबोर्ड खाली होने पर value का मान '' होता है)। ज़रूरत: कुछ नहीं।

import { Clipboard, can } from '@/lib/appmintNative';

async function copyInvite(code: string) {
  await Clipboard.write({ string: code });
}

async function pasteInvite(): Promise<string> {
  if (can('clipboardRead') === 'none') return '';   // hide the Paste button instead
  try {
    const { value } = await Clipboard.read();
    return value;
  } catch {
    return '';   // the browser denied clipboard access
  }
}

कोड कॉपी करें और टोस्ट से पुष्टि करें:

import { Clipboard, toast } from '@/lib/appmintNative';

export async function copyCode(code: string) {
  await Clipboard.write({ string: code });
  toast('Code copied');
}

टेक्स्ट न हो तो लिंक लिखें, और खाली क्लिपबोर्ड को पेस्ट करने को कुछ नहीं मानें:

import { Clipboard } from '@/lib/appmintNative';

export async function copyUrl(url: string) {
  await Clipboard.write({ url });   // used only because string is empty
}

export async function readCode(): Promise<string | null> {
  try {
    const { value } = await Clipboard.read();
    return value.trim() || null;    // '' = the clipboard is empty
  } catch {
    return null;                    // the browser denied access
  }
}

नोट: ऐप में यह Capacitor Clipboard प्लगइन इस्तेमाल करता है; ब्राउज़र में navigator.clipboard। write सिर्फ़ string भेजता है (या जब string खाली हो तो url) - तस्वीरें नहीं। ब्राउज़र में, उपयोगकर्ता के पहुँच न देने पर read() एरर दे सकता है; ऐप में खाली क्लिपबोर्ड '' होता है, एरर नहीं। Paste कंट्रोल दिखाने से पहले can('clipboardRead') पूछें।

appmintNative.collapsingHeader appmintNative#

collapsingHeader(header: HTMLElement, opts?: { scroller?: HTMLElement | Window; range?: number }): () => void

उदाहरण

बड़े शीर्षक वाले हेडर को स्क्रॉल पर वैसे प्रतिक्रिया देने लायक बनाता है जैसे नेटिव वाले देते हैं, एक 0 → 1 मान देकर जिसे आपकी CSS इस्तेमाल कर सकती है।

लौटाता है: एक unsubscribe फ़ंक्शन जो वैरिएबल और एट्रिब्यूट भी हटाता है। ज़रूरत: कुछ नहीं।

यह हेडर पर --collapse सेट करता है (ऊपर 0 से लेकर range px स्क्रॉल के बाद 1 तक), और पूरी तरह सिकुड़ने पर data-collapsed जोड़ता है:

import { useEffect, useRef } from 'react';
import { collapsingHeader } from '@/lib/appmintNative';

export function NotesScreen() {
  const headerRef = useRef<HTMLElement>(null);
  const listRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!headerRef.current || !listRef.current) return;
    return collapsingHeader(headerRef.current, { scroller: listRef.current, range: 80 });
  }, []);

  return (
    <div className="flex h-full flex-col">
      <header ref={headerRef} className="app-header">Notes</header>
      <div ref={listRef} className="flex-1 overflow-y-auto">{/* rows */}</div>
    </div>
  );
}
.app-header { font-size: calc(28px - 10px * var(--collapse, 0)); padding-top: var(--safe-top); }
.app-header[data-collapsed] { box-shadow: 0 1px 0 var(--border); }

साधारण स्क्रिप्ट से सुनना बंद करें; यह --collapse को भी हटाता है और data-collapsed:

import { collapsingHeader } from '@/lib/appmintNative';

const header = document.querySelector<HTMLElement>('.app-header')!;
const stop = collapsingHeader(header);   // whole-page scroll, default 64 px range
addEventListener('pagehide', stop);

ध्यान दें:

  • डिफ़ॉल्ट scroller विंडो है (वह पेज जो पूरा स्क्रॉल होता है); डिफ़ॉल्ट range 64 px है।
  • स्क्रॉल लिसनर पैसिव है और हर एनिमेशन फ़्रेम में एक बार अपडेट होता है, इसलिए स्क्रॉल धीमा नहीं होता।

appmintNative.composeSms appmintNative#

composeSms(phoneNumber: string, message: string): boolean

उदाहरण

नंबर और टेक्स्ट भरे हुए मैसेजिंग ऐप खोलता है; उपयोगकर्ता भेजें दबाता है। Android ऐप (शेल ब्रिज) और वेब पर (sms: लिंक) काम करता है।

लौटाता है: तुरंत boolean - मैसेजिंग ऐप खोलने को कहा गया हो तो true। ज़रूरत: कोई अनुमति नहीं।

एक "Text the shop" बटन।

import { composeSms, can } from '@/lib/appmintNative';

export function setUpTextButton(button: HTMLButtonElement) {
  if (can('smsCompose') === 'none') { button.hidden = true; return; }
  button.addEventListener('click', () => {
    const opened = composeSms('+911234567890', 'Hi, is my order ready?');
    if (!opened) alert('Could not open the messaging app.');
  });
}

एक दोबारा इस्तेमाल होने वाला हुक जो नंबर न होने पर बटन छिपा देता है।

import { composeSms, can } from '@/lib/appmintNative';

export function useTextShop(phone: string) {
  const available = can('smsCompose') !== 'none' && phone.trim() !== '';
  return {
    available,
    send: (orderId: string) => composeSms(phone, `Hi, about order ${orderId}.`),
  };
}

किसी दोस्त को आमंत्रित करें; भेजने का बटन यूज़र दबाता है।

import { composeSms, toast } from '@/lib/appmintNative';

export function inviteFriend(number: string, link: string) {
  if (!composeSms(number, `Join me: ${link}`)) toast('Could not open the messaging app');
}

ध्यान दें: जो यूज़र लिखे या मंज़ूर करे उसके लिए इसे sendSms() से बेहतर मानें - यूज़र के भेजें दबाए बिना फ़ोन से कुछ नहीं जाता। वेब पर यह sms:…?body=… पर जाता है। composeSms( कॉल बिल्ड करते समय SMS (read & send) चालू नहीं करती, इसलिए कोई Play-प्रतिबंधित अनुमति नहीं जुड़ती।

appmintNative.copy appmintNative#

copy(text: string): Promise<boolean>

उदाहरण

सिस्टम क्लिपबोर्ड पर टेक्स्ट रखता है।

लौटाता है: Promise<boolean> - टेक्स्ट कॉपी हो जाने पर true, और क्लिपबोर्ड उपलब्ध न होने या ब्राउज़र के मना करने पर false। ज़रूरत: कुछ नहीं।

import { can, copy, haptic, toast } from '@/lib/appmintNative';

export function CopyLink({ link }: { link: string }) {
  if (can('clipboardWrite') === 'none') return null;
  return (
    <button
      onClick={async () => {
        if (await copy(link)) { haptic('success'); toast('Link copied'); }
        else toast('Could not copy the link');
      }}
    >
      Copy link
    </button>
  );
}

Capacitor वाला रूप भी यही करता है:

import { Clipboard } from '@/lib/appmintNative';

await Clipboard.write({ string: 'ABC-123' });

दोनों नतीजे बताएँ, हैप्टिक सिर्फ़ सफलता पर:

import { copy, haptic, toast } from '@/lib/appmintNative';

export async function copyCoupon(code: string) {
  const ok = await copy(code);
  if (ok) haptic('success');
  toast(ok ? `Copied ${code}` : 'Copy failed, press and hold to copy');
  return ok;
}

ध्यान दें: ऐप में Capacitor Clipboard प्लगइन टेक्स्ट लिखता है (प्लगइन त्रुटि false लौटाने की जगह promise को reject करती है)। ब्राउज़र में यह navigator.clipboard.writeText इस्तेमाल करता है, जिसके लिए https पेज और आमतौर पर यूज़र का टैप चाहिए।

appmintNative.copyImage appmintNative#

import { copyImage } from '@/lib/appmintNative'

उदाहरण

एक तस्वीर क्लिपबोर्ड पर कॉपी करता है ताकि वह चैट और मेल ऐप्स में पेस्ट हो: Android ऐप में ऐप का क्लिपबोर्ड, वेब पर Async Clipboard API।

लौटाता है: Promise<boolean> - कॉपी होने पर true। ज़रूरत: कुछ नहीं; कंट्रोल को इस पर निर्भर रखें can('clipboardImage').

import { can, copyImage, toast } from '@/lib/appmintNative';

export async function copyQr(canvas: HTMLCanvasElement) {
  if (can('clipboardImage') === 'none') return;
  const png = await new Promise<Blob | null>((r) => canvas.toBlob(r, 'image/png'));
  if (png && (await copyImage(png))) toast('QR code copied');
  else toast('Could not copy the image');
}

कैमरे से ली गई फ़ोटो कॉपी करें, फिर टोस्ट से पुष्टि करें।

import { camera, copyImage, toast } from '@/lib/appmintNative';

export async function copySnapshot() {
  const photo = await camera.take({ source: 'camera' });
  if (!photo) return;                                  // the user cancelled
  const blob = await (await fetch(photo.dataUrl)).blob();
  toast((await copyImage(blob)) ? 'Photo copied' : 'Could not copy the photo');
}

बटन सिर्फ़ वहीं दिखाएँ जहाँ क्लिपबोर्ड इमेज लेता है, और URL से इमेज कॉपी करें।

import { useEffect, useState } from 'react';
import { can, copyImage, toast } from '@/lib/appmintNative';

export function CopyLogoButton({ src }: { src: string }) {
  const [ok, setOk] = useState(false);
  useEffect(() => { setOk(can('clipboardImage') !== 'none'); }, []);
  if (!ok) return null;
  return (
    <button onClick={async () => {
      const blob = await (await fetch(src)).blob();
      toast((await copyImage(blob)) ? 'Logo copied' : 'Copy failed');
    }}>
      Copy logo
    </button>
  );
}

ध्यान दें: PNG हर जगह सबसे सुरक्षित प्रकार है; Android ऐप JPEG और WebP भी स्वीकार करता है।

appmintNative.Device appmintNative#

Device.getInfo() / Device.getId() / Device.getLanguageCode()

उदाहरण

@capacitor/device का रूप: फ़ोन का मॉडल, OS वर्ज़न, एक स्थिर ID और भाषा। ऐप में यह Capacitor Device प्लगइन इस्तेमाल करता है और वेब पर ब्राउज़र का जवाब।

लौटाता है: getInfo() → Promise<DeviceInfo & { operatingSystem, isVirtual }> (Capacitor के साथ manufacturer, webViewVersion, … भी); getId() → Promise<{ identifier }>; getLanguageCode() → Promise<{ value }> (दो अक्षर, जैसे "en")। ज़रूरत: कुछ नहीं।

About स्क्रीन पर मॉडल और OS वर्ज़न दिखाएँ।

import { Device } from '@/lib/appmintNative';

export async function aboutText(): Promise<string> {
  const info = await Device.getInfo();
  if (info.platform === 'web') return `Browser (${info.language})`;
  return `${info.model ?? 'Unknown model'} · ${info.operatingSystem} ${info.osVersion ?? ''}` +
    (info.isVirtual ? ' · emulator' : '');
}

एक स्थिर ID और भाषा कोड।

import { Device } from '@/lib/appmintNative';

const { identifier } = await Device.getId();       // Capacitor ID in the app, installId() on the web
const { value: lang } = await Device.getLanguageCode();
await fetch('https://api.example.com/hello', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ device: identifier, lang }),
});

सेटिंग्स फ़ूटर में ऐप वर्ज़न और डिवाइस मॉडल दिखाएँ। सिंक रिपोर्ट ऐप वर्ज़न देती है; प्लगइन ऑपरेटिंग सिस्टम जोड़ता है।

import { deviceInfo, Device } from '@/lib/appmintNative';

export async function footerText(): Promise<string> {
  const base = deviceInfo();                            // sync: platform, language, model, appVersion
  const rich = await Device.getInfo();                  // async: adds operatingSystem, isVirtual
  const app = base.appVersion ? `v${base.appVersion}` : 'web';
  return `${app} · ${rich.model ?? 'browser'} · ${rich.operatingSystem}`;
}

ध्यान दें: ऐप में सिंक वाला deviceInfo() model और osVersion भी भरता है; Device.getInfo() operatingSystem, isVirtual और प्लगइन के अतिरिक्त फ़ील्ड जोड़ता है। वेब पर operatingSystem का मान 'unknown' है और model खाली है। वेब पर getId(), installId() से मिली localStorage ID है और स्टोरेज ब्लॉक होने पर throw करती है।

appmintNative.deviceInfo appmintNative#

deviceInfo(): DeviceInfo

उदाहरण

तुरंत बताता है कि ऐप कहाँ चल रहा है (platform) और भाषा, साथ ही Android ऐप के अंदर Android शेल की पूरी डिवाइस रिपोर्ट।

लौटाता है: DeviceInfo तुरंत: हमेशा { platform, language } - ऐप में platform का मान 'android' और ब्राउज़र में 'web' होता है। Android ऐप में यह model, manufacturer, osVersion (जैसे "14") और appVersion (जैसे "1.4.0") भी भरता है, और WebToApk.getDeviceInfo() की हर कुंजी साथ रखता है: android, hardware, screen, app, runtime, installId। वेब पर यह osVersion (यूज़र एजेंट) जोड़ता है। ज़रूरत: कुछ नहीं।

रेंडर के दौरान प्लैटफ़ॉर्म के हिसाब से शाखा बनाएँ।

import { deviceInfo } from '@/lib/appmintNative';

const info = deviceInfo();
const isAndroidApp = info.platform === 'android';
const lang = info.language;                         // e.g. "en-IN"

Android की जानकारी पढ़ें। आम फ़ील्ड ऊपर हैं; शेल की बाकी रिपोर्ट अपने नेस्टेड समूहों में रहती है।

import { deviceInfo } from '@/lib/appmintNative';

const info: any = deviceInfo();
if (info.platform === 'android') {
  console.log(`${info.manufacturer} ${info.model}`);        // "OPPO CPH2269"
  console.log(`Android ${info.osVersion}, app ${info.appVersion}`);
  console.log(`Battery ${info.runtime?.batteryLevel}%`);
}

प्लेटफ़ॉर्म के हिसाब से सही कंट्रोल चुनें।

import { deviceInfo } from '@/lib/appmintNative';

export function ShareHint() {
  const { platform, language } = deviceInfo();
  return platform === 'android'
    ? <p>Tap Share to send this from your phone.</p>
    : <p>Copy the link to share it ({language}).</p>;
}

ध्यान दें: model और appVersion सिर्फ़ ऐप में भरे जाते हैं। वेब पर osVersion ब्राउज़र की user-agent स्ट्रिंग है, OS वर्ज़न नहीं। अगर शेल का जवाब मान्य JSON नहीं है तो यह त्रुटि फेंकता है (असली गड़बड़ी, कभी अपेक्षित नहीं)।

appmintNative.Dialog appmintNative#

Dialog.alert / Dialog.confirm / Dialog.prompt

उदाहरण

असली सिस्टम डायलॉग: एक संदेश, हाँ/नहीं का सवाल, एक पंक्ति का टेक्स्ट सवाल। ऐप Capacitor Dialog प्लगइन इस्तेमाल करता है; वेब ब्राउज़र का अपना इस्तेमाल करता है alert / confirm / prompt.

लौटाता है: alert({ title?, message }) → Promise<void>। confirm({ title?, message, okButtonTitle?, cancelButtonTitle? }) → Promise<{ value: boolean }>। prompt({ title?, message, inputPlaceholder?, inputText? }) → Promise<{ value: string; cancelled: boolean }>। इनमें से कोई रिजेक्ट नहीं होता। ज़रूरत: कुछ नहीं।

import { Dialog } from '@/lib/appmintNative';

async function deleteList(id: string) {
  const { value } = await Dialog.confirm({
    title: 'Delete list?',
    message: 'All items in it will be removed.',
    okButtonTitle: 'Delete',
    cancelButtonTitle: 'Keep',
  });
  if (value) await removeList(id);
}

Alert और prompt:

import { Dialog } from '@/lib/appmintNative';

await Dialog.alert({ title: 'Offline', message: 'Your changes will sync when you are back online.' });

const r = await Dialog.prompt({ title: 'Rename', message: 'New name', inputText: current, inputPlaceholder: 'List name' });
if (!r.cancelled && r.value.trim()) rename(r.value.trim());

साइन आउट करने से पहले पुष्टि करें। डायलॉग को खारिज करना "नहीं" गिना जाता है।

import { Dialog, toast } from '@/lib/appmintNative';

export async function signOut(doSignOut: () => Promise<void>) {
  const { value } = await Dialog.confirm({
    title: 'Sign out?',
    message: 'You will need to sign in again.',
    okButtonTitle: 'Sign out',
    cancelButtonTitle: 'Stay',
  });
  if (!value) return;
  await doSignOut();
  toast('Signed out');
}

ध्यान दें: कन्फ़र्म खारिज करने पर { value: false } मिलता है; प्रॉम्प्ट खारिज करने पर { value: '', cancelled: true }। डायलॉग स्क्रीन रोक देते हैं - इन्हें असली फ़ैसलों के लिए इस्तेमाल करें, और “सेव हो गया” जैसे संदेशों के लिए toast()। prompt प्लगइन को कस्टम बटन टाइटल नहीं भेजता।

appmintNative.dismissPushMessage appmintNative#

import { dismissPushMessage } from '@/lib/appmintNative'

उदाहरण

AppMint Push इनबॉक्स से एक संदेश छिपाता है (स्वाइप करके हटाना)। इसे वापस करें restorePushMessage(id).

लौटाता है: boolean, तुरंत - छिपा हुआ था तो true। वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { dismissPushMessage, restorePushMessage, pushInbox } from '@/lib/appmintNative';

export function swipeAway(id: string, setItems: (v: ReturnType<typeof pushInbox>) => void,
                          showUndo: (onUndo: () => void) => void) {
  if (!dismissPushMessage(id)) return;
  setItems(pushInbox());
  showUndo(() => {                         // e.g. a 5-second "Undo" snackbar
    restorePushMessage(id);
    setItems(pushInbox());
  });
}

इनबॉक्स को स्वाइप हैंडलर और undo स्थिति के साथ दिखाएँ, ताकि स्वाइप कभी अंतिम न हो।

import { useState } from 'react';
import { pushInbox, dismissPushMessage, restorePushMessage } from '@/lib/appmintNative';

export function Inbox() {
  const [items, setItems] = useState(pushInbox());
  const [undoId, setUndoId] = useState<string | null>(null);

  const swipe = (id: string) => {
    if (!dismissPushMessage(id)) return;     // false on the web or when AppMint Push is off
    setItems(pushInbox());
    setUndoId(id);
  };
  const undo = () => {
    if (undoId && restorePushMessage(undoId)) setItems(pushInbox());
    setUndoId(null);
  };

  return (
    <ul>
      {items.map((m) => <li key={m.id} onClick={() => swipe(m.id)}>{m.title}</li>)}
      {undoId && <button onClick={undo}>Undo</button>}
    </ul>
  );
}

पढ़े हुए सभी संदेश एक साथ छिपाएँ, और बताएँ कि कितने छिपाए गए।

import { pushInbox, dismissPushMessage, toast } from '@/lib/appmintNative';

export function tidyReadMessages(): number {
  const hidden = pushInbox().filter((m) => m.read && dismissPushMessage(m.id)).length;
  toast(hidden ? `${hidden} message(s) hidden` : 'Nothing to hide');
  return hidden;
}

ध्यान दें: सिर्फ़ इस फ़ोन का इनबॉक्स बदलता है; ट्रे की नोटिफ़िकेशन और आपका भेजने का इतिहास बना रहता है।

appmintNative.docScanner appmintNative#

docScanner.scan({ pageLimit?, pdf?, quality? }): Promise<{ pages, pdfPath? } | null> / docScanner.isSupported(): Promise<boolean>

उदाहरण

सिस्टम का डॉक्यूमेंट स्कैनर खोलता है - किनारे पहचानना, सीधा करना, कई पेज - Android ऐप में Google का ML Kit स्कैनर। पेज JPEG data URL के रूप में लौटते हैं।

लौटाता है: Promise<{ pages: string[]; pdfPath?: string } | null> - यूज़र के रद्द करने, स्कैनर शुरू न हो पाने, या वेब पर null। isSupported() → Promise<boolean>: क्या यह फ़ोन स्कैन कर सकता है (Google Play services); वेब पर false। ज़रूरत: चालू करने के लिए कुछ नहीं (Android पर स्कैनर Google Play services में चलता है और कैमरा अनुमति नहीं माँगता)।

import { docScanner, can } from '@/lib/appmintNative';

export function ScanButton({ onPages }: { onPages: (p: string[]) => void }) {
  if (can('docScanner') === 'none') return null;     // no web scanner: hide the button
  return (
    <button onClick={async () => {
      const res = await docScanner.scan({ pageLimit: 3, quality: 0.8 });
      if (res) onPages(res.pages);                   // each a data:image/jpeg;base64,… URL
    }}>
      Scan document
    </button>
  );
}

पहले फ़ोन से पूछें, क्योंकि पुराने डिवाइस में स्कैनर न हो सकता है, फिर पन्ने रखें।

import { useEffect, useState } from 'react';
import { docScanner, can } from '@/lib/appmintNative';

export function ScanPanel() {
  const [ready, setReady] = useState(false);
  const [pages, setPages] = useState<string[]>([]);

  useEffect(() => {
    if (can('docScanner') === 'none') return;          // web: no scanner at all
    docScanner.isSupported().then(setReady);           // false without Google Play services
  }, []);

  if (!ready) return <p>Scanning is not available on this phone.</p>;
  return (
    <>
      <button onClick={async () => {
        const res = await docScanner.scan({ pageLimit: 5 });
        if (!res) return;                              // cancelled or could not start
        setPages((p) => [...p, ...res.pages]);
      }}>Scan pages</button>
      {pages.map((src, i) => <img key={i} src={src} alt={`Page ${i + 1}`} width={120} />)}
    </>
  );
}

स्कैन करें, फिर file-transfer हेल्पर से पहला पेज अपलोड करें।

import { docScanner, fileTransfer } from '@/lib/appmintNative';

export async function scanAndUpload(): Promise<boolean> {
  const res = await docScanner.scan({ pageLimit: 1, quality: 0.7 });
  if (!res || res.pages.length === 0) return false;
  const blob = await (await fetch(res.pages[0])).blob();
  const file = new File([blob], 'page-1.jpg', { type: 'image/jpeg' });
  const r = await fileTransfer.upload('https://example.com/api/scans', file, { fieldName: 'scan' });
  return r.responseCode >= 200 && r.responseCode < 300;
}

नोट: pageLimit 0 (डिफ़ॉल्ट) = कोई सीमा नहीं; quality 0.1-1 (डिफ़ॉल्ट 0.85)। pdf: true के साथ, pdfPath ऐप के कैश में फ़ाइल पाथ है, ऐसा URL नहीं जिसे पेज लोड कर सके। Data URL बड़े होते हैं - उन्हें localStorage में कई रखने के बजाय छोटा करें या अपलोड करें। सादी फ़ोटो के लिए इस्तेमाल करें camera.take().

appmintNative.enableNotifications appmintNative#

enableNotifications(): Promise<EnableNotificationsResult>

उदाहरण

उस उपयोगकर्ता को वापस लाता है जिसने नोटिफ़िकेशन अस्वीकार या छोड़े थे, उसके टैप किए बटन से: जब तक अनुमति डायलॉग दिखाया जा सके तब तक वही, वरना (Android ऐप) इस ऐप की नोटिफ़िकेशन सेटिंग्स।

लौटाता है: Promise<{ state, changed, openedSettings }> - state का मान 'granted' | 'prompt' | 'blocked' | 'unavailable' है, changed true होता है जब वह कॉल से पहले से अलग हो, openedSettings true होता है जब सेटिंग्स स्क्रीन इस्तेमाल हुई। यह कभी रिजेक्ट नहीं होता। वेब पर यह Notification.requestPermission() से पूछता है। ज़रूरत: बिल्ड में नोटिफ़िकेशन (जब भी ऐप नोटिफ़िकेशन भेजता या पाता है, वे चालू रहते हैं)।

import { enableNotifications, getNotificationState } from '@/lib/appmintNative';

export async function TurnOnNotificationsBanner(el: HTMLElement) {
  const state = await getNotificationState();
  el.hidden = !(state === 'prompt' || state === 'blocked');
  el.querySelector('button')!.addEventListener('click', async () => {
    const r = await enableNotifications();
    el.hidden = r.state === 'granted';
  });
}

ध्यान दें: बैनर सिर्फ़ 'prompt' और 'blocked' के लिए दिखाएँ; 'granted' और 'unavailable' के लिए छिपाएँ। टैप से माँगें, लॉन्च पर कभी नहीं।

appmintNative.fileTransfer appmintNative#

fileTransfer.download / fileTransfer.upload from '@/lib/appmintNative'

उदाहरण

प्रोग्रेस कॉलबैक के साथ फ़ाइल डाउनलोड या अपलोड करता है। ऐप में ट्रांसफ़र नेटिव रूप से चलता है (Capacitor FileTransfer); वेब पर वही कॉलबैक देकर fetch / XMLHttpRequest इस्तेमाल होता है।

लौटाता है: download(url, fileName, opts?) → Promise<{ path?: string; blob?: Blob }> - ऐप में path (ऐप के कैश की फ़ाइल), वेब पर blob (जहाँ फ़ाइल ब्राउज़र से भी सेव होती है)। upload(url, file, opts?) → Promise<{ responseCode: number; responseBody: string }>। दोनों विफलता पर रिजेक्ट होते हैं। ज़रूरत: चालू करने को कुछ नहीं।

प्रोग्रेस बार के साथ डाउनलोड करें:

import { fileTransfer, fileViewer } from '@/lib/appmintNative';

async function getReport(bar: HTMLProgressElement) {
  try {
    const r = await fileTransfer.download('https://example.com/report.pdf', 'report.pdf', {
      headers: { Authorization: `Bearer ${token}` },
      onProgress: (p) => { bar.value = p.fraction; },     // {bytes, total, fraction 0..1}
    });
    if (r.path) await fileViewer.open(r.path);
  } catch (e) {
    alert(`Download failed: ${String(e)}`);
  }
}

इससे File अपलोड करें <input type="file">:

import { fileTransfer } from '@/lib/appmintNative';

async function sendPhoto(file: File) {
  const r = await fileTransfer.upload('https://example.com/api/photos', file, {
    fieldName: 'photo',                // form field name, default 'file'
    headers: { Authorization: `Bearer ${token}` },
    onProgress: (p) => console.log(Math.round(p.fraction * 100) + '%'),
  });
  if (r.responseCode >= 200 && r.responseCode < 300) console.log('uploaded', r.responseBody);
  else alert(`Upload failed (${r.responseCode})`);
}

प्रोग्रेस स्थिति के साथ डाउनलोड करें, फिर फ़ाइल खोलें, एक ही हुक में।

import { useState } from 'react';
import { fileTransfer, fileViewer } from '@/lib/appmintNative';

export function useOpenDocument() {
  const [percent, setPercent] = useState(0);
  const [error, setError] = useState<string | null>(null);

  const run = async (url: string, name: string) => {
    setError(null);
    try {
      const r = await fileTransfer.download(url, name, { onProgress: (p) => setPercent(Math.round(p.fraction * 100)) });
      if (r.path) await fileViewer.open(r.path);        // in the app; on the web the browser saved it
    } catch (e) {
      setError(String(e));
    }
  };
  return { percent, error, run };
}

ध्यान दें: जब तक सर्वर लंबाई नहीं भेजता fraction का मान 0 रहता है। ऐप में डाउनलोड ऐप के CACHE फ़ोल्डर में जाता है (यूज़र के Downloads में नहीं); यूज़र को कॉपी देने के लिए saveFile इस्तेमाल करें। अपलोड के लिए ऐप पहले फ़ाइल को अपने कैश में कॉपी करता है, इसलिए बहुत बड़ी फ़ाइलों के लिए उतनी खाली जगह चाहिए। method (डिफ़ॉल्ट 'POST') और fileName वैकल्पिक अपलोड विकल्प हैं।

appmintNative.fileViewer appmintNative#

fileViewer.open(target) from '@/lib/appmintNative'

उदाहरण

फ़ोन के व्यूअर ऐप में दस्तावेज़ (PDF, तस्वीर, ऑफ़िस फ़ाइल) खोलता है। वेब पर यह पता नए टैब में खोलता है।

लौटाता है: Promise<boolean> - व्यूअर खुलने पर true, विफल होने पर (उस प्रकार के लिए ऐप नहीं, ग़लत पाथ) या इस सरफ़ेस पर टार्गेट न खुलने पर false। ज़रूरत: चालू करने को कुछ नहीं; यह ऐप में शामिल Capacitor FileViewer प्लगइन इस्तेमाल करता है।

वेब से कोई दस्तावेज़ खोलें:

import { fileViewer } from '@/lib/appmintNative';

async function openManual() {
  const ok = await fileViewer.open('https://example.com/manual.pdf');
  if (!ok) alert('No app on this phone can open this file.');
}

पहले fileTransfer.download से डाउनलोड की गई फ़ाइल खोलें (ऐप में लोकल पाथ):

import { fileTransfer, fileViewer } from '@/lib/appmintNative';

const r = await fileTransfer.download('https://example.com/invoice.pdf', 'invoice.pdf');
if (r.path) await fileViewer.open(r.path);            // in the app
else if (r.blob) console.log('saved by the browser'); // on the web

बटन से लिंक खोलें और जब कोई व्यूअर उसे न खोल सके तो यूज़र को बताएँ।

import { fileViewer, toast } from '@/lib/appmintNative';

export function ManualButton({ url }: { url: string }) {
  return (
    <button onClick={async () => {
      if (!(await fileViewer.open(url))) toast('No app can open this file');
    }}>
      View manual
    </button>
  );
}

ध्यान दें: http(s) टारगेट openDocumentFromUrl इस्तेमाल करता है; बाकी सब लोकल पाथ माना जाता है (file:// हटा दिया जाता है)। वेब पर सिर्फ़ http(s):, blob: और data: टारगेट खुलते हैं; लोकल पाथ जवाब देता है false.

appmintNative.folder appmintNative#

import { folder } from '@/lib/appmintNative'

उदाहरण

एक पूरा फ़ोल्डर जिसकी अनुमति यूज़र एक बार देता है और ऐप हर लॉन्च में रखता है, उसके अंदर असली फ़ाइल ऑपरेशन के साथ (ऐप में: शेल का फ़ोल्डर ब्रिज)।

लौटाता है: हर सदस्य async है - connected() → boolean, label() → string, request() → boolean (रद्द करने पर false), disconnect() → void, list(path) → [{ name, uri, isDirectory, length, lastModified }], readText(path) → string | null (null = ऐसी कोई फ़ाइल नहीं), writeText(path, text) / mkdir(path) / rename(path, newName) / remove(path, recursive) → boolean, stat(path) → { name, size, mime, lastModified, uri, isDirectory, canWrite } | null। ज़रूरत: जब कोड folder इस्तेमाल करता है तो AI से बना ऐप Native Folder Access (SAF) स्विच खुद चालू कर लेता है; हाथ से बने बिल्ड में इसे चरण 4 (Access) में टिक करें।

कनेक्ट करें, फ़ोल्डर दिखाएँ, और डिस्कनेक्ट करें:

import { can, folder } from '@/lib/appmintNative';

export async function setupFolderPanel(el: { name: HTMLElement; connect: HTMLButtonElement; disconnect: HTMLButtonElement }) {
  if (can('folderAccess') === 'none') { el.connect.hidden = true; return; }   // web build, or not shipped

  const refresh = async () => {
    const on = await folder.connected();
    el.name.textContent = on ? `Saving to: ${await folder.label()}` : 'No folder chosen';
    el.connect.hidden = on;
    el.disconnect.hidden = !on;
  };

  el.connect.onclick = async () => { await folder.request(); await refresh(); };   // from a tap only
  el.disconnect.onclick = async () => { await folder.disconnect(); await refresh(); };
  await refresh();
}

पढ़ें, लिखें, सूची बनाएँ, नाम बदलें और हटाएँ - हर पाथ दी गई फ़ोल्डर के सापेक्ष है ('' उसका रूट है):

import { folder } from '@/lib/appmintNative';

await folder.mkdir('notes');
const saved = await folder.writeText('notes/today.md', '# Today\n');   // parents created
const text = await folder.readText('notes/today.md');                  // null if missing
const entries = await folder.list('notes');
entries.forEach((e) => console.log(e.isDirectory ? 'dir ' : 'file', e.name, e.length));

const info = await folder.stat('notes/today.md');
if (info) console.log(info.size, 'bytes, writable:', info.canWrite);

await folder.rename('notes/today.md', 'journal.md');                   // a new NAME, not a path
await folder.remove('notes', true);                                    // recursive for a non-empty folder

नोट का बैकअप दी गई अनुमति वाले फ़ोल्डर में लें, और कोई फ़ोल्डर जुड़ा न हो तो साफ़ शब्दों में यह बताएँ।

import { can, folder, toast } from '@/lib/appmintNative';

export async function backupNote(name: string, text: string) {
  if (can('folderAccess') === 'none') return;
  if (!(await folder.connected()) && !(await folder.request())) {
    toast('Choose a folder to save backups');
    return;
  }
  const ok = await folder.writeText(`backups/${name}.md`, text);
  toast(ok ? 'Backed up' : 'Could not write to the folder');
}

ध्यान दें: वेब पर can('folderAccess') का मान 'none' है - फ़ोन ब्राउज़र में स्थायी फ़ोल्डर जैसा कुछ नहीं है, इसलिए वहाँ यह फ़ीचर न दिखाएँ (अकेली फ़ाइलों के लिए pickFile() / saveFile() इस्तेमाल करें)। request() सिर्फ़ यूज़र के टैप से कॉल करें। .. और एब्सोल्यूट पाथ नामंज़ूर हैं। अनुमति मिला फ़ोल्डर खुद कभी हटाया नहीं जा सकता। stat() फ़ाइलों और सब-फ़ोल्डर के लिए चलता है (फ़ोल्डर में isDirectory: true और size: 0 होता है); stat('') - रूट - का मान null है, उसके लिए label() इस्तेमाल करें। रद्द करने के बाद request() फिर कॉल किया जा सकता है।

appmintNative.fullscreen appmintNative#

fullscreen.enter() / fullscreen.exit() / fullscreen.isOn()

उदाहरण

वीडियो, गेम या पढ़ने के व्यू के लिए सिस्टम बार छिपाता है (ऐप में) या ब्राउज़र फ़ुलस्क्रीन में जाता है (वेब पर)।

लौटाता है: enter() और exit() दोनों Promise<void> लौटाते हैं और कभी रिजेक्ट नहीं होते; isOn() तुरंत boolean लौटाता है। ज़रूरत: कुछ नहीं।

import { useState } from 'react';
import { can, fullscreen } from '@/lib/appmintNative';

export function FullscreenToggle() {
  const [on, setOn] = useState(fullscreen.isOn());
  if (can('fullscreen') === 'none') return null;
  return (
    <button
      onClick={async () => {
        if (fullscreen.isOn()) await fullscreen.exit(); else await fullscreen.enter();
        setOn(fullscreen.isOn());
      }}
    >
      {on ? 'Exit full screen' : 'Full screen'}
    </button>
  );
}

व्यू बंद होने पर फ़ुलस्क्रीन से बाहर निकलें:

import { useEffect } from 'react';
import { fullscreen } from '@/lib/appmintNative';

export function useFullscreenWhileOpen() {
  useEffect(() => {
    void fullscreen.enter();
    return () => { void fullscreen.exit(); };
  }, []);
}

पढ़ने के व्यू के लिए सिस्टम बार छिपाएँ और एक बटन से स्थिति पर नज़र रखें।

import { useEffect, useState } from 'react';
import { fullscreen, can } from '@/lib/appmintNative';

export function useReaderMode() {
  const [on, setOn] = useState(false);
  useEffect(() => () => { void fullscreen.exit(); }, []);        // always leave on unmount
  const toggle = async () => {
    if (can('fullscreen') === 'none') return;
    if (fullscreen.isOn()) await fullscreen.exit(); else await fullscreen.enter();
    setOn(fullscreen.isOn());                                   // a refused web request leaves it off
  };
  return { on, toggle };
}

ध्यान दें:

  • ऐप में यह शेल की अपनी फ़ुलस्क्रीन है (स्टेटस और नेविगेशन बार छिप जाते हैं); यह पेज के लिए हमेशा उपलब्ध है।
  • वेब पर Fullscreen API सिर्फ़ उपयोगकर्ता के टैप से चलता है। अस्वीकार हुआ अनुरोध चुपचाप अनदेखा हो जाता है, इसलिए मान लेने के बजाय बाद में isOn() पढ़ें।

appmintNative.geofences appmintNative#

import { geofences } from '@/lib/appmintNative'

उदाहरण

जियोफ़ेंस जो ऐप बंद होने पर भी चलते हैं और रीस्टार्ट के बाद भी बने रहते हैं: add, remove, list, events, clearEvents, onEvent, available.

लौटाता है: add(spec) → Promise<BgResult> जो Play services के स्वीकार या अस्वीकार करने पर resolve होता है ({ id, ok, reason? } - background_permission_required, location_off, too_many_geofences, …)। remove / clearEvents → Promise<boolean>। list() → Promise<GeofenceSpec[]>। events() → Promise<GeofenceEvent[]> (आख़िरी 100, ऐप बंद होने के दौरान वाले भी)। onEvent(cb) → एक अनसब्सक्राइब। ज़रूरत: बिल्ड में Geofences और "हर समय अनुमति दें" (backgroundLocation.requestAlways()).

import { geofences, backgroundLocation } from '@/lib/appmintNative';

export async function remindAtStore(lat: number, lng: number) {
  let r = await geofences.add({ id: 'store', lat, lng, radiusM: 200, events: ['enter'],
    notify: { title: 'You are near the store', body: 'Your list is ready' } });
  if (!r.ok && r.reason === 'background_permission_required') {
    const p = await backgroundLocation.requestAlways();
    if (p.background === 'granted') r = await geofences.add({ id: 'store', lat, lng, radiusM: 200, events: ['enter'] });
  }
  return r.ok;
}

geofences.onEvent((e) => console.log(e.id, e.event, new Date(e.time)));

चालू जियोफ़ेंस और ऐप बंद रहने के दौरान दर्ज हुए ट्रांज़िशन की सूची दिखाएँ।

import { geofences } from '@/lib/appmintNative';

export async function geofenceReport(): Promise<string[]> {
  if (geofences.available() === 'none') return [];      // web build: no geofences
  const active = await geofences.list();
  const events = await geofences.events();              // oldest first, last 100
  return [
    `${active.length} active zone(s)`,
    ...events.map((e) => `${e.id} ${e.event} at ${new Date(e.time).toLocaleTimeString()}`),
  ];
}

एंटर और एग्ज़िट के लिए एक वर्क ज़ोन जोड़ें, इनकार का जवाब उसके कारण से दें, और बाद में उसे हटा दें।

import { geofences } from '@/lib/appmintNative';

export async function watchOffice(lat: number, lng: number): Promise<string> {
  const r = await geofences.add({ id: 'office', lat, lng, radiusM: 150, events: ['enter', 'exit'] });
  if (r.ok) return 'Office zone is on';
  if (r.reason === 'location_off') return 'Turn location on to use this';
  if (r.reason === 'too_many_geofences') return 'Remove a zone first';
  return `Could not add the zone (${r.reason ?? 'unknown'})`;
}

export async function stopWatchingOffice() {
  await geofences.remove('office');
  await geofences.clearEvents();
}

appmintNative.Geolocation appmintNative#

Geolocation.getCurrentPosition / watchPosition / clearWatch / checkPermissions / requestPermissions

उदाहरण

@capacitor/geolocation वाला रूप: ऐप में Capacitor Geolocation प्लगइन, वेब पर मानक navigator.geolocation। सभी W3C पोज़िशन ऑब्जेक्ट लौटाते हैं।

लौटाता है: getCurrentPosition(opts) → Promise<GeolocationPosition> (अस्वीकार, टाइमआउट या लोकेशन न मिलने पर रिजेक्ट); watchPosition(opts, cb) → Promise<string> (वॉच id); clearWatch({ id }) → Promise<void>; checkPermissions() / requestPermissions() → Promise<{ location: 'granted' | 'denied' | 'prompt' | … }>। ज़रूरत: चरण 4 (Access) में GPS / लोकेशन - जब आपका कोड getCurrentPosition या कॉल करता है तो बिल्ड दोनों चालू कर देता है watchPosition.

मैं कहाँ हूँ, एक बार।

import { Geolocation } from '@/lib/appmintNative';

export async function currentSpot(): Promise<string> {
  try {
    const perm = await Geolocation.checkPermissions();
    if (perm.location !== 'granted') {
      const asked = await Geolocation.requestPermissions();
      if (asked.location !== 'granted') return 'Location is off for this app.';
    }
    const pos = await Geolocation.getCurrentPosition({ enableHighAccuracy: true, timeout: 15000 });
    return `${pos.coords.latitude.toFixed(5)}, ${pos.coords.longitude.toFixed(5)}`;
  } catch {
    return 'Could not get your location. Is location turned on?';
  }
}

एक रन ट्रैक करें, फिर रोकें। कॉलबैक को शुरू में null मिलता है और फ़िक्स फ़ेल होने पर एक एरर।

import { Geolocation } from '@/lib/appmintNative';

let watchId: string | null = null;

export async function startRun(onPoint: (lat: number, lng: number) => void) {
  watchId = await Geolocation.watchPosition({ enableHighAccuracy: true }, (pos, err) => {
    if (!pos) { console.warn('location error', err); return; }
    onPoint(pos.coords.latitude, pos.coords.longitude);
  });
}

export async function stopRun() {
  if (watchId) { await Geolocation.clearWatch({ id: watchId }); watchId = null; }
}

एक बार की लोकेशन पाएँ। यूज़र के मना करने पर null लौटाता है।

import { Geolocation } from '@/lib/appmintNative';

export async function nearestCoords(): Promise<{ lat: number; lng: number } | null> {
  try {
    const pos = await Geolocation.getCurrentPosition({ timeout: 10000 });
    return { lat: pos.coords.latitude, lng: pos.coords.longitude };
  } catch {
    return null;                          // refused, timed out or location is off
  }
}

ध्यान दें: सादा navigator.geolocation भी ऐप में चलता है; Capacitor वाला रूप चाहिए तो इसे इस्तेमाल करें। Android 12+ पर यूज़र सिर्फ़ अनुमानित लोकेशन की अनुमति दे सकता है - तब भी आपको पोज़िशन मिलती है, बस कम सटीक। वेब पर requestPermissions() ब्राउज़र का प्रॉम्प्ट लाने के लिए एक असली पोज़िशन रिक्वेस्ट करता है। लोकेशन निजी डेटा है: इसे अपने Play Data safety फ़ॉर्म में घोषित करें।

appmintNative.getNotificationState appmintNative#

getNotificationState(): Promise<NotificationState>

उदाहरण

नोटिफ़िकेशन चालू हैं या नहीं, और अगर नहीं तो ऐप फिर से पूछ सकता है या सिर्फ़ Settings ठीक कर सकती है।

लौटाता है: Promise<'granted' | 'prompt' | 'blocked' | 'unavailable'>। वेब पर, Notification.permission को उसी तरह मैप किया जाता है (default → 'prompt', denied → 'blocked'); जहाँ नोटिफ़िकेशन बिल्कुल नहीं हैं वहाँ 'unavailable'। ज़रूरत: कुछ नहीं।

import { getNotificationState } from '@/lib/appmintNative';

export async function shouldShowNotifBanner(): Promise<boolean> {
  const state = await getNotificationState();
  return state === 'prompt' || state === 'blocked';
}

ध्यान दें: ऐप के फ़ोरग्राउंड में लौटने पर (visibilitychange) फिर से जाँचें - यूज़र ने सेटिंग्स में इसे बदला हो सकता है।

appmintNative.getPushTags appmintNative#

import { getPushTags } from '@/lib/appmintNative'

उदाहरण

इस इंस्टॉल के AppMint Push टैग एक ऑब्जेक्ट के रूप में पढ़ता है।

लौटाता है: तुरंत Record<string, string> - जैसे { plan: 'pro', cart: 'open' }; कोई न होने पर और वेब पर {}। ज़रूरत: Android: बिल्ड करते समय इंटीग्रेट चरण (चरण 3) में AppMint Push चालू हो।

import { getPushTags, setPushTag } from '@/lib/appmintNative';

// Every setPushTag makes the install check in, so skip the calls that change nothing.
export function ensurePushTag(key: string, value: string) {
  if ((getPushTags()[key] ?? '') !== value) setPushTag(key, value);
}

ensurePushTag('plan', 'pro');

जब कोई स्क्रीन एक से ज़्यादा टैग बदलती है, तो setPushTags से कई टैग एक बदलाव में सेट करें, और किसी टैग को हटाने के लिए उसका मान खाली करें:

import { setPushTags } from '@/lib/appmintNative';

// Checkout finished: the cart tag goes away and the plan tag is set, all saved or none.
export function onCheckoutDone(plan: string) {
  const saved = setPushTags({ cart: '', plan });
  if (!saved) console.warn('push tags not saved (web, AppMint Push off, or a refused key or value)');
}

टैग वापस पढ़ें, ताकि संकेत तभी दिखे जब इस इंस्टॉल में कोई टैग हो:

import { getPushTags } from '@/lib/appmintNative';

export function OffersHint() {
  const tags = getPushTags();                       // {} on the web
  if (Object.keys(tags).length === 0) return null;
  return <p>Offers for the {tags.plan ?? 'standard'} plan are on.</p>;
}

नोट: यह वह है जो फ़ोन के पास है; सेंड सेंटर इसे इंस्टॉल की अगली चेक-इन के बाद देखता है।

appmintNative.getPushUser appmintNative#

import { getPushUser } from '@/lib/appmintNative'

उदाहरण

वह यूज़र id पढ़ता है जो इस इंस्टॉल ने आख़िरी बार AppMint Push को दी थी setPushUser.

लौटाता है: string, तुरंत - सेव की गई id, या साइन आउट होने, कभी सेट न होने, या वेब पर होने पर ''। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { getPushUser, setPushUser } from '@/lib/appmintNative';

// On app start: the session expired while the app was closed, so stop personal messages here.
export function syncPushUser(currentUserId: string | null) {
  const want = currentUserId ?? '';
  if (getPushUser() !== want) setPushUser(want);
}

साइन-इन के बाद अकाउंट id के साथ setPushUser कॉल करें और साइन-आउट पर '' के साथ; id अस्वीकार होने या AppMint Push बंद होने पर यह false लौटाता है:

import { setPushUser } from '@/lib/appmintNative';

export function onSignedIn(userId: string) {
  if (!setPushUser(userId)) console.warn('push user id not saved');   // ids only: no email or phone number
}

export function onSignedOut() {
  setPushUser('');
}

सेव की गई id को React हुक में पढ़ें, उस यूज़र के टैग के साथ:

import { useEffect, useState } from 'react';
import { getPushUser, setPushTag } from '@/lib/appmintNative';

export function usePushUser(plan: string) {
  const [id] = useState(getPushUser);
  useEffect(() => {
    if (id) setPushTag('plan', plan);     // only tag installs that belong to a signed-in user
  }, [id, plan]);
  return id;
}

ध्यान दें: यह वह है जो फ़ोन के पास है; सेंड सेंटर को बदलाव इंस्टॉल के अगले चेक-इन के बाद दिखता है, ऑनलाइन होने पर कुछ सेकंड बाद।

appmintNative.getVolume appmintNative#

getVolume(stream) — @/lib/appmintNative

उदाहरण

एक स्ट्रीम के लिए फ़ोन का अपना वॉल्यूम स्तर पढ़ता है: 'media', 'alarm', 'ring', 'notification' या 'call'.

लौटाता है: { stream, current, max, percent, muted? } (current/max फ़ोन के अपने चरणों में, percent 0-100), या वेब पर null। ज़रूरत: कुछ नहीं।

import { getVolume } from '@/lib/appmintNative';

export function AlarmVolumeHint() {
  const v = getVolume('alarm');
  if (!v || v.percent > 0) return null;
  return <p role="alert">Your alarm volume is off, so the alarm will not be heard.</p>;
}

getVolume वेब पर null जवाब देता है, इसलिए स्तर तभी बनाएँ जब वह ऑब्जेक्ट हो:

import { getVolume } from '@/lib/appmintNative';

export function MediaLevel() {
  const v = getVolume('media');
  if (!v) return null;                       // web: no phone volume to show
  return <progress max={100} value={v.percent} aria-label="Media volume" />;
}

onVolumeChange से हार्डवेयर कुंजियों के साथ चलें, और धीमे स्तर को बढ़ाएँ, इसके साथ setVolume:

import { useEffect, useState } from 'react';
import { getVolume, onVolumeChange, setVolume, requestDndAccess } from '@/lib/appmintNative';

export function useMediaPercent(): number | null {
  const [pct, setPct] = useState(() => getVolume('media')?.percent ?? null);
  useEffect(() => onVolumeChange((l) => { if (l.stream === 'media') setPct(l.percent); }), []);
  return pct;
}

export function raiseIfQuiet() {
  const v = getVolume('media');
  if (!v || v.percent >= 40) return;
  const r = setVolume('media', 60, true);
  if (!r.ok && r.error === 'dnd_access_required') requestDndAccess();   // the user must allow Do Not Disturb access
}

appmintNative.haptic appmintNative#

haptic(kind?) — @/lib/appmintNative

उदाहरण

टैप और नतीजों के लिए एक छोटा, नेटिव जैसा कंपन। हर मायने वाले टैप पर इसे कॉल करें।

लौटाता है: void (फ़ायर एंड फॉरगेट)। kind: 'light' (डिफ़ॉल्ट), 'medium', 'heavy', 'selection', 'success', 'warning', 'error'। ज़रूरत: चरण 4 (Access) में Vibrate; कोड के कॉल करने पर AI बिल्ड इसे आपके लिए चालू कर देते हैं haptic.

import { haptic } from '@/lib/appmintNative';

export function SaveButton({ onSave }: { onSave: () => Promise<boolean> }) {
  return (
    <button
      onClick={async () => {
        haptic();                          // light tap on press
        const ok = await onSave();
        haptic(ok ? 'success' : 'error');  // result feedback
      }}
    >
      Save
    </button>
  );
}

टैब, टॉगल और पिकर के लिए 'selection' इस्तेमाल करें; डिलीट कन्फ़र्म के लिए इस्तेमाल करें 'heavy':

import { haptic } from '@/lib/appmintNative';

function onTabChange(tab: string) { haptic('selection'); setTab(tab); }
function onConfirmDelete() { haptic('heavy'); deleteItem(); }

एक लाइक बटन जो चालू करने पर हल्का और बंद करने पर ज़्यादा कड़ा टिक देता है:

import { haptic } from '@/lib/appmintNative';

export function LikeButton({ liked, onToggle }: { liked: boolean; onToggle: () => void }) {
  return (
    <button aria-pressed={liked} onClick={() => { haptic(liked ? 'light' : 'medium'); onToggle(); }}>
      {liked ? 'Liked' : 'Like'}
    </button>
  );
}

नोट: ऐप में यह Capacitor Haptics प्लगइन इस्तेमाल करता है (impact, notification, selectionChanged)। वेब पर जहाँ ब्राउज़र में navigator.vibrate हो वहाँ वह इस्तेमाल होता है, और बाकी जगह कुछ नहीं होता; यह कभी एरर नहीं देता। लंबी लय के लिए इस्तेमाल करें hapticPlay.

appmintNative.hapticPlay appmintNative#

hapticPlay(pattern) — @/lib/appmintNative

उदाहरण

ख़ास पल (जीत, लगातार सिलसिला, धड़कन) के लिए समृद्ध कंपन पैटर्न बजाता है। सामान्य टैप के लिए haptic() इस्तेमाल करें।

लौटाता है: Promise<boolean>: पैटर्न चल जाने पर true, न चल पाने पर false। ज़रूरत: चरण 4 (Access) में Vibrate।

नामित पैटर्न: 'success-ramp', 'heartbeat', 'tick-tick-thud', 'drum-roll', 'nudge'.

import { hapticPlay } from '@/lib/appmintNative';

async function onLevelComplete() {
  const felt = await hapticPlay('success-ramp');
  if (!felt) console.log('No vibration on this device');
  showConfetti();
}

आपका अपना पैटर्न: { at, duration?, intensity?, sharpness? } की सूची (ms, ms, 0-1, 0-1):

import { hapticPlay } from '@/lib/appmintNative';

await hapticPlay([
  { at: 0,   intensity: 0.4 },               // soft tap
  { at: 150, intensity: 0.4 },               // soft tap
  { at: 320, duration: 120, intensity: 1 },  // strong buzz
]);

इसे haptic() के साथ मिलाएँ: हर जवाब पर सामान्य टैप फ़ीडबैक, और लंबा पैटर्न सिर्फ़ ख़ास पल के लिए:

import { haptic, hapticPlay } from '@/lib/appmintNative';

export async function onAnswer(correct: boolean, streak: number) {
  haptic(correct ? 'success' : 'error');            // every answer
  if (correct && streak > 0 && streak % 5 === 0) {
    await hapticPlay('drum-roll');                  // every fifth in a row
  }
}

नोट: ऐप AppwrightHaptics Capacitor प्लगइन इस्तेमाल करता है (जहाँ फ़ोन समर्थन करे वहाँ हर बीट की ताकत)। वेब को सिर्फ़ टाइमिंग मिलती है। sharpness स्वीकार होता है पर Android पर उसका कोई असर नहीं। haptic.play(pattern) वही फ़ंक्शन है।

appmintNative.Haptics appmintNative#

Haptics — @/lib/appmintNative

उदाहरण

Capacitor-शैली का Haptics ऑब्जेक्ट: @capacitor/haptics जैसे ही नाम और विकल्प, उसी कोड पर चलता है जिस पर haptic().

लौटाता है: हर मेथड void लौटाता है (Promise नहीं; उस पर await लगाना नुकसानदायक नहीं)। ज़रूरत: चरण 4 (Access) में Vibrate; AI बिल्ड इसे आपके लिए चालू कर देते हैं जब कोड इस्तेमाल करता है Haptics.

import { Haptics } from '@/lib/appmintNative';

Haptics.impact({ style: 'LIGHT' });        // 'LIGHT' | 'MEDIUM' | 'HEAVY'  (default 'LIGHT')
Haptics.notification({ type: 'SUCCESS' }); // 'SUCCESS' | 'WARNING' | 'ERROR' (default 'SUCCESS')
Haptics.vibrate({ duration: 300 });        // ms, default 300

एक पिकर जो मान बदलने पर टिक करता है:

import { Haptics } from '@/lib/appmintNative';

export function Stepper({ value, onChange }: { value: number; onChange: (v: number) => void }) {
  const step = (d: number) => { Haptics.selectionChanged(); onChange(value + d); };
  return (
    <div onPointerDown={() => Haptics.selectionStart()} onPointerUp={() => Haptics.selectionEnd()}>
      <button aria-label="Less" onClick={() => step(-1)}>-</button>
      <span>{value}</span>
      <button aria-label="More" onClick={() => step(1)}>+</button>
    </div>
  );
}

एक फ़ॉर्म जो नोटिफ़िकेशन बज़ से पुष्टि करता है और फ़ील्ड गलत होने पर छोटा कंपन जोड़ता है:

import { Haptics } from '@/lib/appmintNative';

export function onSubmit(valid: boolean) {
  Haptics.notification({ type: valid ? 'SUCCESS' : 'ERROR' });
  if (!valid) Haptics.vibrate({ duration: 200 });
}

ध्यान दें: selectionStart और selectionChanged दोनों एक सिलेक्शन टिक देते हैं; selectionEnd कुछ नहीं करता। यह ऐप में चलता है (Capacitor Haptics प्लगइन); वेब पर जहाँ उपलब्ध हो वहाँ navigator.vibrate इस्तेमाल करता है, वरना कुछ नहीं करता।

appmintNative.has appmintNative#

has(cap: Capability): boolean

उदाहरण

can() का हाँ/नहीं रूप: true जब यह फ़ीचर इस सतह पर कुछ असली करेगा।

लौटाता है: तुरंत boolean - ठीक can(cap) !== 'none'। ज़रूरत: कुछ नहीं।

import { has, copy, toast } from '@/lib/appmintNative';

export function CopyCode({ code }: { code: string }) {
  if (!has('clipboardWrite')) return <code>{code}</code>;   // no button where it cannot copy
  return (
    <button onClick={async () => { if (await copy(code)) toast('Copied'); }}>
      Copy {code}
    </button>
  );
}

पूरे फ़ीचर को गेट करें:

import { has, scanCode } from '@/lib/appmintNative';

export async function onScanTap(): Promise<string | null> {
  if (!has('codeScanner')) return null;     // better: do not render the Scan button at all
  const hit = await scanCode({ formats: ['qr_code'] });
  return hit ? hit.value : null;
}

लेबल इस आधार पर चुनें कि वह कहाँ चलता है: has() तय करता है कि कंट्रोल बनाना है या नहीं, can() बताता है कि वह किस तरह का है:

import { can, has } from '@/lib/appmintNative';

export function shareLabel(): string | null {
  if (!has('share')) return null;                      // nothing to share with: draw no button
  return can('share') === 'native' ? 'Share…' : 'Share link';
}

ध्यान दें: यह वही क्षमता नाम लेता है जो can() लेता है (पूरी सूची के लिए वह एंट्री देखें)। जब यह जानना हो कि यह कहाँ चल रहा है ('native' या 'web'), जैसे लेबल बदलने के लिए, तब can() इस्तेमाल करें।

appmintNative.hideKeyboard appmintNative#

hideKeyboard(): void

उदाहरण

स्क्रीन का कीबोर्ड बंद करता है - जैसे फ़ॉर्म सबमिट होने पर या फ़ील्ड के ऊपर शीट खुलने पर।

लौटाता है: कुछ नहीं। ज़रूरत: कुछ नहीं।

import { hideKeyboard, toast } from '@/lib/appmintNative';

export function SearchForm({ onSearch }: { onSearch: (q: string) => void }) {
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        const q = new FormData(e.currentTarget).get('q') as string;
        hideKeyboard();                       // show the results, not the keyboard
        onSearch(q);
        if (!q) toast('Type something to search');
      }}
    >
      <input name="q" type="search" enterKeyHint="search" aria-label="Search" />
    </form>
  );
}

जब फ़ील्ड के ऊपर शीट खुले तो कीबोर्ड बंद कर दें, ताकि शीट उसके पीछे न छिपे:

import { useState } from 'react';
import { hideKeyboard } from '@/lib/appmintNative';

export function useSheet() {
  const [open, setOpen] = useState(false);
  return {
    open,
    show: () => { hideKeyboard(); setOpen(true); },
    hide: () => setOpen(false),
  };
}

जब उपयोगकर्ता टेक्स्ट फ़ील्ड के बाहर कहीं भी टैप करे तो इसे बंद करें:

import type { ReactNode } from 'react';
import { hideKeyboard } from '@/lib/appmintNative';

export function Screen({ children }: { children: ReactNode }) {
  return (
    <div onPointerDown={(e) => {
      if (!(e.target as HTMLElement).closest('input, textarea')) hideKeyboard();
    }}>
      {children}
    </div>
  );
}

नोट: ऐप में यह Capacitor Keyboard प्लगइन से कीबोर्ड छिपाने को कहता है। वेब पर यह फ़ोकस वाले फ़ील्ड को ब्लर करता है, जिससे ब्राउज़र में कीबोर्ड बंद होता है। कीबोर्ड खुला न होने पर भी इसे कॉल करना सुरक्षित है।

appmintNative.inAppCampaigns appmintNative#

import { inAppCampaigns } from '@/lib/appmintNative'

उदाहरण

इस इंस्टॉल के पास अभी जो इन-ऐप संदेश हैं, {id, name}[] के रूप में: क्रिएटर के वे कैंपेन जिनकी ऑडियंस में यह उपयोगकर्ता है।

लौटाता है: तुरंत { id: string; name: string }[]; वेब पर और AppMint Push के बिना []। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { inAppCampaigns } from '@/lib/appmintNative';

console.log(inAppCampaigns().map((c) => c.name));

एडमिन स्क्रीन में डीबग सूची:

import { inAppCampaigns } from '@/lib/appmintNative';

export function InAppDebug() {
  const list = inAppCampaigns();
  return <ul>{list.length ? list.map((c) => <li key={c.id}>{c.name}</li>) : <li>None for this install</li>}</ul>;
}

जब क्रिएटर उसी नाम का संदेश पहले से चला रहा हो तो अपना संकेत छोड़ दें:

import { inAppCampaigns } from '@/lib/appmintNative';

export const hasCampaign = (name: string) => inAppCampaigns().some((c) => c.name === name);

appmintNative.installId appmintNative#

installId(): string

उदाहरण

इस इंस्टॉल के लिए एक स्थिर रैंडम ID देता है: Android ऐप में शेल की अपनी ID, वेब पर localStorage में सेव की गई रैंडम ID।

लौटाता है: तुरंत string। ज़रूरत: कुछ नहीं।

मुफ़्त ट्रायल को डिवाइस से जोड़ें।

import { installId } from '@/lib/appmintNative';

export async function claimTrial(): Promise<boolean> {
  let id: string;
  try {
    id = installId();
  } catch {
    return false;          // web with storage blocked (private mode): no stable id exists
  }
  const r = await fetch('https://api.example.com/trial', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ device: id }),
  });
  return r.ok;
}

इस इंस्टॉल को अपने एनालिटिक्स में एक बार गिनें।

import { installId } from '@/lib/appmintNative';

export function reportFirstOpen() {
  let id: string;
  try { id = installId(); } catch { return; }          // web with storage blocked: no stable id
  if (localStorage.getItem('first-open-sent')) return;
  navigator.sendBeacon('https://api.example.com/first-open', JSON.stringify({ install: id }));
  localStorage.setItem('first-open-sent', '1');
}

एक छोटा सपोर्ट कोड दिखाएँ जिसे आप बाद में खोज सकें, कॉपी बटन के साथ।

import { installId, copy, toast } from '@/lib/appmintNative';

export function SupportCode() {
  let code = '';
  try { code = installId().slice(0, 8); } catch { /* storage blocked: show nothing */ }
  if (!code) return null;
  return <button onClick={async () => { if (await copy(code)) toast('Copied'); }}>Support code {code}</button>;
}

ध्यान दें: यह ऐप अपडेट के दौरान वही रहता है और अनइंस्टॉल या “डेटा साफ़ करें” पर बदल जाता है। यह हार्डवेयर ID नहीं है। वेब पर localStorage ब्लॉक हो तो यह जानबूझकर throw करता है - साझा खाली ID यूज़र्स को आपस में मिला देगी।

appmintNative.isAlarmRinging appmintNative#

isAlarmRinging() — @/lib/appmintNative

उदाहरण

क्या ऐप का कोई अलार्म क्लॉक अभी बज रहा है।

लौटाता है: boolean, तुरंत; वेब पर हमेशा false। ज़रूरत: बिल्ड करते समय Access चरण (चरण 4) में 🔔 Alarm clock।

import { useEffect, useState } from 'react';
import { isAlarmRinging, onAlarm } from '@/lib/appmintNative';

export function useRinging(): boolean {
  const [ringing, setRinging] = useState(isAlarmRinging);
  useEffect(() => onAlarm(() => setRinging(isAlarmRinging())), []);
  return ringing;
}

अलार्म बजते समय Stop और Snooze बटन दिखाएँ:

import { isAlarmRinging, stopAlarm, snoozeAlarm } from '@/lib/appmintNative';

export function RingingBar() {
  if (!isAlarmRinging()) return null;               // always false on the web
  return (
    <div role="alert">
      <button onClick={() => stopAlarm()}>Stop</button>
      <button onClick={() => snoozeAlarm('', 5)}>Snooze 5 min</button>
    </div>
  );
}

पहले सपोर्ट जाँचें, ताकि पेज अलार्म कंट्रोल सिर्फ़ वहीं दिखाए जहाँ अलार्म बज सकता है:

import { alarmSupport, isAlarmRinging } from '@/lib/appmintNative';

export function alarmState(): 'hidden' | 'ringing' | 'idle' {
  if (alarmSupport() === 'unavailable') return 'hidden';   // web or iOS: no alarm controls
  return isAlarmRinging() ? 'ringing' : 'idle';
}

appmintNative.isNativeApp appmintNative#

isNativeApp(): boolean

उदाहरण

बताता है कि कोड इंस्टॉल किए Android ऐप के अंदर चल रहा है या नहीं (शेल ब्रिज window.WebToApk मौजूद है)।

लौटाता है: तुरंत boolean। ब्राउज़र में false। ज़रूरत: कुछ नहीं।

"Android app" बैज दिखाएँ, और ब्राउज़र में डाउनलोड लिंक दिखाएँ।

import { isNativeApp } from '@/lib/appmintNative';

export function appBadgeText(): string {
  if (isNativeApp()) return 'Android app';
  return 'Get the app on Google Play';
}

अपने एनालिटिक्स इवेंट पर टैग लगाएँ कि वे कहाँ से आए।

import { isNativeApp, platform } from '@/lib/appmintNative';

export function analyticsSource(): string {
  return isNativeApp() ? `app-${platform()}` : 'browser';   // 'app-android' or 'browser'
}

ऐप के अंदर "Get the app" बैनर छिपाएँ।

import { isNativeApp } from '@/lib/appmintNative';

export function InstallBanner() {
  if (isNativeApp()) return null;        // already installed: no banner
  return <a href="https://play.google.com/store/apps/details?id=com.example.app">Get the app</a>;
}

नोट: platform() वही जवाब स्ट्रिंग के रूप में देता है (ऐप में 'android', ब्राउज़र में 'web')। कोई फ़ीचर दिखाया जा सकता है या नहीं, यह तय करने के लिए इसके बजाय can('<capability>') इस्तेमाल करें: वह जानता है कि किस प्लेटफ़ॉर्म पर कौन सा फ़ीचर है।

appmintNative.keepAlive appmintNative#

import { keepAlive } from '@/lib/appmintNative'

उदाहरण

फ़ोरग्राउंड-सर्विस नोटिफ़िकेशन के साथ ऐप को बैकग्राउंड में चालू रखता है (start, update, stop, isRunning, onState, available).

लौटाता है: start / update → Promise<BgResult> ({ ok, reason?, type?, notificationShown? }; ब्राउज़र या iOS में reason: 'unsupported', बिल्ड ने Keep running in background टिक न किया हो तो 'not_enabled')। stop → Promise<boolean>। isRunning() → बूलियन। onState(cb) → अनसब्सक्राइब फ़ंक्शन; cb को appmint:background-service डिटेल मिलती है। ज़रूरत: बिल्ड में Keep running in background।

import { keepAlive } from '@/lib/appmintNative';

export async function startRecording() {
  if (keepAlive.available() === 'none') return;
  const r = await keepAlive.start({ title: 'Recording', text: 'Tap to return', stopLabel: 'Stop' });
  if (!r.ok) console.warn('keep running refused:', r.reason);
}

const off = keepAlive.onState((d) => {
  if (d.state === 'stopped') console.log('service stopped:', d.reason);
});
// later: await keepAlive.update({ title: 'Recording', text: '12:30' }); await keepAlive.stop(); off();

सर्विस रोकें, और पढ़ें कि start को क्यों मना किया गया:

import { keepAlive } from '@/lib/appmintNative';

export async function toggleTracking(on: boolean) {
  if (!on) { await keepAlive.stop(); return; }
  const r = await keepAlive.start({ title: 'Tracking', text: 'Running', type: 'dataSync' });
  if (!r.ok) {
    // 'unsupported' = browser or iOS; 'not_enabled' = the build did not tick Keep running in background
    alert(r.reason === 'not_enabled' ? 'Turn on Keep running in background and rebuild.' : 'Not available here.');
  }
}

React बटन को सर्विस के साथ मिलाकर रखें, और नोटिफ़िकेशन में प्रोग्रेस दिखाएँ:

import { useEffect, useState } from 'react';
import { keepAlive } from '@/lib/appmintNative';

export function useKeepAlive() {
  const [running, setRunning] = useState(keepAlive.isRunning);
  useEffect(() => keepAlive.onState((d) => setRunning(d.state === 'running')), []);
  return running;
}

export function reportProgress(done: number, total: number) {
  void keepAlive.update({ title: 'Uploading', text: `${done} of ${total}`, progress: { value: done, max: total } });
}

appmintNative.keepAwake appmintNative#

keepAwake(on: boolean): Promise<boolean>

उदाहरण

रेसिपी, रूट, QR कोड या टाइमर दिखने के दौरान स्क्रीन चालू रखता है, और उसे फिर सोने देता है।

लौटाता है: Promise<boolean> - अनुरोध लागू होने पर true, जब कुछ भी स्क्रीन को चालू न रख सका (या ब्राउज़र ने मना किया) तब false। ज़रूरत: कुछ नहीं।

import { keepAwake } from '@/lib/appmintNative';
import { useEffect } from 'react';

function CookingMode() {
  useEffect(() => {
    void keepAwake(true);
    return () => { void keepAwake(false); };   // always release on unmount
  }, []);
  return <Steps />;
}

स्क्रीन को सिर्फ़ तब चालू रखें जब टाइमर चल रहा हो, और ऐसा न हो पाए तो यूज़र को बताएँ।

import { keepAwake, toast } from '@/lib/appmintNative';
import { useEffect } from 'react';

export function useAwakeWhile(running: boolean) {
  useEffect(() => {
    if (!running) return;
    let held = false;
    void keepAwake(true).then((ok) => {
      held = ok;
      if (!ok) toast('Your screen may turn off while the timer runs');
    });
    return () => { if (held) void keepAwake(false); };
  }, [running]);
}

पहले क्षमता जाँचें और टैब के सामने आने पर लॉक फिर से लें (पेज छिपने पर वेब इसे छोड़ देता है)।

import { keepAwake, can } from '@/lib/appmintNative';

export function holdScreenOn(): () => void {
  if (can('keepAwake') === 'none') return () => {};
  void keepAwake(true);
  const again = () => { if (document.visibilityState === 'visible') void keepAwake(true); };
  document.addEventListener('visibilitychange', again);
  return () => {
    document.removeEventListener('visibilitychange', again);
    void keepAwake(false);
  };
}

ध्यान दें: ऐप में यह KeepAwake प्लगइन (विंडो का स्क्रीन-ऑन फ़्लैग) इस्तेमाल करता है; वेब पर Screen Wake Lock API, जिसे ब्राउज़र टैब छिपने पर खुद छोड़ देते हैं - अगर स्क्रीन चालू रखनी हो तो पेज के दोबारा दिखने पर keepAwake(true) फिर बुलाएँ। यह सिर्फ़ तब चलता है जब ऐप सामने हो।

appmintNative.Keyboard appmintNative#

Keyboard.hide() / Keyboard.addListener(event, handler): { remove() }

उदाहरण

सॉफ़्टवेयर कीबोर्ड, Capacitor के Keyboard प्लगइन के रूप में: उसे बंद करें, और सुनें कि वह जितनी जगह ढकता है वह कब बदलती है।

लौटाता है: hide() → Promise<void>। addListener(event, handler) → { remove(): void } (तुरंत लौटता है, Promise नहीं)। event का मान 'keyboardWillShow', 'keyboardDidShow', 'keyboardWillHide' या 'keyboardDidHide' होता है; हैंडलर को { keyboardHeight } मिलता है। ज़रूरत: कुछ नहीं।

फ़ॉर्म भेजे जाने या फ़ील्ड के ऊपर शीट खुलने पर कीबोर्ड बंद करें:

import { Keyboard } from '@/lib/appmintNative';

async function onSubmit(e: React.FormEvent) {
  e.preventDefault();
  await Keyboard.hide();
  await save();
}

सुनें (अनमाउंट पर हैंडल हटाएँ):

import { Keyboard } from '@/lib/appmintNative';
import { useEffect } from 'react';

useEffect(() => {
  const h = Keyboard.addListener('keyboardDidShow', ({ keyboardHeight }) => {
    composer.style.paddingBottom = `${keyboardHeight}px`;
  });
  const g = Keyboard.addListener('keyboardDidHide', () => { composer.style.paddingBottom = '0px'; });
  return () => { h.remove(); g.remove(); };
}, []);

कीबोर्ड खुलने पर फ़ोकस वाले फ़ील्ड को दिखाने के लिए स्क्रॉल करने हेतु दोनों को मिलाएँ, फिर Enter पर कीबोर्ड बंद करें:

import { Keyboard } from '@/lib/appmintNative';
import { useEffect } from 'react';

export function useKeyboardScroll() {
  useEffect(() => {
    const h = Keyboard.addListener('keyboardDidShow', () => {
      document.activeElement?.scrollIntoView({ block: 'center', behavior: 'smooth' });
    });
    const onKey = (e: KeyboardEvent) => { if (e.key === 'Enter') void Keyboard.hide(); };
    window.addEventListener('keydown', onKey);
    return () => { h.remove(); window.removeEventListener('keydown', onKey); };
  }, []);
}

नोट: keyboardHeight कीबोर्ड का वह हिस्सा है जिसे पेज को अब भी साफ़ करना है, कच्ची ऊँचाई नहीं: Android ऐप में शेल कीबोर्ड के लिए पेज का आकार बदलता है, इसलिए कीबोर्ड खुला होने पर वहाँ यह 0 होता है (show इवेंट फिर भी चलता है, और संख्या बदलने पर फिर चलता है)। hide इवेंट तभी चलते हैं जब कीबोर्ड सच में बंद होता है, इसलिए वे "क्या कीबोर्ड खुला है" ट्रैक कर सकते हैं। "Will" और "did" इवेंट साथ चलते हैं। सिर्फ़ पैडिंग के लिए onKeyboard((px) => …) ज़्यादा आसान है।

appmintNative.launchUrl appmintNative#

launchUrl(): Promise<string>

उदाहरण

वह लिंक देता है जिसने ऐप को लॉन्च या फिर शुरू किया - डीप लिंक, आइकन शॉर्टकट, विजेट टैप, नोटिफ़िकेशन - ताकि ऐप सही स्क्रीन खोल सके।

लौटाता है: Promise<string> - पूरा URL, या ऐप सामान्य रूप से खुला हो तो ''। वेब पर हमेशा '', जहाँ एड्रेस बार ही रूट है। ज़रूरत: शॉर्टकट और विजेट टैप के लिए कुछ नहीं; बाहरी लिंक के लिए बिल्ड करते समय Deep Linking विकल्प चाहिए।

ऐप माउंट होते समय एक बार इस पर रूट करें:

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { launchUrl } from '@/lib/appmintNative';

export function useLaunchRoute() {
  const navigate = useNavigate();
  useEffect(() => {
    void launchUrl().then((u) => {
      if (!u) return;
      const { pathname, search } = new URL(u);
      navigate(pathname + search, { replace: true });
    });
  }, [navigate]);
}

उन लिंक के लिए इसे onDeepLink() के साथ जोड़ें जो ऐप के पहले से खुले होने पर आते हैं:

import { onDeepLink } from '@/lib/appmintNative';

const off = onDeepLink((url) => console.log('opened with', url));

खाली स्ट्रिंग को "सामान्य रूप से खुला" मानें और शुरुआती स्क्रीन लिंक से तभी चुनें जब लिंक हो। appmint-shortcut://<package>/orders जैसे शॉर्टकट के पाथ में रूट होता है।

import { launchUrl, onDeepLink } from '@/lib/appmintNative';

export async function startRoute(): Promise<string> {
  const u = await launchUrl();
  if (!u) return '/';                         // normal start, or the web
  const { pathname, search } = new URL(u);
  return pathname + search;
}

// While the app stays open, later links arrive here.
export function watchLinks(go: (route: string) => void): () => void {
  return onDeepLink((u) => { const x = new URL(u); go(x.pathname + x.search); });
}

ध्यान दें:

  • मान सहेजकर रखा जाता है, खर्च नहीं होता: देर से माउंट होने वाला कंपोनेंट भी इसे देखता है। नया लिंक इसे बदल देता है।
  • यह शेल से पढ़ा जाता है (WebToApk.getLaunchUrl()); AI से बने ऐप में इसे कभी सीधे कॉल न करें। शॉर्टकट और विजेट टैप इस रूप में आते हैं appmint-shortcut://<package><route>.
  • new URL(u) से पार्स करें और उसके पाथ पर जाएँ; URL को खुद लोड न करें।

appmintNative.listCallLog appmintNative#

listCallLog(limit = 100, offset = 0, sinceMillis = 0): Promise<CallLogEntry[]>

उदाहरण

कॉल हिस्ट्री पढ़ता है, सबसे नई पहले। सिर्फ़ Android ऐप।

लौटाता है: Promise<CallLogEntry[]> - हर { number, name, type, date, duration }; type का मान incoming, outgoing, missed, voicemail, rejected, blocked या other होता है, date epoch मिलीसेकंड है, duration सेकंड है। Call log बंद होने या अनुमति अस्वीकार होने पर खाली। ज़रूरत: चरण 4 (Access) में Call log (पढ़ें) स्विच - जब आपका कोड इसे कॉल करता है तो बिल्ड इसे चालू कर देता है - और पहले इस्तेमाल पर उपयोगकर्ता की अनुमति।

पिछले 7 दिनों की मिस्ड कॉल।

import { listCallLog, can } from '@/lib/appmintNative';

export async function missedThisWeek() {
  if (can('callLog') === 'none') return null;          // web: not possible
  const since = Date.now() - 7 * 24 * 60 * 60 * 1000;
  try {
    const calls = await listCallLog(500, 0, since);   // a number here; the wrapper converts it
    return calls.filter((c) => c.type === 'missed');
  } catch {
    return [];                                          // 2-minute timeout
  }
}

हर संपर्क के साथ कुल बात का समय, आउटगोइंग और इनकमिंग कॉल से, सबसे लंबा पहले।

import { listCallLog, can } from '@/lib/appmintNative';

export async function talkTimeByPerson() {
  if (can('callLog') === 'none') return null;
  const calls = await listCallLog(1000, 0).catch(() => []);
  const totals = new Map<string, number>();
  for (const c of calls) {
    if (c.type !== 'incoming' && c.type !== 'outgoing') continue;
    const who = c.name || c.number || 'Unknown';
    totals.set(who, (totals.get(who) ?? 0) + (c.duration ?? 0));   // seconds
  }
  return [...totals].sort((a, b) => b[1] - a[1]);
}

एक स्क्रीन जो खाली सूची समझाती है, क्योंकि "कोई कॉल नहीं" और "अनुमति अस्वीकार" एक जैसे दिखते हैं।

import { listCallLog, can } from '@/lib/appmintNative';
import { useEffect, useState } from 'react';

export function useCalls() {
  const [calls, setCalls] = useState<Awaited<ReturnType<typeof listCallLog>> | null>(null);
  const supported = can('callLog') !== 'none';
  useEffect(() => {
    if (supported) void listCallLog(50).then(setCalls).catch(() => setCalls([]));
  }, [supported]);
  const hint = !supported ? 'Open the app on your phone to see calls'
    : calls && calls.length === 0 ? 'No calls found. Allow call log access in Settings if you expected some.' : '';
  return { calls: calls ?? [], hint };
}

नोट: ऐप limit को 2000 तक सीमित रखता है। खाली सूची से "कोई कॉल नहीं" और "अनुमति नहीं मिली" में फ़र्क नहीं पता चलता। वेब पर can('callLog') का मान 'none' होता है। कॉल लॉग Google Play की प्रतिबंधित अनुमति है: Play Console में आपको मंज़ूर Permissions Declaration चाहिए, वरना ऐप रिजेक्ट हो जाता है।

appmintNative.listContacts appmintNative#

listContacts(limit = 200, offset = 0): Promise<Contact[]>

उदाहरण

एड्रेस बुक एक बार में एक पेज पढ़ता है। सिर्फ़ Android ऐप, शेल के ब्रिज से।

लौटाता है: Promise<Contact[]>। कोई कॉन्टैक्ट न हो, एक्सेस मना हो, या बिल्ड के समय Contacts चालू न किया गया हो तो खाली array। ज़रूरत: चरण 4 (Access) में Contacts स्विच - आपका कोड इसे कॉल करे तो बिल्ड इसे चालू कर देता है - और पहली बार इस्तेमाल पर यूज़र की अनुमति।

कॉन्टैक्ट सूची की स्क्रीन। इसे सिर्फ़ वहीं दिखाएँ जहाँ यह काम कर सकती है। हर कॉन्टैक्ट है { id?, name?, phones?: string[], emails?: string[], photoUri? }.

import { listContacts, can, type Contact } from '@/lib/appmintNative';

type Row = { id: string; name: string; phone: string };

function toRow(c: Contact): Row {
  return { id: c.id ?? '', name: c.name ?? '', phone: c.phones?.[0] ?? '' };
}

export async function loadPage(page: number): Promise<Row[] | null> {
  if (can('contactList') === 'none') return null;   // web: show "open the app" instead
  try {
    const contacts = await listContacts(100, page * 100);
    return contacts.map(toRow);
  } catch {
    return [];                                        // 2-minute timeout
  }
}

पूरी एड्रेस बुक को पेज-दर-पेज एक सूची में पढ़कर पूरी तरह खोजें।

import { listContacts, can, type Contact } from '@/lib/appmintNative';

export async function findContacts(query: string): Promise<Contact[] | null> {
  if (can('contactList') === 'none') return null;
  const all: Contact[] = [];
  for (let offset = 0; ; offset += 200) {
    const page = await listContacts(200, offset).catch(() => []);
    all.push(...page);
    if (page.length < 200) break;
  }
  const q = query.trim().toLowerCase();
  return all.filter((c) => (c.name ?? '').toLowerCase().includes(q));
}

किसी कॉन्टैक्ट के पहले नंबर पर संदेश भेजने के लिए इसे sendSms से जोड़ें।

import { sendSms, type Contact } from '@/lib/appmintNative';

export async function invite(c: Contact): Promise<boolean> {
  const phone = c.phones?.[0];
  if (!phone) return false;
  return sendSms(phone, `Hi ${c.name ?? ''}, join me on the app!`);
}

ध्यान दें: खाली नतीजे से यह पता नहीं चलता कि "कोई संपर्क नहीं" है या "अनुमति नहीं मिली" - सूची खाली होने पर छोटा संकेत ("Allow contacts in Settings") दिखाएँ। संपर्क नाम के हिसाब से A से Z क्रम में आते हैं; limit 2000 पर सीमित है। एक व्यक्ति के लिए pickContact() इस्तेमाल करें, जिसे अनुमति नहीं चाहिए। Contacts निजी डेटा है: इसे अपने Play Data safety फ़ॉर्म और प्राइवेसी पॉलिसी में बताएँ।

appmintNative.listSms appmintNative#

listSms(box: 'inbox' | 'sent' | 'draft' = 'inbox', limit = 100, offset = 0): Promise<SmsMessage[]>

उदाहरण

इनबॉक्स, भेजे गए या ड्राफ़्ट से SMS संदेश पढ़ता है, सबसे नए पहले। सिर्फ़ Android ऐप।

लौटाता है: Promise<SmsMessage[]> - हर एक { id, address, body, date, read, box }। SMS बंद होने, अनुमति न मिलने या बॉक्स खाली होने पर खाली। ज़रूरत: चरण 4 (Access) में SMS (पढ़ें और भेजें) स्विच - आपका कोड इसे कॉल करे तो बिल्ड इसे चालू कर देता है - और पहली बार इस्तेमाल पर यूज़र की अनुमति।

इनबॉक्स के आखिरी 20 संदेश दिखाएँ।

import { listSms, can, type SmsMessage } from '@/lib/appmintNative';

export async function latestInbox(): Promise<SmsMessage[] | null> {
  if (can('smsList') === 'none') return null;          // web: hide the screen
  try {
    const msgs = await listSms('inbox', 20, 0);
    return msgs.map((m) => ({ ...m, body: (m.body ?? '').slice(0, 120) }));
  } catch {
    return [];                                          // 2-minute timeout
  }
}

एक भेजने वाले के अनपढ़े संदेश, सबसे नए पहले।

import { listSms, can } from '@/lib/appmintNative';

export async function unreadFrom(address: string) {
  if (can('smsList') === 'none') return null;
  const msgs = await listSms('inbox', 200, 0).catch(() => []);
  return msgs.filter((m) => m.address === address && !m.read);
}

भेजे गए और ड्राफ़्ट एक साथ पढ़ें, और उन्हें तारीख के हिसाब से मिलाकर क्रमबद्ध करें।

import { listSms, type SmsMessage } from '@/lib/appmintNative';

export async function outgoing(): Promise<SmsMessage[]> {
  const [sent, drafts] = await Promise.all([
    listSms('sent', 50, 0).catch(() => []),
    listSms('draft', 50, 0).catch(() => []),
  ]);
  return [...sent, ...drafts].sort((a, b) => (b.date ?? 0) - (a.date ?? 0));
}

नोट: खाली सूची से पता नहीं चलता कि "कोई संदेश नहीं" है या "अनुमति अस्वीकार"। ऐप limit को 500 पर सीमित करता है। वेब पर can('smsList') 'none' है। नए आने वाले संदेश इस रैपर का हिस्सा नहीं हैं; हाथ से लिखे पेज उन्हें appmint:sms-received इवेंट से पाते हैं (सीधे ब्रिज के लिए listSms देखें)। SMS एक Google Play प्रतिबंधित अनुमति है: आपको Play Console में स्वीकृत Permissions Declaration चाहिए, जो आम तौर पर सिर्फ़ डिफ़ॉल्ट SMS ऐप्स को मिलती है।

appmintNative.liveProgress appmintNative#

import { liveProgress } from '@/lib/appmintNative'

उदाहरण

Android नोटिफ़िकेशन शेड में एक टिकाऊ, चुप स्टेटस कार्ड जिसे आप वहीं अपडेट करते हैं - डिलीवरी, अपलोड, टाइमर, वर्कआउट। start, update, end.

लौटाता है: start / update → Promise<boolean> (उपलब्ध न होने, नोटिफ़िकेशन की अनुमति न होने, या यूज़र के इसकी “Live status” श्रेणी बंद करने पर false); end → Promise<void>। ज़रूरत: Android ऐप, और Android 13+ पर नोटिफ़िकेशन की अनुमति। वेब पर can('liveProgress') का मान 'none' है - वहाँ वही स्थिति अपने ऐप के अंदर दिखाएँ।

import { liveProgress, can } from '@/lib/appmintNative';

export async function trackUpload(files: File[]) {
  const card = can('liveProgress') !== 'none';
  if (card) await liveProgress.start({ id: 'upload', title: 'Uploading photos', text: 'Starting…', indeterminate: true });

  for (let i = 0; i < files.length; i++) {
    await uploadOne(files[i]);
    if (card) await liveProgress.update({
      id: 'upload', title: 'Uploading photos',
      text: `${i + 1} of ${files.length}`,
      progress: (i + 1) / files.length,       // 0–1
      route: '/uploads',                      // opens this screen when tapped
    });
  }
  if (card) await liveProgress.end('upload'); // removes the card
}

चलता हुआ टाइमर: startedAt उस पल से ऊपर की ओर गिनती करती घड़ी दिखाता है।

await liveProgress.start({ id: 'run', title: 'Morning run', text: '5 km goal', startedAt: Date.now() });
// ...later
await liveProgress.end('run');

इनकार को संभालें। start का जवाब false होता है; liveProgress.lastError कारण बताता है।

import { liveProgress, toast } from '@/lib/appmintNative';

export async function startDelivery() {
  const ok = await liveProgress.start({ id: 'order', title: 'Order on its way', text: 'Packing', progress: 0.1, route: '/orders' });
  if (!ok) {
    if (liveProgress.lastError === 'permission') toast('Allow notifications to follow your order here');
    else if (liveProgress.lastError === 'channel_disabled') toast('Live status is turned off in system settings');
  }
  return ok;
}

नोट: वही id एक कार्ड अपडेट करता है; अलग id अलग कार्ड बनाते हैं (डिफ़ॉल्ट id 'live' है)। उपयोगकर्ता इसे स्वाइप करके नहीं हटा सकता, इसलिए हमेशा end कॉल करें। Android 13+ पर पहले नोटिफ़िकेशन की अनुमति माँगें (Notification.requestPermission()): इसके बिना start जवाब में false देता है और कुछ नहीं दिखता।

appmintNative.LocalNotifications appmintNative#

import { LocalNotifications } from '@/lib/appmintNative'

उदाहरण

Capacitor प्लगइन के हिसाब से लिखे कोड के लिए @capacitor/local-notifications वाला रूप (LocalNotifications.schedule({ notifications }))। यह हर नोटिफ़िकेशन तुरंत दिखाता है notify().

लौटाता है: Promise<{ notifications }> - वही सूची जो आपने दी थी। यह नहीं बताता कि हर एक दिखा या नहीं; जब यह जानना हो तो notify() इस्तेमाल करें। ज़रूरत: Android: बिल्ड करते समय Access चरण (चरण 4) में नोटिफ़िकेशन चालू।

import { LocalNotifications } from '@/lib/appmintNative';

export async function announceSync(count: number) {
  await LocalNotifications.schedule({
    notifications: [
      { title: 'Sync complete', body: `${count} items updated`, tag: 'sync' },
    ],
  });
}

एक कॉल में कई नोटिफ़िकेशन, हर एक पहले वाले को बदलता है जिसका वही tag.

import { LocalNotifications } from '@/lib/appmintNative';

export async function reportBatch(done: number, failed: number) {
  await LocalNotifications.schedule({
    notifications: [
      { title: 'Upload finished', body: `${done} files sent`, tag: 'upload-ok' },
      ...(failed ? [{ title: 'Some files failed', body: `${failed} need a retry`, tag: 'upload-failed' }] : []),
    ],
  });
}

जब जानना हो कि यह दिखा या नहीं, तो इसके बजाय notify() इस्तेमाल करें और जवाब पर प्रतिक्रिया दें।

import { notify, toast } from '@/lib/appmintNative';

export async function remind(text: string) {
  const shown = await notify({ title: 'Reminder', body: text, tag: 'reminder' });
  if (!shown) toast(text);   // notifications are off: tell them in the app
}

नोट: सिर्फ़ तुरंत वाले - Capacitor फ़ील्ड id, schedule, at, actionTypeId और extra समर्थित नहीं हैं; हर आइटम { title, body?, icon?, tag? } है। बाद के समय के नोटिफ़िकेशन के लिए, हाथ से लिखा पेज WebToApk.scheduleNotificationEx (Android) इस्तेमाल करता है। बटन के लिए इस्तेमाल करें notifyWithActions.

appmintNative.lockOrientation appmintNative#

lockOrientation(to: 'portrait' | 'landscape' | 'any'): Promise<boolean>

उदाहरण

स्क्रीन को एक ही तरफ़ से जमाकर रखता है, ऐसे व्यू के लिए जो उसी तरह समझ आता है - गेम बोर्ड, चौड़ा चार्ट, सिग्नेचर पैड। 'any' लॉक हटा देता है।

लौटाता है: Promise<boolean> - लॉक (या रिलीज़) लागू होने पर true, प्लैटफ़ॉर्म के मना करने पर false। यह कभी रिजेक्ट नहीं होता। ज़रूरत: कुछ नहीं।

व्यू खुला रहने तक लॉक रखें, छोड़ते समय रिलीज़ करें:

import { useEffect } from 'react';
import { can, lockOrientation } from '@/lib/appmintNative';

export function ChartScreen() {
  useEffect(() => {
    if (can('orientationLock') === 'none') return;
    void lockOrientation('landscape');
    return () => { void lockOrientation('any'); };   // always give the rotation back
  }, []);
  return <div className="h-full w-full">{/* the chart */}</div>;
}

जब व्यू जवाब पर निर्भर हो, तब जवाब जाँचें:

import { lockOrientation, toast } from '@/lib/appmintNative';

const ok = await lockOrientation('portrait');
if (!ok) toast('Turn your phone upright for the best view');

स्क्रीन के हटने पर लॉक रिलीज़ करें, कई स्क्रीन में साझा हुक के रूप में।

import { useEffect } from 'react';
import { lockOrientation, type OrientationLock } from '@/lib/appmintNative';

export function useOrientation(mode: OrientationLock) {
  useEffect(() => {
    void lockOrientation(mode);
    return () => { void lockOrientation('any'); };
  }, [mode]);
}

// useOrientation('landscape') inside the game screen

ध्यान दें:

  • ऐप में Capacitor ScreenOrientation प्लगइन लॉक लागू करता है।
  • वेब पर यह screen.orientation.lock() इस्तेमाल करता है। ज़्यादातर ब्राउज़र इसे फ़ुलस्क्रीन के बाहर अस्वीकार करते हैं और डेस्कटॉप ब्राउज़र हमेशा अस्वीकार करते हैं, इसलिए वहाँ false की उम्मीद रखें।
  • लॉक किए गए व्यू से निकलने का रास्ता यूज़र को हमेशा दें।

appmintNative.longPress appmintNative#

longPress(el: HTMLElement, handler: (e: PointerEvent) => void, opts?: { ms?: number; haptics?: boolean }): () => void

उदाहरण

जब यूज़र किसी एलिमेंट पर उँगली दबाकर रखे तो आपको कॉल करता है - पंक्ति के संदर्भ मेन्यू, रिएक्शन या कार्ड कार्रवाइयों के लिए।

लौटाता है: अनसब्सक्राइब फ़ंक्शन - अनमाउंट पर उसे कॉल करें। ज़रूरत: कुछ नहीं।

विकल्प: ms - पकड़ने का समय, डिफ़ॉल्ट 450; haptics - चलने पर मीडियम हैप्टिक, डिफ़ॉल्ट true.

लंबे दबाव पर एक्शन शीट खोलें, और उसी मेन्यू के लिए एक दिखने वाला बटन भी रखें:

import { useEffect, useRef } from 'react';
import { actionSheet, longPress } from '@/lib/appmintNative';

export function PhotoTile({ src, onDelete }: { src: string; onDelete(): void }) {
  const ref = useRef<HTMLDivElement>(null);
  const openMenu = async () => {
    const i = await actionSheet('Photo', [{ title: 'Delete', destructive: true }]);
    if (i === 0) onDelete();
  };

  useEffect(() => {
    if (!ref.current) return;
    return longPress(ref.current, () => void openMenu());
  }, []);

  return (
    <div ref={ref} className="relative">
      <img src={src} alt="" />
      <button aria-label="Photo actions" className="absolute right-1 top-1" onClick={() => void openMenu()}>⋯</button>
    </div>
  );
}

धीमा होल्ड, बिना हैप्टिक के:

import { longPress } from '@/lib/appmintNative';

declare const card: HTMLElement;
const off = longPress(card, (e) => console.log('held at', e.clientX, e.clientY), { ms: 700, haptics: false });

अपनी पुष्टि के लिए इसे haptic के साथ जोड़ें, और हर लिस्ट पंक्ति के लिए सफ़ाई करें।

import { useEffect, useRef } from 'react';
import { haptic, longPress } from '@/lib/appmintNative';

export function SelectableRow({ id, onSelect, children }: { id: string; onSelect(id: string): void; children: React.ReactNode }) {
  const ref = useRef<HTMLLIElement>(null);
  useEffect(() => {
    if (!ref.current) return;
    return longPress(ref.current, () => { haptic('heavy'); onSelect(id); }, { haptics: false });
  }, [id, onSelect]);
  return <li ref={ref}>{children}</li>;
}

ध्यान दें:

  • यह हर होल्ड पर एक बार चलता है और उसके बाद आने वाले क्लिक को निगल लेता है, इसलिए लंबा दबाव "टैप" भी नहीं करता।
  • 8 px से ज़्यादा हिलने पर यह रद्द हो जाता है, इसलिए स्क्रॉल करना इसे कभी ट्रिगर नहीं करता। यह उस एलिमेंट पर ब्राउज़र का अपना लंबे-दबाव का मेन्यू भी रोकता है।
  • लॉन्ग प्रेस दिखाई नहीं देता। उसी कार्रवाई के लिए हमेशा एक दिखने वाला बटन रखें।

appmintNative.markPushRead appmintNative#

import { markPushRead } from '@/lib/appmintNative'

उदाहरण

AppMint Push इनबॉक्स के एक संदेश को पढ़ा हुआ चिह्नित करता है, या बिना आर्ग्युमेंट के कॉल करने पर हर संदेश को।

लौटाता है: तुरंत boolean - कुछ बदला हो तो true। वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू हो।

import { pushInbox, markPushRead } from '@/lib/appmintNative';

export function openMessage(id: string) {
  markPushRead(id);
  return pushInbox().find((m) => m.id === id);
}

export function markAllRead() {
  markPushRead();          // no id = all messages
  return pushInbox();
}

एक मैसेज को पढ़ा हुआ मार्क करने के बाद अपठित मैसेज गिनें, अपने हेडर के बैज के लिए।

import { pushInbox, markPushRead } from '@/lib/appmintNative';

export function openAndCount(id: string) {
  const changed = markPushRead(id);          // false on the web or when nothing changed
  const unread = pushInbox().filter((m) => !m.read).length;
  return { changed, unread };
}

यूज़र के इनबॉक्स स्क्रीन खोलने पर सब कुछ पढ़ा हुआ मार्क करें और नया मैसेज आने पर रीफ़्रेश करें।

import { useEffect, useState } from 'react';
import { pushInbox, markPushRead } from '@/lib/appmintNative';

export function useInbox() {
  const [items, setItems] = useState(() => pushInbox());
  useEffect(() => {
    markPushRead();
    const refresh = () => setItems(pushInbox());
    window.addEventListener('appmint:push', refresh);
    return () => window.removeEventListener('appmint:push', refresh);
  }, []);
  return items;
}

ध्यान दें: पढ़ने की स्थिति सिर्फ़ इसी फ़ोन पर रहती है; ट्रे का नोटिफ़िकेशन हटाया नहीं जाता।

appmintNative.mediaSession appmintNative#

mediaSession — @/lib/appmintNative

उदाहरण

ऑडियो या वीडियो चलाने वाले ऐप के लिए लॉक-स्क्रीन और नोटिफ़िकेशन प्लेबैक कंट्रोल: जो चल रहा है उसे प्रकाशित करें, Play / Pause / Next / Previous / Stop बटन पाएँ, और वीडियो को TV पर भेजें।

लौटाता है: setMetadata, setPlaybackState, clear → void। onAction(handler) → अनसब्सक्राइब फ़ंक्शन। cast(url) → Promise<boolean> (कुछ भी ऑफ़र न हो पाए तो false)। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Media Notification; AI बिल्ड इसे आपके लिए चालू कर देते हैं जब कोड इस्तेमाल करता है mediaSession.

import { useEffect, useRef } from 'react';
import { mediaSession } from '@/lib/appmintNative';

type Track = { url: string; title: string; artist: string; cover: string };

export function Player({ track, onNext, onPrev }: { track: Track; onNext: () => void; onPrev: () => void }) {
  const audio = useRef<HTMLAudioElement>(null);
  const unsubscribe = useRef<(() => void) | null>(null);

  const publish = () => {
    const a = audio.current;
    if (!a) return;
    mediaSession.setPlaybackState({
      playing: !a.paused,
      positionMs: a.currentTime * 1000,
      durationMs: (a.duration || 0) * 1000,
    });
  };

  const onPlay = () => {
    // Register the buttons when playback starts, not on mount (see Notes).
    if (!unsubscribe.current) {
      unsubscribe.current = mediaSession.onAction((action) => {
        const a = audio.current;
        if (!a) return;
        if (action === 'play') void a.play();
        if (action === 'pause') a.pause();
        if (action === 'next') onNext();
        if (action === 'prev') onPrev();
        if (action === 'stop') { a.pause(); mediaSession.clear(); }
      });
    }
    mediaSession.setMetadata({ title: track.title, artist: track.artist, artworkUrl: track.cover });
    publish();
  };

  useEffect(() => () => { unsubscribe.current?.(); mediaSession.clear(); }, []);

  return <audio ref={audio} src={track.url} controls onPlay={onPlay} onPause={publish} onSeeked={publish} />;
}

मौजूदा वीडियो को TV पर भेजें:

import { can, mediaSession } from '@/lib/appmintNative';

export function CastButton({ url }: { url: string }) {
  if (can('cast') === 'none') return null;
  return <button onClick={async () => { if (!(await mediaSession.cast(url))) alert('No cast target found'); }}>Cast</button>;
}

प्लेबैक ख़त्म होने पर लॉक-स्क्रीन कार्ड साफ़ करें, और प्लेटफ़ॉर्म में मीडिया कंट्रोल न हों तो उसे छोड़ दें।

import { can, mediaSession } from '@/lib/appmintNative';

export function publishEnded(audio: HTMLAudioElement): () => void {
  if (can('mediaControls') === 'none') return () => {};
  const ended = () => {
    mediaSession.setPlaybackState({ playing: false, positionMs: 0, durationMs: audio.duration * 1000 || 0 });
    mediaSession.clear();
  };
  audio.addEventListener('ended', ended);
  return () => audio.removeEventListener('ended', ended);
}

ध्यान दें:

  • Android पर बटन ऐप के navigator.mediaSession से आते हैं, जो पेज के लोड होने पूरा होने पर इंस्टॉल होता है। पहले रजिस्टर किया गया onAction (जैसे माउंट इफ़ेक्ट में) खो जाता है, इसलिए इसे पहले प्ले पर रजिस्टर करें।
  • seek एक्शन (positionMs के साथ) सिर्फ़ वेब पर दिया जाता है; Android नोटिफ़िकेशन में सीक नहीं है।
  • artworkUrl इंटरनेट पर पूरा https:// पता होना चाहिए। Android पर cast(url) "Cast to..." चूज़र खोलता है (सौंपना; चूज़र माँगे जाने पर यह true के साथ पूरा होता है)। वेब पर यह Remote Playback API इस्तेमाल करता है।

appmintNative.Motion appmintNative#

Motion.addListener('accel' | 'orientation', cb): Promise<{ remove() }>

उदाहरण

स्टैंडर्ड devicemotion और deviceorientation इवेंट्स के ऊपर @capacitor/motion जैसा रूप: फ़ोन की हरकत और झुकाव। वही कोड ऐप और ब्राउज़र दोनों में चलता है।

लौटाता है: Promise<{ remove(): Promise<void> }> - रोकने के लिए remove() कॉल करें। 'accel' { acceleration: {x,y,z}, accelerationIncludingGravity: {x,y,z}, rotationRate: {alpha,beta,gamma}, interval } के साथ वापस कॉल करता है; 'orientation' { alpha, beta, gamma } (डिग्री) के साथ। ज़रूरत: कुछ नहीं।

रिफ़्रेश करने के लिए हिलाएँ।

import { Motion, type AccelEvent } from '@/lib/appmintNative';

let handle: { remove(): Promise<void> } | null = null;

export async function startShakeDetect(onShake: () => void) {
  let last = 0;
  handle = await Motion.addListener('accel', (e) => {
    const g = (e as AccelEvent).accelerationIncludingGravity;
    const force = Math.sqrt(g.x * g.x + g.y * g.y + g.z * g.z);
    if (force > 25 && Date.now() - last > 1000) { last = Date.now(); onShake(); }
  });
}

export async function stopShakeDetect() {
  await handle?.remove();
  handle = null;
}

ओरिएंटेशन इवेंट से लेवल बबल को झुकाएँ।

import { Motion, type OrientationEvent } from '@/lib/appmintNative';

const tilt = await Motion.addListener('orientation', (e) => {
  const { beta, gamma } = e as OrientationEvent;       // front-back, left-right
  bubble.style.transform = `translate(${gamma * 2}px, ${beta * 2}px)`;
});
// later: await tilt.remove();

फ़ोन की हलचल पढ़ने वाले React कॉम्पोनेंट के लिए अपने-आप सफ़ाई वाला हुक फ़ॉर्म।

import { useEffect, useState } from 'react';
import { Motion, can, type AccelEvent } from '@/lib/appmintNative';

export function useAcceleration() {
  const [g, setG] = useState({ x: 0, y: 0, z: 0 });
  const [supported] = useState(() => can('motion') !== 'none');
  useEffect(() => {
    if (!supported) return;
    let handle: { remove(): Promise<void> } | undefined;
    let cancelled = false;
    void Motion.addListener('accel', (e) => setG((e as AccelEvent).accelerationIncludingGravity)).then((h) => {
      if (cancelled) void h.remove(); else handle = h;
    });
    return () => { cancelled = true; void handle?.remove(); };
  }, [supported]);
  return { g, supported };
}

नोट: कुछ ब्राउज़र पहले मोशन एक्सेस माँगते हैं, इसलिए वहाँ इसे टैप से शुरू करें; उपयोगकर्ता मना करे तो हैंडल लौटता है पर कोई इवेंट नहीं आता। जहाँ ब्राउज़र में DeviceMotionEvent नहीं है वहाँ can('motion') का मान 'none' है। गायब सेंसर मान 0 के रूप में आते हैं। स्क्रीन बंद होने पर हमेशा remove() करें - मोशन इवेंट सेकंड में कई बार चलते हैं।

appmintNative.musicVolume appmintNative#

musicVolume() — @/lib/appmintNative

उदाहरण

ऐप के लूप होने वाले बैकग्राउंड म्यूज़िक का लेवल पढ़ता है, 0 से 100।

लौटाता है: number, तुरंत: 0-100, या जब म्यूज़िक न हो तो -1 (वेब पर, या बिना म्यूज़िक फ़ाइल वाले ऐप में)। ज़रूरत: बिल्ड करते समय चरण 3 (Integrate) में Background Music के तहत चुनी गई फ़ाइल।

म्यूज़िक स्लाइडर तभी बनाएँ जब म्यूज़िक हो:

import { useState } from 'react';
import { musicVolume, setMusicVolume } from '@/lib/appmintNative';

export function MusicSetting() {
  const [level, setLevel] = useState(() => musicVolume());
  if (level < 0) return null;                  // no music in this app: no control

  return (
    <label>
      Music
      <input
        type="range" min={0} max={100} value={level}
        onChange={(e) => {
          const v = Number(e.target.value);
          setLevel(v);
          setMusicVolume(v);
        }}
      />
    </label>
  );
}

"Mute music" स्विच के साथ लेवल को मिलाकर रखें, और लौटने के लिए वॉल्यूम याद रखें।

import { useRef, useState } from 'react';
import { musicVolume, setMusicVolume } from '@/lib/appmintNative';

export function MuteMusicSwitch() {
  const before = useRef(Math.max(musicVolume(), 50));
  const [muted, setMuted] = useState(() => musicVolume() === 0);
  if (musicVolume() < 0) return null;            // no music in this build

  const toggle = () => {
    if (muted) setMusicVolume(before.current);
    else { before.current = musicVolume(); setMusicVolume(0); }
    setMuted(!muted);
  };
  return <button onClick={toggle}>{muted ? 'Unmute music' : 'Mute music'}</button>;
}

वीडियो चलते समय संगीत की आवाज़ धीमी करें, फिर उसे वापस करें।

import { musicVolume, setMusicVolume } from '@/lib/appmintNative';

export function duckMusicDuring(video: HTMLVideoElement) {
  const level = musicVolume();
  if (level < 0) return;                         // nothing to duck
  video.addEventListener('play', () => setMusicVolume(0));
  video.addEventListener('pause', () => setMusicVolume(level));
  video.addEventListener('ended', () => setMusicVolume(level));
}

ध्यान दें: सिर्फ़ Android ऐप; वेब -1 जवाब देता है। setMusicVolume से सेट किया स्तर अगले लॉन्च के लिए रखा जाता है; जब तक पेज कोई सेट न करे, वह बिल्ड का स्तर होता है।

appmintNative.navEnd appmintNative#

import { navEnd } from '@/lib/appmintNative'

उदाहरण

appmintNative.navStart के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

navStart() ऐसे स्क्रीन बदलाव के लिए ऐप की पतली ऊपरी लोडिंग बार दिखाता है जो ऐप खुद नहीं देख सकता; navEnd() उसे ख़त्म करता है।

लौटाता है: कुछ नहीं (दोनों तुरंत चलते हैं)। ज़रूरत: कुछ नहीं। वेब पर (ऐप शेल बार नहीं) दोनों कुछ नहीं करते।

इन्हें सिर्फ़ ऐसे इंतज़ार के लिए इस्तेमाल करें जहाँ डेटा आने तक DOM में कुछ नहीं बदलता, और हमेशा इसके साथ जोड़ें try/finally:

import { navStart, navEnd } from '@/lib/appmintNative';

export async function openOrder(id: string, render: (order: unknown) => void) {
  navStart();
  try {
    const res = await fetch(`/api/orders/${id}`);
    render(await res.json());
  } finally {
    navEnd();                // always, also when the fetch failed
  }
}

ऐसे सबमिट को लपेटें जिसमें समय लगता है, ताकि सर्वर के काम करने के दौरान यूज़र को बार दिखे।

import { navStart, navEnd } from '@/lib/appmintNative';

export async function submitForm(data: FormData): Promise<boolean> {
  navStart();
  try {
    const res = await fetch('/api/submit', { method: 'POST', body: data });
    return res.ok;
  } catch {
    return false;            // the bar still ends in finally
  } finally {
    navEnd();
  }
}

एक छोटा हेल्पर बनाएँ जो किसी भी प्रॉमिस को लपेटे।

import { navStart, navEnd } from '@/lib/appmintNative';

export async function withNavBar<T>(work: Promise<T>): Promise<T> {
  navStart();
  try { return await work; } finally { navEnd(); }
}

// usage inside a click handler:
// const items = await withNavBar(loadItems(categoryId));

नोट: ये WebToApk.navStarted() / navSettled() को कॉल करते हैं। जब आपका ऐप DOM में स्क्रीन बदलता है तब ऐप पहले से बार चलाता है, इसलिए ज़्यादातर रूट बदलावों को कुछ नहीं चाहिए। लगभग 200 ms से छोटे बदलाव के लिए कुछ नहीं बनता, और जो बार कभी खत्म नहीं की गई वह 2.5 सेकंड बाद खुद रुक जाती है। अपना स्पिनर या स्केलेटन बनाते रहें; बार अतिरिक्त है।

appmintNative.navStart appmintNative#

navStart() / navEnd() from '@/lib/appmintNative'

उदाहरण

navStart() ऐसे स्क्रीन बदलाव के लिए ऐप की पतली ऊपरी लोडिंग बार दिखाता है जो ऐप खुद नहीं देख सकता; navEnd() उसे ख़त्म करता है।

लौटाता है: कुछ नहीं (दोनों तुरंत चलते हैं)। ज़रूरत: कुछ नहीं। वेब पर (ऐप शेल बार नहीं) दोनों कुछ नहीं करते।

इन्हें सिर्फ़ ऐसे इंतज़ार के लिए इस्तेमाल करें जहाँ डेटा आने तक DOM में कुछ नहीं बदलता, और हमेशा इसके साथ जोड़ें try/finally:

import { navStart, navEnd } from '@/lib/appmintNative';

export async function openOrder(id: string, render: (order: unknown) => void) {
  navStart();
  try {
    const res = await fetch(`/api/orders/${id}`);
    render(await res.json());
  } finally {
    navEnd();                // always, also when the fetch failed
  }
}

ऐसे सबमिट को लपेटें जिसमें समय लगता है, ताकि सर्वर के काम करने के दौरान यूज़र को बार दिखे।

import { navStart, navEnd } from '@/lib/appmintNative';

export async function submitForm(data: FormData): Promise<boolean> {
  navStart();
  try {
    const res = await fetch('/api/submit', { method: 'POST', body: data });
    return res.ok;
  } catch {
    return false;            // the bar still ends in finally
  } finally {
    navEnd();
  }
}

एक छोटा हेल्पर बनाएँ जो किसी भी प्रॉमिस को लपेटे।

import { navStart, navEnd } from '@/lib/appmintNative';

export async function withNavBar<T>(work: Promise<T>): Promise<T> {
  navStart();
  try { return await work; } finally { navEnd(); }
}

// usage inside a click handler:
// const items = await withNavBar(loadItems(categoryId));

नोट: ये WebToApk.navStarted() / navSettled() को कॉल करते हैं। जब आपका ऐप DOM में स्क्रीन बदलता है तब ऐप पहले से बार चलाता है, इसलिए ज़्यादातर रूट बदलावों को कुछ नहीं चाहिए। लगभग 200 ms से छोटे बदलाव के लिए कुछ नहीं बनता, और जो बार कभी खत्म नहीं की गई वह 2.5 सेकंड बाद खुद रुक जाती है। अपना स्पिनर या स्केलेटन बनाते रहें; बार अतिरिक्त है।

appmintNative.Network appmintNative#

Network.getStatus(): Promise<{ connected, connectionType }> / Network.addListener(event, handler): { remove() }

उदाहरण

Capacitor के Network प्लगइन के आकार में ऑनलाइन/ऑफ़लाइन स्थिति - वही कोड जो networkStatus() / onNetworkChange().

लौटाता है: getStatus() → Promise<{ connected: boolean; connectionType: string }>। addListener(event, handler) → { remove(): void } तुरंत; हैंडलर को हर बदलाव पर वही रूप मिलता है। ज़रूरत: कुछ नहीं।

import { Network } from '@/lib/appmintNative';
import { useEffect, useState } from 'react';

function useOnline() {
  const [online, setOnline] = useState(true);
  useEffect(() => {
    void Network.getStatus().then((s) => setOnline(s.connected));
    const h = Network.addListener('networkStatusChange', (s) => setOnline(s.connected));
    return () => h.remove();
  }, []);
  return online;
}

मौजूदा टाइप एक बार पढ़ें और उसे स्टेटस चिप में दिखाएँ।

import { Network } from '@/lib/appmintNative';
import { useEffect, useState } from 'react';

export function ConnectionChip() {
  const [label, setLabel] = useState('Checking...');
  useEffect(() => {
    void Network.getStatus().then((s) =>
      setLabel(s.connected ? (s.connectionType === 'cellular' ? 'Mobile data' : 'Wi-Fi') : 'Offline'));
  }, []);
  return <span className="chip">{label}</span>;
}

मोबाइल डेटा पर बड़े डाउनलोड को रोकें, और ऑफ़लाइन स्थिति भी संभालें।

import { Network, toast } from '@/lib/appmintNative';

export async function startBigDownload(download: () => Promise<void>) {
  const s = await Network.getStatus();
  if (s.connectionType === 'none' || !s.connected) { toast('You are offline.'); return; }
  if (s.connectionType === 'cellular' && !window.confirm('Download over mobile data?')) return;
  await download();
}

ध्यान दें: इवेंट का नाम अनदेखा किया जाता है - हर लिसनर को कनेक्टिविटी बदलाव मिलते हैं। connectionType का मान 'wifi', 'cellular', 'none' या 'unknown' होता है: ऐप में getStatus() और लिसनर Network प्लगइन इस्तेमाल करते हैं, वेब पर navigator.onLine / navigator.connection.type। connected का मतलब है कि फ़ोन के पास नेटवर्क है, यह नहीं कि आपका सर्वर जवाब देता है।

appmintNative.networkStatus appmintNative#

networkStatus(): { connected: boolean; type: string }

उदाहरण

अभी बताता है कि डिवाइस ऑनलाइन है या नहीं।

लौटाता है: { connected, type }, तुरंत। type होता है 'wifi', 'cellular', 'none' (ऑफ़लाइन) या 'unknown'। ज़रूरत: कुछ नहीं।

रेंडर के दौरान कॉल करने लायक हल्का:

import { networkStatus } from '@/lib/appmintNative';

export function SyncButton({ onSync }: { onSync: () => void }) {
  const { connected } = networkStatus();
  return (
    <button disabled={!connected} onClick={onSync}>
      {connected ? 'Sync now' : 'Offline — will sync later'}
    </button>
  );
}

शुरुआती मान पढ़ें, फिर बदलावों पर नज़र रखें:

import { useEffect, useState } from 'react';
import { networkStatus, onNetworkChange } from '@/lib/appmintNative';

export function useOnline(): boolean {
  const [online, setOnline] = useState(() => networkStatus().connected);
  useEffect(() => onNetworkChange((s) => setOnline(s.connected)), []);
  return online;
}

मोबाइल डेटा पर हल्की इमेज साइज़ चुनें, और 'none' (ऑफ़लाइन) स्थिति का भी जवाब दें।

import { networkStatus } from '@/lib/appmintNative';

export function imageUrl(id: string): string {
  const { connected, type } = networkStatus();
  if (!connected) return '/img/placeholder.svg';         // offline: use the packaged picture
  return `/api/images/${id}?size=${type === 'cellular' ? 'small' : 'large'}`;
}

ध्यान दें:

  • ऐप में यह Network प्लगइन का जवाब है (बैकग्राउंड में ताज़ा रखा जाता है, इसलिए बिल्कुल पहली कॉल अब भी WebView का अपना मान पढ़ सकती है)। वेब पर यह navigator.onLine और, जहाँ ब्राउज़र बताए वहाँ, navigator.connection.type है, वरना 'unknown' - '4g' जैसी स्पीड क्लास कभी टाइप के रूप में नहीं बताई जाती।
  • “Connected” का मतलब है कि डिवाइस के पास नेटवर्क है, यह नहीं कि आपका सर्वर जवाब देता है। फिर भी विफल रिक्वेस्ट संभालें।

appmintNative.nfc appmintNative#

import { nfc } from '@/lib/appmintNative'

उदाहरण

Web NFC (NDEFReader) के ऊपर एक-कॉल हेल्पर: सपोर्ट जाँचें, टैग पढ़ें, टेक्स्ट टैग लिखें। Android ऐप अपना NDEFReader इस्तेमाल करता है; वेब पर यह Chrome का अपना Web NFC है (Android पर Chrome)।

लौटाता है: isSupported(): Promise<boolean>; scan(onTag): Promise<() => Promise<void>> (stop फ़ंक्शन), और onTag को हर टैग पर Array<{recordType, text, mediaType?}> मिलता है - Web NFC के टाइप नाम ('text', 'url', 'mime', …) और हर प्लेटफ़ॉर्म पर डिकोड किया टेक्स्ट; write(text): Promise<boolean> (टैग लिखे जाने पर true, किसी भी विफलता पर false)। ज़रूरत: Android पर, बिल्ड करते समय चरण 4 (Access) में Web NFC Support चालू करें। NDEFReader न होने पर scan() throw करता है - पहले can('nfc') जाँचें।

import { useEffect, useRef, useState } from 'react';
import { can, nfc } from '@/lib/appmintNative';

export function TagReader() {
  const [last, setLast] = useState('');
  const stopRef = useRef<null | (() => Promise<void>)>(null);

  useEffect(() => () => { void stopRef.current?.(); }, []);

  // Check at the tap: on Android the app installs NDEFReader while the page loads.
  const start = async () => {
    if (can('nfc') === 'none' || !(await nfc.isSupported())) { setLast('NFC is not available here.'); return; }
    try {
      stopRef.current = await nfc.scan((records) => {
        setLast(records.filter((r) => r.recordType === 'text' || r.recordType === 'url').map((r) => r.text).join(' | '));
      });
      setLast('Hold a tag to the phone.');
    } catch (e) {
      setLast(`NFC error: ${(e as Error).message}`);
    }
  };

  const writeTag = async () => {
    if (can('nfc') === 'none') { setLast('NFC is not available here.'); return; }
    setLast('Tap a tag to write it...');
    setLast((await nfc.write('Hello from my app')) ? 'Tag written.' : 'Write failed or cancelled.');
  };

  return (
    <div>
      <button onClick={start}>Read tags</button>
      <button onClick={writeTag}>Write tag</button>
      <p>{last}</p>
    </div>
  );
}

टैग पर लिंक लिखें, और फ़ेल होने पर यूज़र को बताएँ कि क्यों।

import { can, nfc, toast } from '@/lib/appmintNative';

export async function writeShareTag(url: string) {
  if (can('nfc') === 'none') { toast('This device cannot write NFC tags.'); return; }
  toast('Hold a blank tag to the back of the phone...');
  const ok = await nfc.write(url);
  toast(ok ? 'Tag written.' : 'Could not write the tag. Try again.');
}

एक टैग पढ़ें, फिर स्कैन करना बंद करें।

import { nfc } from '@/lib/appmintNative';

export function readOnce(): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    let stop: (() => Promise<void>) | undefined;
    let done = false;
    nfc.scan((records) => {
      if (done) return;
      done = true;
      void stop?.();
      resolve(records.map((r) => r.text).join(' '));
    }).then((s) => { stop = s; if (done) void s(); }, reject);
  });
}

नोट: Android पर जब भी बिल्ड में NFC हो, isSupported() true होता है - यह नहीं बताता कि NFC चालू है या नहीं (यह window.AndroidNFC.startScan() बताता है)। स्टॉप फ़ंक्शन के बाद onTag दोबारा कभी कॉल नहीं होता। Android पर ऐप खुद सामने रहते हुए टैग पकड़ता रहता है (ताकि टैप से कोई दूसरा ऐप न खुले), बस उन्हें आपको देना बंद कर देता है। टेक्स्ट रिकॉर्ड के text में कोई भाषा प्रीफ़िक्स नहीं होता; URL रिकॉर्ड का text पूरा URL होता है। write() तब तक रुकता है जब तक कोई टैग टैप न हो। URL या mime रिकॉर्ड लिखने और टैग लॉक करने के लिए NDEFReader सीधे इस्तेमाल करें।

appmintNative.notify appmintNative#

import { notify } from '@/lib/appmintNative'

उदाहरण

AI से बने ऐप से अभी एक नोटिफ़िकेशन दिखाता है, Android ऐप में और वेब पर।

लौटाता है: Promise<boolean> - पोस्ट होने पर true, उपयोगकर्ता के नोटिफ़िकेशन अनुमति अस्वीकार करने, ऐप के लिए नोटिफ़िकेशन बंद होने, या यहाँ उपलब्ध न होने पर false। यह पहली बार अनुमति खुद माँगता है (Android 13+ और वेब)। ज़रूरत: Android: बिल्ड करते समय Access चरण (चरण 4) में नोटिफ़िकेशन चालू।

import { notify, can } from '@/lib/appmintNative';

export async function onOrderReady(orderId: string) {
  if (can('notify') === 'none') return;          // hide the feature instead
  const ok = await notify({
    title: 'Order ready',
    body: `Order #${orderId} is ready to collect`,
    icon: 'https://example.com/img/order.png',    // shown beside the text
    tag: `order-${orderId}`,                     // same tag replaces the old one
  });
  if (!ok) setBanner('Turn on notifications to get order alerts.');
}

सही समय पर पूछें: पहला notify() अनुमति डायलॉग दिखाता है। कुछ भी पोस्ट होने से पहले अपने “अलर्ट चालू करें” बटन से पूछना हो, तो मानक API इस्तेमाल करें, जिसे ऐप भी सपोर्ट करता है:

async function enableAlerts() {
  if (typeof Notification === 'undefined') return false;
  const perm = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission();
  return perm === 'granted';
}

यूज़र के नोटिफ़िकेशन पर टैप करने पर सही स्क्रीन खोलें, notify को इसके साथ मिलाकर onNotificationClick.

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { notify, onNotificationClick } from '@/lib/appmintNative';

export async function announceMessage(id: string, from: string) {
  await notify({ title: `New message from ${from}`, tag: `msg-${id}`, data: { messageId: id } });
}

export function NotificationRouter() {
  const navigate = useNavigate();
  useEffect(() => onNotificationClick((e) => {
    const { messageId } = (e.data ?? {}) as { messageId?: string };
    if (messageId) navigate(`/messages/${messageId}`);
  }), [navigate]);
  return null;
}

ध्यान दें: Android शेल का नोटिफ़िकेशन ब्रिज इस्तेमाल करता है (icon बड़ा आइकन बन जाता है; https:// या data: इमेज इस्तेमाल करें); वेब Notification API इस्तेमाल करता है। यूज़र के हमेशा के लिए मना कर देने पर, notify() बिना दोबारा पूछे false देता है - उन्हें फ़ोन की सेटिंग्स की ओर भेजें। बटनों के लिए notifyWithActions इस्तेमाल करें; टिकाऊ प्रोग्रेस कार्ड के लिए इस्तेमाल करें liveProgress.

appmintNative.notifyWithActions appmintNative#

import { notifyWithActions } from '@/lib/appmintNative'

उदाहरण

3 बटन तक वाला नोटिफ़िकेशन दिखाता है ("Mark done", "Snooze")। टैप इसके ज़रिए वापस आता है onNotificationAction.

लौटाता है: Promise<boolean> - पोस्ट होने पर true, नोटिफ़िकेशन की अनुमति अस्वीकार होने पर false (यह पहली बार पूछता है, notify() की तरह)। ज़रूरत: Android: बिल्ड करते समय Access चरण (चरण 4) में नोटिफ़िकेशन चालू, और Android 13+ पर उपयोगकर्ता की अनुमति। वेब पर can('notificationActions') है 'none'.

import { notifyWithActions, onNotificationAction, can } from '@/lib/appmintNative';

export async function remindTask(taskId: string, title: string) {
  if (can('notificationActions') === 'none') return false;   // web: hide the buttons
  return notifyWithActions({
    title,
    body: 'Due now',
    tag: `task-${taskId}`,                       // comes back as e.tag
    actions: [
      { id: 'done', title: 'Mark done' },
      { id: 'snooze', title: 'Snooze' },
    ],
  });
}

// Mount once (e.g. in App.tsx):
export const stopListening = onNotificationAction((e) => {
  // e = { actionId: 'snooze', tag: 'task-42', text: '' }
  const taskId = e.tag.replace('task-', '');
  if (e.actionId === 'done') completeTask(taskId);
  if (e.actionId === 'snooze') snoozeTask(taskId);
});

एक टाइमर खत्म हुआ: एक बटन पाँच मिनट जोड़ता है, दूसरा उसे रोकता है।

import { notifyWithActions, onNotificationAction, can } from '@/lib/appmintNative';

export async function timerDone() {
  if (can('notificationActions') === 'none') return false;
  return notifyWithActions({
    title: 'Timer finished',
    tag: 'timer',
    actions: [{ id: 'more', title: '+5 min' }, { id: 'stop', title: 'Stop' }],
  });
}

export const stopTimerButtons = onNotificationAction((e) => {
  if (e.tag !== 'timer') return;                 // ignore other notifications' buttons
  if (e.actionId === 'more') startTimer(5);
});

कॉल के बाद नामंज़ूर अनुमति को संभालें, और कॉम्पोनेंट में सफ़ाई करें।

import { useEffect, useState } from 'react';
import { notifyWithActions, onNotificationAction } from '@/lib/appmintNative';

export function ApprovalRequest({ requestId }: { requestId: string }) {
  const [note, setNote] = useState('');
  useEffect(() => onNotificationAction((e) => {
    if (e.tag === `req-${requestId}`) setNote(e.actionId === 'approve' ? 'Approved' : 'Rejected');
  }), [requestId]);

  const ask = async () => {
    const posted = await notifyWithActions({
      title: 'Approve request?', tag: `req-${requestId}`,
      actions: [{ id: 'approve', title: 'Approve' }, { id: 'reject', title: 'Reject' }],
    });
    if (!posted) setNote('Notifications are off, so no buttons were shown.');
  };
  return <button onClick={ask}>{note || 'Ask me'}</button>;
}

नोट: हर बटन अपना title दिखाता है; उसका id वह है जो e.actionId के रूप में लौटता है। input विकल्प (इनलाइन जवाब बॉक्स) इस ऐप में समर्थित नहीं है - इसे अनदेखा किया जाता है, बटन में जवाब बॉक्स नहीं होता और e.text हमेशा '' रहता है। टैप पेज तक सिर्फ़ तब पहुँचता है जब ऐप चल रहा हो। वेब पर रैपर बिना बटन का सादा नोटिफ़िकेशन भेजेगा, इसीलिए उदाहरण पहले can() जाँचता है।

appmintNative.offlineBanner appmintNative#

offlineBanner(opts?: { message?: string; onlineMessage?: string }): () => void

उदाहरण

एक तैयार पट्टी लगाता है जो कनेक्शन टूटने पर स्टेटस बार के नीचे सरककर आती है, और लौटने पर थोड़ी देर "Back online" कहती है।

लौटाता है: एक unsubscribe फ़ंक्शन जो बैनर हटाता है और सुनना बंद करता है। ज़रूरत: कुछ नहीं।

ऐप शुरू होते समय इसे एक बार माउंट करें:

import { useEffect } from 'react';
import { offlineBanner } from '@/lib/appmintNative';

export function App() {
  useEffect(() => offlineBanner(), []);          // cleanup removes it
  return <main>{/* … */}</main>;
}

आपके अपने शब्द (जैसे ऐप की भाषा में):

import { offlineBanner } from '@/lib/appmintNative';

const off = offlineBanner({ message: 'Sin conexión', onlineMessage: 'Conectado de nuevo' });

बैनर सिर्फ़ ऐप के साइन इन वाले हिस्से के लिए माउंट करें, और साइन-आउट पर हटा दें।

import { useEffect } from 'react';
import { offlineBanner } from '@/lib/appmintNative';

export function useOfflineBanner(signedIn: boolean) {
  useEffect(() => {
    if (!signedIn) return;
    return offlineBanner({ message: 'Offline: changes will sync later' });
  }, [signedIn]);
}

ध्यान दें:

  • डिफ़ॉल्ट: "You are offline" और "Back online"। ऑनलाइन संदेश करीब 1.8 सेकंड बाद छिप जाता है; ऑफ़लाइन वाला कनेक्शन लौटने तक रहता है।
  • अगर माउंट होते समय डिवाइस पहले से ऑफ़लाइन है, तो बैनर तुरंत दिखता है।
  • onNetworkChange() पर बना है, इसलिए हर सतह पर असली है। रंग ऐप के टोकन से आते हैं (ऑफ़लाइन के लिए --ink / --bg, ऑनलाइन के लिए --accent / --accent-ink)। स्क्रीन रीडर के लिए इसमें role="status" है।
  • इसे एक बार माउंट करें। दो कॉल दो बैनर बनाती हैं।

appmintNative.onAlarm appmintNative#

onAlarm(cb) — @/lib/appmintNative

उदाहरण

अलार्म के नतीजे सुनता है: अलार्म से खुला ऐप (action: 'open', ringing के साथ), और बजना कैसे ख़त्म हुआ - dismiss, snooze, missed (बजकर ख़त्म हुआ, या दूसरे अलार्म ने उसे बदला) या error। ऐप बंद रहने के दौरान के नतीजे पेज के अगली बार लोड होने पर दिए जाते हैं।

लौटाता है: सुनना बंद करने वाला फ़ंक्शन। कॉलबैक को { id, action, ringing?, source?, snoozeMinutes?, snoozedUntil?, error?, soundError? } मिलता है। ज़रूरत: बिल्ड करते समय Access चरण (चरण 4) में 🔔 Alarm clock।

import { useEffect, useState } from 'react';
import { onAlarm } from '@/lib/appmintNative';

export function AlarmHost() {
  const [ringingId, setRingingId] = useState<string | null>(null);
  useEffect(() => onAlarm((e) => {
    if (e.action === 'open' && e.ringing) setRingingId(e.id);
    if (e.action === 'dismiss' || e.action === 'snooze' || e.action === 'missed') setRingingId(null);
  }), []);
  return ringingId ? <RingScreen id={ringingId} onClose={() => setRingingId(null)} /> : null;
}

पहले alarmSupport() का जवाब देखकर अलार्म सेट करें।

import { alarmSupport, scheduleAlarm } from '@/lib/appmintNative';

export function setWakeUp(at: Date): string {
  const support = alarmSupport();
  if (support === 'unavailable') return 'Alarms are only available in the Android app.';
  if (support !== 'ready') return `Alarm needs attention first (${support}).`;
  const ok = scheduleAlarm('wake-up', { at: at.getTime(), title: 'Wake up', repeat: 'weekdays', snoozeMinutes: 10 });
  return ok ? 'Alarm set.' : 'Could not set the alarm.';
}

बज रहे अलार्म को अपनी स्क्रीन से रोकें या स्नूज़ करें, और नतीजे पर प्रतिक्रिया दें।

import { useEffect, useState } from 'react';
import { onAlarm, stopAlarm, snoozeAlarm, isAlarmRinging } from '@/lib/appmintNative';

export function RingControls() {
  const [ringing, setRinging] = useState(() => isAlarmRinging());
  const [last, setLast] = useState('');
  useEffect(() => onAlarm((e) => {
    if (e.action === 'open') setRinging(!!e.ringing);
    else { setRinging(false); setLast(e.action === 'error' ? `Alarm error: ${e.error}` : `Alarm ${e.action}`); }
  }), []);
  if (!ringing) return <p>{last}</p>;
  return (
    <div>
      <button onClick={() => stopAlarm()}>Stop</button>
      <button onClick={() => snoozeAlarm('', 5)}>Snooze 5 min</button>
    </div>
  );
}

नोट: ऐप खोलने वाले सामान्य रिमाइंडर भी यहाँ action: 'open' के साथ आते हैं और ringing: false.

appmintNative.onBackGesture appmintNative#

onBackGesture(handler: () => boolean): () => void

उदाहरण

खुली शीट, मोडल, लाइटबॉक्स या विज़ार्ड चरण को पहले BACK जेस्चर का जवाब देने देता है - ऐप में Android का किनारे से स्वाइप / बैक बटन, वेब पर ब्राउज़र का बैक बटन।

लौटाता है: एक unsubscribe फ़ंक्शन - व्यू बंद या अनमाउंट होने पर इसे कॉल करें। आपका हैंडलर प्रेस को संभालने के लिए true लौटाता है (आपने कुछ बंद किया) या उसे आगे जाने देने के लिए false। ज़रूरत: कुछ नहीं।

सिर्फ़ तब रजिस्टर करें जब शीट खुली हो:

import { useEffect, useState } from 'react';
import { onBackGesture } from '@/lib/appmintNative';

export function FilterPanel() {
  const [open, setOpen] = useState(false);

  useEffect(() => {
    if (!open) return;
    return onBackGesture(() => { setOpen(false); return true; });   // back closes THIS, not the app
  }, [open]);

  return (
    <>
      <button onClick={() => setOpen(true)}>Filters</button>
      {open && <div role="dialog" aria-label="Filters">{/* … */}</div>}
    </>
  );
}

विज़ार्ड में पीछे जाएँ, और आख़िरी चरण का बैक सामान्य रूप से बाहर निकलने दें:

import { useEffect } from 'react';
import { onBackGesture } from '@/lib/appmintNative';

export function useWizardBack(step: number, setStep: (n: number) => void) {
  useEffect(() => onBackGesture(() => {
    if (step === 0) return false;       // not ours: normal back navigation happens
    setStep(step - 1);
    return true;
  }), [step, setStep]);
}

बैक जेस्चर से लाइटबॉक्स बंद करें, और रजिस्ट्रेशन को इमेज के खुले होने से जोड़कर रखें।

import { useEffect } from 'react';
import { onBackGesture } from '@/lib/appmintNative';

export function Lightbox({ src, onClose }: { src: string | null; onClose: () => void }) {
  useEffect(() => {
    if (!src) return;
    return onBackGesture(() => { onClose(); return true; });
  }, [src, onClose]);
  return src ? <img src={src} alt="" onClick={onClose} /> : null;
}

ध्यान दें:

  • हैंडलर एक स्टैक बनाते हैं: सबसे आख़िर में रजिस्टर हुए (ऊपर वाली शीट) से पहले पूछा जाता है। जो हैंडलर त्रुटि फेंकता है वह कुछ भी नहीं लेता।
  • Android पर यह शेल के बैक हैंडलर से जाता है। इसके बिना, शीट खुली होने पर पीछे स्वाइप करने से पूरा ऐप बंद हो जाता है।
  • वेब पर जब तक कोई हैंडलर रजिस्टर है यह एक हिस्ट्री एंट्री जोड़ता है और आख़िरी के अनसब्सक्राइब होने पर उसे हटा देता है।
  • फिर भी दिखने वाला बैक / बंद करने का कंट्रोल रखें: कई यूज़र बैक जेस्चर इस्तेमाल ही नहीं करते।
  • React Router सामान्य पेज बैक पहले से संभालता है; इसे सिर्फ़ उस स्थिति के लिए इस्तेमाल करें जो राउटर नहीं जानता। हमेशा unsubscribe करें, वरना बंद शीट बैक प्रेस खाती रहती है।

appmintNative.onInAppMessage appmintNative#

import { onInAppMessage } from '@/lib/appmintNative'

उदाहरण

इस इंस्टॉल पर दिखाए गए, क्लिक किए गए, खारिज किए गए या जवाब दिए गए हर इन-ऐप संदेश (क्रिएटर के कैंपेन) के लिए आपको कॉल करता है। अनसब्सक्राइब फ़ंक्शन लौटाता है।

लौटाता है: () => void। कॉलबैक को {type: 'shown' | 'clicked' | 'dismissed' | 'answered', id, name, template?, presented?, message?, button?, link?, question?, value?} मिलता है। वेब पर कभी नहीं चलता। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू हो।

import { useEffect } from 'react';
import { onInAppMessage } from '@/lib/appmintNative';

export function useInAppAnalytics(log: (event: string, name: string) => void) {
  useEffect(() => onInAppMessage((e) => log('inapp_' + e.type, e.name)), [log]);
}

संदेश खुद बनाएँ (setInAppPresentation('none') के साथ, presented का मान false होता है और message डिज़ाइन लाता है):

import { useEffect, useState } from 'react';
import { onInAppMessage, setInAppPresentation, type InAppMessageEvent } from '@/lib/appmintNative';

export function useOwnInAppDialog() {
  const [msg, setMsg] = useState<InAppMessageEvent['message'] | null>(null);
  useEffect(() => {
    setInAppPresentation('none');
    const off = onInAppMessage((e) => { if (e.type === 'shown' && !e.presented && e.message) setMsg(e.message); });
    return () => { off(); setInAppPresentation('auto'); };
  }, []);
  return { msg, close: () => setMsg(null) };
}

ऐप की अपनी ड्रॉइंग में यूज़र के दबाए बटन पर प्रतिक्रिया दें:

import { onInAppMessage } from '@/lib/appmintNative';

onInAppMessage((e) => {
  if (e.type === 'clicked' && e.link === 'appmint://notifications/enable') refreshNotificationBadge();
});

नोट: संदेश पर ही टैप करने पर button का मान -1 होता है, वरना बटन का इंडेक्स।

जवाब (type: 'answered')। स्टार पंक्ति (Star survey, Rate us) या 0-10 स्कोर चिप्स (NPS score) वाला संदेश बंद होने पर जवाब एक बार रिपोर्ट करता है: question का मान 'stars' (मान 1-5) या 'nps' (मान 0-10) होता है, और template उस गैलरी टेम्पलेट का नाम बताता है जिससे वह आया। सेंड सेंटर औसत या NPS स्कोर दिखाता है; आपका कोड आगे कार्रवाई कर सकता है:

import { onInAppMessage } from '@/lib/appmintNative';

onInAppMessage((e) => {
  if (e.type === 'answered' && e.question === 'nps' && (e.value ?? 10) <= 6) openFeedbackSheet();
});

स्टार रेटिंग का अपना रिकॉर्ड रखें:

import { onInAppMessage } from '@/lib/appmintNative';

onInAppMessage((e) => {
  if (e.type !== 'answered' || e.question !== 'stars') return;
  void fetch('/api/ratings', { method: 'POST', body: JSON.stringify({ stars: e.value, campaign: e.name }) });
});

ऊँची रेटिंग देने वालों को धन्यवाद दें, एक React कंपोनेंट में:

import { useEffect, useState } from 'react';
import { onInAppMessage } from '@/lib/appmintNative';

export function useHappyUser() {
  const [happy, setHappy] = useState(false);
  useEffect(() => onInAppMessage((e) => {
    if (e.type === 'answered' && ((e.question === 'stars' && (e.value ?? 0) >= 4) || (e.question === 'nps' && (e.value ?? 0) >= 9))) setHappy(true);
  }), []);
  return happy;
}

appmintNative.onKeyboard appmintNative#

onKeyboard(cb: (insetPx: number) => void): () => void

उदाहरण

बताता है कि ऑन-स्क्रीन कीबोर्ड पेज के कितने पिक्सेल ढकता है, हर बार जब वह बदलता है, ताकि नीचे का बार उसके ऊपर रह सके।

लौटाता है: अनसब्सक्राइब फ़ंक्शन - अनमाउंट पर इसे कॉल करें। कॉलबैक को CSS पिक्सेल में एक संख्या मिलती है: नीचे जितनी अतिरिक्त पैडिंग चाहिए, कीबोर्ड बंद होने पर 0। ज़रूरत: कुछ नहीं।

import { useEffect, useState } from 'react';
import { onKeyboard } from '@/lib/appmintNative';

export function Composer() {
  const [kb, setKb] = useState(0);
  useEffect(() => onKeyboard(setKb), []);          // the returned function unsubscribes

  return (
    <form
      className="fixed inset-x-0 bottom-0 flex gap-2 p-3"
      style={{ paddingBottom: `calc(var(--safe-bottom) + 12px + ${kb}px)` }}
    >
      <input className="flex-1" enterKeyHint="send" aria-label="Message" />
      <button type="submit">Send</button>
    </form>
  );
}

कीबोर्ड खुलने पर चैट लिस्ट को सबसे नए संदेश तक स्क्रॉल रखें:

import { useEffect, useRef } from 'react';
import { onKeyboard } from '@/lib/appmintNative';

export function MessageList({ children }: { children: React.ReactNode }) {
  const endRef = useRef<HTMLDivElement>(null);
  useEffect(() => onKeyboard((inset) => {
    if (inset > 0) endRef.current?.scrollIntoView({ block: 'end' });   // keyboard just opened
  }), []);
  return <div className="overflow-y-auto">{children}<div ref={endRef} /></div>;
}

एक दोबारा इस्तेमाल होने वाला हुक, सेफ़-एरिया गैप के साथ जो कीबोर्ड खुला होने पर गायब हो जाता है:

import { useEffect, useState } from 'react';
import { onKeyboard } from '@/lib/appmintNative';

export function useKeyboardInset(): number {
  const [inset, setInset] = useState(0);        // 0 until the keyboard first opens
  useEffect(() => onKeyboard(setInset), []);
  return inset;
}

// In a component: the home-indicator gap is only needed when the keyboard is closed.
// const kb = useKeyboardInset();
// <footer style={{ paddingBottom: kb > 0 ? kb : 'var(--safe-bottom)' }}>...</footer>

ध्यान दें:

  • ऐप में यह Capacitor Keyboard प्लगइन को सुनता है और कीबोर्ड का सिर्फ़ वह हिस्सा बताता है जिसे लेआउट ने पहले से नहीं सोखा। शेल आम तौर पर कीबोर्ड के लिए पेज का आकार बदलता है, इसलिए वहाँ मान अक्सर 0 होता है - यह सही है; कीबोर्ड की कच्ची ऊँचाई खुद न जोड़ें।
  • फ़ुलस्क्रीन या कियोस्क ऐप में (पेज का साइज़ नहीं बदलता) यह कीबोर्ड की पूरी ऊँचाई बताता है।
  • वेब पर यह visualViewport इस्तेमाल करता है, कॉलबैक को तुरंत एक बार कॉल करता है, और 80 px से कम को ब्राउज़र क्रोम मानता है (इस रूप में बताया जाता है 0).
  • ऐप में पहली कॉल तब आती है जब कीबोर्ड पहली बार खुलता है, इसलिए अपनी स्थिति इससे शुरू करें 0.

appmintNative.onNetworkChange appmintNative#

onNetworkChange(cb: (status: { connected: boolean; type: string }) => void): () => void

उदाहरण

कनेक्शन टूटने या लौटने पर आपको कॉल करता है - ऑफ़लाइन बैनर के लिए, या कतार में लगी लिखाई दोबारा आज़माने के लिए।

लौटाता है: अनसब्सक्राइब फ़ंक्शन - अनमाउंट पर उसे कॉल करें। ज़रूरत: कुछ नहीं।

डिवाइस के फिर ऑनलाइन होने पर कतार में लगे काम को दोबारा चलाएँ:

import { useEffect } from 'react';
import { onNetworkChange, toast } from '@/lib/appmintNative';

export function useRetryWhenOnline(flushQueue: () => Promise<number>) {
  useEffect(() => {
    const off = onNetworkChange(async (s) => {
      if (!s.connected) return;
      const sent = await flushQueue();
      if (sent) toast(`Synced ${sent} change(s)`);
    });
    return off;                                  // unsubscribe on unmount
  }, [flushQueue]);
}

मौजूदा स्थिति तुरंत दिखाएँ, फिर बदलावों पर नज़र रखें (सब्सक्राइब करते समय कॉलबैक नहीं चलता):

import { useEffect, useState } from 'react';
import { networkStatus, onNetworkChange } from '@/lib/appmintNative';

export function useOnline(): boolean {
  const [online, setOnline] = useState(() => networkStatus().connected);
  useEffect(() => onNetworkChange((s) => setOnline(s.connected)), []);
  return online;
}

मोबाइल डेटा पर बड़े डाउनलोड रोकें, और 'none' को ऑफ़लाइन मानें:

import { useEffect } from 'react';
import { onNetworkChange, toast } from '@/lib/appmintNative';

export function useDataSaver(setHeavyAllowed: (ok: boolean) => void) {
  useEffect(() => onNetworkChange((s) => {
    if (s.type === 'none' || !s.connected) { toast('You are offline'); setHeavyAllowed(false); return; }
    setHeavyAllowed(s.type !== 'cellular');   // 'wifi' and 'unknown' may download freely
  }), [setHeavyAllowed]);
}

ध्यान दें:

  • सब्सक्राइब करते समय यह आपको मौजूदा स्थिति के साथ कॉल नहीं करता। शुरुआती मान के लिए networkStatus() पढ़ें।
  • ऐप में यह Capacitor Network प्लगइन (networkStatusChange) को सुनता है; type होता है 'wifi', 'cellular', 'none' या 'unknown'.
  • वेब पर यह online / offline इवेंट सुनता है और बताता है networkStatus().
  • तैयार बैनर के लिए offlineBanner() इस्तेमाल करें, जो इसी पर बना है।

appmintNative.onNotificationAction appmintNative#

import { onNotificationAction } from '@/lib/appmintNative'

उदाहरण

नोटिफ़िकेशन बटनों पर टैप सुनता है (इनसे notifyWithActions).

लौटाता है: सुनना बंद करने वाला फ़ंक्शन। कॉलबैक को { actionId, tag } मिलता है - बटन की id और नोटिफ़िकेशन का टैग। Android ऐप कोई रिप्लाई text नहीं भेजता (इनलाइन रिप्लाई सपोर्ट नहीं है), इसलिए e.text पर भरोसा न करें। ज़रूरत: वही जो notifyWithActions.

import { useEffect } from 'react';
import { onNotificationAction } from '@/lib/appmintNative';

export function useNotificationActions(openChat: (chatId: string) => void) {
  useEffect(() => {
    const stop = onNotificationAction(({ actionId, tag }) => {
      if (actionId === 'Open') openChat(tag);
      if (actionId === 'Mute') muteChat(tag);
    });
    return stop;                              // remove on unmount
  }, [openChat]);
}

हाथ से लिखे पेज में रॉ इवेंट (Android ऐप):

window.addEventListener('appmint:notification-action', function (e) {
  // e.detail = { actionId: 'Mute', tag: 'chat-7' }
  console.log(e.detail.actionId, e.detail.tag);
});

बटन वाला नोटिफ़िकेशन सिर्फ़ वहीं भेजें जहाँ बिल्ड उन्हें सपोर्ट करता है, और टैप को नोटिफ़िकेशन के टैग से संभालें:

import { useEffect } from 'react';
import { can, notifyWithActions, onNotificationAction, toast } from '@/lib/appmintNative';

export async function remindWithButtons(taskId: string, title: string) {
  if (can('notificationActions') === 'none') { toast(title); return; }   // no buttons here
  await notifyWithActions({
    title, body: 'Tap a button', tag: `task-${taskId}`,
    actions: [{ id: 'done', title: 'Done' }, { id: 'later', title: 'Later' }],
  });
}

export function useTaskActions(complete: (id: string) => void, snooze: (id: string) => void) {
  useEffect(() => onNotificationAction(({ actionId, tag }) => {
    const id = tag.replace(/^task-/, '');
    if (actionId === 'done') complete(id);
    else if (actionId === 'later') snooze(id);
  }), [complete, snooze]);
}

ध्यान दें: Android ऐप में यह appmint:notification-action इवेंट है (साथ में window.onAppMintNotificationAction(detail)); वेब पर यह कुछ नहीं करता। टैप तभी पहुँचता है जब ऐप चल रहा हो - बटन दबाने से ऐप नहीं खुलता।

appmintNative.onNotificationClick appmintNative#

import { onNotificationClick } from '@/lib/appmintNative'

उदाहरण

जब यूज़र ऐप की पोस्ट की हुई नोटिफ़िकेशन पर टैप करता है - कोल्ड स्टार्ट वाले टैप समेत - तब कॉलबैक चलाता है, ताकि ऐप सही स्क्रीन खोल सके।

लौटाता है: unsubscribe फ़ंक्शन। कॉलबैक को { tag, title, body, data } मिलता है - data वह है जो आपने notify({ …, data }) को दिया। ज़रूरत: नोटिफ़िकेशन, notify() की तरह। इसके पीछे का इवेंट: appmint:notification-click.

import { useEffect } from 'react';
import { notify, onNotificationClick } from '@/lib/appmintNative';

export function useOrderAlerts(navigate: (path: string) => void) {
  useEffect(() => onNotificationClick((e) => {
    const d = e.data as { orderId?: number } | null;
    if (d?.orderId) navigate(`/orders/${d.orderId}`);
  }), [navigate]);
}

async function orderShipped(orderId: number) {
  await notify({ title: 'Order shipped', body: `#${orderId} is on its way`, tag: `order-${orderId}`, data: { orderId } });
}

टैप के बाद सही स्क्रीन खोलें, तब भी जब उसने ऐप को शुरू से चालू किया हो, और ऐसे नोटिफ़िकेशन अनदेखे करें जिनमें डेटा नहीं है:

import { useEffect } from 'react';
import { onNotificationClick } from '@/lib/appmintNative';

export function useNotificationRouting(navigate: (path: string) => void) {
  useEffect(() => onNotificationClick(({ tag, data }) => {
    const d = data as { screen?: string; id?: string } | null;
    if (!d?.screen) { console.log('plain notification tapped:', tag); return; }
    navigate(d.id ? `/${d.screen}/${d.id}` : `/${d.screen}`);
  }), [navigate]);
}

नोटिफ़िकेशन तभी भेजें जहाँ नोटिफ़िकेशन होते हैं, टैप के लिए डेटा के साथ (data को चाहिए tag):

import { can, notify, toast } from '@/lib/appmintNative';

export async function announceMessage(chatId: string, from: string, text: string) {
  if (can('notify') === 'none') return;                 // nothing to tap on this surface
  const shown = await notify({ title: from, body: text, tag: `chat-${chatId}`, data: { screen: 'chat', id: chatId } });
  if (!shown) toast('Notifications are turned off');
}

ध्यान दें: टैप स्प्लैश और PIN स्क्रीन से होकर पेज तक पहुँचता है। data JSON होना चाहिए (16 KB से कम) और इसकी ज़रूरत है tag.

appmintNative.onPullToRefresh appmintNative#

onPullToRefresh(el: HTMLElement, onRefresh: () => void | Promise<void>, opts?: { threshold?: number }): () => void

उदाहरण

स्क्रॉल होने वाली सूची में खींचकर रिफ़्रेश करने वाला जेस्चर जोड़ता है: ऊपर पहुँचकर नीचे खींचें, छोड़ें, और आपका डेटा फिर लोड होता है।

लौटाता है: अनसब्सक्राइब फ़ंक्शन - अनमाउंट पर उसे कॉल करें। ज़रूरत: कुछ नहीं।

el वह एलिमेंट है जो स्क्रॉल होता है (या document.documentElement जब पूरा पेज स्क्रॉल होता है)। स्पिनर तब तक रहता है जब तक onRefresh से मिला promise पूरा नहीं होता:

import { useEffect, useRef } from 'react';
import { onPullToRefresh, toast } from '@/lib/appmintNative';

export function Inbox({ reload }: { reload: () => Promise<void> }) {
  const listRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!listRef.current) return;
    return onPullToRefresh(listRef.current, async () => {
      try { await reload(); }
      catch { toast('Could not refresh — check your connection'); }
    });
  }, [reload]);

  return <div ref={listRef} className="h-full overflow-y-auto">{/* rows */}</div>;
}

पूरा स्क्रॉल होने वाला पेज, लंबे खिंचाव के साथ:

import { onPullToRefresh } from '@/lib/appmintNative';

declare function reloadFeed(): Promise<void>;
const off = onPullToRefresh(document.documentElement, reloadFeed, { threshold: 96 });

डेटा हुक से लिस्ट रिफ़्रेश करें, डेटा आने पर हैप्टिक टिक के साथ:

import { useEffect, useRef } from 'react';
import { onPullToRefresh, haptic } from '@/lib/appmintNative';

export function useListRefresh(refetch: () => Promise<unknown>) {
  const ref = useRef<HTMLUListElement>(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    return onPullToRefresh(el, async () => {
      await refetch();
      haptic('success');                     // the spinner stays until this promise settles
    }, { threshold: 80 });
  }, [refetch]);
  return ref;                                // <ul ref={ref} className="h-full overflow-y-auto">
}

ध्यान दें:

  • यह तभी शुरू होता है जब लिस्ट सबसे ऊपर तक स्क्रॉल हो। डिफ़ॉल्ट threshold 72 px का खिंचाव है।
  • यह ऐप के रंगों (--surface, --accent) में गोल इंडिकेटर बनाता है, कमिट बिंदु पर मध्यम हैप्टिक देता है, और रिफ़्रेश चलने के दौरान नई पुल को अनदेखा करता है।
  • यह आपका DATA रिफ़्रेश करता है, पेज नहीं। शेल का अपना पेज-रीलोड पुल (चरण 3 (Integrate) में Enable Pull-to-Refresh) डिफ़ॉल्ट रूप से बंद है - इसे इस्तेमाल करते समय बंद ही रखें, ताकि पुल पेज को भी रीलोड न करे।
  • Pointer Events पर बना है, इसलिए हर सतह पर एक जैसा है। यह el पर overscroll-behavior-y: contain सेट करता है और unsubscribe पर उसे वापस कर देता है।

appmintNative.onShared appmintNative#

onShared(cb: (p: { text, subject, url, receivedAt }) => void): () => void

उदाहरण

जब कोई दूसरा ऐप इस ऐप में टेक्स्ट या लिंक शेयर करे और ऐप पहले से खुला हो, तब आपको कॉल करता है।

लौटाता है: एक unsubscribe फ़ंक्शन - अनमाउंट पर इसे कॉल करें। ज़रूरत: बिल्ड का दूसरे ऐप्स से शेयर पाएँ विकल्प "टेक्स्ट और लिंक" के साथ - जब आपका कोड onShared( या कॉल करता है तो अपने आप चालू हो जाता है sharedText(.

दोनों स्थितियाँ संभालें - ऐप शुरू करने वाला शेयर, और बाद में आने वाला शेयर:

import { useEffect } from 'react';
import { can, sharedText, clearShared, onShared, toast } from '@/lib/appmintNative';

export function useIncomingShares(save: (text: string, url: string) => Promise<void>) {
  useEffect(() => {
    if (can('shareTarget') === 'none') return;
    const handle = async (p: { text: string; url: string }) => {
      await save(p.text, p.url);
      clearShared();
      toast('Added');
    };
    const waiting = sharedText();
    if (waiting) void handle(waiting);                    // shared before the app opened
    return onShared((p) => void handle(p));              // shared while open; returns the unsubscribe
  }, [save]);
}

हाथ से लिखा पेज शेल इवेंट को खुद सुन सकता है:

window.addEventListener('appmint:shared', function (e) {
  var p = e.detail;                  // { text, subject, url, receivedAt }
  if (p && p.text) console.log('shared:', p.text);
});

शेयर किए गए लिंक से "नया नोट" फ़ॉर्म पहले से भरें, सिर्फ़ वहीं जहाँ बिल्ड शेयर पा सकता है:

import { useEffect, useState } from 'react';
import { can, onShared, toast } from '@/lib/appmintNative';

export function NewNote() {
  const [body, setBody] = useState('');
  useEffect(() => {
    if (can('shareTarget') === 'none') return;
    return onShared((p) => {
      setBody([p.subject, p.text, p.url].filter(Boolean).join('\n'));
      toast('Shared text added');
    });
  }, []);
  return <textarea value={body} onChange={(e) => setBody(e.target.value)} />;
}

ध्यान दें:

  • Android: शेल का appmint:shared इवेंट सुनता है; पेज में window.onAppMintShared(detail) हो तो शेल उसे भी कॉल करता है। वही पेलोड sharedText() के लिए भी तब तक रखा जाता है जब तक clearShared().
  • वेब पर यह कभी नहीं चलता और एक खाली unsubscribe लौटाता है।

appmintNative.onVolumeChange appmintNative#

onVolumeChange(cb) — @/lib/appmintNative

उदाहरण

फ़ोन के किसी भी वॉल्यूम स्तर के बदलने पर cb को कॉल करता है - हार्डवेयर कीज़, सिस्टम स्लाइडर, कोई दूसरा ऐप।

लौटाता है: सुनना बंद करने वाला फ़ंक्शन (आखिरी रुकने वाला नेटिव वॉच बंद करता है)। जिस स्ट्रीम में बदलाव हुआ उसके लिए कॉलबैक को { stream, current, max, percent, muted? } मिलता है। वेब पर कुछ नहीं करता। ज़रूरत: कुछ नहीं।

import { useEffect, useState } from 'react';
import { getVolume, onVolumeChange } from '@/lib/appmintNative';

export function MediaVolumeBadge() {
  const [pct, setPct] = useState(() => getVolume('media')?.percent ?? null);
  useEffect(() => onVolumeChange((v) => { if (v.stream === 'media') setPct(v.percent); }), []);
  return pct === null ? null : <span>🔊 {pct}%</span>;
}

वीडियो चलते समय अगर मीडिया वॉल्यूम पूरा नीचे कर दिया गया हो तो चेतावनी दें, और अनमाउंट पर सुनना बंद करें:

import { useEffect } from 'react';
import { onVolumeChange, toast } from '@/lib/appmintNative';

export function useQuietWarning(playing: boolean) {
  useEffect(() => {
    if (!playing) return;
    return onVolumeChange((v) => {
      if (v.stream === 'media' && (v.percent === 0 || v.muted)) toast('Volume is off');
    });
  }, [playing]);
}

फ़ोन का स्तर स्लाइडर पर दिखाएँ और यूज़र का बदलाव वापस लिखें (वेब पर वॉल्यूम नहीं होता, इसलिए वहाँ getVolume का मान null है):

import { useEffect, useState } from 'react';
import { getVolume, setVolume, onVolumeChange } from '@/lib/appmintNative';

export function MediaVolumeSlider() {
  const [pct, setPct] = useState<number | null>(() => getVolume('media')?.percent ?? null);
  useEffect(() => onVolumeChange((v) => { if (v.stream === 'media') setPct(v.percent); }), []);
  if (pct === null) return null;                       // not the Android app
  return (
    <input type="range" min={0} max={100} value={pct}
      onChange={(e) => { const r = setVolume('media', Number(e.target.value)); if (r.ok) setPct(Number(e.target.value)); }} />
  );
}

appmintNative.openNotificationSettings appmintNative#

openNotificationSettings(): Promise<boolean>

उदाहरण

इस ऐप की अपनी नोटिफ़िकेशन सेटिंग्स स्क्रीन खोलता है (Android ऐप)।

लौटाता है: Promise<boolean> - true जब स्क्रीन खुली; वेब पर और जहाँ ऐसी स्क्रीन नहीं है वहाँ false। कंट्रोल बनाने से पहले can('notificationSettings') जाँचें। ज़रूरत: कुछ नहीं।

import { can, openNotificationSettings } from '@/lib/appmintNative';

export function NotificationSettingsRow(row: HTMLElement) {
  row.hidden = can('notificationSettings') === 'none';
  row.addEventListener('click', () => { void openNotificationSettings(); });
}

ध्यान दें: एक टैप में "Turn on notifications" के लिए enableNotifications() इस्तेमाल करें, जो यह स्क्रीन तभी खोलता है जब अनुमति डायलॉग दोबारा नहीं दिखाया जा सकता।

appmintNative.passkeys appmintNative#

passkeys — available / create / get / origin / assetLinks

उदाहरण

आपके passkey सर्वर के बनाए JSON विकल्पों के साथ Passkey साइन-इन और रजिस्ट्रेशन। Android ऐप में यह Android के passkey मैनेजर का इस्तेमाल करता है; वेब पर यह इस्तेमाल करता है navigator.credentials.

लौटाता है: available() → 'native' | 'web' | 'none'; create(optionsJSON) → RegistrationResponseJSON का Promise; get(optionsJSON) → AuthenticationResponseJSON का Promise (दोनों को अपने सर्वर पर POST करें)। ये DOMException (NotAllowedError, InvalidStateError, SecurityError, NotSupportedError) के साथ reject होते हैं। origin() → स्ट्रिंग, assetLinks() → array (सिर्फ़ ऐप में; वेब पर '' / [])। ज़रूरत: एक passkey सर्वर; ऐप में Google Play services वाला Android 9+ और आपके डोमेन का भी assetlinks.json.

import { passkeys } from '@/lib/appmintNative';

export async function signInWithPasskey(): Promise<boolean> {
  if (passkeys.available() === 'none') return false;          // hide the passkey button
  const options = await (await fetch('https://your-domain.com/passkey/login-options')).json();
  try {
    const answer = await passkeys.get(options);
    const res = await fetch('https://your-domain.com/passkey/login-verify', {
      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(answer),
    });
    return res.ok;
  } catch (e) {
    if ((e as DOMException).name === 'NotAllowedError') return false;   // cancelled / none here
    throw e;
  }
}

export async function addPasskey() {
  const options = await (await fetch('https://your-domain.com/passkey/register-options')).json();
  const answer = await passkeys.create(options);   // InvalidStateError = already registered
  await fetch('https://your-domain.com/passkey/register-verify', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(answer),
  });
}

एक बार की सेटअप वैल्यू, Google Play से इंस्टॉल किए गए ऐप से:

import { passkeys } from '@/lib/appmintNative';

console.log(JSON.stringify(passkeys.assetLinks(), null, 2));  // → /.well-known/assetlinks.json
console.log(passkeys.origin());                               // → add to expected origins

passkey बटन सिर्फ़ वहाँ दें जहाँ वह काम कर सके, और न कर सके तो बताएँ:

import { passkeys } from '@/lib/appmintNative';

export function PasskeyButton({ onClick }: { onClick: () => void }) {
  const kind = passkeys.available();          // 'native' | 'web' | 'none'
  if (kind === 'none') return <p>Passkeys are not available on this device.</p>;
  return <button onClick={onClick}>{kind === 'native' ? 'Use your phone passkey' : 'Use a passkey'}</button>;
}

नोट: वेब पर इसके लिए सुरक्षित (https://) पेज चाहिए। ऐप में बंडल किए गए पेज का कोई डोमेन नहीं होता: आपके सर्वर के विकल्पों में rp.id / rpId आपके डोमेन पर सेट होने चाहिए। पासकी ऑटोफ़िल (mediation: 'conditional') ऐप में उपलब्ध नहीं है - बटन से शुरू करें।

appmintNative.paste appmintNative#

paste(): Promise<string>

उदाहरण

सिस्टम क्लिपबोर्ड का टेक्स्ट पढ़ता है।

लौटाता है: Promise<string> - क्लिपबोर्ड का टेक्स्ट, या क्लिपबोर्ड खाली होने पर ''। जब यह ब्राउज़र क्लिपबोर्ड बिल्कुल न पढ़ सके, या यूज़र अनुमति न दे, तो यह रिजेक्ट होता है। ज़रूरत: कुछ नहीं।

import { can, paste, toast } from '@/lib/appmintNative';

export function PasteCode({ onCode }: { onCode: (c: string) => void }) {
  if (can('clipboardRead') === 'none') return null;   // e.g. Firefox and Safari
  return (
    <button
      onClick={async () => {
        try {
          const text = (await paste()).trim();
          if (!text) { toast('The clipboard is empty'); return; }
          onCode(text);
        } catch {
          toast('Clipboard access was blocked');       // denied permission, not "empty"
        }
      }}
    >
      Paste code
    </button>
  );
}

Capacitor-शैली का रूप:

import { Clipboard } from '@/lib/appmintNative';

const { value } = await Clipboard.read();

हर नतीजे के संदेश के साथ टेक्स्ट बॉक्स में पेस्ट करें, और इसके साथ जोड़ें copy:

import { copy, paste, toast } from '@/lib/appmintNative';

export async function pasteInto(setValue: (s: string) => void) {
  try {
    const text = await paste();
    if (text) setValue(text);
    else toast('Nothing to paste');
  } catch {
    toast('Allow clipboard access to paste');
  }
}

export async function shoutClipboard() {
  const text = await paste().catch(() => '');
  if (text && await copy(text.toUpperCase())) toast('Converted and copied');
}

नोट: ऐप में Clipboard प्लगइन टेक्स्ट पढ़ता है; खाली क्लिपबोर्ड '' देता है, एरर नहीं। ब्राउज़र में यह navigator.clipboard.readText() है (सिर्फ़ Chromium), जो उपयोगकर्ता से अनुमति माँगता है - इसे टैप से कॉल करें।

appmintNative.pickContact appmintNative#

pickContact(): Promise<Contact | null>

उदाहरण

सिस्टम कॉन्टैक्ट पिकर खोलता है ताकि यूज़र एक व्यक्ति चुने: ऐप में Android का पिकर, Android पर Chrome में Contact Picker API।

लौटाता है: Promise<Contact | null> - यूज़र के रद्द करने या एक्सेस मना होने पर null। ज़रूरत: कुछ नहीं (सिस्टम पिकर को किसी अनुमति की ज़रूरत नहीं)।

कोई व्यक्ति चुनें और फ़ॉर्म भरें। संपर्क का ढाँचा ऐप और वेब पर एक जैसा है: { id?, name?, phones?: string[], emails?: string[], photoUri? }.

import { pickContact, can } from '@/lib/appmintNative';

export async function onPickClick(setName: (s: string) => void, setPhone: (s: string) => void) {
  if (can('contactPick') === 'none') return;   // hide the button instead
  try {
    const c = await pickContact();
    if (!c) return;                            // cancelled
    setName(c.name ?? '');
    setPhone(c.phones?.[0] ?? '');
  } catch {
    // only a 5-minute timeout rejects
  }
}

“कॉन्टैक्ट चुनें” बटन जो चुनाव दिखाता है, और जहाँ पिकर नहीं है वहाँ सादा नंबर बॉक्स:

import { useState } from 'react';
import { can, pickContact, type Contact } from '@/lib/appmintNative';

export function InvitePicker() {
  const [who, setWho] = useState<Contact | null>(null);
  if (can('contactPick') === 'none') return <input placeholder="Phone number" />;   // type it by hand
  return (
    <div>
      <button onClick={async () => setWho((await pickContact()) ?? who)}>Choose a contact</button>
      {who && <p>{who.name} {who.phones?.[0]}</p>}
    </div>
  );
}

चुना हुआ नंबर टेक्स्टिंग ऐप को दें, और कॉन्टैक्ट के पास नंबर न हो तो संदेश के साथ:

import { pickContact, toast } from '@/lib/appmintNative';

export async function textAFriend(message: string) {
  const c = await pickContact();
  if (!c) return;                              // cancelled
  const phone = c.phones?.[0];
  if (!phone) { toast(`${c.name ?? 'That contact'} has no phone number`); return; }
  window.location.href = `sms:${encodeURIComponent(phone)}?body=${encodeURIComponent(message)}`;
}

नोट: ऐप में सिर्फ़ टैप किया गया नंबर वापस आता है, ईमेल कभी नहीं, और photoUri संपर्क की फ़ोटो का content:// पता है। जिन ब्राउज़र में Contact Picker API नहीं है (डेस्कटॉप, Safari) वहाँ can('contactPick') का मान 'none' है - वहाँ बटन न बनाएँ।

appmintNative.pickFile appmintNative#

pickFile(accept?) from '@/lib/appmintNative'

उदाहरण

यूज़र को एक फ़ाइल चुनने देता है और उसे टेक्स्ट के रूप में पढ़ता है। ऐप में यह Android का डॉक्यूमेंट पिकर खोलता है; वेब पर सामान्य फ़ाइल इनपुट।

लौटाता है: Promise<PickedFile | null> - { name, mime, size, text(): Promise<string> }, या सिर्फ़ यूज़र के रद्द करने पर null। यह Error के साथ रिजेक्ट होता है जिसका code है 'no-picker' (फ़ोन में डॉक्यूमेंट पिकर ऐप नहीं है), 'disabled' या 'failed' जब कोई पिकर खुल न सके। text() पूरी फ़ाइल पढ़ता है और न पढ़ पाने पर ब्रिज के कोड (जैसे 'not-found') के साथ रिजेक्ट होता है। ज़रूरत: कुछ नहीं। चरण 4 (Access) में Native Folder Access (SAF) चुनने पर ऐप Android का डॉक्यूमेंट पिकर इस्तेमाल करता है; बिना उसके ऐप पेज का अपना फ़ाइल चुनने वाला इस्तेमाल करता है।

import { pickFile } from '@/lib/appmintNative';

async function importCsv() {
  const f = await pickFile('text/csv');          // default '*/*' = any file
  if (!f) return;                                // cancelled (or picker not enabled in the build)
  if (f.size > 2_000_000) { alert('Please choose a file under 2 MB.'); return; }
  try {
    setRows(parseCsv(await f.text()));
  } catch (e) {
    alert(`Could not read ${f.name}: ${String(e)}`);
  }
}

JSON बैकअप इम्पोर्ट करें और pickFile के फ़ेल होने के हर तरीके को संभालें:

import { pickFile, toast } from '@/lib/appmintNative';

export async function restoreBackup(apply: (data: unknown) => void) {
  try {
    const f = await pickFile('application/json');
    if (!f) return;                                    // cancelled
    apply(JSON.parse(await f.text()));
    toast(`Restored ${f.name}`);
  } catch (e) {
    const code = (e as { code?: string }).code;
    if (code === 'no-picker') toast('This phone has no file picker app');
    else if (e instanceof SyntaxError) toast('That file is not a valid backup');
    else toast('Could not open the file');
  }
}

एक टेक्स्ट फ़ाइल चुनें और उसे एडिटर में डालें, फ़ाइल के ब्योरे के बगल में:

import { useState } from 'react';
import { pickFile } from '@/lib/appmintNative';

export function OpenText() {
  const [doc, setDoc] = useState<{ name: string; kb: number; text: string } | null>(null);
  return (
    <div>
      <button onClick={async () => {
        const f = await pickFile('text/*');
        if (f) setDoc({ name: f.name, kb: Math.ceil(f.size / 1024), text: await f.text() });
      }}>Open a text file</button>
      {doc && <><h3>{doc.name} ({doc.kb} KB)</h3><textarea defaultValue={doc.text} /></>}
    </div>
  );
}

ध्यान दें: ऐप में text() फ़ाइल को अंत तक 4 MB के टुकड़ों में पढ़ता है और UTF-8 के रूप में डिकोड करता है, इसलिए बड़ी फ़ाइलें पूरी पढ़ी जाती हैं - स्क्रीन जितना संभाल सके उसके लिए अपनी साइज़ सीमा रखें। जो पिकर खुल न सके वह रिजेक्ट होता है, इसलिए pickFile() को भी try/catch में लपेटें। वेब पर text() ब्राउज़र का अपना File.text() है। बाइनरी फ़ाइलों या वापस लिखने के लिए, हाथ से लिखे पेज इस्तेमाल करते हैं WebToApkFS.

appmintNative.pip appmintNative#

pip.enter(video?) / pip.exit(video?)

उदाहरण

उपयोगकर्ता के कुछ और करते समय वीडियो को छोटी तैरती विंडो में चलता रखता है।

लौटाता है: enter() Promise<boolean> लौटाता है - फ़्लोटिंग विंडो खुलने पर true। exit() Promise<void> लौटाता है। कोई भी reject नहीं होता। ज़रूरत: Android ऐप में चरण 3 (Integrate) का बिल्ड विकल्प Picture-in-Picture (PiP) - जब आपका कोड कॉल करता है तो यह अपने आप चालू हो जाता है pip.enter(.

प्लेयर पर “छोटा करें” बटन:

import { useRef } from 'react';
import { can, pip, toast } from '@/lib/appmintNative';

export function Player({ src }: { src: string }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  return (
    <div>
      <video ref={videoRef} src={src} controls playsInline />
      {can('pip') !== 'none' && (
        <button
          onClick={async () => {
            const ok = await pip.enter(videoRef.current ?? undefined);
            if (!ok) toast('Picture-in-picture is not available here');
          }}
        >
          Minimise
        </button>
      )}
    </div>
  );
}

प्लेयर बंद होने पर इसे छोड़ दें (वेब):

import { pip } from '@/lib/appmintNative';

declare const video: HTMLVideoElement;
await pip.exit(video);

जब उपयोगकर्ता चलता हुआ वीडियो छोड़े तो अपने आप पिक्चर-इन-पिक्चर में जाएँ, सिर्फ़ वहाँ जहाँ यह समर्थित है:

import { useEffect, type RefObject } from 'react';
import { can, pip } from '@/lib/appmintNative';

export function useAutoPip(videoRef: RefObject<HTMLVideoElement>) {
  useEffect(() => {
    if (can('pip') === 'none') return;
    const onHide = () => {
      const v = videoRef.current;
      if (document.hidden && v && !v.paused) void pip.enter(v);
    };
    document.addEventListener('visibilitychange', onHide);
    return () => document.removeEventListener('visibilitychange', onHide);
  }, [videoRef]);
}

ध्यान दें:

  • Android (8+): पूरा ऐप तैरती विंडो में सिमट जाता है, और video आर्गुमेंट अनदेखा किया जाता है। पुराने Android पर या PiP विकल्प के बिना बने बिल्ड में यह false लौटाता है। यूज़र विंडो को बड़ा करके वापस आता है; वहाँ exit() कुछ नहीं करता।
  • वेब पर: <video> एलिमेंट का अपना पिक्चर-इन-पिक्चर। वीडियो लोड होना चाहिए (आम तौर पर चल रहा हो); video के बिना कॉल करने पर यह लौटाता है false.
  • इसे टैप से कॉल करें।

appmintNative.platform appmintNative#

platform(): 'android' | 'ios' | 'web'

उदाहरण

बताता है कि कोड किस सतह पर चल रहा है: इंस्टॉल किया Android ऐप, या वेब ब्राउज़र।

लौटाता है: एक स्ट्रिंग, तुरंत: ऐप के अंदर 'android', ब्राउज़र में 'web'। यह पहले Capacitor से पूछता है (Capacitor.getPlatform()), फिर Android शेल ब्रिज देखता है, वरना 'web'। टाइप में 'ios' भी है, पर AppMint ऐप को यह कभी नहीं मिलता - AppMint सिर्फ़ Android ऐप बनाता है। ज़रूरत: कुछ नहीं।

सही लिंक चुनें और कॉपी करें।

import { platform } from '@/lib/appmintNative';

const inApp = platform() === 'android';

const rateLink = inApp
  ? 'https://play.google.com/store/apps/details?id=com.example.app'
  : '/feedback';

const backHint = inApp ? 'Press Back to go back' : 'Use the browser\'s back button';

सिर्फ़ ऐप वाले कंट्रोल दिखाएँ।

import { platform } from '@/lib/appmintNative';

export function InstallBanner() {
  // In the installed app there is nothing to install.
  if (platform() === 'android') return null;
  return <a href="https://play.google.com/store/apps/details?id=com.example.app">Get the Android app</a>;
}

एनालिटिक्स इवेंट पर सतह का टैग लगाएँ।

import { platform } from '@/lib/appmintNative';

export function track(event: string, data: Record<string, unknown> = {}) {
  return fetch('/api/track', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ event, surface: platform(), ...data }),
  });
}

नोट: इंस्टॉल किए गए ऐप के अंदर platform() कभी 'web' जवाब नहीं देता। इससे यह तय न करें कि कोई फ़ीचर मौजूद है या नहीं - कई फ़ीचर बिल्ड स्विच पर भी निर्भर होते हैं; यह can('<capability>') बताता है।

appmintNative.privacyScreen appmintNative#

privacyScreen — enable() / disable() / isEnabled()

उदाहरण

जब कोई गोपनीय स्क्रीन खुली हो तब स्क्रीनशॉट, स्क्रीन रिकॉर्डिंग और ऐप-स्विचर प्रीव्यू को रोकता है।

लौटाता है: enable() → Promise<boolean> (जहाँ कुछ उसे रोक नहीं सकता वहाँ false); disable() → Promise<void>; isEnabled() → Promise<boolean>। ज़रूरत: कुछ नहीं। Android ऐप में यह शेल का स्क्रीनशॉट ब्लॉक इस्तेमाल करता है (स्क्रीनशॉट, स्क्रीन रिकॉर्डिंग, कास्टिंग और रीसेंट्स थंबनेल)।

import { useEffect } from 'react';
import { privacyScreen } from '@/lib/appmintNative';

/** Use in the component that shows the secret (a card number, a statement). */
export function usePrivacyScreen() {
  useEffect(() => {
    void privacyScreen.enable();
    return () => { void privacyScreen.disable(); };   // e.g. a modal closed without a route change
  }, []);
}

यूज़र को बताएँ कि क्या सुरक्षित है - सिर्फ़ तब जब वह सच में सुरक्षित हो:

import { privacyScreen, can } from '@/lib/appmintNative';

export async function protectionLabel(): Promise<string> {
  if (can('privacyScreen') === 'none') return '';           // web: nothing can stop a screenshot
  return (await privacyScreen.isEnabled()) ? 'Screenshots are blocked on this screen' : '';
}

एक संवेदनशील व्यू के लिए सुरक्षा चालू करें, और लागू न हो पाए तो यूज़र को बताएँ:

import { privacyScreen, toast } from '@/lib/appmintNative';

export async function openStatement(show: () => void) {
  const protectedNow = await privacyScreen.enable();   // false where nothing can block capture
  if (!protectedNow) toast('Screenshots cannot be blocked here');
  show();
}

export async function closeStatement(hide: () => void) {
  hide();
  await privacyScreen.disable();
}

ध्यान दें: ऐप में यह ब्लॉक मौजूदा पेज का होता है और ऐप दूसरे रूट पर जाते ही रिलीज़ हो जाता है, इसलिए वह स्क्रीन फिर दिखे तो इसे दोबारा चालू करें। वेब पर can('privacyScreen') का मान 'none' है और enable() का नतीजा false है - वहाँ सुरक्षा का दावा न करें। स्क्रीन की ओर तनी कैमरे को कुछ नहीं रोक सकता।

appmintNative.pushInbox appmintNative#

import { pushInbox } from '@/lib/appmintNative'

उदाहरण

इस इंस्टॉल को मिले AppMint Push संदेश लौटाता है (सबसे नए पहले, 50 तक), ऐप के अंदर की नोटिफ़िकेशन स्क्रीन के लिए।

लौटाता है: तुरंत PushMessage[]: { id, title, body, imageUrl, link, receivedAt, read }। वेब पर और AppMint Push बंद होने पर खाली ऐरे। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { useEffect, useState } from 'react';
import { pushInbox, markPushRead, type PushMessage } from '@/lib/appmintNative';

export function useInbox() {
  const [items, setItems] = useState<PushMessage[]>(() => pushInbox());
  useEffect(() => {
    const refresh = () => setItems(pushInbox());
    // detail: { type: 'received', message } or { type: 'revoked', id }
    window.addEventListener('appmint:push', refresh);
    return () => window.removeEventListener('appmint:push', refresh);
  }, []);
  const unread = items.filter((m) => !m.read).length;
  const open = (m: PushMessage) => {
    markPushRead(m.id);
    setItems(pushInbox());
  };
  return { items, unread, open };
}

अनपढ़े बैज की गिनती दिखाएँ जो नए संदेश आने के साथ चलती है:

import { useEffect, useState } from 'react';
import { pushInbox } from '@/lib/appmintNative';

export function UnreadBadge() {
  const [n, setN] = useState(() => pushInbox().filter((m) => !m.read).length);
  useEffect(() => {
    const recount = () => setN(pushInbox().filter((m) => !m.read).length);
    window.addEventListener('appmint:push', recount);
    return () => window.removeEventListener('appmint:push', recount);
  }, []);
  return n > 0 ? <span aria-label={`${n} unread`}>{n}</span> : null;
}

सूची दिखाएँ, वेब और AppMint Push के बिना बिल्ड के लिए खाली स्थिति के साथ, और "सब पढ़ा हुआ मानें" बटन के साथ:

import { useState } from 'react';
import { pushInbox, markPushRead } from '@/lib/appmintNative';

export function NotificationsScreen() {
  const [items, setItems] = useState(() => pushInbox());
  if (items.length === 0) return <p>No notifications yet.</p>;
  return (
    <section>
      <button onClick={() => { markPushRead(); setItems(pushInbox()); }}>Mark all read</button>
      <ul>
        {items.map((m) => (
          <li key={m.id} style={{ fontWeight: m.read ? 400 : 700 }}>
            {m.title} <small>{new Date(m.receivedAt).toLocaleString()}</small>
            <p>{m.body}</p>
          </li>
        ))}
      </ul>
    </section>
  );
}

ध्यान दें: इनबॉक्स ही सेव किया गया सच है - माउंट पर उसे पढ़ें; appmint:push इवेंट सिर्फ़ तब आता है जब ऐप स्क्रीन पर हो। खारिज किए गए संदेश शामिल नहीं होते।

appmintNative.readBarcodeFrom appmintNative#

readBarcodeFrom(source: Blob | CanvasImageSource, formats?: BarcodeFormat[]): Promise<{ value, format }[]>

उदाहरण

पेज के पास पहले से मौजूद तस्वीर से बारकोड और QR कोड पढ़ता है - फ़ोटो फ़ाइल, Blob, <img>, <canvas> या वीडियो फ़्रेम। कैमरा नहीं खुलता।

लौटाता है: Promise<{ value: string; format: string }[]> - मिला हुआ हर कोड, डिटेक्टर के क्रम में; जब कोई न हो, तस्वीर पढ़ी न जा सके, या बारकोड पढ़ना उपलब्ध न हो तो खाली ऐरे। यह कभी रिजेक्ट नहीं होता। ज़रूरत: कुछ नहीं।

यूज़र की चुनी फ़ोटो से कोड पढ़ें:

import { can, readBarcodeFrom, toast } from '@/lib/appmintNative';

export function ReadFromPhoto({ onCode }: { onCode: (v: string) => void }) {
  if (can('barcodeScan') === 'none') return null;
  return (
    <label>
      Read a code from a photo
      <input
        type="file"
        accept="image/*"
        onChange={async (e) => {
          const file = e.currentTarget.files?.[0];
          if (!file) return;
          const codes = await readBarcodeFrom(file, ['qr_code']);
          if (codes.length) onCode(codes[0].value);
          else toast('No QR code found in that photo');
        }}
      />
    </label>
  );
}

पेज पर पहले से मौजूद इमेज से:

import { readBarcodeFrom } from '@/lib/appmintNative';

const img = document.querySelector('img#ticket') as HTMLImageElement;
const [first] = await readBarcodeFrom(img);
if (first) console.log(first.format, first.value);

कैनवास फ़्रेम से कोड पढ़ें, पहले यह जाँचकर कि डिवाइस क्या सपोर्ट करता है:

import { barcodeFormats, readBarcodeFrom } from '@/lib/appmintNative';

export async function scanCanvas(canvas: HTMLCanvasElement): Promise<string | null> {
  const supported = await barcodeFormats();
  if (supported.length === 0) return null;                     // no reader on this device
  const wanted = ['qr_code', 'ean_13'].filter((f) => supported.includes(f)) as ('qr_code' | 'ean_13')[];
  const [hit] = await readBarcodeFrom(canvas, wanted);
  return hit?.value ?? null;
}

ध्यान दें:

  • ब्राउज़र इंजन का BarcodeDetector इस्तेमाल करता है (Android ऐप के WebView में मौजूद; Safari और कई डेस्कटॉप ब्राउज़र में नहीं - तब यह लौटाता है []).
  • सादा <input type="file" accept="image/*"> Android फ़ोटो पिकर खोलता है और उसे स्टोरेज अनुमति नहीं चाहिए।
  • जब आपको formats पता हों तो उन्हें सीमित करें: इससे यह तेज़ चलता है और ग़लत पढ़ता कम है। barcodeFormats() बताता है कि डिवाइस क्या सपोर्ट करता है।

appmintNative.readOtp appmintNative#

import { readOtp } from '@/lib/appmintNative'

उदाहरण

इस ऐप के लिए SMS वन-टाइम कोड का इंतज़ार करता है और उसे लौटाता है - Android ऐप में Google की SMS सहमति शीट, वेब पर Chrome में WebOTP।

लौटाता है: Promise<string | null> - कोड, या यूज़र के मना करने, पाँच मिनट बीतने, या signal के रोकने पर null। can('otp') का मान 'none' होने पर throw करता है। ज़रूरत: चालू करने को कुछ नहीं। SMS का अंत @<host> #<code> से होना चाहिए (Android ऐप में होस्ट है appassets.androidplatform.net).

import { useEffect, useState } from 'react';
import { can, readOtp } from '@/lib/appmintNative';

export function CodeField({ onDone }: { onDone: (code: string) => void }) {
  const [code, setCode] = useState('');

  useEffect(() => {
    if (can('otp') === 'none') return;
    const ac = new AbortController();
    readOtp({ signal: ac.signal }).then((c) => { if (c) { setCode(c); onDone(c); } });
    return () => ac.abort();          // leaving the screen stops the wait
  }, [onDone]);

  return (
    <input value={code} onChange={(e) => setCode(e.target.value)}
      inputMode="numeric" autoComplete="one-time-code" aria-label="Verification code" />
  );
}

"कोड नहीं" वाले जवाब को संभालें, और जब इंतज़ार बिना कोड के खत्म हो तो दोबारा भेजने का विकल्प दें:

import { useState } from 'react';
import { can, readOtp } from '@/lib/appmintNative';

export function VerifyStep({ resend, submit }: { resend: () => void; submit: (c: string) => void }) {
  const [waited, setWaited] = useState(false);

  async function start() {
    resend();
    if (can('otp') === 'none') return;          // readOtp would throw here — the user types it
    const code = await readOtp();               // null: declined, 5 minutes passed, or aborted
    if (code) submit(code); else setWaited(true);
  }

  return (
    <div>
      <button onClick={start}>Send code</button>
      {waited && <p>No code detected. <button onClick={start}>Send again</button></p>}
    </div>
  );
}

जब आप पहले can('otp') न जाँच सकें तब throw को पकड़ें:

import { readOtp } from '@/lib/appmintNative';

export async function tryAutoFill(): Promise<string | null> {
  try {
    return await readOtp({ signal: AbortSignal.timeout(60_000) });
  } catch {
    return null;                                // one-time codes unavailable: the user types it
  }
}

ध्यान दें: यह एक एक्सेलेरेटर है - फ़ील्ड को हमेशा टाइप करने लायक रखें।

appmintNative.recognizeText appmintNative#

import { recognizeText } from '@/lib/appmintNative'

उदाहरण

डिवाइस पर इमेज में मौजूद टेक्स्ट पढ़ता है (can('ocr')): Android ऐप का ML Kit रिकग्नाइज़र, या जहाँ चालू हो वहाँ Chrome का TextDetector।

लौटाता है: { text, blocks: [{ text, box, lines: [{ text, box }] }] } का Promise, बॉक्स इमेज पिक्सल में। code के साथ रिजेक्ट होता है 'unavailable', जहाँ can('ocr') है 'none', या पहचान विफल होने पर DOMException। ज़रूरत: कुछ नहीं, कोई अनुमति नहीं।

import { useState } from 'react';
import { can, recognizeText } from '@/lib/appmintNative';

export function ReceiptReader() {
  const [total, setTotal] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  if (can('ocr') === 'none') return null;
  return (
    <label>
      Scan a receipt
      <input type="file" accept="image/*" capture="environment" onChange={async (e) => {
        const file = e.target.files?.[0];
        if (!file) return;
        setBusy(true);
        try {
          const { text } = await recognizeText(file);
          setTotal(/total\s*[:$€£]?\s*([\d.,]+)/i.exec(text)?.[1] ?? 'not found');
        } finally { setBusy(false); }
      }} />
      {busy ? <span role="status">Reading…</span> : total && <strong>Total: {total}</strong>}
    </label>
  );
}

“कोई रिकग्नाइज़र नहीं” और विफलता वाले मामले साफ़ तौर पर संभालें:

import { can, recognizeText } from '@/lib/appmintNative';

export async function readLabel(file: Blob): Promise<string> {
  if (can('ocr') === 'none') return '';
  try {
    const { text } = await recognizeText(file);
    return text.trim();
  } catch (e) {
    if ((e as { code?: string }).code === 'unavailable') return '';
    throw e;                                    // a real recognition failure
  }
}

लैटिन के अलावा किसी लिपि को पढ़ें, और हर पंक्ति को उसकी जगह के साथ सूचीबद्ध करें:

import { recognizeText } from '@/lib/appmintNative';

export async function readHindiSign(img: HTMLImageElement) {
  const { blocks } = await recognizeText(img, { script: 'devanagari' });
  return blocks.flatMap((b) => b.lines).map((l) => ({ text: l.text, box: l.box }));
}

ध्यान दें: सिर्फ़ लैटिन लिपियाँ। नए फ़ोन पर पहली कॉल में कुछ सेकंड लग सकते हैं क्योंकि Play services मॉडल लाता है - प्रगति दिखाएँ।

appmintNative.remoteConfig appmintNative#

remoteConfig.start() / fetch() / apply() / manifestUrl() / handledHere() / dismissed() / dismiss()

उदाहरण

वह घोषणा पट्टी और मेंटेनेंस स्क्रीन बनाता है जो क्रिएटर Remote Update से प्रकाशित करता है - उस पेज के लिए जो वेब ब्राउज़र में चलता है। Android ऐप में शेल यह खुद कर लेता है, इसलिए वहाँ ये कॉल कुछ नहीं करतीं।

लौटाता है: start(manifestUrl?) → Promise<void> (fetch + draw; कभी रिजेक्ट नहीं होता); fetch(url) → Promise<RemoteConfig | null> (कभी रिजेक्ट नहीं होता; null = कुछ लागू नहीं); apply(cfg) → एक teardown फ़ंक्शन; manifestUrl() → string (<meta name="appmint-remote-manifest"> का content, कोई न हो तो ''); handledHere() → boolean; dismissed(id) → boolean; dismiss(id) → void। ज़रूरत: ऐप में कुछ नहीं - वहाँ Remote Update नेटिव है।

स्टार्टअप पर बुलाना सुरक्षित है; ऐप में यह तुरंत लौट आता है:

import { useEffect } from 'react';
import { remoteConfig } from '@/lib/appmintNative';

// Your Remote Update manifest URL. AppMint does not write the meta tag into the page,
// so pass the URL when the same page also runs on the web.
const MANIFEST = 'https://example.com/remote-update/manifest.json';

export function App() {
  useEffect(() => { void remoteConfig.start(MANIFEST); }, []);   // does nothing in the Android app
  return <main>{/* … */}</main>;
}

या मान पढ़ें और अपना UI बनाएँ (सिर्फ़ वेब):

import { remoteConfig, type RemoteConfig } from '@/lib/appmintNative';

export async function loadNotice(url: string): Promise<RemoteConfig | null> {
  if (!remoteConfig.handledHere()) return null;         // the Android app draws its own
  const cfg = await remoteConfig.fetch(url);
  if (!cfg || !cfg.announcementText) return cfg;
  return remoteConfig.dismissed(cfg.announcementId) ? null : cfg;
}
// when the user closes your banner: remoteConfig.dismiss(cfg.announcementId)

मेंटेनेंस का टेक्स्ट वेब पर खुद दिखाएँ, और ऐप को उसकी अपनी स्क्रीन पर छोड़ दें:

import { useEffect, useState } from 'react';
import { remoteConfig } from '@/lib/appmintNative';

export function useMaintenance(url: string): string | null {
  const [text, setText] = useState<string | null>(null);
  useEffect(() => {
    if (!remoteConfig.handledHere()) return;               // the Android app shows its own screen
    void remoteConfig.fetch(url).then((cfg) => {
      setText(cfg?.maintenanceMode ? cfg.maintenanceText : null);
    });
  }, [url]);
  return text;
}

ध्यान दें:

  • RemoteConfig = { version, announcementText, announcementId, announcementImageUrl, announcementBgColor, announcementTextColor, maintenanceMode, maintenanceText }.
  • handledHere() Android ऐप में false होता है: शेल मैनिफ़ेस्ट को नेटिव तरीके से पढ़ता है और घोषणा खुद दिखाता है, इसलिए आपको दो बैनर कभी नहीं दिखते।
  • start() आर्गुमेंट के बिना manifestUrl() इस्तेमाल होता है। AppMint बिल्ड वह meta टैग नहीं जोड़ता, इसलिए आर्गुमेंट के बिना (या आपके अपने <meta name="appmint-remote-manifest" content="…"> के बिना) कुछ भी नहीं लाया जाता।
  • fetch() रोके गए या वापस लिए गए प्रकाशन, ऐसे मैनिफ़ेस्ट जिसकी platforms सूची में web नहीं है, नए मैनिफ़ेस्ट फ़ॉर्मैट, या किसी भी नेटवर्क त्रुटि पर null लौटाता है।
  • मेंटेनेंस मोड जीतता है: तब apply() पूरे पेज को मेंटेनेंस टेक्स्ट से ढक देता है। खारिज की गई घोषणा तब तक छिपी रहती है जब तक नया announcementId प्रकाशित न हो।

appmintNative.requestDndAccess appmintNative#

requestDndAccess() — @/lib/appmintNative

उदाहरण

Do Not Disturb की अनुमति माँगता है, जो Android में तब ज़रूरी है जब Do Not Disturb चालू हो और ऐप को रिंग या नोटिफ़िकेशन की आवाज़ बदलनी हो।

लौटाता है: 'granted', 'opened' (सेटिंग्स स्क्रीन दिखाई गई - यूज़र के लौटने पर फिर जाँचें), 'not_declared', या वेब पर 'unavailable'। ज़रूरत: टिक करने को कुछ नहीं - अपने कोड में इसे कॉल करने से बिल्ड अनुमति घोषित कर देता है।

import { requestDndAccess, setVolume } from '@/lib/appmintNative';

export function silenceRinger(): string {
  const r = setVolume('ring', 0);
  if (r.ok) return 'done';
  if (r.error === 'dnd_access_required') return requestDndAccess();   // 'opened' → ask the user to allow it
  return r.error ?? 'failed';
}

एक बार पूछें, फिर यूज़र के सेटिंग्स से लौटने के बाद वॉल्यूम बदलाव दोबारा आज़माएँ:

import { useEffect, useRef } from 'react';
import { requestDndAccess, setVolume } from '@/lib/appmintNative';

export function useRingerMute() {
  const pending = useRef(false);
  useEffect(() => {
    const onShow = () => {
      if (pending.current && document.visibilityState === 'visible') {
        pending.current = false;
        setVolume('ring', 0);                       // allowed now, or the error comes back again
      }
    };
    document.addEventListener('visibilitychange', onShow);
    return () => document.removeEventListener('visibilitychange', onShow);
  }, []);

  return () => {
    if (requestDndAccess() === 'opened') pending.current = true;
  };
}

हर संभव जवाब पर शाखा बनाएँ:

import { requestDndAccess, toast } from '@/lib/appmintNative';

export function explainDnd() {
  switch (requestDndAccess()) {
    case 'granted': toast('Do Not Disturb access is already on'); break;
    case 'opened': toast('Allow this app, then come back'); break;
    case 'not_declared': toast('This build was made without that permission'); break;
    default: toast('Only available in the Android app');   // 'unavailable' on the web
  }
}

appmintNative.requestReview appmintNative#

requestReview(): Promise<boolean>

उदाहरण

Google Play से ऐप के अंदर उसकी अपनी रेटिंग शीट दिखाने को कहता है (Play In-App Review)।

लौटाता है: Promise<boolean> - true सिर्फ़ तब जब Play ने शीट दिखाई; वेब पर, Google Play से इंस्टॉल न होने पर, Play के मना करने (उसकी सीमा) या विफल होने पर false। कारण जानने के लिए askForReview() इस्तेमाल करें। ज़रूरत: कुछ नहीं; शीट सिर्फ़ Google Play से इंस्टॉल किए ऐप में दिखती है।

import { requestReview } from '@/lib/appmintNative';

async function onOrderDelivered(count: number) {
  // After a success moment, and rarely — not on launch, not after an error.
  if (count === 3 && !localStorage.getItem('askedReview')) {
    localStorage.setItem('askedReview', '1');
    await requestReview();
  }
}

यूज़र को कहीं और भेजे बिना जानें कि शीट क्यों नहीं दिखी:

import { askForReview } from '@/lib/appmintNative';

export async function maybeAsk() {
  const { result } = await askForReview();
  // 'shown' | 'not_from_play' | 'quota' | 'error' | 'unavailable'
  if (result !== 'shown') console.info('review sheet not shown:', result);
}

सिर्फ़ किसी सफलता के पल के बाद पूछें, और महीने में एक बार से ज़्यादा नहीं:

import { requestReview } from '@/lib/appmintNative';

const KEY = 'lastReviewAsk';

export async function afterSuccess() {
  const last = Number(localStorage.getItem(KEY) ?? 0);
  if (Date.now() - last < 30 * 24 * 3600 * 1000) return;
  localStorage.setItem(KEY, String(Date.now()));
  await requestReview();
}

ध्यान दें: Google Play शीट पर सीमा लगाता है और अक्सर कुछ नहीं दिखाता - रिव्यू के बदले कभी इनाम न दें, और ऐसा “हमें रेट करें” बटन कभी न बनाएँ जो शीट खुलने की उम्मीद रखे (बटन के लिए askForReview({ openStore: true }) इस्तेमाल करें)। यह कभी ऐप से बाहर Play लिस्टिंग पर नहीं जाता।

appmintNative.restorePushMessage appmintNative#

import { restorePushMessage } from '@/lib/appmintNative'

उदाहरण

dismissPushMessage(id) को पलटता है - संदेश AppMint Push इनबॉक्स में अपनी पुरानी जगह पर लौट आता है।

लौटाता है: तुरंत boolean - बहाल हो गया हो तो true; id अज्ञात हो या हटाया न गया हो तो false (वेब पर हमेशा false)। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { dismissPushMessage, restorePushMessage, pushInbox } from '@/lib/appmintNative';

let lastDismissed: string | null = null;

export function dismiss(id: string) {
  if (dismissPushMessage(id)) lastDismissed = id;
  return pushInbox();
}

export function undo() {
  if (lastDismissed) restorePushMessage(lastDismissed);
  lastDismissed = null;
  return pushInbox();
}

स्वाइप करके हटाने के तुरंत बाद एक छोटी React पंक्ति में Undo का विकल्प दें:

import { useState } from 'react';
import { dismissPushMessage, restorePushMessage } from '@/lib/appmintNative';

export function SwipeRow({ id, onChange }: { id: string; onChange: () => void }) {
  const [undoable, setUndoable] = useState(false);
  return undoable ? (
    <button onClick={() => { if (restorePushMessage(id)) { setUndoable(false); onChange(); } }}>
      Undo
    </button>
  ) : (
    <button onClick={() => { if (dismissPushMessage(id)) { setUndoable(true); onChange(); } }}>
      Dismiss
    </button>
  );
}

जवाब जाँचें, क्योंकि अनजान या कभी न हटाई गई id देती है false:

import { restorePushMessage, pushInbox } from '@/lib/appmintNative';

export function undoOrReport(id: string): string {
  if (!restorePushMessage(id)) return 'Nothing to restore';
  return `Back in your inbox (${pushInbox().length} messages)`;
}

ध्यान दें: clearPushInbox() से हटाए गए या आपके वापस लिए गए संदेश बहाल नहीं हो सकते।

appmintNative.safeArea appmintNative#

safeArea(): { top: number; right: number; bottom: number; left: number }

उदाहरण

स्टेटस बार, कैमरा कट-आउट और नेविगेशन बार द्वारा घेरी गई जगह CSS पिक्सेल में देता है, ताकि फ़िक्स्ड बार उनके नीचे न दबें।

लौटाता है: { top, right, bottom, left } संख्याएँ, तुरंत। ज़रूरत: कुछ नहीं।

ज़्यादातर समय CSS काफ़ी है - रनटाइम --safe-top, --safe-right, --safe-bottom, --safe-left रखता है :root:

export function BottomNav() {
  return (
    <nav className="fixed inset-x-0 bottom-0" style={{ paddingBottom: 'var(--safe-bottom)' }}>
      {/* tabs */}
    </nav>
  );
}

जब कोड को गणित करनी हो तो नंबर इस्तेमाल करें, जैसे फ़्लोटिंग बटन रखने के लिए:

import { useEffect, useState } from 'react';
import { safeArea } from '@/lib/appmintNative';

export function useBottomInset(): number {
  const [bottom, setBottom] = useState(0);
  useEffect(() => {
    const read = () => setBottom(safeArea().bottom);
    read();
    window.addEventListener('resize', read);        // rotation changes the insets
    return () => window.removeEventListener('resize', read);
  }, []);
  return bottom;
}

स्क्रॉल होने वाली लिस्ट को बार से दूर रखें, उसकी पैडिंग इन संख्याओं से तय करके:

import { useEffect, useState, type ReactNode } from 'react';
import { safeArea } from '@/lib/appmintNative';

export function PaddedList({ children }: { children: ReactNode }) {
  const [inset, setInset] = useState({ top: 0, bottom: 0 });
  useEffect(() => {
    const read = () => { const s = safeArea(); setInset({ top: s.top, bottom: s.bottom }); };
    read();
    window.addEventListener('resize', read);
    return () => window.removeEventListener('resize', read);
  }, []);
  return <div style={{ paddingTop: inset.top + 8, paddingBottom: inset.bottom + 8 }}>{children}</div>;
}

ध्यान दें:

  • CSS env(safe-area-inset-*) से नापा गया। मान कैश किया जाता है और विंडो का आकार बदलने या रोटेशन के बाद फिर नापा जाता है।
  • document.body के बनने से पहले (इम्पोर्ट के बहुत शुरुआती समय में) यह सब शून्य लौटाता है - इसे इफ़ेक्ट से कॉल करें।
  • डेस्कटॉप ब्राउज़र पर चारों आम तौर पर 0.

appmintNative.saveFile appmintNative#

saveFile(fileName, data, mime?) from '@/lib/appmintNative'

उदाहरण

टेक्स्ट या Blob को फ़ाइल के रूप में सेव करता है जिसे उपयोगकर्ता रखता है, आपके चुने नाम से। ऐप में यह उसी नाम के साथ Android की "Save as…" शीट खोलता है; ब्राउज़र में यह सामान्य डाउनलोड है।

लौटाता है: (सिंक) boolean - डाउनलोड शुरू होने पर true, document न होने या उसे बनाते समय throw होने पर false। यह नहीं बताता कि यूज़र ने शीट सेव की या रद्द की। ज़रूरत: कुछ नहीं।

import { saveFile } from '@/lib/appmintNative';

function exportScores(rows: { name: string; score: number }[]) {
  const csv = ['name,score', ...rows.map((r) => `${r.name},${r.score}`)].join('\n');
  if (!saveFile('Scores.csv', csv, 'text/csv')) alert('Could not start the download.');
}

Blob अपना टाइप खुद रखता है (तीसरा आर्गुमेंट अनदेखा किया जाता है):

import { saveFile } from '@/lib/appmintNative';

const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' });
saveFile('Backup.json', blob);

fetch का नतीजा सेव करें, और शुरू न हो पाए तो यह बताएँ:

import { saveFile, toast } from '@/lib/appmintNative';

export async function downloadInvoice(id: string) {
  const res = await fetch(`/api/invoices/${id}.pdf`);
  if (!res.ok) { toast('Could not get the invoice'); return; }
  const pdf = await res.blob();                          // the Blob keeps its own type
  if (!saveFile(`Invoice-${id}.pdf`, pdf)) toast('Could not start the download');
}

ध्यान दें: अंदर से यह सादा <a download> है, जिसे ऐप “ऐसे सेव करें…” शीट में बदल देता है (देखें downloads)। नाम हमेशा एक्सटेंशन के साथ दें; एक्सटेंशन के बिना फ़ाइल download नाम से सेव होती है। स्ट्रिंग का डिफ़ॉल्ट MIME टाइप है text/plain.

appmintNative.saveToGallery appmintNative#

import { saveToGallery } from '@/lib/appmintNative'

उदाहरण

फ़ोटो या वीडियो को फ़ोन की गैलरी में सेव करता है - can('gallery') Android ऐप में 'native' है और ब्राउज़र में 'web' (वहाँ फ़ाइल डाउनलोड होती है)।

लौटाता है: सेव किए गए आइटम के URI का Promise (वेब पर '')। code के साथ रिजेक्ट होता है 'permission-denied', 'download-failed', 'bad-source' या 'failed'। ज़रूरत: कुछ नहीं; Android 10+ पर कोई अनुमति नहीं।

import { can, saveToGallery, toast } from '@/lib/appmintNative';

export function SaveButton({ canvas }: { canvas: HTMLCanvasElement }) {
  if (can('gallery') === 'none') return null;
  return (
    <button onClick={() => canvas.toBlob(async (blob) => {
      if (!blob) return;
      try {
        await saveToGallery(blob, { album: 'My Cards', fileName: 'card.png' });
        toast('Saved to gallery');
      } catch (e) {
        toast('Could not save');
      }
    }, 'image/png')}>Save to gallery</button>
  );
}

वीडियो क्लिप सेव करें और विफलता के उन कोड को संभालें जिनके साथ वह रिजेक्ट हो सकता है:

import { saveToGallery, toast } from '@/lib/appmintNative';

export async function saveClip(url: string) {
  try {
    await saveToGallery(url, { kind: 'video', album: 'My Clips', fileName: 'clip.mp4' });
    toast('Saved to your Movies folder');
  } catch (e) {
    const code = (e as { code?: string }).code;
    toast(code === 'download-failed' ? 'Check your connection and try again' : 'Could not save the clip');
  }
}

data URL से इमेज सेव करें, और उसे शेयर शीट पर सिर्फ़ तब दें जब वह सेव हुई हो:

import { can, saveToGallery, share } from '@/lib/appmintNative';

export async function saveAndShare(dataUrl: string) {
  if (can('gallery') !== 'none') await saveToGallery(dataUrl, { album: 'Receipts' });
  await share({ title: 'My receipt', text: 'Here is my receipt' });
}

नोट: source एक data: URL या http(s) URL भी हो सकता है; बिना वीडियो MIME टाइप के वीडियो URL के लिए { kind: 'video' } दें।

appmintNative.scanBarcode appmintNative#

scanBarcode(video: HTMLVideoElement, opts?: { signal?, formats?, facingMode? }): Promise<{ value, format } | null>

उदाहरण

कैमरा उस <video> में खोलता है जिसे आप अपने लेआउट में रखते हैं, और पहला दिखा बारकोड या QR कोड लौटाता है।

लौटाता है: Promise<{ value: string; format: string } | null> - पहला कोड, या रद्द होने पर (जब signal ने रोका), कैमरा अस्वीकार होने पर, या स्कैनिंग उपलब्ध न होने पर null। यह कभी रिजेक्ट नहीं होता और कैमरा हमेशा बंद करता है। ज़रूरत: कैमरा एक्सेस - Android ऐप में बिल्ड करते समय चरण 4 (Access) में कैमरा चालू करें।

इसे तभी इस्तेमाल करें जब व्यूफ़ाइंडर आपकी अपनी स्क्रीन के अंदर ही रखना हो; वरना scanCode() ज़्यादा आसान है।

import { useEffect, useRef, useState } from 'react';
import { can, scanBarcode, haptic } from '@/lib/appmintNative';

export function InlineScanner({ onCode }: { onCode: (v: string) => void }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [error, setError] = useState('');

  useEffect(() => {
    const video = videoRef.current;
    if (!video || can('barcodeScan') === 'none') return;
    const ctl = new AbortController();
    void scanBarcode(video, { signal: ctl.signal, formats: ['qr_code'] }).then((hit) => {
      if (hit) { haptic('success'); onCode(hit.value); }
      else if (!ctl.signal.aborted) setError('Camera not available. Type the code instead.');
    });
    return () => ctl.abort();          // leaving the screen stops the camera
  }, [onCode]);

  return (
    <div>
      <video ref={videoRef} className="aspect-square w-full object-cover" muted playsInline />
      {error && <p role="alert">{error}</p>}
    </div>
  );
}

फ़्रंट कैमरा इस्तेमाल करें:

import { scanBarcode } from '@/lib/appmintNative';

declare const video: HTMLVideoElement;
const hit = await scanBarcode(video, { facingMode: 'user' });

पहली पहचान के बाद स्कैनर बंद करें, और कैमरा उपलब्ध न हो तो टाइप किया कोड देने का विकल्प दें:

import { useRef, useState } from 'react';
import { can, scanBarcode } from '@/lib/appmintNative';

export function TicketCheck({ onCode }: { onCode: (v: string) => void }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const ctl = useRef<AbortController | null>(null);
  const [typed, setTyped] = useState(false);

  async function start() {
    if (!videoRef.current || can('barcodeScan') === 'none') { setTyped(true); return; }
    ctl.current = new AbortController();
    const hit = await scanBarcode(videoRef.current, { signal: ctl.current.signal, formats: ['ean_13', 'code_128'] });
    if (hit) onCode(hit.value);
    else if (!ctl.current.signal.aborted) setTyped(true);   // null and not cancelled: no camera
  }

  return (
    <div>
      <video ref={videoRef} muted playsInline />
      <button onClick={start}>Scan ticket</button>
      <button onClick={() => ctl.current?.abort()}>Cancel</button>
      {typed && <input placeholder="Type the ticket number" onBlur={(e) => onCode(e.target.value)} />}
    </div>
  );
}

ध्यान दें:

  • यह getUserMedia कॉल करता है (इसलिए यूज़र को कैमरा अनुमति प्रॉम्प्ट दिखता है) और हर फ़्रेम पर BarcodeDetector से डिकोड करता है। इनमें से कोई भी न हो तो can('barcodeScan') का मान 'none' होता है।
  • बिल्ड आपका कोड स्कैन करता है: scanBarcode( कॉल आपके लिए Camera चालू कर देती है। अगर आपने इसे हाथ से बंद किया, तो कैमरा रिक्वेस्ट विफल होती है और आपको मिलता है null.
  • इसे किसी यूज़र की कार्रवाई से शुरू करें (स्कैन बटन या स्कैन स्क्रीन खोलना), ऐप लॉन्च होते ही कभी नहीं। null को एक सामान्य नतीजा मानकर संभालें और हाथ से डालने का विकल्प दें।

appmintNative.scanCode appmintNative#

scanCode(opts?: { formats?: BarcodeFormat[]; manualInput?: boolean }): Promise<ScanCodeResult | null>

उदाहरण

फ़ुल-स्क्रीन स्कैनर खोलता है और पहले बारकोड या QR कोड के साथ पूरा होता है - एक कॉल, कुछ बनाने की ज़रूरत नहीं।

लौटाता है: Promise<ScanCodeResult | null> - { value, format, type?, displayValue?, url?, wifi?, phone?, email?, sms?, geo?, contact? }, या यूज़र के स्कैनर बंद करने पर null। बाकी सब Error के साथ रिजेक्ट होता है जिसका code 'unavailable' (यहाँ कोई स्कैनर नहीं - ऐप में Play services नहीं; वेब पर BarcodeDetector या कैमरा नहीं), 'busy' (स्कैन पहले से खुला है), 'camera-denied' (वेब: कैमरा अनुमति नामंज़ूर) या 'failed' है। ज़रूरत: ऐप में कुछ नहीं (Google के स्कैनर को कैमरा अनुमति नहीं चाहिए)।

Scan बटन से:

import { can, scanCode, haptic, toast } from '@/lib/appmintNative';

export function ScanButton({ onCode }: { onCode: (v: string) => void }) {
  if (can('codeScanner') === 'none') return null;       // nothing can scan here
  return (
    <button
      onClick={async () => {
        try {
          const hit = await scanCode({ formats: ['qr_code'] });
          if (!hit) return;                               // closed: a normal outcome
          haptic('success');
          onCode(hit.value);
        } catch (e) {
          const code = (e as { code?: string }).code;
          toast(code === 'camera-denied' ? 'Allow the camera to scan.' : 'Scanning is not available right now.');
        }
      }}
    >
      Scan QR code
    </button>
  );
}

ऐप जो पार्स किए हिस्से भरता है (Wi-Fi, लिंक, कॉन्टैक्ट्स …) उन्हें इस्तेमाल करें, और Google के स्कैनर में टाइप करके डालने का विकल्प रखें:

import { scanCode } from '@/lib/appmintNative';

const hit = await scanCode({ manualInput: true });
if (hit?.wifi) console.log('Network', hit.wifi.ssid, hit.wifi.encryption);
else if (hit?.url) window.open(hit.url.url, '_blank');
else if (hit) console.log(hit.type ?? 'text', hit.value);

अपनी अलार्म स्क्रीन के बटन से स्नूज़ करें, और अलार्म कब बज रहा है यह जानने के लिए onAlarm इस्तेमाल करें।

import { useEffect, useState } from 'react';
import { onAlarm, snoozeAlarm, stopAlarm } from '@/lib/appmintNative';

export function RingingScreen() {
  const [ringing, setRinging] = useState(false);
  useEffect(() => onAlarm((e) => setRinging(e.action === 'open' && e.ringing === true)), []);
  if (!ringing) return null;
  return (
    <div>
      <button onClick={() => { if (snoozeAlarm('', 10)) setRinging(false); }}>Snooze 10 min</button>
      <button onClick={() => { stopAlarm(); setRinging(false); }}>Stop</button>
    </div>
  );
}

snoozeAlarm कुछ न बज रहा हो तो false जवाब देता है, इसलिए यूज़र से दूसरी बार बजने का वादा करने से पहले जाँचें।

import { snoozeAlarm } from '@/lib/appmintNative';

export function snoozeOrExplain(minutes = 0) {
  return snoozeAlarm('', minutes)
    ? `Ringing again in ${minutes || 'a few'} minutes`
    : 'No alarm is ringing right now.';
}

ध्यान दें:

  • ऐप में यह Google का कोड स्कैनर है (Play services, शेल के AppMint.scanCode के ज़रिए): ऑटो-ज़ूम, कैमरा प्रॉम्प्ट नहीं। type इनमें से एक होता है: 'url', 'wifi', 'text', 'phone', 'email', 'sms', 'geo', 'contact', 'calendar', 'isbn', 'product' या 'driver_license', और मेल खाता फ़ील्ड भरा जाता है। manualInput यूज़र को कोड खुद टाइप करने देता है।
  • वेब पर यह अपनी फ़ुल-स्क्रीन कैमरा लेयर बनाता है (बंद करने का बटन, बैक जेस्चर से बंद) और BarcodeDetector से डिकोड करता है; वहाँ सिर्फ़ value और format भरे जाते हैं, और ब्राउज़र कैमरा अनुमति माँगता है।
  • null हमेशा का मतलब है कि यूज़र ने स्कैनर बंद किया; बाकी हर नतीजा code के साथ रिजेक्ट होता है, इसलिए संदेश दिखाएँ catch.
  • इसे टैप से कॉल करें, पेज लोड पर कभी नहीं। जब आप जानते हों तब formats को सीमित करें।

appmintNative.scheduleAlarm appmintNative#

scheduleAlarm(id, input) — @/lib/appmintNative

उदाहरण

AI से बने ऐप से असली अलार्म घड़ी सेट करता है: यह फ़ोन के अलार्म वॉल्यूम पर बजती है (साइलेंट मोड में भी, और अलार्म की अनुमति हो तो Do Not Disturb में भी), Dismiss / Snooze या ringSeconds तक बजती रहती है, और ऐप को लॉक स्क्रीन के ऊपर दिखाती है।

लौटाता है: boolean, तुरंत - सेट होने पर true; वेब पर, iOS पर, या जब alarmSupport() 'ready' न हो तो false। वही id पुराने अलार्म को बदल देता है। ज़रूरत: बिल्ड करते समय Access चरण (चरण 4) में 🔔 अलार्म क्लॉक।

import { alarmSupport, scheduleAlarm } from '@/lib/appmintNative';

export function setWeekdayAlarm(hour: number, minute: number): string {
  const support = alarmSupport();
  if (support !== 'ready') return support;       // show the matching fix-it step

  const first = new Date();
  first.setHours(hour, minute, 0, 0);
  if (first.getTime() <= Date.now()) first.setDate(first.getDate() + 1);

  const ok = scheduleAlarm('wake', {
    at: first.getTime(),
    title: 'Wake up',
    repeat: 'weekdays',
    days: [1, 2, 3, 4, 5],     // ISO: Monday = 1 … Sunday = 7
    snoozeMinutes: 9,
    ringSeconds: 300,
  });
  return ok ? 'set' : 'failed';
}

अलार्म तभी दें जब ऐप उसे बजा सके, वरना बताएँ कि क्या कमी है:

import { alarmSupport, scheduleAlarm } from '@/lib/appmintNative';

const HELP: Record<string, string> = {
  not_enabled: 'Alarm clock is switched off in this build.',
  needs_exact_alarm: 'Allow "Alarms & reminders" for this app in Settings.',
  needs_notification_permission: 'Allow notifications for this app in Settings.',
  unavailable: 'Alarms work only in the Android app.',
};

export function setTimer(minutes: number): string {
  const s = alarmSupport();
  if (s !== 'ready') return HELP[s] ?? 'Alarm not available';
  const ok = scheduleAlarm('timer', {
    at: Date.now() + minutes * 60_000,
    title: 'Timer done',
    snoozeMinutes: 0,          // hides the Snooze button
    ringSeconds: 60,
  });
  return ok ? `Timer set for ${minutes} min` : 'Could not set the timer';
}

बजते अलार्म के ख़त्म होने के तरीके पर प्रतिक्रिया दें, और उसे अपनी स्क्रीन से रोकें:

import { useEffect, useState } from 'react';
import { onAlarm, stopAlarm, snoozeAlarm, isAlarmRinging } from '@/lib/appmintNative';

export function useAlarmScreen() {
  const [ringing, setRinging] = useState(isAlarmRinging());
  useEffect(() => onAlarm((e) => {
    if (e.action === 'open') setRinging(!!e.ringing);
    else if (e.action === 'dismiss' || e.action === 'snooze' || e.action === 'missed') setRinging(false);
    else if (e.action === 'error') console.warn('Alarm error', e.error);
  }), []);
  return {
    ringing,
    stop: () => stopAlarm(),            // '' = whichever alarm is ringing
    snooze: () => snoozeAlarm('', 5),   // ring again in 5 minutes
  };
}

ध्यान दें: सिर्फ़ Android ऐप। repeat daily / weekly / weekdays / monthly / yearly डेलाइट-सेविंग बदलने पर भी घड़ी का वही समय रखते हैं। ब्रिज पर cancelNotification(id) से रद्द करें; नतीजे इसके ज़रिए आते हैं onAlarm.

appmintNative.ScreenOrientation appmintNative#

ScreenOrientation.lock({ orientation }) / ScreenOrientation.unlock()

उदाहरण

स्क्रीन को एक व्यू के लिए पोर्ट्रेट या लैंडस्केप पर पिन करता है, Capacitor के ScreenOrientation प्लगइन के रूप में - वही कोड जो lockOrientation().

लौटाता है: दोनों के लिए Promise<void>। ये कभी रिजेक्ट नहीं होते; जब जानना हो कि लॉक लगा या नहीं तो lockOrientation() इस्तेमाल करें। ज़रूरत: कुछ नहीं।

import { ScreenOrientation } from '@/lib/appmintNative';
import { useEffect } from 'react';

function GameBoard() {
  useEffect(() => {
    void ScreenOrientation.lock({ orientation: 'landscape' });
    return () => { void ScreenOrientation.unlock(); };   // release when leaving
  }, []);
  return <canvas id="board" />;
}

लॉक की अनुमति हो तभी एक स्क्रीन के लिए लॉक करें, और यूज़र को वापस घुमाने दें:

import { useEffect, useState } from 'react';
import { ScreenOrientation } from '@/lib/appmintNative';

export function ChartView() {
  const [locked, setLocked] = useState(true);
  useEffect(() => {
    if (locked) void ScreenOrientation.lock({ orientation: 'landscape-primary' });   // any 'landscape*' value
    else void ScreenOrientation.unlock();
    return () => { void ScreenOrientation.unlock(); };
  }, [locked]);
  return <button onClick={() => setLocked((v) => !v)}>{locked ? 'Free rotation' : 'Lock landscape'}</button>;
}

जब जानना हो कि लॉक लगा या नहीं, तो lockOrientation() सीधे इस्तेमाल करें (जहाँ लॉक अस्वीकार होता है, जैसे डेस्कटॉप ब्राउज़र, वहाँ यह false जवाब देता है):

import { lockOrientation, toast } from '@/lib/appmintNative';

export async function openSignaturePad() {
  const ok = await lockOrientation('landscape');
  if (!ok) toast('Turn your phone sideways for more room');
}

नोट: कोई भी orientation जो landscape से शुरू हो, लैंडस्केप में लॉक होता है; बाकी सब पोर्ट्रेट में ('landscape-secondary' सादा लैंडस्केप बन जाता है)। डेस्कटॉप ब्राउज़र लॉक अस्वीकार करते हैं; फ़ोन ब्राउज़र आम तौर पर सिर्फ़ फ़ुलस्क्रीन में अनुमति देते हैं। lockOrientation(to) true/false लौटाता है। लॉक किए गए व्यू से निकलने का रास्ता उपयोगकर्ता को दें।

appmintNative.screenReader appmintNative#

screenReader.isEnabled() / screenReader.speak(text, lang?) / screenReader.onChange(cb)

उदाहरण

ऐप को बताता है कि TalkBack चालू है या नहीं, और छोटे संदेश बोलकर सुनाता है।

लौटाता है: isEnabled() → Promise<boolean> (वेब पर false - वहाँ कोई वेब API नहीं है)। speak() → Promise<void>। onChange(cb) → अनसब्सक्राइब फ़ंक्शन; स्क्रीन रीडर चालू या बंद होने पर cb(enabled) चलता है (सिर्फ़ ऐप में)। ज़रूरत: कुछ नहीं।

import { screenReader } from '@/lib/appmintNative';
import { useEffect, useState } from 'react';

function useScreenReader() {
  const [on, setOn] = useState(false);
  useEffect(() => {
    void screenReader.isEnabled().then(setOn);
    return screenReader.onChange(setOn);
  }, []);
  return on;   // e.g. skip auto-advancing carousels when true
}

async function onPaid() {
  await screenReader.speak('Payment complete', 'en');
}

नतीजा सिर्फ़ स्क्रीन रीडर इस्तेमाल करने वालों को सुनाएँ, और बाकी सबके लिए विज़ुअल टोस्ट रहने दें:

import { screenReader, toast } from '@/lib/appmintNative';

export async function notifySaved() {
  if (await screenReader.isEnabled()) await screenReader.speak('Note saved', 'en');
  else toast('Note saved');
}

TalkBack चालू होने पर एनिमेशन हटाएँ, और कंपोनेंट के जाने पर सुनना बंद करें:

import { useEffect, useState } from 'react';
import { screenReader } from '@/lib/appmintNative';

export function Banner({ text }: { text: string }) {
  const [reduce, setReduce] = useState(false);
  useEffect(() => {
    let alive = true;
    void screenReader.isEnabled().then((v) => { if (alive) setReduce(v); });
    const off = screenReader.onChange(setReduce);   // returns an unsubscribe function
    return () => { alive = false; off(); };
  }, []);
  return <div className={reduce ? '' : 'animate-pulse'} role="status" aria-live="polite">{text}</div>;
}

ध्यान दें: ऐप में speak ScreenReader प्लगइन से जाता है; वेब पर (या प्लगइन विफल होने पर) इसे टेक्स्ट-टू-स्पीच से बोला जाता है। ज़्यादातर घोषणाओं के लिए अच्छी मार्कअप (aria-label, aria-live रीजन) को तरजीह दें; छोटे, ज़रूरी बदलावों के लिए speak इस्तेमाल करें।

appmintNative.searchContacts appmintNative#

searchContacts(query: string, limit = 50): Promise<Contact[]>

उदाहरण

खोज शब्द से मेल खाते संपर्क ढूँढता है। सिर्फ़ ऐप।

लौटाता है: Promise<Contact[]> - कुछ न मिलने, पहुँच से मना होने या Contacts बंद होने पर खाली। ज़रूरत: वही जो listContacts की है: चरण 4 (Access) में Contacts (जब आपका कोड इसे बुलाता है तो बिल्ड इसे चालू कर देता है), और पहली बार इस्तेमाल पर यूज़र की अनुमति।

टाइप करते समय थोड़ी देरी वाला सर्च बॉक्स।

import { searchContacts, can } from '@/lib/appmintNative';

let timer: ReturnType<typeof setTimeout> | undefined;
let latest = 0;

export function onSearchInput(text: string, show: (names: string[]) => void) {
  if (can('contactList') === 'none') return;
  clearTimeout(timer);
  timer = setTimeout(async () => {
    const mine = ++latest;
    const found = await searchContacts(text, 20).catch(() => []);
    if (mine !== latest) return;                     // a newer search already started
    show(found.map((c) => c.name ?? ''));
  }, 250);
}

Contacts उपलब्ध न होने पर और कुछ न मिलने पर साफ़ स्थिति दिखाएँ:

import { useState } from 'react';
import { can, searchContacts, type Contact } from '@/lib/appmintNative';

export function ContactPicker({ onPick }: { onPick: (c: Contact) => void }) {
  const [rows, setRows] = useState<Contact[] | null>(null);
  if (can('contactList') === 'none') return <p>Choosing a contact works only in the Android app.</p>;
  return (
    <div>
      <input placeholder="Search contacts" onChange={async (e) => setRows(await searchContacts(e.target.value, 10))} />
      {rows && rows.length === 0 && <p>No contacts match, or access was not allowed.</p>}
      {rows?.map((c) => <button key={c.id ?? c.name} onClick={() => onPick(c)}>{c.name} {c.phones?.[0]}</button>)}
    </div>
  );
}

नाम से किसी व्यक्ति को ढूँढें, फिर उसे भेजने के लिए टेक्स्ट संदेश पहले से भर दें:

import { searchContacts, composeSms } from '@/lib/appmintNative';

export async function textPerson(name: string, message: string): Promise<boolean> {
  const [first] = await searchContacts(name, 1);
  const number = first?.phones?.[0];
  return number ? composeSms(number, message) : false;   // the user presses send
}

नोट: मिलान फ़ोन की संपर्क खोज से नामों पर होता है। हर संपर्क { id?, name?, phones?: string[], emails?: string[], photoUri? } होता है। वेब पर can('contactList') 'none' है - एड्रेस बुक पढ़ने का कोई वेब API नहीं है। संपर्क निजी डेटा है: इसे अपने Play Data safety फ़ॉर्म और प्राइवेसी पॉलिसी में घोषित करें।

appmintNative.SecureKeyError appmintNative#

class SecureKeyError extends Error { code } — from '@/lib/appmintNative'

उदाहरण

वह एरर जो secureKeys देता है। इसका code एक छोटा शब्द है जिस पर आप शाखा बना सकते हैं, ऐप और वेब में एक जैसा।

लौटाता है: code और name === 'SecureKeyError' के साथ एक Error: 'no-such-key', 'auth-required' (अनलॉक करके दोबारा आज़माएँ), 'key-invalidated' (बायोमेट्रिक्स या स्क्रीन लॉक बदला - डेटा चला गया), 'strongbox-unavailable', 'disabled' (इस बिल्ड में Secure Keys नहीं / Web Crypto नहीं), 'bad-alias', 'bad-payload' (वैध base64 नहीं या बहुत छोटा), 'too-large' (Android पर एक कॉल में 4 MB से ज़्यादा), 'foreign-origin' (किसी दूसरी साइट के पेज ने माँगा), 'failed' (बाकी सब)। ज़रूरत: कुछ नहीं।

import { secureKeys, SecureKeyError, unlock } from '@/lib/appmintNative';

export async function openSecret(sealed: string): Promise<string | null> {
  try {
    return await secureKeys.decrypt('vault', sealed);
  } catch (e) {
    if (!(e instanceof SecureKeyError)) throw e;
    switch (e.code) {
      case 'auth-required':
        if (await unlock('Open vault')) return secureKeys.decrypt('vault', sealed);
        return null;
      case 'no-such-key':
      case 'key-invalidated':
        alert('This secret can no longer be opened.');
        return null;
      case 'disabled':
        alert('Secure storage is not available in this app.');
        return null;
      default:
        alert('Could not open it: ' + e.code);
        return null;
    }
  }
}

कोड को एक ही जगह यूज़र के लिए छोटे संदेश में बदलें:

import { SecureKeyError } from '@/lib/appmintNative';

export function secureKeyMessage(e: unknown): string {
  if (!(e instanceof SecureKeyError)) return 'Something went wrong';
  switch (e.code) {
    case 'auth-required': return 'Unlock your phone to continue';
    case 'key-invalidated': return 'Your screen lock changed, so this data was erased';
    case 'too-large': return 'That is too big to protect in one piece';
    case 'foreign-origin': return 'This page is not allowed to use secure storage';
    default: return 'Secure storage failed (' + e.code + ')';
  }
}

छेड़छाड़ वाले या ग़लत पेलोड को सामान्य नतीजा मानें और साफ़ करें, पर ग़ायब कुंजी को ऐसी त्रुटि रखें जो आप दिखाएँ:

import { secureKeys, SecureKeyError } from '@/lib/appmintNative';

export async function readNote(sealed: string): Promise<string | null> {
  try {
    return await secureKeys.decrypt('notes', sealed);
  } catch (e) {
    if (e instanceof SecureKeyError && (e.code === 'bad-payload' || e.code === 'failed')) {
      localStorage.removeItem('note');   // the stored text cannot be read any more
      return null;
    }
    throw e;
  }
}

ध्यान दें: e.message या तो code होता है या "code: detail"; e.code के आधार पर शाखा बनाएँ, संदेश के आधार पर कभी नहीं। प्लेटफ़ॉर्म के अपने त्रुटि शब्द इन कोड में बदल दिए जाते हैं, और उनका मूल टेक्स्ट संदेश में रहता है। बदला हुआ या पराया पेलोड वेब पर 'bad-payload' और ऐप में 'failed' होता है, जो उसे दूसरी cipher त्रुटियों से अलग नहीं पहचान सकता।

appmintNative.secureKeys appmintNative#

secureKeys — generate / encrypt / decrypt / remove / has / list / isHardwareBacked

उदाहरण

एन्क्रिप्शन कीज़ जो डिवाइस के पास रहती हैं और ऐप कभी एक्सपोर्ट नहीं कर सकता: ऐप में Android Keystore, और वेब पर न निकाली जा सकने वाली Web Crypto की। कुंजी हटाना ही एकमात्र असली "सुरक्षित डिलीट" है।

लौटाता है: सभी Promise। generate(alias, {requireAuth?, authValiditySeconds?}) → {hardwareBacked}; encrypt(alias, text) → base64 स्ट्रिंग; decrypt(alias, base64) → टेक्स्ट; remove(alias) / has(alias) / isHardwareBacked(alias) → boolean; list() → string[]। generate, encrypt और decrypt एक code के साथ SecureKeyError देते हैं। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Secure Keys (एन्क्रिप्शन)।

import { secureKeys, SecureKeyError, can } from '@/lib/appmintNative';

export async function saveDiary(text: string) {
  if (can('secureKeys') === 'none') throw new Error('Secure storage is not available here');
  if (!(await secureKeys.has('diary'))) {
    const { hardwareBacked } = await secureKeys.generate('diary', { requireAuth: true, authValiditySeconds: 60 });
    console.log(hardwareBacked ? 'Key in secure hardware' : 'Key in software');
  }
  const sealed = await secureKeys.encrypt('diary', text);    // strings in, base64 out
  localStorage.setItem('diary', sealed);
}

export async function readDiary(): Promise<string | null> {
  const sealed = localStorage.getItem('diary');
  if (!sealed) return null;
  try {
    return await secureKeys.decrypt('diary', sealed);
  } catch (e) {
    if (e instanceof SecureKeyError && e.code === 'key-invalidated') return null;  // data is gone
    throw e;
  }
}

सचमुच मिटाएँ - इसके बाद कोई भी सील किया हुआ डेटा नहीं पढ़ सकता:

import { secureKeys } from '@/lib/appmintNative';

export async function wipeDiary() {
  await secureKeys.remove('diary');
  localStorage.removeItem('diary');
}

ऐप में रखी कुंजियों की सूची दिखाएँ और बताएँ कि क्या हर एक हार्डवेयर से सुरक्षित है (वेब हमेशा जवाब देता है false):

import { secureKeys, can } from '@/lib/appmintNative';

export async function describeKeys(): Promise<string[]> {
  if (can('secureKeys') === 'none') return [];
  const aliases = await secureKeys.list();
  return Promise.all(aliases.map(async (alias) =>
    `${alias}: ${(await secureKeys.isHardwareBacked(alias)) ? 'secure hardware' : 'software'}`));
}

नोट: can('secureKeys') ऐप में 'native' होता है और ब्राउज़र में 'web', जहाँ isHardwareBacked() हमेशा false रहता है - वहाँ कभी "हार्डवेयर" न कहें। requireAuth वाली कुंजी: ऐप में अनलॉक की अवधि बीत जाने के बाद encrypt/decrypt कुछ दिखाए बिना auth-required फेंक देते हैं - unlock() कॉल करें और दोबारा कोशिश करें; वेब पर रनटाइम खुद unlock() दिखाता है। एक बार में एक ही रिकॉर्ड एन्क्रिप्ट करें (ऐप एक कॉल को 4 MB तक सीमित रखता है)। ऐलियास: अक्षर, अंक, _ - ., अधिकतम 64।

appmintNative.sendSms appmintNative#

sendSms(phoneNumber: string, message: string): Promise<boolean>

उदाहरण

बिना पुष्टि स्क्रीन के सीधे SMS भेजता है। सिर्फ़ Android ऐप।

लौटाता है: Promise<boolean> - फ़ोन के संदेश को भेजा हुआ बताने पर true, फ़ेल होने, SMS चालू न होने, या उपयोगकर्ता के अनुमति न देने पर false। ज़रूरत: चरण 4 (Access) में SMS (पढ़ें और भेजें) स्विच - जब आपका कोड इसे कॉल करता है तो बिल्ड इसे चालू कर देता है - और पहले इस्तेमाल पर उपयोगकर्ता की अनुमति।

अलर्ट भेजें और यूज़र को बताएँ कि क्या हुआ।

import { sendSms, can } from '@/lib/appmintNative';

export async function sendAlert(to: string, text: string): Promise<string> {
  if (can('smsSend') === 'none') return 'Sending SMS works only in the Android app.';
  try {
    const ok = await sendSms(to, text);
    return ok ? 'Alert sent' : 'The SMS could not be sent';
  } catch {
    return 'No answer from the phone after 3 minutes';
  }
}

पहले पुष्टि माँगें, क्योंकि संदेश बिना किसी रद्द करने वाली स्क्रीन के चला जाता है:

import { sendSms, composeSms, can, toast } from '@/lib/appmintNative';

export async function sendCheckIn(to: string) {
  if (can('smsSend') === 'none') { composeSms(to, 'I am safe'); return; }   // the user presses send
  if (!window.confirm('Send "I am safe" to ' + to + '? This may cost money.')) return;
  toast((await sendSms(to, 'I am safe')) ? 'Sent' : 'Could not send. Check the SMS permission.');
}

एक ही अलर्ट कई लोगों को, एक-एक करके भेजें, और गिनें कि कितने गए:

import { sendSms } from '@/lib/appmintNative';

export async function alertAll(numbers: string[], text: string) {
  let sent = 0;
  for (const n of numbers) {
    if (await sendSms(n, text)) sent += 1;      // waits for the phone's answer for each one
  }
  return { sent, failed: numbers.length - sent };
}

नोट: रैपर पहले जवाब पर पूरा होता है, जो sent है (संदेश फ़ोन से निकल गया), delivered नहीं। डिलीवरी रिपोर्ट के लिए कच्चा WebToApk.sendSms appmint:sms इवेंट के साथ इस्तेमाल करें। can('smsSend') वेब पर 'none' है (कोई ब्राउज़र SMS नहीं भेज सकता)। हर SMS में उपयोगकर्ता के पैसे लग सकते हैं। SMS एक Google Play प्रतिबंधित अनुमति है: आपको Play Console में स्वीकृत Permissions Declaration चाहिए, इसलिए जब उपयोगकर्ता खुद भेजें दबा सकता हो तो composeSms() को चुनें।

appmintNative.setInAppPresentation appmintNative#

import { setInAppPresentation } from '@/lib/appmintNative'

उदाहरण

चुनता है कि इन-ऐप संदेश (क्रिएटर के कैंपेन) कौन बनाए: ऐप ('auto', डिफ़ॉल्ट: क्रिएटर का बैनर, कार्ड, शीट या फ़ुल-स्क्रीन डिज़ाइन) या आपका अपना UI ('none', इसके ज़रिए onInAppMessage).

लौटाता है: boolean, तुरंत: सेव होने पर true (यह फ़ोन पर ही रहता है)। वेब पर और AppMint Push के बिना हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { setInAppPresentation } from '@/lib/appmintNative';

// The app draws its own messages from now on.
setInAppPresentation('none');

सेटिंग्स का एक स्विच:

import { setInAppPresentation } from '@/lib/appmintNative';

export function onNativePopupsChange(on: boolean) {
  if (!setInAppPresentation(on ? 'auto' : 'none')) console.info('In-app messages are not available here');
}

एक स्क्रीन खुली रहने तक नियंत्रण अपने हाथ में लें, फिर वापस दे दें:

import { useEffect } from 'react';
import { setInAppPresentation } from '@/lib/appmintNative';

export function useQuietScreen() {
  useEffect(() => {
    setInAppPresentation('none');
    return () => { setInAppPresentation('auto'); };
  }, []);
}

ध्यान दें: जो संदेश आपके कोड को सौंपा गया वह दिखाया हुआ गिना जाता है। 'none' सेट करने से स्क्रीन पर अभी खुला संदेश भी बंद हो जाता है।

appmintNative.setMusicVolume appmintNative#

setMusicVolume(percent) — @/lib/appmintNative

उदाहरण

ऐप के लूप होने वाले बैकग्राउंड म्यूज़िक का स्तर 0 से 100 के बीच सेट करता है (राउंड और सीमा के भीतर अपने-आप कर दिया जाता है)।

लौटाता है: void। ज़रूरत: बिल्ड करते समय चरण 3 (Integrate) में Background Music के तहत चुनी गई फ़ाइल। musicVolume() के होने पर कुछ नहीं करता -1.

ऐप अगले लॉन्च के लिए स्तर याद रखता है, इसलिए म्यूट टॉगल ही काफ़ी है:

import { musicVolume, setMusicVolume } from '@/lib/appmintNative';

export const hasMusic = () => musicVolume() >= 0;
export const toggleMute = () => setMusicVolume(musicVolume() > 0 ? 0 : 40);   // remembered by the app

आवाज़ का स्लाइडर जो तभी दिखता है जब ऐप में संगीत हो:

import { useState } from 'react';
import { musicVolume, setMusicVolume } from '@/lib/appmintNative';

export function MusicSlider() {
  const [level, setLevel] = useState(musicVolume());
  if (level < 0) return null;                       // no music in this app: draw no control
  return (
    <input type="range" min={0} max={100} value={level}
      onChange={(e) => { const v = Number(e.target.value); setLevel(v); setMusicVolume(v); }} />
  );
}

वीडियो चलते समय म्यूज़िक धीमा करें और बाद में वापस पहले जैसा कर दें:

import { musicVolume, setMusicVolume } from '@/lib/appmintNative';

let before = -1;
export function duckMusic() { before = musicVolume(); if (before > 0) setMusicVolume(10); }
export function restoreMusic() { if (before >= 0) setMusicVolume(before); before = -1; }

नोट: 0 संगीत को मौन कर देता है पर वह चलता रहता है। स्तर फ़ोन पर सेव होता है (जब तक उपयोगकर्ता ऐप का डेटा न मिटाए)। सिर्फ़ Android ऐप; वेब पर कुछ नहीं होता।

appmintNative.setPushPresentation appmintNative#

import { setPushPresentation } from '@/lib/appmintNative'

उदाहरण

चुनता है कि ऐप खुले रहने पर इन-ऐप स्टाइल (बैनर, पॉप-अप, बॉटम शीट या फ़ुल स्क्रीन) में भेजा गया AppMint Push संदेश कौन बनाए: ऐप शेल ('auto', डिफ़ॉल्ट) या आपका अपना कोड ('none').

लौटाता है: boolean, तुरंत - मोड सेव होने पर true। वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू।

import { useEffect } from 'react';
import { setPushPresentation, pushInbox } from '@/lib/appmintNative';

// Option A — keep the shell's banner, and never draw a second one on top of it.
export function usePushBanner(show: (title: string) => void) {
  useEffect(() => {
    const onPush = (e: Event) => {
      const d = (e as CustomEvent).detail;
      // { type: 'received', message, presented } — presented = the shell already showed it
      if (d.type === 'received' && !d.presented) show(d.message.title);
    };
    window.addEventListener('appmint:push', onPush);
    return () => window.removeEventListener('appmint:push', onPush);
  }, [show]);
}

// Option B — the app draws every message itself.
export function takeOverPushBanners() {
  setPushPresentation('none');   // saved on the phone; 'auto' gives it back to the shell
  return pushInbox();
}

जवाब जाँचें, क्योंकि वेब पर या जब AppMint Push बंद हो तब यह false होता है:

import { setPushPresentation } from '@/lib/appmintNative';

export function useOwnBanners(on: boolean): boolean {
  const saved = setPushPresentation(on ? 'none' : 'auto');
  if (!saved) console.info('Push banners are not controllable here');
  return saved;
}

हर संदेश के लिए अपना बैनर बनाएँ और इनबॉक्स को साथ-साथ अपडेट रखें:

import { useEffect, useState } from 'react';
import { setPushPresentation, markPushRead, pushInbox, type PushMessage } from '@/lib/appmintNative';

export function useCustomBanner() {
  const [msg, setMsg] = useState<PushMessage | null>(null);
  useEffect(() => {
    setPushPresentation('none');                  // the shell draws nothing; the page does
    const onPush = (e: Event) => {
      const d = (e as CustomEvent).detail;
      if (d.type === 'received') setMsg(d.message);
      if (d.type === 'revoked') setMsg((m) => (m?.id === d.id ? null : m));
    };
    window.addEventListener('appmint:push', onPush);
    return () => { window.removeEventListener('appmint:push', onPush); setPushPresentation('auto'); };
  }, []);
  return { msg, unread: pushInbox().filter((m) => !m.read).length, dismiss: () => { if (msg) markPushRead(msg.id); setMsg(null); } };
}

ध्यान दें: इनबॉक्स और appmint:push इवेंट दोनों मोड में एक जैसे काम करते हैं। 'none' सेट करने से शेल जो संदेश अभी दिखा रहा है वह भी बंद हो जाता है।

appmintNative.setPushTag appmintNative#

import { setPushTag } from '@/lib/appmintNative'

उदाहरण

इस इंस्टॉल पर एक AppMint Push टैग सेट करता है। सेंड सेंटर टैग वाले सभी लोगों को भेज सकता है, और टैग ऑटोमेशन टैग सेट होने के तय समय बाद मैसेज भेज सकता है। '' टैग हटा देता है।

लौटाता है: boolean, तुरंत - सेव होने पर true; कुंजी 1-32 अक्षर, अंक या _ न हो, मान 128 अक्षर से ज़्यादा हो या इंस्टॉल 50 टैग से ऊपर चला जाए तो false। वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू; टैग टार्गेटिंग और ऑटोमेशन 30 Sep 2026 से बने ऐप तक पहुँचते हैं।

import { useEffect } from 'react';
import { setPushTag } from '@/lib/appmintNative';

// Abandoned cart: a tag automation on "cart" (say 60 minutes) reminds only phones whose cart is
// still open when the time is up. Setting 'open' again does not restart the clock.
export function useCartPushTag(itemCount: number) {
  const hasItems = itemCount > 0;
  useEffect(() => {
    setPushTag('cart', hasItems ? 'open' : '');
  }, [hasItems]);
}

export function onCheckoutComplete() {
  setPushTag('cart', '');
}

नतीजा जाँचें: false का मतलब है कि टैग नकार दिया गया (गलत key, 128 अक्षरों से लंबा मान, 50 से ज़्यादा टैग) या यह वेब है, इसलिए स्थिति का अपना रिकॉर्ड रखें।

import { setPushTag } from '@/lib/appmintNative';

export function markNewsletterChoice(subscribed: boolean) {
  const saved = setPushTag('newsletter', subscribed ? 'yes' : '');
  if (!saved) console.warn('Push tag not saved (web, Push off, or invalid key/value)');
  return saved;
}

किसी फ़ोन को मील का पत्थर पहुँचने पर टैग करें, ताकि सेंड सेंटर उस स्तर के सभी लोगों को टारगेट कर सके।

import { useEffect } from 'react';
import { setPushTag } from '@/lib/appmintNative';

export function useLevelTag(level: number) {
  useEffect(() => {
    if (level >= 10) setPushTag('veteran', 'yes');
    setPushTag('level', String(level));
  }, [level]);
}

ध्यान दें: टैग इंस्टॉल से जुड़े होते हैं, साइन इन किए यूज़र से नहीं। टैग में कभी निजी डेटा न डालें।

appmintNative.setPushTags appmintNative#

import { setPushTags } from '@/lib/appmintNative'

उदाहरण

एक बदलाव में कई AppMint Push टैग सेट करता है; '' वैल्यू उस टैग को हटा देती है, जिन टैग का आप नाम नहीं लेते वे बने रहते हैं।

लौटाता है: तुरंत boolean - सेव होने पर true; किसी कुंजी (key) या वैल्यू के setPushTag नियम तोड़ने पर false (तब कुछ सेव नहीं होता)। वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय इंटीग्रेट चरण (चरण 3) में AppMint Push चालू हो; टैग टार्गेटिंग और ऑटोमेशन 30 Sep 2026 से बने ऐप्स तक पहुँचते हैं।

import { setPushTags, getPushTags, setPushUser } from '@/lib/appmintNative';

export function tagProfile(profile: { plan: 'free' | 'pro'; level: number; team?: string }) {
  return setPushTags({
    plan: profile.plan,
    level: String(profile.level),   // values are text
    team: profile.team ?? '',       // '' removes the tag
  });
}

// Sign-out: clear every tag and the user id.
export function signOutOfPush() {
  const cleared = Object.fromEntries(Object.keys(getPushTags()).map((k) => [k, '']));
  setPushTags(cleared);
  setPushUser('');
}

उपयोगकर्ता के सेटिंग्स सेव करने पर फ़ोन को उसकी चुनी भाषा और विषयों से टैग करें, और जाँचें कि हर टैग स्वीकार हुआ।

import { setPushTags } from '@/lib/appmintNative';

export function savePreferences(prefs: { lang: string; topics: string[] }) {
  const ok = setPushTags({
    lang: prefs.lang,
    topics: prefs.topics.join(',').slice(0, 128),   // a value is at most 128 characters
    onboarded: 'yes',
  });
  if (!ok) console.warn('Push tags not saved: nothing was changed');
  return ok;
}

React इफ़ेक्ट में टैग को साइन इन खाते के साथ मिलाकर रखें, और जो अब लागू नहीं हैं उन्हें हटा दें।

import { useEffect } from 'react';
import { setPushTags, setPushUser } from '@/lib/appmintNative';

export function usePushProfile(user: { id: string; plan: 'free' | 'pro'; trialEnds?: string } | null) {
  useEffect(() => {
    if (!user) return;
    setPushUser(user.id);
    setPushTags({ plan: user.plan, trial: user.plan === 'pro' ? '' : (user.trialEnds ?? '') });
  }, [user?.id, user?.plan, user?.trialEnds]);
}

ध्यान दें: एक कॉल का सब कुछ सेव होता है या कुछ भी नहीं।

appmintNative.setPushUser appmintNative#

import { setPushUser } from '@/lib/appmintNative'

उदाहरण

AppMint Push को बताता है कि आपका कौन सा उपयोगकर्ता इस इंस्टॉल पर साइन-इन है, ताकि सेंड सेंटर उसी एक उपयोगकर्ता को संदेश भेज सके। साइन-इन के बाद अपना स्थिर अकाउंट id दें और साइन-आउट पर ''।

लौटाता है: boolean, तुरंत - id सेव होने पर true; अक्षर, अंक और _ @ . : + - (128 तक) के अलावा कुछ भी होने पर false। वेब पर हमेशा false। ज़रूरत: Android: बिल्ड करते समय Integrate चरण (चरण 3) में AppMint Push चालू; यूज़र को भेजना 30 Sep 2026 से बने ऐप तक पहुँचता है।

import { useEffect } from 'react';
import { setPushUser, getPushUser } from '@/lib/appmintNative';
import { supabase } from '@/lib/supabase';

// Keeps AppMint Push in step with the signed-in account: the auth user id, never the email.
export function usePushUser() {
  useEffect(() => {
    const { data } = supabase.auth.onAuthStateChange((_event, session) => {
      const id = session?.user.id ?? '';
      if (getPushUser() !== id) setPushUser(id);
    });
    return () => data.subscription.unsubscribe();
  }, []);
}

नामंज़ूर id को संभालें: ईमेल पता या अनुमत सेट से बाहर के अक्षरों वाली किसी भी id पर setPushUser जवाब में false देता है, इसलिए एक स्थिर id भेजें।

import { setPushUser } from '@/lib/appmintNative';

export function linkPushToAccount(account: { id: string; email: string }) {
  const ok = setPushUser(account.id);   // the id, never the email
  if (!ok) console.warn('Push user id not saved (web, Push off, or invalid characters)');
  return ok;
}

यूज़र के साइन-आउट करने पर id साफ़ करें, सादे क्लिक हैंडलर में।

import { setPushUser, getPushUser } from '@/lib/appmintNative';

export function SignOutButton({ signOut }: { signOut: () => Promise<void> }) {
  return (
    <button
      onClick={async () => {
        await signOut();
        if (getPushUser()) setPushUser('');   // this install stops receiving "send to this user" messages
      }}
    >
      Sign out
    </button>
  );
}

इसे एक टैग के साथ मिलाएँ ताकि साइन-इन का एक ही चरण उपयोगकर्ता और उसके प्लान दोनों की पहचान करे।

import { setPushUser, setPushTag } from '@/lib/appmintNative';

export function onSignedIn(userId: string, plan: 'free' | 'pro') {
  setPushUser(userId);
  setPushTag('plan', plan);
}

ध्यान दें: id इंस्टॉल की होती है, इसलिए साइन-आउट पर इसे साफ़ करना होगा। यह टैग साफ़ नहीं करता - यूज़र-विशिष्ट टैग के लिए setPushTags को '' वैल्यू के साथ बुलाएँ।

appmintNative.setStatusBar appmintNative#

setStatusBar(backgroundColor: string | null, style?: 'light' | 'dark' | null): void

उदाहरण

सिस्टम स्टेटस बार को स्क्रीन से मेल खाते रंग में रंगता है, और उसके लिए हल्के या गहरे आइकन चुनता है।

लौटाता है: कुछ नहीं। ज़रूरत: कुछ नहीं।

style आइकनों का रंग है (ICONS): 'light' = गहरे बार के लिए हल्के आइकन (डिफ़ॉल्ट), 'dark' = हल्के बार के लिए गहरे आइकन। इसे शुरू में ऐप के --bg के साथ एक बार कॉल करें, और अलग बैकग्राउंड वाली हर स्क्रीन पर फिर से:

import { useEffect } from 'react';
import { setStatusBar } from '@/lib/appmintNative';

export function App() {
  useEffect(() => { setStatusBar('#0B0F1A', 'light'); }, []);   // dark app, light icons
  return <main>{/* … */}</main>;
}

export function PhotoViewer() {
  useEffect(() => {
    setStatusBar('#000000', 'light');
    return () => setStatusBar('#0B0F1A', 'light');                // restore on leave
  }, []);
  return null;
}

एक हल्की स्क्रीन:

import { setStatusBar } from '@/lib/appmintNative';

setStatusBar('#FFFFFF', 'dark');

Capacitor वाला रूप (स्टाइल का नाम BACKGROUND के नाम पर है: 'DARK' = हल्के आइकन):

import { StatusBar } from '@/lib/appmintNative';

await StatusBar.setBackgroundColor({ color: '#0B0F1A' });
await StatusBar.setStyle({ style: 'DARK' });

ध्यान दें:

  • Android ऐप में शेल खुद बार को रंगता है, जो Android 15 के एज-टू-एज में भी चलता है; न पढ़ा जा सकने वाला रंग अनदेखा किया जाता है - #RRGGBB दें। सिर्फ़ स्टाइल वाली कॉल (backgroundColor = null) वही रंग रखती है जो इस पेज ने आखिरी बार सेट किया था और आइकन पलटती है; पेज ने कोई रंग सेट करने से पहले यह StatusBar प्लगइन से आइकन पलटती है और बार वही रंग रखता है जो बिल्ड के समय चुना गया था।
  • वेब पर यह <meta name="theme-color"> सेट करता है; आइकन का रंग ब्राउज़र खुद चुनता है।

appmintNative.setVolume appmintNative#

setVolume(stream, percent, showUi?) — @/lib/appmintNative

उदाहरण

एक स्ट्रीम के लिए फ़ोन का अपना वॉल्यूम लेवल सेट करता है, 0-100। showUi फ़ोन का वॉल्यूम स्लाइडर चलते हुए दिखाता है।

लौटाता है: { ok, stream?, current?, max?, percent?, error? } - एरर unknown_stream, bad_percent, fixed_volume, dnd_access_required (Do Not Disturb रिंग / नोटिफ़िकेशन को रोकता है - requestDndAccess() कॉल करें), muted (Do Not Disturb "Total silence" स्ट्रीम को म्यूट करता है, इसलिए स्तर नहीं बदला), not_applied, foreign_origin, failed, या वेब पर unavailable होता है। ज़रूरत: मीडिया, अलार्म और कॉल के लिए कुछ नहीं।

import { useState } from 'react';
import { getVolume, setVolume, requestDndAccess } from '@/lib/appmintNative';

export function RingVolume() {
  const [level, setLevel] = useState(() => getVolume('ring')?.percent ?? null);
  if (level === null) return null;                   // web: no phone volume to set
  return (
    <input
      type="range" min={0} max={100} value={level}
      onChange={(e) => {
        const r = setVolume('ring', Number(e.target.value));
        if (r.ok) setLevel(r.percent ?? level);
        else if (r.error === 'dnd_access_required') requestDndAccess();
      }}
    />
  );
}

मीडिया वॉल्यूम को स्लाइडर के रूप में दिखाएँ जो हार्डवेयर कुंजियों के साथ चलता है, इसका इस्तेमाल करके onVolumeChange.

import { useEffect, useState } from 'react';
import { getVolume, setVolume, onVolumeChange } from '@/lib/appmintNative';

export function MediaVolume() {
  const [pct, setPct] = useState<number | null>(() => getVolume('media')?.percent ?? null);
  useEffect(() => onVolumeChange((l) => { if (l.stream === 'media') setPct(l.percent); }), []);
  if (pct === null) return null;   // web: no phone volume
  return <input type="range" min={0} max={100} value={pct} onChange={(e) => setVolume('media', Number(e.target.value))} />;
}

अलार्म स्क्रीन बजने से पहले अलार्म स्ट्रीम की आवाज़ बढ़ाएँ, और बताएँ कि वह क्यों नहीं बदली।

import { setVolume } from '@/lib/appmintNative';

export function louderAlarm() {
  const r = setVolume('alarm', 90, true);   // true shows the phone's volume slider
  if (!r.ok) {
    switch (r.error) {
      case 'fixed_volume': return 'This phone has a fixed volume.';
      case 'unavailable': return 'Volume control needs the Android app.';
      default: return `Could not change the volume (${r.error}).`;
    }
  }
  return `Alarm volume is now ${r.percent}%`;
}

appmintNative.share appmintNative#

share(input: { title?, text?, url?, files? }): Promise<boolean>

उदाहरण

ऐप में सिस्टम शेयर शीट खोलता है, और ब्राउज़र में Web Share API।

लौटाता है: Promise<boolean> - शीट खुलने पर true; यूज़र के उसे बंद करने या शेयरिंग उपलब्ध न होने पर false। यह कभी reject नहीं होता। ज़रूरत: कुछ नहीं।

import { can, share, toast } from '@/lib/appmintNative';

export function ShareRecipe({ recipe }: { recipe: { title: string; url: string } }) {
  if (can('share') === 'none') return null;
  return (
    <button
      onClick={async () => {
        const ok = await share({ title: recipe.title, text: 'Try this recipe', url: recipe.url });
        if (ok) toast('Shared');           // false = the user closed the sheet; say nothing
      }}
    >
      Share
    </button>
  );
}

सिर्फ़ टेक्स्ट (बिना लिंक) भी ठीक है:

import { share } from '@/lib/appmintNative';

await share({ text: 'My score today: 42' });

Capacitor जैसा रूप Share.share(opts) वही कोड कॉल करता है, पर यूज़र के रद्द करने पर REJECT होता है (Capacitor का नियम) और बाकी स्थिति में { activityType: '' } के साथ resolve होता है; Share.canShare() resolve होता है { value: can('share') !== 'none' }:

import { Share } from '@/lib/appmintNative';

if ((await Share.canShare()).value) {
  try {
    await Share.share({ title: 'Invite', url: 'https://example.com/join' });
  } catch {
    /* cancelled or unavailable — never report success here */
  }
}

ध्यान दें:

  • सिर्फ़ वे फ़ील्ड दें जो आपके पास हैं। ऐप में, टेक्स्ट और लिंक Capacitor Share प्लगइन से जाते हैं।
  • files Android ऐप में काम करता है (फ़ाइलें असली अटैचमेंट के रूप में शेयर शीट में जाती हैं; टेक्स्ट में url जोड़ा जाता है) और वेब पर जहाँ ब्राउज़र फ़ाइलें शेयर कर सकता है (navigator.canShare({ files }))। फ़ाइलें छोटी रखें - वे ऐप को मेमोरी में दी जाती हैं।
  • वेब पर इसे टैप से कॉल करें; ब्राउज़र ऐसा शेयर अस्वीकार करते हैं जिसे किसी उपयोगकर्ता क्रिया ने शुरू नहीं किया।

appmintNative.Share appmintNative#

import { Share } from '@/lib/appmintNative'

उदाहरण

appmintNative.share के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

ऐप में सिस्टम शेयर शीट खोलता है, और ब्राउज़र में Web Share API।

लौटाता है: Promise<boolean> - शीट खुलने पर true; यूज़र के उसे बंद करने या शेयरिंग उपलब्ध न होने पर false। यह कभी reject नहीं होता। ज़रूरत: कुछ नहीं।

import { can, share, toast } from '@/lib/appmintNative';

export function ShareRecipe({ recipe }: { recipe: { title: string; url: string } }) {
  if (can('share') === 'none') return null;
  return (
    <button
      onClick={async () => {
        const ok = await share({ title: recipe.title, text: 'Try this recipe', url: recipe.url });
        if (ok) toast('Shared');           // false = the user closed the sheet; say nothing
      }}
    >
      Share
    </button>
  );
}

सिर्फ़ टेक्स्ट (बिना लिंक) भी ठीक है:

import { share } from '@/lib/appmintNative';

await share({ text: 'My score today: 42' });

Capacitor जैसा रूप Share.share(opts) वही कोड कॉल करता है, पर यूज़र के रद्द करने पर REJECT होता है (Capacitor का नियम) और बाकी स्थिति में { activityType: '' } के साथ resolve होता है; Share.canShare() resolve होता है { value: can('share') !== 'none' }:

import { Share } from '@/lib/appmintNative';

if ((await Share.canShare()).value) {
  try {
    await Share.share({ title: 'Invite', url: 'https://example.com/join' });
  } catch {
    /* cancelled or unavailable — never report success here */
  }
}

ध्यान दें:

  • सिर्फ़ वे फ़ील्ड दें जो आपके पास हैं। ऐप में, टेक्स्ट और लिंक Capacitor Share प्लगइन से जाते हैं।
  • files Android ऐप में काम करता है (फ़ाइलें असली अटैचमेंट के रूप में शेयर शीट में जाती हैं; टेक्स्ट में url जोड़ा जाता है) और वेब पर जहाँ ब्राउज़र फ़ाइलें शेयर कर सकता है (navigator.canShare({ files }))। फ़ाइलें छोटी रखें - वे ऐप को मेमोरी में दी जाती हैं।
  • वेब पर इसे टैप से कॉल करें; ब्राउज़र ऐसा शेयर अस्वीकार करते हैं जिसे किसी उपयोगकर्ता क्रिया ने शुरू नहीं किया।

appmintNative.sharedText appmintNative#

sharedText(): { text, subject, url, receivedAt } | null

उदाहरण

किसी दूसरे ऐप ने इस ऐप में जो टेक्स्ट या लिंक शेयर किया (“Share → आपका ऐप”) वह देता है, जैसे ब्राउज़र या YouTube से शेयर किया गया लिंक।

लौटाता है: तुरंत, { text, subject, url, receivedAt } या कुछ न मिलने पर null। url वह पहला http(s) लिंक है जो text में मिला (न मिले तो ''); receivedAt 1970 से मिलीसेकंड है। ज़रूरत: बिल्ड का Receive Shares From Other Apps विकल्प "Text and links" के साथ - जब आपका कोड sharedText( या बुलाता है तो अपने-आप चालू हो जाता है onShared(.

माउंट होने पर जाँचें, सेव करें, फिर साफ़ करें:

import { useEffect } from 'react';
import { can, sharedText, clearShared, toast } from '@/lib/appmintNative';

export function useSaveSharedLink(save: (item: { title: string; url: string; text: string }) => Promise<void>) {
  useEffect(() => {
    if (can('shareTarget') === 'none') return;     // on the web
    const p = sharedText();
    if (!p) return;
    void save({ title: p.subject || p.url || 'Shared note', url: p.url, text: p.text })
      .then(() => { clearShared(); toast('Saved'); });
  }, [save]);
}

ऐप पहले से खुला हो तब आने वाले शेयर को onShared से सुनें, और इस्तेमाल के बाद उसे साफ़ कर दें।

import { useEffect, useState } from 'react';
import { sharedText, onShared, clearShared } from '@/lib/appmintNative';

export function useIncomingShare() {
  const [text, setText] = useState(() => sharedText()?.text ?? '');
  useEffect(() => onShared((p) => setText(p.text)), []);
  return { text, done: () => { clearShared(); setText(''); } };
}

शेयर किए गए लिंक से फ़ॉर्म पहले से भरें और वेब पर कुछ न दिखाएँ, जहाँ sharedText() है null.

import { useEffect, useState } from 'react';
import { can, sharedText } from '@/lib/appmintNative';

export function NewBookmark() {
  const [url, setUrl] = useState('');
  useEffect(() => {
    if (can('shareTarget') === 'none') return;   // web: nobody can share into the page
    const p = sharedText();
    if (p?.url) setUrl(p.url);
  }, []);
  return <input value={url} onChange={(e) => setUrl(e.target.value)} placeholder="Paste a link" />;
}

ध्यान दें:

  • शेयर तब तक रखा रहता है जब तक आप clearShared() कॉल न करें, ताकि शेयर के बाद माउंट होने वाला ऐप भी उसे देख सके। सेव करने के बाद उसे साफ़ करें, वरना अगला माउंट उसे फिर सेव कर देगा।
  • ऐप खुला होने पर आने वाले शेयर के लिए इस्तेमाल करें onShared().
  • कैप्शन के साथ शेयर की गई फ़ोटो या वीडियो: कैप्शन यहाँ आता है, फ़ाइल खोली-गई-फ़ाइल API से।
  • सिर्फ़ Android ऐप। can('shareTarget') वेब पर 'none' है, और sharedText() वहाँ null लौटाता है।

appmintNative.sheet appmintNative#

sheet(opts: { title?, content, dismissible? }): { close(), element, closed }

उदाहरण

असली शीट व्यवहार वाली मॉडल बॉटम शीट खोलता है - फ़िल्टर पैनल, कंपोज़र या डिटेल प्रीव्यू के लिए।

लौटाता है: एक हैंडल: close() इसे बंद करता है, element बॉडी एलिमेंट है (उसे भरें या अपडेट करें), closed एक Promise<void> है जो शीट बंद होने पर पूरा होता है, चाहे वह कैसे भी बंद हुई हो। ज़रूरत: कुछ नहीं।

विकल्प: title (वैकल्पिक शीर्षक), content - एक एलिमेंट, सादा टेक्स्ट (HTML नहीं), या बॉडी भरने वाला फ़ंक्शन (host) => { … }; dismissible (डिफ़ॉल्ट true: बैकड्रॉप टैप, नीचे खींचना, Escape और बैक जेस्चर इसे बंद करते हैं)।

सादा DOM कंटेंट:

import { sheet } from '@/lib/appmintNative';

const h = sheet({
  title: 'About this list',
  content: (host) => {
    const p = document.createElement('p');
    p.textContent = 'Items sync to all your devices.';
    host.appendChild(p);
  },
});
await h.closed;

React कंटेंट, जो पोर्टल से शीट में रेंडर होता है:

import { useState } from 'react';
import { createPortal } from 'react-dom';
import { sheet } from '@/lib/appmintNative';

export function FiltersButton() {
  const [host, setHost] = useState<HTMLElement | null>(null);
  const [close, setClose] = useState<() => void>(() => () => {});

  const open = () => {
    const h = sheet({ title: 'Filters', content: () => {} });
    setHost(h.element);
    setClose(() => h.close);
    void h.closed.then(() => setHost(null));      // unmount the portal after any close
  };

  return (
    <>
      <button onClick={open}>Filters</button>
      {host && createPortal(
        <form onSubmit={(e) => { e.preventDefault(); close(); }}>
          <label><input type="checkbox" name="open" /> Open now</label>
          <button type="submit">Apply</button>
        </form>,
        host,
      )}
    </>
  );
}

एक शीट जो तब तक खुली रहती है जब तक उपयोगकर्ता चुन न ले, dismissible: false और closed promise के साथ।

import { sheet } from '@/lib/appmintNative';

export async function confirmDelete(name: string): Promise<boolean> {
  let answer = false;
  const h = sheet({
    title: 'Delete this item?',
    dismissible: false,                       // only the buttons below close it
    content: (host) => {
      const p = document.createElement('p');
      p.textContent = `"${name}" will be removed.`;
      const yes = document.createElement('button');
      yes.textContent = 'Delete';
      yes.onclick = () => { answer = true; h.close(); };
      const no = document.createElement('button');
      no.textContent = 'Keep it';
      no.onclick = () => h.close();
      host.append(p, yes, no);
    },
  });
  await h.closed;                             // resolves however the sheet closed
  return answer;
}

ध्यान दें:

  • ऐप के टोकन (--surface, --ink, --muted, --border) में बना, सेफ़ एरिया के ऊपर, खुलने पर हल्के हैप्टिक के साथ।
  • नीचे खींचकर 120 px से ज़्यादा ले जाने या झटके से खींचने पर यह बंद होती है; जब बॉडी सबसे ऊपर न हो तो पहले वह स्क्रॉल होती है।
  • dismissible: false के साथ केवल आपका अपना close() इसे बंद करता है - उपयोगकर्ता को एक बटन दें।
  • कार्रवाइयों की सादी सूची के लिए इस्तेमाल करें actionSheet().

appmintNative.shortcuts appmintNative#

shortcuts.set(items: { id?, title, route }[]) / shortcuts.clear()

उदाहरण

ऐप आइकन के लॉन्ग-प्रेस मेन्यू में शॉर्टकट जोड़ता है (Android डायनामिक शॉर्टकट)। टैप करने पर ऐप उसी रूट पर खुलता है।

लौटाता है: set() Promise<boolean> लौटाता है - शॉर्टकट सिस्टम को सौंपे जाने पर true, जहाँ कोई नहीं है (वेब) वहाँ false। clear() Promise<void> लौटाता है। ज़रूरत: कुछ नहीं।

इन्हें शुरू में एक बार सेट करें, और लॉन्च लिंक के हिसाब से रूट करें:

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { can, shortcuts, launchUrl } from '@/lib/appmintNative';

export function useAppShortcuts() {
  const navigate = useNavigate();
  useEffect(() => {
    if (can('shortcuts') !== 'none') {
      void shortcuts.set([
        { id: 'new', title: 'New note', route: '/new' },
        { id: 'search', title: 'Search', route: '/search' },
      ]);
    }
    void launchUrl().then((u) => { if (u) navigate(new URL(u).pathname); });
  }, [navigate]);
}

उन्हें हटाएँ (जैसे साइन-आउट के बाद):

import { shortcuts } from '@/lib/appmintNative';

await shortcuts.clear();

ऐप पहले से चल रहा हो और शॉर्टकट पर टैप हो तो मेल खाती स्क्रीन खोलें, इसके साथ onDeepLink.

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { onDeepLink } from '@/lib/appmintNative';

export function useShortcutRouting() {
  const navigate = useNavigate();
  useEffect(() => onDeepLink((url) => navigate(new URL(url).pathname)), [navigate]);
}

शॉर्टकट सिर्फ़ साइन-इन किए उपयोगकर्ता के लिए दिखाएँ, और वेब को संभालें, जहाँ set() जवाब देता है false.

import { useEffect } from 'react';
import { shortcuts } from '@/lib/appmintNative';

export function useAccountShortcuts(signedIn: boolean) {
  useEffect(() => {
    if (!signedIn) { void shortcuts.clear(); return; }
    void shortcuts.set([
      { id: 'inbox', title: 'Inbox', route: '/inbox' },
      { id: 'compose', title: 'Write a message', route: 'compose' },   // gets a leading "/"
    ]).then((handed) => { if (!handed) console.info('No launcher shortcuts on this platform'); });
  }, [signedIn]);
}

ध्यान दें:

  • अधिकतम 4 आइटम; ज़्यादा आइटम हटा दिए जाते हैं। शुरू में / के बिना route में वह जोड़ दिया जाता है। id वैकल्पिक है।
  • Android 7.1+; पुराने Android पर कॉल कुछ नहीं करती। शीर्षक 25 अक्षरों (छोटा लेबल) और 50 (लंबा लेबल) पर काट दिए जाते हैं।
  • टैप से ऐप लॉन्च लिंक appmint-shortcut://<package><route> के साथ खुलता है, इसलिए new URL(u).pathname आपका रूट है। इसे माउंट पर launchUrl() से पढ़ें और ऐप चलते समय onDeepLink() से।
  • can('shortcuts') वेब पर 'none' होता है: ब्राउज़र में ऐप आइकन नहीं होता।

appmintNative.snoozeAlarm appmintNative#

snoozeAlarm(id?, minutes?) — @/lib/appmintNative

उदाहरण

बजता हुआ अलार्म रोकता है और minutes बाद उसे फिर बजाता है (0 या न देने पर = अलार्म का अपना snoozeMinutes, डिफ़ॉल्ट 10)। दोहराने वाले अलार्म की कल की घंटी नहीं बदलती।

लौटाता है: boolean - बज रहा अलार्म स्नूज़ हो जाने पर true। ज़रूरत: बिल्ड करते समय Access चरण (चरण 4) में 🔔 Alarm clock।

import { isAlarmRinging, snoozeAlarm } from '@/lib/appmintNative';

// A shake-to-snooze handler:
window.addEventListener('devicemotion', (e) => {
  const a = e.accelerationIncludingGravity;
  if (a && Math.abs(a.x ?? 0) > 25 && isAlarmRinging()) snoozeAlarm('', 5);
});

अपनी अलार्म स्क्रीन के बटन से स्नूज़ करें, और अलार्म कब बज रहा है यह जानने के लिए onAlarm इस्तेमाल करें।

import { useEffect, useState } from 'react';
import { onAlarm, snoozeAlarm, stopAlarm } from '@/lib/appmintNative';

export function RingingScreen() {
  const [ringing, setRinging] = useState(false);
  useEffect(() => onAlarm((e) => setRinging(e.action === 'open' && e.ringing === true)), []);
  if (!ringing) return null;
  return (
    <div>
      <button onClick={() => { if (snoozeAlarm('', 10)) setRinging(false); }}>Snooze 10 min</button>
      <button onClick={() => { stopAlarm(); setRinging(false); }}>Stop</button>
    </div>
  );
}

snoozeAlarm कुछ न बज रहा हो तो false जवाब देता है, इसलिए यूज़र से दूसरी बार बजने का वादा करने से पहले जाँचें।

import { snoozeAlarm } from '@/lib/appmintNative';

export function snoozeOrExplain(minutes = 0) {
  return snoozeAlarm('', minutes)
    ? `Ringing again in ${minutes || 'a few'} minutes`
    : 'No alarm is ringing right now.';
}

appmintNative.speak appmintNative#

speak(text, opts?) — @/lib/appmintNative

उदाहरण

फ़ोन के टेक्स्ट-टू-स्पीच इंजन से टेक्स्ट को ज़ोर से पढ़ता है।

लौटाता है: void - यह बोलना शुरू करता है (या मौजूदा बोल के बाद कतार में लगता है) और "पूरा हुआ" का कोई संकेत नहीं देता। opts: { lang?: string; rate?: number; pitch?: number } (डिफ़ॉल्ट: इंजन की भाषा, 1, 1)। ज़रूरत: कुछ नहीं।

import { speak, stopSpeaking } from '@/lib/appmintNative';

export function ReadAloud({ text }: { text: string }) {
  return (
    <div>
      <button onClick={() => speak(text, { lang: 'en-IN', rate: 0.9 })}>Read aloud</button>
      <button onClick={() => stopSpeaking()}>Stop</button>
    </div>
  );
}

अलग-अलग भाषाएँ और गति:

import { speak } from '@/lib/appmintNative';

speak('வணக்கம்', { lang: 'ta-IN' });          // Tamil
speak('Slow and clear', { rate: 0.7 });       // rate 0.1..4, 1 = normal
speak('High voice', { pitch: 1.5 });          // pitch 0.1..4, 1 = normal

किसी कार्रवाई के बाद छोटी पुष्टि बोलकर सुनाएँ, और यूज़र को टेक्स्ट में भी बताएँ।

import { speak, toast } from '@/lib/appmintNative';

export function announceSaved(title: string) {
  toast('Saved');
  speak(`${title} saved`, { rate: 1.1 });
}

सूची को एक-एक आइटम करके पढ़ें; speak() कतार में लगाता है, इसलिए इसे लूप में कॉल करें और स्क्रीन बंद होने पर stopSpeaking() से सब रोक दें।

import { useEffect } from 'react';
import { speak, stopSpeaking } from '@/lib/appmintNative';

export function ReadHeadlines({ items }: { items: string[] }) {
  useEffect(() => () => stopSpeaking(), []);   // leaving the screen stops the voice
  return <button onClick={() => items.forEach((t) => speak(t))}>Read all</button>;
}

नोट: Android ऐप में: ऐप का नेटिव इंजन। वेब पर: speechSynthesis। कई speak() कॉल एक के बाद एक चलती हैं। अगर आपको जानना है कि बोलना कब खत्म हुआ (शब्द हाइलाइट करने या आगे बढ़ने के लिए), तो window.speechSynthesis के साथ utterance का onend सीधे इस्तेमाल करें; यह Android ऐप में भी काम करता है।

appmintNative.speech appmintNative#

speech — @/lib/appmintNative

उदाहरण

डिक्टेशन: यूज़र की बोली को टेक्स्ट में बदलता है। Android ऐप में Android का स्पीच रिकग्नाइज़र, Chrome में Web Speech API।

लौटाता है: speech.isAvailable() → Promise<boolean>। speech.start(onResult, opts?) → Promise<boolean>: सुनना शुरू होने पर true, माइक्रोफ़ोन की अनुमति न मिलने या डिक्टेशन उपलब्ध न होने पर false। onResult(text, isFinal) टेक्स्ट के साथ कॉल होता है। speech.stop() → Promise<void>। opts: { lang?: string; partial?: boolean; prompt?: string }। ज़रूरत: चरण 4 (Access) में माइक; जब कोड स्पीच रिकग्निशन इस्तेमाल करता है तो AI बिल्ड इसे आपके लिए चालू कर देते हैं।

import { useEffect, useState } from 'react';
import { speech } from '@/lib/appmintNative';

export function VoiceNote() {
  const [ok, setOk] = useState(false);
  const [text, setText] = useState('');
  const [msg, setMsg] = useState('');

  useEffect(() => { void speech.isAvailable().then(setOk); }, []);
  if (!ok) return null;                      // no dictation here: hide the mic button

  const listen = async () => {
    setMsg('Listening...');
    const started = await speech.start(
      (t, isFinal) => { setText(t); if (isFinal) setMsg(''); },
      { lang: 'en-US' },                     // partial results by default
    );
    if (!started) setMsg('Microphone permission is needed for voice input.');
  };

  return (
    <div>
      <button aria-label="Speak" onClick={listen}>🎤</button>
      <button onClick={() => { void speech.stop(); setMsg(''); }}>Stop</button>
      <p>{text}</p>
      <p role="status">{msg}</p>
    </div>
  );
}

टेक्स्ट फ़ील्ड में बोलकर लिखें और माइक्रोफ़ोन मना किए जाने पर यह बताएँ, क्योंकि start() जवाब देता है false.

import { useState } from 'react';
import { speech } from '@/lib/appmintNative';

export function DictateField() {
  const [value, setValue] = useState('');
  const [error, setError] = useState('');

  const dictate = async () => {
    setError('');
    const started = await speech.start((text, isFinal) => { if (isFinal) setValue((v) => (v ? `${v} ${text}` : text)); }, { partial: false });
    if (!started) setError('Voice input is not available. Check the microphone permission.');
  };

  return (
    <div>
      <textarea value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={dictate}>Dictate</button>
      {error && <p role="alert">{error}</p>}
    </div>
  );
}

स्क्रीन बंद होने पर सुनना बंद करें, और नतीजा इससे वापस पढ़ें speak.

import { useEffect, useState } from 'react';
import { speech, speak } from '@/lib/appmintNative';

export function VoiceCommand() {
  const [heard, setHeard] = useState('');
  useEffect(() => () => { void speech.stop(); }, []);

  return (
    <button
      onClick={() => void speech.start((t, isFinal) => {
        setHeard(t);
        if (isFinal) speak(`You said ${t}`);
      }, { lang: 'en-US' })}
    >
      {heard || 'Say something'}
    </button>
  );
}

ध्यान दें:

  • partial चालू होने पर (डिफ़ॉल्ट) शब्द आते ही onResult(text, false) आता है, फिर अंतिम टेक्स्ट के साथ एक बार onResult(text, true)। partial: false के साथ यूज़र के बोलना बंद करने पर आपको सिर्फ़ अंतिम वाला मिलता है।
  • अनुमति का प्रॉम्प्ट पहली बार पर दिखता है start().
  • अगर यूज़र कुछ नहीं बोलता, तो onResult कॉल नहीं होता; सुनना बस ख़त्म हो जाता है। अपने UI में Stop बटन या टाइमआउट रखें।
  • रुकने के बाद सुनना अपने-आप बंद हो जाता है। lang डिफ़ॉल्ट रूप से फ़ोन की भाषा होती है।

appmintNative.splash appmintNative#

splash.hold() / splash.ready()

उदाहरण

पहली असली स्क्रीन तैयार होने तक लॉन्च स्प्लैश को स्क्रीन पर रखता है, ताकि “स्प्लैश, फिर स्पिनर” न दिखे।

लौटाता है: कुछ नहीं (दोनों तुरंत चलते हैं)। ज़रूरत: कुछ नहीं।

डिफ़ॉल्ट रूप से स्प्लैश ऐप के पहले पेंट पर अपने-आप छिप जाता है - आपको कुछ नहीं करना। जब पहली स्क्रीन को डेटा चाहिए, तो मॉड्यूल के ऊपर स्प्लैश को रोकें और वह स्क्रीन दिखने पर रिलीज़ करें:

// main.tsx
import { splash } from '@/lib/appmintNative';

splash.hold();   // at import time, before the first render
// Home.tsx
import { useEffect, useState } from 'react';
import { splash } from '@/lib/appmintNative';

export function Home({ load }: { load: () => Promise<string[]> }) {
  const [items, setItems] = useState<string[] | null>(null);

  useEffect(() => {
    load()
      .then(setItems)
      .catch(() => setItems([]))
      .finally(() => splash.ready());     // ALWAYS release, also when loading failed
  }, [load]);

  if (!items) return null;
  return <ul>{items.map((t) => <li key={t}>{t}</li>)}</ul>;
}

साइन इन सेशन बहाल होने तक स्प्लैश रोकें, और उसे finally में रिलीज़ करें ताकि विफलता उसे टिकाए न रखे।

// main.tsx
import { splash } from '@/lib/appmintNative';
import { supabase } from '@/lib/supabase';

splash.hold();
supabase.auth.getSession().finally(() => splash.ready());

पहली स्क्रीन का डेटा दिखने के बाद, हुक से इसे एक बार रिलीज़ करें।

import { useEffect } from 'react';
import { splash } from '@/lib/appmintNative';

export function useReleaseSplash(loaded: boolean) {
  useEffect(() => {
    if (loaded) splash.ready();   // calling it again later does nothing harmful
  }, [loaded]);
}

ध्यान दें:

  • Android ऐप में hold() शेल से कहता है कि स्प्लैश रखे; ready() उसे छिपाता है (क्रिएटर का न्यूनतम स्प्लैश समय फिर भी लागू रहता है)।
  • वेब पर दोनों कुछ नहीं करते - वहाँ कोई स्प्लैश नहीं है।
  • शेल होल्ड को लगभग 6 सेकंड पर सीमित कर देता है, इसलिए जो पेज कभी ready() कॉल नहीं करता वह भी खुल जाता है।

appmintNative.StatusBar appmintNative#

StatusBar.setBackgroundColor({ color }) / StatusBar.setStyle({ style })

उदाहरण

Capacitor के StatusBar प्लगइन के आकार में स्टेटस बार, जो इस पर बना है setStatusBar().

लौटाता है: दोनों के लिए Promise<void>। ज़रूरत: कुछ नहीं।

import { StatusBar } from '@/lib/appmintNative';

// Colour only — the icon style is left alone (the shell picks readable icons on Android).
await StatusBar.setBackgroundColor({ color: '#0B0F1A' });

रंग और आइकन साथ में - setStatusBar() इस्तेमाल करें, जो दोनों एक कॉल में सेट करता है:

import { setStatusBar } from '@/lib/appmintNative';

setStatusBar('#0B0F1A', 'light');   // dark bar, light icons
setStatusBar('#FFFFFF', 'dark');    // light bar, dark icons

बार को ऐप की अपनी लाइट और डार्क थीम के साथ बदलें, Capacitor-शैली की कॉल साथ में इस्तेमाल करके।

import { useEffect } from 'react';
import { StatusBar } from '@/lib/appmintNative';

export function useStatusBarForTheme(dark: boolean) {
  useEffect(() => {
    void StatusBar.setBackgroundColor({ color: dark ? '#0B0F1A' : '#FFFFFF' });
    void StatusBar.setStyle({ style: dark ? 'DARK' : 'LIGHT' });   // 'DARK' = dark bar, light icons
  }, [dark]);
}

ध्यान दें: style Capacitor के हिसाब से चलता है: 'DARK' का मतलब गहरा बैकग्राउंड (हल्के आइकन) है, बाकी सब का मतलब गहरे आइकन। Android ऐप में शेल ब्रिज बार को रंगता है (Android 15+ पर प्लगइन की रंग कॉल कुछ नहीं करती), और setStyle() बार के मौजूदा रंग पर आइकन पलट देता है। वेब पर रंग बन जाता है <meta name="theme-color">.

appmintNative.stopAlarm appmintNative#

stopAlarm(id?) — @/lib/appmintNative

उदाहरण

बजता हुआ अलार्म बंद करता है, उसके Dismiss बटन की तरह। दोहराने वाला अलार्म अपने अगले समय के लिए सेट रहता है।

लौटाता है: boolean - बज रहे अलार्म के अब रुक जाने पर true। id छोड़ने पर जो भी बज रहा हो वही। ज़रूरत: बिल्ड करते समय Access चरण (चरण 4) में 🔔 Alarm clock।

import { stopAlarm, snoozeAlarm } from '@/lib/appmintNative';

export function RingScreen({ id, onClose }: { id: string; onClose: () => void }) {
  return (
    <div className="ring">
      <button onClick={() => { snoozeAlarm(id, 10); onClose(); }}>Snooze 10 min</button>
      <button onClick={() => { stopAlarm(id); onClose(); }}>Stop</button>
    </div>
  );
}

अलार्म बजने पर ऐप जो स्क्रीन खोलता है, उसी से अलार्म रोकें, और उसके खत्म होने के हर तरीके पर प्रतिक्रिया दें:

import { useEffect, useState } from 'react';
import { onAlarm, isAlarmRinging, stopAlarm } from '@/lib/appmintNative';

export function useRingingAlarm() {
  const [ringingId, setRingingId] = useState<string | null>(null);
  useEffect(() => onAlarm((e) => {
    if (e.action === 'open' && e.ringing) setRingingId(e.id);
    else setRingingId(null);   // dismiss, snooze, missed or error: it is no longer ringing
  }), []);
  return {
    ringingId,
    stop: () => { if (isAlarmRinging()) stopAlarm(ringingId ?? ''); },
  };
}

जवाब जाँचें: false का मतलब कुछ बज नहीं रहा था (या यह वेब है), इसलिए यह न कहें कि वह रुक गया:

import { stopAlarm, alarmSupport } from '@/lib/appmintNative';

export function stopWithFeedback(id: string, say: (m: string) => void) {
  if (alarmSupport() === 'unavailable') return say('Alarms only work in the Android app');
  say(stopAlarm(id) ? 'Alarm stopped' : 'That alarm is not ringing');
}

appmintNative.stopSpeaking appmintNative#

stopSpeaking() — @/lib/appmintNative

उदाहरण

टेक्स्ट-टू-स्पीच को अभी रोकता है और बोले जाने के इंतज़ार में जो भी है उसे हटा देता है।

लौटाता है: void। ज़रूरत: कुछ नहीं।

import { useEffect } from 'react';
import { speak, stopSpeaking } from '@/lib/appmintNative';

export function StoryPage({ story }: { story: string }) {
  // Stop reading when the user leaves the page.
  useEffect(() => () => stopSpeaking(), []);

  return (
    <article>
      <p>{story}</p>
      <button onClick={() => { stopSpeaking(); speak(story); }}>Read from the start</button>
      <button onClick={stopSpeaking}>Stop</button>
    </article>
  );
}

यूज़र की चुनी भाषा और स्पीड में लंबा टेक्स्ट ज़ोर से पढ़ें, और एक बटन से रोकें:

import { useState } from 'react';
import { speak, stopSpeaking } from '@/lib/appmintNative';

export function ReadAloud({ text, lang }: { text: string; lang: string }) {
  const [rate, setRate] = useState(1);
  return (
    <div>
      <input type="range" min={0.5} max={2} step={0.1} value={rate} onChange={(e) => setRate(Number(e.target.value))} />
      <button onClick={() => { stopSpeaking(); speak(text, { lang, rate }); }}>Read aloud</button>
      <button onClick={() => stopSpeaking()}>Stop</button>
    </div>
  );
}

जब यूज़र जवाब टाइप करना शुरू करे तो आवाज़ काट दें:

import { stopSpeaking } from '@/lib/appmintNative';

export function ReplyBox({ onSend }: { onSend: (text: string) => void }) {
  return <textarea onFocus={() => stopSpeaking()} onBlur={(e) => onSend(e.currentTarget.value)} />;
}

ध्यान दें: जब कुछ बोला न जा रहा हो तब भी कॉल करना सुरक्षित है। Android ऐप में: नेटिव इंजन का stop। वेब पर: speechSynthesis.cancel().

appmintNative.swipeable appmintNative#

swipeable(el: HTMLElement, opts: SwipeOptions): () => void

उदाहरण

किसी एलिमेंट में स्वाइप जेस्चर जोड़ता है - स्वाइप करके मिटाने वाली पंक्तियाँ, पेजिंग, कार्ड हटाना।

लौटाता है: एक unsubscribe फ़ंक्शन जो एलिमेंट का मूल touch-action भी बहाल करता है। ज़रूरत: कुछ नहीं।

विकल्प: onLeft, onRight, onUp, onDown (कम से कम एक), threshold (px की दूरी, डिफ़ॉल्ट 56), edgeGuard (बाएँ/दाएँ स्क्रीन किनारे से px जहाँ आड़े स्वाइप अनदेखे किए जाते हैं, डिफ़ॉल्ट 24), haptics (स्वाइप पर सिलेक्शन हैप्टिक, डिफ़ॉल्ट true).

हटाने के लिए बाएँ स्वाइप करें, उसी काम के लिए दिखाई देने वाले बटन के साथ:

import { useEffect, useRef } from 'react';
import { swipeable, toast } from '@/lib/appmintNative';

export function MessageRow({ id, text, onDelete, onUndo }: {
  id: string; text: string; onDelete(id: string): void; onUndo(id: string): void;
}) {
  const ref = useRef<HTMLLIElement>(null);
  useEffect(() => {
    if (!ref.current) return;
    return swipeable(ref.current, {
      onLeft: () => { onDelete(id); toast('Deleted'); },
    });
  }, [id, onDelete]);

  return (
    <li ref={ref} className="flex items-center justify-between p-4">
      <span>{text}</span>
      <button onClick={() => onDelete(id)}>Delete</button>
      {/* swipe is never the only way to an action */}
    </li>
  );
}

बाएँ / दाएँ से टैब के बीच पेज बदलें:

import { swipeable } from '@/lib/appmintNative';

declare const panel: HTMLElement;
declare let tab: number;
const off = swipeable(panel, { onLeft: () => { tab += 1; }, onRight: () => { tab -= 1; }, threshold: 72 });
// later: off();

कार्ड को ऊपर स्वाइप करके हटाएँ, और अनमाउंट पर हैंडलर हटा दें:

import { useEffect, useRef } from 'react';
import { swipeable } from '@/lib/appmintNative';

export function Banner({ onClose, children }: { onClose: () => void; children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => (ref.current ? swipeable(ref.current, { onUp: onClose, threshold: 40 }) : undefined), [onClose]);
  return <div ref={ref} className="banner">{children}</div>;
}

ध्यान दें:

  • Pointer Events पर बना है, इसलिए वही कोड टच, पेन और माउस ड्रैग के साथ काम करता है।
  • यह आपके लिए touch-action सेट करता है: क्षैतिज हैंडलर के लिए pan-y (वर्टिकल स्क्रॉलिंग फिर भी चलती है), वर्टिकल के लिए pan-x, दोनों के लिए none।
  • स्वाइप साफ़ तौर पर एक ही दिशा में होना चाहिए (एक अक्ष पर 1.5 गुना ज़्यादा); तिरछे स्क्रॉल नहीं गिने जाते। दूसरी उँगली जेस्चर रद्द कर देती है।
  • एज गार्ड इसलिए है क्योंकि Android का सिस्टम बैक जेस्चर स्क्रीन के किनारों पर अधिकार रखता है।

appmintNative.systemPalette appmintNative#

systemPalette(): Promise<SystemPalette>

उदाहरण

फ़ोन के Material You रंग पढ़ता है (Android 12+ पर वॉलपेपर से बने), ताकि ऐप का एक्सेंट सिस्टम ऐप की तरह यूज़र के वॉलपेपर के साथ चल सके।

लौटाता है: Promise<{ available: boolean; accent1?, accent2?, accent3?, neutral1?, neutral2? }> - हर स्वैच एक टोन ('0', '10', '50', '100' … '900', '1000') को hex रंग से जोड़ता है। Android 12 से नीचे और वेब पर { available: false }। ज़रूरत: कुछ नहीं।

import { systemPalette } from '@/lib/appmintNative';

export async function applyWallpaperAccent() {
  const p = await systemPalette();
  if (!p.available || !p.accent1) return;          // keep the app's own palette
  const root = document.documentElement.style;
  root.setProperty('--accent', p.accent1['600']);
  root.setProperty('--accent-soft', p.accent1['100']);
}

वॉलपेपर के रंग सिर्फ़ तब इस्तेमाल करें जब फ़ोन उन्हें दे, और ऐप के स्क्रीन पर लौटने पर उन्हें फिर पढ़ें:

import { useEffect, useState } from 'react';
import { systemPalette } from '@/lib/appmintNative';

export function useWallpaperAccent(enabled: boolean) {
  const [accent, setAccent] = useState<string | null>(null);
  useEffect(() => {
    if (!enabled) { setAccent(null); return; }
    const read = () => systemPalette().then((p) => setAccent(p.available ? p.accent1?.['600'] ?? null : null));
    void read();
    document.addEventListener('visibilitychange', read);
    return () => document.removeEventListener('visibilitychange', read);
  }, [enabled]);
  return accent;   // null = no wallpaper palette on this device (Android 11 and older, or the web)
}

“मेरे वॉलपेपर से मिलाएँ” स्विच सिर्फ़ तब दिखाएँ जब पैलेट मौजूद हो, “यहाँ नहीं” के जवाब के रूप में { available: false } का इस्तेमाल करके:

import { useEffect, useState } from 'react';
import { can, systemPalette } from '@/lib/appmintNative';

export function WallpaperToggle({ value, onChange }: { value: boolean; onChange: (v: boolean) => void }) {
  const [ok, setOk] = useState(false);
  useEffect(() => { if (can('systemPalette') !== 'none') systemPalette().then((p) => setOk(p.available)); }, []);
  if (!ok) return null;
  return <label><input type="checkbox" checked={value} onChange={(e) => onChange(e.target.checked)} /> Match my wallpaper</label>;
}

नोट: वॉलपेपर के रंग को टेक्स्ट या बटन के लिए इस्तेमाल करने से पहले कंट्रास्ट जाँचें - --accent को आपकी पृष्ठभूमि पर अब भी 3:1 तक पहुँचना चाहिए। इसे विकल्प के रूप में दें ("मेरे वॉलपेपर से मिलाएँ"), ज़बरदस्ती की थीम के रूप में नहीं। ऐप के दोबारा खुलने पर इसे फिर कॉल करें; हो सकता है उपयोगकर्ता ने वॉलपेपर बदल दिया हो।

appmintNative.TextToSpeech appmintNative#

TextToSpeech — @/lib/appmintNative

उदाहरण

Capacitor जैसा TextToSpeech ऑब्जेक्ट (speak / stop), जो speak() वाले ही कोड पर चलता है और stopSpeaking().

लौटाता है: speak({ text, lang?, rate?, pitch? }) → Promise<void> जो तब पूरा होता है जब टेक्स्ट कतार में लग जाए, बोले जाने पर नहीं। stop() → Promise<void>। ज़रूरत: कुछ नहीं।

import { TextToSpeech } from '@/lib/appmintNative';

async function announce(orderNo: number) {
  await TextToSpeech.speak({ text: `Order ${orderNo} is ready`, lang: 'en-US', rate: 1, pitch: 1 });
  // Here the text is queued; it may still be speaking.
}

async function silence() {
  await TextToSpeech.stop();
}

लाइनों की सूची एक के बाद एक बोलकर सुनाएँ; हर speak कतार में लगते ही पूरा हो जाता है, इसलिए इंजन उन्हें क्रम से चलाता है:

import { TextToSpeech } from '@/lib/appmintNative';

export async function readSteps(steps: string[]) {
  await TextToSpeech.stop();   // drop anything still waiting from before
  for (const [i, s] of steps.entries()) {
    await TextToSpeech.speak({ text: `Step ${i + 1}. ${s}`, lang: 'en-US' });
  }
}

ज़ोर से पढ़ने वाला बटन जो स्क्रीन छोड़ने पर बोलना बंद कर देता है:

import { useEffect } from 'react';
import { TextToSpeech } from '@/lib/appmintNative';

export function ArticleReader({ text }: { text: string }) {
  useEffect(() => () => { void TextToSpeech.stop(); }, []);
  return <button onClick={() => void TextToSpeech.speak({ text, rate: 0.9 })}>Listen</button>;
}

नोट: क्योंकि speak जल्दी पूरा हो जाता है, "बोलने के बाद अगला चरण" उस पर न जोड़ें। असली अंत संकेत के लिए window.speechSynthesis के साथ utterance का onend इस्तेमाल करें। ऐप में: शेल का नेटिव Android टेक्स्ट-टू-स्पीच इंजन; वेब पर: speechSynthesis.

appmintNative.textZoom appmintNative#

textZoom.get() / textZoom.getPreferred() / textZoom.set(value)

उदाहरण

ऐप के अंदर टेक्स्ट-साइज़ कंट्रोल। ऐप में यह पेज का टेक्स्ट ज़ूम बदलता है (1 = 100%); वेब पर यह रूट फ़ॉन्ट साइज़ बदलता है, जिससे हर rem-आधारित लेआउट बदलता है।

लौटाता है: get() → Promise<number>। getPreferred() → Promise<number> - यूज़र ने फ़ोन की सेटिंग्स में जो साइज़ चुना (वेब पर 1)। set(value) → Promise<void>; मान 0.5-3 के बीच रखा जाता है। ज़रूरत: कुछ नहीं।

import { textZoom } from '@/lib/appmintNative';

async function bigger() { await textZoom.set((await textZoom.get()) + 0.1); }
async function smaller() { await textZoom.set((await textZoom.get()) - 0.1); }
async function reset() { await textZoom.set(await textZoom.getPreferred()); }

लॉन्च के बीच चुनाव याद रखें:

import { textZoom } from '@/lib/appmintNative';

const saved = Number(localStorage.getItem('textZoom'));
if (saved) await textZoom.set(saved);

export async function setSize(v: number) {
  await textZoom.set(v);
  localStorage.setItem('textZoom', String(v));
}

टेक्स्ट-साइज़ स्टेपर जो मौजूदा मान पढ़ता है, उसे अनुमत सीमा में रखता है, और प्रतिशत में दिखाता है:

import { useEffect, useState } from 'react';
import { textZoom } from '@/lib/appmintNative';

export function TextSizeStepper() {
  const [z, setZ] = useState(1);
  useEffect(() => { textZoom.get().then(setZ); }, []);
  const change = async (d: number) => {
    const next = Math.min(3, Math.max(0.5, Math.round((z + d) * 10) / 10));
    await textZoom.set(next);
    setZ(next);
  };
  return (
    <div>
      <button onClick={() => change(-0.1)}>A-</button>
      <span>{Math.round(z * 100)}%</span>
      <button onClick={() => change(0.1)}>A+</button>
    </div>
  );
}

नोट: ऐप पहले से फ़ोन की फ़ॉन्ट-साइज़ सेटिंग का पालन करता है; यह एक अतिरिक्त, ऐप के अंदर का कंट्रोल है। बड़े साइज़ पर लेआउट आज़माएँ - टेक्स्ट कंट्रोल की तय ऊँचाई कट जाती है।

appmintNative.toast appmintNative#

toast(message: string, opts?: { duration?: 'short' | 'long' }): void

उदाहरण

एक छोटा संदेश दिखाता है जिसके जवाब की ज़रूरत नहीं - “कॉपी हो गया”, “सेव हो गया”, “सूची में जोड़ा गया”।

लौटाता है: कुछ नहीं (भेजो और भूल जाओ)। ज़रूरत: कुछ नहीं।

import { toast, haptic } from '@/lib/appmintNative';

export function SaveButton({ onSave }: { onSave: () => Promise<void> }) {
  return (
    <button
      onClick={async () => {
        await onSave();
        haptic('success');
        toast('Saved');                              // 'short' ≈ 2 s
      }}
    >
      Save
    </button>
  );
}

लंबा संदेश लगभग 3.5 सेकंड रुकता है:

import { toast } from '@/lib/appmintNative';

toast('Your export will be ready in a minute', { duration: 'long' });

Capacitor वाला रूप Toast.show({ text, duration? }) यही करता है और देता है Promise<void>:

import { Toast } from '@/lib/appmintNative';

await Toast.show({ text: 'Added to list' });

ऐसे संदेश के लिए जो यूज़र को ज़रूर दिखना चाहिए, इसकी जगह डायलॉग इस्तेमाल करें:

import { Dialog } from '@/lib/appmintNative';

await Dialog.alert({ title: 'Payment failed', message: 'Your card was declined.' });

ध्यान दें:

  • Android ऐप में Capacitor Toast प्लगइन इसे स्क्रीन के नीचे दिखाता है।
  • बाकी सब जगह यह पेज पर ऐप के अपने रंगों (--bg पर --ink) में बना एक छोटा स्नैकबार है, सेफ़-एरिया इनसेट के ऊपर, role="status" के साथ ताकि स्क्रीन रीडर इसे बोलें।
  • एक समय में सिर्फ़ एक दिखता है; नया टोस्ट पुराने की जगह ले लेता है।
  • टोस्ट को जानबूझकर आसानी से छूट जाने लायक बनाया गया है। जिस एरर पर उपयोगकर्ता को कदम उठाना है, उसके लिए इसे कभी इस्तेमाल न करें।

appmintNative.Toast appmintNative#

import { Toast } from '@/lib/appmintNative'

उदाहरण

appmintNative.toast के साथ दस्तावेज़ित - वहाँ का उदाहरण इसे भी दिखाता है।

एक छोटा संदेश दिखाता है जिसके जवाब की ज़रूरत नहीं - “कॉपी हो गया”, “सेव हो गया”, “सूची में जोड़ा गया”।

लौटाता है: कुछ नहीं (भेजो और भूल जाओ)। ज़रूरत: कुछ नहीं।

import { toast, haptic } from '@/lib/appmintNative';

export function SaveButton({ onSave }: { onSave: () => Promise<void> }) {
  return (
    <button
      onClick={async () => {
        await onSave();
        haptic('success');
        toast('Saved');                              // 'short' ≈ 2 s
      }}
    >
      Save
    </button>
  );
}

लंबा संदेश लगभग 3.5 सेकंड रुकता है:

import { toast } from '@/lib/appmintNative';

toast('Your export will be ready in a minute', { duration: 'long' });

Capacitor वाला रूप Toast.show({ text, duration? }) यही करता है और देता है Promise<void>:

import { Toast } from '@/lib/appmintNative';

await Toast.show({ text: 'Added to list' });

ऐसे संदेश के लिए जो यूज़र को ज़रूर दिखना चाहिए, इसकी जगह डायलॉग इस्तेमाल करें:

import { Dialog } from '@/lib/appmintNative';

await Dialog.alert({ title: 'Payment failed', message: 'Your card was declined.' });

ध्यान दें:

  • Android ऐप में Capacitor Toast प्लगइन इसे स्क्रीन के नीचे दिखाता है।
  • बाकी सब जगह यह पेज पर ऐप के अपने रंगों (--bg पर --ink) में बना एक छोटा स्नैकबार है, सेफ़-एरिया इनसेट के ऊपर, role="status" के साथ ताकि स्क्रीन रीडर इसे बोलें।
  • एक समय में सिर्फ़ एक दिखता है; नया टोस्ट पुराने की जगह ले लेता है।
  • टोस्ट को जानबूझकर आसानी से छूट जाने लायक बनाया गया है। जिस एरर पर उपयोगकर्ता को कदम उठाना है, उसके लिए इसे कभी इस्तेमाल न करें।

appmintNative.torch appmintNative#

import { torch } from '@/lib/appmintNative'

उदाहरण

फ़ोन की फ़्लैशलाइट: torch.on(), off(), toggle(), isOn(), isAvailable()। can('torch') वेब पर 'none' है।

लौटाता है: on()/off() → Promise (code 'unavailable' या 'CAMERA_IN_USE' के साथ रिजेक्ट होता है); toggle() → नई स्थिति; isOn() / isAvailable() → boolean। ज़रूरत: कुछ नहीं, कोई अनुमति नहीं।

import { useEffect, useState } from 'react';
import { can, torch } from '@/lib/appmintNative';

export function LightButton() {
  const [on, setOn] = useState(false);
  const [ok, setOk] = useState(false);
  useEffect(() => { if (can('torch') !== 'none') torch.isAvailable().then(setOk); }, []);
  if (!ok) return null;
  return (
    <button aria-pressed={on} aria-label="Flashlight" onClick={async () => setOn(await torch.toggle())}>
      {on ? 'Light off' : 'Light on'}
    </button>
  );
}

यूज़र के स्क्रीन छोड़ने पर लाइट बंद करें, और जब वह बंद न हो सके तो फ़ोन जो त्रुटि बताए वह दिखाएँ:

import { useEffect, useState } from 'react';
import { torch } from '@/lib/appmintNative';

export function LightSwitch() {
  const [on, setOn] = useState(false);
  const [err, setErr] = useState('');
  useEffect(() => () => { void torch.off().catch(() => {}); }, []);
  const flip = async () => {
    try { setOn(await torch.toggle()); setErr(''); }
    catch (e) {
      const code = (e as { code?: string }).code;
      setErr(code === 'CAMERA_IN_USE' ? 'Close the camera first' : 'No flashlight on this device');
    }
  };
  return <div><button onClick={flip}>{on ? 'Off' : 'On'}</button>{err && <p role="alert">{err}</p>}</div>;
}

लाइट को संकेत के रूप में टिमटिमाएँ, फिर उसे बंद छोड़ दें:

import { can, torch } from '@/lib/appmintNative';

export async function blink(times = 3) {
  if (can('torch') === 'none') return;
  for (let i = 0; i < times; i++) {
    await torch.on();
    await new Promise((r) => setTimeout(r, 200));
    await torch.off();
    await new Promise((r) => setTimeout(r, 200));
  }
}

ध्यान दें: ऐप बंद होने पर बंद हो जाती है। जब ऐप की अपनी कैमरा स्ट्रीम खुली हो, तो उसे ट्रैक की torch कंस्ट्रेंट से जलाएँ।

appmintNative.trackEvent appmintNative#

import { trackEvent } from '@/lib/appmintNative'

उदाहरण

आपके ऐप के किसी पल की सूचना देता है ताकि क्रिएटर के वे इन-ऐप संदेश दिखें जिनका ट्रिगर "जब कोई इवेंट हो" (वही नाम) है। ऐप AppMint Push सेंड सेंटर से क्रिएटर के कैंपेन डाउनलोड करता है और खुद दिखाता है।

लौटाता है: boolean तुरंत: अमान्य नाम (अक्षर, अंक और _ . : -, ज़्यादा से ज़्यादा 64), वेब पर, और बिना AppMint Push वाले ऐप में false। ज़रूरत: Android: बिल्ड करते समय इंटीग्रेट चरण (चरण 3) में AppMint Push चालू हो।

import { trackEvent } from '@/lib/appmintNative';

export async function completeCheckout(cart: { total: number }) {
  await saveOrder(cart);
  trackEvent('checkout_done', { total: cart.total });   // props are optional and not stored
}

ऐसा व्यू बदलाव रिपोर्ट करें जो URL नहीं दिखाता, ताकि “On a page” संदेश उसे देख सकें:

import { trackEvent } from '@/lib/appmintNative';

export function selectTab(tab: 'home' | 'cart' | 'profile') {
  setActiveTab(tab);
  trackEvent('appmint:route', { path: '/' + tab });
}

एक हुक जो किसी स्क्रीन के पहली बार खुलने को ट्रैक करता है:

import { useEffect } from 'react';
import { can, trackEvent } from '@/lib/appmintNative';

export function useTrackOnce(name: string) {
  useEffect(() => {
    if (can('inAppMessages') === 'none') return;
    const key = 'tracked:' + name;
    if (localStorage.getItem(key)) return;
    if (trackEvent(name)) localStorage.setItem(key, '1');
  }, [name]);
}

ध्यान दें: URL बदलाव (पेज लोड, pushState, #hash रूट) पहले से दिख जाते हैं; सिर्फ़ वही रिपोर्ट करें जिसमें पता नहीं बदलता।

appmintNative.transition appmintNative#

transition(update: () => void | Promise<void>, opts?: { type?: 'push' | 'pop' | 'fade' }): Promise<void>

उदाहरण

स्क्रीन बदलाव को नेटिव जैसे एनिमेशन के साथ चलाता है: push नई स्क्रीन को दाईं ओर से सरकाकर लाता है, pop वापस सरकाता है, fade क्रॉस-फ़ेड करता है।

लौटाता है: Promise<void> जो बदलाव और उसके एनिमेशन के बाद पूरा होता है। update आपका स्टेट बदलाव है (एक राउटर navigate, एक setState) और हमेशा लागू होता है। ज़रूरत: कुछ नहीं।

import { useNavigate } from 'react-router-dom';
import { transition, haptic } from '@/lib/appmintNative';

export function NoteRow({ id, title }: { id: string; title: string }) {
  const navigate = useNavigate();
  return (
    <button
      onClick={() => { haptic('light'); void transition(() => navigate(`/note/${id}`)); }}   // push
    >
      {title}
    </button>
  );
}

export function BackButton() {
  const navigate = useNavigate();
  return <button onClick={() => void transition(() => navigate(-1), { type: 'pop' })}>Back</button>;
}

दो टैब के बीच फ़ेड करें:

import { transition } from '@/lib/appmintNative';

declare function setTab(t: 'list' | 'map'): void;
await transition(() => setTab('map'), { type: 'fade' });

टैब बदलने के लिए फ़ेड इस्तेमाल करें, हर टैब के लिए एक हैंडलर में:

import { transition } from '@/lib/appmintNative';

export function Tabs({ tab, setTab }: { tab: 'home' | 'stats'; setTab: (t: 'home' | 'stats') => void }) {
  return (
    <nav>
      <button onClick={() => void transition(() => setTab('home'), { type: 'fade' })} aria-current={tab === 'home'}>Home</button>
      <button onClick={() => void transition(() => setTab('stats'), { type: 'fade' })} aria-current={tab === 'stats'}>Stats</button>
    </nav>
  );
}

ध्यान दें:

  • WebView में View Transitions API हो तो यह उसे इस्तेमाल करता है। वरना अपडेट लागू करके #root पर (या पहले चाइल्ड पर) एक छोटा एंट्री एनिमेशन चलाता है <body>).
  • जब यूज़र ने सिस्टम से कम मोशन माँगा हो, तब यह सिर्फ़ अपडेट लागू करता है - कोई एनिमेशन नहीं।
  • एनिमेशन लगभग 240 ms के होते हैं।

appmintNative.unlock appmintNative#

unlock(title?, subtitle?) — from '@/lib/appmintNative'

उदाहरण

फ़ोन के मालिक से साबित करने को कहता है कि वह मौजूद है - फ़िंगरप्रिंट, चेहरा, या डिवाइस PIN/पासकोड - और आपको बताता है कि उसने किया या नहीं।

लौटाता है: Promise<boolean> - अनलॉक होने पर true; रद्द, फ़ेल, लॉकआउट, या इस डिवाइस पर कुछ भी जाँच न सके तो false। यह कभी एरर नहीं देता। ज़रूरत: ऐप में, बिल्ड करते समय चरण 4 (Access) में फ़िंगरप्रिंट। वेब पर: ब्राउज़र का प्लैटफ़ॉर्म ऑथेंटिकेटर (WebAuthn)।

import { unlock, can } from '@/lib/appmintNative';

export async function openPrivateNotes(show: () => void) {
  if (can('unlock') === 'none') {
    alert('This device has no fingerprint, face or PIN check.');
    return;
  }
  const ok = await unlock('Unlock notes', 'Use your fingerprint or PIN');
  if (ok) show();
}

जहाँ अनलॉक करने का कोई तरीका नहीं है, वहाँ बटन छिपा दें:

import { can } from '@/lib/appmintNative';

const showLockButton = can('unlock') !== 'none';   // 'native' | 'web' | 'none'

हटाने को ताज़ा अनलॉक से सुरक्षित करें, फिर हैप्टिक और टोस्ट से पुष्टि करें:

import { unlock, haptic, toast } from '@/lib/appmintNative';

export async function deleteAccountData(wipe: () => Promise<void>) {
  const ok = await unlock('Confirm delete', 'This removes your saved data');
  if (!ok) { haptic('warning'); toast('Not unlocked, nothing was deleted'); return; }
  await wipe();
  haptic('success');
  toast('Data deleted');
}

ध्यान दें: ऐप में दूसरे रास्ते के तौर पर डिवाइस का PIN, पैटर्न या पासवर्ड चलता है। वेब पर पहली कॉल ब्राउज़र के प्लेटफ़ॉर्म ऑथेंटिकेटर को नामांकित करती है और अनलॉक मानी जाती है। इससे साबित होता है कि डिवाइस का मालिक मौजूद है; यह लॉगिन नहीं है - सर्वर पर रखे डेटा की इकलौती सुरक्षा के तौर पर इसे कभी इस्तेमाल न करें। Android 11 और नए पर यह प्रॉम्प्ट स्क्रीन लॉक वाले हर फ़ोन पर खुलता है - सिर्फ़ PIN वाले फ़ोन पर भी, और PIN से अनलॉक होता है; Android 10 और पुराने पर पहले फ़िंगरप्रिंट सेव होना चाहिए, वरना बिना प्रॉम्प्ट के false जवाब मिलता है।

appmintNative.updates appmintNative#

updates.supported() / download() / stage() / confirm() / revertIfUnconfirmed() / current()

उदाहरण

वेब-बंडल अपडेट के लिए पेज-साइड अपडेटर। पेज कोड को इसकी ज़रूरत नहीं: AppMint ऐप में Remote Update Android ऐप खुद करता है, और updates.supported() हर जगह false लौटाता है।

लौटाता है: supported() → boolean (अभी हमेशा false); download(url, sha256, version, binaryVersion) → Promise<boolean> (असमर्थित रहने तक false); stage(dir, version, binaryVersion) → Promise<boolean>; confirm() → void; revertIfUnconfirmed(binaryVersion) → Promise<boolean>; current() → { path, version, binaryVersion } | null। ज़रूरत: आपके ऐप के लिए कुछ नहीं - अपडेट AppMint में Remote Update से प्रकाशित होते हैं।

ऐप कोड में इसका एकमात्र सुरक्षित इस्तेमाल जाँचना है, और बंद होने पर कुछ न करना:

import { updates } from '@/lib/appmintNative';

if (updates.supported()) {
  // Not reached today: the Android app's own updater owns the whole update lane.
  const staged = await updates.download(
    'https://example.com/bundle-1.2.0.zip',
    'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',  // sha256, required
    '1.2.0',
    '1',
  );
  console.log('update staged:', staged);
}

दिखाएँ कि कौन-सा बंडल चल रहा है (सेटिंग्स की "About" पंक्ति में):

import { updates } from '@/lib/appmintNative';

const active = updates.current();          // null = no page-staged bundle (always, today)
const label = active ? `Content ${active.version}` : 'Built-in content';

सेटिंग्स की पंक्ति में चल रहा कंटेंट वर्ज़न दिखाएँ, और जब पेज में कोई स्टेज्ड बंडल न हो तो बिल्ट-इन लेबल:

import { useMemo } from 'react';
import { updates } from '@/lib/appmintNative';

export function AboutRow() {
  const label = useMemo(() => {
    const c = updates.current();
    return c ? `Content ${c.version} (app ${c.binaryVersion})` : 'Built-in content';
  }, []);
  return <p>{label}</p>;
}

ध्यान दें:

  • Android ऐप में शेल का Remote Update मैनेजर अपडेट डाउनलोड करता है, जाँचता है और चलाता है, और लोड न होने वाले बंडल को वापस कर देता है। पेज शेल खुद चलाता है, इसलिए पेज कोड से रखा गया बंडल कभी नहीं दिखेगा - इसीलिए supported() है false.
  • वेब पर अपडेट करने को कुछ नहीं है; पेज वही है जो सर्वर भेजता है।
  • अपडेट सिर्फ़ HTML, CSS और JavaScript बदल सकते हैं; नए नेटिव फ़ीचर के लिए फिर भी नया ऐप बिल्ड चाहिए।

appmintNative.watchIdle appmintNative#

import { watchIdle } from '@/lib/appmintNative'

उदाहरण

ऐप को बताता है कि उपयोगकर्ता कब निष्क्रिय हुआ या स्क्रीन लॉक की, और कब वापस आया (can('idle'))। पहली बार इसे टैप से शुरू करें - अनुमति के अनुरोध के लिए टैप चाहिए।

लौटाता है: रोकने वाले फ़ंक्शन का Promise। कॉलबैक को शुरू में और हर बदलाव पर { user: 'active' | 'idle', screen: 'locked' | 'unlocked' } मिलता है। code 'permission-denied' या 'unavailable' के साथ रिजेक्ट होता है। ज़रूरत: कुछ नहीं, कोई Android अनुमति नहीं।

import { useRef } from 'react';
import { can, watchIdle } from '@/lib/appmintNative';

export function AwayStatus({ setAway }: { setAway: (away: boolean) => void }) {
  const stop = useRef<(() => void) | null>(null);
  if (can('idle') === 'none') return null;
  return (
    <button onClick={async () => {
      stop.current?.();
      stop.current = await watchIdle((s) => setAway(s.user === 'idle' || s.screen === 'locked'), { thresholdMs: 5 * 60000 });
    }}>Show me as away when idle</button>
  );
}

अनमाउंट पर दूर रहने की स्थिति दिखाएँ और देखना बंद करें; अस्वीकार की गई अनुमति इसके साथ बताई जाती है code:

import { useEffect, useRef, useState } from 'react';
import { can, watchIdle } from '@/lib/appmintNative';

export function useIdleWatch() {
  const stop = useRef<(() => void) | null>(null);
  const [state, setState] = useState<'off' | 'active' | 'idle' | 'denied'>('off');
  useEffect(() => () => stop.current?.(), []);
  // Call this from a tap: the first permission request needs a gesture.
  const start = async () => {
    if (can('idle') === 'none') return;
    try {
      stop.current = await watchIdle((s) => setState(s.user === 'idle' || s.screen === 'locked' ? 'idle' : 'active'));
    } catch (e) {
      if ((e as { code?: string }).code === 'permission-denied') setState('denied');
    }
  };
  return { state, start };
}

उपयोगकर्ता के दूर रहने पर पोलिंग टाइमर रोकें और वापस आने पर रिफ़्रेश करें:

import { watchIdle } from '@/lib/appmintNative';

export async function pausePollingWhenIdle(refresh: () => void) {
  let timer: ReturnType<typeof setInterval> | undefined = setInterval(refresh, 30000);
  const stop = await watchIdle((s) => {
    const away = s.user === 'idle' || s.screen === 'locked';
    if (away && timer) { clearInterval(timer); timer = undefined; }
    if (!away && !timer) { refresh(); timer = setInterval(refresh, 30000); }
  });
  return () => { stop(); if (timer) clearInterval(timer); };
}

ध्यान दें: सीमा कम से कम एक मिनट है (स्पेक की न्यूनतम सीमा)।

appmintNative.widget appmintNative#

widget.set(card) / widget.clear() / widget.placedCount() / widget.requestPin()

उदाहरण

ऐप का होम-स्क्रीन विजेट भरता है - एक कार्ड जिसमें शीर्षक, एक बड़ा मान, छह पंक्तियों तक, और एक टैप जो ऐप को किसी रूट पर खोलता है।

लौटाता है: set(card) → Promise<boolean> (सेव होने पर true); clear() → Promise<void>; placedCount() → Promise<number> (उपयोगकर्ता ने कितनी कॉपी रखीं); requestPin() → Promise<boolean> (लॉन्चर का "विजेट जोड़ें" फ़्लो खुलने पर true)। इनमें से कोई रिजेक्ट नहीं होता। ज़रूरत: Android ऐप में कुछ नहीं (विजेट हमेशा बिल्ड का हिस्सा होता है)।

होम स्क्रीन पर यूज़र जो भी बदलाव देखना चाहेगा, उसके बाद कार्ड अपडेट करें:

import { can, widget } from '@/lib/appmintNative';

export async function updateTodayWidget(tasks: { title: string; done: boolean }[]) {
  if (can('widget') === 'none') return;          // on the web
  const open = tasks.filter((t) => !t.done);
  await widget.set({
    title: 'Today',
    value: `${open.length} left`,
    lines: open.slice(0, 6).map((t) => '• ' + t.title),
    route: '/today',                              // a tap opens the app here
  });
}

एक "होम स्क्रीन पर जोड़ें" बटन:

import { can, widget, toast } from '@/lib/appmintNative';

export function AddWidgetButton() {
  if (can('widget') === 'none') return null;
  return (
    <button
      onClick={async () => {
        if (!(await widget.requestPin())) toast('Long-press your home screen, tap Widgets, and pick this app');
      }}
    >
      Add widget to home screen
    </button>
  );
}

यूज़र के साइन-आउट करने पर कार्ड खाली करें, और दिखाएँ कि होम स्क्रीन पर कितनी कॉपी हैं:

import { useEffect, useState } from 'react';
import { can, widget } from '@/lib/appmintNative';

export function WidgetStatus({ signedIn }: { signedIn: boolean }) {
  const [placed, setPlaced] = useState(0);
  useEffect(() => {
    if (can('widget') === 'none') return;
    if (!signedIn) void widget.clear();
    widget.placedCount().then(setPlaced);
  }, [signedIn]);
  return <p>{placed === 0 ? 'Not on your home screen yet' : `On your home screen (${placed})`}</p>;
}

ध्यान दें:

  • कार्ड डिवाइस पर सेव होता है, रीबूट के बाद भी रहता है, और रखी गई हर कॉपी रिफ़्रेश होती है। 0 का placedCount() यानी अभी होम स्क्रीन पर कोई कॉपी नहीं है।
  • कार्ड फ़ील्ड: title (खाली = ऐप का नाम), value (accent में बड़ा दिखता है, खाली होने पर छिपा), lines (अधिकतम 6), route (डिफ़ॉल्ट /), और bg / ink / accent। रंग CSS टोकन --bg, --ink, --accent पर डिफ़ॉल्ट होते हैं; वे ऐसे रंग होने चाहिए जिन्हें Android पढ़ सके, जैसे #RRGGBB - hsl()/oklch() मान डिफ़ॉल्ट रंगों पर लौट जाता है।
  • टैप करने पर ऐप appmint-shortcut://<package><route> के साथ खुलता है; इसे पढ़ें launchUrl() / onDeepLink().
  • requestPin() को Android 8+ और इसे समर्थन देने वाला लॉन्चर चाहिए। clear() खाली "अभी दिखाने को कुछ नहीं" कार्ड दिखाता है।
  • सिर्फ़ Android ऐप: वेब पर can('widget') का मान 'none' होता है, जहाँ set() और requestPin() लौटाते हैं false.

हर डॉक्स बिल्ड पर ऐप रनटाइम और उसकी उदाहरण फ़ाइलों से बनाया जाता है। Markdown में पढ़ें · सभी फ़ैमिली.

2026-10-03 को शिप किए गए ब्रिज से मिलाकर जाँचा गया।