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:
2026-03-14 14:14:06 -03:00
parent 416e13893c
commit 9c0b3d584c
33 changed files with 519 additions and 164 deletions
+211 -64
View File
@@ -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
> ```