Aplicație pentru ceas inteligent

4league vine cu o aplicație-companion pentru Wear OS și watchOS. Pe un cont PRO, arbitrii, managerii de echipă și organizatorii pot porni meciuri și pot înregistra goluri, cartonașe, faulturi, schimbări și greșeli personale direct de la încheietură — fiecare acțiune este pusă în coadă pe ceas și retrimisă până când telefonul o confirmă. Utilizatorii de pe planul gratuit, precum și oricine se află pe un meci pe care nu îl poate edita, primesc totuși o vizualizare live doar-citire: scor, cronometru și evenimente. Fanii, la rândul lor, primesc pe orice plan un widget cu poziția în clasament, care arată unde se situează echipele lor urmărite în fiecare competiție în care joacă.

ℹ️
Doar companion, nu de sine stătătoare

Aplicația de ceas este un companion al aplicației de telefon 4league — toată autentificarea, apelurile API și scrierile de date trec prin telefon. Dacă telefonul este în afara razei Bluetooth sau aplicația 4league este închisă forțat, acțiunile sunt puse în coadă pe ceas și trimise în momentul în care conexiunea revine (vezi Coadă offline), în loc să eșueze pur și simplu.

Prezentare generală

Aplicația-companion pentru ceas inteligent are două capabilități principale:

  • Înregistrare live a meciului (PRO) — disponibilă utilizatorilor cu drepturi de editare a meciului (organizatori, arbitri, manageri de echipă) al căror cont de telefon are un plan PRO activ. Pornește un meci, adaugă goluri / cartonașe / faulturi / schimbări / greșeli, revizuiește și editează evenimentele trecute și încheie meciul — totul de pe ceas, cu scorul final, golurile, cartonașele și pasele decisive derivate automat. Vezi cerința PRO mai jos pentru ce vede în schimb un utilizator de pe planul gratuit.
  • Widget cu poziția în clasament — disponibil fiecărui utilizator, pe orice plan. O listă la o privire cu fiecare competiție în care joacă echipele tale urmărite, cu poziția curentă, punctele și o săgeată zilnică ▲/▼ față de instantaneul zilei precedente.

Ceasul nu deține o sesiune proprie și nu apelează niciodată backendul direct — fiecare cerere este retransmisă prin telefonul asociat, care face apelul API real, autentificat, și răspunde prin Bluetooth.

Cerința PRO

⚠️
Înregistrarea este o funcție PRO

Pornirea/pauzarea/încheierea unui meci și înregistrarea oricărui eveniment (goluri, cartonașe, faulturi, schimbări, greșeli) de pe ceas necesită ca contul de telefon asociat cu ceasul să aibă un plan PRO activ — nu planul echipei, nu planul competiției. Nu există nicio modalitate de a verifica acest lucru de pe un cont gratuit chiar pe ceas; fiecare încercare este refuzată cu același motiv.

Ce vede în schimb pe ceas un utilizator de pe planul gratuit care are drepturi de editare a meciului:

  • Lista de meciuri, cronometrul live, scorul curent și lista de evenimente — complet vizibile, doar-citire.
  • Un mesaj de blocare pe ecranul live („PRO — fă upgrade în aplicația de telefon”) în locul comenzilor de înregistrare.
  • Niciun fel de flux de cumpărare pe ceas — upgrade-ul trebuie făcut în aplicația de telefon 4league. Vezi Plăți & abonamente și Abonează-te la PRO & anulează.

În culise, telefonul asociat îi spune ceasului dacă utilizatorul autentificat este PRO de fiecare dată când trimite lista de meciuri, iar telefonul refuză independent fiecare acțiune de înregistrare venită de la un cont non-PRO, chiar dacă o versiune mai veche a aplicației de ceas ar mai afișa cumva comenzile — telefonul, niciodată ceasul, este punctul de aplicare a regulii.

Platforme

Platformă Stack tehnologic Țintă Pachet / Bundle
Wear OS Kotlin, Jetpack Compose for Wear, Material3 for Wear, Wearable Data Layer API Wear OS 3+ (API 30+) com.nativeuplab.fourleague
watchOS Swift, SwiftUI, WatchConnectivity watchOS 9+ com.nativeuplab.fourleague.watchkitapp

Ambele platforme includ acum fluxul complet de înregistrare live a meciului. Cele două diferențe rămase, intenționate, sunt aspectul ecranului live (Wear OS folosește un pager glisabil pentru Acțiuni/Greșeli; watchOS ajunge la aceleași ecrane printr-un meniu în loc de glisare) și widgetul de clasament, care este doar pe Wear OS. Vezi tabelul de paritate a funcțiilor mai jos.

Paritatea funcțiilor

Ambele aplicații de ceas împart același backend, aceeași condiție PRO și același tipar cu telefonul asociat ca sursă de adevăr.

Funcție Wear OS watchOS
Listă de meciuri
Meci live (scor / cronometru / pauză+reluare)
Start meci / Încheie meciul cu scor final derivat
Introducere gol (marcator + pasă decisivă)
Introducere cartonaș / fault / schimbare
Contor de greșeli — a 3-a pagină a pagerului live — Acțiuni → Greșeli
Listă de evenimente (revizuire / editare / ștergere) — Acțiuni → Evenimente
Coadă offline
Pager glisabil pe ecranul live (scor → acțiuni → greșeli) ⚠ Aceleași ecrane, accesate printr-un meniu în loc de glisare
Widget de clasamente

Asociere & sincronizarea sesiunii

Instalarea aplicației de ceas 4league din Play Store (Wear OS) sau App Store (watchOS) este suficientă — nu există o autentificare separată pe ceas. Aplicația de telefon detectează un ceas asociat cu aplicația-companion instalată și trimite datele meciurilor și statusul PRO al contului prin Wearable Data Layer / WatchConnectivity.

1

Instalează aplicația de ceas

Din magazinul companion al telefonului, sau direct pe ceas dacă utilizatorul caută „4league" pe un dispozitiv Wear OS.

2

Deschide aplicația de telefon 4league

Autentifică-te pe telefon ca de obicei. Imediat ce telefonul detectează un ceas accesibil, poate răspunde cererilor ceasului pentru meciuri și clasamente.

3

Deschide aplicația de ceas

Ecranul de așteptare se rezolvă imediat ce telefonul răspunde. Meniul principal oferă apoi Meciuri și Clasamente.

Ecrane meci live

Când utilizatorul autentificat pe telefon are drepturi de editare a meciului (organizator, arbitru sau manager de echipă) și este pe un plan PRO activ, ceasul expune fluxul complet de înregistrare live a meciului. Oricine altcineva vede aceleași informații — scor/cronometru/evenimente — doar-citire, conform cerinței PRO de mai sus.

Navigare Wear OS

Wear OS
├── WaitingScreen — pre-sesiune
├── MatchListScreen — meciuri viitoare/în desfășurare
│   └── LiveMatchScreen — un pager glisabil cu 3 pagini odată ce meciul e live
│       ├── Pagina 1: scor / cronometru — apasă scorul pentru a deschide GoalEntryScreen
│       ├── Pagina 2: LiveActionsPage — Cartonaș / Fault / Schimbare / Evenimente
│       └── Pagina 3: MistakesPage — contorul de greșeli
└── RankingsScreen — widgetul cu poziția în clasament (toți utilizatorii)
    └── RankingDetailScreen — Top 5 al competiției apăsate

Navigare watchOS

watchOS
├── WaitingView — pre-sesiune
├── MatchListView — meciuri viitoare/în desfășurare
│   └── LiveMatchView — scor, cronometru, Start/Încheiere
│       └── ActionMenuView — Cartonaș / Fault / Schimbare / Greșeli / Evenimente
│           ├── CardEntryView / FoulView / SubstitutionView / GoalEntryView
│           ├── MistakesView — contorul de greșeli
│           └── EventListView — revizuire / editare / ștergere
└── (fără ecrane de Clasamente pe watchOS)

watchOS ajunge la fiecare dintre acestea prin lista din ActionMenuView, nu prin pagerul glisabil al Wear OS — aceleași ecrane, altă formă de navigare. watchOS nu redă încă nici contextul phone/error pe care Wear OS îl arată pe ecranul său de așteptare (care telefon răspunde, dacă este autentificat).

Lista de meciuri conține doar meciurile pe care utilizatorul autentificat are drepturi de editare. Aplicația de telefon este sursa de adevăr pentru acea filtrare — ea calculează lista pe baza sesiunii live a utilizatorului și împinge payload-ul rezultat către ceas, astfel încât ceasul însuși nu face verificări de permisiuni și nu interoghează backendul pentru datele meciurilor.

Restricționarea pe roluri

Aplicația de ceas nu conține nicio logică de verificare a rolurilor — este un client subțire. Telefonul (și backendul) decid ce se împinge: fanii și urmăritorii văd doar widgetul de Clasamente (doar Wear OS — watchOS nu are ecran de clasamente); doar utilizatorii cu drepturi de editare pentru un anumit meci (organizator, arbitru sau manager de echipă) primesc acel meci în lista de pe ceas, și doar când acel utilizator este și PRO poate înregistra ceva pe el (vezi cerința PRO). Endpointul de clasamente filtrează în plus, pe server, competițiile cu visibility === 0 sau !onboardingDone (vezi backend/controllers/Competition.js), astfel încât competițiile private sau nepublicate nu ajung niciodată la încheietură.

Contor de greșeli

O evidență a greșelilor personale stă alături de goluri, cartonașe și faulturi — pe Wear OS este a treia pagină a pagerului meciului live (glisează spre stânga de două ori din pagina scorului); pe watchOS se ajunge la ea din meniul de acțiuni.

  • Două coloane: echipa gazdă în stânga și echipa oaspete în dreapta.
  • Antetul fiecărei echipe arată totalul curent al acelei echipe — fiecare greșeală atribuită echipei în ansamblu, plus fiecare greșeală atribuită unuia dintre jucătorii ei, contează în total.
  • Apăsarea pe rândul unui jucător adaugă o greșeală acelui jucător anume.
  • Apăsarea pe antetul echipei îți permite să atribui o greșeală întregii echipe sau să alegi în schimb un jucător din formație.
⚠️
Resetarea este pe fiecare parte și este distructivă

Fiecare echipă are propriul buton Resetează, protejat de un pas de confirmare, care șterge doar greșelile acelei părți pentru acest meci — nu atinge niciodată evidența celeilalte echipe. Resetarea șterge acele înregistrări de greșeli și pe telefon, și pe server, nu doar pe ceas, deci nu există undo dincolo de re-adăugarea lor manuală. Butonul este dezactivat cât timp acea parte are deja zero greșeli.

Greșelile sunt singurul tip de eveniment fără ecran dedicat de editare — pentru a corecta o apăsare greșită, șterge intrarea din lista de evenimente și adaug-o din nou, sau folosește Resetarea pe partea respectivă pentru un început curat.

Coadă offline

Fiecare acțiune făcută pe ceas — nu doar cele făcute cât timp e vizibil deconectat — este mai întâi pusă în coadă local și este ștearsă doar după ce telefonul confirmă că a ajuns. Asta îi permite unui arbitru să pornească un meci, să bage telefonul în buzunar și să gestioneze tot jocul de la încheietură, fără să-și facă griji pentru o legătură Bluetooth căzută.

  • Un gol apăsat în afara razei este marcat cu ceasul propriu al dispozitivului din momentul apăsării, astfel încât, la revenirea conexiunii, ajunge tot la minutul corect al meciului — nu la minutul la care s-a întâmplat să îl primească telefonul.
  • Coada se rejoacă de la cel mai vechi și se oprește la primul refuz, așa că ordinea capturării este mereu păstrată; un element trimis cu câteva momente în urmă este sărit, în caz că îi este încă pe drum confirmarea.
  • O acțiune din coadă deja vizibilă în lista de evenimente a serverului (potrivită după echipă, jucător, tip și un timestamp apropiat) este retrasă automat — o confirmare pierdută nu poate produce niciodată un gol duplicat la retrimitere.
  • După mai multe respingeri la rând, o intrare încetează să se retrimită automat și este lăsată arbitrului spre rezolvare manuală.
  • Ecranul live arată un număr de acțiuni în așteptare ori de câte ori ceva este încă în coadă, iar un eveniment din coadă apare în lista de evenimente la minutul lui corect, marcat ca neconfirmat încă.

Încheierea meciului salvează scorul final

Apăsarea pe Încheie pe ceas termină acum meciul exact așa cum o face încheierea de pe telefon: backendul derivă scorul final, golurile și cartonașele fiecărui jucător și pasa decisivă a fiecărui gol direct din evenimentele înregistrate în timpul meciului live și le salvează la fel cum este salvat un meci încheiat de pe telefon.

ℹ️
Aceasta închide o lacună reală, cunoscută anterior

Un meci încheiat de pe ceas putea înainte să se închidă fără niciun scor înregistrat, pentru că ceasul nu are cum să construiască singur payload-ul complet de salvare al telefonului. Backendul derivă acum acel payload din evenimentele live înregistrate ale meciului, așa că un meci încheiat de pe ceas nu mai este un caz special, fără scor.

Revizuirea, editarea & ștergerea evenimentelor

Evenimentele trecute ale meciului live pot fi revizuite de pe ceas — pe Wear OS din LiveActionsPage, pe watchOS din Acțiuni → Evenimente — cele mai noi primele, fiecare arătând minutul, tipul și jucătorul implicat.

  • Editarea redeschide ecranul de introducere corespunzător (gol, cartonaș, fault, schimbare) precompletat cu valorile acelui eveniment; salvarea actualizează evenimentul existent în loc să creeze unul nou.
  • Ștergerea elimină evenimentul. Ștergerea unui gol elimină și pasa decisivă asociată — cele două nu rămân niciodată pe jumătate șterse.

Widget poziție în clasament

Widgetul de clasamente este o cartelă la o privire pentru fiecare competiție în care joacă o echipă urmărită — vizibil pe orice plan (Gratuit, PRO Jucător, PRO, Enterprise). Este gândit astfel încât un fan care urmărește trei echipe în cinci competiții să poată vedea toate cele cinci poziții dintr-o singură derulare, fără să deschidă telefonul.

Ce vede utilizatorul

Fiecare cartelă de pe ecranul de Clasamente arată:

  • Numele organizației (dacă competiția aparține uneia), ca antet mic.
  • Numele echipei — mare, îngroșat.
  • Poziția curentă (de ex. „#4”) și punctele din acea competiție.
  • O săgeată de variație pe partea dreaptă:
    • ▲ verde — echipa a urcat față de instantaneul zilei precedente.
    • ▼ roșu — echipa a coborât.
    • — gri — poziție neschimbată față de ieri.
    • · estompat — încă nu există un instantaneu anterior (prima rulare sau competiție proaspăt publicată). Redat intenționat ca stare fără săgeată; zero și null sunt stări diferite.
ℹ️
Convenția de semn a variației

Backendul emite delta = priorRank − currentRank. Pozitiv înseamnă că echipa a urcat poziții (de ex. 5 → 3 dă delta = +2 → ▲ verde). Aceasta corespunde convenției de semn folosite de notificarea push rankingPositionChange, așa că săgeata pe care utilizatorul a văzut-o în notificare corespunde săgeții de pe cartela ceasului.

Indiciul de date învechite

Dacă ultima preluare reușită este mai veche de 5 minute cât timp ecranul de Clasamente este deschis, deasupra listei apare o mică linie galbenă „Actualizat acum >5m”. Ecranul reevaluează vechimea la fiecare 30 de secunde cât timp este vizibil — cronometrul rulează doar după primul răspuns, așa că nu consumă baterie înainte ca utilizatorul să aibă vreo dată.

Starea goală

Dacă utilizatorul nu urmărește nicio echipă, sau competiția fiecărei echipe urmărite este privată/nepublicată, ecranul arată „Niciun clasament disponibil” plus un buton manual de Reîmprospătare. Competițiile private nu sunt afișate niciodată pe ceas utilizatorilor care nu le pot vedea.

Endpointul de backend

Ecranul de Clasamente este susținut de un singur endpoint — GET /competition/watch-rankings (acceptă și POST pentru clienții care preferă să trimită lista de echipe în body). Acesta primește ID-urile echipelor urmărite ca parametru de query repetat teamIds sau ca array în body, parcurge competițiile fiecărei echipe urmărite, elimină tot ce e privat (visibility === 0) sau cu onboardingDone === false și returnează payload-ul cartelelor:

{
  "tiles": [
    {
      "rankingId":  "...",
      "orgId":      "...",
      "orgName":    "...",
      "orgLogo":    "...",
      "teamId":     "...",
      "teamName":   "...",
      "teamCrest":  "...",
      "rank":       4,
      "points":     27,
      "delta":      2,
      "top5":       [ ... ]
    }
  ]
}

Același pipeline pe care îl folosește Competition.getStandings calculează rank și points, așa că numărul de pe o cartelă corespunde întotdeauna ecranului de clasament de pe telefon. Array-ul top5 este cel care alimentează vizualizarea Top 5 din RankingDetailScreen.

Ecrane clasamente

RankingsScreen — lista de cartele

Un ScalingLazyColumn vertical de RankingTileCard-uri, câte unul pentru fiecare pereche (echipă, competiție). Ecranul cere date proaspete la navigare și la reîmprospătarea manuală; lista redă ce a observat ultima dată repository-ul și se actualizează reactiv când sosesc date proaspete.

RankingDetailScreen — Top 5 al unei competiții

Apăsarea pe o cartelă deschide o vizualizare detaliată care redă primele 5 rânduri din clasamentul acelei competiții — același clasament pe care utilizatorul îl vede pe ecranul de clasament de pe telefon. Echipa urmărită este evidențiată cu o linie verde pe marginea stângă și text îngroșat.

Dacă echipa urmărită nu este în top 5 (să zicem că e pe locul 9), ecranul arată top 5, un separator ··· și apoi un rând evidențiat cu poziția curentă a echipei urmărite.

Scenariu: Alex verifică mișcările de sâmbătă

Alex urmărește două echipe — clubul U14 al fiului său și echipa sa locală din liga de duminică. Între meciuri își ridică încheietura, vede că echipa U14 a urcat un loc (▲ +1) și că echipa de duminică a rămas pe loc (—). Apasă cartela U14 și confirmă că clubul este acum pe locul 2, cu liderul la doar 1 punct în față. Alex este pe planul Gratuit, ceea ce nu e un obstacol aici — widgetul de clasamente este disponibil pe orice plan.

Widgetul la o privire este completat de o notificare push la cerere (rankingPositionChange), care se declanșează când poziția unei echipe urmărite se mișcă în raport cu sistemul de clasament la nivel de organizație. Apăsarea notificării deschide direct, prin deep link, ecranul RankingTable din aplicația de telefon pentru organizația respectivă.

Pentru că positionChange din notificare folosește aceeași convenție pozitiv-înseamnă-a-urcat ca delta de pe cartela ceasului, săgeata ▲/▼ pe care utilizatorul a văzut-o în push corespunde săgeții ▲/▼ pe care o vede când își ridică încheietura o clipă mai târziu.

💡
Sfat pentru organizatori

Pragurile de notificare (câte poziții trebuie să se miște o echipă înainte să plece un push) sunt configurabile per organizație din profilul organizației — vezi setările de admin ale acelei pagini pentru controlul pragului, cu jurnal de audit.