harbour-expenditure/qml/pages/SettingsPage.qml
2023-11-13 19:04:58 +01:00

456 lines
20 KiB
QML

import QtQuick 2.6
import Sailfish.Silica 1.0
import Sailfish.Pickers 1.0
import FileIO 1.0
import Nemo.Notifications 1.0
Dialog {
id: pageSettings
property int showMaintenanceButtonsCounter : 0
property string notificationString : ""
//property int oldProjectIndex
allowedOrientations: Orientation.Portrait
backNavigation: (bannerAddProject.opacity < 1) // && (updateEvenWhenCanceled === false)
forwardNavigation: (bannerAddProject.opacity < 1)
onOpened: {
//console.log("old project index = " + activeProjectID_unixtime)
showMaintenanceButtonsCounter = 0
updateEvenWhenCanceled = false
idComboboxProject.currentIndex = 0
for (var j = 0; j < listModel_allProjects.count ; j++) {
if (Number(listModel_allProjects.get(j).project_id_timestamp) === Number(storageItem.getSettings("activeProjectID_unixtime", 0))) {
idComboboxProject.currentIndex = j
}
}
idComboboxSortingExpenses.currentIndex = Number(storageItem.getSettings("sortOrderExpensesIndex", 0)) // 0=descending, 1=ascending
idComboboxExchangeRateMode.currentIndex = Number(storageItem.getSettings("exchangeRateModeIndex", 0)) // 0=collective, 1=individual
idComboboxShowInteractiveScrollbar.currentIndex = Number(storageItem.getSettings("interativeScrollbarMode", 0)) //0=standard, 1=interactive
notificationString = ""
}
onDone: {
if (result == DialogResult.Accepted) {
writeDB_Settings()
}
}
onRejected: {
// in certain cases reload list even on cancel: if project was cleared, delted or created
// then use previous activeProjectID_unixtime instead of the new one from dropdown menu
if (updateEvenWhenCanceled === true) {
loadActiveProjectInfos_FromDB(activeProjectID_unixtime)
updateEvenWhenCanceled = false
}
}
BannerAddProject {
id: bannerAddProject
}
Banner2ButtonsChoice {
id: banner2ButtonsChoice
}
Notification {
id: notificationBackup
expireTimeout: 4000
//appName: qsTr("Expenditure")
//icon: "image://theme/icon-lock-warning"
function showSmall(message) {
replacesId = 0
previewSummary = ""
previewBody = message
publish()
}
function showBig(title, message) {
replacesId = 0
previewSummary = title
previewBody = message
publish()
}
}
RemorsePopup {
z: 10
id: remorse_clearExchangeRatesDB
}
RemorsePopup {
z: 10
id: remorse_restoreBackupFile
}
ListModel {
id: listModel_tempProjectExpenses
}
Component {
id: idFolderPickerPage
FolderPickerPage {
dialogTitle: qsTr("Backup to")
onSelectedPathChanged: {
backupProjectExpenses( selectedPath )
}
}
}
Component {
id: idFilePickerPage
FilePickerPage {
title: qsTr("Restore backup file")
nameFilters: [ '*.csv' ]
onSelectedContentPropertiesChanged: {
var selectedPath = selectedContentProperties.filePath
idTextFileBackup.source = selectedPath
var tempProjectIndex_File = ((idTextFileBackup.text).split("\n"))[0]
var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
var headlineText = qsTr("Restore backup - choose action")
var otherText = qsTr("Replace deletes all former project-expenses and uses those from backup-file instead.") + " "
+ qsTr("Merge keeps former project-expenses and adds those from backup-file which are not yet on the list.")
if (parseInt(tempProjectIndex) !== parseInt(tempProjectIndex_File)) {
var detailText = qsTr("File info: This backup was created by a different project.")
} else {
detailText = qsTr("File info: This backup was created by the original project.")
}
var choiceText_1 = qsTr("Replace")
var choiceText_2 = qsTr("Merge")
banner2ButtonsChoice.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, headlineText, detailText, otherText, choiceText_1, choiceText_2, selectedPath )
}
}
}
TextFileIO {
id: idTextFileBackup
}
SilicaFlickable{
anchors.fill: parent
contentHeight: column.height // tell overall height
Column {
id: column
width: pageSettings.width
DialogHeader {
//title: qsTr("Settings")
}
Row {
width: parent.width
bottomPadding: Theme.paddingLarge
Label {
id: idLabelSettingsHeader
width: parent.width / 6 * 5
leftPadding: Theme.paddingLarge
font.pixelSize: Theme.fontSizeExtraLarge
color: Theme.highlightColor
text: qsTr("Settings")
MouseArea {
anchors.fill: parent
onClicked: showMaintenanceButtonsCounter = showMaintenanceButtonsCounter + 1
}
}
IconButton {
width: parent.width / 6
anchors.verticalCenter: idLabelSettingsHeader.verticalCenter
icon.color: Theme.highlightColor
icon.scale: 1.1
icon.source: "image://theme/icon-m-about?"
onClicked: {
pageStack.animatorPush(Qt.resolvedUrl("AboutPage.qml"), {})
}
}
}
Row {
width: parent.width
ComboBox {
id: idComboboxProject
width: (listModel_allProjects.count === 0) ? (parent.width / 6*5) : (parent.width / 6*4)
label: qsTr("Project")
menu: ContextMenu {
Repeater {
enabled: listModel_allProjects.count > 0
model: listModel_allProjects
MenuItem {
text: project_name
}
}
}
MouseArea {
enabled: listModel_allProjects.count === 0
anchors.fill: parent
preventStealing: true
onClicked: bannerAddProject.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "new", idComboboxProject.currentIndex )
}
}
IconButton {
visible: listModel_allProjects.count > 0
width: parent.width / 6
icon.source: "image://theme/icon-m-edit?"
onClicked: {
bannerAddProject.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "edit", idComboboxProject.currentIndex )
}
}
IconButton {
width: parent.width / 6
icon.source: "image://theme/icon-m-add?"
onClicked: {
bannerAddProject.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "new", idComboboxProject.currentIndex )
}
}
}
ComboBox {
id: idComboboxSortingExpenses
width: parent.width
label: qsTr("Sorting")
menu: ContextMenu {
MenuItem {
text: qsTr("descending")
}
MenuItem {
text: qsTr("ascending")
}
}
}
ComboBox {
id: idComboboxExchangeRateMode
width: parent.width
label: qsTr("Exchange rate")
menu: ContextMenu {
MenuItem {
text: qsTr("per currency (constant)")
}
MenuItem {
text: qsTr("per transaction (dates)")
}
}
}
ComboBox {
id: idComboboxShowInteractiveScrollbar
width: parent.width
label: qsTr("Scrollbar")
menu: ContextMenu {
MenuItem {
text: qsTr("normal")
}
MenuItem {
text: qsTr("interactive (beta)")
}
}
}
Column {
visible: showMaintenanceButtonsCounter > 9
width: parent.width
topPadding: Theme.paddingLarge * 3
spacing: Theme.paddingLarge
Label {
width: parent.width
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
color: Theme.errorColor
text: qsTr("Database cleanup - requires restart:")
leftPadding: Theme.paddingLarge + Theme.paddingSmall
rightPadding: leftPadding
}
Button {
text: qsTr("settings")
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
remorse_clearExchangeRatesDB.execute(qsTr("Delete stored settings?"), function() {
storageItem.removeFullTable( "settings_table" )
})
}
}
Button {
text: qsTr("exchange rates")
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
remorse_clearExchangeRatesDB.execute(qsTr("Delete stored exchange rates?"), function() {
storageItem.removeFullTable( "exchange_rates_table" )
})
}
}
Button {
text: qsTr("projects")
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
remorse_clearExchangeRatesDB.execute(qsTr("Delete stored projects?"), function() {
// remove all individual project expense tables
for (var j = 0; j < listModel_allProjects.count ; j++) {
var tempTablename = "table_" + listModel_allProjects.get(j).project_id_timestamp
storageItem.removeFullTable(tempTablename)
}
// remove all projects overview table
storageItem.removeFullTable( "projects_table" )
// set latest setting_id to zero
activeProjectID_unixtime = 0
storageItem.setSettings("activeProjectID_unixtime", activeProjectID_unixtime)
// update front page
updateEvenWhenCanceled = true
})
}
}
}
Item {
width: parent.width
height: Theme.paddingLarge
}
}
}
// ******************************************** important functions ******************************************** //
function writeDB_Settings() {
storageItem.setSettings("sortOrderExpensesIndex", idComboboxSortingExpenses.currentIndex)
sortOrderExpenses = idComboboxSortingExpenses.currentIndex
listModel_activeProjectExpenses.quick_sort()
storageItem.setSettings("exchangeRateModeIndex", idComboboxExchangeRateMode.currentIndex)
exchangeRateMode = idComboboxExchangeRateMode.currentIndex
storageItem.setSettings("interativeScrollbarMode", idComboboxShowInteractiveScrollbar.currentIndex)
interativeScrollbarMode = idComboboxShowInteractiveScrollbar.currentIndex
if (listModel_allProjects.count > 0) { // only works if a project is actually created and loaded, otherwise this gets triggered directly in BannerAddProject.qml
storageItem.setSettings("activeProjectID_unixtime", Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp))
activeProjectID_unixtime = Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp)
loadActiveProjectInfos_FromDB(Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp))
}
// ToDo: update base currency, if it was changed
}
function backupProjectExpenses(selectedPath) {
listModel_tempProjectExpenses.clear()
var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
var backupFileName = encodeURIComponent(listModel_allProjects.get(idComboboxProject.currentIndex).project_name) + "_backup.csv" //replaces misleading special characters with %-symbols
var backupFilePath = selectedPath + "/" + backupFileName //StandardPaths.documents
// check if project exists
var currentProjectEntries = storageItem.getAllExpenses(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp, "none")
if (currentProjectEntries !== "none") {
// generate temp project expenses list from chosen list
for (var i = 0; i < currentProjectEntries.length ; i++) {
listModel_tempProjectExpenses.append({
id_unixtime_created : Number(currentProjectEntries[i][0]).toFixed(0),
date_time : Number(currentProjectEntries[i][1]).toFixed(0),
expense_name : currentProjectEntries[i][2],
expense_sum : Number(currentProjectEntries[i][3]).toFixed(2),
expense_currency : currentProjectEntries[i][4],
expense_info : currentProjectEntries[i][5],
expense_payer : currentProjectEntries[i][6],
expense_members : currentProjectEntries[i][7],
})
}
// create string from these temp info
var toBackupString = tempProjectIndex + "\n"
+ listModel_allProjects.get(idComboboxProject.currentIndex).project_name + "\n"
+ "id_unixtime_created;*;date_time;*;expense_payer;*;expense_name;*;expense_sum;*;expense_currency;*;expense_members;*;expense_info" + "\n"
for (var j = 0; j < listModel_tempProjectExpenses.count; j++) {
toBackupString += listModel_tempProjectExpenses.get(j).id_unixtime_created
+ ";*;" + listModel_tempProjectExpenses.get(j).date_time
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_payer
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_name
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_sum
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_currency
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_members
+ ";*;" + listModel_tempProjectExpenses.get(j).expense_info
+ "\n"
}
//console.log(toBackupString)
// store in file and give notification
idTextFileBackup.source = backupFilePath
idTextFileBackup.text = toBackupString
notificationString = backupFilePath
//show notification somehow on top
var headlineText = qsTr("Backup successful")
var detailText = qsTr("File saved to:") + backupFilePath
var triggerHiding = false
notificationBackup.showBig(headlineText, detailText)
}
}
function restoreProjectExpenses(selectedPath, selectedAction) {
idTextFileBackup.source = selectedPath
var loadTextString = (idTextFileBackup.text)
var pos = loadTextString.lastIndexOf("\n") // ToDo: remove last occurance of "\n"
var loadTextLinesArray = (loadTextString.substring(0,pos) + loadTextString.substring(pos+1)).split("\n")
var tempStringExistingId_unixtime = ""
//var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
// plausibility check on lines 0 to 2
if (loadTextLinesArray[2] === "id_unixtime_created;*;date_time;*;expense_payer;*;expense_name;*;expense_sum;*;expense_currency;*;expense_members;*;expense_info") {
var tempProjectIndex = listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp
// if REPLACE: delete old entries in project-specific expense table in database, otherwise just MERGE
if (selectedAction === "replace") {
storageItem.deleteAllExpenses(tempProjectIndex)
} else { //"merge"
// generate expenses list for this project
var currentProjectEntries = storageItem.getAllExpenses( activeProjectID_unixtime, "none")
if (currentProjectEntries !== "none") {
for (var i = 0; i < currentProjectEntries.length ; i++) {
tempStringExistingId_unixtime += (currentProjectEntries[i][0]).toString() + ";"
}
}
//console.log(tempStringExistingId_unixtime)
}
// fill with backup entries
for (i = 3; i < loadTextLinesArray.length; i++) {
var tempExpenseLineArray = (loadTextLinesArray[i]).split(";*;")
var project_name_table = tempProjectIndex
var id_unixtime_created = tempExpenseLineArray[0]
var date_time = tempExpenseLineArray[1]
var expense_payer = tempExpenseLineArray[2]
var expense_name = tempExpenseLineArray[3]
var expense_sum = tempExpenseLineArray[4]
var expense_currency = tempExpenseLineArray[5]
var expense_members = tempExpenseLineArray[6]
var expense_info = tempExpenseLineArray[7]
if (selectedAction === "replace") {
storageItem.setExpense(project_name_table, id_unixtime_created.toString(), date_time.toString(), expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members)
//console.log("entering DB -> " + tempExpenseLineArray)
} else {
// check for double entries, only add if not existent
if (tempStringExistingId_unixtime.indexOf(id_unixtime_created.toString()) === -1) {
storageItem.setExpense(project_name_table, id_unixtime_created.toString(), date_time.toString(), expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members)
//console.log("entering DB -> " + tempExpenseLineArray)
}
}
}
//show notification somehow on top
var headlineText = qsTr("Backup successfully restored.")
if (selectedAction === "replace") {
var detailText = qsTr("Project expenses have been overwritten by backup-file expenses.")
} else {
detailText = qsTr("Project expenses have been merged with backup-file expenses.")
}
var triggerHiding = false
notificationBackup.showBig(headlineText, detailText)
// set this flag when the current project gets merged or replaced with a backup
if ( Number(tempProjectIndex) === Number(activeProjectID_unixtime) ) {
updateEvenWhenCanceled = true
}
} else {
//show notification somehow on top
headlineText = qsTr("Validity check failed.")
detailText = qsTr("This backup file does not seem to be created by Expenditure:") + " " + backupFilePath
triggerHiding = false
notificationBackup.showBig(headlineText, detailText)
}
}
}