Merge pull request 'Commit' (#6) from feature/card-brand-logo into main

Reviewed-on: Loverde-Company-LTDA/Pedi-Foods-Skip#6
This commit is contained in:
2026-07-07 16:34:51 -03:00
293 changed files with 457 additions and 4919 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,642 +0,0 @@
# Atomenta API - Store Management & External Guide (Pedi Foods Edition)
Este documento é o guia definitivo e completo para parceiros e aplicações externas (como pedifoods.com.br) que consomem a API da plataforma Atomenta para gestão de lojas. **Esta API reflete 100% das funcionalidades do painel administrativo.**
## 🔐 Autenticação e Headers
Todas as chamadas devem usar autenticação híbrida.
### Headers Obrigatórios:
- **Accept**: `application/json`
- **Atomenta-Token**: `550e8400-e29b-41d4-a716-446655440008` (Escopo: Store Management)
- **Authorization**: `Bearer <JWT_TOKEN>`
---
## 🏬 Gestão de Identidade e Lojas
### 1. Listar Lojas do Usuário
**GET** `/api/store/list` - Retorna todas as lojas vinculadas ao token do usuário.
### 2. Informações Completas da Loja (Settings)
**GET** `/api/store/:storeId/info` - Retorna a configuração completa da loja, incluindo dados bancários, horários e flags de pagamento.
**Resposta Exemplo:**
```json
{
"error": false,
"result": {
"isOpen": true,
"statusLabel": "Aberto Agora",
"isManualOpen": true,
"fantasyName": "Pizzaria do Arantes",
"razaoSocial": "Loverde Co LTDA",
"document": "12.345.678/0001-99",
"documentType": "CNPJ",
"email": "loja@loverde.com.br",
"phone": "11988776655",
"paymentMethods": {
"paymentOnDelivery": true,
"acceptCash": true,
"acceptCreditVisa": true,
"acceptCreditMaster": true,
"acceptCreditElo": true,
"acceptVoucherAlelo": true,
"acceptPix": true
},
"bankInfo": {
"bankName": "Banco do Brasil",
"accountType": "Corrente",
"agency": "1234",
"account": "56789-0",
"pixKey": "12345678000199"
},
"openingHours": {
"monday": [{"open": "18:00", "close": "23:00"}],
"friday": [{"open": "11:00", "close": "15:00"}, {"open": "18:00", "close": "00:00"}]
},
"address": {
"street": "Av. Brasil, 1000",
"latitude": -23.55052,
"longitude": -46.633308
}
}
}
```
---
## 📊 Dashboard Real-time (Portal Parity)
### 3. Dashboard e Monitoramento
**GET** `/api/store/:storeId/dashboard`
**Resposta Detalhada (100% Parity):**
```json
{
"error": false,
"result": {
"orderFlow": {
"urgencys": [ { "id": "ORD-123", "clientName": "Daniel", "minutes": 45, "status": "PREPARING" } ],
"received": [ /* Pedidos Pendentes */ ],
"inProgress": [ /* Pedidos Em Preparo */ ],
"readyTo": [ /* Pedidos Prontos para Entrega/Retirada */ ],
"inRoute": [ /* Pedidos em Rota */ ],
"delivered": [ /* Pedidos Finalizados na Sessão */ ]
},
"payments": {
"totalSelledToday": 2450.00,
"growthToday": 15.5,
"avgTicket": 65.20,
"growthPeriod": 8.5
},
"alerts": {
"pendingOver3Minutes": 2,
"delayedInProgress": 1
}
}
}
```
---
## 🍴 Catálogo e Menu (Complex Payloads)
### 3.1 Catálogo Consolidado (App)
**GET** `/api/store/:storeId/catalog`
**Resposta:**
```json
{
"error": false,
"code": "STORE_CATALOG_RETRIEVED",
"result": [
{
"id": "cat_lanches",
"name": "Lanches",
"isPizzaCategory": false,
"products": [
{
"id": "prod_xburger",
"type": "prepared",
"name": "X-Burger",
"description": "Pão, carne e queijo",
"image": "/uploads/products/xburger.jpg",
"price": "25.00",
"addons": "yes",
"addonGroups": []
}
]
},
{
"id": "cat_pizzas",
"name": "Pizzas",
"isPizzaCategory": true,
"pizzaConfig": {
"sizes": [{ "id": "broto", "name": "Broto", "slices": 4, "maxFlavors": 1 }],
"doughs": [{ "id": "tradicional", "name": "Tradicional", "active": true }],
"crusts": [{ "id": "catupiry", "name": "Catupiry", "active": true, "priceModifier": "10.00" }]
},
"products": [
{
"id": "prod_pizza_calabresa",
"type": "pizza",
"name": "Pizza Calabresa",
"description": "Molho, mussarela e calabresa",
"image": "/uploads/products/pizza-calabresa.jpg",
"price": "0.00",
"pizzaPrices": {
"broto": "35.00",
"grande": "55.00"
},
"addons": "yes",
"addonGroups": []
}
]
}
]
}
```
**Regra de identificação de pizza:**
- Item pizza: `products[].type === "pizza"`.
- Configuração de montagem vem em `category.pizzaConfig`.
- Preço por tamanho vem em `product.pizzaPrices`.
### 4. Listar Categorias do Catálogo
**GET** `/api/store/:storeId/catalog/categories`
**Resposta:**
```json
{
"error": false,
"code": "CATEGORIES_RETRIEVED",
"result": [
{ "id": "cat_1", "name": "Pizzas", "isActive": true }
],
"message": "Categorias recuperadas com sucesso"
}
```
### 5. Criar Categoria (Ex.: Pizza)
**POST** `/api/store/:storeId/catalog/categories`
```json
{
"name": "Pizzas Salgadas",
"isPizzaCategory": true,
"pizzaConfig": {
"sizes": [
{ "id": "small", "name": "Broto", "slices": 4, "maxFlavors": 1 },
{ "id": "large", "name": "Grande", "slices": 8, "maxFlavors": 3 }
],
"doughs": [ { "id": "tradicional", "name": "Tradicional", "price": 0 } ],
"crusts": [ { "id": "catupiry", "name": "Borda de Catupiry", "price": 10.00 } ]
}
}
```
### 6. Atualizar Categoria
**PUT** `/api/store/:storeId/catalog/categories/:id`
### 7. Excluir Categoria
**DELETE** `/api/store/:storeId/catalog/categories/:id`
### 8. Produtos Tipo Combo ou Pizza
**POST** `/api/store/:storeId/catalog/products`
```json
{
"type": "pizza",
"name": "Pizza de Calabresa",
"categoryId": "cat_pizzas",
"pizzaPrices": {
"small": "35.00",
"large": "55.00"
},
"addonGroups": [
{
"name": "Remover Ingredientes",
"minSelectors": 0, "maxSelectors": 5,
"items": [ { "name": "Sem Cebola", "price": 0 } ]
}
]
}
```
---
## 📦 Histórico e Detalhes do Pedido
### 9. Histórico com Filtros
**GET** `/api/store/:storeId/orders/history?start=2024-01-01&end=2024-01-31&q=Ana`
**Objeto de Pedido Completo (result.list[0]):**
```json
{
"id": "order_guid",
"shortId": "1234",
"clientName": "Arantes Loverde",
"totalValue": 120.50,
"netValue": 112.40,
"pediFoodsFee": 1.00,
"status": "COMPLETED",
"createdAt": "2024-01-08T15:00:00Z",
"confirmOtp": "12345678",
"deliveryEstimate": "35-50 min",
"deliveryTypeLabel": "Entrega Própria",
"timeline": [
{ "status": "PENDING", "time": "15:00:00", "message": "Pedido criado" },
{ "status": "ACCEPTED", "time": "15:02:00", "message": "Chef aceitou o pedido" },
{ "status": "COMPLETED", "time": "15:45:00", "message": "Entregue ao cliente" }
],
"items": [
{ "name": "Pizza Grande", "price": 55.00, "choices": ["Borda Catupiry", "Meio Calabresa", "Meio Mussarela"] }
]
}
```
> [!IMPORTANT]
> O campo `confirmOtp` (8 dígitos) é o código que o motoboy deve utilizar para iniciar a confirmação. O `customerOtp` (4 dígitos do cliente) **NÃO** é retornado via API por questões de segurança.
```
### 10. Detalhes de um Pedido Individual
**GET** `/api/store/:storeId/orders/:orderId`
Retorna o objeto completo do pedido (parity 100% com o painel).
---
## 💰 Financeiro Detalhado
### 11. Resumo de Performance e Gateway
**GET** `/api/store/:storeId/financial/summary`
**Resposta:**
```json
{
"error": false,
"result": {
"performance": {
"totalSelledAllTime": 150230.50,
"avgTicket": 72.00
},
"gateway": {
"balance": { "balance": 1500.00, "available": 1200.00 },
"transfers": [
{ "id": "TRANS-1", "date": "2024-01-05", "value": 500.00, "status": "DONE" }
]
}
}
}
```
---
## ⭐ Reviews e Avaliações
### 12. Métricas e Respostas
**GET** `/api/store/:storeId/reviews`
**Resposta:**
```json
{
"error": false,
"result": {
"metrics": {
"averageRate": "4.1",
"totalReviews": 260,
"starsDistribution": { "5": 121, "4": 60, "3": 23, "2": 15, "1": 39 },
"itemFeedback": {
"flavor": 207,
"ingredients": 94,
"packaging": 35,
"temperature": 29,
"appearance": 23
},
"improvementFeedback": {
"quantity": 128,
"temperature": 89,
"packaging": 42,
"ingredients": 23,
"flavor": 13
},
"deliveryFeedback": {
"positive": 102,
"negative": 60
}
},
"list": [
{
"id": "rev_1",
"clientName": "Ana",
"rate": 5,
"message": "Melhor pizza!",
"reply": "Obrigado Ana!",
"repliedAt": "2024-01-08T16:00:00Z"
}
]
}
}
```
### 9. Contestar Avaliação
**POST** `/api/store/:storeId/reviews/:reviewId/dispute`
```json
{ "reason": "Cliente agressivo e mensagem falsa sobre o produto." }
```
---
## 🖼️ Upload de Ativos e Galeria
**POST** `/api/store/:storeId/assets/upload` (Form-data: `file`) -> Retorna URL absoluta.
**GET** `/api/store/:storeId/assets` -> Retorna lista de URLs absolutas de todas as imagens da loja (logo, capa e produtos).
---
## 🏷️ Reordenação de Catálogo
**POST** `/api/store/:storeId/catalog/products/reorder` (Payload: `{ "ids": ["p1", "p2", ...] }`)
**POST** `/api/store/:storeId/catalog/categories/reorder` (Payload: `{ "ids": ["c1", "c2", ...] }`)
---
## 🛵 Fluxo de Entrega Pedi Foods (2-Step OTP)
| Campo | Tipo | Descrição |
|---|---|---|
| `otp` | String | Legacy OTP (4 digits). |
| `confirmOtp` | String | **Novo:** Código de 8 dígitos para o motoboy. (Use este para o fluxo de 2 etapas) |
| `deliveryEstimate` | String | Tempo estimado de entrega (ex: "30-45 min"). |
| `pediFoodsFee` | Number | Taxa de serviço Pedi Foods calculada. |
## 8. Entrega e Confirmação (Motoboy Flow)
Novos endpoints públicos para confirmação de entrega em duas etapas.
### 8.1. Página de Confirmação
`GET /delivery/confirm/:orderId`
Renderiza a página para o motoboy iniciar o processo.
### 8.2. Validar Motoboy (Passo 1)
`POST /delivery/confirm/:orderId/validate-motoboy`
Valida o código de 8 dígitos (`confirmOtp`).
**Payload:**
```json
{ "otp": "12345678" }
```
### 8.3. Validar Cliente (Passo 2)
`POST /delivery/confirm/:orderId/validate-customer`
Valida o código de 4 dígitos (`customerOtp`) e finaliza o pedido (`COMPLETED`).
**Payload:**
```json
{ "otp": "1234" }
```
Para garantir a segurança da entrega, o sistema utiliza um fluxo de confirmação em duas etapas via página pública:
1. **URL de Confirmação**: `https://atomenta.loverde.com.br/delivery/confirm/:orderRealId`
2. **Passo 1 (Motoboy)**: O entregador insere o `confirmOtp` (8 dígitos) visível no painel da loja.
3. **Passo 2 (Cliente)**: Após validar o código do motoboy, o sistema solicita o `customerOtp` (4 dígitos) que o cliente possui em seu app/notificação.
4. **Finalização**: A validação bem-sucedida do segundo código altera o status do pedido para `COMPLETED` automaticamente.
Endpoints utilizados pela página pública (CORS habilitado para domínios parceiros):
- `POST /delivery/confirm/:orderId/validate-motoboy` { "otp": "String(8)" }
- `POST /delivery/confirm/:orderId/validate-customer` { "otp": "String(4)" }
---
## 🛒 Checkout e Criação de Pedido
### 10.0 Validar Endereço de Entrega (Checkout)
**POST** `/api/store/:storeId/delivery/validate-address`
Endpoint para validar cobertura de entrega no checkout, quando o cliente troca o endereço antes do pagamento.
**Payload:**
```json
{
"address": {
"street": "Avenida das Andorinhas",
"number": "477",
"neighborhood": "Jardim Andorinhas",
"city": "Campinas",
"state": "SP",
"zip": "13101-400",
"lat": -22.918,
"lng": -47.01813
}
}
```
**Resposta Exemplo:**
```json
{
"error": false,
"code": "DELIVERY_ADDRESS_VALIDATED",
"result": {
"deliveryAllowed": true,
"reasonCode": "DELIVERY_ALLOWED",
"reasonMessage": "Endereço válido para entrega",
"deliveryMode": "KM",
"distance": 1.3,
"deliveryFee": 8,
"deliveryTime": "35 min",
"sameCity": false,
"matchedKmRange": { "from": 1, "to": 2, "price": 8, "time": 35 },
"matchedNeighborhood": null
}
}
```
**Regras:**
- `KM`: aprova somente até o limite da última faixa + `0.5 km` de tolerância.
- `NEIGHBORHOOD`: reprova se a cidade do endereço for diferente da cidade da loja.
- `NEIGHBORHOOD`: aprova com bairro configurado ou com fallback de mesma cidade.
### 10. Criar Novo Pedido (Public)
**POST** `/api/public/orders`
Este endpoint é utilizado pelo front-end de checkout (Pedi Foods) para criar o pedido no sistema da loja.
**Payload:**
```json
{
"storeId": "store_123456",
"userId": "user_guid_or_guest",
"clientName": "João Silva",
"clientPhone": "11999999999",
"deliveryType": "DELIVERY", // ou "PICKUP"
"address": {
"street": "Rua das Flores",
"number": "123",
"neighborhood": "Centro",
"city": "São Paulo",
"state": "SP",
"zip": "01000-000",
"complement": "Apt 10",
"lat": -23.55,
"lng": -46.63
},
"paymentMethod": "PIX", // "CREDIT_CARD", "DEBIT_CARD", "CASH"
"itemsTotal": 50.00,
"deliveryFee": 5.00,
"serviceFee": 0.00,
"discount": 0.00,
"total": 55.00,
"items": [
{
"id": "prod_1",
"name": "Pizza",
"price": 50.00,
"qty": 1,
"image": "...",
"addons": [
{
"addonId": "addon_bacon",
"name": "Bacon",
"price": 4.00,
"qty": 2
}
]
}
]
}
```
**Regras para `items[].addons`:**
- `qty` do adicional é suportado (ex.: 2x bacon/ovo).
- Se `qty` não for enviado ou vier inválido, a API assume `1`.
- `qty` mínimo efetivo é `1`.
**Resposta Sucesso:**
```json
{
"error": false,
"result": {
"id": "order_guid",
"shortId": "1234",
"status": "PAYMENT_PENDING",
"paymentStatus": "PENDING",
"paymentMethod": "PIX",
"paymentPayload": {
"copyPaste": "000201010212...",
"qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAA...",
"expirationDate": "2026-02-14T22:30:00.000Z"
},
"payment": {
"method": "PIX",
"status": "PENDING",
"pix": {
"copyPaste": "000201010212...",
"qrCodeImage": "iVBORw0KGgoAAAANSUhEUgAA...",
"expirationDate": "2026-02-14T22:30:00.000Z"
}
}
}
}
```
**Observação de segurança:** o Atomenta não retorna links de checkout/fatura do Asaas no response de criação de pedido.
### 10.1 Realtime de Status de Pedido (Socket.IO)
Para atualização em tempo real do checkout/app (sem depender de push), usar Socket.IO.
**Socket endpoint:**
- `ws://<host>:<port>/socket.io/` (usar `wss://` em produção com TLS)
**Auth no handshake:**
- Enviar JWT no campo `auth.token`.
- Exemplo: `Bearer <JWT_TOKEN>`.
**Evento para o cliente:**
- `order_update`
**Payload típico do evento:**
```json
{
"id": "order_guid",
"shortId": "1234",
"storeId": "store_123456",
"userId": "user_guid",
"status": "CONFIRMED",
"paymentStatus": "CONFIRMED",
"updatedAt": "2026-02-15T20:10:00.000Z"
}
```
### 10.2 Fallback de Status (Polling)
Caso não haja conexão socket, consultar status por HTTP até estado final.
**Endpoint:**
- `GET /api/public/orders/:orderId`
**Recomendação de intervalo:**
- 0-60s: `3s`
- 60-180s: `5s`
- acima de 180s: `10s`
- parar em status final (`COMPLETED`, `CANCELED`, `REFUNDED`).
### 11. Listar Lojas (Public App)
**GET** `/api/public/stores`
Endpoint utilizado pelo aplicativo Pedi Foods para listar todas as lojas disponíveis para o cliente.
**Resposta Sucesso:**
```json
{
"error": false,
"result": [
{
"id": "company_guid",
"storeId": "store_123456",
"name": "Pizzaria do Arantes",
"logo": "https://...",
"cover": "https://...",
"category": "Restaurante",
"isOpen": true,
"statusLabel": "Aberto Agora",
"rating": 4.8,
"deliveryTime": "30-45 min",
"deliveryFee": 5.00
}
]
}
```
---
## 📝 Gestão de Contratos (Store)
### 12. Verificar Status do Contrato
**GET** `/api/store/:storeId/contract/status`
Retorna o ID e o status do contrato mais recente da loja.
**Resposta Exemplo:**
```json
{
"error": false,
"result": {
"id": "contract_guid",
"status": "draft" // ou "signed"
}
}
```
### 13. Assinar Contrato
**POST** `/api/store/:storeId/contract/sign`
Assina o contrato draft atual da loja.
**Payload:**
```json
{
"method": "typed",
"typedName": "João Silva", // Obrigatório se method === "typed"
"signatureDataUrl": "data:image/png;base64,..." // Obrigatório se method === "draw"
}
```
### 14. Visualizar PDF do Contrato
**GET** `/api/store/contract/:contractId/pdf`
Retorna o stream do arquivo PDF do contrato (draft ou assinado).
Autor: Daniel Arantes Loverde

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 835 B

After

Width:  |  Height:  |  Size: 835 B

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 667 KiB

After

Width:  |  Height:  |  Size: 667 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 406 KiB

After

Width:  |  Height:  |  Size: 406 KiB

View File

Before

Width:  |  Height:  |  Size: 940 KiB

After

Width:  |  Height:  |  Size: 940 KiB

View File

Before

Width:  |  Height:  |  Size: 799 KiB

After

Width:  |  Height:  |  Size: 799 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -185,6 +185,9 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
@@ -199,6 +202,9 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
@@ -267,7 +273,7 @@
repositoryURL = "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.10;
minimumVersion = 1.0.11;
};
};
/* End XCRemoteSwiftPackageReference section */

View File

@@ -1,8 +0,0 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

121
README.md
View File

@@ -1,99 +1,62 @@
# ⚠️ INFORMAÇÕES CONFIDENCIAIS E PROPRIEDADE INTELECTUAL ⚠️
# PediFoods
## 🚨 AVISO LEGAL IMPORTANTE
This is a [Skip](https://skip.tools) dual-platform app project.
**ATENÇÃO: Este repositório contém informações CONFIDENCIAIS e PROPRIETÁRIAS.**
### ⛔ PROIBIÇÕES ABSOLUTAS
<!-- TODO: add iOS screenshots to fastlane metadata
## iPhone Screenshots
- **NÃO** compartilhe este código com terceiros
- **NÃO** faça fork, clone ou cópia sem autorização expressa
- **NÃO** utilize qualquer parte deste código em outros projetos
- **NÃO** distribua, venda ou comercialize este software
- **NÃO** reverse engineer ou descompile o código
- **NÃO** acesse sem permissão explícita do proprietário
<img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/1_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/2_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/3_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/4_en-US.png" style="width: 18%" /> <img alt="iPhone Screenshot" src="Darwin/fastlane/screenshots/en-US/5_en-US.png" style="width: 18%" />
-->
### 🔒 DIREITOS DE PROPRIEDADE INTELECTUAL
<!-- TODO: add Android screenshots to fastlane metadata
## Android Screenshots
Este software é propriedade exclusiva de **Daniel Arantes Loverde** e está protegido por:
<img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png" style="width: 18%" /> <img alt="Android Screenshot" src="Android/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png" style="width: 18%" />
-->
- **Lei de Propriedade Intelectual (Lei 9.610/98)**
- **Lei de Software (Lei 9.609/98)**
- **Lei Geral de Proteção de Dados (LGPD - Lei 13.709/18)**
- **Direitos Autorais e Patentes**
## Building
### ⚖️ CONSEQUÊNCIAS LEGAIS
This project is both a stand-alone Swift Package Manager module,
as well as an Xcode project that builds and translates the project
into a Kotlin Gradle project for Android using the skipstone plugin.
O acesso não autorizado, uso indevido, cópia ou distribuição deste código constitui:
Building the module requires that Skip be installed using
[Homebrew](https://brew.sh) with `brew install skiptools/skip/skip`.
- **CRIME** de violação de propriedade intelectual
- **CRIME** de roubo de propriedade intelectual
- **CRIME** de violação de direitos autorais
- **CRIME** de acesso não autorizado a sistema informatizado
This will also install the necessary Skip prerequisites:
Kotlin, Gradle, and the Android build tools.
**Penas previstas: 2 a 5 anos de reclusão + multa**
Installation prerequisites can be confirmed by running
`skip checkup`. The project can be validated with `skip verify`.
### 🛡️ MEDIDAS DE SEGURANÇA
## Running
- **Monitoramento**: Todo acesso é registrado e monitorado
- **Rastreamento**: IPs e atividades são rastreados
- **Logs**: Sistema completo de auditoria
- **Criptografia**: Dados protegidos com AES-256
- **Autenticação**: Acesso restrito com JWT + OTP
Xcode and Android Studio must be downloaded and installed in order to
run the app in the iOS simulator / Android emulator.
An Android emulator must already be running, which can be launched from
Android Studio's Device Manager.
### 📧 CONTATO AUTORIZADO
The project can be opened and run in Xcode from
`Project.xcworkspace`, which also enabled parallel
development of any Skip libary dependencies.
**ÚNICO CONTATO VÁLIDO:**
- **Email**: daniel@loverde.com.br
- **Autorização**: Apenas via email oficial
To run both the Swift and Kotlin apps simultaneously,
launch the "PediFoods App" target from Xcode.
A build phases runs the "Launch Android APK" script that
will deploy the Skip app to a running Android emulator or connected device.
Logging output for the iOS app can be viewed in the Xcode console, and in
Android Studio's logcat tab for the transpiled Kotlin app, or
using `adb logcat` from a terminal.
### 🔐 CLASSIFICAÇÃO
## Testing
**NÍVEL DE CONFIDENCIALIDADE: MÁXIMO**
- **Restrito**: Apenas para o proprietário
- **Não Divulgar**: Informações proprietárias
- **Não Compartilhar**: Código fonte protegido
The module can be tested using the standard `swift test` command
or by running the test target for the macOS destination in Xcode,
which will run the Swift tests as well as the transpiled
Kotlin JUnit tests in the Robolectric Android simulation environment.
---
## ⚠️ DISCLAIMER FINAL
**Ao acessar este repositório, você concorda que:**
1. **Reconhece** que este é um software proprietário
2. **Compromete-se** a não violar os direitos de propriedade intelectual
3. **Aceita** as consequências legais por qualquer violação
4. **Entende** que o acesso é monitorado e rastreado
5. **Concorda** em respeitar todas as leis aplicáveis
---
## Sobre este repositório
App **PediFoods iOS** — projeto [Skip](https://skip.tools) dual-platform em
Swift/SwiftUI (`pedi-foods/`), transpilado para Android via skipstone. É o app
de produção e a **fonte da verdade** para requests, responses e comportamento;
o port nativo Android é construído em paridade com ele.
Build e execução: ver `pedi-foods/README.md` (Skip, `skip checkup`/`skip verify`,
Xcode + emulador Android). Testes via `swift test` / `skip test`.
---
## Fluxo Git (desenvolvimento)
- Ao terminar cada task ou fatia lógica (ex.: fatia de checkout, ajuste de tela, correção de testes): fazer **commit imediato** com mensagem clara (`feat:`, `fix:`, `chore:`).
- Não commitar artefactos de build (`.build/`, saída do skipstone, `Darwin/` gerado) nem ficheiros de estado local irrelevantes para o projeto rodar a partir do código-fonte, salvo pedido explícito.
- O agente faz commit local; o push é sempre feito pelo Daniel após revisão.
---
**© 2024 Daniel Arantes Loverde. Todos os direitos reservados.**
**Última atualização: Junho 2026**
---
*Este documento é parte integrante do software e deve ser mantido confidencial.*
Parity testing can be performed with `skip test`,
which will output a table of the test results for both platforms.
Autor: Daniel Arantes Loverde

View File

@@ -6,9 +6,20 @@ import UIKit
import AppKit
#endif
enum ImageFitMode {
/// Scale to cover the whole box, cropping whichever axis overflows.
/// Default matches historical behavior for every image in the app.
case fill
/// Scale so height always matches the box exactly; width follows the
/// source's aspect ratio and gets cropped/gapped on the sides. Opt-in
/// only used by the Store Detail cover header.
case heightFit
}
struct CachedRemoteImage<Placeholder: View>: View {
let imageURL: String?
let ttl: TimeInterval
let fitMode: ImageFitMode
let placeholder: Placeholder
#if canImport(UIKit) || canImport(AppKit)
@@ -18,48 +29,50 @@ struct CachedRemoteImage<Placeholder: View>: View {
init(
imageURL: String?,
ttl: TimeInterval = AppCacheTTL.twoHours,
fitMode: ImageFitMode = .fill,
@ViewBuilder placeholder: () -> Placeholder
) {
self.imageURL = imageURL
self.ttl = ttl
self.fitMode = fitMode
self.placeholder = placeholder()
}
var body: some View {
Group {
GeometryReader { geometry in
Group {
#if canImport(UIKit)
if let uiImage = loader.uiImage {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
} else {
placeholder
}
#elseif canImport(AppKit)
if let nsImage = loader.nsImage {
Image(nsImage: nsImage)
.resizable()
.scaledToFill()
} else {
placeholder
}
#else
if let imageURL,
let url = URL(string: imageURL) {
AsyncImage(url: url) { phase in
switch phase {
case .success(let image):
image
.resizable()
.scaledToFill()
default:
placeholder
}
if let uiImage = loader.uiImage {
rendered(image: Image(uiImage: uiImage), pixelSize: uiImage.size, in: geometry.size)
} else {
placeholder
}
#elseif canImport(AppKit)
if let nsImage = loader.nsImage {
rendered(image: Image(nsImage: nsImage), pixelSize: nsImage.size, in: geometry.size)
} else {
placeholder
}
#else
if let imageURL,
let url = URL(string: imageURL) {
AsyncImage(url: url) { phase in
switch phase {
case .success(let image):
image
.resizable()
.scaledToFill()
default:
placeholder
}
}
} else {
placeholder
}
} else {
placeholder
}
#endif
}
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
.onAppear {
#if canImport(UIKit) || canImport(AppKit)
@@ -72,6 +85,25 @@ struct CachedRemoteImage<Placeholder: View>: View {
#endif
}
}
#if canImport(UIKit) || canImport(AppKit)
@ViewBuilder
private func rendered(image: Image, pixelSize: CGSize, in containerSize: CGSize) -> some View {
switch fitMode {
case .fill:
image
.resizable()
.scaledToFill()
case .heightFit:
let aspect = pixelSize.height > 0 ? pixelSize.width / pixelSize.height : 1
let renderWidth = containerSize.height * aspect
image
.resizable()
.frame(width: renderWidth, height: containerSize.height)
.frame(width: containerSize.width, height: containerSize.height)
}
}
#endif
}
#if canImport(UIKit) || canImport(AppKit)

View File

@@ -215,6 +215,9 @@
},
"Atualizando status do pedido..." : {
},
"Atualizando..." : {
},
"Avaliar Pedido" : {
@@ -825,9 +828,6 @@
"O que vai querer \npedir hoje?" : {
"comment" : "A title displayed above the search bar in the home view.",
"isCommentAutoGenerated" : true
},
"O que vai querer pedir hoje?\n " : {
},
"O que você achou do pedido?" : {
"comment" : "A question prompt asking users to rate their experience with the order.",

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 835 B

After

Width:  |  Height:  |  Size: 835 B

View File

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Some files were not shown because too many files have changed in this diff Show More