commit 7cef6954ebf7121f8b281f3efe32f1d874930c00 Author: Carlo Baratto Date: Thu Oct 8 22:10:51 2026 +0200 scheletro porting nativo Sailfish OS di Henflow: packaging, settings, pagine, icone, i18n diff --git a/README.md b/README.md new file mode 100644 index 0000000..513f572 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# harbour-henflow + +Porting **nativo Sailfish OS** dell'app Android *Henflow* (`it.carlobaratto.henflow`, +Gestione ordini uova, galline e consegne). UI in QML/Silica, logica e dati in C++, +pubblicazione prevista sullo **store Jolla Harbour**. + +Il porting **non** usa il runtime Flutter community (`flutter-sailfishos`): quello +resta una via OpenRepos, non Harbour-safe, e non supporta i plugin nativi Android +(AdMob/Firebase/Billing). Dati locali sul telefono: nessun account, nessun cloud. + +## Stato + +Guscio completo e pronto alla prima build (finestra, pagina principale, cover, +impostazioni con lingua, icone, catalogo traduzioni, spec RPM). Le schermate sono +segnaposto: vanno mappate 1:1 dai sorgenti Flutter. + +## Struttura + +``` +harbour-henflow.pro # QT += quick qml, INSTALLS icone +rpm/harbour-henflow.spec # lrelease + desktop cercati con find (layout sorgente variabile) +rpm/harbour-henflow.desktop # profilo [X-Sailjail] esplicito (dati persistenti) +src/main.cpp # SailfishApp, context property appSettings, lingua +src/settings.h/.cpp # QSettings su path esplicito AppConfigLocation + sync() +qml/harbour-henflow.qml # ApplicationWindow: initialPage + cover +qml/pages/MainPage.qml # segnaposto con i moduli previsti +qml/cover/CoverPage.qml +icons/make_icons.py # icone 86/108/128/172 (PIL) +translations/*.ts # stringhe sorgente INGLESI + catalogo italiano +``` + +## Build e deploy + +Sul PC con l'SDK Sailfish (qui non c'e' sfdk): + +```sh +sfdk target use SailfishOS-5.0.0.62-aarch64 # o il target del tuo device +sfdk build # RPM in build-*/RPMS/ +sfdk deploy # oppure Qt Creator +``` + +Tarball per lo store (con `--prefix`, esclude .git e i file non tracciati): + +```sh +git archive --format=tar.gz --prefix=harbour-henflow-0.1.0/ -o harbour-henflow-0.1.0.tar.gz HEAD +``` + +## Modello dati proposto (da confermare sui sorgenti Flutter) + +Un file JSON in `AppConfigLocation` (`~/.config/harbour/henflow/henflow.json`) +e' sufficiente: nessuna dipendenza extra e nessun driver SQL da verificare sul +device. SQLite resta l'alternativa se servono query piu' ricche. + +| entita' | campi | +|---|---| +| `hens` | id, nome, razza, data_nascita, uova_settimana (stima), attiva, note | +| `egg_log` | data, uova_raccolte (+ eventuale id gallina) | +| `customers` | id, nome, telefono, quantita_abituale, giorno_consegna, note | +| `orders` | id, id_cliente, quantita, data_ordine, data_consegna, stato, note | + +Derivati: **uova disponibili oggi** = produzione stimata/registrata meno gli ordini +con data di consegna odierna; **data consegna automatica** = primo giorno di +consegna del cliente successivo all'ordine. + +## Prossimi passi + +1. Copiare i sorgenti Flutter sul server (vedi sotto). +2. Mappare schermate e campi reali (non la descrizione dello store). +3. Implementare storage + schermate, verifica su device (192.168.2.15). +4. Asset store (icona, cover 1080x540), descrizioni EN, upload Harbour. + +```sh +# dal PC con i sorgenti Flutter (LAN o VPN) +rsync -av --exclude build --exclude .dart_tool --exclude .git \ + ~/percorso/henflow/ kaneda@192.168.3.21:/home/kaneda/workspace/henflow-flutter/ +``` diff --git a/harbour-henflow.pro b/harbour-henflow.pro new file mode 100644 index 0000000..3f94eb2 --- /dev/null +++ b/harbour-henflow.pro @@ -0,0 +1,45 @@ +TEMPLATE = app +TARGET = harbour-henflow + +CONFIG += sailfishapp +CONFIG += c++11 + +# Il sailfishapp.prf del kit 5.1 NON aggiunge da solo gli include QtQuick/QtQml +# (sintomo: "QQuickView: No such file or directory"). +QT += quick qml + +SOURCES += \ + src/main.cpp \ + src/settings.cpp + +HEADERS += \ + src/settings.h + +OTHER_FILES += \ + qml/harbour-henflow.qml \ + qml/pages/*.qml \ + qml/cover/*.qml \ + rpm/harbour-henflow.spec \ + rpm/harbour-henflow.desktop \ + translations/harbour-henflow.ts \ + translations/harbour-henflow_it.ts + +DISTFILES += $$OTHER_FILES + +# Le traduzioni NON passano dal template sailfishapp_i18n (sul kit 5.1 il .qm +# non viene generato/installato in modo affidabile): lo spec compila il catalogo +# con lrelease e lo installa nel datadir dell'app, main.cpp lo carica da li'. +# Le stringhe sorgente sono INGLESI (lingua di default di Sailfish). + +# Icone: struttura standard hicolor, path assoluti (gli install manuali con +# path relativi falliscono se il tar sorgente e' incompleto). +icons86.path = /usr/share/icons/hicolor/86x86/apps +icons86.files = $$PWD/icons/86x86/harbour-henflow.png +icons108.path = /usr/share/icons/hicolor/108x108/apps +icons108.files = $$PWD/icons/108x108/harbour-henflow.png +icons128.path = /usr/share/icons/hicolor/128x128/apps +icons128.files = $$PWD/icons/128x128/harbour-henflow.png +icons172.path = /usr/share/icons/hicolor/172x172/apps +icons172.files = $$PWD/icons/172x172/harbour-henflow.png + +INSTALLS += icons86 icons108 icons128 icons172 diff --git a/icons/108x108/harbour-henflow.png b/icons/108x108/harbour-henflow.png new file mode 100644 index 0000000..dabae7c Binary files /dev/null and b/icons/108x108/harbour-henflow.png differ diff --git a/icons/128x128/harbour-henflow.png b/icons/128x128/harbour-henflow.png new file mode 100644 index 0000000..82260b0 Binary files /dev/null and b/icons/128x128/harbour-henflow.png differ diff --git a/icons/172x172/harbour-henflow.png b/icons/172x172/harbour-henflow.png new file mode 100644 index 0000000..e74e1a8 Binary files /dev/null and b/icons/172x172/harbour-henflow.png differ diff --git a/icons/86x86/harbour-henflow.png b/icons/86x86/harbour-henflow.png new file mode 100644 index 0000000..6821277 Binary files /dev/null and b/icons/86x86/harbour-henflow.png differ diff --git a/icons/make_icons.py b/icons/make_icons.py new file mode 100644 index 0000000..66c4f7b --- /dev/null +++ b/icons/make_icons.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Genera le icone dell'app in stile Sailfish OS: + sfondo a gradiente ambrato->arancio bruciato con angoli molto arrotondati, + un uovo bianco con ombra morbida e una pagliuzza di grano. + Dimensioni: 86, 108, 128, 172 (struttura standard hicolor).""" +import os +from PIL import Image, ImageDraw + +C_TOP = (250, 214, 137) # #FAD689 ambrato chiaro +C_BOT = (196, 122, 41) # #C47A29 arancio bruciato +EGG = (255, 253, 246) +STRAW = (255, 236, 179) + + +def gradient(size, c1, c2): + img = Image.new("RGB", (size, size)) + d = ImageDraw.Draw(img) + for y in range(size): + t = y / (size - 1) + d.line([(0, y), (size, y)], + fill=(int(c1[0] + (c2[0] - c1[0]) * t), + int(c1[1] + (c2[1] - c1[1]) * t), + int(c1[2] + (c2[2] - c1[2]) * t))) + return img + + +def draw_icon(size): + m = size * 0.05 + r = size * 0.22 + out = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + + grad = gradient(size, C_TOP, C_BOT).convert("RGBA") + mask = Image.new("L", (size, size), 0) + ImageDraw.Draw(mask).rounded_rectangle([m, m, size - m, size - m], + radius=r, fill=255) + out.paste(grad, (0, 0), mask) + + d = ImageDraw.Draw(out, "RGBA") + + # pagliuzza: due archi chiari in basso, suggeriscono il nido + lw = max(2, int(size * 0.03)) + for k, off in ((0.0, 0.0), (0.06, 0.03)): + d.arc([size * (0.16 + k), size * (0.62 + off), + size * (0.84 - k), size * (1.02 + off)], + start=200, end=340, fill=STRAW + (110,), width=lw) + + # uovo bianco, leggermente spostato in alto, con ombra morbida + ex0, ey0 = size * 0.30, size * 0.22 + ex1, ey1 = size * 0.70, size * 0.70 + d.ellipse([ex0 + size * 0.015, ey0 + size * 0.02, + ex1 + size * 0.015, ey1 + size * 0.02], + fill=(90, 50, 10, 70)) + d.ellipse([ex0, ey0, ex1, ey1], fill=EGG + (255,)) + + # riflesso + d.ellipse([size * 0.35, size * 0.30, size * 0.45, size * 0.40], + fill=(255, 255, 255, 120)) + return out + + +def main(): + for s in (86, 108, 128, 172): + os.makedirs(f"icons/{s}x{s}", exist_ok=True) + draw_icon(s).save(f"icons/{s}x{s}/harbour-henflow.png") + print("icone scritte: 86, 108, 128, 172") + + +if __name__ == "__main__": + main() diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml new file mode 100644 index 0000000..01f3f14 --- /dev/null +++ b/qml/cover/CoverPage.qml @@ -0,0 +1,25 @@ +import QtQuick 2.2 +import Sailfish.Silica 1.0 + +CoverBackground { + Label { + id: title + anchors { + top: parent.top + topMargin: Theme.paddingLarge + horizontalCenter: parent.horizontalCenter + } + text: qsTr("Henflow") + color: Theme.primaryColor + } + + Label { + anchors.centerIn: parent + width: parent.width - 2 * Theme.paddingLarge + text: qsTr("Eggs, hens and orders") + font.pixelSize: Theme.fontSizeSmall + color: Theme.secondaryColor + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } +} diff --git a/qml/harbour-henflow.qml b/qml/harbour-henflow.qml new file mode 100644 index 0000000..81a8bb8 --- /dev/null +++ b/qml/harbour-henflow.qml @@ -0,0 +1,12 @@ +import QtQuick 2.2 +import Sailfish.Silica 1.0 +import "pages" +import "cover" + +ApplicationWindow +{ + id: app + + initialPage: Component { MainPage { } } + cover: Component { CoverPage { } } +} diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml new file mode 100644 index 0000000..c0bcd73 --- /dev/null +++ b/qml/pages/MainPage.qml @@ -0,0 +1,68 @@ +import QtQuick 2.2 +import Sailfish.Silica 1.0 + +Page { + id: page + allowedOrientations: Orientation.All + + SilicaListView { + id: listView + anchors.fill: parent + + header: PageHeader { + title: qsTr("Henflow") + } + + model: ListModel { + id: sections + // Moduli previsti dall'app Android, da mappare sulle schermate + // Flutter quando i sorgenti arrivano sul server. + Component.onCompleted: { + append({ "name": qsTr("Hens"), "note": qsTr("name, breed, hatch date, estimated production") }) + append({ "name": qsTr("Available eggs"), "note": qsTr("eggs ready today") }) + append({ "name": qsTr("Orders"), "note": qsTr("customer, quantity, automatic delivery date") }) + append({ "name": qsTr("Calendar"), "note": qsTr("orders and pickups") }) + append({ "name": qsTr("Customers"), "note": qsTr("regular buyers") }) + append({ "name": qsTr("Dashboard"), "note": qsTr("summary of the day") }) + } + } + + delegate: ListItem { + id: item + contentHeight: Theme.itemSizeMedium + + Column { + anchors.verticalCenter: parent.verticalCenter + x: Theme.horizontalPageMargin + width: parent.width - 2 * Theme.horizontalPageMargin + + Label { + text: model.name + color: item.highlighted ? Theme.highlightColor : Theme.primaryColor + truncationMode: TruncationMode.Fade + width: parent.width + } + + Label { + text: model.note + font.pixelSize: Theme.fontSizeExtraSmall + color: Theme.secondaryColor + truncationMode: TruncationMode.Fade + width: parent.width + } + } + } + + footer: Label { + x: Theme.horizontalPageMargin + width: parent.width - 2 * Theme.horizontalPageMargin + text: qsTr("Native port in progress") + font.pixelSize: Theme.fontSizeExtraSmall + color: Theme.secondaryHighlightColor + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + + VerticalScrollDecorator {} + } +} diff --git a/rpm/harbour-henflow.desktop b/rpm/harbour-henflow.desktop new file mode 100644 index 0000000..76f7d71 --- /dev/null +++ b/rpm/harbour-henflow.desktop @@ -0,0 +1,20 @@ +[Desktop Entry] +Name=Henflow +Name[it_IT]=Henflow +Comment=Manage hens, eggs and orders +Comment[it_IT]=Gestisci galline, uova e ordini +Exec=harbour-henflow +Icon=harbour-henflow +Type=Application +X-Nemo-Application-Type=silica-qt5 +Categories=Utility; + +# Profilo SailJail esplicito: senza X-Sailjail la sandbox di Sailfish 5.x usa il +# profilo default, che NASCONDE ~/.config/harbour -> impostazioni e dati non +# persistenti (sintomo: "salva via SSH ma non dal launcher"). +# Org/App dichiarati QUI (li legge la sandbox) e nel main.cpp (li legge +# QStandardPaths): AppConfigLocation = ~/.config/harbour/henflow. +# Nessuna permission: l'app e' completamente locale (nessuna rete). +[X-Sailjail] +OrganizationName=harbour +ApplicationName=henflow diff --git a/rpm/harbour-henflow.spec b/rpm/harbour-henflow.spec new file mode 100644 index 0000000..0050666 --- /dev/null +++ b/rpm/harbour-henflow.spec @@ -0,0 +1,81 @@ +Name: harbour-henflow +Summary: Manage hens, eggs and orders +Version: 0.1.0 +Release: 1 +Group: Qt/Qt +License: MIT +URL: https://git.hackatoniclife.com/kaneda/harbour-henflow +Source0: %{name}-%{version}.tar.gz +Source1: %{name}.desktop + +Requires: sailfishsilica-qt5 >= 0.10.9 +BuildRequires: pkgconfig(sailfishapp) >= 1.0.2 +BuildRequires: pkgconfig(Qt5Core) +BuildRequires: pkgconfig(Qt5Qml) +BuildRequires: pkgconfig(Qt5Quick) +BuildRequires: qt5-qttools-linguist +BuildRequires: desktop-file-utils + +%global debug_package %{nil} + +%description +Manage hens, egg production and customer orders locally on the phone. +Chicken registry (name, breed, hatch date), daily egg estimates, +available eggs today, quick order entry with automatic delivery date, +pickup calendar, regular customers and a simple dashboard. +No account, no cloud: all data stays on the device. + +%changelog +* Thu Oct 08 2026 Carlo Baratto - 0.1.0-1 +- Shell del porting nativo Sailfish OS: finestra, pagina principale, + cover, impostazioni (lingua) e pipeline RPM complete + +%prep +%setup -q -n %{name}-%{version} + +%build +%qmake5 +make %{?_smp_mflags} + +# Catalogo di traduzione generato esplicitamente con lrelease (il template +# sailfishapp_i18n non produce il .qm in modo affidabile sul kit 5.1). +# Il .ts viene cercato in modo robusto: sfdk usa i sorgenti live (shadow build) +# e la cartella sorgente puo' chiamarsi henflow-0.1.0 (tarball), harbour-henflow +# (clone) o harbour-henflow-main (zip di Gitea). +TS=$(find %{_builddir}/.. -maxdepth 4 -name '%{name}_it.ts' 2>/dev/null | head -1) +if [ -z "$TS" ]; then + echo "ERROR: %{name}_it.ts not found" >&2 + exit 1 +fi +lrelease "$TS" -qm %{_builddir}/%{name}_it.qm + +%install +make install INSTALL_ROOT=%{buildroot} +mkdir -p %{buildroot}%{_datadir}/applications + +# Cartella sorgente con nome variabile: cerca il desktop invece di assumere +# un path fisso; la sorgente dichiarata nello spec resta il fallback. +DESKTOP=$(find %{_builddir}/.. -maxdepth 4 -name '%{name}.desktop' 2>/dev/null | head -1) +if [ -z "$DESKTOP" ] && [ -f %{SOURCE1} ]; then DESKTOP=%{SOURCE1}; fi +if [ -z "$DESKTOP" ]; then + echo "ERROR: %{name}.desktop not found" >&2 + exit 1 +fi +install -m 0644 "$DESKTOP" %{buildroot}%{_datadir}/applications/%{name}.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop || true + +# Traduzione italiana nel datadir dell'app (il validatore RPM per Harbour +# rifiuta la cartella di sistema delle traduzioni); main.cpp la carica da qui. +mkdir -p %{buildroot}%{_datadir}/%{name}/translations +install -m 0644 %{_builddir}/%{name}_it.qm %{buildroot}%{_datadir}/%{name}/translations/ + +%files +%defattr(-,root,root,-) +%{_bindir}/%{name} +%{_datadir}/applications/%{name}.desktop +%{_datadir}/icons/hicolor/86x86/apps/%{name}.png +%{_datadir}/icons/hicolor/108x108/apps/%{name}.png +%{_datadir}/icons/hicolor/128x128/apps/%{name}.png +%{_datadir}/icons/hicolor/172x172/apps/%{name}.png +%{_datadir}/%{name}/qml +%{_datadir}/%{name}/translations/%{name}*.qm diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..2511e04 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "settings.h" + +Q_DECL_EXPORT int main(int argc, char *argv[]) +{ + QGuiApplication *app = SailfishApp::application(argc, argv); + + // Identita' allineata al profilo X-Sailjail del desktop file: + // AppConfigLocation = ~/.config/harbour/henflow (zona persistente). + app->setOrganizationName(QStringLiteral("harbour")); + app->setApplicationName(QStringLiteral("henflow")); + app->setApplicationVersion(QStringLiteral("0.1.0")); + + Settings settings; + + // Lingua scelta dall'utente: "system" segue Sailfish, "en" e' la lingua + // delle stringhe sorgente, "it" carica il catalogo dal datadir dell'app. + QString lang = settings.language(); + if (lang == QLatin1String("system")) { + const QString sys = QLocale::system().name(); + lang = sys.startsWith(QLatin1String("it")) ? QStringLiteral("it") + : QStringLiteral("en"); + } + if (lang == QLatin1String("it")) { + const QList trs = app->findChildren(); + foreach (QTranslator *t, trs) { + app->removeTranslator(t); + t->deleteLater(); + } + QTranslator *translator = new QTranslator(app); + if (translator->load(QStringLiteral("harbour-henflow_it"), + QStringLiteral("/usr/share/harbour-henflow/translations"))) { + app->installTranslator(translator); + } else { + qWarning("harbour-henflow: cannot load harbour-henflow_it.qm"); + } + } + + QQuickView *view = SailfishApp::createView(); + + qDebug() << "harbour-henflow v0.1.0 build" << __DATE__ << __TIME__; + + view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings); + view->rootContext()->setContextProperty(QStringLiteral("appVersion"), + QCoreApplication::applicationVersion()); + + // pathTo: un QUrl relativo si risolve contro la CWD, che dal launcher non e' + // la cartella dell'app -> QML non caricato e prima schermata BIANCA. + view->setSource(SailfishApp::pathTo(QStringLiteral("qml/harbour-henflow.qml"))); + view->show(); + + return app->exec(); +} diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..44cb017 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,37 @@ +#include "settings.h" + +#include +#include + +Settings::Settings(QObject *parent) + : QObject(parent) + , m_settings(configPath(), QSettings::IniFormat) +{ +} + +QString Settings::configPath() +{ + // AppConfigLocation dipende da organizationName/applicationName impostati + // nel main.cpp: ~/.config/harbour/henflow (zona persistente della sandbox). + const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QDir().mkpath(dir); + return dir + QStringLiteral("/henflow.conf"); +} + +QString Settings::language() const +{ + return m_settings.value(QStringLiteral("ui/language"), + QStringLiteral("system")).toString(); +} + +void Settings::setLanguage(const QString &language) +{ + if (language == this->language()) + return; + + m_settings.setValue(QStringLiteral("ui/language"), language); + // sync() esplicito: su Sailfish l'app puo' chiudersi bruscamente e il flush + // del distruttore non gira -> le scritture non sincronizzate vanno perse. + m_settings.sync(); + emit languageChanged(); +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..4c20c74 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,32 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include + +// Wrapper QSettings esposto a QML come context property "appSettings". +// NB: si usa un PATH ESPLICITO su AppConfigLocation, non il costruttore +// QSettings(org, app): con la sandbox SailJail il costruttore da solo non +// persiste le modifiche fatte dal launcher. +class Settings : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) + +public: + explicit Settings(QObject *parent = nullptr); + + QString language() const; + void setLanguage(const QString &language); + +signals: + void languageChanged(); + +private: + static QString configPath(); + + QSettings m_settings; +}; + +#endif // SETTINGS_H diff --git a/translations/harbour-henflow.ts b/translations/harbour-henflow.ts new file mode 100644 index 0000000..8c38603 --- /dev/null +++ b/translations/harbour-henflow.ts @@ -0,0 +1,74 @@ + + + + + CoverPage + + Henflow + + + + Eggs, hens and orders + + + + + MainPage + + Henflow + + + + Hens + + + + name, breed, hatch date, estimated production + + + + Available eggs + + + + eggs ready today + + + + Orders + + + + customer, quantity, automatic delivery date + + + + Calendar + + + + orders and pickups + + + + Customers + + + + regular buyers + + + + Dashboard + + + + summary of the day + + + + Native port in progress + + + + diff --git a/translations/harbour-henflow_it.ts b/translations/harbour-henflow_it.ts new file mode 100644 index 0000000..a120e09 --- /dev/null +++ b/translations/harbour-henflow_it.ts @@ -0,0 +1,74 @@ + + + + + CoverPage + + Henflow + Henflow + + + Eggs, hens and orders + Uova, galline e ordini + + + + MainPage + + Henflow + Henflow + + + Hens + Galline + + + name, breed, hatch date, estimated production + nome, razza, data di nascita, produzione stimata + + + Available eggs + Uova disponibili + + + eggs ready today + uova pronte oggi + + + Orders + Ordini + + + customer, quantity, automatic delivery date + cliente, quantità, data di consegna automatica + + + Calendar + Calendario + + + orders and pickups + ordini e ritiri + + + Customers + Clienti + + + regular buyers + acquirenti abituali + + + Dashboard + Dashboard + + + summary of the day + riepilogo della giornata + + + Native port in progress + Porting nativo in corso + + +