refactor(config): niente configurazione cablata — tutto parametrizzato (v0.3.0)

Prima di pubblicare: eliminati dai sorgenti tutti i valori "cablati".

- settings: default neutri (URL, voce, engine vuoti = "decide il server");
  nuove chiavi ttsVoices/ttsEngines (preset dei selettori) e vadMaxMs
  (durata massima registrazione, prima fissa a 60 s nel codice)
- ApiClient: guard su baseUrl vuoto (checkAuth/login si comportano senza
  rete), voce/engine omessi nella richiesta TTS se vuoti, Content-Type
  audio coerente col formato reale del file (ogg/wav/mp3/m4a/opus)
- SettingsPage: selettori voce/engine costruiti dai preset configurabili
  (Repeater dentro ContextMenu = pattern provato su tide) + campi per
  modificare i preset; LoginPage richiede indirizzo server non vuoto
- VoiceRecorder: tetto di registrazione da configurazione
- README: sezione "Configuration" completa; docs neutralizzate (niente
  URL/valori personali: repo public-friendly)
This commit is contained in:
2026-09-12 12:42:06 +02:00
parent 558a384339
commit a5932f0025
11 changed files with 232 additions and 64 deletions
+83 -38
View File
@@ -4,14 +4,10 @@ import Sailfish.Silica 1.0
Page {
id: page
property var voiceNames: [
"it-IT-ElsaNeural",
"it-IT-DiegoNeural",
"it-IT-IsabellaNeural",
"it-IT-GiuseppeMultilingualNeural",
"en-US-AriaNeural"
]
property var engineNames: ["edge", "openai", "elevenlabs"]
// Nessun elenco cablato: le voci/gli engine dei selettori vengono dalle
// impostazioni (ttsVoices/ttsEngines) e il valore corrente è sempre
// incluso. loading evita di salvare durante il caricamento iniziale.
property bool loading: true
SilicaFlickable {
anchors.fill: parent
@@ -42,51 +38,61 @@ Page {
ComboBox {
label: qsTr("Read-aloud voice")
currentIndex: Math.max(0, page.voiceNames.indexOf(appSettings.ttsVoice))
// MenuItems espliciti (no Repeater: pattern non provato sul kit 5.1)
currentIndex: page.voiceIndex()
// Repeater dentro ContextMenu = pattern provato sul device (tide)
menu: ContextMenu {
MenuItem {
text: "it-IT-ElsaNeural"
onClicked: appSettings.ttsVoice = "it-IT-ElsaNeural"
text: qsTr("(server default)")
onClicked: appSettings.ttsVoice = ""
}
MenuItem {
text: "it-IT-DiegoNeural"
onClicked: appSettings.ttsVoice = "it-IT-DiegoNeural"
}
MenuItem {
text: "it-IT-IsabellaNeural"
onClicked: appSettings.ttsVoice = "it-IT-IsabellaNeural"
}
MenuItem {
text: "it-IT-GiuseppeMultilingualNeural"
onClicked: appSettings.ttsVoice = "it-IT-GiuseppeMultilingualNeural"
}
MenuItem {
text: "en-US-AriaNeural"
onClicked: appSettings.ttsVoice = "en-US-AriaNeural"
Repeater {
model: page.voiceChoices().length
delegate: MenuItem {
text: page.voiceChoices()[index]
onClicked: appSettings.ttsVoice = page.voiceChoices()[index]
}
}
}
}
TextField {
id: voicesField
width: parent.width
label: qsTr("Voice presets (comma-separated)")
onTextChanged: {
if (!page.loading)
appSettings.ttsVoices = text.split(",")
}
}
ComboBox {
label: qsTr("TTS engine")
currentIndex: Math.max(0, page.engineNames.indexOf(appSettings.ttsEngine))
currentIndex: page.engineIndex()
menu: ContextMenu {
MenuItem {
text: "edge"
onClicked: appSettings.ttsEngine = "edge"
text: qsTr("(server default)")
onClicked: appSettings.ttsEngine = ""
}
MenuItem {
text: "openai"
onClicked: appSettings.ttsEngine = "openai"
}
MenuItem {
text: "elevenlabs"
onClicked: appSettings.ttsEngine = "elevenlabs"
Repeater {
model: page.engineChoices().length
delegate: MenuItem {
text: page.engineChoices()[index]
onClicked: appSettings.ttsEngine = page.engineChoices()[index]
}
}
}
}
TextField {
id: enginesField
width: parent.width
label: qsTr("Engine presets (comma-separated)")
onTextChanged: {
if (!page.loading)
appSettings.ttsEngines = text.split(",")
}
}
TextSwitch {
text: qsTr("Read replies aloud")
description: qsTr("Speak every completed reply automatically")
@@ -159,5 +165,44 @@ Page {
pageStack.replace(Qt.resolvedUrl("LoginPage.qml"))
}
Component.onCompleted: urlField.text = appSettings.baseUrl
// Elenchi dei selettori: preset configurati + valore corrente (così resta
// selezionabile anche se non è tra i preset). Indice 0 = "(server default)".
function voiceChoices() {
var out = appSettings.ttsVoices.slice()
var cur = appSettings.ttsVoice
if (cur.length > 0 && out.indexOf(cur) < 0)
out.push(cur)
return out
}
function voiceIndex() {
var cur = appSettings.ttsVoice
if (cur.length === 0)
return 0
var i = page.voiceChoices().indexOf(cur)
return i < 0 ? 0 : i + 1
}
function engineChoices() {
var out = appSettings.ttsEngines.slice()
var cur = appSettings.ttsEngine
if (cur.length > 0 && out.indexOf(cur) < 0)
out.push(cur)
return out
}
function engineIndex() {
var cur = appSettings.ttsEngine
if (cur.length === 0)
return 0
var i = page.engineChoices().indexOf(cur)
return i < 0 ? 0 : i + 1
}
Component.onCompleted: {
urlField.text = appSettings.baseUrl
voicesField.text = appSettings.ttsVoices.join(", ")
enginesField.text = appSettings.ttsEngines.join(", ")
page.loading = false
}
}