Merge pull request 'Commit' (#6) from feature/card-brand-logo into main
Reviewed-on: Loverde-Company-LTDA/Pedi-Foods-Skip#6
0
pedi-foods/.gitignore → .gitignore
vendored
1288
API_Mobile_App.md
@@ -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
|
|
||||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 835 B After Width: | Height: | Size: 835 B |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 667 KiB After Width: | Height: | Size: 667 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 406 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 924 KiB After Width: | Height: | Size: 924 KiB |
|
Before Width: | Height: | Size: 951 KiB After Width: | Height: | Size: 951 KiB |
|
Before Width: | Height: | Size: 940 KiB After Width: | Height: | Size: 940 KiB |
|
Before Width: | Height: | Size: 926 KiB After Width: | Height: | Size: 926 KiB |
|
Before Width: | Height: | Size: 799 KiB After Width: | Height: | Size: 799 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -185,6 +185,9 @@
|
|||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@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;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -199,6 +202,9 @@
|
|||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@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;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -267,7 +273,7 @@
|
|||||||
repositoryURL = "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git";
|
repositoryURL = "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 1.0.10;
|
minimumVersion = 1.0.11;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
121
README.md
@@ -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
|
<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%" />
|
||||||
- **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
|
|
||||||
|
|
||||||
### 🔒 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)**
|
## Building
|
||||||
- **Lei de Software (Lei 9.609/98)**
|
|
||||||
- **Lei Geral de Proteção de Dados (LGPD - Lei 13.709/18)**
|
|
||||||
- **Direitos Autorais e Patentes**
|
|
||||||
|
|
||||||
### ⚖️ 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
|
This will also install the necessary Skip prerequisites:
|
||||||
- **CRIME** de roubo de propriedade intelectual
|
Kotlin, Gradle, and the Android build tools.
|
||||||
- **CRIME** de violação de direitos autorais
|
|
||||||
- **CRIME** de acesso não autorizado a sistema informatizado
|
|
||||||
|
|
||||||
**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
|
Xcode and Android Studio must be downloaded and installed in order to
|
||||||
- **Rastreamento**: IPs e atividades são rastreados
|
run the app in the iOS simulator / Android emulator.
|
||||||
- **Logs**: Sistema completo de auditoria
|
An Android emulator must already be running, which can be launched from
|
||||||
- **Criptografia**: Dados protegidos com AES-256
|
Android Studio's Device Manager.
|
||||||
- **Autenticação**: Acesso restrito com JWT + OTP
|
|
||||||
|
|
||||||
### 📧 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:**
|
To run both the Swift and Kotlin apps simultaneously,
|
||||||
- **Email**: daniel@loverde.com.br
|
launch the "PediFoods App" target from Xcode.
|
||||||
- **Autorização**: Apenas via email oficial
|
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**
|
The module can be tested using the standard `swift test` command
|
||||||
- **Restrito**: Apenas para o proprietário
|
or by running the test target for the macOS destination in Xcode,
|
||||||
- **Não Divulgar**: Informações proprietárias
|
which will run the Swift tests as well as the transpiled
|
||||||
- **Não Compartilhar**: Código fonte protegido
|
Kotlin JUnit tests in the Robolectric Android simulation environment.
|
||||||
|
|
||||||
---
|
Parity testing can be performed with `skip test`,
|
||||||
|
which will output a table of the test results for both platforms.
|
||||||
## ⚠️ 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.*
|
|
||||||
|
|
||||||
Autor: Daniel Arantes Loverde
|
Autor: Daniel Arantes Loverde
|
||||||
|
|||||||
@@ -6,9 +6,20 @@ import UIKit
|
|||||||
import AppKit
|
import AppKit
|
||||||
#endif
|
#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 {
|
struct CachedRemoteImage<Placeholder: View>: View {
|
||||||
let imageURL: String?
|
let imageURL: String?
|
||||||
let ttl: TimeInterval
|
let ttl: TimeInterval
|
||||||
|
let fitMode: ImageFitMode
|
||||||
let placeholder: Placeholder
|
let placeholder: Placeholder
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
#if canImport(UIKit) || canImport(AppKit)
|
||||||
@@ -18,48 +29,50 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
|||||||
init(
|
init(
|
||||||
imageURL: String?,
|
imageURL: String?,
|
||||||
ttl: TimeInterval = AppCacheTTL.twoHours,
|
ttl: TimeInterval = AppCacheTTL.twoHours,
|
||||||
|
fitMode: ImageFitMode = .fill,
|
||||||
@ViewBuilder placeholder: () -> Placeholder
|
@ViewBuilder placeholder: () -> Placeholder
|
||||||
) {
|
) {
|
||||||
self.imageURL = imageURL
|
self.imageURL = imageURL
|
||||||
self.ttl = ttl
|
self.ttl = ttl
|
||||||
|
self.fitMode = fitMode
|
||||||
self.placeholder = placeholder()
|
self.placeholder = placeholder()
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
GeometryReader { geometry in
|
||||||
|
Group {
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
if let uiImage = loader.uiImage {
|
if let uiImage = loader.uiImage {
|
||||||
Image(uiImage: uiImage)
|
rendered(image: Image(uiImage: uiImage), pixelSize: uiImage.size, in: geometry.size)
|
||||||
.resizable()
|
} else {
|
||||||
.scaledToFill()
|
placeholder
|
||||||
} else {
|
}
|
||||||
placeholder
|
#elseif canImport(AppKit)
|
||||||
}
|
if let nsImage = loader.nsImage {
|
||||||
#elseif canImport(AppKit)
|
rendered(image: Image(nsImage: nsImage), pixelSize: nsImage.size, in: geometry.size)
|
||||||
if let nsImage = loader.nsImage {
|
} else {
|
||||||
Image(nsImage: nsImage)
|
placeholder
|
||||||
.resizable()
|
}
|
||||||
.scaledToFill()
|
#else
|
||||||
} else {
|
if let imageURL,
|
||||||
placeholder
|
let url = URL(string: imageURL) {
|
||||||
}
|
AsyncImage(url: url) { phase in
|
||||||
#else
|
switch phase {
|
||||||
if let imageURL,
|
case .success(let image):
|
||||||
let url = URL(string: imageURL) {
|
image
|
||||||
AsyncImage(url: url) { phase in
|
.resizable()
|
||||||
switch phase {
|
.scaledToFill()
|
||||||
case .success(let image):
|
default:
|
||||||
image
|
placeholder
|
||||||
.resizable()
|
}
|
||||||
.scaledToFill()
|
}
|
||||||
default:
|
} else {
|
||||||
placeholder
|
placeholder
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
placeholder
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
|
.clipped()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
#if canImport(UIKit) || canImport(AppKit)
|
||||||
@@ -72,6 +85,25 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
|||||||
#endif
|
#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)
|
#if canImport(UIKit) || canImport(AppKit)
|
||||||
@@ -215,6 +215,9 @@
|
|||||||
},
|
},
|
||||||
"Atualizando status do pedido..." : {
|
"Atualizando status do pedido..." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Atualizando..." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Avaliar Pedido" : {
|
"Avaliar Pedido" : {
|
||||||
|
|
||||||
@@ -825,9 +828,6 @@
|
|||||||
"O que vai querer \npedir hoje?" : {
|
"O que vai querer \npedir hoje?" : {
|
||||||
"comment" : "A title displayed above the search bar in the home view.",
|
"comment" : "A title displayed above the search bar in the home view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
|
||||||
"O que vai querer pedir hoje?\n " : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"O que você achou do pedido?" : {
|
"O que você achou do pedido?" : {
|
||||||
"comment" : "A question prompt asking users to rate their experience with the order.",
|
"comment" : "A question prompt asking users to rate their experience with the order.",
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 835 B After Width: | Height: | Size: 835 B |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |