Przejdź do treści

Architektura aplikacji

Struktura projektu

lib/
├── main.dart                  # Punkt wejścia, konfiguracja motywu, bottom nav
├── models/
│   ├── part.dart              # Part, PartLot, PartParameter
│   ├── api_exception.dart     # Wyjątek API z kodem HTTP
│   └── label_config.dart      # Konfiguracja etykiet Niimbot
├── pages/
│   ├── search_page.dart       # Główny ekran wyszukiwania
│   ├── barcode_scan_page.dart # Kamera + ML Kit
│   ├── part_detail_page.dart  # Szczegóły i edycja części
│   ├── ipn_generator_page.dart# Generator IPN
│   ├── category_browser_page.dart # Drzewo kategorii
│   ├── config_page.dart       # Konfiguracja serwera
│   ├── label_print_page.dart  # Drukowanie Niimbot
│   └── stock_taking_page.dart # Inwentaryzacja
└── services/
    ├── api_service.dart        # Klient REST Part-DB
    ├── history_service.dart    # Historia ostatnio oglądanych
    ├── export_service.dart     # Eksport CSV
    ├── printer_service.dart    # Drukarka Sunmi
    ├── printer_controller.dart # Abstrakcja Sunmi
    └── niimbot_service.dart    # Drukarka Niimbot D101

Wzorzec architektoniczny

Aplikacja używa wzorca Provider do zarządzania stanem i wstrzykiwania zależności.

main.dart
└── MultiProvider
    └── Provider<ApiService>        # Singleton klienta API
        └── MaterialApp
            └── HomePage (StatefulWidget)
                └── BottomNavigationBar
                    ├── SearchPage
                    ├── IpnGeneratorPage
                    ├── CategoryBrowserPage
                    └── ConfigPage

Każdy ekran jest niezależnym StatefulWidget i odczytuje ApiService z kontekstu przez Provider.of<ApiService>(context).


Graf nawigacji

graph TD
    Home["HomePage\n(BottomNavigation)"]
    Search["SearchPage"]
    IPN["IpnGeneratorPage"]
    Cat["CategoryBrowserPage"]
    Cfg["ConfigPage"]

    Home --> Search
    Home --> IPN
    Home --> Cat
    Home --> Cfg

    Search -->|"push"| Detail["PartDetailPage"]
    Search -->|"push"| Scan1["BarcodeScanPage"]
    Search -->|"push"| StockTaking["StockTakingPage"]

    Detail -->|"push"| Label["LabelPrintPage"]
    Detail -->|"push"| Scan2["BarcodeScanPage"]

    Cat -->|"push"| CatParts["_CategoryPartsPage"]
    CatParts -->|"push"| Detail

    Cfg -->|"push"| Scan3["BarcodeScanPage"]

Note

BarcodeScanPage zwraca wynik przez Navigator.pop(context, result). Wywoływana jest z trzech różnych miejsc z różnymi kontekstami użycia (wyszukiwanie IPN, skanowanie tokenu, skanowanie w inwentaryzacji).


Przepływ danych

sequenceDiagram
    participant U as Użytkownik
    participant P as SearchPage
    participant A as ApiService
    participant S as Part-DB Server

    U->>P: wpisuje zapytanie
    P->>A: searchParts(query)
    A->>S: GET /api/parts?name={query}
    S-->>A: [{id, name, ipn, ...}]
    A-->>P: List<Part>
    P->>U: wyświetla listę

    U->>P: wybiera część
    P->>A: fetchPartById(id)
    A->>S: GET /api/parts/{id}
    S-->>A: {parameters[], partLots[], ...}
    A-->>P: Part (pełny)
    P->>U: PartDetailPage

Serwisy

ApiService

Centralny klient HTTP dla Part-DB API. Przechowuje konfigurację (URL, token) w Flutter Secure Storage – dane są zaszyfrowane kluczem sprzętowym Android Keystore.

Kluczowe metody:

Metoda Opis
searchParts(query) Wyszukiwanie wielotryb (IPN, nazwa, parametr, wartość, auto)
fetchPartById(id) Pełne dane części z parametrami i partiami
fetchAllParts() Stronicowany pobór wszystkich części (max 2000)
patchPartLot(id, amount) Aktualizacja ilości w lokalizacji
patchPartParameter(id, value) Aktualizacja wartości parametru
patchPartIpn(id, ipn) Nadanie IPN do części
fetchCategories() Drzewo kategorii (max 200, paginacja)
uploadAttachment(partId, bytes) Przesłanie zdjęcia jako base64

Timeout standardowy: 10 s, dla uploadu pliku: 30 s.

HistoryService

Przechowuje ostatnie 20 przeglądanych części w SharedPreferences jako JSON. Używany przez SearchPage do wyświetlenia historii przy pustym polu wyszukiwania.

ExportService

Generuje plik CSV z wyników wyszukiwania (ID, IPN, Nazwa, Stan, Min, Kategoria, Producent, Opis) i udostępnia go przez natywny dialog share_plus.

NiimbotService

Generuje bitmapy etykiet za pomocą dart:ui Canvas API i przesyła je przez Bluetooth do drukarki Niimbot D101. Obsługuje trzy typy etykiet: szufladkową (22×14 mm) i dwa warianty szpulowe (12×40 mm).

PrinterService / PrinterController

Abstrakcja nad SunmiPrinterPlus do drukowania paragonów termicznych na urządzeniach Sunmi. Formatuje dane części (nazwa, IPN, parametry, lokalizacje, kod QR).


Przechowywanie danych lokalnych

Dane Mechanizm Klucz
Adres serwera Flutter Secure Storage partdb_base_url
Token API Flutter Secure Storage partdb_token
Zoom kamery Flutter Secure Storage camera_zoom
Historia części SharedPreferences (JSON) part_history
Konfiguracja etykiet SharedPreferences (JSON) niimbot_label_params

Motyw

Material 3, tryb ciemny, kolor dominujący: Deep Orange (#FF9800). Tryb ciemny jest domyślny i jedyny dostępny (ThemeMode.dark).


Zależności kluczowe

Pakiet Wersja Rola
flutter SDK ^3.9.2 Framework UI
provider ^6.0.5 Wstrzykiwanie zależności / state management
http ^1.2.2 Klient REST
flutter_secure_storage ^10.0.0 Szyfrowane przechowywanie tokenu
shared_preferences ^2.2.3 Lokalna historia i konfiguracja
camera ^0.11.0+2 Podgląd kamery
google_mlkit_barcode_scanning ^0.14.2 Detekcja kodów kreskowych
niim_blue_flutter ^1.0.0 Drukarka Niimbot (Bluetooth)
sunmi_printer_plus ^4.1.1 Drukarka Sunmi
barcode 2.2.9 Generowanie Data Matrix / Code128
share_plus ^10.1.2 Eksport CSV
image_picker ^1.1.2 Wybór zdjęcia do załącznika