Aplicația știa deja să facă totul. Dar trebuia să facă lucrurile și când nu era deschisă.

De ce widgets

Rolog e o aplicație pe care o deschizi de câteva ori pe zi, adaugi o călătorie, scanezi un bon, verifici o alertă. Dar între aceste momente, telefonul stă pe birou cu ecranul de blocare vizibil. Am vrut ca informația relevantă să fie acolo fără să deschizi aplicația.

iOS oferă două suprafețe relevante: widget-uri pe home screen și Live Activities în Dynamic Island și pe lock screen. Am construit pentru ambele.

Cinci widget-uri și o activitate live

Stats Widget

Widget-ul principal. Afișează un sumar al flotei: km luna curentă, cost combustibil, număr de călătorii. Există în trei mărimi: small, medium și large, fiecare cu nivelul potrivit de detaliu. Small-ul arată doar km-ul și costul. Large-ul adaugă graficul pe ultimele 3 luni.

Alerts Widget

Alertele de expirare, ITP, RCA, CASCO, Rovinieta și alertele de revizie periodică sunt exact genul de informație pe care vrei să o vezi fără să deschizi nimic. Widget-ul de alerte arată ce expiră curând și când e scadentă următoarea revizie, cu zile rămase și culori de status: roșu pentru urgent, portocaliu pentru atenție, verde pentru ok.

Quick Action Widgets

Trei widget-uri mici, fiecare cu o singură acțiune:

Scan: deschide scannerul OCR direct. Un tap, camera pornește, scanezi bonul.

Add Trip: deschide formularul de călătorie nouă.

GPS Trip: pornește înregistrarea GPS.

Fiecare folosește deep links (rolog://scan, rolog://add-trip, rolog://gps-trip) ca să navigheze direct la acțiunea respectivă, fără ecrane intermediare.

Sincronizarea datelor cu widget-urile

Asta a fost partea tehnică interesantă. Widget-urile iOS rulează într-un proces separat, nu au acces la baza de date a aplicației. Soluția: App Group.

La fiecare salvare în aplicație (călătorie nouă, alimentare, alertă modificată), copiez un snapshot al datelor relevante din Documents în containerul App Group. Widget-urile citesc de acolo. E o copie unidirecțională, aplicația scrie, widget-urile doar citesc.

Am încercat inițial să mut întreaga bază de date SwiftData în App Group. A fost o idee proastă. Migrarea store-ului existent riscă pierderea datelor, și SwiftData are probleme documentate cu acces concurent din două procese pe același fișier. Snapshot-ul e simplu, sigur, și suficient.

Live Activity, călătoria cu GPS pe Dynamic Island

Când pornești o călătorie cu GPS, în Dynamic Island apare o activitate live: numele locației de plecare, destinația, distanța parcursă și timpul scurs. Toate se actualizează în timp real.

Lock screen-ul arată aceeași informație într-un card mai detaliat, cu un progress bar vizual în stilul Uber: un punct albastru de plecare, o mașinuță care avansează pe traseu, și un punct alb de destinație.

Am reprodus același design și în aplicație. Când GPS-ul e activ, hero card-ul din ecranul principal se transformă: în loc de sumarul lunar, afișează distanța parcursă ca număr mare central, ruta (plecare -> destinație), timpul scurs, ora de start, și același progress bar cu mașinuța. Un indicator GPS roșu pulsează în colțul dreapta jos.

Tranziția între cele două stări (sumar lunar și GPS activ) e animată, conținutul vechi face fade out și cel nou apare cu un slide-up subtil.

Detaliul tehnic care a contat

Un lucru care m-a blocat: operațiile de file I/O pe main thread. Serviciul de backup iCloud era marcat ca @MainActor, ceea ce însemna că orice copiere de fișiere (store, WAL, date externe) bloca thread-ul principal. Rezultat: la o reinstalare cu restore din iCloud, ecranul de restaurare nu se afișa deloc, vedeai doar fundalul gol până când restore-ul termina.

Fix-ul a fost să mut operațiile de copiere într-un Task.detached thread-ul principal rămâne liber pentru UI, fișierele se copiază în background. Simplu în retrospectivă, dar m-a învățat o lecție: @MainActor e pentru stare UI, nu pentru I/O.

Secțiunea Learn, educație fiscală în aplicație

Rolog generează foi de parcurs și FAZ-uri. Dar mulți utilizatori nu știu de ce le generează. Ce înseamnă deductibilitate 50% vs 100%? De ce trebuie CUI-ul pe bon? Ce verifică ANAF la un control?

Am construit o secțiune educațională completă, organizată în trei categorii:

Învață despre aplicație

15 lecții despre funcțiile Rolog, cum adaugi o călătorie, cum funcționează GPS-ul, cum setezi alertele, cum generezi rapoarte. Fiecare lecție e un set de carduri swipeable cu progress bar. La final, un ecran "Ai terminat!" cu checkmark animat.

Ghid fiscal auto

6 lecții de legislație fiscală românească aplicată pe cheltuieli auto:

Deductibilitatea cheltuielilor auto: regula de 50%, cum ajungi la 100%, ce cheltuieli intră.

Foaia de parcurs: ce trebuie să conțină, cât timp o păstrezi, greșeli frecvente.

Fișa Activității Zilnice (FAZ): consum normat vs efectiv, de ce contează.

Ce verifică ANAF la un control: documente solicitate, ce compară inspectorii.

TVA pe cheltuielile auto: reguli pentru combustibil, service, achiziție mașină.

Mașina personală folosită pe firmă: contract de comodat, reguli PFA vs SRL.

Conținutul e bilingv (română și engleză), scris în limbaj accesibil, fără jargon juridic. Nu înlocuiește un contabil, dar răspunde la întrebările pe care le-ai pune contabilului.

Sfaturi pentru flotă

5 lecții practice:

Conducere economică: cum reduci consumul cu 20-30% schimbând obiceiuri.

Întreținerea care economisește bani: anvelope, revizii, semnale de consum excesiv.

Reducerea costurilor de flotă: monitorizare, planificare rute, când înlocuiești o mașină.

Sezonul rece: de ce crește consumul iarna, checklist pregătire, sfaturi de condus.

Siguranța flotei: documente la zi, proces verbal predare-primire, ce faci la accident.

Designul secțiunii Learn

Ecranul principal Learn are trei carduri mari, fiecare cu o iconiță decorativă în dreapta sus, titlul în stânga jos și o descriere scurtă. Stilul oferă spațiu generos, focalizare pe titlu, fără elemente inutile.

Fiecare categorie deschide un grid de 2 coloane cu topic cards. Fiecare card are un număr mare de ordine în dreapta sus (care iese din card ca un element decorativ) și titlul în stânga jos. Numerele rămân fixe și la căutare, dacă filtrăm și apare doar topicul 4, el rămâne cu numărul 4.

Ce am învățat din aceste funcționalități

Widget-urile sunt o extensie a aplicației, nu o aplicație separată. Cel mai greu nu a fost să construiesc widget-urile, a fost să sincronizez datele între aplicație și widget-uri fără să risc integritatea bazei de date. App Group + snapshot unidirecțional e soluția care funcționează fără surprize.

Live Activities trebuie să reflecte ce vezi în aplicație. Dacă design-ul din Dynamic Island e complet diferit de ce vezi când deschizi aplicația, utilizatorul simte o deconectare. Am reprodus același layout (plecare, destinație, progress bar cu mașina) în ambele locuri.

Conținutul educațional nu e un "nice to have". Utilizatorii care înțeleg de ce completează foi de parcurs folosesc aplicația mai consistent decât cei care doar completează câmpuri. Secțiunea Learn nu e documentație, e context. Și contextul face diferența între un utilizator care deschide aplicația o dată pe săptămână și unul care o deschide zilnic.

Permisiunile iOS trebuie cerute la momentul potrivit. La început, ceream permisiunea pentru notificări la login, inclusiv pentru utilizatori noi care nu aveau nicio alertă configurată. Promptul apărea din senin, fără context. Acum îl cerem doar când utilizatorul configurează prima alertă sau primul reminder. Rata de acceptare e mai mare când utilizatorul înțelege de ce i se cere permisiunea.

Rolog cu widgets, Live Activities și ghid fiscal e disponibil în App Store. 30 de zile gratuite, funcțiile principale incluse. Dacă ai întrebări despre implementare, App Group sync, Live Activities, sau cum să structurezi conținut educativ în SwiftUI, scrie-mi la contact@lorinbute.com.