Initial commit

This commit is contained in:
yajo10 2023-11-13 19:04:58 +01:00
commit 4721e6c034
28 changed files with 5945 additions and 0 deletions

View file

@ -0,0 +1,20 @@
[Desktop Entry]
Type=Application
X-Nemo-Application-Type=silica-qt5
Icon=harbour-expenditure
Exec=harbour-expenditure
Name=Expenditure
# translation example:
# your app name in German locale (de)
#
# Remember to comment out the following line, if you do not want to use
# a different app name in German locale (de).
#Name[de]=Expenditure
[X-Sailjail]
# Replace with your organization as a reverse domain name
OrganizationName=org.tplabs
# ApplicationName does not have to be identical to Name
ApplicationName=expenditure
# Add the required permissions here
Permissions=PublicDir;UserDirs;RemovableMedia

48
harbour-expenditure.pro Normal file
View file

@ -0,0 +1,48 @@
# NOTICE:
#
# Application name defined in TARGET has a corresponding QML filename.
# If name defined in TARGET is changed, the following needs to be done
# to match new name:
# - corresponding QML filename must be changed
# - desktop icon filename must be changed
# - desktop filename must be changed
# - icon definition filename in desktop file must be changed
# - translation filenames have to be changed
# The name of your application
TARGET = harbour-expenditure
CONFIG += sailfishapp
SOURCES += src/harbour-expenditure.cpp \
src/File.cpp
DISTFILES += qml/harbour-expenditure.qml \
qml/cover/CoverPage.qml \
qml/pages/AboutPage.qml \
qml/pages/Banner2ButtonsChoice.qml \
qml/pages/BannerAddProject.qml \
qml/pages/CalcPage.qml \
qml/pages/FirstPage.qml \
qml/pages/SettingsPage.qml \
rpm/harbour-expenditure.changes.in \
rpm/harbour-expenditure.changes.run.in \
rpm/harbour-expenditure.spec \
rpm/harbour-expenditure.yaml \
translations/*.ts \
harbour-expenditure.desktop
SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172
# to disable building translations every time, comment out the
# following CONFIG line
CONFIG += sailfishapp_i18n
# German translation is enabled as an example. If you aren't
# planning to localize your app, remember to comment out the
# following TRANSLATIONS line. And also do not forget to
# modify the localized app name in the the .desktop file.
TRANSLATIONS += translations/harbour-expenditure-de.ts
HEADERS += \
src/File.h

1117
harbour-expenditure.pro.user Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

34
qml/cover/CoverPage.qml Normal file
View file

@ -0,0 +1,34 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
CoverBackground {
/*
Label {
id: label
anchors.centerIn: parent
text: qsTr("Expenditure")
}
*/
Image {
id: name
anchors.centerIn: parent
source: "harbour-expenditure.png"
width: Theme.iconSizeLarge
height: Theme.iconSizeLarge
sourceSize.width: width
sourceSize.height: height
fillMode: Image.PreserveAspectFit
}
/*
CoverActionList {
id: coverAction
CoverAction {
iconSource: "image://theme/icon-cover-next"
}
CoverAction {
iconSource: "image://theme/icon-cover-pause"
}
}
*/
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

277
qml/cover/harbour-expenditure.svg Executable file
View file

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 17.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
x="0px"
y="0px"
width="86px"
height="86px"
viewBox="0 0 86 86"
enable-background="new 0 0 86 86"
xml:space="preserve"
id="svg25"
sodipodi:docname="harbour-expenditure.svg"
inkscape:version="1.2 (1:1.2+202206011327+fc4e4096c5)"
inkscape:export-filename="harbour-expenditure.png"
inkscape:export-xdpi="295.81396"
inkscape:export-ydpi="295.81396"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs29"><linearGradient
id="linearGradient6854"
gradientUnits="userSpaceOnUse"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937"
gradientTransform="translate(-0.29999871,-0.29999764)">
<stop
offset="0.0054"
style="stop-color:#336f00;stop-opacity:1;"
id="stop6850" />
<stop
offset="1"
style="stop-color:#1cae40;stop-opacity:1;"
id="stop6852" />
</linearGradient>
<linearGradient
id="linearGradient360"
gradientUnits="userSpaceOnUse"
x1="12.8064"
y1="12.8066"
x2="73.193497"
y2="73.193703"
gradientTransform="translate(-0.29999871,-0.29999764)">
<stop
offset="0.0054"
style="stop-color:#483737;stop-opacity:1"
id="stop3-4" />
<stop
offset="1"
style="stop-color:#6c5353;stop-opacity:1"
id="stop5-9" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#SVGID_1_"
id="linearGradient1977"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-8.4350098e-6)"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937" /><linearGradient
inkscape:collect="always"
xlink:href="#SVGID_1_"
id="linearGradient5185"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-61.2709,-105.56315)"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937" /><linearGradient
inkscape:collect="always"
xlink:href="#SVGID_1_"
id="linearGradient5876"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-122.40401,-2.5764892)"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937" /><linearGradient
inkscape:collect="always"
xlink:href="#SVGID_1_"
id="linearGradient6600"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(119.58762,-0.27337463)"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6854"
id="linearGradient6651"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(235.36607,-0.27268137)"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937" /></defs><sodipodi:namedview
id="namedview27"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:zoom="4.0000002"
inkscape:cx="58.249997"
inkscape:cy="62.624997"
inkscape:window-width="1920"
inkscape:window-height="1049"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1"
inkscape:snap-page="true"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<g
id="Layer_2"
display="none">
</g>
<g
id="Layer_1">
<linearGradient
id="SVGID_1_"
gradientUnits="userSpaceOnUse"
x1="12.8064"
y1="12.8066"
x2="73.1935"
y2="73.1937"
gradientTransform="translate(-0.29999871,-0.29999764)">
<stop
offset="0.0054"
style="stop-color:#483737;stop-opacity:1"
id="stop3" />
<stop
offset="1"
style="stop-color:#6c5353;stop-opacity:1"
id="stop5" />
</linearGradient>
<circle
fill="url(#SVGID_1_)"
cx="43"
cy="43"
id="circle8-2-1"
r="42.700001"
style="fill:url(#linearGradient1977);stroke:none"
inkscape:export-filename="harbour-expenditure.png"
inkscape:export-xdpi="297.89001"
inkscape:export-ydpi="297.89001" /><circle
fill="url(#SVGID_1_)"
cx="-18.270916"
cy="-62.563152"
id="circle8-2-3"
r="42.700001"
style="fill:url(#linearGradient5185)"
inkscape:export-filename="/home/tobias/Dokumente/Sailfish/harbour-expenditure/icons/108x108/harbour-expenditure.png"
inkscape:export-xdpi="121.40515"
inkscape:export-ydpi="121.40515" /><g
id="g1596-6"
transform="translate(-109.6625)"><path
id="path4805-0-6-0-5"
style="font-variation-settings:normal;fill:#ff7f2a;fill-opacity:1;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 59.438474,-60.537276 a 32.075813,32.075813 0 0 0 15.88477,25.753904 32.075813,32.075813 0 0 0 32.076176,0 32.075813,32.075813 0 0 0 15.88476,-25.753904 z"
inkscape:export-filename="/home/tobias/Dokumente/Sailfish/harbour-expenditure/icons/108x108/harbour-expenditure.png"
inkscape:export-xdpi="121.40515"
inkscape:export-ydpi="121.40515" /><path
id="path4805-2-4-1-6-63"
style="font-variation-settings:normal;fill:#00a1a9;fill-opacity:1;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 91.827145,-94.636885 c -4.286601,-0.06328 -8.620161,0.732953 -12.740231,2.439453 -10.80486,5.056629 -19.112,15.691881 -19.64844,27.609375 H 90.522465 L 112.56153,-86.62712 c -5.82161,-5.131559 -13.203344,-7.898584 -20.734385,-8.009765 z"
inkscape:export-filename="/home/tobias/Dokumente/Sailfish/harbour-expenditure/icons/108x108/harbour-expenditure.png"
inkscape:export-xdpi="121.40515"
inkscape:export-ydpi="121.40515" /><path
id="path4805-2-6-8-5-8-9"
style="font-variation-settings:normal;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 115.42676,-83.765792 -19.177735,19.177735 h 27.095705 a 32.075813,32.075813 0 0 0 -7.91797,-19.177735 z"
inkscape:export-filename="/home/tobias/Dokumente/Sailfish/harbour-expenditure/icons/108x108/harbour-expenditure.png"
inkscape:export-xdpi="121.40515"
inkscape:export-ydpi="121.40515" /></g><circle
fill="url(#SVGID_1_)"
cx="-79.403999"
cy="40.423508"
id="circle8-2-1-8"
r="42.700001"
style="fill:url(#linearGradient5876)"
inkscape:export-filename="harbour-expenditure.png"
inkscape:export-xdpi="297.89001"
inkscape:export-ydpi="297.89001" /><path
id="path4805-0-5-0-8-2-0-8"
style="font-variation-settings:normal;vector-effect:none;fill:#f18500;fill-opacity:1;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
d="m -111.48017,40.424196 a 32.075813,32.075813 0 0 0 16.03711,27.77735 32.075813,32.075813 0 0 0 32.07617,0 32.075813,32.075813 0 0 0 16.03906,-27.77735 h -23.03125 c 0.0565,0.0484 0.11882,0.0932 0.17383,0.14258 1.875,1.68403 2.8125,3.8635 2.8125,6.53711 0,2.67361 -1.01562,4.90322 -3.04688,6.69141 -2.01388,1.78819 -4.65213,2.76172 -7.91601,2.91797 l -0.0274,7.89062 h -2.60351 l -0.0254,-7.83984 c -1.77083,-0.0868 -3.53365,-0.32921 -5.28711,-0.72852 -1.73611,-0.3993 -3.45487,-0.95421 -5.15625,-1.66601 v -4.6875 c 1.73611,1.07638 3.48091,1.89974 5.23437,2.47265 1.77084,0.57292 3.51563,0.87739 5.23438,0.91211 v -11.92773 c -1.15561,-0.17912 -2.20665,-0.42034 -3.17188,-0.71485 z m 33.11718,1.23633 v 11.30274 c 1.90972,-0.0521 3.40409,-0.58291 4.48047,-1.58985 1.0764,-1.00694 1.61328,-2.37717 1.61328,-4.11328 0,-1.61458 -0.4848,-2.86458 -1.45703,-3.75 -0.97222,-0.90278 -2.51866,-1.51975 -4.63672,-1.84961 z" /><path
id="path4805-2-4-7-6-7-3-6-5"
style="font-variation-settings:normal;vector-effect:none;fill:#00a1a9;fill-opacity:1;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
d="m -91.6794,10.789435 a 32.075813,32.075813 0 0 0 -19.80078,29.634761 h 27.33984 c -1.93468,-0.59036 -3.49625,-1.40699 -4.66602,-2.46093 -1.75346,-1.57986 -2.6289,-3.67123 -2.6289,-6.275394 0,-2.72569 0.91146,-4.897138 2.73437,-6.511719 1.84028,-1.631942 4.41841,-2.569444 7.73438,-2.8125 v -6.11914 h 2.60351 l 0.0254,6.11914 c 1.37152,0.08681 2.76172,0.261069 4.16796,0.521485 1.40625,0.260416 2.83855,0.616971 4.29688,1.068359 v 4.503906 c -1.47569,-0.746526 -2.91797,-1.319445 -4.32422,-1.71875 -1.38889,-0.416666 -2.77713,-0.659071 -4.16602,-0.728515 v 11.224608 c 0.63124,0.095 1.22646,0.21306 1.80469,0.34375 L -56.72432,17.74256 A 32.075813,32.075813 0 0 0 -91.6794,10.789435 Z m 10.71289,15.167968 c -1.80555,0.06945 -3.22179,0.59028 -4.24609,1.5625 -1.00695,0.972222 -1.50977,2.274309 -1.50977,3.90625 0,1.493054 0.46875,2.674913 1.40625,3.542973 0.95486,0.86805 2.40516,1.449 4.34961,1.74414 z" /><path
id="path4805-2-6-8-1-8-0-7-3-0"
style="font-variation-settings:normal;vector-effect:none;fill:#75ae2c;fill-opacity:1;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000"
d="m -56.72431,17.742555 -19.83426,19.835671 c 2.60112,0.58792 4.66872,1.53545 6.19727,2.8457 h 23.0332 a 32.075813,32.075813 0 0 0 -9.39648,-22.68164 z" /><g
aria-label="$ "
id="text427-0-6-7-5-2-9"
style="font-size:53.3333px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Normal';fill:#6c5d53"
transform="translate(-312.29297,107.58858)" /><g
aria-label="$ "
id="text427-0-6-7-5-2-9-4"
style="font-size:53.3333px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Normal';fill:#6c5d53"
transform="translate(-365.15599,103.03036)" /><g
aria-label="$ "
id="text427-0-6-7-5-2-9-3"
style="font-size:53.3333px;line-height:1.25;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro, Normal';fill:#6c5d53"
transform="translate(-383.55449,91.55018)" /><circle
fill="url(#SVGID_1_)"
cx="162.58763"
cy="42.726624"
id="circle8-2-1-8-3"
r="42.700001"
style="fill:url(#linearGradient6600)"
inkscape:export-filename="harbour-expenditure.png"
inkscape:export-xdpi="297.89001"
inkscape:export-ydpi="297.89001" /><g
id="g7584"><path
id="path4805-0-5-0-8-2-0-8-1"
style="font-variation-settings:normal;fill:#ffffff;fill-opacity:0.603438;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 130.51146,42.727315 a 32.075813,32.075813 0 0 0 16.03711,27.77735 32.075813,32.075813 0 0 0 32.07617,0 32.075813,32.075813 0 0 0 16.03906,-27.77735 h -23.03125 c 0.0565,0.0484 0.11882,0.0932 0.17383,0.14258 1.875,1.68403 2.8125,3.8635 2.8125,6.53711 0,2.67361 -1.01562,4.90322 -3.04688,6.69141 -2.01388,1.78819 -4.65213,2.76172 -7.91601,2.91797 l -0.0274,7.89062 h -2.60351 l -0.0254,-7.83984 c -1.77083,-0.0868 -3.53365,-0.32921 -5.28711,-0.72852 -1.73611,-0.3993 -3.45487,-0.95421 -5.15625,-1.66601 v -4.6875 c 1.73611,1.07638 3.48091,1.89974 5.23437,2.47265 1.77084,0.57292 3.51563,0.87739 5.23438,0.91211 v -11.92773 c -1.15561,-0.17912 -2.20665,-0.42034 -3.17188,-0.71485 z m 33.11718,1.23633 v 11.30274 c 1.90972,-0.0521 3.40409,-0.58291 4.48047,-1.58985 1.0764,-1.00694 1.61328,-2.37717 1.61328,-4.11328 0,-1.61458 -0.4848,-2.86458 -1.45703,-3.75 -0.97222,-0.90278 -2.51866,-1.51975 -4.63672,-1.84961 z" /><path
id="path4805-2-4-7-6-7-3-6-5-7"
style="font-variation-settings:normal;fill:#999999;fill-opacity:0.595851;stroke:none;stroke-width:0.431;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 150.31223,13.092555 a 32.075813,32.075813 0 0 0 -19.80078,29.63476 h 27.33984 c -1.93468,-0.59036 -3.49625,-1.40699 -4.66602,-2.46093 -1.75346,-1.57986 -2.6289,-3.67123 -2.6289,-6.2754 0,-2.72569 0.91146,-4.89714 2.73437,-6.51172 1.84028,-1.63194 4.41841,-2.56944 7.73438,-2.8125 v -6.11914 h 2.60351 l 0.0254,6.11914 c 1.37152,0.0868 2.76172,0.26107 4.16796,0.52149 1.40625,0.26041 2.83855,0.61697 4.29688,1.06836 v 4.5039 c -1.47569,-0.74652 -2.91797,-1.31944 -4.32422,-1.71875 -1.38889,-0.41666 -2.77713,-0.65907 -4.16602,-0.72851 v 11.22461 c 0.63124,0.095 1.22646,0.21306 1.80469,0.34375 l 19.83399,-19.83594 a 32.075813,32.075813 0 0 0 -34.95508,-6.95312 z m 10.71289,15.16796 c -1.80555,0.0694 -3.22179,0.59028 -4.24609,1.5625 -1.00695,0.97222 -1.50977,2.27431 -1.50977,3.90625 0,1.49306 0.46875,2.67492 1.40625,3.54298 0.95486,0.86805 2.40516,1.449 4.34961,1.74414 z" /><path
id="path4805-2-6-8-1-8-0-7-3-0-5"
style="font-variation-settings:normal;fill:#cccccc;fill-opacity:0.601781;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 185.26732,20.045675 -19.83426,19.83567 c 2.60112,0.58792 4.66872,1.53545 6.19727,2.8457 h 23.0332 a 32.075813,32.075813 0 0 0 -9.39648,-22.68164 z" /></g><g
id="g7584-5"
transform="translate(-119.58762,0.27336443)"><path
id="path4805-0-5-0-8-2-0-8-1-7"
style="font-variation-settings:normal;fill:#ffffff;fill-opacity:0.69999999;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 130.51146,42.727315 a 32.075813,32.075813 0 0 0 16.03711,27.77735 32.075813,32.075813 0 0 0 32.07617,0 32.075813,32.075813 0 0 0 16.03906,-27.77735 h -23.03125 c 0.0565,0.0484 0.11882,0.0932 0.17383,0.14258 1.875,1.68403 2.8125,3.8635 2.8125,6.53711 0,2.67361 -1.01562,4.90322 -3.04688,6.69141 -2.01388,1.78819 -4.65213,2.76172 -7.91601,2.91797 l -0.0274,7.89062 h -2.60351 l -0.0254,-7.83984 c -1.77083,-0.0868 -3.53365,-0.32921 -5.28711,-0.72852 -1.73611,-0.3993 -3.45487,-0.95421 -5.15625,-1.66601 v -4.6875 c 1.73611,1.07638 3.48091,1.89974 5.23437,2.47265 1.77084,0.57292 3.51563,0.87739 5.23438,0.91211 v -11.92773 c -1.15561,-0.17912 -2.20665,-0.42034 -3.17188,-0.71485 z m 33.11718,1.23633 v 11.30274 c 1.90972,-0.0521 3.40409,-0.58291 4.48047,-1.58985 1.0764,-1.00694 1.61328,-2.37717 1.61328,-4.11328 0,-1.61458 -0.4848,-2.86458 -1.45703,-3.75 -0.97222,-0.90278 -2.51866,-1.51975 -4.63672,-1.84961 z" /><path
id="path4805-2-4-7-6-7-3-6-5-7-4"
style="font-variation-settings:normal;fill:#999999;fill-opacity:0.69999999;stroke:none;stroke-width:0.431;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 150.31223,13.092555 a 32.075813,32.075813 0 0 0 -19.80078,29.63476 h 27.33984 c -1.93468,-0.59036 -3.49625,-1.40699 -4.66602,-2.46093 -1.75346,-1.57986 -2.6289,-3.67123 -2.6289,-6.2754 0,-2.72569 0.91146,-4.89714 2.73437,-6.51172 1.84028,-1.63194 4.41841,-2.56944 7.73438,-2.8125 v -6.11914 h 2.60351 l 0.0254,6.11914 c 1.37152,0.0868 2.76172,0.26107 4.16796,0.52149 1.40625,0.26041 2.83855,0.61697 4.29688,1.06836 v 4.5039 c -1.47569,-0.74652 -2.91797,-1.31944 -4.32422,-1.71875 -1.38889,-0.41666 -2.77713,-0.65907 -4.16602,-0.72851 v 11.22461 c 0.63124,0.095 1.22646,0.21306 1.80469,0.34375 l 19.83399,-19.83594 a 32.075813,32.075813 0 0 0 -34.95508,-6.95312 z m 10.71289,15.16796 c -1.80555,0.0694 -3.22179,0.59028 -4.24609,1.5625 -1.00695,0.97222 -1.50977,2.27431 -1.50977,3.90625 0,1.49306 0.46875,2.67492 1.40625,3.54298 0.95486,0.86805 2.40516,1.449 4.34961,1.74414 z" /><path
id="path4805-2-6-8-1-8-0-7-3-0-5-1"
style="font-variation-settings:normal;fill:#cccccc;fill-opacity:0.69999999;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 185.26732,20.045675 -19.83426,19.83567 c 2.60112,0.58792 4.66872,1.53545 6.19727,2.8457 h 23.0332 a 32.075813,32.075813 0 0 0 -9.39648,-22.68164 z" /></g><circle
fill="url(#SVGID_1_)"
cx="278.36606"
cy="42.727314"
id="circle8-2-1-8-3-2"
r="42.700001"
style="fill:url(#linearGradient6651)"
inkscape:export-filename="harbour-expenditure.png"
inkscape:export-xdpi="297.89001"
inkscape:export-ydpi="297.89001" /><path
id="path4805-0-5-0-8-2-0-8-1-1"
style="font-variation-settings:normal;fill:#ffffff;fill-opacity:0.60415018;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 246.28991,42.728013 a 32.075813,32.075813 0 0 0 16.03711,27.77735 32.075813,32.075813 0 0 0 32.07617,0 32.075813,32.075813 0 0 0 16.03906,-27.77735 H 287.411 c 0.0565,0.0484 0.11882,0.0932 0.17383,0.14258 1.875,1.68403 2.8125,3.8635 2.8125,6.53711 0,2.67361 -1.01562,4.90322 -3.04688,6.69141 -2.01388,1.78819 -4.65213,2.76172 -7.91601,2.91797 l -0.0274,7.89062 h -2.60351 l -0.0254,-7.83984 c -1.77083,-0.0868 -3.53365,-0.32921 -5.28711,-0.72852 -1.73611,-0.3993 -3.45487,-0.95421 -5.15625,-1.66601 v -4.6875 c 1.73611,1.07638 3.48091,1.89974 5.23437,2.47265 1.77084,0.57292 3.51563,0.87739 5.23438,0.91211 v -11.92773 c -1.15561,-0.17912 -2.20665,-0.42034 -3.17188,-0.71485 z m 33.11718,1.23633 v 11.30274 c 1.90972,-0.0521 3.40409,-0.58291 4.48047,-1.58985 1.0764,-1.00694 1.61328,-2.37717 1.61328,-4.11328 0,-1.61458 -0.4848,-2.86458 -1.45703,-3.75 -0.97222,-0.90278 -2.51866,-1.51975 -4.63672,-1.84961 z" /><path
id="path4805-2-4-7-6-7-3-6-5-7-7"
style="font-variation-settings:normal;fill:#999999;fill-opacity:0.59676164;stroke:none;stroke-width:0.431;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="M 266.09068,13.093249 A 32.075813,32.075813 0 0 0 246.2899,42.728013 h 27.33984 c -1.93468,-0.59036 -3.49625,-1.40699 -4.66602,-2.46093 -1.75346,-1.57986 -2.6289,-3.67123 -2.6289,-6.2754 0,-2.72569 0.91146,-4.89714 2.73437,-6.51172 1.84028,-1.631944 4.41841,-2.569444 7.73438,-2.812504 v -6.11914 h 2.60351 l 0.0254,6.11914 c 1.37152,0.0868 2.76172,0.26107 4.16796,0.52149 1.40625,0.26041 2.83855,0.61697 4.29688,1.06836 v 4.503904 c -1.47569,-0.74652 -2.91797,-1.31944 -4.32422,-1.71875 -1.38889,-0.41666 -2.77713,-0.65907 -4.16602,-0.72851 v 11.22461 c 0.63124,0.095 1.22646,0.21306 1.80469,0.34375 l 19.83399,-19.835944 a 32.075813,32.075813 0 0 0 -34.95508,-6.95312 z m 10.71289,15.167964 c -1.80555,0.0694 -3.22179,0.59028 -4.24609,1.5625 -1.00695,0.97222 -1.50977,2.27431 -1.50977,3.90625 0,1.49306 0.46875,2.67492 1.40625,3.54298 0.95486,0.86805 2.40516,1.449 4.34961,1.74414 z" /><path
id="path4805-2-6-8-1-8-0-7-3-0-5-8"
style="font-variation-settings:normal;fill:#cccccc;fill-opacity:0.6033051;stroke:none;stroke-width:0.430866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000"
d="m 301.04577,20.046369 -19.83426,19.835674 c 2.60112,0.58792 4.66872,1.53545 6.19727,2.8457 h 23.0332 A 32.075813,32.075813 0 0 0 301.0455,20.046099 Z" /></g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

409
qml/harbour-expenditure.qml Normal file
View file

@ -0,0 +1,409 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
import QtQuick.LocalStorage 2.0
import "pages"
ApplicationWindow {
initialPage: Component { FirstPage { } }
cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: defaultAllowedOrientations
Item {
id: storageItem
// general functions
function getDatabase() {
return storageItem.LocalStorage.openDatabaseSync("Bible_DB", "0.1", "BibleDatabaseComplete", 5000000); // 5 MB estimated size
}
function removeFullTable (tableName) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) { tx.executeSql('DROP TABLE IF EXISTS ' + tableName) });
}
function getTableCount (tableName, default_value) {
var db = getDatabase();
var res="";
try {
db.transaction(function(tx) {
var rs = tx.executeSql('SELECT count(*) AS some_info FROM ' + tableName + ';');
if (rs.rows.length > 0) {
res = rs.rows.item(0).some_info;
} else {
res = default_value;
}
})
} catch (err) {
//console.log("Database " + err);
res = default_value;
};
return res
}
// settings
function setSettings( setting, value ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + 'settings_table' + '(setting TEXT UNIQUE, value TEXT)');
var rs = tx.executeSql('INSERT OR REPLACE INTO ' + 'settings_table' + ' VALUES (?,?);', [setting,value]);
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function getSettings( setting, default_value ) {
var db = getDatabase();
var res="";
try {
db.transaction(function(tx) {
var rs = tx.executeSql('SELECT value FROM '+ 'settings_table' +' WHERE setting=?;', [setting]);
if (rs.rows.length > 0) {
res = rs.rows.item(0).value;
} else {
res = default_value;
}
if (res === null) {
res = default_value
}
})
} catch (err) {
//console.log("Database " + err);
res = default_value;
};
//console.log(setting + " = " + res)
return res
}
// all projects available
function setProject( project_id_timestamp, project_name, project_members, project_recent_payer_boolarray, project_recent_beneficiaries_boolarray, project_base_currency ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + 'projects_table' + ' (project_id_timestamp TEXT,
project_name TEXT,
project_members TEXT,
project_recent_payer_boolarray TEXT,
project_recent_beneficiaries_boolarray TEXT,
project_base_currency TEXT)' );
var rs = tx.executeSql('INSERT OR REPLACE INTO ' + 'projects_table' + ' VALUES (?,?,?,?,?,?);', [project_id_timestamp,
project_name,
project_members,
project_recent_payer_boolarray,
project_recent_beneficiaries_boolarray,
project_base_currency ]);
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function updateProject ( project_id_timestamp, project_name, project_members, project_recent_payer_boolarray, project_recent_beneficiaries_boolarray, project_base_currency ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + 'projects_table' + ' (project_id_timestamp TEXT,
project_name TEXT,
project_members TEXT,
project_recent_payer_boolarray TEXT,
project_recent_beneficiaries_boolarray TEXT,
project_base_currency TEXT)' );
var rs = tx.executeSql('UPDATE projects_table'
+ ' SET project_name="' + project_name
+ '", project_members="' + project_members
+ '", project_recent_payer_boolarray="' + project_recent_payer_boolarray
+ '", project_recent_beneficiaries_boolarray="' + project_recent_beneficiaries_boolarray
+ '", project_base_currency="' + project_base_currency
+ '" WHERE project_id_timestamp=' + project_id_timestamp + ';');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function updateField_Project ( project_id_timestamp, field_name, new_value ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + 'projects_table' + ' (project_id_timestamp TEXT,
project_name TEXT,
project_members TEXT,
project_recent_payer_boolarray TEXT,
project_recent_beneficiaries_boolarray TEXT,
project_base_currency TEXT)' );
var rs = tx.executeSql('UPDATE projects_table SET ' + field_name + '="' + new_value + '" WHERE project_id_timestamp=' + project_id_timestamp + ';');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function deleteProject (project_id_timestamp) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + 'projects_table' + ' (project_id_timestamp TEXT,
project_name TEXT,
project_members TEXT,
project_recent_payer_boolarray TEXT,
project_recent_beneficiaries_boolarray TEXT,
project_base_currency TEXT)' );
var rs = tx.executeSql('DELETE FROM ' + 'projects_table WHERE project_id_timestamp=' + project_id_timestamp + ';');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
removeFullTable("table_" + project_id_timestamp)
return res;
}
function getAllProjects( default_value ) {
var db = getDatabase();
var res=[];
try {
db.transaction(function(tx) {
var rs = tx.executeSql('SELECT * FROM '+ 'projects_table;')
if (rs.rows.length > 0) {
for (var i = 0; i < rs.rows.length; i++) {
res.push([rs.rows.item(i).project_id_timestamp,
rs.rows.item(i).project_name,
rs.rows.item(i).project_members,
rs.rows.item(i).project_recent_payer_boolarray,
rs.rows.item(i).project_recent_beneficiaries_boolarray,
rs.rows.item(i).project_base_currency,
])
}
} else {
res = default_value;
}
})
} catch (err) {
//console.log("Database " + err);
res = default_value;
};
return res
}
// all exchange rates used
function countExchangeRateOccurances (exchange_rate_currency, default_value) {
var db = getDatabase();
var res="";
try {
db.transaction(function(tx) {
var rs = tx.executeSql('SELECT count(*) AS some_info FROM exchange_rates_table WHERE exchange_rate_currency=?;', [exchange_rate_currency]);
if (rs.rows.length > 0) {
res = rs.rows.item(0).some_info;
} else {
res = default_value;
}
})
} catch (err) {
//console.log("Database " + err);
res = default_value;
};
return res
}
function setExchangeRate( exchange_rate_currency, exchange_rate_value ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS ' + 'exchange_rates_table' + ' (exchange_rate_currency TEXT, exchange_rate_value TEXT)' );
var rs = tx.executeSql('INSERT OR REPLACE INTO ' + 'exchange_rates_table' + ' VALUES (?,?);', [exchange_rate_currency, exchange_rate_value ]);
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function updateExchangeRate( exchange_rate_currency, exchange_rate_value ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS exchange_rates_table (exchange_rate_currency TEXT, exchange_rate_value TEXT)');
var rs = tx.executeSql('UPDATE exchange_rates_table SET exchange_rate_value="' + exchange_rate_value + '" WHERE exchange_rate_currency="' + exchange_rate_currency + '";');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function getExchangeRate(exchange_rate_currency, default_value) {
var db = getDatabase();
var res=[];
try {
db.transaction(function(tx) {
var rs = tx.executeSql('SELECT * FROM '+ 'exchange_rates_table' +' WHERE exchange_rate_currency=?;', [exchange_rate_currency]);
if (rs.rows.length > 0) {
for (var i = 0; i < rs.rows.length; i++) {
res.push(rs.rows.item(i).exchange_rate_value)
}
} else {
res = default_value;
}
})
} catch (err) {
//console.log("Database " + err);
res = default_value;
};
return res
}
// all expenes in current project
function setExpense( project_name_table, id_unixtime_created, date_time, expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS table_' + project_name_table + ' (id_unixtime_created TEXT,
date_time TEXT,
expense_name TEXT,
expense_sum TEXT,
expense_currency TEXT,
expense_info TEXT,
expense_payer TEXT,
expense_members TEXT)' );
var rs = tx.executeSql('INSERT OR REPLACE INTO table_' + project_name_table + ' VALUES (?,?,?,?,?,?,?,?);', [ id_unixtime_created,
date_time,
expense_name,
expense_sum,
expense_currency,
expense_info,
expense_payer,
expense_members ]);
if (rs.rowsAffected > 0) {
res = "OK";
//console.log("project info found and updated")
} else {
res = "Error";
}
}
);
return res;
}
function updateExpense ( project_name_table, id_unixtime_created, date_time, expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members ) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS table_' + project_name_table + ' (id_unixtime_created TEXT,
date_time TEXT,
expense_name TEXT,
expense_sum TEXT,
expense_currency TEXT,
expense_info TEXT,
expense_payer TEXT,
expense_members TEXT)' );
var rs = tx.executeSql('UPDATE table_' + project_name_table
+ ' SET date_time="' + date_time
+ '", expense_name="' + expense_name
+ '", expense_sum="' + expense_sum
+ '", expense_currency="' + expense_currency
+ '", expense_info="' + expense_info
+ '", expense_payer="' + expense_payer
+ '", expense_members="' + expense_members
+ '" WHERE id_unixtime_created=' + id_unixtime_created + ';');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function deleteExpense (project_id_timestamp, id_unixtime_created) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS table_' + project_id_timestamp + ' (id_unixtime_created TEXT,
date_time TEXT,
expense_name TEXT,
expense_sum TEXT,
expense_currency TEXT,
expense_info TEXT,
expense_payer TEXT,
expense_members TEXT)' );
//var rs = tx.executeSql('DELETE FROM table_' + project_id_timestamp + ';');
var rs = tx.executeSql('DELETE FROM table_' + project_id_timestamp + ' WHERE id_unixtime_created=' + id_unixtime_created + ';');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function deleteAllExpenses (project_id_timestamp) {
var db = getDatabase();
var res = "";
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS table_' + project_id_timestamp + ' (id_unixtime_created TEXT,
date_time TEXT,
expense_name TEXT,
expense_sum TEXT,
expense_currency TEXT,
expense_info TEXT,
expense_payer TEXT,
expense_members TEXT)' );
var rs = tx.executeSql('DELETE FROM table_' + project_id_timestamp + ';');
if (rs.rowsAffected > 0) {
res = "OK";
} else {
res = "Error";
}
}
);
return res;
}
function getAllExpenses( project_name_table, default_value ) {
var db = getDatabase();
var res=[];
try {
db.transaction(function(tx) {
var rs = tx.executeSql('SELECT * FROM table_'+ project_name_table + ';');
if (rs.rows.length > 0) {
for (var i = 0; i < rs.rows.length; i++) {
res.push([rs.rows.item(i).id_unixtime_created,
rs.rows.item(i).date_time,
rs.rows.item(i).expense_name,
rs.rows.item(i).expense_sum,
rs.rows.item(i).expense_currency,
rs.rows.item(i).expense_info,
rs.rows.item(i).expense_payer,
rs.rows.item(i).expense_members,
])
}
} else {
res = default_value;
}
})
} catch (err) {
//console.log("Database " + err);
res = default_value;
};
return res
}
}
}

91
qml/pages/AboutPage.qml Normal file
View file

@ -0,0 +1,91 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
Page {
id: page
allowedOrientations: Orientation.Portrait //All
SilicaFlickable {
id: listView
anchors.fill: parent
contentHeight: idColumn.height // Tell SilicaFlickable the height of its content.
VerticalScrollDecorator {}
Column {
id: idColumn
x: Theme.paddingLarge
width: parent.width - 2*x
Label {
width: parent.width
height: Theme.itemSizeLarge
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: Theme.fontSizeLarge
color: Theme.primaryColor
text: qsTr("Expenditure")
}
Item {
width: parent.width
height: Theme.paddingLarge
}
Image {
width: parent.width
height: Theme.itemSizeHuge
source: "../cover/harbour-expenditure.png"
sourceSize.width: height
sourceSize.height: height
fillMode: Image.PreserveAspectFit
}
Item {
width: parent.width
height: Theme.paddingLarge * 2.5
}
Label {
x: Theme.paddingMedium
width: parent.width - 2*x
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraSmall
wrapMode: Text.Wrap
text: qsTr("Expenditure is a tool to track and split bills, project or trip expenses in multiple currencies among groups.")
}
Label {
x: Theme.paddingMedium
width: parent.width - 2*x
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraSmall
wrapMode: Text.Wrap
text: qsTr("Thanksgiving, feedback and support is always welcome.")
bottomPadding: Theme.paddingLarge * 2
}
Label {
x: Theme.paddingMedium
width: parent.width - 2*x
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraSmall
wrapMode: Text.Wrap
text: qsTr("Troubleshooting:")
+ "\n" + qsTr("In case of any database error tap 10x on the word 'Settings' for cleanup options.")
bottomPadding: Theme.paddingLarge * 2
}
Label {
width: parent.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
wrapMode: Text.Wrap
text: qsTr("Contact:")
+ "\n" + qsTr("Copyright © 2022 Tobias Planitzer")
+ "\n" + ("tp.labs@protonmail.com")
+ "\n" + qsTr("License: GPL v3")
}
Item {
width: parent.width
height: Theme.paddingLarge * 2.5
}
}
} // end Silica Flickable
}

View file

@ -0,0 +1,153 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
MouseArea {
id: popup
z: 10
width: parent.width
height: parent.height
visible: opacity > 0
opacity: 0.0
onClicked: {
hide()
}
// UI variables
property var hideBackColor : Theme.rgba(Theme.overlayBackgroundColor, 0.9)
property string headlineInfoText : ""
property string detailedInfoText : ""
property string otherInfoText : ""
property string filePath_Action : ""
Behavior on opacity {
FadeAnimator {}
}
Rectangle {
anchors.fill: parent
color: hideBackColor
onColorChanged: opacity = 4
Rectangle {
id: idBackgroundRectProject
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: parent.width
height: parent.height - anchors.topMargin - Theme.paddingLarge
radius: Theme.paddingLarge
SilicaFlickable {
anchors.fill: parent
contentHeight: addExpenseColumn.height
clip: true
Column {
id: addExpenseColumn
width: parent.width
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
Label {
x: Theme.paddingLarge
width: parent.width - 2*x
wrapMode: Text.WordWrap
text: headlineInfoText
bottomPadding: Theme.paddingLarge
}
Label {
x: Theme.paddingLarge
width: parent.width - 2*x
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeTiny
text: detailedInfoText
bottomPadding: Theme.paddingLarge
}
Row {
x: Theme.paddingLarge
width: parent.width - 2*x
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
Button {
id: idButton1
width: parent.width /2 - Theme.paddingLarge /2
onClicked: {
restoreProjectExpenses(filePath_Action, "replace")
//backNavigationBlocked_deletedAddedProject = true
hide()
}
}
Item {
width: Theme.paddingLarge
height: 1
}
Button {
id: idButton2
width: parent.width /2 - Theme.paddingLarge /2
height: idButton1.height
onClicked: {
restoreProjectExpenses(filePath_Action, "merge")
//backNavigationBlocked_deletedAddedProject = true
hide()
}
}
}
Label {
x: Theme.paddingLarge
width: parent.width - 2*x
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeTiny
color: Theme.secondaryColor
text: otherInfoText
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
}
}
}
}
}
Icon {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: idBackgroundRectProject.anchors.topMargin / 2 - height/2
source: "image://theme/icon-splus-cancel?"
opacity: 1
}
function notify( color, upperMargin, headText, bodyText, otherText, choiceText_1, choiceText_2, filePath ) {
// color settings
if (color && (typeof(color) != "undefined")) {
idBackgroundRectProject.color = color
} else {
idBackgroundRectProject.color = Theme.rgba(Theme.highlightBackgroundColor, 0.9)
}
// position settings
if (upperMargin && (typeof(upperMargin) != "undefined")) {
idBackgroundRectProject.anchors.topMargin = upperMargin
} else {
idBackgroundRectProject.anchors.topMargin = 0
}
// set texts
headlineInfoText = headText
detailedInfoText = bodyText
otherInfoText = otherText
idButton1.text = choiceText_1
idButton2.text = choiceText_2
filePath_Action = filePath
// show banner overlay
popup.opacity = 1.0
}
function hide() {
popup.opacity = 0.0
}
}

View file

@ -0,0 +1,601 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
MouseArea {
id: popup
z: 10
width: parent.width
height: parent.height
visible: opacity > 0
opacity: 0.0
onClicked: {
hide()
}
onOpacityChanged: {
//if needed
}
// UI variables
property var activeProjectID
property var hideBackColor : Theme.rgba(Theme.overlayBackgroundColor, 0.9)
property int amountBeneficiaries : 0
property string modeEdit : "new"
property date currentDate
property date currentTime
property double editedTimeStamp // unixtime, can be edited, is NOT entry creation timestamp
property double createdTimeStamp
property bool dateTimeManuallyChanged : false
// suppress blend to main window on this overlay, e.g. for context menu ...
// BUG: creates problems with _selectOrientation for context menus larger than 5 entries
property alias __silica_applicationwindow_instance: fakeApplicationWindow
Item {
id: fakeApplicationWindow
// suppresses warnings by context menu
property var _dimScreen
property var _undim
function _undim() {}
function _dimScreen() {}
}
Behavior on opacity {
FadeAnimator {}
}
Rectangle {
anchors.fill: parent
color: hideBackColor
onColorChanged: opacity = 4
Rectangle {
id: idBackgroundRectExpenses
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: parent.width
height: parent.height - anchors.topMargin - Theme.paddingLarge
radius: Theme.paddingLarge
SilicaFlickable {
anchors.fill: parent
contentHeight: addExpenseColumn.height
clip: true
Column {
id: addExpenseColumn
width: parent.width
Row {
x: Theme.paddingLarge
width: parent.width - 2*x
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge * 2
Column {
id: idColumnAddDate
width: parent.width /3*2 - Theme.paddingLarge /2
Label {
width: parent.width
text: currentDate.toLocaleDateString(Qt.locale(), "dd. MMMM yyyy")
color: Theme.highlightColor
MouseArea {
anchors.fill: parent
onClicked: {
unFocusTextFields()
var dialog = pageStack.push(datePickerComponent, {
date: currentDate // preset picker to todays date
} )
dialog.accepted.connect( function () {
currentDate = (dialog.date)
editedTimeStamp = Number((combineDateAndTime(currentDate, currentTime)).getTime())
dateTimeManuallyChanged = true
} )
}
}
}
Label {
width: parent.width
text: currentTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
color: Theme.highlightColor
MouseArea {
anchors.fill: parent
onClicked: {
unFocusTextFields()
var dialog = pageStack.push(timePickerComponent, {
hour: currentTime.getHours(), // preset picker to current time
minute: currentTime.getMinutes(),
hourMode: 1
} )
dialog.accepted.connect( function () {
currentTime = new Date ( dialog.time)
editedTimeStamp = Number((combineDateAndTime(currentDate, currentTime)).getTime())
dateTimeManuallyChanged = true
} )
}
}
}
/*
Label {
width: parent.width
font.pixelSize: Theme.fontSizeTiny
text: "create_" + createdTimeStamp
}
Label {
width: parent.width
font.pixelSize: Theme.fontSizeTiny
text: "edit_" + editedTimeStamp
}
*/
}
Item {
width: Theme.paddingLarge
height: 1
}
Button {
id: idLabelHeaderAdd2
enabled: (idTextfieldItem.length > 0) && (amountBeneficiaries > 0)
width: parent.width /3 - Theme.paddingLarge /2
height: idColumnAddDate.height
text: (modeEdit === "new") ? qsTr("Add") : qsTr("Save")
onClicked: {
addEditExpense()
}
}
}
TextField {
id: idTextfieldItem
width: page.width
acceptableInput: text.length < 255
font.pixelSize: Theme.fontSizeMedium
EnterKey.onClicked: {
focus = false
}
Label {
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("expense")
}
}
Row {
width: parent.width
TextField {
id: idTextfieldPrice
width: parent.width /3 + parent.width /6
textRightMargin: 0
inputMethodHints: Qt.ImhFormattedNumbersOnly //use "Qt.ImhDigitsOnly" for INT
text: Number("0").toFixed(2)
EnterKey.onClicked: {
focus = false
}
onFocusChanged: {
text = text.replace(",", ".")
text = Number(text).toFixed(2)
if (focus) {
selectAll()
}
}
Label {
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("price")
}
}
Item {
width: parent.width / 6
height: 1
}
TextField {
id: idTextfieldCurrency
width: parent.width /3
textLeftMargin: 0
horizontalAlignment: TextInput.AlignRight
acceptableInput: text.length > 0
EnterKey.enabled: text.length >= 0
EnterKey.onClicked: {
focus = false
}
onFocusChanged: {
if (text.length === 0) {
text = recentlyUsedCurrency
}
if (focus) {
selectAll()
}
}
Label {
anchors.right: parent.right
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("currency")
}
}
}
TextField {
id: idTextfieldInfo
width: page.width
//acceptableInput: text.length < 255
font.pixelSize: Theme.fontSizeMedium
EnterKey.onClicked: {
focus = false
}
Label {
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("info")
}
}
Row {
x: Theme.paddingLarge
width: parent.width - 2*x
topPadding: Theme.paddingLarge * 2
bottomPadding: Theme.paddingMedium
Label {
width: parent.width / 2 - Theme.paddingLarge/2
verticalAlignment: Text.AlignVCenter
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("payment by")
}
Item {
width: Theme.paddingLarge
height: 1
}
Label {
width: parent.width / 2 - Theme.paddingLarge/2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("beneficiary")
}
}
Column {
x: Theme.paddingLarge
width: parent.width - 2*x
Repeater {
model: listModel_activeProjectMembers
delegate: Row {
id: idtestcolumn
width: parent.width
Label {
id: idLabelPayerName
width: parent.width / 3*2 - Theme.paddingLarge/2
height: Theme.iconSizeSmallPlus
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall
font.bold: member_isPayer === "true"
color: (member_isPayer === "true") ? Theme.primaryColor : Theme.highlightColor
text: member_name
MouseArea {
anchors.fill: parent
onClicked: {
for (var i = 0; i < listModel_activeProjectMembers.count ; i++) {
listModel_activeProjectMembers.setProperty(i, "member_isPayer", "false")
}
member_isPayer="true"
}
}
}
Item {
width: Theme.paddingLarge
height: 1
}
Item {
width: parent.width / 3 - Theme.paddingLarge/2
height: parent.height
Icon {
anchors.right: parent.right
height: parent.height
width: height
color: (member_isPayer==="true") ? Theme.primaryColor : Theme.highlightColor
source: (member_isBeneficiary==="true") ? "image://theme/icon-m-accept?" : ""
Rectangle {
z: -1
anchors.centerIn: parent
width: parent.width - Theme.paddingSmall
height: width
color: "transparent"
border.width: (member_isPayer==="true") ? 2 : 1
border.color: Theme.secondaryColor
radius: width/4
}
MouseArea {
anchors.fill: parent
anchors.leftMargin: -Theme.paddingLarge
anchors.rightMargin: -Theme.paddingLarge
onClicked: {
(member_isBeneficiary==="true") ? (member_isBeneficiary="false") : (member_isBeneficiary="true")
amountBeneficiaries = 0
for (var i = 0; i < listModel_activeProjectMembers.count ; i++) {
if (listModel_activeProjectMembers.get(i).member_isBeneficiary === "true") {
amountBeneficiaries += 1
}
}
}
}
}
}
}
}
}
Item {
width: parent.width
height: Theme.itemSizeSmall / 2
}
}
}
}
}
Icon {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: idBackgroundRectExpenses.anchors.topMargin / 2 - height/2
source: "image://theme/icon-splus-cancel?"
opacity: 1
}
function notify( color, upperMargin, modeEditNew, activeProjectID_unixtime, expense_ID_created ) {
// color settings
if (color && (typeof(color) != "undefined")) {
idBackgroundRectExpenses.color = color
}
else {
idBackgroundRectExpenses.color = Theme.rgba(Theme.highlightBackgroundColor, 0.9)
}
// position settings
if (upperMargin && (typeof(upperMargin) != "undefined")) {
idBackgroundRectExpenses.anchors.topMargin = upperMargin
}
else {
idBackgroundRectExpenses.anchors.topMargin = 0
}
// project settings
activeProjectID = activeProjectID_unixtime
modeEdit = modeEditNew
// reset time and date to current time and date
if (modeEditNew === "new") {
idTextfieldItem.text = ""
idTextfieldPrice.text = "0"
idTextfieldCurrency.text = recentlyUsedCurrency
idTextfieldInfo.text = ""
currentDate = new Date()
currentTime = new Date()
createdTimeStamp = Number(new Date().getTime())
editedTimeStamp = Number(new Date().getTime())
} else { // modeEditNew === "edit"
//console.log("editing " + expense_ID_created)
for (var i = 0; i < listModel_activeProjectExpenses.count ; i++) {
if (Number(expense_ID_created) === Number(listModel_activeProjectExpenses.get(i).id_unixtime_created)) {
idTextfieldItem.text = listModel_activeProjectExpenses.get(i).expense_name
idTextfieldPrice.text = listModel_activeProjectExpenses.get(i).expense_sum
idTextfieldCurrency.text = listModel_activeProjectExpenses.get(i).expense_currency
idTextfieldInfo.text = listModel_activeProjectExpenses.get(i).expense_info
var olderDateTime = Number(listModel_activeProjectExpenses.get(i).date_time)
currentDate = new Date(olderDateTime)
currentTime = new Date (olderDateTime)
createdTimeStamp = Number(listModel_activeProjectExpenses.get(i).id_unixtime_created)
editedTimeStamp = Number(listModel_activeProjectExpenses.get(i).date_time)
}
}
}
// remember last beneficiaries and payer in "new" mode, load expense beneficiaries and payer in "edit" mode
amountBeneficiaries = 0
if (modeEditNew === "new") {
// count beneficiaries
for (i = 0; i < listModel_activeProjectMembers.count ; i++) {
if (listModel_activeProjectMembers.get(i).member_isBeneficiary === "true") {
amountBeneficiaries += 1
}
}
// use last used project specific beneficiaries and payers settings
for (var j = 0; j < listModel_allProjects.count ; j++) {
if ( Number(listModel_allProjects.get(j).project_id_timestamp) === Number(activeProjectID_unixtime) ) {
var activeProjectMembersArray = (listModel_allProjects.get(j).project_members).split(" ||| ")
var activeProjectRecentPayerArray = (listModel_allProjects.get(j).project_recent_payer_boolarray).split(" ||| ")
var activeProjectRecentBeneficiariesArray = (listModel_allProjects.get(j).project_recent_beneficiaries_boolarray).split(" ||| ")
for (var s = 0; s < activeProjectMembersArray.length ; s++) {
listModel_activeProjectMembers.set( s, {
"member_isBeneficiary" : activeProjectRecentBeneficiariesArray[s],
"member_isPayer" : activeProjectRecentPayerArray[s],
})
}
}
}
} else { // "edit" mode
// find item in expenses list for editing
for (var k = 0; k < listModel_activeProjectExpenses.count ; k++) {
if (Number(expense_ID_created) === Number(listModel_activeProjectExpenses.get(k).id_unixtime_created)) {
var editedBeneficiariesList = (listModel_activeProjectExpenses.get(k).expense_members).split(" ||| ")
var editPayersList =(listModel_activeProjectExpenses.get(k).expense_payer).split(" ||| ")
for (var l = 0; l < listModel_activeProjectMembers.count; l++) {
listModel_activeProjectMembers.setProperty(l, "member_isBeneficiary", "false")
listModel_activeProjectMembers.setProperty(l, "member_isPayer", "false")
// mark beneficiaries and get amount
for (var m = 0; m < editedBeneficiariesList.length; m++) {
if (listModel_activeProjectMembers.get(l).member_name === editedBeneficiariesList[m]) {
listModel_activeProjectMembers.setProperty(l, "member_isBeneficiary", "true")
amountBeneficiaries += 1
}
}
// mark payer
for (m = 0; m < editPayersList.length; m++) {
if (listModel_activeProjectMembers.get(l).member_name === editPayersList[m]) {
listModel_activeProjectMembers.setProperty(l, "member_isPayer", "true")
}
}
}
}
}
}
//console.log(amountBeneficiaries)
// show banner overlay
popup.opacity = 1.0
// focus on expense text searchField
if (modeEditNew === "new") {
idTextfieldItem.forceActiveFocus()
}
}
function hide() {
unFocusTextFields()
popup.opacity = 0.0 // make invisible
// clear all fields
idTextfieldItem.text = ""
idTextfieldPrice.text = "0"
idTextfieldCurrency.text = recentlyUsedCurrency
idTextfieldInfo.text = ""
//idButtonAddExpense.visible = true
}
function unFocusTextFields() {
idTextfieldItem.focus = false
idTextfieldPrice.focus = false
idTextfieldCurrency.focus = false
idTextfieldInfo.focus = false
}
function combineDateAndTime(date, time) {
// warning: slice necessary to avoid singele digit outputs which can not be used in combined call
var year = date.getFullYear();
var month = ('0' + (date.getMonth() + 1)).slice(-2); // Jan is 0, dec is 11
var day = ('0' + date.getDate()).slice(-2)
//var month = date.getMonth() // only give one digit outputs <10, which causes errors later
//var day = date.getDate(); // only gives one digit outputs <10, which causes errors later
var dateString = year + '-' + month + '-' + day;
var hours = ('0' + time.getHours()).slice(-2)
var minutes = ('0' + time.getMinutes()).slice(-2)
var timeString = hours + ':' + minutes + ':00';
//var timeString = time.getHours() + ':' + time.getMinutes() + ':00';
//console.log(dateString)
//console.log(timeString)
var combined = Date.fromLocaleString(Qt.locale(), dateString + ' ' + timeString, "yyyy-MM-dd hh:mm:ss")
//console.log(combined)
return combined;
}
function addEditExpense() {
var project_name_table = activeProjectID.toString()
var id_unixtime_created = createdTimeStamp // time of entry creation, does not change, serves as unique expense_ID
var date_time = editedTimeStamp // new or edited time of expense
var expense_name = idTextfieldItem.text
var expense_sum = idTextfieldPrice.text
var expense_currency = idTextfieldCurrency.text
var expense_info = idTextfieldInfo.text
var expense_members = ""
var project_recent_payer_boolarray = ""
var project_recent_beneficiaries_boolarray = ""
for (var i = 0; i < listModel_activeProjectMembers.count ; i++) {
project_recent_payer_boolarray += " ||| " + listModel_activeProjectMembers.get(i).member_isPayer
project_recent_beneficiaries_boolarray += " ||| " + listModel_activeProjectMembers.get(i).member_isBeneficiary
if (listModel_activeProjectMembers.get(i).member_isPayer === "true") {
var expense_payer = listModel_activeProjectMembers.get(i).member_name
}
if (listModel_activeProjectMembers.get(i).member_isBeneficiary === "true") {
expense_members += " ||| " + listModel_activeProjectMembers.get(i).member_name
}
}
project_recent_payer_boolarray = project_recent_payer_boolarray.replace(" ||| ", "")
project_recent_beneficiaries_boolarray = project_recent_beneficiaries_boolarray.replace(" ||| ", "")
expense_members = expense_members.replace(" ||| ", "")
//console.log("table_name= " + project_name_table)
//console.log(id_unixtime_created + ", " + date_time + ", " + expense_name + ", " + expense_sum + ", " + expense_currency + ", " + expense_info + ", " + expense_payer + ", " + expense_members)
// update listmodel expenses and store in DB
if (modeEdit === "new") {
listModel_activeProjectExpenses.append({
id_unixtime_created : Number(id_unixtime_created).toFixed(0),
date_time : Number(date_time).toFixed(0),
expense_name : expense_name,
expense_sum : Number(expense_sum).toFixed(2),
expense_currency : expense_currency,
expense_info : expense_info,
expense_payer : expense_payer,
expense_members : expense_members,
})
storageItem.setExpense(project_name_table, id_unixtime_created.toString(), date_time.toString(), expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members)
} else { //modeEdit === "edit"
for (var j = 0; j < listModel_activeProjectExpenses.count ; j++) {
if (id_unixtime_created === Number(listModel_activeProjectExpenses.get(j).id_unixtime_created)) {
listModel_activeProjectExpenses.set(j, {
id_unixtime_created : Number(id_unixtime_created).toFixed(0),
date_time : Number(date_time).toFixed(0),
expense_name : expense_name,
expense_sum : Number(expense_sum).toFixed(2),
expense_currency : expense_currency,
expense_info : expense_info,
expense_payer : expense_payer,
expense_members : expense_members,
})
}
}
storageItem.updateExpense(project_name_table, id_unixtime_created.toString(), date_time.toString(), expense_name, expense_sum, expense_currency, expense_info, expense_payer, expense_members)
// if dates got changed: also sort expenses list
if (dateTimeManuallyChanged) {
listModel_activeProjectExpenses.quick_sort()
dateTimeManuallyChanged = false
}
}
//remember recently used currency
recentlyUsedCurrency = expense_currency
storageItem.setSettings("recentlyUsedCurrency", recentlyUsedCurrency)
// update allProject_Listmodel and DB for recent_beneficiaries and recent_payer in case of "new" entry
if (modeEdit === "new") {
for (var k = 0; k < listModel_allProjects.count ; k++) {
if ( Number(listModel_allProjects.get(k).project_id_timestamp) === Number(activeProjectID_unixtime) ) {
listModel_allProjects.set(k, {
"project_recent_payer_boolarray" : project_recent_payer_boolarray ,
"project_recent_beneficiaries_boolarray" : project_recent_beneficiaries_boolarray,
})
}
}
storageItem.updateField_Project(activeProjectID_unixtime, "project_recent_payer_boolarray", project_recent_payer_boolarray)
storageItem.updateField_Project(activeProjectID_unixtime, "project_recent_beneficiaries_boolarray", project_recent_beneficiaries_boolarray)
}
// finally hide popup banner
hide()
}
}

View file

@ -0,0 +1,502 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
MouseArea {
id: popup
z: 10
width: parent.width
height: parent.height
visible: opacity > 0
opacity: 0.0
onClicked: {
hide()
}
onOpacityChanged: {
//if needed
}
// UI variables
property var hideBackColor : Theme.rgba(Theme.overlayBackgroundColor, 0.9)
property int amountBeneficiaries : listModel_activeProjectMembersTEMP.count
property string modeEdit : "new"
property real editItemIndex : -1 // -1=new, otherwise gives index of list
property int tempProjectListIndex
property bool showTextfieldMemberName : false
property string timeStamp : ((new Date).getTime()).toString() // creates unix timestamp
property string backupFilePath : ""
// suppress blend to main window on this overlay, e.g. for context menu ...
// BUG: creates problems with _selectOrientation for context menus larger than 5 entries
property alias __silica_applicationwindow_instance: fakeApplicationWindow
Item {
id: fakeApplicationWindow
// suppresses warnings by context menu
property var _dimScreen
property var _undim
function _undim() {}
function _dimScreen() {}
}
Behavior on opacity {
FadeAnimator {}
}
ListModel {
id: listModel_activeProjectMembersTEMP
}
RemorsePopup {
z: 10
id: remorse_deleteProject
}
Rectangle {
anchors.fill: parent
color: hideBackColor
onColorChanged: opacity = 4
Rectangle {
id: idBackgroundRectProject
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: parent.width
height: parent.height - anchors.topMargin - Theme.paddingLarge
radius: Theme.paddingLarge
SilicaFlickable {
anchors.fill: parent
contentHeight: addExpenseColumn.height
clip: true
Column {
id: addExpenseColumn
width: parent.width
Row {
x: Theme.paddingLarge
width: parent.width - 2*x
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
Column {
id: idColumnAddProject
width: parent.width /3*2 - Theme.paddingLarge /2
Label {
width: parent.width
text: qsTr("Project")
}
Label {
width: parent.width
font.pixelSize: Theme.fontSizeTiny
text: (modeEdit === "new") ? (qsTr("create")) : (qsTr("edit"))
}
}
Item {
width: Theme.paddingLarge
height: 1
}
Button {
id: idLabelHeaderAdd2
enabled: (idTextfieldProjectname.length > 0) && (amountBeneficiaries > 0)
width: parent.width /3 - Theme.paddingLarge /2
height: idColumnAddProject.height
text: (modeEdit === "new") ? qsTr("Add") : qsTr("Save")
onClicked: {
addProjectDB()
}
}
}
Row {
width: parent.width
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
TextField {
id: idTextfieldProjectname
width: parent.width /3 * 2 - Theme.paddingLarge
acceptableInput: text.length < 255
font.pixelSize: Theme.fontSizeMedium
EnterKey.onClicked: {
focus = false
}
Label {
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("name")
}
}
Item {
width: Theme.paddingLarge
height: 1
}
TextField {
id: idTextfieldCurrencyProject
width: parent.width /3
textLeftMargin: 0
horizontalAlignment: TextInput.AlignRight
acceptableInput: text.length > 0
EnterKey.enabled: text.length >= 0
EnterKey.onClicked: {
focus = false
}
onFocusChanged: {
if (text.length === 0) {
text = recentlyUsedCurrency
}
if (focus) {
selectAll()
}
}
Label {
anchors.right: parent.right
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
color: Theme.secondaryColor
text: qsTr("base currency")
}
}
}
Row {
x: Theme.paddingLarge
width: parent.width - 2*x
topPadding: Theme.paddingLarge
Label {
x: Theme.paddingLarge
width: parent.width/ 3*2 - Theme.paddingLarge / 2
height: idAddMemberButton.height
verticalAlignment: Text.AlignVCenter
font.pixelSize: Theme.fontSizeMedium
text: qsTr("Members")
}
Item {
width: Theme.paddingLarge
height: 1
}
Item {
id: idAddMemberButton
width: parent.width / 3 - Theme.paddingLarge/2
height: Theme.iconSizeMedium
Icon {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: parent.height
width: height
source: !showTextfieldMemberName ? "image://theme/icon-m-add?" : "image://theme/icon-m-clear?"
}
MouseArea {
anchors.fill: parent
onClicked: {
showTextfieldMemberName ? showTextfieldMemberName=false : showTextfieldMemberName=true
if (showTextfieldMemberName) {
// clear field and set for new member
editItemIndex = -1 // -1=add new member
idTextfieldAddMember.text = ""
idTextfieldAddMember.forceActiveFocus()
} else {
idTextfieldAddMember.text = ""
idTextfieldAddMember.focus = false
}
}
}
}
}
Column {
visible: !showTextfieldMemberName
width: parent.width
Repeater {
model: listModel_activeProjectMembersTEMP
delegate: ListItem {
contentHeight: Theme.itemSizeExtraSmall
menu: ContextMenu {
MenuItem {
text: qsTr("rename")
onClicked: {
showTextfieldMemberName ? showTextfieldMemberName=false : showTextfieldMemberName=true
editItemIndex = index
idTextfieldAddMember.text = member_name
idTextfieldAddMember.forceActiveFocus()
}
}
MenuItem {
text: qsTr("remove")
onClicked: {
listModel_activeProjectMembersTEMP.remove(index)
}
}
}
Row {
x: Theme.paddingLarge
width: parent.width - 2*x
height: parent.height
Label {
width: parent.width
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall
color: Theme.highlightColor
text: member_name
}
}
}
}
}
Row {
width: parent.width
visible: showTextfieldMemberName
topPadding: Theme.paddingMedium
TextField {
id: idTextfieldAddMember
width: parent.width // 3*2 - Theme.paddingLarge
acceptableInput: text.length < 255
font.pixelSize: Theme.fontSizeSmall
onActiveFocusChanged: {
if (!focus) {
showTextfieldMemberName = false
idTextfieldAddMember.text = ""
}
}
EnterKey.onClicked: {
if (text.length > 0) {
if (editItemIndex === -1) { // -1=add new member
listModel_activeProjectMembersTEMP.append({ member_name : idTextfieldAddMember.text,
member_isBeneficiary : true,
member_isPayer : false,
})
} else { // "edit" existing member name by index in listmodel
listModel_activeProjectMembersTEMP.setProperty(editItemIndex, "member_name", idTextfieldAddMember.text)
}
}
focus = false
showTextfieldMemberName = false
}
Label {
anchors.top: parent.bottom
anchors.topMargin: Theme.paddingSmall
font.pixelSize: Theme.fontSizeExtraSmall
text: qsTr("name")
}
}
}
Item {
width: parent.width
height: Theme.itemSizeSmall
}
Row {
width: parent.width
visible: (modeEdit === "edit") && (listModel_allProjects.count > 0) // make sure there is always one project left once created
spacing: Theme.paddingLarge
leftPadding: Theme.paddingLarge
Button {
id: idLabelDeleteProject
width: parent.width /3 - parent.spacing * 1.5
height: idColumnAddProject.height
color: Theme.errorColor
text: (Number(activeProjectID_unixtime) != Number(timeStamp)) ? qsTr("Delete") : qsTr("Reset")
onClicked: {
if (Number(activeProjectID_unixtime) != Number(timeStamp)) { // if it is not the active project, delete it
remorse_deleteProject.execute(qsTr("Delete this project?"), function() {
deleteProject()
})
} else { // if active project
remorse_deleteProject.execute(qsTr("Clear all transactions?"), function() {
clearProject()
})
}
}
}
Button {
id: idLabelBackupProject
width: parent.width /3 - parent.spacing * 1.5
height: idColumnAddProject.height
text: qsTr("Backup")
onClicked: {
// hide() // ToDo: maybe close this popup?
pageStack.push(idFolderPickerPage)
}
}
Button {
id: idLabelRestoreProject
width: parent.width /3 - parent.spacing
height: idColumnAddProject.height
text: qsTr("Restore")
onClicked: {
// hide() // ToDo: maybe close this popup?
pageStack.push(idFilePickerPage)
}
}
}
Item {
width: parent.width
height: Theme.itemSizeSmall / 2
}
}
}
}
}
Icon {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: idBackgroundRectProject.anchors.topMargin / 2 - height/2
source: "image://theme/icon-splus-cancel?"
opacity: 1
}
function notify( color, upperMargin, modeEditNew, indexCurrentProject ) {
// color settings
if (color && (typeof(color) != "undefined")) {
idBackgroundRectProject.color = color
}
else {
idBackgroundRectProject.color = Theme.rgba(Theme.highlightBackgroundColor, 0.9)
}
// position settings
if (upperMargin && (typeof(upperMargin) != "undefined")) {
idBackgroundRectProject.anchors.topMargin = upperMargin
}
else {
idBackgroundRectProject.anchors.topMargin = 0
}
// adjust input fields
tempProjectListIndex = indexCurrentProject
listModel_activeProjectMembersTEMP.clear()
if (modeEditNew === "new") {
modeEdit = "new"
timeStamp = ((new Date).getTime()).toString()
idTextfieldCurrencyProject.text = recentlyUsedCurrency
idTextfieldProjectname.text = ""
idTextfieldProjectname.forceActiveFocus()
} else { // edit project mode
modeEdit = "edit"
var tempProjectMembersArray = []
tempProjectMembersArray = (listModel_allProjects.get(idComboboxProject.currentIndex).project_members).split(" ||| ")
for (var i = 0; i < tempProjectMembersArray.length ; i++) {
listModel_activeProjectMembersTEMP.append({ member_name : tempProjectMembersArray[i],
})
}
timeStamp = (listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp).toString()
idTextfieldProjectname.text = listModel_allProjects.get(idComboboxProject.currentIndex).project_name
idTextfieldCurrencyProject.text = listModel_allProjects.get(idComboboxProject.currentIndex).project_base_currency
}
// all members are beneficiaries
// show banner overlay
popup.opacity = 1.0
}
function hide() {
idTextfieldProjectname.focus = false
idTextfieldCurrencyProject.focus = false
// clear all fields
idTextfieldProjectname.text = ""
idTextfieldCurrencyProject.text = recentlyUsedCurrency
// make invisible
popup.opacity = 0.0
}
function addProjectDB () {
var project_id_timestamp = timeStamp
var project_name = idTextfieldProjectname.text
var project_members = ""
var project_recent_payer_boolarray = ""
var project_recent_beneficiaries_boolarray = ""
var project_base_currency = idTextfieldCurrencyProject.text
for (var i = 0; i < listModel_activeProjectMembersTEMP.count ; i++) {
project_members += " ||| " + listModel_activeProjectMembersTEMP.get(i).member_name
project_recent_beneficiaries_boolarray += " ||| " + "true"
// recent_payer can only be one person, initially this will be the first entry of the list
if (i===0) {
project_recent_payer_boolarray += " ||| " + "true"
} else {
project_recent_payer_boolarray += " ||| " + "false"
}
}
// remove first occurance of " ||| " to later be able to split that string
project_members = project_members.replace(" ||| ", "")
project_recent_payer_boolarray = project_recent_payer_boolarray.replace(" ||| ", "")
project_recent_beneficiaries_boolarray = project_recent_beneficiaries_boolarray.replace(" ||| ", "")
if (modeEdit === "new") {
// store in DB and list for new project
storageItem.setProject( project_id_timestamp, project_name, project_members, project_recent_payer_boolarray, project_recent_beneficiaries_boolarray, project_base_currency )
listModel_allProjects.append({ project_id_timestamp : Number(project_id_timestamp),
project_name : project_name,
project_members : project_members,
project_recent_payer_boolarray : project_recent_payer_boolarray,
project_recent_beneficiaries_boolarray : project_recent_beneficiaries_boolarray,
project_base_currency : project_base_currency,
})
// if this is the first project, auto set it as active project
if (listModel_allProjects.count === 1) { // auto-sets as currently active project, if this project is very first one
storageItem.setSettings("activeProjectID_unixtime", Number(project_id_timestamp) )
activeProjectID_unixtime = Number(project_id_timestamp)
loadActiveProjectInfos_FromDB( Number(project_id_timestamp) )
//console.log("auto set as active project ID = " + activeProjectID_unixtime)
}
} else { // modeEdit === "edit
// update DB and list for existing project
storageItem.updateProject( project_id_timestamp, project_name, project_members, project_recent_payer_boolarray, project_recent_beneficiaries_boolarray, project_base_currency )
for (var j = 0; j < listModel_allProjects.count ; j++) {
if (listModel_allProjects.get(j).project_id_timestamp === Number(project_id_timestamp)) {
//console.log("updated entry at: id_" + project_id_timestamp)
listModel_allProjects.set(j, { "project_name" : project_name,
"project_members" : project_members,
"project_recent_payer_boolarray" : project_recent_payer_boolarray,
"project_recent_beneficiaries_boolarray" : project_recent_beneficiaries_boolarray,
"project_base_currency" : project_base_currency
})
}
}
}
updateEvenWhenCanceled = true
hide()
}
function deleteProject() {
updateEvenWhenCanceled = true
storageItem.deleteProject(timeStamp)
listModel_allProjects.remove(tempProjectListIndex)
// set active project to reasonable one
if (idComboboxProject.currentIndex != 0) {
idComboboxProject.currentIndex = idComboboxProject.currentIndex -1
}
//console.log("auto set after deleting ID = " + Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp))
loadActiveProjectInfos_FromDB(Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp)) // needed to make sure there is no expense or member list still active
hide()
}
function clearProject() {
updateEvenWhenCanceled = true
listModel_activeProjectExpenses.clear()
storageItem.removeFullTable( "table_" + activeProjectID_unixtime.toString() )
loadActiveProjectInfos_FromDB(Number(listModel_allProjects.get(idComboboxProject.currentIndex).project_id_timestamp)) // needed to make sure there is no expense or member list still active
hide()
}
}

556
qml/pages/CalcPage.qml Normal file
View file

@ -0,0 +1,556 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
Page {
id: pageResults
allowedOrientations: Orientation.All
onVisibleChanged: {
if (visible === true) { // entered page
listModel_activeProjectResults.clear()
listModel_activeProjectResultsSettlement.clear()
generateExchangeRateListFromExpenses()
addExchangeRates_listmodelActiveProjectExpenses()
}
}
property real totalSpendingAmount_baseCurrency : 0
property int counterShownExchangeRates : 0
property string toShareString : ""
ListModel {
id: listModel_activeProjectResultsSettlement
}
SilicaFlickable{
id: listView
anchors.fill: parent
contentHeight: resultsColumn.height // tell overall height
PullDownMenu {
MenuItem {
text: qsTr("Share detailed")
onClicked: {
createShareString("detailed")
}
}
MenuItem {
text: qsTr("Share compact")
onClicked: {
createShareString("compact")
}
}
}
Column {
id: resultsColumn
x: Theme.paddingLarge
width: parent.width - 2*x
Column {
width: parent.width
topPadding: Theme.paddingLarge
Label {
width: parent.width
horizontalAlignment: Text.AlignRight
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeLarge
text: qsTr("Results")
}
Label {
width: parent.width
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
color: Theme.highlightColor
text: activeProjectName + " [" + activeProjectCurrency + "]"
}
}
Item {
width: parent.width
height: Theme.paddingLarge + Theme.paddingSmall
}
Label {
visible: listModel_exchangeRates.count > 1 // main exchange rate is also counted in
width: parent.width
bottomPadding: Theme.paddingMedium
font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryColor
text: qsTr("EXCHANGE RATES")
}
Repeater {
id: idRepeaterExchangeRates
width: parent.width
model: listModel_exchangeRates
delegate: Column {
id: idColumnContent
visible: expense_currency !== activeProjectCurrency
width: parent.width
Row {
width: parent.width
Label {
width: parent.width / 3
height: parent.height
topPadding: Theme.paddingSmall
font.pixelSize: Theme.fontSizeSmall
wrapMode: Text.WordWrap
text: (exchangeRateMode === 1)
? (new Date(Number(date_time)).toLocaleString(Qt.locale(), "dd.MM.yy" + " - " + "hh:mm")) //"ddd dd.MM.yyyy - hh:mm"
: (qsTr("constant"))
}
Label {
width: parent.width / 3
font.pixelSize: Theme.fontSizeSmall
wrapMode: Text.WordWrap
topPadding: Theme.paddingSmall
horizontalAlignment: Text.AlignRight
text: Number(1).toFixed(2) + " " + "<b>" +expense_currency + "</b>" + " = "
}
TextField {
id: idTextfieldExchangeRate
width: parent.width / 3
textRightMargin: idLabelProjectCurrency.width
textLeftMargin: 0
inputMethodHints: Qt.ImhFormattedNumbersOnly //use "Qt.ImhDigitsOnly" for INT
font.pixelSize: Theme.fontSizeSmall
horizontalAlignment: Text.AlignRight
text: Number(exchange_rate) //.toFixed(decimalPlacesCurrencyRate)
EnterKey.onClicked: {
focus = false
}
onFocusChanged: {
if (text.length < 1) {
text = Number(exchange_rate) //.toFixed(decimalPlacesCurrencyRate)
} else {
text = text.replace(",", ".")
text = Number(text) //.toFixed(decimalPlacesCurrencyRate)
}
if (focus) {
selectAll()
} else { // unfocus
exchange_rate = Number(text)
storeExchangeRate_DB(expense_currency, exchange_rate)
addExchangeRates_listmodelActiveProjectExpenses()
}
}
Label {
id: idLabelProjectCurrency
anchors.left: parent.right
font.pixelSize: Theme.fontSizeSmall
//color: Theme.highlightColor
color: Theme.secondaryColor
text: "<b>" + base_currency + "</b>"
}
}
}
}
}
Item {
width: parent.width
height: Theme.paddingLarge
}
Label {
width: parent.width
bottomPadding: Theme.paddingMedium
font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryColor
text: qsTr("SPENDING OVERVIEW")
}
Row {
visible: listModel_activeProjectResults.count > 0
width: parent.width
Label {
width: parent.width / 4
color: Theme.secondaryColor
font.italic: true
font.pixelSize: Theme.fontSizeSmall
text: qsTr("name")
}
Label {
width: parent.width / 4
color: Theme.secondaryColor
font.italic: true
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
text: qsTr("payments")
}
Label {
width: parent.width / 4
color: Theme.secondaryColor
font.italic: true
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
text: qsTr("benefits")
}
Label {
width: parent.width / 4
color: Theme.secondaryColor
font.italic: true
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
text: qsTr("saldo")
}
}
Repeater {
id: idRepeaterExpensesOverview
model: listModel_activeProjectResults
delegate: Row {
width: parent.width
Label {
width: parent.width / 4
font.pixelSize: Theme.fontSizeSmall
text: beneficiary_name
}
Label {
width: parent.width / 4
font.pixelSize: Theme.fontSizeSmall
horizontalAlignment: Text.AlignRight
text: expense_sum.toFixed(2)
}
Label {
width: parent.width / 4
font.pixelSize: Theme.fontSizeSmall
horizontalAlignment: Text.AlignRight
text: beneficiary_sum.toFixed(2)
}
Label {
width: parent.width / 4
font.pixelSize: Theme.fontSizeSmall
horizontalAlignment: Text.AlignRight
color: (( Number(expense_sum) - Number(beneficiary_sum) ).toFixed(2) < 0) ? Theme.errorColor : "green"
text: ( Number(expense_sum) - Number(beneficiary_sum) ).toFixed(2)
}
}
}
Row {
visible: listModel_activeProjectResults.count > 0
width: parent.width
topPadding: Theme.paddingMedium
Label {
width: parent.width / 4 * 2
color: Theme.secondaryColor
font.bold: true
font.overline: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
text: totalSpendingAmount_baseCurrency.toFixed(2)
//text: activeProjectCurrency + " " + totalSpendingAmount_baseCurrency.toFixed(2)
}
Item {
width: parent.width / 4
height: 1
}
Label {
width: parent.width / 4
color: Theme.secondaryColor
font.bold: true
font.overline: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
text: activeProjectCurrency
}
}
Item {
width: parent.width
height: Theme.paddingLarge * 2.5
}
Label {
width: parent.width
bottomPadding: Theme.paddingMedium
font.pixelSize: Theme.fontSizeSmall
color: Theme.secondaryColor
text: qsTr("SETTLEMENT SUGGESTION")
}
Repeater {
id: idRepeaterSettlementSuggestions
visible: model.count > 0
model: listModel_activeProjectResultsSettlement
delegate: Row {
width: parent.width
Label {
width: parent.width
font.pixelSize: Theme.fontSizeSmall
wrapMode: Text.WordWrap
text: (settling_sum.toFixed(2) >= 0) ?
("<b>" + from_name + "</b> " + qsTr("owes") + " <b>" + to_name + "</b> " + qsTr("the sum of") + " <b>" + settling_sum.toFixed(2) + "</b> <i>" + activeProjectCurrency + "</i>.")
: ("<b>" + to_name + "</b> " + qsTr("owes") + " <b>" + from_name + "</b> " + qsTr("the sum of") + " <b>" + (-settling_sum).toFixed(2) + "</b> <i>" + activeProjectCurrency + "</i>.")
}
}
}
Item {
width: parent.width
height: Theme.itemSizeSmall
}
}
}
function generateExchangeRateListFromExpenses() {
listModel_exchangeRates.clear()
for (var i = 0; i < listModel_activeProjectExpenses.count; i++) {
if (exchangeRateMode === 0 ) { // collective currency
// check if available and where occuring
var currencyAlreadySet = false
var addCurrencyIndex = 0
for (var j = 0; j < listModel_exchangeRates.count; j++) {
if ( listModel_activeProjectExpenses.get(i).expense_currency === listModel_exchangeRates.get(j).expense_currency ) {
currencyAlreadySet = true
addCurrencyIndex = j
}
}
// if it has not been set yet, add to exchange rate list
if (currencyAlreadySet === false) {
var tempStoredExchangeRateDB = Number(storageItem.getExchangeRate(listModel_activeProjectExpenses.get(i).expense_currency, 1))
listModel_exchangeRates.append({ expense_currency : listModel_activeProjectExpenses.get(i).expense_currency,
base_currency : activeProjectCurrency,
exchange_rate : tempStoredExchangeRateDB,
date_time : Number(0)
})
}
} else { // exchangeRateMode === 1 ... individual transactions
var tempExpenseCurrency = listModel_activeProjectExpenses.get(i).expense_currency
if (tempExpenseCurrency !== activeProjectCurrency) {
tempStoredExchangeRateDB = Number(storageItem.getExchangeRate(listModel_activeProjectExpenses.get(i).expense_currency, 1))
listModel_exchangeRates.append({ expense_currency : listModel_activeProjectExpenses.get(i).expense_currency,
base_currency : activeProjectCurrency,
exchange_rate : tempStoredExchangeRateDB,
date_time : Number(listModel_activeProjectExpenses.get(i).date_time)
})
}
}
}
}
function storeExchangeRate_DB (exchange_rate_currency, exchange_rate_value) {
var tempOccurences = Number(storageItem.countExchangeRateOccurances(exchange_rate_currency, 0))
if (tempOccurences === 0) { // add new entry
storageItem.setExchangeRate(exchange_rate_currency, exchange_rate_value)
} else { // update existing entry
storageItem.updateExchangeRate(exchange_rate_currency, exchange_rate_value)
}
}
function addExchangeRates_listmodelActiveProjectExpenses() {
for (var i = 0; i < listModel_activeProjectExpenses.count; i++) {
var tempExpenseCurrency = listModel_activeProjectExpenses.get(i).expense_currency
var tempExpenseDateTime = Number(listModel_activeProjectExpenses.get(i).date_time)
if (tempExpenseCurrency === activeProjectCurrency) { // paid in project specific currency
listModel_activeProjectExpenses.set(i, {"exchange_rate": Number(1)})
} else { // if paid in different currency
if ( exchangeRateMode === 0 ) { // ... collective currency
for (var k = 0; k < listModel_exchangeRates.count; k++) {
if (listModel_exchangeRates.get(k).expense_currency === tempExpenseCurrency) {
listModel_activeProjectExpenses.set(i, {"exchange_rate": Number(listModel_exchangeRates.get(k).exchange_rate)})
//console.log(Number(listModel_exchangeRates.get(k).exchange_rate))
}
}
} else { // ... individual transactions
for (k = 0; k < listModel_exchangeRates.count; k++) {
if ((listModel_exchangeRates.get(k).expense_currency === tempExpenseCurrency) && (Number(listModel_exchangeRates.get(k).date_time) === tempExpenseDateTime)) {
listModel_activeProjectExpenses.set(i, {"exchange_rate": Number(listModel_exchangeRates.get(k).exchange_rate)})
}
}
}
}
}
calculateResults_members()
}
function calculateResults_members () {
listModel_activeProjectResults.clear()
listModel_activeProjectResultsSettlement.clear()
totalSpendingAmount_baseCurrency = 0
// sum up benefits per member
for (var i = 0; i < listModel_activeProjectExpenses.count; i++) {
var tempBeneficiariesArray = (listModel_activeProjectExpenses.get(i).expense_members).split(" ||| ")
for (var j = 0; j < tempBeneficiariesArray.length; j++) {
var tempExpense_inBaseCurrency_perBeneficiariy = ((Number(listModel_activeProjectExpenses.get(i).expense_sum) / tempBeneficiariesArray.length) * Number(listModel_activeProjectExpenses.get(i).exchange_rate))
// cycle through results list, check if beneficiary is already available and where
var tempBeneficiaryAvailable = false
var tempBeneficiaryIndex = 0
for (var k = 0; k < listModel_activeProjectResults.count; k++ ) {
if (listModel_activeProjectResults.get(k).beneficiary_name === tempBeneficiariesArray[j]) {
tempBeneficiaryAvailable = true
tempBeneficiaryIndex = k
}
}
// if not available, add him to results list (only on first occurance)
if (tempBeneficiaryAvailable === false) {
//console.log("first entry for: " + tempBeneficiariesArray[j])
//console.log(tempExpense_inBaseCurrency_perBeneficiariy + " " + activeProjectCurrency)
listModel_activeProjectResults.append({ beneficiary_name : tempBeneficiariesArray[j],
beneficiary_sum : Number(tempExpense_inBaseCurrency_perBeneficiariy),
base_currency : activeProjectCurrency,
expense_sum : 0 // this info gets added later
})
} else { // otherwise add his share to the existing list
var tempBeneficiarySum = Number(listModel_activeProjectResults.get(tempBeneficiaryIndex).beneficiary_sum) + Number(tempExpense_inBaseCurrency_perBeneficiariy)
listModel_activeProjectResults.set(tempBeneficiaryIndex, { "beneficiary_sum": Number(tempBeneficiarySum) })
//console.log("latest benefitSum for " + tempBeneficiariesArray[j] + " is: " + tempBeneficiarySum)
}
//console.log(tempBeneficiariesArray[j])
//console.log(tempExpense_inBaseCurrency_perBeneficiariy)
}
}
// once listModel_activeProjectResults is created, go over expenses again and add payments
for ( i = 0; i < listModel_activeProjectExpenses.count; i++) {
var tempExpensePayer = listModel_activeProjectExpenses.get(i).expense_payer
var tempExpense_inBaseCurrency = Number(listModel_activeProjectExpenses.get(i).expense_sum) * Number(listModel_activeProjectExpenses.get(i).exchange_rate)
totalSpendingAmount_baseCurrency += tempExpense_inBaseCurrency
//console.log(tempExpensePayer)
//console.log(tempExpense_inBaseCurrency)
// check if payer is already in results list as benefitter
var tempPayerAvailable = false
var tempPayerIndex = 0
for (var l = 0; l < listModel_activeProjectResults.count; l++ ) {
if (listModel_activeProjectResults.get(l).beneficiary_name === tempExpensePayer) {
tempPayerAvailable = true
tempPayerIndex = l
}
}
// if not available, add him to results list (only on first occurance)
if (tempPayerAvailable === false) {
//console.log("first entry for payer: " + tempExpensePayer + " = " + tempExpense_inBaseCurrency + " " + activeProjectCurrency)
//console.log("seems he paid but never benefits from anything")
listModel_activeProjectResults.append({ beneficiary_name : tempExpensePayer,
beneficiary_sum : 0,
base_currency : activeProjectCurrency,
expense_sum : Number(tempExpense_inBaseCurrency),
})
} else { // otherwise add his share to the existing list
var tempPayerSum = Number(listModel_activeProjectResults.get(tempPayerIndex).expense_sum) + Number(tempExpense_inBaseCurrency)
listModel_activeProjectResults.set(tempPayerIndex, { "expense_sum": Number(tempPayerSum) })
//console.log("latest payerSum for " + tempExpensePayer + " is: " + tempPayerSum)
}
}
// sort results list according
listModel_activeProjectResults.quick_sort("desc") // payer with highest expense on top
// apply a (n-1) algorithm to settle expenses (how much each person ows to whom)
var outstandingNamesArray = []
var outstandingSumsArray = []
var totalSum = 0
// iter backwards to get smallest amounts first, since listModel_activeProjectResults starts with highest expenses
for (var m = listModel_activeProjectResults.count-1; m >= 0; m--) {
outstandingNamesArray.push(listModel_activeProjectResults.get(m).beneficiary_name)
outstandingSumsArray.push( Number(listModel_activeProjectResults.get(m).expense_sum) - Number(listModel_activeProjectResults.get(m).beneficiary_sum) )
totalSum += ( Number(listModel_activeProjectResults.get(m).expense_sum) - Number(listModel_activeProjectResults.get(m).beneficiary_sum) )
}
//console.log(outstandingNamesArray)
//console.log(outstandingSumsArray)
//console.log(totalSum)
function splitPayments() {
const mean = totalSum / outstandingNamesArray.length
var sortedValuesPaid = []
for (var n = 0; n < outstandingSumsArray.length; n++) {
sortedValuesPaid.push(outstandingSumsArray[n] - mean)
}
var i = 0
var j = outstandingNamesArray.length - 1
var debt
while (i < j) {
debt = Math.min(-(sortedValuesPaid[i]), sortedValuesPaid[j])
sortedValuesPaid[i] += debt
sortedValuesPaid[j] -= debt
listModel_activeProjectResultsSettlement.append({ from_name : outstandingNamesArray[i],
to_name : outstandingNamesArray[j],
settling_sum : Number(debt),
})
//console.log(outstandingNamesArray[i] + " owes " + outstandingNamesArray[j] + " the sum of " + debt )
if (sortedValuesPaid[i] === 0) { i++ }
if (sortedValuesPaid[j] === 0) { j-- }
}
}
splitPayments()
}
function createShareString (detailGrade) {
// create a shareable string
toShareString = "\n" + qsTr("Project:") + " " + activeProjectName
+ "\n" + qsTr("Total expenses") + " = "
+ totalSpendingAmount_baseCurrency.toFixed(2) + " " + activeProjectCurrency
+ "\n" + "\n"
for (var i = 0; i < listModel_activeProjectResults.count; i++) {
toShareString += listModel_activeProjectResults.get(i).beneficiary_name + ":"
+ "\n" + qsTr("payed") + " " + listModel_activeProjectResults.get(i).expense_sum.toFixed(2) + " " + activeProjectCurrency
+ "\n" + qsTr("received") + " " + listModel_activeProjectResults.get(i).beneficiary_sum.toFixed(2) + " " + activeProjectCurrency
+ "\n" + qsTr("saldo") + " " + (Number(listModel_activeProjectResults.get(i).expense_sum) - Number(listModel_activeProjectResults.get(i).beneficiary_sum)).toFixed(2) + " " + activeProjectCurrency
+ "\n" + "\n"
}
toShareString += qsTr("Settlement suggestion:")
+ "\n"
for (i = 0; i < listModel_activeProjectResultsSettlement.count; i++) {
if ( Number(listModel_activeProjectResultsSettlement.get(i).settling_sum).toFixed(2) >= 0 ) {
toShareString += listModel_activeProjectResultsSettlement.get(i).from_name
+ " " + qsTr("owes") + " "
+ listModel_activeProjectResultsSettlement.get(i).to_name
+ " " + qsTr("the sum of") + " " + Number(listModel_activeProjectResultsSettlement.get(i).settling_sum).toFixed(2) + " " + activeProjectCurrency
+ "." + "\n"
} else { // amount < 0
toShareString += listModel_activeProjectResultsSettlement.get(i).to_name
+ " " + qsTr("owes") + " "
+ listModel_activeProjectResultsSettlement.get(i).from_name
+ " " + qsTr("the sum of") + " " + (-1*Number(listModel_activeProjectResultsSettlement.get(i).settling_sum).toFixed(2)) + " " + activeProjectCurrency
+ "." + "\n"
}
}
//console.log(toShareString)
// add details if necessary
if (detailGrade === "detailed") {
toShareString += "\n" + "\n" + "\n"
+ qsTr("Detailed Spendings:")
+ "\n" + "\n"
for ( i = 0; i < listModel_activeProjectExpenses.count; i++) {
if (sortOrderExpenses === 0) { // 0=descending, 1=ascending
var tmpEntryNumber = (listModel_activeProjectExpenses.count - i)
} else {
tmpEntryNumber = (i+1)
}
toShareString += qsTr("Expense #") + tmpEntryNumber
+ "\n" + qsTr("date:") + " " + new Date(Number(listModel_activeProjectExpenses.get(i).date_time)).toLocaleString(Qt.locale(), "ddd dd.MM.yyyy - hh:mm")
+ "\n" + qsTr("payer:") + " " + listModel_activeProjectExpenses.get(i).expense_payer
+ "\n" + qsTr("item:") + " " + listModel_activeProjectExpenses.get(i).expense_name
+ "\n" + qsTr("price:") + " " + Number(listModel_activeProjectExpenses.get(i).expense_sum).toFixed(2) + " " + listModel_activeProjectExpenses.get(i).expense_currency
+ "\n" + qsTr("beneficiaries:") + " " + listModel_activeProjectExpenses.get(i).expense_members.split(" ||| ").join(", ") // .replace(" ||| ", ", ")
+ "\n" + qsTr("info:") + " " + listModel_activeProjectExpenses.get(i).expense_info
+ "\n" + "\n"
}
}
console.log(toShareString)
// send this string
Clipboard.text = toShareString
shareActionText.mimeType = "text/plain" // "text/*" or "application/text"
shareActionText.resources = [{
"data": toShareString,
"name": activeProjectName,
} ]
shareActionText.trigger()
}
}

445
qml/pages/FirstPage.qml Normal file
View file

@ -0,0 +1,445 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
import Sailfish.Share 1.0 // ToDo: instead of copy to clipboard, send to app
Page {
id: page
allowedOrientations: Orientation.All
// project specific global variables, loaded when activating a new project
property double activeProjectID_unixtime : Number(storageItem.getSettings("activeProjectID_unixtime", 0))
property int activeProjectID_listIndex
property string activeProjectName
property string activeProjectCurrency : "EUR"
// program specific global variables
property int sortOrderExpenses : Number(storageItem.getSettings("sortOrderExpensesIndex", 0)) // 0=descending, 1=ascending
property int exchangeRateMode : Number(storageItem.getSettings("exchangeRateModeIndex", 0)) // 0=collective, 1=individual
property int interativeScrollbarMode : Number(storageItem.getSettings("interativeScrollbarMode", 0)) // 0=standard, 1=interactive
property string recentlyUsedCurrency : storageItem.getSettings("recentlyUsedCurrency", activeProjectCurrency)
// navigation specific blocking
property bool updateEvenWhenCanceled : false
property bool delegateMenuOpen : false
// autostart
Component.onCompleted: {
generateAllProjectsList_FromDB()
loadActiveProjectInfos_FromDB(activeProjectID_unixtime)
}
// other items, components and pages
ListModel {
id: listModel_allProjects
}
ListModel {
id: listModel_activeProjectMembers
}
ListModel {
id: listModel_activeProjectExpenses
property string sortColumnName: "date_time" //"id_unixtime_created"
function swap(a,b) {
if (a<b) {
move(a,b,1);
move (b-1,a,1);
}
else if (a>b) {
move(b,a,1);
move (a-1,b,1);
}
}
function partition(begin, end, pivot) {
var piv=get(pivot)[sortColumnName];
swap(pivot, end-1);
var store=begin;
var ix;
for(ix=begin; ix<end-1; ++ix) {
if (sortOrderExpenses === 1){
if(get(ix)[sortColumnName] < piv) {
swap(store,ix);
++store;
}
} else { // (sortOrderExpenses === 0)
if(get(ix)[sortColumnName] > piv) {
swap(store,ix);
++store;
}
}
}
swap(end-1, store);
return store;
}
function qsort(begin, end) {
if(end-1>begin) {
var pivot=begin+Math.floor(Math.random()*(end-begin));
pivot=partition( begin, end, pivot);
qsort(begin, pivot);
qsort(pivot+1, end);
}
}
function quick_sort() {
qsort(0,count)
}
onCountChanged: {
quick_sort()
}
}
ListModel {
id: listModel_exchangeRates
}
ListModel {
id: listModel_activeProjectResults
property string sortColumnName : "expense_sum"
property string sortOrderResults : "desc" //"asc"
function swap(a,b) {
if (a<b) {
move(a,b,1);
move (b-1,a,1);
}
else if (a>b) {
move(b,a,1);
move (a-1,b,1);
}
}
function partition(begin, end, pivot) {
var piv=get(pivot)[sortColumnName];
swap(pivot, end-1);
var store=begin;
var ix;
for(ix=begin; ix<end-1; ++ix) {
if (sortOrderResults === "asc"){
if(get(ix)[sortColumnName] < piv) {
swap(store,ix);
++store;
}
} else { // (sortOrderResults === "desc")
if(get(ix)[sortColumnName] > piv) {
swap(store,ix);
++store;
}
}
}
swap(end-1, store);
return store;
}
function qsort(begin, end) {
if(end-1>begin) {
var pivot=begin+Math.floor(Math.random()*(end-begin));
pivot=partition( begin, end, pivot);
qsort(begin, pivot);
qsort(pivot+1, end);
}
}
function quick_sort(orderDirection) {
sortOrderResults = orderDirection
qsort(0,count)
}
}
SettingsPage {
id: settingsPage
}
CalcPage {
id: calcPage
}
BannerAddExpense {
id: bannerAddExpense
}
Component {
id: datePickerComponent
DatePickerDialog {}
}
Component {
id: timePickerComponent
TimePickerDialog {}
}
ShareAction {
id: shareActionText
}
// main page, current project
SilicaListView {
id: idSilicaListView
anchors.fill: parent
header: Row {
width: (interativeScrollbarMode === 0) ? (parent.width) : ((isPortrait) ? (parent.width) : (parent.width - Theme.paddingLarge*2))
visible: activeProjectID_unixtime !== 0
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
Item {
id: idLeftSpacer
width: Theme.paddingSmall + Theme.paddingMedium
height: 1
}
Column {
id: idHeaderInfoColumn
width: parent.width - idLeftSpacer.width
bottomPadding: Theme.paddingSmall
Label {
x: Theme.paddingMedium
width: parent.width - 2*x
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeLarge
color: Theme.highlightColor
wrapMode: Text.WordWrap
text: qsTr("Expenses")
}
Label {
x: Theme.paddingMedium
width: parent.width - 2*x
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
color: Theme.highlightColor
wrapMode: Text.WordWrap
//text: "ID_" + activeProjectID_unixtime + " [" + activeProjectCurrency + "]"
text: activeProjectName + " [" + activeProjectCurrency + "]"
}
}
}
footer: Item {
width: parent.width
height: Theme.itemSizeSmall
}
spacing: Theme.paddingMedium
quickScroll: (interativeScrollbarMode === 0) ? (true) : (false)
VerticalScrollDecorator {
enabled: (interativeScrollbarMode === 0) ? (true) : (false)
visible: enabled
}
ScrollBar {
id: idScrollBarDate
enabled: (interativeScrollbarMode === 0) ? false : true
labelVisible: true
topPadding: (isPortrait) ? (Theme.itemSizeLarge + Theme.paddingLarge) : (0)
bottomPadding: (isPortrait) ? Theme.itemSizeSmall : 0
labelModelTag: "date_time"
visible: (interativeScrollbarMode === 0) ? (false) : (idPulldownMenu.active === false) && (delegateMenuOpen === false)
}
PullDownMenu {
id: idPulldownMenu
quickSelect: true
MenuItem {
text: qsTr("Settings")
onClicked: pageStack.push(settingsPage)
}
MenuItem {
text: qsTr("Calculate")
enabled: activeProjectID_unixtime !== 0
onClicked: pageStack.animatorPush(calcPage)
}
MenuItem {
text: qsTr("Add")
enabled: activeProjectID_unixtime !== 0
onClicked: bannerAddExpense.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "new", activeProjectID_unixtime, 0 )
}
}
ViewPlaceholder {
enabled: activeProjectID_unixtime === 0 // listModel_allProjects.count === 0
text: qsTr("Create new project.")
hintText: qsTr("Nothing loaded yet.")
}
model: listModel_activeProjectExpenses
delegate: ListItem {
id: idListItem
contentHeight: idListLabelsQML.height
contentWidth: (interativeScrollbarMode === 0) ? (parent.width) : (parent.width - idScrollBarDate.width)
//contentWidth: (idSilicaListView.visibleArea.heightRatio < 1.0 && idPulldownMenu.active === false) ? (parent.width - idScrollBarDate.width) : (parent.width)
menu: ContextMenu {
id: idContextMenu
MenuItem {
text: qsTr("Edit")
onClicked: {
bannerAddExpense.notify( Theme.rgba(Theme.highlightDimmerColor, 1), Theme.itemSizeLarge, "edit", activeProjectID_unixtime, id_unixtime_created )
}
}
MenuItem {
text: qsTr("Remove")
onClicked: {
idRemorseDelete.execute(idListItem, qsTr("Remove entry?"), function() {
storageItem.deleteExpense(activeProjectID_unixtime, id_unixtime_created )
listModel_activeProjectExpenses.remove(index)
} )
}
}
}
onMenuOpenChanged: {
// set variable to disable scrollBar visibility
if (menuOpen === true) {
delegateMenuOpen = true
} else {
delegateMenuOpen = false
}
}
RemorseItem {
id: idRemorseDelete
}
Row {
width: parent.width
Rectangle {
width: Theme.paddingSmall
height: idListLabelsQML.height
color: Theme.rgba(Theme.highlightBackgroundColor, 0.4)
}
Item {
width: Theme.paddingMedium
height: 1
}
Column {
id: idListLabelsQML
width: parent.width - Theme.paddingSmall - 2*Theme.paddingMedium
Row {
width: parent.width
Label {
width: parent.width/3*2- Theme.paddingLarge/2
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall
text: new Date(Number(date_time)).toLocaleString(Qt.locale(), "ddd dd.MM.yyyy - hh:mm")
}
Item {
width: Theme.paddingLarge
height: 1
}
Label {
width: parent.width/3 - Theme.paddingLarge/2
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall
horizontalAlignment: Text.AlignRight
text: expense_payer
}
}
Row {
width: parent.width
Label {
width: parent.width/3*2 - Theme.paddingLarge/2
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeSmall
text: expense_name
}
Item {
width: Theme.paddingLarge
height: 1
}
Label {
width: parent.width/3 - Theme.paddingLarge/2
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignRight
font.pixelSize: Theme.fontSizeSmall
text: Number(expense_sum).toFixed(2) + " " + expense_currency.toString()
}
}
Label {
id: idLabelExpenseInfo
visible: idLabelExpenseInfo.text.length > 0
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeTiny
color: Theme.secondaryColor
text: expense_info
}
Label {
id: idLabelBeneficiaries
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: Theme.fontSizeTiny
color: Theme.secondaryColor
text: qsTr("group:") + " " + expense_members.split(" ||| ").join(", ")
}
Item {
width: parent.width
height: Theme.paddingSmall
}
}
}
}
} // end SilicaListView
function generateAllProjectsList_FromDB() {
listModel_allProjects.clear()
var allProjectsOverview = storageItem.getAllProjects("none")
//console.log(allProjectsOverview)
if (allProjectsOverview !== "none") {
for (var i = 0; i < allProjectsOverview.length ; i++) {
listModel_allProjects.append({
project_id_timestamp : Number(allProjectsOverview[i][0]),
project_name : allProjectsOverview[i][1],
project_members : allProjectsOverview[i][2],
project_recent_payer_boolarray : allProjectsOverview[i][3],
project_recent_beneficiaries_boolarray : allProjectsOverview[i][4],
project_base_currency : allProjectsOverview[i][5],
})
}
}
}
function loadActiveProjectInfos_FromDB(activeProjectID_unixtime) {
//console.log( "loading project: " + Number(activeProjectID_unixtime) )
listModel_activeProjectMembers.clear()
listModel_activeProjectExpenses.clear()
for (var j = 0; j < listModel_allProjects.count ; j++) {
//console.log("in listmodel: " + Number(listModel_allProjects.get(j).project_id_timestamp) )
// only use active project infos
if ( Number(listModel_allProjects.get(j).project_id_timestamp) === Number(activeProjectID_unixtime) ) {
// find active project name and currency
activeProjectName = listModel_allProjects.get(j).project_name
activeProjectID_listIndex = j
activeProjectCurrency = listModel_allProjects.get(j).project_base_currency
// generate active project members list
var activeProjectMembersArray = (listModel_allProjects.get(j).project_members).split(" ||| ")
var activeProjectRecentPayerBoolArray = (listModel_allProjects.get(j).project_recent_payer_boolarray).split(" ||| ")
var activeProjectRecentBeneficiariesBoolArray = (listModel_allProjects.get(j).project_recent_beneficiaries_boolarray).split(" ||| ")
for (var i = 0; i < activeProjectMembersArray.length ; i++) {
listModel_activeProjectMembers.append({
member_name : activeProjectMembersArray[i],
member_isBeneficiary : activeProjectRecentBeneficiariesBoolArray[i],
member_isPayer : activeProjectRecentPayerBoolArray[i],
})
}
// generate active project expenses list
var currentProjectEntries = storageItem.getAllExpenses( activeProjectID_unixtime, "none")
if (currentProjectEntries !== "none") {
for (i = 0; i < currentProjectEntries.length ; i++) {
listModel_activeProjectExpenses.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],
})
}
}
}
}
}
}

106
qml/pages/ScrollBar.qml Executable file
View file

@ -0,0 +1,106 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
Rectangle {
// inherited values
property var flickable: parent
property bool labelVisible : true
property string labelModelTag : ""
property real topPadding : 0
property real bottomPadding : 0
property color handleColor : Theme.highlightBackgroundColor
// own values but overwritable
property int scrollToNumber : 0
property string showLabel : ""
property int listcountMax : flickable.count // (flickable.count !== undefined) ? flickable.count : 0 // dirty bugfix for scrollBar wron position on empty lists
property real roundCornersRadius : Theme.paddingLarge
id: scrollbar
anchors.top: parent.top
anchors.topMargin: topPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: bottomPadding
anchors.right: parent.right
width: roundCornersRadius * 2
radius: width / 2
color: Theme.rgba(Theme.primaryColor, 0.075)
visible: flickable.visibleArea.heightRatio < 1.0
Rectangle {
id: handle
width: parent.width
height: Math.max(roundCornersRadius*2, flickable.visibleArea.heightRatio * scrollbar.height)
color: handleColor
opacity: (clicker.drag.active ) ? 1 : 0.4
radius: width / 2
}
Rectangle {
id: scrollLabelBackground
visible: labelVisible
anchors.verticalCenter: handle.verticalCenter
anchors.right: handle.left
anchors.rightMargin: Theme.paddingLarge * 1.5
width: scrollLabel.width + height*1.2
height: roundCornersRadius * 2
radius: roundCornersRadius
color: handleColor
opacity: (clicker.drag.active ) ? 1 : 0
Label {
id: scrollLabel
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
//text: showLabel
text: new Date(Number(showLabel)).toLocaleString(Qt.locale(), "dd.MM.yyyy - hh:mm")
}
Rectangle {
anchors.left: parent.right
anchors.leftMargin: -roundCornersRadius
anchors.verticalCenter: parent.verticalCenter
width: parent.anchors.rightMargin + 2*roundCornersRadius
height: Theme.paddingSmall/3*2
color: parent.color
}
}
Binding {
// jump handle to where the currently visible part of flickable ist
target: handle
property: "y"
value: (flickable.visibleArea.yPosition) * (scrollbar.height - ( handle.height - (flickable.visibleArea.heightRatio * scrollbar.height)))
when: !clicker.pressed
}
MouseArea {
id: clicker
anchors.fill: parent
anchors.rightMargin: -Theme.paddingSmall
anchors.leftMargin: -Theme.paddingSmall
preventStealing: true
drag {
target: handle
minimumY: 0
maximumY: scrollbar.height - handle.height
axis: Drag.YAxis
}
onMouseYChanged: {
//flickable.contentY = handle.y / drag.maximumY * (flickable.contentHeight - flickable.height)
scrollToNumber = Math.ceil(handle.y / drag.maximumY * listcountMax)
if (scrollToNumber < 0) {
scrollToNumber = 0
} else if (scrollToNumber >= listcountMax) {
scrollToNumber = listcountMax -1 // because index starts at 0, while count() starts at 1
}
showLabel = (flickable.model.get(scrollToNumber)[labelModelTag] !== undefined) ? flickable.model.get(scrollToNumber)[labelModelTag] : "" //flickable.model.get(scrollToNumber)[labelModelTag]
flickable.positionViewAtIndex( scrollToNumber, ListView.Center)
//flickable.contentY = handle.y / drag.maximumY * (flickable.contentHeight - flickable.height)
}
onClicked: {
//flickable.contentY = mouse.y / scrollbar.height * (flickable.contentHeight - flickable.height)
scrollToNumber = Math.ceil(mouse.y / scrollbar.height * listcountMax)
showLabel = (flickable.model.get(scrollToNumber)[labelModelTag] !== undefined) ? flickable.model.get(scrollToNumber)[labelModelTag] : "" //flickable.model.get(scrollToNumber)[labelModelTag]
flickable.positionViewAtIndex( scrollToNumber, ListView.Center )
}
//onReleased: console.log("released")
}
}

456
qml/pages/SettingsPage.qml Normal file
View file

@ -0,0 +1,456 @@
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)
}
}
}

View file

@ -0,0 +1,18 @@
# Rename this file as harbour-expenditure.changes to include changelog
# entries in your RPM file.
#
# Add new changelog entries following the format below.
# Add newest entries to the top of the list.
# Separate entries from eachother with a blank line.
#
# Alternatively, if your changelog is automatically generated (e.g. with
# the git-change-log command provided with Sailfish OS SDK), create a
# harbour-expenditure.changes.run script to let mb2 run the required commands for you.
# * date Author's Name <author's email> version-release
# - Summary of changes
* Sun Apr 13 2014 Jack Tar <jack.tar@example.com> 0.0.1-1
- Scrubbed the deck
- Hoisted the sails

View file

@ -0,0 +1,25 @@
#!/bin/bash
#
# Rename this file as harbour-expenditure.changes.run to let mb2 automatically
# generate changelog from well formatted Git commit messages and tag
# annotations.
git-change-log
# Here are some basic examples how to change from the default behavior. Run
# git-change-log --help inside the Sailfish OS SDK chroot or build engine to
# learn all the options git-change-log accepts.
# Use a subset of tags
#git-change-log --tags refs/tags/my-prefix/*
# Group entries by minor revision, suppress headlines for patch-level revisions
#git-change-log --dense '/[0-9]+.[0-9+$'
# Trim very old changes
#git-change-log --since 2014-04-01
#echo '[ Some changelog entries trimmed for brevity ]'
# Use the subjects (first lines) of tag annotations when no entry would be
# included for a revision otherwise
#git-change-log --auto-add-annotations

View file

@ -0,0 +1,67 @@
#
# Do NOT Edit the Auto-generated Part!
# Generated by: spectacle version 0.32
#
Name: harbour-expenditure
# >> macros
# << macros
Summary: Expenditure
Version: 0.2
Release: 1
Group: Qt/Qt
License: LICENSE
URL: http://example.org/
Source0: %{name}-%{version}.tar.bz2
Source100: harbour-expenditure.yaml
Requires: sailfishsilica-qt5 >= 0.10.9
BuildRequires: pkgconfig(sailfishapp) >= 1.0.2
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: desktop-file-utils
%description
Short description of my Sailfish OS Application
%prep
%setup -q -n %{name}-%{version}
# >> setup
# << setup
%build
# >> build pre
# << build pre
%qmake5
make %{?_smp_mflags}
# >> build post
# << build post
%install
rm -rf %{buildroot}
# >> install pre
# << install pre
%qmake5_install
# >> install post
# << install post
desktop-file-install --delete-original \
--dir %{buildroot}%{_datadir}/applications \
%{buildroot}%{_datadir}/applications/*.desktop
%files
%defattr(-,root,root,-)
%{_bindir}/%{name}
%{_datadir}/%{name}
%{_datadir}/applications/%{name}.desktop
%{_datadir}/icons/hicolor/*/apps/%{name}.png
# >> files
# << files

View file

@ -0,0 +1,42 @@
Name: harbour-expenditure
Summary: Expenditure
Version: 0.2
Release: 1
# The contents of the Group field should be one of the groups listed here:
# https://github.com/mer-tools/spectacle/blob/master/data/GROUPS
Group: Qt/Qt
URL: http://example.org/
License: LICENSE
# This must be generated before uploading a package to a remote build service.
# Usually this line does not need to be modified.
Sources:
- '%{name}-%{version}.tar.bz2'
Description: |
Short description of my Sailfish OS Application
Builder: qmake5
# This section specifies build dependencies that are resolved using pkgconfig.
# This is the preferred way of specifying build dependencies for your package.
PkgConfigBR:
- sailfishapp >= 1.0.2
- Qt5Core
- Qt5Qml
- Qt5Quick
# Build dependencies without a pkgconfig setup can be listed here
# PkgBR:
# - package-needed-to-build
# Runtime dependencies which are not automatically detected
Requires:
- sailfishsilica-qt5 >= 0.10.9
# All installed files
Files:
- '%{_bindir}/%{name}'
- '%{_datadir}/%{name}'
- '%{_datadir}/applications/%{name}.desktop'
- '%{_datadir}/icons/hicolor/*/apps/%{name}.png'
# For more information about yaml and what's supported in Sailfish OS
# build system, please see https://wiki.merproject.org/wiki/Spectacle

60
src/File.cpp Normal file
View file

@ -0,0 +1,60 @@
#include "File.h"
#include <QFile>
#include <QDebug>
File::File()
{
connect(this, SIGNAL(sourceChanged()), this, SLOT(readFile()));
}
void File::setSource(const QString &source)
{
m_source = source;
emit sourceChanged();
}
QString File::source() const
{
return m_source;
}
void File::setText(const QString &text)
{
QFile file(m_source);
if (!file.open(QIODevice::WriteOnly)) {
m_text = "";
qDebug() << "Error:" << m_source << "open failed! File not yet created.";
}
else {
qint64 byte = file.write(text.toUtf8());
if (byte != text.toUtf8().size()) {
m_text = text.toUtf8().left(byte);
qDebug() << "Error:" << m_source << "open failed!";
}
else {
m_text = text;
}
file.close();
}
emit textChanged();
}
void File::readFile()
{
QFile file(m_source);
if (!file.open(QIODevice::ReadOnly)) {
m_text = "";
qDebug() << "Error:" << m_source << "open failed!";
}
m_text = file.readAll();
emit textChanged();
}
QString File::text() const
{
return m_text;
}

33
src/File.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef QT_HUB_FILE_H
#define QT_HUB_FILE_H
#include <QObject>
class File : public QObject
{
Q_OBJECT
public:
File();
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
QString source() const;
void setSource(const QString &source);
QString text() const;
void setText(const QString &text);
signals:
void sourceChanged();
void textChanged();
private slots:
void readFile();
private:
QString m_source;
QString m_text;
};
#endif //FILE_H

View file

@ -0,0 +1,25 @@
#ifdef QT_QML_DEBUG
#include <QtQuick>
#endif
#include <sailfishapp.h>
#include <QtQuick> // FileIO
#include "File.h" // FileIO
int main(int argc, char *argv[])
{
// SailfishApp::main() will display "qml/harbour-expenditure.qml", if you need more
// control over initialization, you can use:
//
// - SailfishApp::application(int, char *[]) to get the QGuiApplication *
// - SailfishApp::createView() to get a new QQuickView * instance
// - SailfishApp::pathTo(QString) to get a QUrl to a resource file
// - SailfishApp::pathToMainQml() to get a QUrl to the main QML file
//
// To display the view, call "show()" (will show fullscreen on device).
qmlRegisterType<File>("FileIO", 1, 0, "TextFileIO"); // FileIO
return SailfishApp::main(argc, argv);
}

View file

@ -0,0 +1,430 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>AboutPage</name>
<message>
<source>Expenditure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Copyright © 2022 Tobias Planitzer</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>License: GPL v3</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expenditure is a tool to track and split bills, project or trip expenses in multiple currencies among groups.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Thanksgiving, feedback and support is always welcome.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Troubleshooting:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>In case of any database error tap 10x on the word &apos;Settings&apos; for cleanup options.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Contact:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BannerAddExpense</name>
<message>
<source>expense</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>info</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>payment by</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Save</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>price</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>currency</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>beneficiary</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BannerAddProject</name>
<message>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>base currency</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Members</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>rename</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Save</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete this project?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Clear all transactions?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>create</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Restore</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CalcPage</name>
<message>
<source>payments</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>saldo</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>owes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>EXCHANGE RATES</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SPENDING OVERVIEW</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SETTLEMENT SUGGESTION</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>the sum of</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total expenses</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>payed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>received</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Settlement suggestion:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>item:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>payer:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>price:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>info:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>beneficiaries:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Detailed Spendings:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expense #</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>date:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Share detailed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Share compact</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>benefits</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Results</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>constant</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FirstPage</name>
<message>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Create new project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Nothing loaded yet.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Calculate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>group:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expenses</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove entry?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>
<message>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sorting</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>descending</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ascending</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Exchange rate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete stored exchange rates?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete stored projects?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete stored settings?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>per currency (constant)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>per transaction (dates)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Restore backup file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup to</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Scrollbar</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>interactive (beta)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>normal</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup successful</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Replace</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Merge</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File saved to:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Validity check failed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>This backup file does not seem to be created by Expenditure:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup successfully restored.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project expenses have been overwritten by backup-file expenses.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project expenses have been merged with backup-file expenses.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Restore backup - choose action</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Replace deletes all former project-expenses and uses those from backup-file instead.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Merge keeps former project-expenses and adds those from backup-file which are not yet on the list.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File info: This backup was created by a different project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File info: This backup was created by the original project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>exchange rates</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database cleanup - requires restart:</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View file

@ -0,0 +1,430 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>AboutPage</name>
<message>
<source>Expenditure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Copyright © 2022 Tobias Planitzer</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>License: GPL v3</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expenditure is a tool to track and split bills, project or trip expenses in multiple currencies among groups.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Thanksgiving, feedback and support is always welcome.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Troubleshooting:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>In case of any database error tap 10x on the word &apos;Settings&apos; for cleanup options.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Contact:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BannerAddExpense</name>
<message>
<source>expense</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>info</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>payment by</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Save</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>price</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>currency</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>beneficiary</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BannerAddProject</name>
<message>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>base currency</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Members</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>rename</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Save</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete this project?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Clear all transactions?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>create</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reset</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Restore</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CalcPage</name>
<message>
<source>payments</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>saldo</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>owes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>EXCHANGE RATES</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SPENDING OVERVIEW</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>SETTLEMENT SUGGESTION</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>the sum of</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Total expenses</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>payed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>received</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Settlement suggestion:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>item:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>payer:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>price:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>info:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>beneficiaries:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Detailed Spendings:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expense #</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>date:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Share detailed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Share compact</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>name</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>benefits</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Results</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>constant</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FirstPage</name>
<message>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Create new project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Nothing loaded yet.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Calculate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>group:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Expenses</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove entry?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>
<message>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sorting</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>descending</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>ascending</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Exchange rate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete stored exchange rates?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete stored projects?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Delete stored settings?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>per currency (constant)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>per transaction (dates)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Restore backup file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup to</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Scrollbar</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>interactive (beta)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>normal</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup successful</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Replace</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Merge</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File saved to:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Validity check failed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>This backup file does not seem to be created by Expenditure:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Backup successfully restored.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project expenses have been overwritten by backup-file expenses.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Project expenses have been merged with backup-file expenses.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Restore backup - choose action</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Replace deletes all former project-expenses and uses those from backup-file instead.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Merge keeps former project-expenses and adds those from backup-file which are not yet on the list.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File info: This backup was created by a different project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File info: This backup was created by the original project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>exchange rates</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>projects</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database cleanup - requires restart:</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>