Skip and docs
358
API_Mobile_App.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# Atomenta Mobile App API Documentation
|
||||
|
||||
## 🔐 Authentication & Headers
|
||||
|
||||
The mobile app accesses distinct sets of APIs:
|
||||
1. **App APIs (`/api/app/*`)**: For general app features like Home Screen and Orders listing.
|
||||
2. **Customer APIs (`/api/customer/*`)**: For User Registration, Login, and Profile Management.
|
||||
3. **Store External APIs (`/api/store/*`)**: For store-specific operations like Catalog and Checkout.
|
||||
|
||||
### Common Headers
|
||||
All requests should ideally include:
|
||||
|
||||
```http
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
### Authentication Strategies
|
||||
|
||||
#### 1. Customer Auth (for App & Customer APIs)
|
||||
Used for everything related to the logged-in user (Profile, Orders, etc).
|
||||
- **Header:** `Authorization: Bearer <Users_JWT_Token>`
|
||||
- **Note:** Obtained via `/api/customer/login`.
|
||||
|
||||
#### 3. Hybrid Store Auth (for Store External APIs)
|
||||
Used when interacting with a specific store (Catalog, Checkout).
|
||||
- **Header 1:** `Atomenta-Token: 550e8400-e29b-41d4-a716-446655440008` (Store Module ID)
|
||||
- **Header 2:** `Authorization: Bearer <Users_JWT_Token>` (Required for Checkout/Orders)
|
||||
|
||||
---
|
||||
|
||||
## 👤 Customer Management
|
||||
|
||||
### 1. Register Customer
|
||||
**POST** `/api/customer`
|
||||
|
||||
**Headers:**
|
||||
- `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a` (Customer Module ID)
|
||||
|
||||
**Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Daniel Loverde",
|
||||
"email": "daniel@example.com",
|
||||
"phoneNumber": "+5511999999999",
|
||||
"birthDate": "1990-01-01T00:00:00Z" // Opcional
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"code": "CUSTOMER_CREATED",
|
||||
"result": {
|
||||
"id": "cust_uuid...",
|
||||
"name": "Daniel Loverde",
|
||||
"email": "daniel@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Login (Get Token)
|
||||
**POST** `/api/customer/login`
|
||||
|
||||
**Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "daniel@example.com",
|
||||
"otp": "123456" // Em dev/demo, apenas email pode bastar se configurado assim
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"code": "LOGIN_SUCCESS",
|
||||
"result": {
|
||||
"token": "eyJhbGciOi...", // <--- Use as Bearer Token for other requests
|
||||
"customer": {
|
||||
"id": "cust_uuid...",
|
||||
"name": "Daniel Loverde",
|
||||
"email": "daniel@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Get Profile
|
||||
**GET** `/api/customer/profile`
|
||||
|
||||
**Headers:**
|
||||
|
||||
- `Authorization: Bearer <JWT_Token>`
|
||||
- `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"code": "CUSTOMER_PROFILE_RETRIEVED",
|
||||
"result": {
|
||||
"id": "cust_uuid...",
|
||||
"name": "Daniel Loverde",
|
||||
"email": "daniel@example.com",
|
||||
"phoneNumber": "+5511999999999",
|
||||
"favorites": ["store_abc...", "store_xyz..."],
|
||||
"address_book": [],
|
||||
"behavioral_stats": {
|
||||
"total_orders": 5,
|
||||
"avg_ticket_size": 45.00
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update Profile
|
||||
**POST** `/api/customer/:id`
|
||||
|
||||
**Headers:**
|
||||
|
||||
- `Authorization: Bearer <JWT_Token>`
|
||||
- `Atomenta-Token: 550e8400-e29b-41d4-a716-44665544000a`
|
||||
|
||||
**Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Daniel A. Loverde", // Fields to update
|
||||
"phoneNumber": "+5511988888888",
|
||||
"biometricsEnabled": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏠 App Home Screen
|
||||
|
||||
### List Stores (Smart Listing)
|
||||
**GET** `/api/app/stores`
|
||||
|
||||
**Headers:**
|
||||
- `Authorization: Bearer <Users_JWT_Token>` (Customer obrigatório)
|
||||
|
||||
Lists stores based on user location, filtered by distance.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `lat` (Required): User Latitude (e.g., `-23.550520`)
|
||||
- `lng` (Required): User Longitude (e.g., `-46.633308`)
|
||||
- `category` (Optional): Filter by category name (e.g., `Lanches`)
|
||||
- `search` (Optional): Search by store name
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Sem login do customer, a listagem é bloqueada.
|
||||
- Se `lat/lng` não forem enviados, a API tenta usar o endereço salvo no perfil do customer (`address_book.lat_long`).
|
||||
- Lojas fora do raio/bairro de entrega não são exibidas.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"result": [
|
||||
{
|
||||
"id": "store_123...",
|
||||
"name": "Burger King",
|
||||
"logo": "https://...",
|
||||
"cover": "https://...",
|
||||
"category": "Lanches",
|
||||
"rating": 4.8,
|
||||
"deliveryTime": "30-45 min",
|
||||
"deliveryFee": 5.99,
|
||||
"distance": 1.2, // km
|
||||
"isOpen": true,
|
||||
"statusLabel": "Aberto"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### List User Orders
|
||||
**GET** `/api/app/orders`
|
||||
|
||||
**Headers:**
|
||||
|
||||
- `Authorization: Bearer <JWT>`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"result": [
|
||||
{
|
||||
"id": "ord_123...",
|
||||
"total": 54.90,
|
||||
"status": "completed",
|
||||
"createdAt": "2024-03-20T10:00:00Z",
|
||||
"items": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏪 Store Integration (External API)
|
||||
|
||||
**Base URL:** `/api/store/:storeId`
|
||||
|
||||
### 1. Store Identity & Status
|
||||
**GET** `/api/store/:storeId/identity`
|
||||
|
||||
**Headers/Query:**
|
||||
|
||||
- `x-user-lat` / `lat`: User Latitude
|
||||
- `x-user-lng` / `lng`: User Longitude
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"result": {
|
||||
"fantasyName": "Mc Donalds",
|
||||
"distance": 2.5,
|
||||
"deliveryTime": "30-45 min",
|
||||
"minOrder": 15.00
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Full Store Info
|
||||
**GET** `/api/store/:storeId/info`
|
||||
|
||||
Returns detailed info including operating hours and accepted payment methods.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"code": "STORE_INFO_RETRIEVED",
|
||||
"result": {
|
||||
"isOpen": true,
|
||||
"paymentMethods": {
|
||||
"paymentOnDelivery": true,
|
||||
"paymentOnPickup": true,
|
||||
"acceptCash": true,
|
||||
"acceptPix": true,
|
||||
"acceptCreditCard": true, // Resumo Geral
|
||||
"acceptDebitCard": true, // Resumo Geral
|
||||
"brands": {
|
||||
"credit": {
|
||||
"visa": true,
|
||||
"master": true,
|
||||
"elo": true,
|
||||
"amex": false,
|
||||
"hipercard": false
|
||||
},
|
||||
"debit": {
|
||||
"visa": true,
|
||||
"master": true,
|
||||
"elo": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"openingHours": { ... },
|
||||
"address": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Product Catalog
|
||||
**GET** `/api/store/:storeId/catalog`
|
||||
|
||||
Returns categories with their respective products.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"result": [
|
||||
{
|
||||
"id": "cat_1...",
|
||||
"name": "Burgers",
|
||||
"products": [
|
||||
{
|
||||
"id": "prod_1...",
|
||||
"name": "Big Mac",
|
||||
"price": 25.90,
|
||||
"image": "https://...",
|
||||
"addonGroups": [...]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Order (Checkout)
|
||||
**POST** `/api/store/:storeId/orders`
|
||||
|
||||
**Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"customer": {
|
||||
"name": "Daniel Loverde",
|
||||
"phone": "11999999999",
|
||||
"email": "daniel@example.com",
|
||||
"asaasId": "cus_000005165985" // Opcional.
|
||||
},
|
||||
// Nota: O usuário DEVE estar autenticado via Bearer Token.
|
||||
// Os dados de 'customer' enviados aqui são usados preferencialmente para
|
||||
// registrar os dados de entrega/cobrança deste pedido específico no Asaas,
|
||||
// mas o 'userId' e o histórico são vinculados ao token do usuário logado.
|
||||
"items": [
|
||||
{
|
||||
"productId": "prod_123",
|
||||
"qty": 2,
|
||||
"price": 25.00,
|
||||
"addons": []
|
||||
}
|
||||
],
|
||||
"total": 50.00,
|
||||
"paymentMethod": "PIX", // "PIX" | "CREDIT_CARD" | "DEBIT_CARD" | "MONEY"
|
||||
"deliveryType": "DELIVERY", // "DELIVERY" | "PICKUP"
|
||||
"address": {
|
||||
"street": "Rua Exemplo",
|
||||
"number": "123",
|
||||
"neighborhood": "Centro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"code": "ORDER_CREATED",
|
||||
"result": {
|
||||
"id": "ord_987...",
|
||||
"status": "created",
|
||||
"paymentPayload": "https://www.asaas.com/i/..." // Link para pagamento (Pix/Boleto)
|
||||
}
|
||||
}
|
||||
```
|
||||
389
API_Store_External.md
Normal file
@@ -0,0 +1,389 @@
|
||||
# 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)
|
||||
|
||||
### 4. Categorias de 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 } ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 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
|
||||
|
||||
### 6. 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.
|
||||
```
|
||||
|
||||
### 7. Detalhes de um Pedido Individual
|
||||
**GET** `/api/store/:storeId/orders/:orderId`
|
||||
|
||||
Retorna o objeto completo do pedido (parity 100% com o painel).
|
||||
|
||||
---
|
||||
|
||||
## 💰 Financeiro Detalhado
|
||||
|
||||
### 7. 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
|
||||
|
||||
### 8. 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. 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": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Resposta Sucesso:**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": false,
|
||||
"result": {
|
||||
"id": "order_guid",
|
||||
"shortId": "1234",
|
||||
"status": "PENDING",
|
||||
"paymentStatus": "PENDING",
|
||||
"paymentPayload": "..." // Se PIX (Big String Base64 ou Copia e Cola)
|
||||
}
|
||||
}
|
||||
```
|
||||
76
pedi-foods/.gitignore
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
## User settings
|
||||
|
||||
# vi
|
||||
.*.swp
|
||||
.*.swo
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# gradle properties
|
||||
local.properties
|
||||
.gradle/
|
||||
.android/
|
||||
.kotlin/
|
||||
Android/app/keystore.jks
|
||||
Android/app/keystore.properties
|
||||
|
||||
xcodebuild*.log
|
||||
|
||||
default.profraw
|
||||
*.mobileprovision
|
||||
*.cer
|
||||
*.p12
|
||||
*.p12.password
|
||||
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
.swiftpm
|
||||
.build/
|
||||
build/
|
||||
DerivedData/
|
||||
xcuserdata/
|
||||
xcodebuild*.log
|
||||
.idea/
|
||||
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
Packages/
|
||||
Package.pins
|
||||
Package.resolved
|
||||
#*.xcodeproj
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# fastlane
|
||||
|
||||
**/fastlane/apikey.json
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/README.md
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/test_output
|
||||
91
pedi-foods/Android/app/build.gradle.kts
Normal file
@@ -0,0 +1,91 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.android.application)
|
||||
id("skip-build-plugin")
|
||||
}
|
||||
|
||||
skip {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(libs.versions.jvm.get().toString())
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = group as String
|
||||
compileSdk = libs.versions.android.sdk.compile.get().toInt()
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.toVersion(libs.versions.jvm.get())
|
||||
targetCompatibility = JavaVersion.toVersion(libs.versions.jvm.get())
|
||||
}
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols.add("**/*.so")
|
||||
pickFirsts.add("**/*.so")
|
||||
// this option will compress JNI .so files
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.sdk.min.get().toInt()
|
||||
targetSdk = libs.versions.android.sdk.compile.get().toInt()
|
||||
// skip.tools.skip-build-plugin will automatically use Skip.env properties for:
|
||||
// applicationId = ANDROID_APPLICATION_ID ?? PRODUCT_BUNDLE_IDENTIFIER
|
||||
// versionCode = CURRENT_PROJECT_VERSION
|
||||
// versionName = MARKETING_VERSION
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
lint {
|
||||
disable.add("Instantiatable")
|
||||
disable.add("MissingPermission")
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
// Disables dependency metadata when building APKs.
|
||||
includeInApk = false
|
||||
// Disables dependency metadata when building Android App Bundles.
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
// default signing configuration tries to load from keystore.properties
|
||||
// see: https://skip.tools/docs/deployment/#export-signing
|
||||
signingConfigs {
|
||||
val keystorePropertiesFile = file("keystore.properties")
|
||||
create("release") {
|
||||
if (keystorePropertiesFile.isFile) {
|
||||
val keystoreProperties = Properties()
|
||||
keystoreProperties.load(keystorePropertiesFile.inputStream())
|
||||
keyAlias = keystoreProperties.getProperty("keyAlias")
|
||||
keyPassword = keystoreProperties.getProperty("keyPassword")
|
||||
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
||||
storePassword = keystoreProperties.getProperty("storePassword")
|
||||
} else {
|
||||
// when there is no keystore.properties file, fall back to signing with debug config
|
||||
keyAlias = signingConfigs.getByName("debug").keyAlias
|
||||
keyPassword = signingConfigs.getByName("debug").keyPassword
|
||||
storeFile = signingConfigs.getByName("debug").storeFile
|
||||
storePassword = signingConfigs.getByName("debug").storePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.findByName("release")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
isDebuggable = false // can be set to true for debugging release build, but needs to be false when uploading to store
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
}
|
||||
10
pedi-foods/Android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
-keeppackagenames **
|
||||
-keep class skip.** { *; }
|
||||
-keep class tools.skip.** { *; }
|
||||
-keep class kotlin.jvm.functions.** {*;}
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-dontwarn java.awt.**
|
||||
-keep class * implements com.sun.jna.** { *; }
|
||||
-keep class * implements skip.bridge.** { *; }
|
||||
-keep class **._ModuleBundleAccessor_* { *; }
|
||||
-keep class pedi.foods.** { *; }
|
||||
30
pedi-foods/Android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This AndroidManifest.xml template was generated by Skip -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- example permissions for using device location -->
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> -->
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> -->
|
||||
|
||||
<!-- permissions needed for using the internet or an embedded WebKit browser -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
|
||||
<application
|
||||
android:label="${PRODUCT_NAME}"
|
||||
android:name=".AndroidAppMain"
|
||||
android:supportsRtl="true"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
167
pedi-foods/Android/app/src/main/kotlin/Main.kt
Normal file
@@ -0,0 +1,167 @@
|
||||
package pedi.foods
|
||||
|
||||
import skip.lib.*
|
||||
import skip.model.*
|
||||
import skip.foundation.*
|
||||
import skip.ui.*
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import android.graphics.Color as AndroidColor
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.core.app.ActivityCompat
|
||||
|
||||
internal val logger: SkipLogger = SkipLogger(subsystem = "pedi.foods", category = "PediFoods")
|
||||
|
||||
private typealias AppRootView = PediFoodsRootView
|
||||
private typealias AppDelegate = PediFoodsAppDelegate
|
||||
|
||||
/// AndroidAppMain is the `android.app.Application` entry point, and must match `application android:name` in the AndroidMainfest.xml file.
|
||||
open class AndroidAppMain: Application {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
logger.info("starting app")
|
||||
ProcessInfo.launch(applicationContext)
|
||||
AppDelegate.shared.onInit()
|
||||
}
|
||||
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
/// AndroidAppMain is initial `androidx.appcompat.app.AppCompatActivity`, and must match `activity android:name` in the AndroidMainfest.xml file.
|
||||
open class MainActivity: AppCompatActivity {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
logger.info("starting activity")
|
||||
UIApplication.launch(this)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
val saveableStateHolder = rememberSaveableStateHolder()
|
||||
saveableStateHolder.SaveableStateProvider(true) {
|
||||
PresentationRootView(ComposeContext())
|
||||
SideEffect { saveableStateHolder.removeState(true) }
|
||||
}
|
||||
}
|
||||
|
||||
AppDelegate.shared.onLaunch()
|
||||
|
||||
// Example of requesting permissions on startup.
|
||||
// These must match the permissions in the AndroidManifest.xml file.
|
||||
//let permissions = listOf(
|
||||
// Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
// Manifest.permission.ACCESS_FINE_LOCATION
|
||||
// Manifest.permission.CAMERA,
|
||||
// Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
//)
|
||||
//let requestTag = 1
|
||||
//ActivityCompat.requestPermissions(self, permissions.toTypedArray(), requestTag)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
logger.info("onStart")
|
||||
super.onStart()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
AppDelegate.shared.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
AppDelegate.shared.onPause()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
AppDelegate.shared.onStop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
AppDelegate.shared.onDestroy()
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
AppDelegate.shared.onLowMemory()
|
||||
}
|
||||
|
||||
override fun onRestart() {
|
||||
logger.info("onRestart")
|
||||
super.onRestart()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: android.os.Bundle): Unit = super.onSaveInstanceState(outState)
|
||||
|
||||
override fun onRestoreInstanceState(bundle: android.os.Bundle) {
|
||||
// Usually you restore your state in onCreate(). It is possible to restore it in onRestoreInstanceState() as well, but not very common. (onRestoreInstanceState() is called after onStart(), whereas onCreate() is called before onStart().
|
||||
logger.info("onRestoreInstanceState")
|
||||
super.onRestoreInstanceState(bundle)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: kotlin.Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
logger.info("onRequestPermissionsResult: ${requestCode}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SyncSystemBarsWithTheme() {
|
||||
val dark = MaterialTheme.colorScheme.background.luminance() < 0.5f
|
||||
|
||||
val transparent = AndroidColor.TRANSPARENT
|
||||
val style = if (dark) {
|
||||
SystemBarStyle.dark(transparent)
|
||||
} else {
|
||||
SystemBarStyle.light(transparent, transparent)
|
||||
}
|
||||
|
||||
val activity = LocalContext.current as? ComponentActivity
|
||||
DisposableEffect(style) {
|
||||
activity?.enableEdgeToEdge(
|
||||
statusBarStyle = style,
|
||||
navigationBarStyle = style
|
||||
)
|
||||
onDispose { }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun PresentationRootView(context: ComposeContext) {
|
||||
val colorScheme = if (isSystemInDarkTheme()) ColorScheme.dark else ColorScheme.light
|
||||
PresentationRoot(defaultColorScheme = colorScheme, context = context) { ctx ->
|
||||
SyncSystemBarsWithTheme()
|
||||
val contentContext = ctx.content()
|
||||
Box(modifier = ctx.modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
AppRootView().Compose(context = contentContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
BIN
pedi-foods/Android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
pedi-foods/Android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 565 B |
|
After Width: | Height: | Size: 565 B |
BIN
pedi-foods/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
11
pedi-foods/Android/fastlane/Appfile
Normal file
@@ -0,0 +1,11 @@
|
||||
# This file contains the app distribution configuration
|
||||
# for the Android half of the Skip app.
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
|
||||
# Load the shared Skip.env properties with the app info
|
||||
require('dotenv')
|
||||
Dotenv.load('../../Skip.env')
|
||||
package_name(ENV['PRODUCT_BUNDLE_IDENTIFIER'].sub("-", "_"))
|
||||
|
||||
# Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
json_key_file("fastlane/apikey.json")
|
||||
51
pedi-foods/Android/fastlane/Fastfile
Normal file
@@ -0,0 +1,51 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# for the Android half of the Skip app.
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
|
||||
# Load the shared Skip.env properties with the app info
|
||||
require('dotenv')
|
||||
Dotenv.load '../../Skip.env'
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
# use the Homebrew gradle rather than expecting a local gradlew
|
||||
gradle_bin = (ENV['HOMEBREW_PREFIX'] ? ENV['HOMEBREW_PREFIX'] : "/opt/homebrew") + "/bin/gradle"
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
desc "Build Skip Android App"
|
||||
lane :build do |options|
|
||||
build_config = (options[:release] ? "Release" : "Debug")
|
||||
gradle(
|
||||
task: "build${build_config}",
|
||||
gradle_path: gradle_bin,
|
||||
flags: "--warning-mode none -x lint"
|
||||
)
|
||||
end
|
||||
|
||||
desc "Test Skip Android App"
|
||||
lane :test do
|
||||
gradle(
|
||||
task: "test",
|
||||
gradle_path: gradle_bin
|
||||
)
|
||||
end
|
||||
|
||||
desc "Assemble Skip Android App"
|
||||
lane :assemble do
|
||||
gradle(
|
||||
gradle_path: gradle_bin,
|
||||
task: "bundleRelease"
|
||||
)
|
||||
# sh "your_script.sh"
|
||||
end
|
||||
|
||||
desc "Deploy Skip Android App to Google Play"
|
||||
lane :release do
|
||||
|
||||
assemble
|
||||
|
||||
upload_to_play_store(
|
||||
aab: '../.build/Android/app/outputs/bundle/release/app-release.aab'
|
||||
)
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
A great new app built with Skip!
|
||||
@@ -0,0 +1 @@
|
||||
A great new app built with Skip!
|
||||
@@ -0,0 +1 @@
|
||||
PediFoods
|
||||
3
pedi-foods/Android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4g
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
1
pedi-foods/Android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||
25
pedi-foods/Android/settings.gradle.kts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This gradle project is part of a conventional Skip app project.
|
||||
pluginManagement {
|
||||
// Initialize the Skip plugin folder and perform a pre-build for non-Xcode builds
|
||||
val pluginPath = File.createTempFile("skip-plugin-path", ".tmp")
|
||||
|
||||
// overriding outputs for an Android IDE can be done by un-commenting and setting the Xcode path:
|
||||
//System.setProperty("BUILT_PRODUCTS_DIR", "${System.getProperty("user.home")}/Library/Developer/Xcode/DerivedData/MySkipProject-HASH/Build/Products/Debug-iphonesimulator")
|
||||
|
||||
val skipPluginResult = providers.exec {
|
||||
commandLine("/bin/sh", "-c", "skip plugin --prebuild --package-path '${settings.rootDir.parent}' --plugin-ref '${pluginPath.absolutePath}'")
|
||||
environment("PATH", "${System.getenv("PATH")}:/opt/homebrew/bin")
|
||||
}
|
||||
val skipPluginOutput = skipPluginResult.standardOutput.asText.get()
|
||||
print(skipPluginOutput)
|
||||
val skipPluginError = skipPluginResult.standardError.asText.get()
|
||||
print(skipPluginError)
|
||||
|
||||
includeBuild(pluginPath.readText()) {
|
||||
name = "skip-plugins"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("skip-plugin") apply true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 897 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-20~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-20@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-29@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-40@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-83.5@2x~ipad.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon~ios-marketing.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
pedi-foods/Darwin/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
pedi-foods/Darwin/Entitlements.plist
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
</dict>
|
||||
</plist>
|
||||
8
pedi-foods/Darwin/Info.plist
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
58
pedi-foods/Darwin/PediFoods.xcconfig
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "../Skip.env"
|
||||
|
||||
// Set the action that will be executed as part of the Xcode Run Script phase
|
||||
// Setting to "launch" will build and run the app in the first open Android emulator or device
|
||||
// Setting to "build" will just run gradle build, but will not launch the app
|
||||
// Setting to "none" will completely disable the build and launch of the Android app
|
||||
SKIP_ACTION = launch
|
||||
//SKIP_ACTION = build
|
||||
//SKIP_ACTION = none
|
||||
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
||||
|
||||
INFOPLIST_FILE = Info.plist
|
||||
GENERATE_INFOPLIST_FILE = YES
|
||||
|
||||
// The user-visible name of the app (localizable)
|
||||
//INFOPLIST_KEY_CFBundleDisplayName = App Name
|
||||
//INFOPLIST_KEY_LSApplicationCategoryType = public.app-category.utilities
|
||||
//INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This app uses your location to …"
|
||||
|
||||
// iOS-specific Info.plist property keys
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = YES
|
||||
INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations[sdk=iphone*] = UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
|
||||
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0
|
||||
SUPPORTS_MACCATALYST = NO
|
||||
|
||||
// iPhone + iPad
|
||||
TARGETED_DEVICE_FAMILY = 1,2
|
||||
|
||||
// iPhone only
|
||||
// TARGETED_DEVICE_FAMILY = 1
|
||||
|
||||
SWIFT_EMIT_LOC_STRINGS = YES
|
||||
|
||||
// the name of the product module; this can be anything, but cannot conflict with any Swift module names
|
||||
PRODUCT_MODULE_NAME = $(PRODUCT_NAME:c99extidentifier)App
|
||||
|
||||
// On-device testing may need to override the bundle ID
|
||||
// PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=iphoneos*] = cool.beans.BundleIdentifer
|
||||
|
||||
SDKROOT = auto
|
||||
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
||||
SWIFT_EMIT_LOC_STRINGS = YES
|
||||
|
||||
SWIFT_VERSION = 6
|
||||
|
||||
// Development team ID for on-device testing
|
||||
CODE_SIGNING_REQUIRED = NO
|
||||
CODE_SIGN_STYLE = Automatic
|
||||
CODE_SIGN_ENTITLEMENTS = Entitlements.plist
|
||||
//CODE_SIGNING_IDENTITY = -
|
||||
//DEVELOPMENT_TEAM =
|
||||
289
pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,289 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
491F27822DA55B72004926EE /* PediFoods in Frameworks */ = {isa = PBXBuildFile; productRef = 491F27812DA55B72004926EE /* PediFoods */; };
|
||||
491F27832DA55B72004926EE /* PediFoods in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 491F27812DA55B72004926EE /* PediFoods */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
|
||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
|
||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
491F27832DA55B72004926EE /* PediFoods in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4900101C2BACEA710000DE33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
493609562A6B7EAE00C401E2 /* PediFoods */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = PediFoods; path = ..; sourceTree = "<group>"; };
|
||||
496BDBEB2B89A47800C09264 /* PediFoods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ../Sources/PediFoods/Resources/Localizable.xcstrings; sourceTree = "<group>"; };
|
||||
496EB72F2A6AE4DE00C1253A /* Skip.env */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Skip.env; path = ../Skip.env; sourceTree = "<group>"; };
|
||||
496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = PediFoods.xcconfig; sourceTree = "<group>"; };
|
||||
496EB72F2A6AE4DE00C1253C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
499AB9082B0581F4005E8330 /* plugins */ = {isa = PBXFileReference; lastKnownFileType = folder; name = plugins; path = ../../Intermediates.noindex/BuildToolPluginIntermediates; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
|
||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
499CD43C2AC5B799001AE8D8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
491F27822DA55B72004926EE /* PediFoods in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
496BDBEC2B89A47800C09264 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
496BDBEB2B89A47800C09264 /* PediFoods.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49AB54462B066A7E007B79B2 /* SkipStone */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
499AB9082B0581F4005E8330 /* plugins */,
|
||||
);
|
||||
name = SkipStone;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49F90C1F2A52156200F06D93 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
496EB72F2A6AE4DE00C1253C /* README.md */,
|
||||
496EB72F2A6AE4DE00C1253A /* Skip.env */,
|
||||
496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */,
|
||||
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */,
|
||||
493609562A6B7EAE00C401E2 /* PediFoods */,
|
||||
49F90C2A2A52156200F06D93 /* App */,
|
||||
49AB54462B066A7E007B79B2 /* SkipStone */,
|
||||
496BDBEC2B89A47800C09264 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
49F90C2A2A52156200F06D93 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49F90C2B2A52156200F06D93 /* Main.swift */,
|
||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */,
|
||||
49F90C312A52156300F06D93 /* Entitlements.plist */,
|
||||
4900101C2BACEA710000DE33 /* Info.plist */,
|
||||
);
|
||||
name = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
499CD4382AC5B799001AE8D8 /* PediFoods App */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "PediFoods App" */;
|
||||
buildPhases = (
|
||||
499CD43A2AC5B799001AE8D8 /* Sources */,
|
||||
499CD43C2AC5B799001AE8D8 /* Frameworks */,
|
||||
499CD43E2AC5B799001AE8D8 /* Resources */,
|
||||
499CD4452AC5B869001AE8D8 /* Run skip gradle */,
|
||||
499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "PediFoods App";
|
||||
packageProductDependencies = (
|
||||
491F27812DA55B72004926EE /* PediFoods */,
|
||||
);
|
||||
productName = App;
|
||||
productReference = 496BDBEB2B89A47800C09264 /* PediFoods.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
49F90C202A52156200F06D93 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1430;
|
||||
LastUpgradeCheck = 1630;
|
||||
};
|
||||
buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "PediFoods" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
es,
|
||||
ja,
|
||||
"zh-Hans",
|
||||
);
|
||||
mainGroup = 49F90C1F2A52156200F06D93;
|
||||
packageReferences = (
|
||||
);
|
||||
productRefGroup = 496BDBEC2B89A47800C09264 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
499CD4382AC5B799001AE8D8 /* PediFoods App */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
499CD43E2AC5B799001AE8D8 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */,
|
||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
499CD4452AC5B869001AE8D8 /* Run skip gradle */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run skip gradle";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/sh -e";
|
||||
shellScript = "if [ \"${SKIP_ZERO}\" != \"\" ]; then\n echo \"note: skipping skip due to SKIP_ZERO\"\n exit 0\nelif [ \"${ENABLE_PREVIEWS}\" = \"YES\" ]; then\n echo \"note: skipping skip due to ENABLE_PREVIEWS\"\n exit 0\nelif [ \"${ACTION}\" = \"install\" ]; then\n echo \"note: skipping skip due to archive install\"\n exit 0\nelif [ \"${SKIP_ACTION}\" = \"none\" ]; then\n echo \"note: skipping skip due to SKIP_ACTION none\"\n exit 0\nelse\n SKIP_ACTION=\"${SKIP_ACTION:-launch}\"\nfi\nPATH=${BUILD_ROOT}/Release:${BUILD_ROOT}/Debug:${BUILD_ROOT}/../../SourcePackages/artifacts/skip/skip/skip.artifactbundle/macos:${PATH}:${HOMEBREW_PREFIX:-/opt/homebrew}/bin\necho \"note: running gradle build with: $(which skip) gradle -p ${PWD}/../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\"\nskip gradle -p ../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
499CD43A2AC5B799001AE8D8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
499CD4422AC5B799001AE8D8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
|
||||
buildSettings = {
|
||||
ENABLE_PREVIEWS = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
499CD4432AC5B799001AE8D8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
|
||||
buildSettings = {
|
||||
ENABLE_PREVIEWS = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
49F90C4B2A52156300F06D93 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
49F90C4C2A52156300F06D93 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "PediFoods App" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
499CD4422AC5B799001AE8D8 /* Debug */,
|
||||
499CD4432AC5B799001AE8D8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
49F90C232A52156200F06D93 /* Build configuration list for PBXProject "PediFoods" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
49F90C4B2A52156300F06D93 /* Debug */,
|
||||
49F90C4C2A52156300F06D93 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
491F27812DA55B72004926EE /* PediFoods */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PediFoods;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 49F90C202A52156200F06D93 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1630"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
||||
BuildableName = "PediFoods.app"
|
||||
BlueprintName = "PediFoods App"
|
||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
||||
BuildableName = "PediFoods.app"
|
||||
BlueprintName = "PediFoods App"
|
||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
||||
BuildableName = "PediFoods.app"
|
||||
BlueprintName = "PediFoods App"
|
||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
86
pedi-foods/Darwin/Sources/Main.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
import SwiftUI
|
||||
import PediFoods
|
||||
|
||||
private typealias AppRootView = PediFoodsRootView
|
||||
private typealias AppDelegate = PediFoodsAppDelegate
|
||||
|
||||
/// The entry point to the app simply loads the App implementation from SPM module.
|
||||
@main struct AppMain: App {
|
||||
@AppDelegateAdaptor(AppMainDelegate.self) var appDelegate
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
AppRootView()
|
||||
}
|
||||
.onChange(of: scenePhase) { oldPhase, newPhase in
|
||||
switch newPhase {
|
||||
case .active:
|
||||
AppDelegate.shared.onResume()
|
||||
case .inactive:
|
||||
AppDelegate.shared.onPause()
|
||||
case .background:
|
||||
AppDelegate.shared.onStop()
|
||||
@unknown default:
|
||||
print("unknown app phase: \(newPhase)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor
|
||||
typealias AppMainDelegateBase = UIApplicationDelegate
|
||||
typealias AppType = UIApplication
|
||||
#elseif canImport(AppKit)
|
||||
typealias AppDelegateAdaptor = NSApplicationDelegateAdaptor
|
||||
typealias AppMainDelegateBase = NSApplicationDelegate
|
||||
typealias AppType = NSApplication
|
||||
#endif
|
||||
|
||||
@MainActor final class AppMainDelegate: NSObject, AppMainDelegateBase {
|
||||
let application = AppType.shared
|
||||
|
||||
#if canImport(UIKit)
|
||||
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
AppDelegate.shared.onInit()
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
AppDelegate.shared.onLaunch()
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
AppDelegate.shared.onDestroy()
|
||||
}
|
||||
|
||||
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
|
||||
AppDelegate.shared.onLowMemory()
|
||||
}
|
||||
|
||||
// support for SkipNotify.fetchNotificationToken()
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("didRegisterForRemoteNotificationsWithDeviceToken"), object: application, userInfo: ["deviceToken": deviceToken])
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) {
|
||||
NotificationCenter.default.post(name: NSNotification.Name("didFailToRegisterForRemoteNotificationsWithError"), object: application, userInfo: ["error": error])
|
||||
}
|
||||
#elseif canImport(AppKit)
|
||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||
AppDelegate.shared.onInit()
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
AppDelegate.shared.onLaunch()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: Notification) {
|
||||
AppDelegate.shared.onDestroy()
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
5
pedi-foods/Darwin/fastlane/AppStore.xcconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
// Additional properties included by the Fastfile build_app
|
||||
|
||||
// This file can be used to override various properties from Skip.env
|
||||
//PRODUCT_BUNDLE_IDENTIFIER =
|
||||
//DEVELOPMENT_TEAM =
|
||||
8
pedi-foods/Darwin/fastlane/Appfile
Normal file
@@ -0,0 +1,8 @@
|
||||
# For more information about the Appfile, see:
|
||||
# https://docs.fastlane.tools/advanced/#appfile
|
||||
|
||||
require('dotenv')
|
||||
Dotenv.load '../../Skip.env'
|
||||
app_identifier(ENV['ANDROID_APPLICATION_ID'] || ENV['PRODUCT_BUNDLE_IDENTIFIER'])
|
||||
|
||||
# apple_id("my@email")
|
||||
27
pedi-foods/Darwin/fastlane/Deliverfile
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
copyright "#{Time.now.year}"
|
||||
|
||||
force(true) # Skip HTML report verification
|
||||
automatic_release(true)
|
||||
skip_screenshots(false)
|
||||
precheck_include_in_app_purchases(false)
|
||||
|
||||
#skip_binary_upload(true)
|
||||
submit_for_review(true)
|
||||
|
||||
submission_information({
|
||||
add_id_info_serves_ads: false,
|
||||
add_id_info_uses_idfa: false,
|
||||
add_id_info_tracks_install: false,
|
||||
add_id_info_tracks_action: false,
|
||||
add_id_info_limits_tracking: false,
|
||||
content_rights_has_rights: false,
|
||||
content_rights_contains_third_party_content: false,
|
||||
export_compliance_contains_third_party_cryptography: false,
|
||||
export_compliance_encryption_updated: false,
|
||||
export_compliance_platform: 'ios',
|
||||
export_compliance_compliance_required: false,
|
||||
export_compliance_uses_encryption: false,
|
||||
export_compliance_is_exempt: false,
|
||||
export_compliance_contains_proprietary_cryptography: false
|
||||
})
|
||||
40
pedi-foods/Darwin/fastlane/Fastfile
Normal file
@@ -0,0 +1,40 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# for the iOS half of the Skip app.
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
|
||||
default_platform(:ios)
|
||||
|
||||
lane :assemble do |options|
|
||||
# only build the iOS side of the app
|
||||
ENV["SKIP_ZERO"] = "true"
|
||||
build_app(
|
||||
scheme: "PediFoods App",
|
||||
sdk: "iphoneos",
|
||||
xcconfig: "fastlane/AppStore.xcconfig",
|
||||
xcargs: "-skipPackagePluginValidation -skipMacroValidation",
|
||||
derived_data_path: "../.build/Darwin/DerivedData",
|
||||
output_directory: "../.build/fastlane/Darwin",
|
||||
skip_archive: ENV["FASTLANE_SKIP_ARCHIVE"] == "YES",
|
||||
skip_codesigning: ENV["FASTLANE_SKIP_CODESIGNING"] == "YES"
|
||||
)
|
||||
end
|
||||
|
||||
lane :release do |options|
|
||||
desc "Build and release app"
|
||||
|
||||
# see https://docs.fastlane.tools/uploading-app-privacy-details/
|
||||
#upload_app_privacy_details_to_app_store(json_path: "fastlane/app_privacy_details.json")
|
||||
|
||||
# if you have an apikey.json file (https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api), fastlane can automatically fetch certificates and the ASC authentication information
|
||||
#get_certificates(api_key_path: "fastlane/apikey.json")
|
||||
get_provisioning_profile(api_key_path: "fastlane/apikey.json")
|
||||
|
||||
assemble
|
||||
|
||||
upload_to_app_store(
|
||||
api_key_path: "fastlane/apikey.json",
|
||||
app_rating_config_path: "fastlane/metadata/rating.json",
|
||||
release_notes: { default: "Fixes and improvements." }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"data_protections": [
|
||||
"DATA_NOT_COLLECTED"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
A great new app built with Skip!
|
||||
1
pedi-foods/Darwin/fastlane/metadata/en-US/keywords.txt
Normal file
@@ -0,0 +1 @@
|
||||
app,key,words
|
||||
@@ -0,0 +1 @@
|
||||
https://example.org/privacy/
|
||||
@@ -0,0 +1 @@
|
||||
Bug fixes and performance improvements.
|
||||
@@ -0,0 +1 @@
|
||||
https://example.org/app/
|
||||
1
pedi-foods/Darwin/fastlane/metadata/en-US/subtitle.txt
Normal file
@@ -0,0 +1 @@
|
||||
A new Skip app
|
||||
@@ -0,0 +1 @@
|
||||
https://example.org/support/
|
||||
1
pedi-foods/Darwin/fastlane/metadata/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
PediFoods
|
||||
@@ -0,0 +1 @@
|
||||
New features and better performance.
|
||||
18
pedi-foods/Darwin/fastlane/metadata/rating.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
|
||||
"contests": "NONE",
|
||||
"gamblingSimulated": "NONE",
|
||||
"horrorOrFearThemes": "NONE",
|
||||
"koreaAgeRatingOverride": "NONE",
|
||||
"matureOrSuggestiveThemes": "NONE",
|
||||
"medicalOrTreatmentInformation": "NONE",
|
||||
"profanityOrCrudeHumor": "NONE",
|
||||
"sexualContentGraphicAndNudity": "NONE",
|
||||
"sexualContentOrNudity": "NONE",
|
||||
"violenceCartoonOrFantasy": "NONE",
|
||||
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
|
||||
"violenceRealistic": "NONE",
|
||||
"gambling": false,
|
||||
"lootBox": false,
|
||||
"unrestrictedWebAccess": false
|
||||
}
|
||||
21
pedi-foods/Package.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
// swift-tools-version: 6.1
|
||||
// This is a Skip (https://skip.tools) package.
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "pedi-foods",
|
||||
defaultLocalization: "en",
|
||||
platforms: [.iOS(.v17), .macOS(.v14)],
|
||||
products: [
|
||||
.library(name: "PediFoods", type: .dynamic, targets: ["PediFoods"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://source.skip.tools/skip.git", from: "1.7.0"),
|
||||
.package(url: "https://source.skip.tools/skip-fuse-ui.git", from: "1.0.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "PediFoods", dependencies: [
|
||||
.product(name: "SkipFuseUI", package: "skip-fuse-ui")
|
||||
], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]),
|
||||
]
|
||||
)
|
||||
7
pedi-foods/Project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Darwin/PediFoods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
60
pedi-foods/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# PediFoods
|
||||
|
||||
This is a [Skip](https://skip.tools) dual-platform app project.
|
||||
|
||||
|
||||
<!-- TODO: add iOS screenshots to fastlane metadata
|
||||
## iPhone Screenshots
|
||||
|
||||
<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%" />
|
||||
-->
|
||||
|
||||
<!-- TODO: add Android screenshots to fastlane metadata
|
||||
## Android Screenshots
|
||||
|
||||
<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%" />
|
||||
-->
|
||||
|
||||
## Building
|
||||
|
||||
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.
|
||||
|
||||
Building the module requires that Skip be installed using
|
||||
[Homebrew](https://brew.sh) with `brew install skiptools/skip/skip`.
|
||||
|
||||
This will also install the necessary Skip prerequisites:
|
||||
Kotlin, Gradle, and the Android build tools.
|
||||
|
||||
Installation prerequisites can be confirmed by running
|
||||
`skip checkup`. The project can be validated with `skip verify`.
|
||||
|
||||
## Running
|
||||
|
||||
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.
|
||||
|
||||
The project can be opened and run in Xcode from
|
||||
`Project.xcworkspace`, which also enabled parallel
|
||||
development of any Skip libary dependencies.
|
||||
|
||||
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.
|
||||
|
||||
## Testing
|
||||
|
||||
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.
|
||||
|
||||
Parity testing can be performed with `skip test`,
|
||||
which will output a table of the test results for both platforms.
|
||||
23
pedi-foods/Skip.env
Normal file
@@ -0,0 +1,23 @@
|
||||
// The configuration file for your Skip App (https://skip.tools).
|
||||
// Properties specified here are shared between
|
||||
// Darwin/PediFoods.xcconfig and Android/settings.gradle.kts
|
||||
// and will be included in the app's metadata files
|
||||
// Info.plist and AndroidManifest.xml
|
||||
|
||||
// PRODUCT_NAME is the default title of the app, which must match the app's Swift module name
|
||||
PRODUCT_NAME = PediFoods
|
||||
|
||||
// PRODUCT_BUNDLE_IDENTIFIER is the unique id for both the iOS and Android app
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app
|
||||
|
||||
// The semantic version of the app
|
||||
MARKETING_VERSION = 0.0.1
|
||||
|
||||
// The build number specifying the internal app version
|
||||
CURRENT_PROJECT_VERSION = 1
|
||||
|
||||
// The package name for the Android entry point, referenced by the AndroidManifest.xml
|
||||
ANDROID_PACKAGE_NAME = pedi.foods
|
||||
|
||||
// If your Android appId is different from the iOS Bundle Identifer, specify it here
|
||||
// ANDROID_APPLICATION_ID = com.br.pedifoods.app
|
||||
177
pedi-foods/Sources/PediFoods/ContentView.swift
Normal file
@@ -0,0 +1,177 @@
|
||||
import SwiftUI
|
||||
|
||||
enum ContentTab: String, Hashable {
|
||||
case welcome, home, settings
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@AppStorage("tab") var tab = ContentTab.welcome
|
||||
@AppStorage("name") var welcomeName = "Skipper"
|
||||
@AppStorage("appearance") var appearance = ""
|
||||
@State var viewModel = ViewModel()
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $tab) {
|
||||
NavigationStack {
|
||||
WelcomeView(welcomeName: $welcomeName)
|
||||
}
|
||||
.tabItem { Label("Welcome", systemImage: "heart.fill") }
|
||||
.tag(ContentTab.welcome)
|
||||
|
||||
NavigationStack {
|
||||
ItemListView()
|
||||
.navigationTitle(Text("\(viewModel.items.count) Items"))
|
||||
}
|
||||
.tabItem { Label("Home", systemImage: "house.fill") }
|
||||
.tag(ContentTab.home)
|
||||
|
||||
NavigationStack {
|
||||
SettingsView(appearance: $appearance, welcomeName: $welcomeName)
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
|
||||
.tag(ContentTab.settings)
|
||||
}
|
||||
.environment(viewModel)
|
||||
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct WelcomeView : View {
|
||||
@State var heartBeating = false
|
||||
@Binding var welcomeName: String
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text("Hello [\(welcomeName)](https://skip.tools)!")
|
||||
.padding()
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundStyle(.red)
|
||||
.scaleEffect(heartBeating ? 1.5 : 1.0)
|
||||
.animation(.easeInOut(duration: 1).repeatForever(), value: heartBeating)
|
||||
.task { heartBeating = true }
|
||||
}
|
||||
.font(.largeTitle)
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemListView : View {
|
||||
@Environment(ViewModel.self) var viewModel: ViewModel
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(viewModel.items) { item in
|
||||
NavigationLink(value: item) {
|
||||
Label {
|
||||
Text(item.itemTitle)
|
||||
} icon: {
|
||||
if item.favorite {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundStyle(.yellow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDelete { offsets in
|
||||
viewModel.items.remove(atOffsets: offsets)
|
||||
}
|
||||
.onMove { fromOffsets, toOffset in
|
||||
viewModel.items.move(fromOffsets: fromOffsets, toOffset: toOffset)
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Item.self) { item in
|
||||
ItemView(item: item)
|
||||
.navigationTitle(item.itemTitle)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItemGroup {
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.items.insert(Item(), at: 0)
|
||||
}
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemView : View {
|
||||
@State var item: Item
|
||||
@Environment(ViewModel.self) var viewModel: ViewModel
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Title", text: $item.title)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
Toggle("Favorite", isOn: $item.favorite)
|
||||
DatePicker("Date", selection: $item.date)
|
||||
Text("Notes").font(.title3)
|
||||
TextEditor(text: $item.notes)
|
||||
.border(Color.secondary, width: 1.0)
|
||||
}
|
||||
.navigationBarBackButtonHidden()
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") {
|
||||
viewModel.save(item: item)
|
||||
dismiss()
|
||||
}
|
||||
.disabled(!viewModel.isUpdated(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView : View {
|
||||
@Binding var appearance: String
|
||||
@Binding var welcomeName: String
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
TextField("Name", text: $welcomeName)
|
||||
Picker("Appearance", selection: $appearance) {
|
||||
Text("System").tag("")
|
||||
Text("Light").tag("light")
|
||||
Text("Dark").tag("dark")
|
||||
}
|
||||
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
|
||||
let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
||||
Text("Version \(version) (\(buildNumber))")
|
||||
}
|
||||
HStack {
|
||||
PlatformHeartView()
|
||||
Text("Powered by [Skip](https://skip.tools)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view that shows a blue heart on iOS and a green heart on Android.
|
||||
struct PlatformHeartView : View {
|
||||
var body: some View {
|
||||
#if os(Android)
|
||||
ComposeView {
|
||||
HeartComposer()
|
||||
}
|
||||
#else
|
||||
Text(verbatim: "💙")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if SKIP
|
||||
/// Use a ContentComposer to integrate Compose content. This code will be transpiled to Kotlin.
|
||||
struct HeartComposer : ContentComposer {
|
||||
@Composable func Compose(context: ComposeContext) {
|
||||
androidx.compose.material3.Text("💚", modifier: context.modifier)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
59
pedi-foods/Sources/PediFoods/PediFoodsApp.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
import SkipFuse
|
||||
import SwiftUI
|
||||
|
||||
/// A logger for the PediFoods module.
|
||||
let logger: Logger = Logger(subsystem: "com.br.pedifoods.app", category: "PediFoods")
|
||||
|
||||
/// The shared top-level view for the app, loaded from the platform-specific App delegates below.
|
||||
///
|
||||
/// The default implementation merely loads the `ContentView` for the app and logs a message.
|
||||
/* SKIP @bridge */public struct PediFoodsRootView : View {
|
||||
/* SKIP @bridge */public init() {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ContentView()
|
||||
.task {
|
||||
logger.info("Skip app logs are viewable in the Xcode console for iOS; Android logs can be viewed in Studio or using adb logcat")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global application delegate functions.
|
||||
///
|
||||
/// These functions can update a shared observable object to communicate app state changes to interested views.
|
||||
/* SKIP @bridge */public final class PediFoodsAppDelegate : Sendable {
|
||||
/* SKIP @bridge */public static let shared = PediFoodsAppDelegate()
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onInit() {
|
||||
logger.debug("onInit")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onLaunch() {
|
||||
logger.debug("onLaunch")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onResume() {
|
||||
logger.debug("onResume")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onPause() {
|
||||
logger.debug("onPause")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onStop() {
|
||||
logger.debug("onStop")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onDestroy() {
|
||||
logger.debug("onDestroy")
|
||||
}
|
||||
|
||||
/* SKIP @bridge */public func onLowMemory() {
|
||||
logger.debug("onLowMemory")
|
||||
}
|
||||
}
|
||||
563
pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings
Normal file
@@ -0,0 +1,563 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"%lld Items" : {
|
||||
"comment" : "Header title for a list that contains the number of items",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld elementos"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld éléments"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "アイテム数 %lld"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%lld 个条目"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Add" : {
|
||||
"comment" : "Button in items list that will cause a new item to be added",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Añadir"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ajouter"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "追加"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "添加"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Appearance" : {
|
||||
"comment" : "Settings select label for the interface style of the controls (light, dark, or default)",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Apariencia"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Apparence"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "外観"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "外观"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cancel" : {
|
||||
"comment" : "Button title indicating that the operation should be cancelled",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Cancelar"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Annuler"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "キャンセル"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "取消"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Dark" : {
|
||||
"comment" : "Menu item indicating that the appearance should be in dark mode",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Oscuro"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sombre"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ダーク"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "深色"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Date" : {
|
||||
"comment" : "Item editor form label for the Date field",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fecha"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Date"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "日付"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "日期"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Favorite" : {
|
||||
"comment" : "Item editor title label for marking the item as a favorite",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Favorito"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Favori"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "お気に入り"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "收藏"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Hello [%@](https://skip.tools)!" : {
|
||||
"comment" : "Welcome tab contents",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "¡Hola [%@](https://skip.tools)!"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bonjour [%@](https://skip.tools)!"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "こんにちは [%@](https://skip.tools)"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "你好 [%@](https://skip.tools)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Home" : {
|
||||
"comment" : "Tab bar item title for the Home tab",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Inicio"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Accueil"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ホーム"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "首页"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Light" : {
|
||||
"comment" : "Menu item indicating that the appearance should be in light mode",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Claro"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Clair"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "明るい"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "浅色"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Name" : {
|
||||
"comment" : "Placeholder title for the Name field in a form",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nombre"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nom"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "名前"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "姓名"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Notes" : {
|
||||
"comment" : "Item editor form label for the Notes field",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notas"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notes"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ノート"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "笔记"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Powered by [Skip](https://skip.tools)" : {
|
||||
"comment" : "Link markdown text for the Powered by… label",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Impulsado por [Skip](https://skip.tools)"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Fonctionnalités offertes par [Skip](https://skip.tools)"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "[Skip](https://skip.tools) を使って動かしています"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "由[Skip](https://skip.tools)提供支持"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Save" : {
|
||||
"comment" : "Button title indicating that the current contents should be saved",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Guardar"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Enregistrer"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "保存"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "保存"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Settings" : {
|
||||
"comment" : "Tab bar item title for the Settings tab",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Configuración"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Paramètres"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "設定"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "设置"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"System" : {
|
||||
"comment" : "Menu item indicating that the appearance should be in the default system mode",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sistema"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Système"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "システム"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "系统"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Title" : {
|
||||
"comment" : "Label for the item editor form indicating the title of the item",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Título"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Titre"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "タイトル"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "标题"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Version %@ (%@)" : {
|
||||
"comment" : "Settings label showing the current version of the app",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Version %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Versión %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Version %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "バージョン %1$@ (%2$@)"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "版本 %1$@ (%2$@)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Welcome" : {
|
||||
"comment" : "Tab bar item title for the Welcome tab",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bienvenido"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bienvenue"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ようこそ"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "欢迎"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
pedi-foods/Sources/PediFoods/Skip/skip.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Configuration file for https://skip.tools project
|
||||
#
|
||||
# Kotlin dependencies and Gradle build options for this module can be configured here
|
||||
#build:
|
||||
# contents:
|
||||
# - block: 'dependencies'
|
||||
# contents:
|
||||
# - 'implementation("androidx.compose.runtime:runtime")'
|
||||
|
||||
# this is a natively-compiled Skip Fuse module
|
||||
skip:
|
||||
mode: 'native'
|
||||
93
pedi-foods/Sources/PediFoods/ViewModel.swift
Normal file
@@ -0,0 +1,93 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import SkipFuse
|
||||
|
||||
/// The Observable ViewModel used by the application.
|
||||
@Observable public class ViewModel {
|
||||
var items: [Item] = loadItems() {
|
||||
didSet { saveItems() }
|
||||
}
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func clear() {
|
||||
items.removeAll()
|
||||
}
|
||||
|
||||
func isUpdated(_ item: Item) -> Bool {
|
||||
item != items.first { i in
|
||||
i.id == item.id
|
||||
}
|
||||
}
|
||||
|
||||
func save(item: Item) {
|
||||
items = items.map { i in
|
||||
i.id == item.id ? item : i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An individual item held by the ViewModel
|
||||
struct Item : Identifiable, Hashable, Codable {
|
||||
let id: UUID
|
||||
var date: Date
|
||||
var favorite: Bool
|
||||
var title: String
|
||||
var notes: String
|
||||
|
||||
init(id: UUID = UUID(), date: Date = .now, favorite: Bool = false, title: String = "", notes: String = "") {
|
||||
self.id = id
|
||||
self.date = date
|
||||
self.favorite = favorite
|
||||
self.title = title
|
||||
self.notes = notes
|
||||
}
|
||||
|
||||
var itemTitle: String {
|
||||
!title.isEmpty ? title : dateString
|
||||
}
|
||||
|
||||
var dateString: String {
|
||||
date.formatted(date: .complete, time: .omitted)
|
||||
}
|
||||
|
||||
var dateTimeString: String {
|
||||
date.formatted(date: .abbreviated, time: .shortened)
|
||||
}
|
||||
}
|
||||
|
||||
/// Utilities for defaulting and persising the items in the list
|
||||
extension ViewModel {
|
||||
private static let savePath = URL.applicationSupportDirectory.appendingPathComponent("appdata.json")
|
||||
|
||||
fileprivate static func loadItems() -> [Item] {
|
||||
do {
|
||||
let start = Date.now
|
||||
let data = try Data(contentsOf: savePath)
|
||||
defer {
|
||||
let end = Date.now
|
||||
logger.info("loaded \(data.count) bytes from \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
|
||||
}
|
||||
return try JSONDecoder().decode([Item].self, from: data)
|
||||
} catch {
|
||||
// perhaps the first launch, or the data could not be read
|
||||
logger.warning("failed to load data from \(Self.savePath), using defaultItems: \(error)")
|
||||
let defaultItems = (1...365).map { Date(timeIntervalSinceNow: Double($0 * 60 * 60 * 24 * -1)) }
|
||||
return defaultItems.map({ Item(date: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func saveItems() {
|
||||
do {
|
||||
let start = Date.now
|
||||
let data = try JSONEncoder().encode(items)
|
||||
try FileManager.default.createDirectory(at: URL.applicationSupportDirectory, withIntermediateDirectories: true)
|
||||
try data.write(to: Self.savePath)
|
||||
let end = Date.now
|
||||
logger.info("saved \(data.count) bytes to \(Self.savePath.path) in \(end.timeIntervalSince(start)) seconds")
|
||||
} catch {
|
||||
logger.error("error saving data: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
82
pedifoods_app_plan.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Plano de implementacao do app PediFoods (Skip + SwiftUI)
|
||||
|
||||
## Premissas de design e navegacao
|
||||
- A tabbar e o centro de navegacao. Toda navegacao que nao for modal/over-context acontece dentro da tabbar.
|
||||
- Login e cadastro ficam fora da tabbar. Ao logar/cadastrar, a Home abre ja com as tabs ativas.
|
||||
- O verde da imagem `home_tabs.png` e o primary. O verde fluorescente sera usado raramente e fica como cor terciaria.
|
||||
- Tipografia base: Plus Jakarta Sans.
|
||||
|
||||
## Etapa 1 — Fundacao tecnica (obrigatoria antes das telas)
|
||||
1. Criar o projeto Skip (Fuse) e estrutura base de pastas.
|
||||
2. Configurar tokens do design system e tema (cores, tipografia, espacamentos, raios, sombras).
|
||||
3. Implementar o service base de API:
|
||||
- Camada HTTP
|
||||
- Headers padrao
|
||||
- Tokens por modulo (customer, store, resource)
|
||||
- Tratamento de erros e rate limit
|
||||
- Persistencia segura de JWT
|
||||
4. Criar um coordinator de navegacao:
|
||||
- Fluxo de autenticacao (login/cadastro/OTP) fora da tabbar
|
||||
- Fluxo principal com tabbar (Home, Cart, Orders, Profile)
|
||||
- Modais over-context (address, filtros, etc)
|
||||
5. Configurar state global (sessao, perfil, cart, endereco ativo, favoritos).
|
||||
6. Integrar permissions e GPS:
|
||||
- Solicitar permissao ao cadastrar endereço
|
||||
- Obter localizacao atual ao cadastrar endereço
|
||||
- Cache de ultima localizacao valida
|
||||
|
||||
## Etapa 2 — Autenticacao (fora da tabbar)
|
||||
1. Tela Login
|
||||
2. Tela Cadastro
|
||||
3. Tela OTP (8 digitos)
|
||||
4. Fluxo pos-login:
|
||||
- Buscar perfil
|
||||
- Abrir Home com tabbar ativa
|
||||
|
||||
## Etapa 3 — Home e listagem de lojas (Tab Home)
|
||||
1. Home com header, busca e categorias
|
||||
2. Listagem de lojas (API /api/app/stores com lat/lng)
|
||||
3. Filtros e busca
|
||||
4. Favoritos
|
||||
|
||||
## Etapa 4 — Store Detail e Catalogo
|
||||
1. Tela de detalhes da loja
|
||||
2. Catalogo por categorias
|
||||
3. Produtos com addons
|
||||
4. Favoritos de loja
|
||||
|
||||
## Etapa 5 — Produto e carrinho
|
||||
1. Tela de produto (detalhes + adicionais)
|
||||
2. Carrinho (Tab Cart)
|
||||
3. Atualizacao de quantidade e subtotal
|
||||
|
||||
## Etapa 6 — Checkout e pagamento
|
||||
1. Tela de checkout
|
||||
2. Endereco de entrega
|
||||
3. Metodos de pagamento (PIX, credito, debito, dinheiro)
|
||||
4. Criacao de pedido
|
||||
5. Fluxo de PIX (payload e QR)
|
||||
|
||||
## Etapa 7 — Pedidos e rastreio (Tab Orders)
|
||||
1. Lista de pedidos
|
||||
2. Detalhe do pedido
|
||||
3. Tracking de status
|
||||
|
||||
## Etapa 8 — Perfil e enderecos (Tab Profile)
|
||||
1. Perfil do usuario
|
||||
2. Lista de enderecos
|
||||
3. Criar/editar endereco
|
||||
|
||||
## Etapa 9 — Mapa e GPS
|
||||
1. Mapa iOS via MapKit
|
||||
2. Mapa Android via ComposeView (Google Maps)
|
||||
3. Pinos de loja e localizacao atual
|
||||
4. Ajustes de permissao e fallback sem GPS
|
||||
|
||||
## Etapa 10 — Polimento e release
|
||||
1. Dark mode
|
||||
2. Estados vazios e erros
|
||||
3. Performance e cache
|
||||
4. Build iOS e Android
|
||||
5. Export APK/AAB
|
||||
|
||||
224
pedifoods_system_design.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 📦 Design System — Green Food Delivery App
|
||||
|
||||
Este documento define o **design system oficial** do webapp de food delivery, baseado em uma identidade visual **fresh, health-focused e energética**, com forte uso de verde neon e contrastes suaves.
|
||||
|
||||
Este arquivo deve ser usado como **prompt base canônico** para qualquer IA, designer ou desenvolvedor.
|
||||
|
||||
---
|
||||
|
||||
## 1. FUNDAMENTOS VISUAIS
|
||||
|
||||
### 1.1 🎨 Paleta de Cores
|
||||
|
||||
#### Cores Principais
|
||||
|
||||
```ts
|
||||
colors: {
|
||||
primary: "#8CFF2E", // ação principal, CTAs, destaque
|
||||
secondary: "#388E3C", // ações secundárias, títulos
|
||||
backgroundLight: "#F7F8F5",
|
||||
backgroundDark: "#18230F",
|
||||
textPrimary: "#333333",
|
||||
textInverse: "#FFFFFF"
|
||||
}
|
||||
```
|
||||
|
||||
Uso:
|
||||
- Primary → botões principais, ícones ativos, ratings
|
||||
- Secondary → headers, botões secundários
|
||||
- BackgroundLight → fundo global
|
||||
- BackgroundDark → dark mode
|
||||
|
||||
---
|
||||
|
||||
### 1.2 🌙 Dark Theme
|
||||
|
||||
Dark mode nativo, não invertido.
|
||||
|
||||
```ts
|
||||
dark: {
|
||||
background: "#18230F",
|
||||
surface: "rgba(255,255,255,0.05)",
|
||||
textPrimary: "#FFFFFF",
|
||||
textSecondary: "#A3A3A3",
|
||||
primary: "#8CFF2E",
|
||||
secondary: "#388E3C"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 🔤 Tipografia
|
||||
|
||||
Fonte oficial:
|
||||
- **Plus Jakarta Sans**
|
||||
|
||||
```ts
|
||||
typography: {
|
||||
fontFamily: "'Plus Jakarta Sans', sans-serif",
|
||||
|
||||
heading1: {
|
||||
size: "30px",
|
||||
weight: 800
|
||||
},
|
||||
|
||||
heading2: {
|
||||
size: "22px",
|
||||
weight: 800
|
||||
},
|
||||
|
||||
body: {
|
||||
size: "16px",
|
||||
weight: 400,
|
||||
lineHeight: "1.6"
|
||||
},
|
||||
|
||||
button: {
|
||||
size: "14px",
|
||||
weight: 800,
|
||||
transform: "uppercase",
|
||||
letterSpacing: "0.08em"
|
||||
},
|
||||
|
||||
caption: {
|
||||
size: "10px",
|
||||
weight: 700,
|
||||
letterSpacing: "0.12em"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.4 📐 Bordas, Espaçamento e Elevação
|
||||
|
||||
```ts
|
||||
radius: {
|
||||
md: "0.75rem",
|
||||
lg: "1rem",
|
||||
xl: "1.5rem",
|
||||
full: "9999px"
|
||||
}
|
||||
|
||||
spacing: [4, 8, 12, 16, 24, 32]
|
||||
|
||||
shadow: {
|
||||
soft: "0 4px 20px -2px rgba(0,0,0,0.05)",
|
||||
glow: "0 8px 24px rgba(140,255,46,0.2)"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. COMPONENTES
|
||||
|
||||
### 2.1 🔘 Botões
|
||||
|
||||
#### Primary Action Button
|
||||
- Fundo: primary
|
||||
- Texto: secondary
|
||||
- Altura: 56px
|
||||
- Radius: lg
|
||||
- Sombra glow
|
||||
- Feedback de escala
|
||||
|
||||
```ts
|
||||
Button {
|
||||
variant: "primary" | "secondary"
|
||||
fullWidth: boolean
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 🔍 Input & Search
|
||||
|
||||
- Fundo claro
|
||||
- Ícone à esquerda
|
||||
- Sem borda
|
||||
- Focus ring em primary
|
||||
|
||||
```ts
|
||||
SearchInput {
|
||||
placeholder: string
|
||||
icon: "search"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 🃏 Food Card
|
||||
|
||||
Contém:
|
||||
- Imagem hero
|
||||
- Botão de favorito flutuante
|
||||
- Nome
|
||||
- Rating
|
||||
- Tempo estimado
|
||||
|
||||
```ts
|
||||
FoodCard {
|
||||
title: string
|
||||
image: string
|
||||
rating: number
|
||||
timeRange: string
|
||||
favorite: boolean
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 🧭 Navigation Bar
|
||||
|
||||
- Fixa no rodapé
|
||||
- Ícones Material Symbols
|
||||
- Item ativo em primary
|
||||
|
||||
```ts
|
||||
TabBar {
|
||||
items: TabItem[]
|
||||
activeIndex: number
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 🧩 Ícones
|
||||
|
||||
- Material Symbols Outlined
|
||||
- Peso visual leve
|
||||
- Cor semântica
|
||||
|
||||
```ts
|
||||
Icon {
|
||||
name: string
|
||||
active?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. IMPLEMENTAÇÃO (TAILWIND-FIRST)
|
||||
|
||||
### 3.1 Tokens no Tailwind
|
||||
|
||||
```ts
|
||||
theme.extend.colors = {
|
||||
primary: "#8CFF2E",
|
||||
secondary: "#388E3C",
|
||||
backgroundLight: "#F7F8F5",
|
||||
backgroundDark: "#18230F",
|
||||
typographyDark: "#333333"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. PRINCÍPIOS DO SISTEMA
|
||||
|
||||
- Mobile-first
|
||||
- Tailwind-first
|
||||
- Dark mode por classe
|
||||
- Tokens semânticos
|
||||
- UI energética e saudável
|
||||
- Feedback visual imediato
|
||||
BIN
reference_img/cart.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
reference_img/cart_checkout.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
reference_img/category_all.png
Normal file
|
After Width: | Height: | Size: 621 KiB |
BIN
reference_img/create_account.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
reference_img/cuppons.png
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
reference_img/home_tabs.png
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
reference_img/login.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
reference_img/my_address.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
reference_img/my_favorites.png
Normal file
|
After Width: | Height: | Size: 664 KiB |