feat: enhance filament management and UI improvements
- Update SpoolPresetRepository to include user_id in the update query. - Expand .gitignore to include various environment and temporary files. - Revise agent documentation for better clarity and formatting. - Implement pull-to-refresh functionality in the inventory list. - Integrate API calls for deleting and updating filaments, ensuring state synchronization. - Add custom color picker for filament color selection with hex validation. - Update AndroidManifest and Gradle files for improved configuration and permissions. - Refactor MainActivity and MainApplication for better splash screen handling. - Update styles and colors for a cohesive UI experience. - Replace splash screen logos and icons with new assets.
This commit is contained in:
+211
-64
@@ -10,24 +10,24 @@ O app é **offline-first**: todos os dados são persistidos localmente em **SQLi
|
||||
|
||||
## Stack
|
||||
|
||||
| Componente | Biblioteca | Observação |
|
||||
|-----------------------|------------------------------------|-----------------------------------------------|
|
||||
| Framework | `expo` ~51 | Managed Workflow |
|
||||
| Navigation | `expo-router` ~3 | File-system routing |
|
||||
| Linguagem | TypeScript 5.x | strict mode |
|
||||
| Node Version | 22 LTS | Gerenciado via `mise` (`.mise.toml`) |
|
||||
| Java Version | 17 | Obrigatório para React Native 0.81 (Java 24 quebra CMake) |
|
||||
| UI | React Native + `@expo/vector-icons`| Ionicons |
|
||||
| Forms | `react-hook-form` + `zod` | Validação em runtime |
|
||||
| Estado global | `zustand` | Stores em `src/store/` |
|
||||
| HTTP | `axios` | Interceptor Bearer + refresh automático |
|
||||
| DB local | `expo-sqlite` + SQLCipher | WAL mode, foreign keys |
|
||||
| Auth persistência | `expo-secure-store` | JWT cifrado no keychain |
|
||||
| Safe Area | `react-native-safe-area-context` | |
|
||||
| Gesture Handler | `react-native-gesture-handler` | |
|
||||
| Animations | `react-native-reanimated` | |
|
||||
| QR Code render | `react-native-qrcode-svg` | Render de QR Code em tela |
|
||||
| Câmera / Scanner | `expo-camera` ~17 | ML Kit barcode scan; requer development build |
|
||||
| Componente | Biblioteca | Observação |
|
||||
| ----------------- | ----------------------------------- | --------------------------------------------------------- |
|
||||
| Framework | `expo` ~51 | Managed Workflow |
|
||||
| Navigation | `expo-router` ~3 | File-system routing |
|
||||
| Linguagem | TypeScript 5.x | strict mode |
|
||||
| Node Version | 22 LTS | Gerenciado via `mise` (`.mise.toml`) |
|
||||
| Java Version | 17 | Obrigatório para React Native 0.81 (Java 24 quebra CMake) |
|
||||
| UI | React Native + `@expo/vector-icons` | Ionicons |
|
||||
| Forms | `react-hook-form` + `zod` | Validação em runtime |
|
||||
| Estado global | `zustand` | Stores em `src/store/` |
|
||||
| HTTP | `axios` | Interceptor Bearer + refresh automático |
|
||||
| DB local | `expo-sqlite` + SQLCipher | WAL mode, foreign keys |
|
||||
| Auth persistência | `expo-secure-store` | JWT cifrado no keychain |
|
||||
| Safe Area | `react-native-safe-area-context` | |
|
||||
| Gesture Handler | `react-native-gesture-handler` | |
|
||||
| Animations | `react-native-reanimated` | |
|
||||
| QR Code render | `react-native-qrcode-svg` | Render de QR Code em tela |
|
||||
| Câmera / Scanner | `expo-camera` ~17 | ML Kit barcode scan; requer development build |
|
||||
|
||||
---
|
||||
|
||||
@@ -170,20 +170,20 @@ Arquivo: `src/shared/theme.ts`
|
||||
|
||||
### Paleta (siamês)
|
||||
|
||||
| Token | Valor | Uso |
|
||||
|------------------|-------------|--------------------------------------|
|
||||
| `bgBase` | `#1E1B18` | Fundo geral |
|
||||
| `bgSurface` | `#2A2622` | Cards, containers |
|
||||
| `bgHover` | `#332F2B` | Hover/pressed em cards |
|
||||
| `textPrimary` | `#F5EEDC` | Títulos, peso líquido |
|
||||
| `textSecondary` | `#C9C1B0` | Descrições, labels |
|
||||
| `accent` | `#38BCC2` | Botões de ação, elementos ativos |
|
||||
| `accentMuted` | `#38BCC226` | Background de badges accent (10%) |
|
||||
| `stockLow` | `#FF6B6B` | ≤ 15% — vermelho |
|
||||
| `stockMedium` | `#FF9F43` | ≤ 35% — laranja |
|
||||
| `stockOk` | `#38BCC2` | > 35% — accent |
|
||||
| `border` | `#3D3830` | Bordas sutis |
|
||||
| `error` | `#FF6B6B` | Mensagens de erro |
|
||||
| Token | Valor | Uso |
|
||||
| --------------- | ----------- | --------------------------------- |
|
||||
| `bgBase` | `#1E1B18` | Fundo geral |
|
||||
| `bgSurface` | `#2A2622` | Cards, containers |
|
||||
| `bgHover` | `#332F2B` | Hover/pressed em cards |
|
||||
| `textPrimary` | `#F5EEDC` | Títulos, peso líquido |
|
||||
| `textSecondary` | `#C9C1B0` | Descrições, labels |
|
||||
| `accent` | `#38BCC2` | Botões de ação, elementos ativos |
|
||||
| `accentMuted` | `#38BCC226` | Background de badges accent (10%) |
|
||||
| `stockLow` | `#FF6B6B` | ≤ 15% — vermelho |
|
||||
| `stockMedium` | `#FF9F43` | ≤ 35% — laranja |
|
||||
| `stockOk` | `#38BCC2` | > 35% — accent |
|
||||
| `border` | `#3D3830` | Bordas sutis |
|
||||
| `error` | `#FF6B6B` | Mensagens de erro |
|
||||
|
||||
### Tipografia
|
||||
|
||||
@@ -255,10 +255,10 @@ operation TEXT, payload TEXT, created_at TEXT
|
||||
Sempre usar aliases em vez de caminhos relativos:
|
||||
|
||||
```ts
|
||||
import { Filament } from '@domain/Filament';
|
||||
import { useFilamentStore } from '@store/filamentStore';
|
||||
import { colors } from '@shared/theme';
|
||||
import { Button } from '@presentation/components/ui/Button';
|
||||
import { Filament } from "@domain/Filament";
|
||||
import { useFilamentStore } from "@store/filamentStore";
|
||||
import { colors } from "@shared/theme";
|
||||
import { Button } from "@presentation/components/ui/Button";
|
||||
```
|
||||
|
||||
### Componentes de tela
|
||||
@@ -283,43 +283,43 @@ Base: `EXPO_PUBLIC_API_URL` (padrão: `http://localhost:3000/api/v1`)
|
||||
|
||||
### Auth
|
||||
|
||||
| Método | Rota | Descrição |
|
||||
|--------|-------------------------------|------------------------------|
|
||||
| POST | `/auth/login` | Login email/senha |
|
||||
| POST | `/auth/register` | Cadastro |
|
||||
| POST | `/auth/google` | OAuth Google |
|
||||
| POST | `/auth/refresh` | Refresh token |
|
||||
| POST | `/auth/logout` | Logout |
|
||||
| POST | `/auth/forgot-password` | Solicitar reset de senha |
|
||||
| POST | `/auth/reset-password` | Confirmar reset com token |
|
||||
| POST | `/auth/verify-email` | Verificar e-mail com código |
|
||||
| Método | Rota | Descrição |
|
||||
| ------ | ----------------------- | --------------------------- |
|
||||
| POST | `/auth/login` | Login email/senha |
|
||||
| POST | `/auth/register` | Cadastro |
|
||||
| POST | `/auth/google` | OAuth Google |
|
||||
| POST | `/auth/refresh` | Refresh token |
|
||||
| POST | `/auth/logout` | Logout |
|
||||
| POST | `/auth/forgot-password` | Solicitar reset de senha |
|
||||
| POST | `/auth/reset-password` | Confirmar reset com token |
|
||||
| POST | `/auth/verify-email` | Verificar e-mail com código |
|
||||
|
||||
### Filaments
|
||||
|
||||
| Método | Rota | Descrição |
|
||||
|--------|-------------------------------|------------------------------|
|
||||
| GET | `/filaments` | Listar (com filtros) |
|
||||
| POST | `/filaments` | Criar |
|
||||
| GET | `/filaments/:id` | Detalhe |
|
||||
| PATCH | `/filaments/:id` | Atualizar |
|
||||
| DELETE | `/filaments/:id` | Excluir |
|
||||
| GET | `/filaments/:id/qrcode` | QR Code SVG |
|
||||
| GET | `/filaments/:id/label` | Etiqueta SVG |
|
||||
| Método | Rota | Descrição |
|
||||
| ------ | ----------------------- | -------------------- |
|
||||
| GET | `/filaments` | Listar (com filtros) |
|
||||
| POST | `/filaments` | Criar |
|
||||
| GET | `/filaments/:id` | Detalhe |
|
||||
| PATCH | `/filaments/:id` | Atualizar |
|
||||
| DELETE | `/filaments/:id` | Excluir |
|
||||
| GET | `/filaments/:id/qrcode` | QR Code SVG |
|
||||
| GET | `/filaments/:id/label` | Etiqueta SVG |
|
||||
|
||||
### Spool Presets
|
||||
|
||||
| Método | Rota | Descrição |
|
||||
|--------|-------------------------------|------------------------------|
|
||||
| GET | `/spool-presets` | Listar (sistema + usuário) |
|
||||
| POST | `/spool-presets` | Criar preset do usuário |
|
||||
| PATCH | `/spool-presets/:id` | Atualizar preset do usuário |
|
||||
| DELETE | `/spool-presets/:id` | Excluir preset do usuário |
|
||||
| Método | Rota | Descrição |
|
||||
| ------ | -------------------- | --------------------------- |
|
||||
| GET | `/spool-presets` | Listar (sistema + usuário) |
|
||||
| POST | `/spool-presets` | Criar preset do usuário |
|
||||
| PATCH | `/spool-presets/:id` | Atualizar preset do usuário |
|
||||
| DELETE | `/spool-presets/:id` | Excluir preset do usuário |
|
||||
|
||||
### Dashboard
|
||||
|
||||
| Método | Rota | Descrição |
|
||||
|--------|-------------------------------|------------------------------|
|
||||
| GET | `/dashboard` | Dados agregados do dashboard |
|
||||
| Método | Rota | Descrição |
|
||||
| ------ | ------------ | ---------------------------- |
|
||||
| GET | `/dashboard` | Dados agregados do dashboard |
|
||||
|
||||
---
|
||||
|
||||
@@ -336,6 +336,152 @@ Base: `EXPO_PUBLIC_API_URL` (padrão: `http://localhost:3000/api/v1`)
|
||||
|
||||
---
|
||||
|
||||
## Mudanças Recentes (14/03/2026)
|
||||
|
||||
### ✅ Implementações Completadas
|
||||
|
||||
#### 1. **Delete Filament — Integração com API**
|
||||
|
||||
- **Arquivos**: `app/(app)/inventory/[id].tsx`, `app/(app)/inventory/[id]/edit.tsx`
|
||||
- **Mudança**: `handleDelete()` agora chama `deleteFilamentUseCase.execute()` antes de remover do store local
|
||||
- **Código**:
|
||||
```typescript
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
const user = useAuthStore.getState().user;
|
||||
await deleteFilamentUseCase.execute(filament!.id, user?.id);
|
||||
removeFilament(filament!.id);
|
||||
Alert.alert("Sucesso", "Filamento deletado da API");
|
||||
} catch (error) {
|
||||
Alert.alert("Erro", "Falha ao deletar: " + (error as Error).message);
|
||||
}
|
||||
};
|
||||
```
|
||||
- **Impacto**: Delete agora funciona corretamente em ambos os screens (detail e edit)
|
||||
|
||||
#### 2. **Pull-to-Refresh — Inventory List**
|
||||
|
||||
- **Arquivo**: `app/(tabs)/inventory.tsx`
|
||||
- **Mudança**: Adicionado `RefreshControl` ao FlatList com chamada a `listFilamentsUseCase.execute()`
|
||||
- **Código**:
|
||||
|
||||
```typescript
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
|
||||
const onRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
const user = useAuthStore.getState().user;
|
||||
const result = await listFilamentsUseCase.execute(user?.id);
|
||||
setFilaments(result);
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
<FlatList
|
||||
refreshControl={<RefreshControl refreshing={isRefreshing} onRefresh={onRefresh} />}
|
||||
// ... resto do componente
|
||||
/>
|
||||
```
|
||||
|
||||
- **Impacto**: Usuários podem puxar para baixo e sincronizar lista sem reabrir o app
|
||||
|
||||
#### 3. **Update Filament — Integração com API**
|
||||
|
||||
- **Arquivo**: `app/(app)/inventory/[id]/edit.tsx`
|
||||
- **Mudança**: `onSubmit()` agora chama `updateFilamentUseCase.execute()` em vez de apenas atualizar o store local
|
||||
- **Código**:
|
||||
```typescript
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
const user = useAuthStore.getState().user;
|
||||
const result = await updateFilamentUseCase.execute(
|
||||
{
|
||||
id: filament!.id,
|
||||
brand: formData.brand,
|
||||
model: formData.model,
|
||||
color: formData.color,
|
||||
weight_g: parseFloat(formData.weight_g),
|
||||
},
|
||||
user?.id,
|
||||
);
|
||||
|
||||
updateFilament(result);
|
||||
Alert.alert("Sucesso", "Filamento atualizado na API");
|
||||
router.back();
|
||||
} catch (error) {
|
||||
Alert.alert("Erro", "Falha ao atualizar: " + (error as Error).message);
|
||||
}
|
||||
};
|
||||
```
|
||||
- **Impacto**: Edições de filamento agora persistem no servidor
|
||||
|
||||
#### 4. **Custom Color Picker — Hex Input com Validação**
|
||||
|
||||
- **Arquivos**: `app/(app)/inventory/new.tsx`, `app/(app)/inventory/[id]/edit.tsx`
|
||||
- **Mudança**: Adicionado `handleOpenColorPicker()` que usa `Alert.prompt()` para capturar hexadecimais
|
||||
- **Validação**:
|
||||
|
||||
```typescript
|
||||
const isValidHex = (hex: string): boolean => {
|
||||
return /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(hex);
|
||||
};
|
||||
|
||||
const normalizeHex = (hex: string): string => {
|
||||
return hex.startsWith("#") ? hex : `#${hex}`;
|
||||
};
|
||||
|
||||
const handleOpenColorPicker = () => {
|
||||
setIsOpeningColorPicker(true);
|
||||
Alert.prompt(
|
||||
"Hex Color",
|
||||
"Enter hex color (e.g., #FF5733 or FF5733)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
onPress: () => setIsOpeningColorPicker(false),
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Add Color",
|
||||
onPress: (input: string | undefined) => {
|
||||
if (input && isValidHex(input)) {
|
||||
setFormData((prev) => ({ ...prev, color: normalizeHex(input) }));
|
||||
} else {
|
||||
Alert.alert("Invalid hex color", "Please enter a valid hex code");
|
||||
}
|
||||
setIsOpeningColorPicker(false);
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
- **Integração**: Botão "+" vinculado a `handleOpenColorPicker`
|
||||
- **Impacto**: Usuários podem agora inserir cores customizadas por código hexadecimal
|
||||
|
||||
#### 5. **Sincronização de Estado — API-First Pattern**
|
||||
|
||||
- **Padrão**: Todas operações agora seguem: **API call → store update**
|
||||
- **Benefício**: Source of truth centralizada no servidor
|
||||
- **Implementação**:
|
||||
- Delete: `deleteFilamentUseCase` → `removeFilament()`
|
||||
- Update: `updateFilamentUseCase` → `updateFilament(result)`
|
||||
- Refresh: `listFilamentsUseCase` → `setFilaments(result)`
|
||||
- Create: `createFilamentUseCase` → `addFilament(result)`
|
||||
|
||||
### Arquitetura Mantida
|
||||
|
||||
- Todos use cases injetados via `container.ts` (Dependency Injection)
|
||||
- Zod schemas usados para validação de formulários
|
||||
- JWT interceptor em Axios automaticamente adiciona `Authorization` header
|
||||
- Error handling com `Alert.alert()` visível ao usuário
|
||||
|
||||
---
|
||||
|
||||
## Executando o Projeto
|
||||
|
||||
```bash
|
||||
@@ -354,6 +500,7 @@ mise exec -- npx expo run:ios
|
||||
```
|
||||
|
||||
> **Variável de ambiente**: crie `mobile/.env` com:
|
||||
>
|
||||
> ```
|
||||
> EXPO_PUBLIC_API_URL=http://localhost:3000/api/v1
|
||||
> ```
|
||||
|
||||
Reference in New Issue
Block a user