291 lines
11 KiB
QML
291 lines
11 KiB
QML
import QtQuick 2.0
|
|
import Sailfish.Silica 1.0
|
|
import "../js/tides.js" as Tides
|
|
import "../js/comuni.js" as Comuni
|
|
|
|
Page {
|
|
id: page
|
|
|
|
// Messaggio mostrato nel footer quando la lista è vuota
|
|
property string hintMsg: ""
|
|
|
|
SilicaListView {
|
|
id: list
|
|
anchors.fill: parent
|
|
model: ListModel { id: resultModel }
|
|
|
|
PullDownMenu {
|
|
MenuItem {
|
|
text: qsTr("Settings")
|
|
onClicked: page.openSettings()
|
|
}
|
|
}
|
|
|
|
// Header della lista: titolo + ricerca (i campi qui scrollano con la
|
|
// lista; le funzioni della pagina ricevono il testo come parametro,
|
|
// perché gli id dell'header non sono visibili fuori dal componente)
|
|
header: Column {
|
|
width: parent.width
|
|
|
|
PageHeader {
|
|
title: "Tide"
|
|
description: "v" + appVersion
|
|
}
|
|
|
|
SearchField {
|
|
placeholderText: qsTr("Search a port or a town…")
|
|
onTextChanged: page.updateList(text)
|
|
EnterKey.enabled: text.trim().length > 0
|
|
EnterKey.onClicked: page.findOnline(text)
|
|
}
|
|
}
|
|
|
|
footer: Item {
|
|
width: parent.width
|
|
height: resultModel.count === 0 ? Theme.itemSizeSmall * 1.8 : 0
|
|
Label {
|
|
visible: resultModel.count === 0
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.horizontalPageMargin
|
|
text: page.hintMsg
|
|
color: Theme.secondaryColor
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
|
|
delegate: ListItem {
|
|
contentHeight: Theme.itemSizeMedium
|
|
highlighted: model.isPreferred
|
|
|
|
// Due righe manuali (ListItemLayout non esiste su questo kit Qt 5.6)
|
|
Column {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.horizontalPageMargin
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.horizontalPageMargin + 80
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
Label {
|
|
text: model.name
|
|
color: model.isPreferred ? Theme.highlightColor : Theme.primaryColor
|
|
width: parent.width
|
|
truncationMode: TruncationMode.Fade
|
|
}
|
|
Label {
|
|
text: model.country
|
|
color: Theme.secondaryColor
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
width: parent.width
|
|
truncationMode: TruncationMode.Fade
|
|
}
|
|
}
|
|
Label {
|
|
text: model.range
|
|
color: Theme.secondaryColor
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.horizontalPageMargin
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
onClicked: {
|
|
console.log("[tide] open result:", model.name, "kind:", model.kind)
|
|
page.openResult(model.kind, model.portIndex, model.name, model.lat, model.lon)
|
|
}
|
|
}
|
|
|
|
VerticalScrollDecorator {}
|
|
}
|
|
|
|
// --- Ricerca live (porti noti + tutti i comuni italiani) ---
|
|
function norm(s) {
|
|
// minuscolo + apostrofi tipografici -> dritti + accenti rimossi
|
|
// ("citta" trova "Città", "sant'angelo" trova "Sant'Angelo")
|
|
return s.toLowerCase()
|
|
.replace(/[\u2018\u2019]/g, "'")
|
|
.replace(/[àáâãä]/g, "a")
|
|
.replace(/[èéêë]/g, "e")
|
|
.replace(/[ìíîï]/g, "i")
|
|
.replace(/[òóôõö]/g, "o")
|
|
.replace(/[ùúûü]/g, "u")
|
|
}
|
|
|
|
function rangeOf(cost) {
|
|
return "±" + (cost[1][0] + cost[2][0]).toFixed(2) + " m"
|
|
}
|
|
|
|
function updateList(q) {
|
|
q = norm(q.trim())
|
|
resultModel.clear()
|
|
if (q === "") {
|
|
hintMsg = qsTr("Type to search among all Italian towns and known ports.")
|
|
return
|
|
}
|
|
hintMsg = qsTr("No local results — press Enter to search online.")
|
|
var i, p, name
|
|
// porti noti (match su nome o paese)
|
|
for (i = 0; i < Tides.PORTS.length; i++) {
|
|
p = Tides.PORTS[i]
|
|
if (norm(p[0] + " " + p[1]).indexOf(q) !== -1) {
|
|
resultModel.append({
|
|
name: p[0], country: p[1],
|
|
range: rangeOf(p[4]),
|
|
kind: "port", portIndex: i, lat: 0, lon: 0,
|
|
isPreferred: (appSettings.preferredPort === p[0])
|
|
})
|
|
}
|
|
}
|
|
// comuni italiani (max 25 risultati)
|
|
var n = 0
|
|
for (i = 0; i < Comuni.COMUNI.length && n < 25; i++) {
|
|
name = Comuni.COMUNI[i][0]
|
|
if (norm(name).indexOf(q) !== -1) {
|
|
var lat = Comuni.COMUNI[i][1]
|
|
var lon = Comuni.COMUNI[i][2]
|
|
resultModel.append({
|
|
name: name, country: qsTr("Italy"),
|
|
range: rangeOf(Tides.nearestPort(lat, lon).port[4]),
|
|
kind: "comune", portIndex: -1, lat: lat, lon: lon,
|
|
isPreferred: false
|
|
})
|
|
n++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Navigazione centralizzata: pageStack si risolve solo nel contesto del
|
|
// Page (nei delegate della lista non è visibile su questo kit).
|
|
function openDetail(port, portIndex, custom) {
|
|
console.log("[tide] openDetail:", port ? port[0] : "undefined", "custom:", custom)
|
|
pageStack.push(Qt.resolvedUrl("DetailPage.qml"),
|
|
{ port: port, portIndex: portIndex, custom: custom })
|
|
}
|
|
|
|
function openResult(kind, portIndex, name, lat, lon) {
|
|
if (kind === "port") {
|
|
page.openDetail(Tides.PORTS[portIndex], portIndex, false)
|
|
return
|
|
}
|
|
// comune italiano: costanti dal porto noto entro 222 km, altrimenti
|
|
// stima sintetica da lat/lon (etichettata nella pagina di dettaglio)
|
|
var np = Tides.nearestPort(lat, lon)
|
|
var cost, flag
|
|
if (np.km < 222) {
|
|
cost = np.port[4].slice()
|
|
flag = "near:" + np.port[0] + ":" + (np.km / 111).toFixed(1)
|
|
} else {
|
|
cost = Tides.synthConst(lat, lon)
|
|
flag = "synth"
|
|
}
|
|
page.openDetail([name, qsTr("Italy"), lat, lon, cost, flag], -1, true)
|
|
}
|
|
|
|
function openSettings() {
|
|
console.log("[tide] openSettings")
|
|
pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
|
|
}
|
|
|
|
// --- Ricerca online (geocoding Photon) per città estere / non trovate ---
|
|
function geoFound(name, lat, lon, src) {
|
|
console.log("[tide] geoFound:", name, lat, lon, src)
|
|
if (!name) return
|
|
var np = Tides.nearestPort(lat, lon)
|
|
var cost, flag
|
|
if (np.km < 222) {
|
|
cost = np.port[4].slice()
|
|
flag = "near:" + np.port[0] + ":" + (np.km / 111).toFixed(1)
|
|
} else {
|
|
cost = Tides.synthConst(lat, lon)
|
|
flag = "synth"
|
|
}
|
|
page.openDetail([name, "", lat, lon, cost, flag], -1, true)
|
|
}
|
|
|
|
function geoXhr(query, cb) {
|
|
// Photon (komoot) invece di Nominatim: accetta qualsiasi User-Agent
|
|
// (Nominatim risponde 403 all'UA generico inviato dalle XHR QML di Qt)
|
|
// e supporta il filtro osm_tag=place:city per le città vere.
|
|
console.log("[tide] geoXhr:", query)
|
|
var xhr = new XMLHttpRequest()
|
|
xhr.open("GET", "https://photon.komoot.io/api/?limit=1&osm_tag=place:city&q="
|
|
+ encodeURIComponent(query), true)
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4) {
|
|
console.log("[tide] geoXhr status:", xhr.status)
|
|
if (xhr.status === 200) {
|
|
try {
|
|
var j = JSON.parse(xhr.responseText)
|
|
if (j && j.features && j.features.length > 0) {
|
|
var f = j.features[0]
|
|
cb(f.properties.name || query,
|
|
f.geometry.coordinates[1],
|
|
f.geometry.coordinates[0])
|
|
} else {
|
|
cb(null)
|
|
}
|
|
} catch (e) { cb(null) }
|
|
} else {
|
|
cb(null)
|
|
}
|
|
}
|
|
}
|
|
xhr.send()
|
|
}
|
|
|
|
function findOnline(q) {
|
|
q = q.trim()
|
|
console.log("[tide] findOnline:", q)
|
|
if (q === "") return
|
|
// se è un porto noto, apri direttamente
|
|
var ql = norm(q)
|
|
for (var i = 0; i < Tides.PORTS.length; i++) {
|
|
if (norm(Tides.PORTS[i][0]) === ql) {
|
|
page.openDetail(Tides.PORTS[i], i, false)
|
|
return
|
|
}
|
|
}
|
|
// se è un comune italiano esatto, apri direttamente
|
|
for (i = 0; i < Comuni.COMUNI.length; i++) {
|
|
if (norm(Comuni.COMUNI[i][0]) === ql) {
|
|
page.openResult("comune", -1, Comuni.COMUNI[i][0],
|
|
Comuni.COMUNI[i][1], Comuni.COMUNI[i][2])
|
|
return
|
|
}
|
|
}
|
|
geoXhr(q, function(name, lat, lon) {
|
|
if (!name) {
|
|
console.log("[tide] online: non trovato")
|
|
return
|
|
}
|
|
geoFound(name, lat, lon, "OpenStreetMap")
|
|
})
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
updateList("")
|
|
var pref = appSettings.preferredPort
|
|
if (pref !== "") {
|
|
var found = false
|
|
for (var i = 0; i < Tides.PORTS.length; i++) {
|
|
if (Tides.PORTS[i][0] === pref) {
|
|
page.openDetail(Tides.PORTS[i], i, false)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
// preferito custom (comune): ricostruisci e apri il dettaglio
|
|
if (!found && appSettings.preferredLat >= 0) {
|
|
var lat = appSettings.preferredLat
|
|
var lon = appSettings.preferredLon
|
|
var np = Tides.nearestPort(lat, lon)
|
|
var cost = np.km < 222 ? np.port[4].slice()
|
|
: Tides.synthConst(lat, lon)
|
|
page.openDetail([pref, qsTr("Italy"), lat, lon, cost,
|
|
np.km < 222 ? "near:" + np.port[0] : "synth"],
|
|
-1, true)
|
|
}
|
|
}
|
|
}
|
|
}
|