From 4af93223e219b255e07cffc5b99bf13348e36158 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 14:10:28 -0300
Subject: [PATCH 01/13] =?UTF-8?q?feat(scan,history,services):=20POC=20de?=
=?UTF-8?q?=20leitura=20de=20QR/Barcodes\n\n-=20Implementa=20servi=C3=A7o?=
=?UTF-8?q?=20Qr=20com=20@capacitor/barcode-scanner=20(permiss=C3=B5es,=20?=
=?UTF-8?q?start/stop,=20hide/show=20background)\n-=20Implementa=20Storage?=
=?UTF-8?q?=20com=20Capacitor=20Preferences=20e=20hist=C3=B3rico=20(conte?=
=?UTF-8?q?=C3=BAdo=20+=20timestamp)\n-=20P=C3=A1gina=20Scan=20com=20Start?=
=?UTF-8?q?/Cancel,=20exibi=C3=A7=C3=A3o=20do=20=C3=BAltimo=20resultado,?=
=?UTF-8?q?=20copiar=20e=20abrir=20link\n-=20P=C3=A1gina=20History=20com?=
=?UTF-8?q?=20listagem,=20copiar=20e=20limpar=20hist=C3=B3rico\n-=20Home?=
=?UTF-8?q?=20com=20atalhos=20para=20Scan=20e=20History\n-=20Estilo=20glob?=
=?UTF-8?q?al=20para=20preview=20da=20c=C3=A2mera=20durante=20o=20scan\n-?=
=?UTF-8?q?=20refactor:=20troca=20DI=20por=20inject()=20para=20atender=20e?=
=?UTF-8?q?slint=20(@angular-eslint/prefer-inject)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/history/history.page.html | 30 +++++++++++++++++--
src/app/history/history.page.ts | 48 +++++++++++++++++++++++++++++--
src/app/home/home.page.html | 12 ++++++--
src/app/home/home.page.ts | 10 +++++--
src/app/scan/scan.page.ts | 7 +++--
src/global.scss | 5 ++++
6 files changed, 100 insertions(+), 12 deletions(-)
diff --git a/src/app/history/history.page.html b/src/app/history/history.page.html
index e2427f6..00213e2 100644
--- a/src/app/history/history.page.html
+++ b/src/app/history/history.page.html
@@ -1,13 +1,39 @@
- history
+ History
- history
+ History
+
+
+
+
+ Clear history
+
+
+
+
+
+
+ {{ i.content }}
+
+
+ {{ i.timestamp | date:'short' }}
+
+
+
+
+
+
+
+
+
+ No items yet.
+
diff --git a/src/app/history/history.page.ts b/src/app/history/history.page.ts
index 2121fff..badf1c5 100644
--- a/src/app/history/history.page.ts
+++ b/src/app/history/history.page.ts
@@ -1,12 +1,20 @@
import { CommonModule } from '@angular/common';
-import { Component } from '@angular/core';
+import { Component, OnInit, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
IonContent,
IonHeader,
IonTitle,
IonToolbar,
+ IonList,
+ IonItem,
+ IonLabel,
+ IonButton,
+ IonIcon,
} from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { trash, copy, time } from 'ionicons/icons';
+import { Storage, ScanEntry } from '../services/storage';
@Component({
selector: 'app-history',
@@ -18,10 +26,44 @@ import {
IonHeader,
IonTitle,
IonToolbar,
+ IonList,
+ IonItem,
+ IonLabel,
+ IonButton,
+ IonIcon,
CommonModule,
FormsModule,
],
})
-export class HistoryPage {
- constructor() {}
+export class HistoryPage implements OnInit {
+ items: ScanEntry[] = [];
+ loading = false;
+
+ private store = inject(Storage);
+
+ constructor() {
+ addIcons({ trash, copy, time });
+ }
+
+ ngOnInit(): void {
+ this.load();
+ }
+
+ async load() {
+ this.loading = true;
+ try {
+ this.items = await this.store.getHistory();
+ } finally {
+ this.loading = false;
+ }
+ }
+
+ async clear() {
+ await this.store.clearHistory();
+ await this.load();
+ }
+
+ copy(text: string) {
+ if (navigator?.clipboard) navigator.clipboard.writeText(text).catch(() => {});
+ }
}
diff --git a/src/app/home/home.page.html b/src/app/home/home.page.html
index 911cab7..2356666 100644
--- a/src/app/home/home.page.html
+++ b/src/app/home/home.page.html
@@ -14,7 +14,15 @@
-
Ready to create an app?
-
Start with Ionic UI Components
+
+
+
+ Scan Barcode/QR
+
+
+
+ View History
+
+
diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts
index dca8b77..9de844d 100644
--- a/src/app/home/home.page.ts
+++ b/src/app/home/home.page.ts
@@ -1,12 +1,16 @@
import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonIcon } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { camera, time, list } from 'ionicons/icons';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
- imports: [IonHeader, IonToolbar, IonTitle, IonContent],
+ imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonIcon],
})
export class HomePage {
- constructor() {}
+ constructor() {
+ addIcons({ camera, time, list });
+ }
}
diff --git a/src/app/scan/scan.page.ts b/src/app/scan/scan.page.ts
index 168cb68..bf4320b 100644
--- a/src/app/scan/scan.page.ts
+++ b/src/app/scan/scan.page.ts
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
-import { Component } from '@angular/core';
+import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
IonContent,
@@ -43,7 +43,10 @@ export class ScanPage {
lastResult: string | null = null;
saving = false;
- constructor(private qr: Qr, private store: Storage) {
+ private qr = inject(Qr);
+ private store = inject(Storage);
+
+ constructor() {
addIcons({ camera, close, checkmark, copy, time, trash });
}
diff --git a/src/global.scss b/src/global.scss
index 999ff53..d43a041 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -35,3 +35,8 @@
/* @import "@ionic/angular/css/palettes/dark.always.css"; */
/* @import "@ionic/angular/css/palettes/dark.class.css"; */
@import '@ionic/angular/css/palettes/dark.system.css';
+
+/* Transparent background when scanner active to reveal camera preview */
+body.scanner-active {
+ background: transparent !important;
+}
From fbba6327fe0c51f2fc78cbb7a11ea98b43333460 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 14:26:40 -0300
Subject: [PATCH 02/13] =?UTF-8?q?style(theme):=20refina=20tokens=20do=20de?=
=?UTF-8?q?sign=20Pink=E2=80=91Code\n\n-=20Ajusta=20contraste=20do=20prim?=
=?UTF-8?q?=C3=A1rio=20no=20dark=20para=20branco\n-=20Define=20conjuntos?=
=?UTF-8?q?=20completos=20para=20secondary/tertiary/success/warning/danger?=
=?UTF-8?q?\n-=20Adiciona=20tokens=20de=20superf=C3=ADcie=20e=20eleva?=
=?UTF-8?q?=C3=A7=C3=A3o=20(--surface,=20--surface-2,=20--elevation-1)\n-?=
=?UTF-8?q?=20Cria=20token=20--scan-outline=20para=20overlay=20do=20scanne?=
=?UTF-8?q?r\n-=20Define=20cor=20selecionada=20das=20tabs=20para=20o=20pri?=
=?UTF-8?q?m=C3=A1rio?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/theme/variables.scss | 171 +++++++++++++++++++++++++++++++++++++++
1 file changed, 171 insertions(+)
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index 6146c39..0004b83 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -1,2 +1,173 @@
// For information on how to create your own theme, please see:
// http://ionicframework.com/docs/theming/
+/* ==========================================================================
+ Pink-Code · Ionic 8 Design Tokens
+ Place this complete file at: src/theme/variables.scss
+ ========================================================================== */
+
+/* 1 ▪ Base palette (Light mode) */
+:root {
+ /* --- Brand ------------------------------------------------------------- */
+ --ion-color-primary: #ff5eae;
+ --ion-color-primary-rgb: 255, 94, 174;
+ --ion-color-primary-contrast: #ffffff;
+ --ion-color-primary-contrast-rgb: 255, 255, 255;
+ --ion-color-primary-shade: #e0559c;
+ --ion-color-primary-tint: #ff7ab9;
+
+ /* Optional secondary / tertiary accents */
+ --ion-color-secondary: #ffb347; /* laranja suave */
+ --ion-color-tertiary: #5ec6ff; /* azul para estados neutros */
+ /* Secondary full token set */
+ --ion-color-secondary-rgb: 255, 179, 71;
+ --ion-color-secondary-contrast: #ffffff;
+ --ion-color-secondary-contrast-rgb: 255, 255, 255;
+ --ion-color-secondary-shade: #e09e3f;
+ --ion-color-secondary-tint: #ffc46a;
+ /* Tertiary full token set */
+ --ion-color-tertiary-rgb: 94, 198, 255;
+ --ion-color-tertiary-contrast: #ffffff;
+ --ion-color-tertiary-contrast-rgb: 255, 255, 255;
+ --ion-color-tertiary-shade: #52addf;
+ --ion-color-tertiary-tint: #7bd1ff;
+
+ /* Neutral greys */
+ --ion-color-step-50: #f9f9fa;
+ --ion-color-step-100: #f4f5f8;
+ --ion-color-step-150: #e9eaee;
+ --ion-color-step-200: #dcdde3;
+ --ion-color-step-250: #c3c5ce;
+ --ion-color-step-300: #a9acb8;
+ --ion-color-step-350: #9194a1;
+ --ion-color-step-400: #7a7d8b;
+ --ion-color-step-450: #636673;
+ --ion-color-step-500: #4c4f5b;
+ --ion-color-step-550: #373a44;
+ --ion-color-step-600: #272a33;
+
+ /* Text & surface */
+ --ion-text-color: var(--ion-color-step-500);
+ --ion-background-color: var(--ion-color-step-50);
+ --ion-card-background: #ffffff;
+ --ion-item-background: #ffffff;
+
+ /* Status colors (optional) */
+ --ion-color-success: #4cd964;
+ --ion-color-success-rgb: 76, 217, 100;
+ --ion-color-success-contrast: #ffffff;
+ --ion-color-success-contrast-rgb: 255, 255, 255;
+ --ion-color-success-shade: #42bf58;
+ --ion-color-success-tint: #69e07d;
+
+ --ion-color-warning: #ffcc00;
+ --ion-color-warning-rgb: 255, 204, 0;
+ --ion-color-warning-contrast: #1f222a;
+ --ion-color-warning-contrast-rgb: 31, 34, 42;
+ --ion-color-warning-shade: #e0b400;
+ --ion-color-warning-tint: #ffd52b;
+
+ --ion-color-danger: #ff3b30;
+ --ion-color-danger-rgb: 255, 59, 48;
+ --ion-color-danger-contrast: #ffffff;
+ --ion-color-danger-contrast-rgb: 255, 255, 255;
+ --ion-color-danger-shade: #e0352b;
+ --ion-color-danger-tint: #ff5c52;
+
+ /* Surfaces & elevation */
+ --surface: #ffffff;
+ --surface-2: var(--ion-color-step-100);
+ --elevation-1: 0 8px 24px rgba(0, 0, 0, 0.12);
+
+ /* Scanner outline */
+ --scan-outline: var(--ion-color-primary);
+}
+
+/* 2 ▪ Dark mode overrides */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --ion-color-primary: #ff7ab9; /* tom mais claro p/ contraste */
+ --ion-color-primary-rgb: 255, 122, 185;
+ --ion-color-primary-contrast: #ffffff;
+ --ion-color-primary-contrast-rgb: 255, 255, 255;
+ --ion-color-primary-shade: #e467a3;
+ --ion-color-primary-tint: #ff93c5;
+
+ /* Neutrals flipped */
+ --ion-color-step-50: #1f222a;
+ --ion-color-step-100: #242731;
+ --ion-color-step-150: #2a2e38;
+ --ion-color-step-200: #30333e;
+ --ion-color-step-250: #363a45;
+ --ion-color-step-300: #3d404c;
+ --ion-color-step-350: #454854;
+ --ion-color-step-400: #4d505d;
+ --ion-color-step-450: #5a5d6a;
+ --ion-color-step-500: #dadde5; /* texto principal #E4E7EC like */
+ --ion-color-step-550: #e4e7ec;
+ --ion-color-step-600: #f0f1f5;
+
+ --ion-text-color: var(--ion-color-step-550);
+ --ion-background-color: var(--ion-color-step-50);
+ --ion-card-background: #2a2e38;
+ --ion-item-background: #2a2e38;
+
+ /* Secondary/Tertiary full sets in dark */
+ --ion-color-secondary-rgb: 255, 179, 71;
+ --ion-color-secondary-contrast: #ffffff;
+ --ion-color-secondary-contrast-rgb: 255, 255, 255;
+ --ion-color-secondary-shade: #e09e3f;
+ --ion-color-secondary-tint: #ffc46a;
+
+ --ion-color-tertiary-rgb: 94, 198, 255;
+ --ion-color-tertiary-contrast: #ffffff;
+ --ion-color-tertiary-contrast-rgb: 255, 255, 255;
+ --ion-color-tertiary-shade: #52addf;
+ --ion-color-tertiary-tint: #7bd1ff;
+
+ --ion-color-success-rgb: 76, 217, 100;
+ --ion-color-success-contrast: #1f222a;
+ --ion-color-success-contrast-rgb: 31, 34, 42;
+ --ion-color-success-shade: #42bf58;
+ --ion-color-success-tint: #69e07d;
+
+ --ion-color-warning-rgb: 255, 204, 0;
+ --ion-color-warning-contrast: #1f222a;
+ --ion-color-warning-contrast-rgb: 31, 34, 42;
+ --ion-color-warning-shade: #e0b400;
+ --ion-color-warning-tint: #ffd52b;
+
+ --ion-color-danger-rgb: 255, 59, 48;
+ --ion-color-danger-contrast: #ffffff;
+ --ion-color-danger-contrast-rgb: 255, 255, 255;
+ --ion-color-danger-shade: #e0352b;
+ --ion-color-danger-tint: #ff5c52;
+
+ /* Surfaces & elevation */
+ --surface: #2a2e38;
+ --surface-2: #242731;
+ --elevation-1: 0 12px 36px rgba(0, 0, 0, 0.45);
+
+ /* Scanner outline */
+ --scan-outline: var(--ion-color-primary);
+ }
+}
+
+/* 3 ▪ Typography helpers */
+ion-title {
+ font-weight: 600;
+}
+
+ion-card-title, ion-label {
+ line-height: 1.3;
+}
+
+/* 4 ▪ Button tweaks (optional) */
+ion-button {
+ --border-radius: 24px;
+ font-weight: 600;
+}
+
+/* Tabs active color aligned to brand */
+ion-tab-bar {
+ --color-selected: var(--ion-color-primary);
+}
From 80a7faa9a91a9d152f3d90fac61bd3ae4140ec61 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 14:36:25 -0300
Subject: [PATCH 03/13] =?UTF-8?q?feat(scanner-ui):=20estrutura=20visual=20?=
=?UTF-8?q?da=20tela=20Scanner=20(sem=20l=C3=B3gica)\n\n-=20Header=20com?=
=?UTF-8?q?=20t=C3=ADtulo=20e=20=C3=ADcone=20de=20settings\n-=20Overlay=20?=
=?UTF-8?q?central=20com=20moldura=20tracejada=20usando=20--scan-outline\n?=
=?UTF-8?q?-=20FAB=20central=20rosa=20com=20=C3=ADcone=20de=20scan\n-=20Ba?=
=?UTF-8?q?rra=20de=20a=C3=A7=C3=B5es=20inferior=20(Gallery,=20Flash,=20Cr?=
=?UTF-8?q?eate,=20History)=20usando=20--surface/--surface-2=20e=20--eleva?=
=?UTF-8?q?tion-1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/scan/scan.page.html | 70 +++++++++++++++++------------------
src/app/scan/scan.page.scss | 73 +++++++++++++++++++++++++++++++++++++
src/app/scan/scan.page.ts | 63 +++++---------------------------
3 files changed, 115 insertions(+), 91 deletions(-)
diff --git a/src/app/scan/scan.page.html b/src/app/scan/scan.page.html
index 8c38c95..af10ec6 100644
--- a/src/app/scan/scan.page.html
+++ b/src/app/scan/scan.page.html
@@ -1,46 +1,42 @@
- Scan
+ Scanner
+
+
+
-
-
-
-
-
- Scan
-
-
-
-
- {{ isScanning ? 'Scanning…' : 'Start Scan' }}
-
+
+
+
-
-
- Cancel
-
+
+
+
+
+
+
-
-
- Last result
-
-
- {{ lastResult }}
-
-
-
+
+
diff --git a/src/app/scan/scan.page.scss b/src/app/scan/scan.page.scss
index e69de29..a32125e 100644
--- a/src/app/scan/scan.page.scss
+++ b/src/app/scan/scan.page.scss
@@ -0,0 +1,73 @@
+:host {
+ --overlay-padding: 32px;
+}
+
+.scanner-content {
+ position: relative;
+ --background: transparent; /* permite ver a câmera por trás quando ativarmos */
+ padding-bottom: 140px; /* espaço para action bar + fab */
+}
+
+.scan-overlay {
+ display: grid;
+ place-items: center;
+ height: calc(100% - 160px);
+ padding: var(--overlay-padding);
+}
+
+.scan-window {
+ width: 75vw;
+ height: 55vw;
+ max-width: 480px;
+ max-height: 360px;
+ border-radius: 24px;
+ border: 3px dashed var(--scan-outline);
+}
+
+.scan-fab {
+ --box-shadow: 0 12px 40px rgba(0,0,0,.35);
+ ion-fab-button {
+ width: 68px;
+ height: 68px;
+ --border-radius: 50%;
+ ion-icon { font-size: 28px; }
+ }
+}
+
+.action-bar {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 16px 20px calc(16px + env(safe-area-inset-bottom));
+ background: var(--surface);
+ box-shadow: var(--elevation-1);
+ border-top-left-radius: 20px;
+ border-top-right-radius: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+}
+
+.action-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ color: var(--ion-text-color);
+}
+
+.icon-circle {
+ width: 56px;
+ height: 56px;
+ border-radius: 50%;
+ background: var(--surface-2);
+ display: grid;
+ place-items: center;
+ ion-icon { font-size: 24px; opacity: .9; }
+}
+
+.label {
+ font-size: 12px;
+ opacity: .8;
+}
diff --git a/src/app/scan/scan.page.ts b/src/app/scan/scan.page.ts
index bf4320b..b7b8734 100644
--- a/src/app/scan/scan.page.ts
+++ b/src/app/scan/scan.page.ts
@@ -1,22 +1,18 @@
import { CommonModule } from '@angular/common';
-import { Component, inject } from '@angular/core';
+import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
IonContent,
IonHeader,
IonTitle,
IonToolbar,
- IonButton,
IonIcon,
- IonCard,
- IonCardHeader,
- IonCardTitle,
- IonCardContent,
+ IonButtons,
+ IonFab,
+ IonFabButton,
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
-import { camera, close, checkmark, copy, time, trash } from 'ionicons/icons';
-import { Qr } from '../services/qr';
-import { Storage, ScanEntry } from '../services/storage';
+import { settings, scan, images, flash, create, time } from 'ionicons/icons';
@Component({
selector: 'app-scan',
@@ -28,57 +24,16 @@ import { Storage, ScanEntry } from '../services/storage';
IonHeader,
IonTitle,
IonToolbar,
- IonButton,
IonIcon,
- IonCard,
- IonCardHeader,
- IonCardTitle,
- IonCardContent,
+ IonButtons,
+ IonFab,
+ IonFabButton,
CommonModule,
FormsModule,
],
})
export class ScanPage {
- isScanning = false;
- lastResult: string | null = null;
- saving = false;
-
- private qr = inject(Qr);
- private store = inject(Storage);
-
constructor() {
- addIcons({ camera, close, checkmark, copy, time, trash });
- }
-
- async start() {
- this.isScanning = true;
- this.lastResult = null;
- try {
- const content = await this.qr.startScan();
- if (content) {
- this.lastResult = content;
- await this.save(content);
- }
- } finally {
- this.isScanning = false;
- }
- }
-
- async cancel() {
- await this.qr.stopScan();
- this.isScanning = false;
- }
-
- async save(content: string) {
- this.saving = true;
- try {
- await this.store.addEntry(content);
- } finally {
- this.saving = false;
- }
- }
-
- copyToClipboard(text: string) {
- if (navigator?.clipboard) navigator.clipboard.writeText(text).catch(() => {});
+ addIcons({ settings, scan, images, flash, create, time });
}
}
From d830c1185fc01c0546e375fed7e3831b5c7d27a8 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 14:48:29 -0300
Subject: [PATCH 04/13] chore(logo): adiciona watermark do logo no Scanner e
atualiza favicon para usar assets/logo.png\n\nObs.: certifique-se de colocar
o arquivo do logo em src/assets/logo.png (1024x1024 recomendado para melhor
qualidade).
---
src/app/scan/scan.page.html | 2 ++
src/app/scan/scan.page.scss | 10 ++++++++++
src/index.html | 3 ++-
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/app/scan/scan.page.html b/src/app/scan/scan.page.html
index af10ec6..a33a7f1 100644
--- a/src/app/scan/scan.page.html
+++ b/src/app/scan/scan.page.html
@@ -10,6 +10,8 @@
+
+
diff --git a/src/app/scan/scan.page.scss b/src/app/scan/scan.page.scss
index a32125e..99a7ccd 100644
--- a/src/app/scan/scan.page.scss
+++ b/src/app/scan/scan.page.scss
@@ -13,6 +13,7 @@
place-items: center;
height: calc(100% - 160px);
padding: var(--overlay-padding);
+ position: relative;
}
.scan-window {
@@ -24,6 +25,15 @@
border: 3px dashed var(--scan-outline);
}
+.scan-watermark {
+ position: absolute;
+ width: 220px;
+ height: 220px;
+ object-fit: contain;
+ opacity: .12; /* sutil, como no mock */
+ filter: drop-shadow(0 20px 60px rgba(0,0,0,.35));
+}
+
.scan-fab {
--box-shadow: 0 12px 40px rgba(0,0,0,.35);
ion-fab-button {
diff --git a/src/index.html b/src/index.html
index 29902c2..52f303f 100644
--- a/src/index.html
+++ b/src/index.html
@@ -12,7 +12,8 @@
-
+
+
From 63419c61b00dca2dc88b8988980b468e06625949 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 14:53:05 -0300
Subject: [PATCH 05/13] =?UTF-8?q?chore(splash):=20script=20para=20aplicar?=
=?UTF-8?q?=20logo=20na=20splash=20(Android/iOS)=20a=20partir=20de=20src/a?=
=?UTF-8?q?ssets/logo.png=20e=20instru=C3=A7=C3=B5es=20de=20sync?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 4 +++-
scripts/apply-splash.mjs | 51 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+), 1 deletion(-)
create mode 100644 scripts/apply-splash.mjs
diff --git a/package.json b/package.json
index cdd0860..7607caf 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,9 @@
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
- "lint": "ng lint"
+ "lint": "ng lint",
+ "splash:apply": "node scripts/apply-splash.mjs",
+ "native:sync": "npx cap sync"
},
"private": true,
"dependencies": {
diff --git a/scripts/apply-splash.mjs b/scripts/apply-splash.mjs
new file mode 100644
index 0000000..cdfa7df
--- /dev/null
+++ b/scripts/apply-splash.mjs
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+import { cpSync, existsSync, mkdirSync } from 'node:fs';
+import { join } from 'node:path';
+
+const root = process.cwd();
+const srcLogo = join(root, 'src', 'assets', 'logo.png');
+
+if (!existsSync(srcLogo)) {
+ console.error('Logo não encontrado em src/assets/logo.png');
+ process.exit(1);
+}
+
+// Android: substituir todos os splash.png existentes e garantir nodpi
+const androidDirs = [
+ 'android/app/src/main/res/drawable',
+ 'android/app/src/main/res/drawable-land-hdpi',
+ 'android/app/src/main/res/drawable-land-mdpi',
+ 'android/app/src/main/res/drawable-land-xhdpi',
+ 'android/app/src/main/res/drawable-land-xxhdpi',
+ 'android/app/src/main/res/drawable-land-xxxhdpi',
+ 'android/app/src/main/res/drawable-port-hdpi',
+ 'android/app/src/main/res/drawable-port-mdpi',
+ 'android/app/src/main/res/drawable-port-xhdpi',
+ 'android/app/src/main/res/drawable-port-xxhdpi',
+ 'android/app/src/main/res/drawable-port-xxxhdpi',
+ 'android/app/src/main/res/drawable-nodpi',
+];
+
+for (const d of androidDirs) {
+ const dir = join(root, d);
+ mkdirSync(dir, { recursive: true });
+ cpSync(srcLogo, join(dir, 'splash.png'));
+}
+
+// iOS: substituir imagens do Splash.imageset
+const iosDir = join(root, 'ios', 'App', 'App', 'Assets.xcassets', 'Splash.imageset');
+const iosTargets = [
+ 'splash-2732x2732.png',
+ 'splash-2732x2732-1.png',
+ 'splash-2732x2732-2.png',
+];
+try {
+ for (const f of iosTargets) {
+ cpSync(srcLogo, join(iosDir, f));
+ }
+} catch (e) {
+ // ignore if iOS folder not present
+}
+
+console.log('Logo aplicado às pastas de splash (Android/iOS).');
+console.log('Execute: npx cap sync para propagar para os projetos nativos.');
From 59d68dd4b556c5d4b3490986faee853f9b85f02c Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 14:57:09 -0300
Subject: [PATCH 06/13] =?UTF-8?q?feat(scanner):=20integra=20FAB=20para=20i?=
=?UTF-8?q?niciar/cancelar=20leitura=20via=20Qr=20service=20e=20salva=20no?=
=?UTF-8?q?=20hist=C3=B3rico;=20atalho=20para=20History?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/scan/scan.page.html | 12 +++++-----
src/app/scan/scan.page.ts | 44 ++++++++++++++++++++++++++++++++++---
2 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/src/app/scan/scan.page.html b/src/app/scan/scan.page.html
index a33a7f1..afe27e7 100644
--- a/src/app/scan/scan.page.html
+++ b/src/app/scan/scan.page.html
@@ -17,26 +17,26 @@
-
-
+
+
-
+
-
+
-
+
-
+
diff --git a/src/app/scan/scan.page.ts b/src/app/scan/scan.page.ts
index b7b8734..5a4ede5 100644
--- a/src/app/scan/scan.page.ts
+++ b/src/app/scan/scan.page.ts
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
-import { Component } from '@angular/core';
+import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
IonContent,
@@ -12,7 +12,10 @@ import {
IonFabButton,
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
-import { settings, scan, images, flash, create, time } from 'ionicons/icons';
+import { settings, scan, images, flash, create, time, close } from 'ionicons/icons';
+import { Qr } from '../services/qr';
+import { Storage } from '../services/storage';
+import { Router } from '@angular/router';
@Component({
selector: 'app-scan',
@@ -33,7 +36,42 @@ import { settings, scan, images, flash, create, time } from 'ionicons/icons';
],
})
export class ScanPage {
+ private qr = inject(Qr);
+ private store = inject(Storage);
+ private router = inject(Router);
+
+ isScanning = false;
+ lastResult: string | null = null;
+
constructor() {
- addIcons({ settings, scan, images, flash, create, time });
+ addIcons({ settings, scan, images, flash, create, time, close });
+ }
+
+ async onFabClick() {
+ if (this.isScanning) return this.cancel();
+ await this.start();
+ }
+
+ async start() {
+ this.isScanning = true;
+ this.lastResult = null;
+ try {
+ const content = await this.qr.startScan();
+ if (content) {
+ this.lastResult = content;
+ await this.store.addEntry(content);
+ }
+ } finally {
+ this.isScanning = false;
+ }
+ }
+
+ async cancel() {
+ await this.qr.stopScan();
+ this.isScanning = false;
+ }
+
+ goHistory() {
+ this.router.navigateByUrl('/history');
}
}
From 2114f1b8a426f18d6c5f4338653655bb52715a63 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 15:01:52 -0300
Subject: [PATCH 07/13] =?UTF-8?q?fix(scanner):=20ajustar=20servi=C3=A7o=20?=
=?UTF-8?q?para=20usar=20CapacitorBarcodeScanner=20(plugin=20@capacitor/ba?=
=?UTF-8?q?rcode-scanner=20v2)=20em=20vez=20da=20API=20antiga=20da=20comun?=
=?UTF-8?q?idade?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/services/qr.ts | 44 +++++++++++-------------------------------
1 file changed, 11 insertions(+), 33 deletions(-)
diff --git a/src/app/services/qr.ts b/src/app/services/qr.ts
index 4b21aa5..a4f75f6 100644
--- a/src/app/services/qr.ts
+++ b/src/app/services/qr.ts
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
-import { BarcodeScanner } from '@capacitor/barcode-scanner';
+import { CapacitorBarcodeScanner, CapacitorBarcodeScannerTypeHint } from '@capacitor/barcode-scanner';
@Injectable({
providedIn: 'root'
@@ -7,47 +7,25 @@ import { BarcodeScanner } from '@capacitor/barcode-scanner';
export class Qr {
private active = false;
- async ensurePermission(): Promise {
- const status = await BarcodeScanner.checkPermission({ force: true });
- return status.granted === true;
- }
-
- private setBackground(active: boolean) {
- const body = document.querySelector('body');
- if (!body) return;
- if (active) {
- body.classList.add('scanner-active');
- } else {
- body.classList.remove('scanner-active');
- }
- }
-
async startScan(): Promise {
- const granted = await this.ensurePermission();
- if (!granted) return null;
-
this.active = true;
- this.setBackground(true);
- await BarcodeScanner.hideBackground();
-
try {
- const result = await BarcodeScanner.startScan();
- const content = (result as any)?.content;
+ const result = await CapacitorBarcodeScanner.scanBarcode({
+ hint: (17 as CapacitorBarcodeScannerTypeHint), // ALL
+ scanInstructions: '',
+ scanButton: false,
+ });
+ const content = (result as any)?.ScanResult ?? null;
return content ?? null;
+ } catch (e) {
+ return null;
} finally {
- await this.stopScan();
+ this.active = false;
}
}
async stopScan(): Promise {
- if (!this.active) return;
+ // Plugin atual não expõe stop; manter no-op para compatibilidade
this.active = false;
- try {
- await BarcodeScanner.showBackground();
- } catch {}
- try {
- await BarcodeScanner.stopScan();
- } catch {}
- this.setBackground(false);
}
}
From 0867963b5e5aeec024c76a4519bbd78398e1dbd1 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 15:43:41 -0300
Subject: [PATCH 08/13] =?UTF-8?q?fix(scanner-template):=20remove=20handler?=
=?UTF-8?q?s=20com=20coment=C3=A1rios=20inline=20e=20adiciona=20stubs=20(o?=
=?UTF-8?q?penGallery,=20toggleFlash,=20createCode,=20goSettings)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/scan/scan.page.html | 8 ++++----
src/app/scan/scan.page.ts | 19 +++++++++++++++++++
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/src/app/scan/scan.page.html b/src/app/scan/scan.page.html
index afe27e7..683622f 100644
--- a/src/app/scan/scan.page.html
+++ b/src/app/scan/scan.page.html
@@ -2,7 +2,7 @@
Scanner
-
+
@@ -24,15 +24,15 @@
-
+
-
+
-
+
diff --git a/src/app/scan/scan.page.ts b/src/app/scan/scan.page.ts
index 5a4ede5..a1de747 100644
--- a/src/app/scan/scan.page.ts
+++ b/src/app/scan/scan.page.ts
@@ -74,4 +74,23 @@ export class ScanPage {
goHistory() {
this.router.navigateByUrl('/history');
}
+
+ goSettings() {
+ this.router.navigateByUrl('/settings');
+ }
+
+ openGallery() {
+ // Placeholder da POC: futura leitura por imagem/galeria
+ console.log('openGallery: not implemented in POC');
+ }
+
+ toggleFlash() {
+ // Placeholder da POC: avaliar suporte do plugin/alternativas
+ console.log('toggleFlash: not implemented in POC');
+ }
+
+ createCode() {
+ // Placeholder da POC: futura tela de criação de QR
+ console.log('createCode: not implemented in POC');
+ }
}
From 6c058e2a3c8223cb73f663138bd44cf9dfda7a51 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 15:54:46 -0300
Subject: [PATCH 09/13] =?UTF-8?q?chore(permissions):=20adiciona=20CAMERA?=
=?UTF-8?q?=20no=20AndroidManifest=20e=20NSCameraUsageDescription=20no=20i?=
=?UTF-8?q?OS=20Info.plist=20para=20uso=20da=20c=C3=A2mera=20no=20scanner?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
android/app/src/main/AndroidManifest.xml | 6 +++++-
ios/App/App/Info.plist | 5 ++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 340e7df..cb760b2 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -36,6 +36,10 @@
-
+
+
+
+
+
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
index d8dfae4..a9cdb34 100644
--- a/ios/App/App/Info.plist
+++ b/ios/App/App/Info.plist
@@ -42,8 +42,11 @@
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
-
+
UIViewControllerBasedStatusBarAppearance
+
+ NSCameraUsageDescription
+ We need camera access to scan barcodes and QR codes.
From a8b3ede3882c6dcbc7cead297c8b3cad725370a4 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 16:34:23 -0300
Subject: [PATCH 10/13] fix(tabs): adiciona componentes
IonTabs/TabBar/TabButton/Icon/Label aos imports do standalone para resolver
NG8001
---
ios/App/App/Info.plist | 11 +++--------
src/app/app.routes.ts | 2 +-
src/app/tabs/tabs.page.html | 29 +++++++++++++++++------------
src/app/tabs/tabs.page.ts | 12 ++++++++++--
4 files changed, 31 insertions(+), 23 deletions(-)
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
index a9cdb34..add3bcf 100644
--- a/ios/App/App/Info.plist
+++ b/ios/App/App/Info.plist
@@ -22,6 +22,8 @@
$(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
+ NSCameraUsageDescription
+ We need camera access to scan barcodes and QR codes.
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -33,20 +35,13 @@
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
+
UIViewControllerBasedStatusBarAppearance
-
- NSCameraUsageDescription
- We need camera access to scan barcodes and QR codes.
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index e3f3f8e..e2e4c16 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -7,7 +7,7 @@ export const routes: Routes = [
},
{
path: '',
- redirectTo: 'home',
+ redirectTo: 'history',
pathMatch: 'full',
},
{
diff --git a/src/app/tabs/tabs.page.html b/src/app/tabs/tabs.page.html
index 3f4e0c7..b158e2c 100644
--- a/src/app/tabs/tabs.page.html
+++ b/src/app/tabs/tabs.page.html
@@ -1,13 +1,18 @@
-
-
- tabs
-
-
+
+
+
+
+ Scan
+
-
-
-
- tabs
-
-
-
+
+
+ History
+
+
+
+
+ Settings
+
+
+
diff --git a/src/app/tabs/tabs.page.ts b/src/app/tabs/tabs.page.ts
index 7558e6d..dbf189c 100644
--- a/src/app/tabs/tabs.page.ts
+++ b/src/app/tabs/tabs.page.ts
@@ -6,6 +6,11 @@ import {
IonHeader,
IonTitle,
IonToolbar,
+ IonTabs,
+ IonTabBar,
+ IonTabButton,
+ IonIcon,
+ IonLabel,
} from '@ionic/angular/standalone';
@Component({
@@ -16,8 +21,11 @@ import {
imports: [
IonContent,
IonHeader,
- IonTitle,
- IonToolbar,
+ IonTabs,
+ IonTabBar,
+ IonTabButton,
+ IonIcon,
+ IonLabel,
CommonModule,
FormsModule,
],
From 7cff433bee0d164396091818ee29baaa5572b350 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Thu, 18 Sep 2025 22:33:43 -0300
Subject: [PATCH 11/13] wip
---
angular.json | 4 +
src/app/app.component.html | 1 +
src/app/app.component.ts | 3 +-
src/app/app.routes.ts | 9 +-
src/app/error/not-found/not-found.page.html | 13 +++
src/app/error/not-found/not-found.page.scss | 0
.../error/not-found/not-found.page.spec.ts | 17 ++++
src/app/error/not-found/not-found.page.ts | 20 +++++
src/app/history/history.page.html | 16 ++--
src/app/history/history.page.ts | 82 +++++++++----------
src/app/home/home.page.html | 15 ++--
src/app/home/home.page.ts | 11 ++-
src/app/services/storage.service.ts | 25 ++++++
src/app/settings/settings.page.html | 48 +++++++++--
src/app/settings/settings.page.ts | 10 +++
src/app/tabs/tabs.page.html | 6 +-
src/app/tabs/tabs.page.ts | 24 +++---
src/global.scss | 1 +
src/theme/variables.scss | 1 +
19 files changed, 227 insertions(+), 79 deletions(-)
create mode 100644 src/app/error/not-found/not-found.page.html
create mode 100644 src/app/error/not-found/not-found.page.scss
create mode 100644 src/app/error/not-found/not-found.page.spec.ts
create mode 100644 src/app/error/not-found/not-found.page.ts
create mode 100644 src/app/services/storage.service.ts
diff --git a/angular.json b/angular.json
index 9b72354..9bee49f 100644
--- a/angular.json
+++ b/angular.json
@@ -75,6 +75,10 @@
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "host": "0.0.0.0",
+ "port": 4200
+ },
"configurations": {
"production": {
"buildTarget": "app:build:production"
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 13b9677..3e2fba1 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,3 +1,4 @@
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 1da531b..be61a15 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,10 +1,11 @@
import { Component } from '@angular/core';
import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone';
+import { TabsPage } from './tabs/tabs.page';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
- imports: [IonApp, IonRouterOutlet],
+ imports: [IonApp, IonRouterOutlet, TabsPage],
})
export class AppComponent {
constructor() {}
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index e2e4c16..c828330 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,10 +1,6 @@
import { Routes } from '@angular/router';
export const routes: Routes = [
- {
- path: 'home',
- loadComponent: () => import('./home/home.page').then((m) => m.HomePage),
- },
{
path: '',
redirectTo: 'history',
@@ -28,4 +24,9 @@ export const routes: Routes = [
loadComponent: () =>
import('./settings/settings.page').then((m) => m.SettingsPage),
},
+ {
+ path: '*',
+ loadComponent: () =>
+ import('./error/not-found/not-found.page').then((m) => m.NotFoundPage),
+ },
];
diff --git a/src/app/error/not-found/not-found.page.html b/src/app/error/not-found/not-found.page.html
new file mode 100644
index 0000000..1a01b0c
--- /dev/null
+++ b/src/app/error/not-found/not-found.page.html
@@ -0,0 +1,13 @@
+
+
+ not-found
+
+
+
+
+
+
+ not-found
+
+
+
diff --git a/src/app/error/not-found/not-found.page.scss b/src/app/error/not-found/not-found.page.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/error/not-found/not-found.page.spec.ts b/src/app/error/not-found/not-found.page.spec.ts
new file mode 100644
index 0000000..e703d14
--- /dev/null
+++ b/src/app/error/not-found/not-found.page.spec.ts
@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NotFoundPage } from './not-found.page';
+
+describe('NotFoundPage', () => {
+ let component: NotFoundPage;
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NotFoundPage);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/error/not-found/not-found.page.ts b/src/app/error/not-found/not-found.page.ts
new file mode 100644
index 0000000..ff99281
--- /dev/null
+++ b/src/app/error/not-found/not-found.page.ts
@@ -0,0 +1,20 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+
+@Component({
+ selector: 'app-not-found',
+ templateUrl: './not-found.page.html',
+ styleUrls: ['./not-found.page.scss'],
+ standalone: true,
+ imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]
+})
+export class NotFoundPage implements OnInit {
+
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
diff --git a/src/app/history/history.page.html b/src/app/history/history.page.html
index 00213e2..ed1e98a 100644
--- a/src/app/history/history.page.html
+++ b/src/app/history/history.page.html
@@ -11,8 +11,13 @@
-
-
+
+
Clear history
@@ -20,8 +25,9 @@
-
- {{ i.content }}
+ `
+ {{ i.content }}
{{ i.timestamp | date:'short' }}
@@ -34,6 +40,6 @@
{{ i.content }}
- No items yet.
+ No items yet.
diff --git a/src/app/history/history.page.ts b/src/app/history/history.page.ts
index badf1c5..63bc101 100644
--- a/src/app/history/history.page.ts
+++ b/src/app/history/history.page.ts
@@ -1,69 +1,69 @@
import { CommonModule } from '@angular/common';
-import { Component, OnInit, inject } from '@angular/core';
-import { FormsModule } from '@angular/forms';
+import { Component, signal } from '@angular/core';
import {
- IonContent,
+ IonButton,
IonHeader,
- IonTitle,
- IonToolbar,
- IonList,
+ IonIcon,
IonItem,
IonLabel,
- IonButton,
- IonIcon,
+ IonList,
+ IonToolbar,
+ IonContent,
+ IonTitle,
+ ViewWillEnter,
} from '@ionic/angular/standalone';
-import { addIcons } from 'ionicons';
-import { trash, copy, time } from 'ionicons/icons';
-import { Storage, ScanEntry } from '../services/storage';
+import { StorageService } from '../services/storage.service';
@Component({
+ standalone: true,
selector: 'app-history',
templateUrl: './history.page.html',
- styleUrls: ['./history.page.scss'],
- standalone: true,
+
imports: [
- IonContent,
IonHeader,
- IonTitle,
- IonToolbar,
+ IonIcon,
+ IonButton,
IonList,
+ IonToolbar,
IonItem,
IonLabel,
- IonButton,
- IonIcon,
+ IonTitle,
+ IonContent,
CommonModule,
- FormsModule,
],
})
-export class HistoryPage implements OnInit {
- items: ScanEntry[] = [];
- loading = false;
+export class HistoryPage implements ViewWillEnter {
+ list = signal
>({}); // group by section
+ items: any = [];
- private store = inject(Storage);
-
- constructor() {
- addIcons({ trash, copy, time });
- }
-
- ngOnInit(): void {
+ constructor(private store: StorageService) {}
+ ionViewWillEnter(): void {
this.load();
}
-
async load() {
- this.loading = true;
- try {
- this.items = await this.store.getHistory();
- } finally {
- this.loading = false;
- }
+ this.items = (await this.store.all()) || [];
+ const sections: Record = {};
+ const today = new Date().toDateString();
+ const yesterday = new Date(Date.now() - 864e5).toDateString();
+
+ this.items.forEach((code: string | number | Date) => {
+ const key =
+ new Date(code).toDateString() === today
+ ? 'Today'
+ : new Date(code).toDateString() === yesterday
+ ? 'Yesterday'
+ : 'Last 7 Days';
+ sections[key] ??= [];
+ sections[key].push(code as unknown as string);
+ });
+ this.list.set(sections);
}
- async clear() {
- await this.store.clearHistory();
- await this.load();
+ clear() {
+ console.log(`clear`);
}
- copy(text: string) {
- if (navigator?.clipboard) navigator.clipboard.writeText(text).catch(() => {});
+ copy(content: string) {
+ console.log(`copied`);
}
}
diff --git a/src/app/home/home.page.html b/src/app/home/home.page.html
index 2356666..ed8b39e 100644
--- a/src/app/home/home.page.html
+++ b/src/app/home/home.page.html
@@ -1,8 +1,6 @@
-
- Blank
-
+ Blank
@@ -14,12 +12,19 @@
-
+
Scan Barcode/QR
-
+
View History
diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts
index 9de844d..7c0b43c 100644
--- a/src/app/home/home.page.ts
+++ b/src/app/home/home.page.ts
@@ -1,7 +1,14 @@
import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonIcon } from '@ionic/angular/standalone';
+import {
+ IonButton,
+ IonContent,
+ IonHeader,
+ IonIcon,
+ IonTitle,
+ IonToolbar,
+} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
-import { camera, time, list } from 'ionicons/icons';
+import { camera, list, time } from 'ionicons/icons';
@Component({
selector: 'app-home',
diff --git a/src/app/services/storage.service.ts b/src/app/services/storage.service.ts
new file mode 100644
index 0000000..5622ec4
--- /dev/null
+++ b/src/app/services/storage.service.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { Preferences } from '@capacitor/preferences';
+
+const KEY = 'history';
+
+@Injectable({ providedIn: 'root' })
+export class StorageService {
+ async all(): Promise {
+ const { value } = await Preferences.get({ key: KEY });
+ return value ? JSON.parse(value) : [];
+ }
+
+ async save(item: string) {
+ const list = await this.all();
+ list.unshift(item);
+ await Preferences.set({
+ key: KEY,
+ value: JSON.stringify(list.slice(0, 50)),
+ });
+ }
+
+ async clear() {
+ await Preferences.remove({ key: KEY });
+ }
+}
diff --git a/src/app/settings/settings.page.html b/src/app/settings/settings.page.html
index 003abd6..7e9c9f1 100644
--- a/src/app/settings/settings.page.html
+++ b/src/app/settings/settings.page.html
@@ -1,13 +1,45 @@
-
+
- settings
+ Settings
-
-
-
- settings
-
-
+
+
+ Preferences
+
+ App Theme
+ System
+
+
+ Language
+ English
+
+
+
+ Notifications
+
+ App Updates
+
+
+
+ Scan Notifications
+
+
+
+
+ Privacy
+
+ Data Management
+
+
+ Privacy Policy
+
+
+
+ About
+
+ App Version
+ 1.2.3
+
diff --git a/src/app/settings/settings.page.ts b/src/app/settings/settings.page.ts
index 83d889f..a0229f3 100644
--- a/src/app/settings/settings.page.ts
+++ b/src/app/settings/settings.page.ts
@@ -6,6 +6,11 @@ import {
IonHeader,
IonTitle,
IonToolbar,
+ IonNote,
+ IonToggle,
+ IonItem,
+ IonListHeader,
+ IonLabel,
} from '@ionic/angular/standalone';
@Component({
@@ -18,6 +23,11 @@ import {
IonHeader,
IonTitle,
IonToolbar,
+ IonNote,
+ IonToggle,
+ IonLabel,
+ IonItem,
+ IonListHeader,
CommonModule,
FormsModule,
],
diff --git a/src/app/tabs/tabs.page.html b/src/app/tabs/tabs.page.html
index b158e2c..0f51e5f 100644
--- a/src/app/tabs/tabs.page.html
+++ b/src/app/tabs/tabs.page.html
@@ -1,12 +1,12 @@
-
- Scan
+
+ Scan
-
+
History
diff --git a/src/app/tabs/tabs.page.ts b/src/app/tabs/tabs.page.ts
index dbf189c..5e9f42e 100644
--- a/src/app/tabs/tabs.page.ts
+++ b/src/app/tabs/tabs.page.ts
@@ -2,25 +2,23 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
- IonContent,
- IonHeader,
- IonTitle,
- IonToolbar,
- IonTabs,
- IonTabBar,
- IonTabButton,
IonIcon,
IonLabel,
+ IonTabBar,
+ IonTabButton,
+ IonTabs,
} from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+
+import { settingsOutline, refreshOutline, qrCodeOutline } from 'ionicons/icons';
+
@Component({
selector: 'app-tabs',
templateUrl: './tabs.page.html',
styleUrls: ['./tabs.page.scss'],
standalone: true,
imports: [
- IonContent,
- IonHeader,
IonTabs,
IonTabBar,
IonTabButton,
@@ -31,5 +29,11 @@ import {
],
})
export class TabsPage {
- constructor() {}
+ constructor() {
+ addIcons({
+ settingsOutline,
+ refreshOutline,
+ qrCodeOutline,
+ });
+ }
}
diff --git a/src/global.scss b/src/global.scss
index d43a041..121d308 100644
--- a/src/global.scss
+++ b/src/global.scss
@@ -40,3 +40,4 @@
body.scanner-active {
background: transparent !important;
}
+
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index 0004b83..169d8d5 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -171,3 +171,4 @@ ion-button {
ion-tab-bar {
--color-selected: var(--ion-color-primary);
}
+
\ No newline at end of file
From 40df96c46485cf63784faebc624d3f46f719ce64 Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Fri, 19 Sep 2025 01:12:51 -0300
Subject: [PATCH 12/13] =?UTF-8?q?chore(build):=20arquiva=20com=20destino?=
=?UTF-8?q?=20gen=C3=A9rico=20do=20iOS=20(-destination=20'generic/platform?=
=?UTF-8?q?=3DiOS')=20para=20evitar=20depend=C3=AAncia=20do=20device=20e?=
=?UTF-8?q?=20incompatibilidade=20de=20device=20support?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ExportOptions.plist | 13 ++++++++++++
build.sh | 43 +++++++++++++++++++++++++++++++++++++++
package.json | 3 ++-
scripts/patch-cap-ios.mjs | 27 ++++++++++++++++++++++++
4 files changed, 85 insertions(+), 1 deletion(-)
create mode 100644 ExportOptions.plist
create mode 100755 build.sh
create mode 100644 scripts/patch-cap-ios.mjs
diff --git a/ExportOptions.plist b/ExportOptions.plist
new file mode 100644
index 0000000..8b14c2f
--- /dev/null
+++ b/ExportOptions.plist
@@ -0,0 +1,13 @@
+
+
+
+
+ method
+ development
+ compileBitcode
+
+ signingStyle
+ automatic
+
+
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..a8e6194
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+set -e # fail fast
+
+### 0) Caminhos/nomes
+PROJECT_ROOT="$(pwd)" # raiz do app Ionic
+IOS_DIR="${PROJECT_ROOT}/ios/App"
+WORKSPACE_PATH="${IOS_DIR}/App.xcworkspace"
+SCHEME_NAME="App"
+ARCHIVE_PATH="${PROJECT_ROOT}/build/${SCHEME_NAME}.xcarchive"
+IPA_EXPORT_PATH="${PROJECT_ROOT}/build/ipa"
+EXPORT_OPTIONS_PLIST="${IOS_DIR}/ExportOptions.plist" # já existe no projeto iOS
+
+### 1) Build web + sync
+echo "📦 Building web assets…"
+# Patch conhecido do Capacitor iOS (milissegundos)
+node scripts/patch-cap-ios.mjs || true
+npm run build
+npx cap sync ios # copia www e plugins
+
+### 2) Limpar build anterior
+rm -rf "${PROJECT_ROOT}/build"
+mkdir -p "$IPA_EXPORT_PATH"
+
+### 3) Arquivar (Release)
+echo "🛠️ Archiving (${SCHEME_NAME})…"
+xcodebuild \
+ -workspace "$WORKSPACE_PATH" \
+ -scheme "$SCHEME_NAME" \
+ -configuration Release \
+ -sdk iphoneos \
+ -destination 'generic/platform=iOS' \
+ -archivePath "$ARCHIVE_PATH" \
+ archive
+
+### 4) Exportar IPA
+echo "📤 Exporting IPA…"
+xcodebuild \
+ -exportArchive \
+ -archivePath "$ARCHIVE_PATH" \
+ -exportOptionsPlist "$EXPORT_OPTIONS_PLIST" \
+ -exportPath "$IPA_EXPORT_PATH"
+
+echo "✅ IPA gerada em: $IPA_EXPORT_PATH"
diff --git a/package.json b/package.json
index 7607caf..1c9fa78 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
"test": "ng test",
"lint": "ng lint",
"splash:apply": "node scripts/apply-splash.mjs",
- "native:sync": "npx cap sync"
+ "native:sync": "npx cap sync",
+ "postinstall": "node scripts/patch-cap-ios.mjs"
},
"private": true,
"dependencies": {
diff --git a/scripts/patch-cap-ios.mjs b/scripts/patch-cap-ios.mjs
new file mode 100644
index 0000000..6e79b94
--- /dev/null
+++ b/scripts/patch-cap-ios.mjs
@@ -0,0 +1,27 @@
+#!/usr/bin/env node
+import { readFileSync, writeFileSync, existsSync } from 'node:fs';
+import { join } from 'node:path';
+
+const root = process.cwd();
+const files = [
+ join(root, 'node_modules', '@capacitor', 'ios', 'Capacitor', 'Capacitor', 'Codable', 'JSValueDecoder.swift'),
+ join(root, 'node_modules', '@capacitor', 'ios', 'Capacitor', 'Capacitor', 'Codable', 'JSValueEncoder.swift'),
+];
+
+let changed = 0;
+for (const file of files) {
+ if (!existsSync(file)) continue;
+ const src = readFileSync(file, 'utf8');
+ if (src.includes('MSEC_PER_SEC')) {
+ const out = src.replaceAll('MSEC_PER_SEC', '1000.0');
+ writeFileSync(file, out);
+ changed++;
+ console.log(`Patched: ${file}`);
+ } else {
+ // already patched or not needed
+ }
+}
+
+if (changed === 0) {
+ console.log('No iOS Capacitor patches needed.');
+}
From 9fb511e43f285322cd9df89a85e9c176e250408c Mon Sep 17 00:00:00 2001
From: Vagner Leite da Silva
Date: Fri, 19 Sep 2025 04:30:00 -0300
Subject: [PATCH 13/13] =?UTF-8?q?chore(ios/build):=20ajustes=20no=20Xcode?=
=?UTF-8?q?=20project=20e=20ExportOptions;=20patch=20autom=C3=A1tico=20do?=
=?UTF-8?q?=20Capacitor=20iOS=20(postinstall)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ios/App/App.xcodeproj/project.pbxproj | 41 ++++++++++++++++++++-------
ios/App/ExportOptions.plist | 14 ++++-----
scripts/patch-cap-ios.mjs | 34 ++++++++++++++++------
3 files changed, 64 insertions(+), 25 deletions(-)
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index 64375f6..e17cc0c 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 48;
+ objectVersion = 53;
objects = {
/* Begin PBXBuildFile section */
@@ -122,13 +122,13 @@
504EC2FC1FED79650016851F /* Project object */ = {
isa = PBXProject;
attributes = {
+ BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 1420;
+ LastUpgradeCheck = 1430;
TargetAttributes = {
504EC3031FED79650016851F = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
- ProvisioningStyle = Automatic;
};
};
};
@@ -267,7 +267,6 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -327,7 +326,6 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
@@ -343,7 +341,8 @@
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -353,19 +352,30 @@
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5H4TCPBPYP;
INFOPLIST_FILE = App/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = PinkCOdeScanner;
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.vagner.pinkcode;
+ "PRODUCT_BUNDLE_IDENTIFIER[sdk=*]" = com.vagner.pinkcode;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
@@ -374,18 +384,29 @@
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 5H4TCPBPYP;
INFOPLIST_FILE = App/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = PinkCOdeScanner;
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.vagner.pinkcode;
+ "PRODUCT_BUNDLE_IDENTIFIER[sdk=*]" = com.vagner.pinkcode;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
diff --git a/ios/App/ExportOptions.plist b/ios/App/ExportOptions.plist
index 4e3db35..a3db099 100644
--- a/ios/App/ExportOptions.plist
+++ b/ios/App/ExportOptions.plist
@@ -3,12 +3,12 @@
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
- methodad-hoc
- signingStylemanual
- provisioningProfiles
-
- com.vagner.pinkcodePINKCODE_AdHoc
-
- teamIDABCDE12345
+ method
+ development
+ compileBitcode
+
+ signingStyle
+ automatic
+
diff --git a/scripts/patch-cap-ios.mjs b/scripts/patch-cap-ios.mjs
index 6e79b94..70e38a3 100644
--- a/scripts/patch-cap-ios.mjs
+++ b/scripts/patch-cap-ios.mjs
@@ -3,22 +3,40 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
const root = process.cwd();
-const files = [
- join(root, 'node_modules', '@capacitor', 'ios', 'Capacitor', 'Capacitor', 'Codable', 'JSValueDecoder.swift'),
- join(root, 'node_modules', '@capacitor', 'ios', 'Capacitor', 'Capacitor', 'Codable', 'JSValueEncoder.swift'),
-];
+const decoder = join(root, 'node_modules', '@capacitor', 'ios', 'Capacitor', 'Capacitor', 'Codable', 'JSValueDecoder.swift');
+const encoder = join(root, 'node_modules', '@capacitor', 'ios', 'Capacitor', 'Capacitor', 'Codable', 'JSValueEncoder.swift');
+const files = [decoder, encoder];
let changed = 0;
for (const file of files) {
if (!existsSync(file)) continue;
const src = readFileSync(file, 'utf8');
- if (src.includes('MSEC_PER_SEC')) {
- const out = src.replaceAll('MSEC_PER_SEC', '1000.0');
+ let out = src;
+ let fileChanged = false;
+ if (out.includes('MSEC_PER_SEC')) {
+ out = out.replaceAll('MSEC_PER_SEC', '1000.0');
+ fileChanged = true;
+ }
+ if (file === encoder) {
+ // Ensure switch returns String in EncodingContainer.type
+ out = out.replace(
+ /case \.singleValue:\s*"SingleValueContainer"/m,
+ 'case .singleValue:\n return "SingleValueContainer"'
+ );
+ out = out.replace(
+ /case \.unkeyed:\s*"UnkeyedContainer"/m,
+ 'case .unkeyed:\n return "UnkeyedContainer"'
+ );
+ out = out.replace(
+ /case \.keyed:\s*"KeyedContainer"/m,
+ 'case .keyed:\n return "KeyedContainer"'
+ );
+ if (out !== src) fileChanged = true;
+ }
+ if (fileChanged) {
writeFileSync(file, out);
changed++;
console.log(`Patched: ${file}`);
- } else {
- // already patched or not needed
}
}