Initial commit: Add financial viewer
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="17" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
18
.idea/deploymentTargetSelector.xml
generated
Normal file
18
.idea/deploymentTargetSelector.xml
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetSelector">
|
||||||
|
<selectionStates>
|
||||||
|
<SelectionState runConfigName="app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2024-10-27T16:19:47.507081400Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=e05f0e1e" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
</selectionStates>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
19
.idea/gradle.xml
generated
Normal file
19
.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="1.9.0" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/migrations.xml
generated
Normal file
10
.idea/migrations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/misc.xml
generated
Normal file
9
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
329
.idea/other.xml
generated
Normal file
329
.idea/other.xml
generated
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="direct_access_persist.xml">
|
||||||
|
<option name="deviceSelectionList">
|
||||||
|
<list>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="27" />
|
||||||
|
<option name="brand" value="DOCOMO" />
|
||||||
|
<option name="codename" value="F01L" />
|
||||||
|
<option name="id" value="F01L" />
|
||||||
|
<option name="manufacturer" value="FUJITSU" />
|
||||||
|
<option name="name" value="F-01L" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1280" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="28" />
|
||||||
|
<option name="brand" value="DOCOMO" />
|
||||||
|
<option name="codename" value="SH-01L" />
|
||||||
|
<option name="id" value="SH-01L" />
|
||||||
|
<option name="manufacturer" value="SHARP" />
|
||||||
|
<option name="name" value="AQUOS sense2 SH-01L" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2160" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="Lenovo" />
|
||||||
|
<option name="codename" value="TB370FU" />
|
||||||
|
<option name="id" value="TB370FU" />
|
||||||
|
<option name="manufacturer" value="Lenovo" />
|
||||||
|
<option name="name" value="Tab P12" />
|
||||||
|
<option name="screenDensity" value="340" />
|
||||||
|
<option name="screenX" value="1840" />
|
||||||
|
<option name="screenY" value="2944" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="31" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="a51" />
|
||||||
|
<option name="id" value="a51" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy A51" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="akita" />
|
||||||
|
<option name="id" value="akita" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="b0q" />
|
||||||
|
<option name="id" value="b0q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S22 Ultra" />
|
||||||
|
<option name="screenDensity" value="600" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3088" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="32" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="bluejay" />
|
||||||
|
<option name="id" value="bluejay" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 6a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="caiman" />
|
||||||
|
<option name="id" value="caiman" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="960" />
|
||||||
|
<option name="screenY" value="2142" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="comet" />
|
||||||
|
<option name="id" value="comet" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro Fold" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="2076" />
|
||||||
|
<option name="screenY" value="2152" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="29" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="crownqlteue" />
|
||||||
|
<option name="id" value="crownqlteue" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Note9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2220" />
|
||||||
|
<option name="screenY" value="1080" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="dm3q" />
|
||||||
|
<option name="id" value="dm3q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S23 Ultra" />
|
||||||
|
<option name="screenDensity" value="600" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3088" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="e1q" />
|
||||||
|
<option name="id" value="e1q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S24" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix_camera" />
|
||||||
|
<option name="id" value="felix_camera" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gts8uwifi" />
|
||||||
|
<option name="id" value="gts8uwifi" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Tab S8 Ultra" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="1848" />
|
||||||
|
<option name="screenY" value="2960" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="husky" />
|
||||||
|
<option name="id" value="husky" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8 Pro" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="java" />
|
||||||
|
<option name="id" value="java" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="G20" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1600" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="komodo" />
|
||||||
|
<option name="id" value="komodo" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro XL" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="lynx" />
|
||||||
|
<option name="id" value="lynx" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 7a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="31" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="oriole" />
|
||||||
|
<option name="id" value="oriole" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 6" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="panther" />
|
||||||
|
<option name="id" value="panther" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 7" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="q5q" />
|
||||||
|
<option name="id" value="q5q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Z Fold5" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1812" />
|
||||||
|
<option name="screenY" value="2176" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="q6q" />
|
||||||
|
<option name="id" value="q6q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="SM-F956B" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1856" />
|
||||||
|
<option name="screenY" value="2160" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="r11" />
|
||||||
|
<option name="id" value="r11" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Watch" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="384" />
|
||||||
|
<option name="screenY" value="384" />
|
||||||
|
<option name="type" value="WEAR_OS" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="redfin" />
|
||||||
|
<option name="id" value="redfin" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 5" />
|
||||||
|
<option name="screenDensity" value="440" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="shiba" />
|
||||||
|
<option name="id" value="shiba" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tangorpro" />
|
||||||
|
<option name="id" value="tangorpro" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Tablet" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="1600" />
|
||||||
|
<option name="screenY" value="2560" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tokay" />
|
||||||
|
<option name="id" value="tokay" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2424" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
54
app/build.gradle.kts
Normal file
54
app/build.gradle.kts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.jetbrains.kotlin.android)
|
||||||
|
id("com.google.devtools.ksp") version "1.9.0-1.0.13"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.financialviewer"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.financialviewer"
|
||||||
|
minSdk = 31
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.appcompat)
|
||||||
|
implementation(libs.material)
|
||||||
|
implementation(libs.androidx.activity)
|
||||||
|
implementation(libs.androidx.constraintlayout)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
implementation (libs.androidx.work.runtime.ktx)
|
||||||
|
ksp (libs.androidx.room.compiler)
|
||||||
|
implementation (libs.androidx.room.ktx)
|
||||||
|
implementation (libs.smbj)
|
||||||
|
implementation(libs.opencsv)
|
||||||
|
}
|
||||||
21
app/proguard-rules.pro
vendored
Normal file
21
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.financialviewer
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.financialviewer", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/src/main/AndroidManifest.xml
Normal file
49
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".App"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.FinancialViewer" >
|
||||||
|
<activity
|
||||||
|
android:name=".ui.MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Login Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.LoginActivity" />
|
||||||
|
<!-- Home Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.HomeActivity" />
|
||||||
|
<!-- Fixed Costs Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.FixedCostsActivity" />
|
||||||
|
<!-- Fixed Cost Details Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.FixedCostDetailsActivity" />
|
||||||
|
<!-- Loan Overview Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.LoanOverviewActivity" />
|
||||||
|
<!-- Loan Details Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.LoanDetailsActivity" />
|
||||||
|
<!-- Bank Transactions Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.BankTransactionsActivity" />
|
||||||
|
<!-- Bank Transaction Details Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.BankTransactionDetailsActivity" />
|
||||||
|
<!-- Monthly Summary Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.MonthlySummaryActivity" />
|
||||||
|
<!-- Yearly Summary Activity -->
|
||||||
|
<activity android:name="com.financialviewer.ui.YearlySummaryActivity" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
BIN
app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
25
app/src/main/java/com/financialviewer/App.kt
Normal file
25
app/src/main/java/com/financialviewer/App.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package com.financialviewer
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import com.financialviewer.work.StartupWorker
|
||||||
|
|
||||||
|
class App : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
val reset = false
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
this.deleteDatabase("app_database")
|
||||||
|
val appPreferences = SharedPreferencesHelper(this)
|
||||||
|
appPreferences.clearAllSharedPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
val workRequest = OneTimeWorkRequest.Builder(StartupWorker::class.java).build()
|
||||||
|
WorkManager.getInstance(this).enqueue(workRequest)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.financialviewer.constants
|
||||||
|
|
||||||
|
val COUNTERPARTY_MAP = mapOf(
|
||||||
|
"1118 Reembroden 31-43" to Pair("Hausmann Hausverwaltung", "物业费"),
|
||||||
|
"AMERICAN EXPRESS EUROPE" to Pair("American Express", "信用卡帐"),
|
||||||
|
"Commerzbank AG" to Pair("Commerzbank AG", "房贷"),
|
||||||
|
"DKV KRANKENVERS. AG" to Pair("DKV Zahnzusatzversicherung", "牙医险"),
|
||||||
|
"Einkommensteuer" to Pair("Steuerkasse Hamburg", "补交所得税"),
|
||||||
|
"Grundsteuer" to Pair("Steuerkasse Hamburg", "房产税"),
|
||||||
|
"HAFEN UND." to Pair("HHLA", "工资"),
|
||||||
|
"Haftpflichtvers. SV95925167" to Pair("ERGO Haftpflichtversicherung", "第三责任险"),
|
||||||
|
"Hauptzollamt Hamburg" to Pair("Hauptzollamt Hamburg", "车税"),
|
||||||
|
"Hausratvers." to Pair("ERGO Hausratversicherung", "家财险"),
|
||||||
|
"KFZ-Versicherung" to Pair("HuK KFZ-Versicherung", "车险"),
|
||||||
|
"MIETE" to Pair("Reembroden 35 Miete", "房租"),
|
||||||
|
"Rechtsschutzversicherung" to Pair("ERGO Rechtsschutzversicherung", "法律险"),
|
||||||
|
"Rundfunk" to Pair("Rundfunk ARD,ZDF,DRadio", "广播电视费"),
|
||||||
|
"Stadtreinigung" to Pair("Stadtreinigung Hamburg", "垃圾费"),
|
||||||
|
"Stromnetz" to Pair("Stromnetz Hamburg", "电网"),
|
||||||
|
"Telefonica Germany GmbH" to Pair("O2 Germany", "手机费"),
|
||||||
|
"Unfallversicherung" to Pair("ERGO Unfallversicherung", "意外险"),
|
||||||
|
"VATTENFALL EUROPE SALES" to Pair("Vattenfall Europe", "电费"),
|
||||||
|
"Vodafone Deutschland GmbH" to Pair("Vodafone Kabel", "网费"),
|
||||||
|
"Vorsorge LV AG" to Pair("ERGO Lebensversicherung", "人寿险"),
|
||||||
|
"Wasserwerke" to Pair("Hamburger Wasserwerke", "水费"),
|
||||||
|
"Wohngebaeudevers." to Pair("ERGO Wohngebaeudeversicherung", "房屋险")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
64
app/src/main/java/com/financialviewer/constants/Category.kt
Normal file
64
app/src/main/java/com/financialviewer/constants/Category.kt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package com.financialviewer.constants
|
||||||
|
|
||||||
|
import com.financialviewer.data.CategoryItem
|
||||||
|
|
||||||
|
val CATEGORY_LIST = listOf(
|
||||||
|
CategoryItem("1.收入", listOf("工资", "房租", "电网")),
|
||||||
|
CategoryItem("2.税", listOf("补交所得税", "房产税", "车税")),
|
||||||
|
CategoryItem("3.生活成本", listOf("房贷", "电费", "水费", "网费", "手机费", "垃圾费")),
|
||||||
|
CategoryItem("4.保险", listOf("第三责任险", "人寿险", "意外险", "车险", "房屋险", "家财险", "牙医险", "法律险")),
|
||||||
|
CategoryItem("5.其他", listOf("物业费", "信用卡帐", "广播电视费")),
|
||||||
|
)
|
||||||
|
|
||||||
|
val CATEGORY_FIXED_COST_LIST = listOf(
|
||||||
|
CategoryItem("税", listOf("房产税", "车税")),
|
||||||
|
CategoryItem("生活成本", listOf("房贷", "电费", "水费", "网费", "手机费", "垃圾费")),
|
||||||
|
CategoryItem("保险", listOf("第三责任险", "人寿险", "意外险", "车险", "房屋险", "家财险", "牙医险", "法律险")),
|
||||||
|
CategoryItem("其他", listOf("物业费", "广播电视费"))
|
||||||
|
)
|
||||||
|
|
||||||
|
const val MONTHLY = "monthly"
|
||||||
|
const val QUARTERLY = "quarterly"
|
||||||
|
const val YEARLY = "yearly"
|
||||||
|
|
||||||
|
val FIXED_COST_TYPE = mapOf(
|
||||||
|
"房贷" to MONTHLY,
|
||||||
|
"电费" to MONTHLY,
|
||||||
|
"水费" to MONTHLY,
|
||||||
|
"网费" to MONTHLY,
|
||||||
|
"手机费" to MONTHLY,
|
||||||
|
"牙医险" to MONTHLY,
|
||||||
|
"物业费" to MONTHLY,
|
||||||
|
"房产税" to QUARTERLY,
|
||||||
|
"垃圾费" to QUARTERLY,
|
||||||
|
"广播电视费" to QUARTERLY,
|
||||||
|
"车税" to YEARLY,
|
||||||
|
"第三责任险" to YEARLY,
|
||||||
|
"人寿险" to YEARLY,
|
||||||
|
"意外险" to YEARLY,
|
||||||
|
"车险" to YEARLY,
|
||||||
|
"房屋险" to YEARLY,
|
||||||
|
"家财险" to YEARLY,
|
||||||
|
"法律险" to YEARLY
|
||||||
|
)
|
||||||
|
|
||||||
|
val FIXED_COST_COUNT = mapOf(
|
||||||
|
"房贷" to 4,
|
||||||
|
"电费" to 2,
|
||||||
|
"水费" to 1,
|
||||||
|
"网费" to 1,
|
||||||
|
"手机费" to 1,
|
||||||
|
"牙医险" to 1,
|
||||||
|
"物业费" to 2,
|
||||||
|
"房产税" to 3,
|
||||||
|
"垃圾费" to 1,
|
||||||
|
"广播电视费" to 1,
|
||||||
|
"车税" to 1,
|
||||||
|
"第三责任险" to 1,
|
||||||
|
"人寿险" to 2,
|
||||||
|
"意外险" to 1,
|
||||||
|
"车险" to 1,
|
||||||
|
"房屋险" to 1,
|
||||||
|
"家财险" to 1,
|
||||||
|
"法律险" to 1
|
||||||
|
)
|
||||||
88
app/src/main/java/com/financialviewer/constants/Comdirect.kt
Normal file
88
app/src/main/java/com/financialviewer/constants/Comdirect.kt
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package com.financialviewer.constants
|
||||||
|
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.utils.convertStringToDouble
|
||||||
|
|
||||||
|
val COMDIRECT_LIST = listOf(
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect1",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-310,83"),
|
||||||
|
"房贷",
|
||||||
|
"Commerzbank AG",
|
||||||
|
"SEPA-LASTSCHRIFT VON Commerzbank AG Comdirect1 IBAN DE79200400000504264302 BIC COBADEFF",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect2",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-852,5"),
|
||||||
|
"房贷",
|
||||||
|
"Commerzbank AG",
|
||||||
|
"SEPA-LASTSCHRIFT VON Commerzbank AG Comdirect2 IBAN DE09200400000504264301 BIC COBADEFF",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect3",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-756,25"),
|
||||||
|
"房贷",
|
||||||
|
"Commerzbank AG",
|
||||||
|
"SEPA-LASTSCHRIFT VON Commerzbank AG Comdirect3 IBAN DE09200400000504264301 BIC COBADEFF",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect4",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("9"),
|
||||||
|
"电网",
|
||||||
|
"Stromnetz Hamburg",
|
||||||
|
"ÜBERWEISUNG VON Stromnetz Hamburg GmbH Comdirect4 IBAN DE17500500000090085242 BIC HELADEFFXXX",
|
||||||
|
false),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect5",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-65,44"),
|
||||||
|
"电费",
|
||||||
|
"Vattenfall Europe",
|
||||||
|
"SEPA-LASTSCHRIFT VON VATTENFALL EUROPE SALES Comdirect5 IBAN DE93500500000090085135 BIC HELADEFF",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect6",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-73,44"),
|
||||||
|
"电费",
|
||||||
|
"Vattenfall Europe",
|
||||||
|
"SEPA-LASTSCHRIFT VON VATTENFALL EUROPE SALES Comdirect6 IBAN DE93500500000090085135 BIC HELADEFF",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect7",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-776,87"),
|
||||||
|
"车险",
|
||||||
|
"ERGO KFZ-Versicherung",
|
||||||
|
"SEPA-LASTSCHRIFT VON ERGO VERSICHERUNG AG Comdirect7 IBAN DE67302201900004471610 BIC HYVEDEMM KFZ-Versicherung",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect8",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-20"),
|
||||||
|
"物业费",
|
||||||
|
"Hausmann Hausverwaltung",
|
||||||
|
"SEPA-LASTSCHRIFT VON WEG 1118 Reembroden 31-43 Hausmann Hausv Comdirect8 IBAN DE77217919060000773573 BIC GENODEFF",
|
||||||
|
true),
|
||||||
|
BankTransaction(
|
||||||
|
"Comdirect9",
|
||||||
|
"ComdirectAccount",
|
||||||
|
"2024-01-02",
|
||||||
|
convertStringToDouble("-325"),
|
||||||
|
"物业费",
|
||||||
|
"Hausmann Hausverwaltung",
|
||||||
|
"SEPA-LASTSCHRIFT VON WEG 1118 Reembroden 31-43 Hausmann Hausv Comdirec9 IBAN DE77217919060000773573 BIC GENODEFF",
|
||||||
|
true)
|
||||||
|
)
|
||||||
19
app/src/main/java/com/financialviewer/constants/Const.kt
Normal file
19
app/src/main/java/com/financialviewer/constants/Const.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.financialviewer.constants
|
||||||
|
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
|
||||||
|
const val YEAR = 2024
|
||||||
|
const val FIXED_COST_TIP = "该转账为固定支出: 是"
|
||||||
|
const val NON_FIXED_COST_TIP = "该转账为固定支出: 否"
|
||||||
|
|
||||||
|
val BLANK_BANK_TRANSACTION =
|
||||||
|
BankTransaction(
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0.0,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
false
|
||||||
|
)
|
||||||
49
app/src/main/java/com/financialviewer/constants/LoanList.kt
Normal file
49
app/src/main/java/com/financialviewer/constants/LoanList.kt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package com.financialviewer.constants
|
||||||
|
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
val lastUpdate = Calendar.getInstance().timeInMillis
|
||||||
|
|
||||||
|
val LoanList = mutableListOf(
|
||||||
|
Loan(
|
||||||
|
"R1",
|
||||||
|
"Reembroden 35 (1)",
|
||||||
|
"150.000,00",
|
||||||
|
"3,66%",
|
||||||
|
"1.168,59",
|
||||||
|
"30.08.2027",
|
||||||
|
"38.669,79",
|
||||||
|
lastUpdate
|
||||||
|
),
|
||||||
|
Loan(
|
||||||
|
"R2",
|
||||||
|
"Reembroden 35 (2)",
|
||||||
|
"100.000,00",
|
||||||
|
"1,74%",
|
||||||
|
"310,83",
|
||||||
|
"30.11.2033",
|
||||||
|
"91.365,58",
|
||||||
|
lastUpdate
|
||||||
|
),
|
||||||
|
Loan(
|
||||||
|
"V1",
|
||||||
|
"Voßstraat 24 (1)",
|
||||||
|
"300.000,00",
|
||||||
|
"1,42%",
|
||||||
|
"852,50",
|
||||||
|
"30.11.2028",
|
||||||
|
"274.266,59",
|
||||||
|
lastUpdate
|
||||||
|
),
|
||||||
|
Loan(
|
||||||
|
"V2",
|
||||||
|
"Voßstraat 24 (2)",
|
||||||
|
"250.000,00",
|
||||||
|
"1,64%",
|
||||||
|
"756,25",
|
||||||
|
"30.11.2033",
|
||||||
|
"220.338,01",
|
||||||
|
lastUpdate
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.financialviewer.data
|
||||||
|
|
||||||
|
data class CategoryItem (
|
||||||
|
val title: String,
|
||||||
|
val subList: List<String>
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.financialviewer.data
|
||||||
|
|
||||||
|
data class LoanMonthlyDetails(
|
||||||
|
val month: String,
|
||||||
|
val interest: String,
|
||||||
|
val principal: String,
|
||||||
|
val remaining: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.financialviewer.data
|
||||||
|
|
||||||
|
data class YearMonthDay(
|
||||||
|
val year: Int,
|
||||||
|
val month: Int,
|
||||||
|
val day: Int
|
||||||
|
)
|
||||||
37
app/src/main/java/com/financialviewer/db/AppDatabase.kt
Normal file
37
app/src/main/java/com/financialviewer/db/AppDatabase.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database(entities = [
|
||||||
|
Loan::class,
|
||||||
|
FixedCost::class,
|
||||||
|
BankTransaction::class,
|
||||||
|
YearlySummary::class,
|
||||||
|
MonthlySummary::class], version = 1)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun loanDao(): LoanDao
|
||||||
|
abstract fun fixedCostDao(): FixedCostDao
|
||||||
|
abstract fun bankTransactionDao(): BankTransactionDao
|
||||||
|
abstract fun yearlySummaryDao(): YearlySummaryDao
|
||||||
|
abstract fun monthlySummaryDao(): MonthlySummaryDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: AppDatabase? = null
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): AppDatabase {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val instance = Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
"app_database"
|
||||||
|
).build()
|
||||||
|
INSTANCE = instance
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/src/main/java/com/financialviewer/db/BankTransaction.kt
Normal file
17
app/src/main/java/com/financialviewer/db/BankTransaction.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "bank_transactions")
|
||||||
|
data class BankTransaction(
|
||||||
|
@PrimaryKey val refId: String,
|
||||||
|
@ColumnInfo(name = "account") val account: String,
|
||||||
|
@ColumnInfo(name = "date") val date: String,
|
||||||
|
@ColumnInfo(name = "amount") val amount: Double,
|
||||||
|
@ColumnInfo(name = "category") val category: String,
|
||||||
|
@ColumnInfo(name = "counterparty") val counterparty: String,
|
||||||
|
@ColumnInfo(name = "reference") val reference: String,
|
||||||
|
@ColumnInfo(name = "is_fixed_cost") var isFixedCost: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface BankTransactionDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertBankTransaction(bankTransaction: BankTransaction)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateBankTransaction(bankTransaction: BankTransaction)
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM bank_Transactions")
|
||||||
|
suspend fun getRowCount(): Int
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions WHERE refId = :refId")
|
||||||
|
suspend fun getBankTransactionById(refId: String): BankTransaction?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions ORDER BY date DESC LIMIT 1")
|
||||||
|
suspend fun getLastBankTransaction(): BankTransaction?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions WHERE date LIKE :year || '-%' ORDER BY date DESC")
|
||||||
|
suspend fun getAllBankTransactionsDesc(year: String): List<BankTransaction>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions WHERE date LIKE :year || '-%' AND category = :category ORDER BY date DESC")
|
||||||
|
suspend fun getBankTransactionsByCategoryDesc(year: String, category: String): List<BankTransaction>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions WHERE date LIKE :year || '-%' AND category IN (:categoryList) ORDER BY date DESC")
|
||||||
|
suspend fun getBankTransactionsByCategoryListDesc(year: String, categoryList: MutableList<String>): List<BankTransaction>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions WHERE date LIKE :year || '-' || :month || '%' ORDER BY date DESC")
|
||||||
|
suspend fun getBankTransactionsByMonthDesc(year: String, month: String): List<BankTransaction>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM bank_Transactions WHERE date LIKE :year || '-' || :month || '%' AND category IN (:categoryList) ORDER BY date DESC")
|
||||||
|
suspend fun getBankTransactionsByMonthAndCategoryListDesc(year: String, month: String, categoryList: MutableList<String>): List<BankTransaction>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteBankTransaction(bankTransaction: BankTransaction)
|
||||||
|
}
|
||||||
13
app/src/main/java/com/financialviewer/db/FixedCost.kt
Normal file
13
app/src/main/java/com/financialviewer/db/FixedCost.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "fixed_costs")
|
||||||
|
data class FixedCost(
|
||||||
|
@PrimaryKey val category: String,
|
||||||
|
@ColumnInfo(name = "amount") var amount: Double,
|
||||||
|
@ColumnInfo(name = "type") val type: String,
|
||||||
|
@ColumnInfo(name = "refIds") var refIds: String
|
||||||
|
)
|
||||||
33
app/src/main/java/com/financialviewer/db/FixedCostDao.kt
Normal file
33
app/src/main/java/com/financialviewer/db/FixedCostDao.kt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface FixedCostDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertFixedCost(fixedCost: FixedCost)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateFixedCost(fixedCost: FixedCost)
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM fixed_costs")
|
||||||
|
suspend fun getRowCount(): Int
|
||||||
|
|
||||||
|
@Query("SELECT * FROM fixed_costs WHERE category = :category")
|
||||||
|
suspend fun getFixedCostByCategory(category: String): FixedCost?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM fixed_costs")
|
||||||
|
suspend fun getAllFixedCosts(): List<FixedCost>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM fixed_costs WHERE type = :type")
|
||||||
|
suspend fun getAllFixedCostsByType(type: String): List<FixedCost>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteFixedCost(fixedCost: FixedCost)
|
||||||
|
}
|
||||||
17
app/src/main/java/com/financialviewer/db/Loan.kt
Normal file
17
app/src/main/java/com/financialviewer/db/Loan.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "loans")
|
||||||
|
data class Loan(
|
||||||
|
@PrimaryKey val id: String,
|
||||||
|
@ColumnInfo(name = "title") val title: String,
|
||||||
|
@ColumnInfo(name = "amount") val amount: String,
|
||||||
|
@ColumnInfo(name = "rate") var rate: String,
|
||||||
|
@ColumnInfo(name = "payment") var payment: String,
|
||||||
|
@ColumnInfo(name = "maturity") var maturity: String,
|
||||||
|
@ColumnInfo(name = "remaining_loan") var remainingLoan: String,
|
||||||
|
@ColumnInfo(name = "last_update") var lastUpdate: Long
|
||||||
|
)
|
||||||
30
app/src/main/java/com/financialviewer/db/LoanDao.kt
Normal file
30
app/src/main/java/com/financialviewer/db/LoanDao.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LoanDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertLoan(loan: Loan)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateLoan(loan: Loan)
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM loans")
|
||||||
|
suspend fun getRowCount(): Int
|
||||||
|
|
||||||
|
@Query("SELECT * FROM loans WHERE id = :loanId")
|
||||||
|
suspend fun getLoanById(loanId: String): Loan?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM loans")
|
||||||
|
suspend fun getAllLoans(): List<Loan>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteLoan(loan: Loan)
|
||||||
|
}
|
||||||
14
app/src/main/java/com/financialviewer/db/MonthlySummary.kt
Normal file
14
app/src/main/java/com/financialviewer/db/MonthlySummary.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "monthly_summary")
|
||||||
|
data class MonthlySummary(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||||
|
@ColumnInfo(name = "year") val year: Int,
|
||||||
|
@ColumnInfo(name = "month") val month: Int,
|
||||||
|
@ColumnInfo(name = "income") var income: Double,
|
||||||
|
@ColumnInfo(name = "cost") var cost: Double
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface MonthlySummaryDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertMonthlySummary(monthlySummary: MonthlySummary)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateMonthlySummary(monthlySummary: MonthlySummary)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM monthly_summary WHERE year = :year")
|
||||||
|
suspend fun getMonthlySummary(year: Int): List<MonthlySummary>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM monthly_summary WHERE year = :year AND month = :month")
|
||||||
|
suspend fun getMonthlySummaryByMonth(year: Int, month: Int): MonthlySummary?
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteMonthlySummary(monthlySummary: MonthlySummary)
|
||||||
|
}
|
||||||
13
app/src/main/java/com/financialviewer/db/YearlySummary.kt
Normal file
13
app/src/main/java/com/financialviewer/db/YearlySummary.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "yearly_summary")
|
||||||
|
data class YearlySummary(
|
||||||
|
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||||
|
@ColumnInfo(name = "year") val year: Int,
|
||||||
|
@ColumnInfo(name = "category") val category: String,
|
||||||
|
@ColumnInfo(name = "total") var total: Double
|
||||||
|
)
|
||||||
27
app/src/main/java/com/financialviewer/db/YearlySummaryDao.kt
Normal file
27
app/src/main/java/com/financialviewer/db/YearlySummaryDao.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.financialviewer.db
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface YearlySummaryDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertYearlySummary(yearlySummary: YearlySummary)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateYearlySummary(yearlySummary: YearlySummary)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM yearly_summary WHERE year = :year")
|
||||||
|
suspend fun getYearlySummary(year: Int): List<YearlySummary>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM yearly_summary WHERE year = :year AND category = :category")
|
||||||
|
suspend fun getYearlySummaryByCategory(year: Int, category: String): YearlySummary?
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteYearlySummary(yearlySummary: YearlySummary)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.financialviewer.network
|
||||||
|
|
||||||
|
object ConnectionData {
|
||||||
|
const val SHARED_FOLDER: String = "Working"
|
||||||
|
const val BANK_CSV_FOLDER: String = "Bank_CSV"
|
||||||
|
const val USERNAME: String = "halio"
|
||||||
|
const val PASSWORD: String = "zhao8888"
|
||||||
|
const val DOMAIN: String = ""
|
||||||
|
const val HOSTNAME: String = "192.168.0.10"
|
||||||
|
}
|
||||||
123
app/src/main/java/com/financialviewer/network/SmbClientHelper.kt
Normal file
123
app/src/main/java/com/financialviewer/network/SmbClientHelper.kt
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package com.financialviewer.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.util.Log
|
||||||
|
import com.financialviewer.utils.pathJoin
|
||||||
|
import com.hierynomus.msdtyp.AccessMask
|
||||||
|
import com.hierynomus.msfscc.fileinformation.FileIdBothDirectoryInformation
|
||||||
|
import com.hierynomus.mssmb2.SMB2CreateDisposition
|
||||||
|
import com.hierynomus.mssmb2.SMB2ShareAccess
|
||||||
|
import com.hierynomus.smbj.SMBClient
|
||||||
|
import com.hierynomus.smbj.auth.AuthenticationContext
|
||||||
|
import com.hierynomus.smbj.connection.Connection
|
||||||
|
import com.hierynomus.smbj.session.Session
|
||||||
|
import com.hierynomus.smbj.share.DiskShare
|
||||||
|
import com.hierynomus.smbj.share.File
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
class SmbClientHelper(private val context: Context) {
|
||||||
|
private lateinit var connection: Connection
|
||||||
|
private lateinit var session: Session
|
||||||
|
private lateinit var share: DiskShare
|
||||||
|
|
||||||
|
suspend fun connect(): Boolean {
|
||||||
|
return try {
|
||||||
|
val client = SMBClient()
|
||||||
|
|
||||||
|
// 使用 withContext 切换到 IO 线程执行连接操作
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
connection = client.connect(ConnectionData.HOSTNAME)
|
||||||
|
Log.d("SmbClientHelper", "Connected to server: ${ConnectionData.HOSTNAME}")
|
||||||
|
|
||||||
|
val ac = AuthenticationContext(
|
||||||
|
ConnectionData.USERNAME, ConnectionData.PASSWORD.toCharArray(),
|
||||||
|
ConnectionData.DOMAIN
|
||||||
|
)
|
||||||
|
session = connection.authenticate(ac)
|
||||||
|
Log.d("SmbClientHelper", "Authenticated with username: ${ConnectionData.USERNAME}")
|
||||||
|
|
||||||
|
share = session.connectShare(ConnectionData.SHARED_FOLDER) as DiskShare
|
||||||
|
Log.d("SmbClientHelper", "Connected to share: ${ConnectionData.SHARED_FOLDER}")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 捕获 SMB 连接异常
|
||||||
|
Log.e("SmbClientHelper", "Error connecting to share", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renameFile(file: File, newName: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCSVFromShare(): List<FileIdBothDirectoryInformation> {
|
||||||
|
val allFiles = share.list(ConnectionData.BANK_CSV_FOLDER)
|
||||||
|
val csvFiles = allFiles.filter { file -> file.fileName.endsWith(".csv", ignoreCase = true) }
|
||||||
|
return csvFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCSVFile(filename: String): File {
|
||||||
|
return share.openFile(
|
||||||
|
pathJoin(ConnectionData.BANK_CSV_FOLDER, filename),
|
||||||
|
setOf(AccessMask.GENERIC_READ),
|
||||||
|
null,
|
||||||
|
SMB2ShareAccess.ALL,
|
||||||
|
SMB2CreateDisposition.FILE_OPEN,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getImagePathsFromShare(title: String, episode: String): MutableList<String> {
|
||||||
|
val episodeFolderPath = pathJoin(ConnectionData.BANK_CSV_FOLDER, title, episode)
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
var imageNames = mutableListOf<String>()
|
||||||
|
for (item in share.list(episodeFolderPath)) {
|
||||||
|
val fileInfo = item as FileIdBothDirectoryInformation
|
||||||
|
if (fileInfo.fileName.endsWith(".jpg", true)) {
|
||||||
|
imageNames.add(fileInfo.fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageNames = imageNames.sortedBy { it.substringBefore('.').toInt() }.toMutableList()
|
||||||
|
imageNames.map { "$episodeFolderPath/$it" }.toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadImageFromShare(imagePath: String): Bitmap? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val file = share.openFile(
|
||||||
|
imagePath,
|
||||||
|
setOf(AccessMask.GENERIC_READ),
|
||||||
|
null,
|
||||||
|
SMB2ShareAccess.ALL,
|
||||||
|
SMB2CreateDisposition.FILE_OPEN,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
val inputStream: InputStream = file.inputStream
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
val imageData = outputStream.toByteArray()
|
||||||
|
BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun disconnect() {
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
share.close()
|
||||||
|
session.close()
|
||||||
|
connection.close()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("SmbClientHelper", "Error disconnecting", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.financialviewer.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.db.AppDatabase
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
|
||||||
|
class BankTransactionRepository(context: Context) {
|
||||||
|
private val bankTransactionDao = AppDatabase.getDatabase(context).bankTransactionDao()
|
||||||
|
private val showAllMonth: String = context.getString(R.string.show_all_month)
|
||||||
|
private val showAllCategory: String = context.getString(R.string.show_all_category)
|
||||||
|
|
||||||
|
suspend fun insertBankTransaction(bankTransaction: BankTransaction) {
|
||||||
|
bankTransactionDao.insertBankTransaction(bankTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateBankTransaction(bankTransaction: BankTransaction) {
|
||||||
|
bankTransactionDao.updateBankTransaction(bankTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRowCount(): Int{
|
||||||
|
return bankTransactionDao.getRowCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBankTransactionById(refId: String): BankTransaction? {
|
||||||
|
return bankTransactionDao.getBankTransactionById(refId)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getLatestYearOfBankTransaction(): String {
|
||||||
|
val bankTransaction = bankTransactionDao.getLastBankTransaction()
|
||||||
|
return if (bankTransaction != null) {
|
||||||
|
bankTransaction.date.split("-")[0]
|
||||||
|
} else {
|
||||||
|
YEAR.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBankTransactionsByDateAndCategoryDesc(year: String, month: String, categoryList: MutableList<String>): MutableList<BankTransaction> {
|
||||||
|
return if (categoryList[0].contains(showAllCategory) && month.contains(showAllMonth)) {
|
||||||
|
bankTransactionDao.getAllBankTransactionsDesc(year).toMutableList()
|
||||||
|
} else if (categoryList[0].contains(showAllCategory)) {
|
||||||
|
bankTransactionDao.getBankTransactionsByMonthDesc(year, month).toMutableList()
|
||||||
|
} else if (month.contains(showAllMonth)) {
|
||||||
|
bankTransactionDao.getBankTransactionsByCategoryListDesc(year, categoryList).toMutableList()
|
||||||
|
} else {
|
||||||
|
bankTransactionDao.getBankTransactionsByMonthAndCategoryListDesc(year, month, categoryList).toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBankTransactionsByMonth(year: String, month: String): MutableList<BankTransaction> {
|
||||||
|
return bankTransactionDao.getBankTransactionsByMonthDesc(year, month).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBankTransactionsByCategory(year: String, category: String): MutableList<BankTransaction> {
|
||||||
|
return bankTransactionDao.getBankTransactionsByCategoryDesc(year, category).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteBankTransaction(bankTransaction: BankTransaction) {
|
||||||
|
bankTransactionDao.deleteBankTransaction(bankTransaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.financialviewer.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.db.AppDatabase
|
||||||
|
import com.financialviewer.db.FixedCost
|
||||||
|
|
||||||
|
class FixedCostRepository(context: Context) {
|
||||||
|
private val fixedCostDao = AppDatabase.getDatabase(context).fixedCostDao()
|
||||||
|
|
||||||
|
suspend fun insertFixedCost(fixedCost: FixedCost) {
|
||||||
|
fixedCostDao.insertFixedCost(fixedCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateFixedCost(fixedCost: FixedCost) {
|
||||||
|
fixedCostDao.updateFixedCost(fixedCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRowCount(): Int {
|
||||||
|
return fixedCostDao.getRowCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFixedCostByCategory(category: String): FixedCost? {
|
||||||
|
return fixedCostDao.getFixedCostByCategory(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAllFixedCosts(): MutableList<FixedCost> {
|
||||||
|
return fixedCostDao.getAllFixedCosts().toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAllFixedCostsByType(type: String): MutableList<FixedCost> {
|
||||||
|
return fixedCostDao.getAllFixedCostsByType(type).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteFixedCost(fixedCost: FixedCost) {
|
||||||
|
fixedCostDao.deleteFixedCost(fixedCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.financialviewer.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.constants.LoanList
|
||||||
|
import com.financialviewer.db.AppDatabase
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
import com.financialviewer.utils.calculateRemainingLoan
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
class LoanRepository(context: Context) {
|
||||||
|
private val loanDao = AppDatabase.getDatabase(context).loanDao()
|
||||||
|
|
||||||
|
private suspend fun insertLoan(loan: Loan) {
|
||||||
|
loanDao.insertLoan(loan)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateLoan(loan: Loan) {
|
||||||
|
loanDao.updateLoan(loan)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRowCount(): Int {
|
||||||
|
return loanDao.getRowCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getLoanById(loanId: String): Loan? {
|
||||||
|
return loanDao.getLoanById(loanId)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAllLoans(): MutableList<Loan> {
|
||||||
|
return loanDao.getAllLoans().toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun initDB() {
|
||||||
|
for (loan in LoanList) {
|
||||||
|
insertLoan(loan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateRemainingLoan(loan: Loan, monthDiff: Int) {
|
||||||
|
loan.remainingLoan = calculateRemainingLoan(loan, monthDiff)
|
||||||
|
loan.lastUpdate = Calendar.getInstance().timeInMillis
|
||||||
|
updateLoan(loan)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.financialviewer.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.db.AppDatabase
|
||||||
|
import com.financialviewer.db.MonthlySummary
|
||||||
|
|
||||||
|
|
||||||
|
class MonthlySummaryRepository(context: Context) {
|
||||||
|
private val monthlySummaryDao = AppDatabase.getDatabase(context).monthlySummaryDao()
|
||||||
|
|
||||||
|
suspend fun insertMonthlySummary(monthlySummary: MonthlySummary) {
|
||||||
|
monthlySummaryDao.insertMonthlySummary(monthlySummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateMonthlySummary(monthlySummary: MonthlySummary) {
|
||||||
|
monthlySummaryDao.updateMonthlySummary(monthlySummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMonthlySummary(year: Int): MutableList<MonthlySummary> {
|
||||||
|
return monthlySummaryDao.getMonthlySummary(year).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMonthlySummaryByMonth(year: Int, month: Int): MonthlySummary? {
|
||||||
|
return monthlySummaryDao.getMonthlySummaryByMonth(year, month)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteMonthlySummary(monthlySummary: MonthlySummary) {
|
||||||
|
monthlySummaryDao.deleteMonthlySummary(monthlySummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.financialviewer.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.db.AppDatabase
|
||||||
|
import com.financialviewer.db.YearlySummary
|
||||||
|
|
||||||
|
class YearlySummaryRepository(context: Context) {
|
||||||
|
private val yearlySummaryDao = AppDatabase.getDatabase(context).yearlySummaryDao()
|
||||||
|
|
||||||
|
suspend fun insertYearlySummary(yearlySummary: YearlySummary) {
|
||||||
|
yearlySummaryDao.insertYearlySummary(yearlySummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateYearlySummary(yearlySummary: YearlySummary) {
|
||||||
|
yearlySummaryDao.updateYearlySummary(yearlySummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getYearlySummary(year: Int): MutableList<YearlySummary> {
|
||||||
|
return yearlySummaryDao.getYearlySummary(year).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getYearlySummaryByCategory(year: Int, category: String): YearlySummary? {
|
||||||
|
return yearlySummaryDao.getYearlySummaryByCategory(year, category)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteYearlySummary(yearlySummary: YearlySummary) {
|
||||||
|
yearlySummaryDao.deleteYearlySummary(yearlySummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.SwitchCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.BLANK_BANK_TRANSACTION
|
||||||
|
import com.financialviewer.constants.FIXED_COST_TIP
|
||||||
|
import com.financialviewer.constants.FIXED_COST_TYPE
|
||||||
|
import com.financialviewer.constants.NON_FIXED_COST_TIP
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.repository.BankTransactionRepository
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class BankTransactionDetailsActivity: AppCompatActivity() {
|
||||||
|
private lateinit var bankTransactionRepository: BankTransactionRepository
|
||||||
|
private lateinit var bankTransaction: BankTransaction
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_bank_transaction_details)
|
||||||
|
|
||||||
|
bankTransactionRepository = BankTransactionRepository(this)
|
||||||
|
|
||||||
|
val refId = intent.getStringExtra("refId") ?: ""
|
||||||
|
val accountTextView: TextView = findViewById(R.id.account)
|
||||||
|
val dateTextView: TextView = findViewById(R.id.first)
|
||||||
|
val amountTextView: TextView = findViewById(R.id.second)
|
||||||
|
val counterpartyTextView: TextView = findViewById(R.id.third)
|
||||||
|
val referenceTextView: TextView = findViewById(R.id.reference)
|
||||||
|
val isFixedCostTextView: TextView = findViewById(R.id.isFixedCostTextView)
|
||||||
|
val switch: SwitchCompat = findViewById(R.id.isFixedCostSwitch)
|
||||||
|
|
||||||
|
if (refId != "") {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
|
||||||
|
bankTransaction = withContext(Dispatchers.IO) {
|
||||||
|
bankTransactionRepository.getBankTransactionById(refId) ?: BLANK_BANK_TRANSACTION
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
accountTextView.text = bankTransaction.account
|
||||||
|
dateTextView.text = bankTransaction.date
|
||||||
|
amountTextView.text = convertDoubleToString(bankTransaction.amount)
|
||||||
|
counterpartyTextView.text = bankTransaction.counterparty
|
||||||
|
referenceTextView.text = bankTransaction.reference
|
||||||
|
if (FIXED_COST_TYPE.containsKey(bankTransaction.category)) {
|
||||||
|
isFixedCostTextView.visibility = View.VISIBLE
|
||||||
|
isFixedCostTextView.text = if (bankTransaction.isFixedCost) {
|
||||||
|
FIXED_COST_TIP
|
||||||
|
} else {
|
||||||
|
NON_FIXED_COST_TIP
|
||||||
|
}
|
||||||
|
switch.visibility = View.VISIBLE
|
||||||
|
switch.isChecked = bankTransaction.isFixedCost
|
||||||
|
switch.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
isFixedCostTextView.text = FIXED_COST_TIP
|
||||||
|
} else {
|
||||||
|
isFixedCostTextView.text = NON_FIXED_COST_TIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onBackPressedDispatcher.addCallback(this, object: OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
bankTransaction.isFixedCost = switch.isChecked
|
||||||
|
bankTransactionRepository.updateBankTransaction(bankTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnabled = false
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.repository.BankTransactionRepository
|
||||||
|
import com.financialviewer.ui.adapter.BankTransactionAdapter
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.utils.generateCategoryWithMainCategory
|
||||||
|
import com.financialviewer.utils.generateMonth
|
||||||
|
import com.financialviewer.utils.generateYears
|
||||||
|
import com.financialviewer.utils.getSelectedCategory
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class BankTransactionsActivity: AppCompatActivity() {
|
||||||
|
private lateinit var bankTransactionRepository: BankTransactionRepository
|
||||||
|
private lateinit var bankTransactionAdapter: BankTransactionAdapter
|
||||||
|
private lateinit var bankTransactionList: MutableList<BankTransaction>
|
||||||
|
private lateinit var sumAmountTextView: TextView
|
||||||
|
private var sumAmount = 0.0
|
||||||
|
private var yearSpinnerIsInitialized = false
|
||||||
|
private var monthSpinnerIsInitialized = false
|
||||||
|
private var categorySpinnerIsInitialized = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_bank_transactions)
|
||||||
|
|
||||||
|
bankTransactionRepository = BankTransactionRepository(this)
|
||||||
|
val appPreferences = SharedPreferencesHelper((this))
|
||||||
|
|
||||||
|
var year = intent.getIntExtra("year", 0).toString()
|
||||||
|
if (year == "0") {
|
||||||
|
year = appPreferences.readDefaultYear(YEAR).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
var month = intent.getIntExtra("month", 0).toString().padStart(2, '0')
|
||||||
|
if (month == "00") {
|
||||||
|
month = getString(R.string.show_all_month)
|
||||||
|
}
|
||||||
|
|
||||||
|
val category = intent.getStringExtra("category") ?: ""
|
||||||
|
var resultCategories: MutableList<String> = mutableListOf()
|
||||||
|
if (category == "") {
|
||||||
|
resultCategories.add(getString(R.string.show_all_category))
|
||||||
|
} else {
|
||||||
|
resultCategories.add(category)
|
||||||
|
}
|
||||||
|
|
||||||
|
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
bankTransactionAdapter = BankTransactionAdapter(this, mutableListOf()) { selectedBankTransaction ->
|
||||||
|
onBankTransactionClick(selectedBankTransaction)
|
||||||
|
}
|
||||||
|
recyclerView.adapter = bankTransactionAdapter
|
||||||
|
sumAmountTextView = findViewById(R.id.sumAmountValue)
|
||||||
|
updateBankTransactionList(year, month, resultCategories)
|
||||||
|
|
||||||
|
val yearSpinner: Spinner = findViewById(R.id.yearSpinner)
|
||||||
|
val latestYear = appPreferences.readDefaultYear(YEAR).toString()
|
||||||
|
val yearList = generateYears(latestYear)
|
||||||
|
val yearAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, yearList)
|
||||||
|
yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
yearSpinner.adapter = yearAdapter
|
||||||
|
val yearPosition = yearList.indexOf(year)
|
||||||
|
yearSpinner.setSelection(yearPosition)
|
||||||
|
yearSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||||
|
val selectedItem = yearList[position]
|
||||||
|
year = selectedItem
|
||||||
|
|
||||||
|
if (isInitialized()) {
|
||||||
|
updateBankTransactionList(year, month, resultCategories)
|
||||||
|
}
|
||||||
|
yearSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||||
|
yearSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val monthSpinner: Spinner = findViewById(R.id.monthSpinner)
|
||||||
|
val monthList = generateMonth().toMutableList()
|
||||||
|
monthList.add(0, getString(R.string.show_all_month))
|
||||||
|
val monthAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, monthList)
|
||||||
|
monthAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
monthSpinner.adapter = monthAdapter
|
||||||
|
val monthPosition = monthList.indexOf(month)
|
||||||
|
monthSpinner.setSelection(monthPosition)
|
||||||
|
monthSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||||
|
val selectedItem = monthList[position]
|
||||||
|
month = selectedItem
|
||||||
|
if (isInitialized()) {
|
||||||
|
updateBankTransactionList(year, month, resultCategories)
|
||||||
|
}
|
||||||
|
monthSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||||
|
monthSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val categorySpinner: Spinner = findViewById(R.id.categorySpinner)
|
||||||
|
val categoryList = generateCategoryWithMainCategory().toMutableList()
|
||||||
|
categoryList.add(0, getString(R.string.show_all_category))
|
||||||
|
val categoryAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, categoryList)
|
||||||
|
categoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
categorySpinner.adapter = categoryAdapter
|
||||||
|
val categoryPosition = categoryList.indexOf(category)
|
||||||
|
categorySpinner.setSelection(categoryPosition)
|
||||||
|
categorySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||||
|
val selectedItem = categoryList[position]
|
||||||
|
resultCategories = getSelectedCategory(selectedItem)
|
||||||
|
if (isInitialized()) {
|
||||||
|
updateBankTransactionList(year, month, resultCategories)
|
||||||
|
}
|
||||||
|
categorySpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||||
|
categorySpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBankTransactionClick(selectedBankTransaction: BankTransaction) {
|
||||||
|
val intent = Intent(this@BankTransactionsActivity, BankTransactionDetailsActivity::class.java)
|
||||||
|
intent.putExtra("refId", selectedBankTransaction.refId)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateBankTransactionList(year: String, month: String, categories: MutableList<String>) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
bankTransactionList = bankTransactionRepository.getBankTransactionsByDateAndCategoryDesc(year,month,
|
||||||
|
categories
|
||||||
|
)
|
||||||
|
sumAmount = calculateSumAmount(bankTransactionList)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
bankTransactionAdapter.updateBankTransactionList(bankTransactionList)
|
||||||
|
sumAmountTextView.text = convertDoubleToString(sumAmount)
|
||||||
|
val color = if (sumAmount > 0) {
|
||||||
|
ContextCompat.getColor(this@BankTransactionsActivity, R.color.green)
|
||||||
|
} else {
|
||||||
|
ContextCompat.getColor(this@BankTransactionsActivity, R.color.red)
|
||||||
|
}
|
||||||
|
sumAmountTextView.setTextColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateSumAmount(bankTransactionList: MutableList<BankTransaction>): Double {
|
||||||
|
var sum = 0.0
|
||||||
|
for (bankTransaction in bankTransactionList) {
|
||||||
|
sum += bankTransaction.amount
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInitialized(): Boolean {
|
||||||
|
return yearSpinnerIsInitialized && monthSpinnerIsInitialized && categorySpinnerIsInitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.repository.BankTransactionRepository
|
||||||
|
import com.financialviewer.ui.adapter.FixedCostDetailsAdapter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class FixedCostDetailsActivity : AppCompatActivity() {
|
||||||
|
private lateinit var bankTransactionRepository: BankTransactionRepository
|
||||||
|
private lateinit var sourceAdapter: FixedCostDetailsAdapter
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_fixed_cost_details)
|
||||||
|
|
||||||
|
bankTransactionRepository = BankTransactionRepository(this)
|
||||||
|
|
||||||
|
val refIds = intent.getStringExtra("refIds") ?: ""
|
||||||
|
val category = intent.getStringExtra("category") ?: ""
|
||||||
|
val refIdList = refIds.split(",")
|
||||||
|
|
||||||
|
val sourceRecyclerView: RecyclerView = findViewById(R.id.sourceRecyclerView)
|
||||||
|
sourceRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
val currentSourceList: MutableList<BankTransaction> = mutableListOf()
|
||||||
|
val allSourceList: MutableList<BankTransaction> = mutableListOf()
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
for (refId in refIdList) {
|
||||||
|
val bankTransaction = bankTransactionRepository.getBankTransactionById(refId)
|
||||||
|
if (bankTransaction != null) {
|
||||||
|
currentSourceList.add(bankTransaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allSourceList.addAll(
|
||||||
|
bankTransactionRepository.getBankTransactionsByCategory(YEAR.toString(), category))
|
||||||
|
allSourceList.addAll(
|
||||||
|
bankTransactionRepository.getBankTransactionsByCategory((YEAR - 1).toString(), category))
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
sourceAdapter =
|
||||||
|
FixedCostDetailsAdapter(this@FixedCostDetailsActivity, allSourceList, currentSourceList) { selectedBankTransaction ->
|
||||||
|
onBankTransactionClick(selectedBankTransaction)
|
||||||
|
}
|
||||||
|
sourceRecyclerView.adapter = sourceAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBankTransactionClick(selectedBankTransaction: BankTransaction) {
|
||||||
|
val intent = Intent(this@FixedCostDetailsActivity, BankTransactionDetailsActivity::class.java)
|
||||||
|
intent.putExtra("refId", selectedBankTransaction.refId)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
147
app/src/main/java/com/financialviewer/ui/FixedCostsActivity.kt
Normal file
147
app/src/main/java/com/financialviewer/ui/FixedCostsActivity.kt
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.MONTHLY
|
||||||
|
import com.financialviewer.constants.QUARTERLY
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.db.FixedCost
|
||||||
|
import com.financialviewer.repository.FixedCostRepository
|
||||||
|
import com.financialviewer.ui.adapter.FixedCostAdapter
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.work.UpdateFixedCosts
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
class FixedCostsActivity : AppCompatActivity() {
|
||||||
|
private lateinit var fixedCostRepository: FixedCostRepository
|
||||||
|
private lateinit var monthlyAdapter: FixedCostAdapter
|
||||||
|
private lateinit var quarterlyAdapter: FixedCostAdapter
|
||||||
|
private lateinit var yearlyAdapter: FixedCostAdapter
|
||||||
|
|
||||||
|
private var year = YEAR
|
||||||
|
private val monthlyList: MutableList<FixedCost> = mutableListOf()
|
||||||
|
private val quarterlyList: MutableList<FixedCost> = mutableListOf()
|
||||||
|
private val yearlyList: MutableList<FixedCost> = mutableListOf()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_fixed_costs)
|
||||||
|
|
||||||
|
fixedCostRepository = FixedCostRepository(this)
|
||||||
|
|
||||||
|
val appPreferences = SharedPreferencesHelper((this))
|
||||||
|
year = appPreferences.readDefaultYear(YEAR)
|
||||||
|
|
||||||
|
val lastUpdateTextView: TextView = findViewById(R.id.lastUpdateValue)
|
||||||
|
lastUpdateTextView.text = appPreferences.readCalculateFixedCostTime()
|
||||||
|
|
||||||
|
val monthlyRecyclerView: RecyclerView = findViewById(R.id.monthlyRecyclerView)
|
||||||
|
monthlyRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
val quarterlyRecyclerView: RecyclerView = findViewById(R.id.quarterlyRecyclerView)
|
||||||
|
quarterlyRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
val yearlyRecyclerView: RecyclerView = findViewById(R.id.yearlyRecyclerView)
|
||||||
|
yearlyRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
val monthlyFixedCostTextView: TextView = findViewById(R.id.monthlyFixedCostValue)
|
||||||
|
val yearlyFixedCostTextView: TextView = findViewById(R.id.yearlyFixedCostValue)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
generateFixedCostList()
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
monthlyAdapter = FixedCostAdapter(this@FixedCostsActivity, monthlyList) { monthlyFixedCost ->
|
||||||
|
onFixedCostClick(monthlyFixedCost)
|
||||||
|
}
|
||||||
|
monthlyRecyclerView.adapter = monthlyAdapter
|
||||||
|
|
||||||
|
quarterlyAdapter = FixedCostAdapter(this@FixedCostsActivity, quarterlyList) { quarterlyFixedCost ->
|
||||||
|
onFixedCostClick(quarterlyFixedCost)
|
||||||
|
}
|
||||||
|
quarterlyRecyclerView.adapter = quarterlyAdapter
|
||||||
|
|
||||||
|
yearlyAdapter = FixedCostAdapter(this@FixedCostsActivity, yearlyList) { yearlyFixedCost ->
|
||||||
|
onFixedCostClick(yearlyFixedCost)
|
||||||
|
}
|
||||||
|
yearlyRecyclerView.adapter = yearlyAdapter
|
||||||
|
|
||||||
|
val monthlyFixedCost = calculateMonthlyFixedCost()
|
||||||
|
monthlyFixedCostTextView.text = convertDoubleToString(monthlyFixedCost)
|
||||||
|
yearlyFixedCostTextView.text = convertDoubleToString(monthlyFixedCost * 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val recalculateButton: Button = findViewById(R.id.recalculateButton)
|
||||||
|
recalculateButton.setOnClickListener {
|
||||||
|
val updateFixedCost = UpdateFixedCosts(this)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
updateFixedCost.updateDB()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun generateFixedCostList() {
|
||||||
|
val fixedCostList = fixedCostRepository.getAllFixedCosts()
|
||||||
|
for (fixedCost in fixedCostList) {
|
||||||
|
when (fixedCost.type) {
|
||||||
|
MONTHLY -> {
|
||||||
|
monthlyList.add(fixedCost)
|
||||||
|
}
|
||||||
|
QUARTERLY -> {
|
||||||
|
quarterlyList.add(fixedCost)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
yearlyList.add(fixedCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateMonthlyFixedCost(): Double {
|
||||||
|
var monthlySum = BigDecimal(0.0)
|
||||||
|
var quarterlySum = BigDecimal(0.0)
|
||||||
|
var yearlySum = BigDecimal(0.0)
|
||||||
|
|
||||||
|
for (monthly in monthlyList) {
|
||||||
|
monthlySum += BigDecimal(monthly.amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (quarterly in quarterlyList) {
|
||||||
|
quarterlySum += BigDecimal(quarterly.amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (yearly in yearlyList) {
|
||||||
|
yearlySum += BigDecimal(yearly.amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (monthlySum +
|
||||||
|
(quarterlySum / BigDecimal("3")) +
|
||||||
|
(yearlySum / BigDecimal("12")))
|
||||||
|
.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun onFixedCostClick(fixedCost: FixedCost) {
|
||||||
|
val intent = Intent(this@FixedCostsActivity, FixedCostDetailsActivity::class.java)
|
||||||
|
intent.putExtra("refIds", fixedCost.refIds)
|
||||||
|
intent.putExtra("category", fixedCost.category)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
98
app/src/main/java/com/financialviewer/ui/HomeActivity.kt
Normal file
98
app/src/main/java/com/financialviewer/ui/HomeActivity.kt
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.work.UpdateBankTransactions
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class HomeActivity: AppCompatActivity() {
|
||||||
|
private lateinit var progressBarContainer: LinearLayout
|
||||||
|
private var isProgressing = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_home)
|
||||||
|
|
||||||
|
progressBarContainer = findViewById(R.id.progressBarContainer)
|
||||||
|
|
||||||
|
val loanOverviewTextView : TextView =findViewById(R.id.loanOverview)
|
||||||
|
loanOverviewTextView.setOnClickListener {
|
||||||
|
if (!isProgressing) {
|
||||||
|
val intent = Intent(this@HomeActivity, LoanOverviewActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fixedCostsTextView : TextView =findViewById(R.id.fixedCosts)
|
||||||
|
fixedCostsTextView.setOnClickListener {
|
||||||
|
if (!isProgressing) {
|
||||||
|
val intent = Intent(this@HomeActivity, FixedCostsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bankTransactionTextView : TextView =findViewById(R.id.bankTransaction)
|
||||||
|
bankTransactionTextView.setOnClickListener {
|
||||||
|
if (!isProgressing) {
|
||||||
|
val intent = Intent(this@HomeActivity, BankTransactionsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val monthlySummaryTextView : TextView =findViewById(R.id.monthlySummary)
|
||||||
|
monthlySummaryTextView.setOnClickListener {
|
||||||
|
if (!isProgressing) {
|
||||||
|
val intent = Intent(this@HomeActivity, MonthlySummaryActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val yearlySummaryTextView : TextView =findViewById(R.id.yearlySummary)
|
||||||
|
yearlySummaryTextView.setOnClickListener {
|
||||||
|
if (!isProgressing) {
|
||||||
|
val intent = Intent(this@HomeActivity, YearlySummaryActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val updateTransactionTextView : TextView =findViewById(R.id.updateTransaction)
|
||||||
|
updateTransactionTextView.setOnClickListener {
|
||||||
|
if (!isProgressing) {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle("确认更新")
|
||||||
|
.setMessage("是否确定更新银行流水?")
|
||||||
|
.setPositiveButton("确定") { dialog, _ ->
|
||||||
|
showProgressBar()
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val updateBankTransaction = UpdateBankTransactions(this@HomeActivity)
|
||||||
|
updateBankTransaction.updateDB()
|
||||||
|
hideProgressBar()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton("取消") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showProgressBar() {
|
||||||
|
progressBarContainer.visibility = View.VISIBLE
|
||||||
|
isProgressing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideProgressBar() {
|
||||||
|
progressBarContainer.visibility = View.GONE
|
||||||
|
isProgressing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
165
app/src/main/java/com/financialviewer/ui/LoanDetailsActivity.kt
Normal file
165
app/src/main/java/com/financialviewer/ui/LoanDetailsActivity.kt
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.icu.util.Calendar
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.data.LoanMonthlyDetails
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
import com.financialviewer.repository.LoanRepository
|
||||||
|
import com.financialviewer.ui.adapter.LoanDetailsAdapter
|
||||||
|
import com.financialviewer.utils.calculateNextInterest
|
||||||
|
import com.financialviewer.utils.calculateNextPrincipal
|
||||||
|
import com.financialviewer.utils.calculateNextRemaining
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.utils.convertInputStringToDouble
|
||||||
|
import com.financialviewer.utils.convertStringToDouble
|
||||||
|
import com.financialviewer.utils.getNextMonth
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class LoanDetailsActivity: AppCompatActivity() {
|
||||||
|
private lateinit var loanRepository: LoanRepository
|
||||||
|
private lateinit var loanDetailsAdapter: LoanDetailsAdapter
|
||||||
|
private var remainingUpdated: Boolean = false
|
||||||
|
private var sumInterest: Double = 0.0
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_loan_details)
|
||||||
|
|
||||||
|
loanRepository = LoanRepository(this)
|
||||||
|
|
||||||
|
val loanId = intent.getStringExtra("loanId") ?: "R1"
|
||||||
|
|
||||||
|
val titleTextView: TextView = findViewById(R.id.loanTitle)
|
||||||
|
val paymentTextView: TextView = findViewById(R.id.payment)
|
||||||
|
val remainingTextView: TextView = findViewById(R.id.remaining)
|
||||||
|
val rateTextView: TextView = findViewById(R.id.rate)
|
||||||
|
val sumInterestTextView: TextView = findViewById(R.id.sumInterest)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val loan = loanRepository.getLoanById(loanId)
|
||||||
|
if (loan != null) {
|
||||||
|
titleTextView.text = loan.title
|
||||||
|
remainingTextView.text = "当前欠款:" + loan.remainingLoan
|
||||||
|
paymentTextView.text = "月供: " + loan.payment
|
||||||
|
rateTextView.text = "利率:" + loan.rate
|
||||||
|
|
||||||
|
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this@LoanDetailsActivity)
|
||||||
|
loanDetailsAdapter = LoanDetailsAdapter(generateMonthlyDetails(loan))
|
||||||
|
recyclerView.adapter = loanDetailsAdapter
|
||||||
|
|
||||||
|
sumInterestTextView.text = "预计还需支付利息:" + convertDoubleToString(sumInterest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val refreshButton: Button = findViewById(R.id.refresh)
|
||||||
|
refreshButton.setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val loan = loanRepository.getLoanById(loanId)
|
||||||
|
if (loan != null) {
|
||||||
|
loanDetailsAdapter.updateLoanDetailsList(generateMonthlyDetails(loan))
|
||||||
|
sumInterestTextView.text = "预计还需支付利息:" + convertDoubleToString(sumInterest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val specialRepaymentButton: Button = findViewById(R.id.specialRepayment)
|
||||||
|
specialRepaymentButton.setOnClickListener {
|
||||||
|
// 创建一个EditText作为输入框
|
||||||
|
val input = EditText(this@LoanDetailsActivity)
|
||||||
|
|
||||||
|
// 创建并显示AlertDialog
|
||||||
|
val dialog = AlertDialog.Builder(this@LoanDetailsActivity)
|
||||||
|
.setTitle("提前还款金额")
|
||||||
|
.setView(input) // 将EditText添加到对话框
|
||||||
|
.setPositiveButton("Confirm") { _, _ ->
|
||||||
|
// 获取用户输入并更新TextView的内容
|
||||||
|
val inputString = input.text.toString()
|
||||||
|
try {
|
||||||
|
val inputDouble = convertInputStringToDouble(inputString) // 成功转换
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val loan = loanRepository.getLoanById(loanId)
|
||||||
|
|
||||||
|
if (loan != null) {
|
||||||
|
val newRemainingLoan = convertStringToDouble(loan.remainingLoan) - inputDouble
|
||||||
|
remainingTextView.text = "当前欠款:" + convertDoubleToString(newRemainingLoan)
|
||||||
|
|
||||||
|
loan.remainingLoan = convertDoubleToString(newRemainingLoan)
|
||||||
|
loanRepository.updateLoan(loan)
|
||||||
|
remainingUpdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
// 如果输入不是有效的数字,弹出错误提示
|
||||||
|
Toast.makeText(this@LoanDetailsActivity, "Invalid input. Please enter a valid number.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback(this, object: OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
if (remainingUpdated) {
|
||||||
|
val resultIntent = Intent().apply {
|
||||||
|
putExtra("loanId", loanId)
|
||||||
|
}
|
||||||
|
setResult(Activity.RESULT_OK, resultIntent)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateMonthlyDetails(loan: Loan): MutableList<LoanMonthlyDetails> {
|
||||||
|
sumInterest = 0.0
|
||||||
|
val monthlyDetails: MutableList<LoanMonthlyDetails> = mutableListOf()
|
||||||
|
val now = Calendar.getInstance()
|
||||||
|
var currentYear = now.get(Calendar.YEAR)
|
||||||
|
var currentMonth = now.get(Calendar.MONTH) + 1
|
||||||
|
var base = convertStringToDouble(loan.remainingLoan)
|
||||||
|
var remaining = 1.0
|
||||||
|
while (remaining > 0) {
|
||||||
|
val month = getNextMonth(currentYear, currentMonth)
|
||||||
|
currentYear = month.split(".")[1].toInt()
|
||||||
|
currentMonth = month.split(".")[0].toInt()
|
||||||
|
|
||||||
|
val interest = calculateNextInterest(loan, base)
|
||||||
|
var principal = calculateNextPrincipal(loan, base)
|
||||||
|
remaining = calculateNextRemaining(loan, base)
|
||||||
|
|
||||||
|
if (remaining < 0) {
|
||||||
|
remaining = 0.0
|
||||||
|
principal = base
|
||||||
|
}
|
||||||
|
base = remaining
|
||||||
|
|
||||||
|
monthlyDetails.add(LoanMonthlyDetails(
|
||||||
|
month,
|
||||||
|
convertDoubleToString(interest),
|
||||||
|
convertDoubleToString(principal),
|
||||||
|
convertDoubleToString(remaining)
|
||||||
|
))
|
||||||
|
sumInterest += interest
|
||||||
|
}
|
||||||
|
return monthlyDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
162
app/src/main/java/com/financialviewer/ui/LoanOverviewActivity.kt
Normal file
162
app/src/main/java/com/financialviewer/ui/LoanOverviewActivity.kt
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
import com.financialviewer.repository.LoanRepository
|
||||||
|
import com.financialviewer.ui.adapter.LoanOverviewAdapter
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.utils.convertInputStringToDouble
|
||||||
|
import com.financialviewer.utils.convertPercentageStringToDouble
|
||||||
|
import com.financialviewer.utils.convertPercentageToString
|
||||||
|
import com.financialviewer.utils.convertStringToDouble
|
||||||
|
import com.financialviewer.utils.convertStringToGermanDate
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.format.DateTimeParseException
|
||||||
|
|
||||||
|
class LoanOverviewActivity : AppCompatActivity() {
|
||||||
|
private lateinit var loanRepository: LoanRepository
|
||||||
|
private lateinit var recyclerView: RecyclerView
|
||||||
|
private var loansInDB: MutableList<Loan> = mutableListOf()
|
||||||
|
|
||||||
|
private val startForResult =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
val data: Intent? = result.data
|
||||||
|
val loanId = data?.getStringExtra("loanId")
|
||||||
|
loanId?.let {
|
||||||
|
// 在当前 Activity 的协程作用域中调用 refreshData
|
||||||
|
lifecycleScope.launch {
|
||||||
|
refresh(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_loan_overview)
|
||||||
|
|
||||||
|
loanRepository = LoanRepository(this)
|
||||||
|
recyclerView = findViewById(R.id.recyclerView)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
var sumLoan = 0.0
|
||||||
|
var sumPayment = 0.0
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
loansInDB = loanRepository.getAllLoans()
|
||||||
|
val adapter = LoanOverviewAdapter(
|
||||||
|
loansInDB,
|
||||||
|
onEditClick = { loan, field, value, position ->
|
||||||
|
// 处理编辑点击事件,例如弹出对话框
|
||||||
|
showEditDialog(loan, field, value, position)
|
||||||
|
},
|
||||||
|
onTitleClick = { loan ->
|
||||||
|
// 处理标题点击事件,例如显示详细信息
|
||||||
|
showLoanDetails(loan)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
for (loan in loansInDB) {
|
||||||
|
sumLoan += convertStringToDouble(loan.remainingLoan)
|
||||||
|
sumPayment += convertStringToDouble(loan.payment)
|
||||||
|
}
|
||||||
|
val sumLoanValueTextView: TextView = findViewById(R.id.sumLoanValue)
|
||||||
|
sumLoanValueTextView.text = convertDoubleToString(sumLoan)
|
||||||
|
|
||||||
|
val sumPaymentValueTextView: TextView = findViewById(R.id.sumPaymentValue)
|
||||||
|
sumPaymentValueTextView.text = convertDoubleToString(sumPayment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLoanDetails(loan: Loan) {
|
||||||
|
val intent = Intent(this@LoanOverviewActivity, LoanDetailsActivity::class.java)
|
||||||
|
intent.putExtra("loanId", loan.id)
|
||||||
|
startForResult.launch(intent)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEditDialog(loan: Loan, field: String, value: String, position: Int) {
|
||||||
|
val builder = AlertDialog.Builder(this)
|
||||||
|
builder.setTitle("Edit $field")
|
||||||
|
|
||||||
|
val input = EditText(this)
|
||||||
|
input.setText(value)
|
||||||
|
builder.setView(input)
|
||||||
|
|
||||||
|
builder.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
val inputString = input.text.toString()
|
||||||
|
try {
|
||||||
|
// 更新字段值
|
||||||
|
when (field) {
|
||||||
|
"rate" -> {
|
||||||
|
val validValue = convertPercentageStringToDouble(inputString)
|
||||||
|
loan.rate = convertPercentageToString(validValue)
|
||||||
|
}
|
||||||
|
"payment" -> {
|
||||||
|
val validValue = convertInputStringToDouble(inputString)
|
||||||
|
loan.payment = convertDoubleToString(validValue)
|
||||||
|
}
|
||||||
|
"maturity" -> {
|
||||||
|
val validValue = convertStringToGermanDate(inputString)
|
||||||
|
loan.maturity = validValue
|
||||||
|
}
|
||||||
|
"remainingLoan" -> {
|
||||||
|
val validValue = convertInputStringToDouble(inputString)
|
||||||
|
loan.remainingLoan = convertDoubleToString(validValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据库中的数据
|
||||||
|
lifecycleScope.launch {
|
||||||
|
loanRepository.updateLoan(loan)
|
||||||
|
runOnUiThread {
|
||||||
|
recyclerView.adapter?.notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
// 如果输入不是有效的数字,弹出错误提示
|
||||||
|
Toast.makeText(this@LoanOverviewActivity, "Invalid input. Please enter a valid number.", Toast.LENGTH_SHORT).show()
|
||||||
|
} catch (e: DateTimeParseException) {
|
||||||
|
// 如果输入不是有效的数字,弹出错误提示
|
||||||
|
Toast.makeText(this@LoanOverviewActivity, "Invalid input. Please enter a valid date.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh(loanId: String) {
|
||||||
|
val position = loansInDB.indexOfFirst { it.id == loanId }
|
||||||
|
if (position != -1) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val newLoan = loanRepository.getLoanById(loanId)
|
||||||
|
if (newLoan != null) {
|
||||||
|
loansInDB[position] = newLoan
|
||||||
|
runOnUiThread {
|
||||||
|
recyclerView.adapter?.notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/src/main/java/com/financialviewer/ui/LoginActivity.kt
Normal file
42
app/src/main/java/com/financialviewer/ui/LoginActivity.kt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
|
||||||
|
class LoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_login)
|
||||||
|
|
||||||
|
val password: EditText = findViewById(R.id.password)
|
||||||
|
|
||||||
|
// 处理登录逻辑
|
||||||
|
val loginButton: Button = findViewById(R.id.button)
|
||||||
|
loginButton.setOnClickListener {
|
||||||
|
val enteredPassword = password.text.toString()
|
||||||
|
if (enteredPassword.isEmpty()) {
|
||||||
|
Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
// 处理登录逻辑,例如验证密码
|
||||||
|
if (enteredPassword == "3713") {
|
||||||
|
val appPreferences = SharedPreferencesHelper((this))
|
||||||
|
appPreferences.saveAuthStatus("is_authenticated", true)
|
||||||
|
|
||||||
|
// 跳转到主页面
|
||||||
|
val intent = Intent(this, HomeActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "密码错误", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/src/main/java/com/financialviewer/ui/MainActivity.kt
Normal file
21
app/src/main/java/com/financialviewer/ui/MainActivity.kt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val appPreferences = SharedPreferencesHelper((this))
|
||||||
|
appPreferences.saveAuthStatus("is_authenticated", false)
|
||||||
|
|
||||||
|
val intent = Intent(this, LoginActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.db.MonthlySummary
|
||||||
|
import com.financialviewer.repository.MonthlySummaryRepository
|
||||||
|
import com.financialviewer.ui.adapter.MonthlySummaryAdapter
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.utils.generateYears
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
class MonthlySummaryActivity : AppCompatActivity() {
|
||||||
|
private lateinit var monthlySummaryRepository: MonthlySummaryRepository
|
||||||
|
private lateinit var monthlySummaryAdapter: MonthlySummaryAdapter
|
||||||
|
private lateinit var monthlySummaryList: MutableList<MonthlySummary>
|
||||||
|
|
||||||
|
private lateinit var incomeSumTextView: TextView
|
||||||
|
private lateinit var costSumTextView: TextView
|
||||||
|
|
||||||
|
private var year = YEAR
|
||||||
|
private var yearSpinnerIsInitialized = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_monthly_summary)
|
||||||
|
|
||||||
|
monthlySummaryRepository = MonthlySummaryRepository(this)
|
||||||
|
val appPreferences = SharedPreferencesHelper((this))
|
||||||
|
|
||||||
|
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
monthlySummaryAdapter = MonthlySummaryAdapter(this, mutableListOf()) { selectedMonthlySummary ->
|
||||||
|
onMonthlySummaryClick(selectedMonthlySummary)
|
||||||
|
}
|
||||||
|
recyclerView.adapter = monthlySummaryAdapter
|
||||||
|
|
||||||
|
incomeSumTextView = findViewById(R.id.sumIncomeValue)
|
||||||
|
costSumTextView = findViewById(R.id.sumCostValue)
|
||||||
|
|
||||||
|
updateYearlySummaryList()
|
||||||
|
|
||||||
|
year = appPreferences.readDefaultYear(YEAR)
|
||||||
|
val yearSpinner: Spinner = findViewById(R.id.yearSpinner)
|
||||||
|
val yearList = generateYears(year.toString())
|
||||||
|
val yearAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, yearList)
|
||||||
|
yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
yearSpinner.adapter = yearAdapter
|
||||||
|
yearSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||||
|
val selectedItem = yearList[position]
|
||||||
|
year = selectedItem.toInt()
|
||||||
|
if (isInitialized()) {
|
||||||
|
updateYearlySummaryList()
|
||||||
|
}
|
||||||
|
yearSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||||
|
yearSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateYearlySummaryList() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
var incomeSum = BigDecimal("0.0")
|
||||||
|
var costSum = BigDecimal("0.0")
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
monthlySummaryList = monthlySummaryRepository.getMonthlySummary(year)
|
||||||
|
for (monthlySummary in monthlySummaryList) {
|
||||||
|
incomeSum += BigDecimal(monthlySummary.income.toString())
|
||||||
|
costSum += BigDecimal(monthlySummary.cost.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
monthlySummaryAdapter.updateMonthlySummaryList(monthlySummaryList)
|
||||||
|
incomeSumTextView.text = convertDoubleToString(incomeSum.toDouble())
|
||||||
|
costSumTextView.text = convertDoubleToString(costSum.toDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMonthlySummaryClick(monthlySummary: MonthlySummary) {
|
||||||
|
val intent = Intent(this@MonthlySummaryActivity, BankTransactionsActivity::class.java)
|
||||||
|
intent.putExtra("year", monthlySummary.year)
|
||||||
|
intent.putExtra("month", monthlySummary.month)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInitialized(): Boolean {
|
||||||
|
return yearSpinnerIsInitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.financialviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.db.YearlySummary
|
||||||
|
import com.financialviewer.repository.YearlySummaryRepository
|
||||||
|
import com.financialviewer.ui.adapter.YearlySummaryAdapter
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.utils.generateYears
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
class YearlySummaryActivity : AppCompatActivity() {
|
||||||
|
private lateinit var yearlySummaryRepository: YearlySummaryRepository
|
||||||
|
private lateinit var yearlySummaryAdapter: YearlySummaryAdapter
|
||||||
|
private lateinit var yearlySummaryList: MutableList<YearlySummary>
|
||||||
|
|
||||||
|
private lateinit var sumAmountTextView: TextView
|
||||||
|
private var year = YEAR
|
||||||
|
private var yearSpinnerIsInitialized = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_yearly_summary)
|
||||||
|
|
||||||
|
yearlySummaryRepository = YearlySummaryRepository(this)
|
||||||
|
val appPreferences = SharedPreferencesHelper((this))
|
||||||
|
|
||||||
|
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
|
yearlySummaryAdapter = YearlySummaryAdapter(this, mutableListOf()) { selectedYearlySummary ->
|
||||||
|
onYearlySummaryClick(selectedYearlySummary)
|
||||||
|
}
|
||||||
|
recyclerView.adapter = yearlySummaryAdapter
|
||||||
|
|
||||||
|
sumAmountTextView = findViewById(R.id.sumAmountValue)
|
||||||
|
updateYearlySummaryList()
|
||||||
|
|
||||||
|
year = appPreferences.readDefaultYear(YEAR)
|
||||||
|
val yearSpinner: Spinner = findViewById(R.id.yearSpinner)
|
||||||
|
val yearList = generateYears(year.toString())
|
||||||
|
val yearAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, yearList)
|
||||||
|
yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
yearSpinner.adapter = yearAdapter
|
||||||
|
yearSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||||
|
val selectedItem = yearList[position]
|
||||||
|
year = selectedItem.toInt()
|
||||||
|
if (isInitialized()) {
|
||||||
|
updateYearlySummaryList()
|
||||||
|
}
|
||||||
|
yearSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||||
|
yearSpinnerIsInitialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateYearlySummaryList() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
var sum = BigDecimal("0.0")
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
yearlySummaryList = yearlySummaryRepository.getYearlySummary(year)
|
||||||
|
for (yearlySummary in yearlySummaryList) {
|
||||||
|
sum += BigDecimal(yearlySummary.total.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
yearlySummaryAdapter.updateYearlySummaryList(yearlySummaryList)
|
||||||
|
sumAmountTextView.text = convertDoubleToString(sum.toDouble())
|
||||||
|
|
||||||
|
val color = if (sum.toDouble() > 0) {
|
||||||
|
ContextCompat.getColor(this@YearlySummaryActivity, R.color.green)
|
||||||
|
} else {
|
||||||
|
ContextCompat.getColor(this@YearlySummaryActivity, R.color.red)
|
||||||
|
}
|
||||||
|
sumAmountTextView.setTextColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onYearlySummaryClick(yearlySummary: YearlySummary) {
|
||||||
|
val intent = Intent(this@YearlySummaryActivity, BankTransactionsActivity::class.java)
|
||||||
|
intent.putExtra("year", yearlySummary.year)
|
||||||
|
intent.putExtra("category", yearlySummary.category)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInitialized(): Boolean {
|
||||||
|
return yearSpinnerIsInitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import com.financialviewer.utils.convertStandardToGermanDate
|
||||||
|
|
||||||
|
class BankTransactionAdapter (
|
||||||
|
private val context: Context,
|
||||||
|
private val bankTransactionList: MutableList<BankTransaction>,
|
||||||
|
private val onItemClick: (BankTransaction) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<BankTransactionAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val date: TextView = itemView.findViewById(R.id.first)
|
||||||
|
val category: TextView = itemView.findViewById(R.id.second)
|
||||||
|
val amount: TextView = itemView.findViewById(R.id.third)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_three_equal_texts, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val bankTransaction = bankTransactionList[position]
|
||||||
|
val dateParts = convertStandardToGermanDate(bankTransaction.date).split(".")
|
||||||
|
holder.date.text = "${dateParts[0]}.${dateParts[1]}"
|
||||||
|
holder.category.text = bankTransaction.category
|
||||||
|
holder.amount.text = convertDoubleToString(bankTransaction.amount)
|
||||||
|
|
||||||
|
val color = if (bankTransaction.amount > 0) {
|
||||||
|
ContextCompat.getColor(context, R.color.green)
|
||||||
|
} else {
|
||||||
|
ContextCompat.getColor(context, R.color.red)
|
||||||
|
}
|
||||||
|
holder.amount.setTextColor(color)
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClick(bankTransaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = bankTransactionList.size
|
||||||
|
|
||||||
|
private fun initBankTransactionList() {
|
||||||
|
bankTransactionList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBankTransactionList(newList: List<BankTransaction>) {
|
||||||
|
initBankTransactionList()
|
||||||
|
bankTransactionList.addAll(newList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.FixedCost
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
|
||||||
|
class FixedCostAdapter (
|
||||||
|
private val context: Context,
|
||||||
|
private val fixedCostList: MutableList<FixedCost>,
|
||||||
|
private val onItemClick: (FixedCost) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<FixedCostAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val category: TextView = itemView.findViewById(R.id.first)
|
||||||
|
val amount: TextView = itemView.findViewById(R.id.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_two_equal_texts, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val fixedCost = fixedCostList[position]
|
||||||
|
holder.category.text = fixedCost.category
|
||||||
|
holder.amount.text = convertDoubleToString(fixedCost.amount)
|
||||||
|
holder.amount.setTextColor(ContextCompat.getColor(context, R.color.red))
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClick(fixedCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = fixedCostList.size
|
||||||
|
|
||||||
|
private fun initBankTransactionList() {
|
||||||
|
fixedCostList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateFixedCostList(newList: List<FixedCost>) {
|
||||||
|
initBankTransactionList()
|
||||||
|
fixedCostList.addAll(newList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
|
||||||
|
class FixedCostDetailsAdapter (
|
||||||
|
private val context: Context,
|
||||||
|
private val allSourceList: MutableList<BankTransaction>,
|
||||||
|
private val currentSourceList: MutableList<BankTransaction>,
|
||||||
|
private val onItemClick: (BankTransaction) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<FixedCostDetailsAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val date: TextView = itemView.findViewById(R.id.first)
|
||||||
|
val category: TextView = itemView.findViewById(R.id.second)
|
||||||
|
val amount: TextView = itemView.findViewById(R.id.third)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_three_equal_texts, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val bankTransaction = allSourceList[position]
|
||||||
|
holder.date.text = bankTransaction.date
|
||||||
|
holder.category.text = bankTransaction.category
|
||||||
|
holder.amount.text = convertDoubleToString(bankTransaction.amount)
|
||||||
|
holder.amount.setTextColor(ContextCompat.getColor(context, R.color.red))
|
||||||
|
|
||||||
|
if (currentSourceList.contains(bankTransaction)) {
|
||||||
|
holder.date.setTypeface(null, Typeface.BOLD)
|
||||||
|
holder.category.setTypeface(null, Typeface.BOLD)
|
||||||
|
holder.amount.setTypeface(null, Typeface.BOLD)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bankTransaction.isFixedCost) {
|
||||||
|
holder.date.setTypeface(null, Typeface.ITALIC)
|
||||||
|
holder.category.setTypeface(null, Typeface.ITALIC)
|
||||||
|
holder.amount.setTypeface(null, Typeface.ITALIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClick(bankTransaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = allSourceList.size
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.data.LoanMonthlyDetails
|
||||||
|
|
||||||
|
class LoanDetailsAdapter (
|
||||||
|
private val loanDetailsList: MutableList<LoanMonthlyDetails>
|
||||||
|
) : RecyclerView.Adapter<LoanDetailsAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val monthText: TextView = itemView.findViewById(R.id.first)
|
||||||
|
val interestText: TextView = itemView.findViewById(R.id.second)
|
||||||
|
val principalText: TextView = itemView.findViewById(R.id.third)
|
||||||
|
val remainingDebtText: TextView = itemView.findViewById(R.id.fourth)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_four_equal_texts, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val loanDetails = loanDetailsList[position]
|
||||||
|
holder.monthText.text = loanDetails.month
|
||||||
|
holder.interestText.text = loanDetails.interest
|
||||||
|
holder.principalText.text = loanDetails.principal
|
||||||
|
holder.remainingDebtText.text = loanDetails.remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = loanDetailsList.size
|
||||||
|
|
||||||
|
private fun initLoanDetailsList() {
|
||||||
|
loanDetailsList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLoanDetailsList(newList: List<LoanMonthlyDetails>) {
|
||||||
|
initLoanDetailsList()
|
||||||
|
loanDetailsList.addAll(newList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
|
||||||
|
class LoanOverviewAdapter (
|
||||||
|
private val loans: MutableList<Loan>,
|
||||||
|
private val onTitleClick: (Loan) -> Unit,
|
||||||
|
private val onEditClick: (Loan, String, String, Int) -> Unit
|
||||||
|
) : RecyclerView.Adapter<LoanOverviewAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val title: TextView = itemView.findViewById(R.id.loanTitle)
|
||||||
|
val amount: TextView = itemView.findViewById(R.id.loanAmount)
|
||||||
|
val rate: TextView = itemView.findViewById(R.id.loanRate)
|
||||||
|
val payment: TextView = itemView.findViewById(R.id.loanPayment)
|
||||||
|
val maturity: TextView = itemView.findViewById(R.id.loanMaturity)
|
||||||
|
val remainingLoan: TextView = itemView.findViewById(R.id.loanRemainingLoan)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_loan_overview, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val loan = loans[position]
|
||||||
|
holder.title.text = loan.title
|
||||||
|
holder.amount.text = loan.amount
|
||||||
|
holder.rate.text = loan.rate
|
||||||
|
holder.payment.text = loan.payment
|
||||||
|
holder.maturity.text = loan.maturity
|
||||||
|
holder.remainingLoan.text = loan.remainingLoan
|
||||||
|
|
||||||
|
// 设置 title 为加粗大号字体
|
||||||
|
holder.title.setTypeface(null, Typeface.BOLD)
|
||||||
|
holder.title.textSize = 20f
|
||||||
|
|
||||||
|
holder.title.setOnClickListener {
|
||||||
|
onTitleClick(loan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为可编辑字段设置点击事件
|
||||||
|
holder.rate.setOnClickListener {
|
||||||
|
onEditClick(loan, "rate", loan.rate, position)
|
||||||
|
}
|
||||||
|
holder.payment.setOnClickListener {
|
||||||
|
onEditClick(loan, "payment", loan.payment, position)
|
||||||
|
}
|
||||||
|
holder.maturity.setOnClickListener {
|
||||||
|
onEditClick(loan, "maturity", loan.maturity, position)
|
||||||
|
}
|
||||||
|
holder.remainingLoan.setOnClickListener {
|
||||||
|
onEditClick(loan, "remainingLoan", loan.remainingLoan, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = loans.size
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.MonthlySummary
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
import java.text.DateFormatSymbols
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class MonthlySummaryAdapter (
|
||||||
|
private val context: Context,
|
||||||
|
private val monthlySummaryList: MutableList<MonthlySummary>,
|
||||||
|
private val onItemClick: (MonthlySummary) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<MonthlySummaryAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val month: TextView = itemView.findViewById(R.id.first)
|
||||||
|
val income: TextView = itemView.findViewById(R.id.second)
|
||||||
|
val cost: TextView = itemView.findViewById(R.id.third)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_three_equal_texts, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val monthlySummary = monthlySummaryList[position]
|
||||||
|
holder.month.text = getMonthNameInGerman(monthlySummary.month)
|
||||||
|
holder.income.text = convertDoubleToString(monthlySummary.income)
|
||||||
|
holder.cost.text = convertDoubleToString(monthlySummary.cost)
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClick(monthlySummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.income.setTextColor(ContextCompat.getColor(context, R.color.green))
|
||||||
|
holder.cost.setTextColor(ContextCompat.getColor(context, R.color.red))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = monthlySummaryList.size
|
||||||
|
|
||||||
|
private fun initMonthlySummaryList() {
|
||||||
|
monthlySummaryList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateMonthlySummaryList(newList: List<MonthlySummary>) {
|
||||||
|
initMonthlySummaryList()
|
||||||
|
monthlySummaryList.addAll(newList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMonthNameInGerman(month: Int): String {
|
||||||
|
// 获取德文的月份名称数组
|
||||||
|
val monthsInGerman = DateFormatSymbols(Locale.GERMAN).months
|
||||||
|
// 检查输入是否在有效范围内
|
||||||
|
return if (month in 1..12) monthsInGerman[month - 1] else "Ungültiger Monat"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.financialviewer.ui.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.financialviewer.R
|
||||||
|
import com.financialviewer.db.YearlySummary
|
||||||
|
import com.financialviewer.utils.convertDoubleToString
|
||||||
|
|
||||||
|
class YearlySummaryAdapter (
|
||||||
|
private val context: Context,
|
||||||
|
private val yearlySummaryList: MutableList<YearlySummary>,
|
||||||
|
private val onItemClick: (YearlySummary) -> Unit,
|
||||||
|
) : RecyclerView.Adapter<YearlySummaryAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val category: TextView = itemView.findViewById(R.id.first)
|
||||||
|
val amount: TextView = itemView.findViewById(R.id.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_two_equal_texts, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val yearlySummary = yearlySummaryList[position]
|
||||||
|
holder.category.text = yearlySummary.category
|
||||||
|
holder.amount.text = convertDoubleToString(yearlySummary.total)
|
||||||
|
|
||||||
|
val color = if (yearlySummary.total > 0) {
|
||||||
|
ContextCompat.getColor(context, R.color.green)
|
||||||
|
} else {
|
||||||
|
ContextCompat.getColor(context, R.color.red)
|
||||||
|
}
|
||||||
|
holder.amount.setTextColor(color)
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClick(yearlySummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = yearlySummaryList.size
|
||||||
|
|
||||||
|
private fun initYearlySummaryList() {
|
||||||
|
yearlySummaryList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateYearlySummaryList(newList: List<YearlySummary>) {
|
||||||
|
initYearlySummaryList()
|
||||||
|
yearlySummaryList.addAll(newList)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/src/main/java/com/financialviewer/utils/BankRefParser.kt
Normal file
33
app/src/main/java/com/financialviewer/utils/BankRefParser.kt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package com.financialviewer.utils
|
||||||
|
|
||||||
|
import com.financialviewer.constants.COUNTERPARTY_MAP
|
||||||
|
|
||||||
|
fun getCounterpartyKey(ref: String): String? {
|
||||||
|
return COUNTERPARTY_MAP.keys.find { key -> ref.contains(key) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCounterparty(ref: String): String {
|
||||||
|
var counterparty = ""
|
||||||
|
val counterpartyOriginal = getCounterpartyKey(ref)
|
||||||
|
if (counterpartyOriginal != null) {
|
||||||
|
counterparty = COUNTERPARTY_MAP[counterpartyOriginal]?.first ?: ""
|
||||||
|
}
|
||||||
|
return counterparty
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCategory(ref: String): String {
|
||||||
|
var category = ""
|
||||||
|
val counterpartyOriginal = getCounterpartyKey(ref)
|
||||||
|
if (counterpartyOriginal != null) {
|
||||||
|
category = COUNTERPARTY_MAP[counterpartyOriginal]?.second ?: ""
|
||||||
|
}
|
||||||
|
return category
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBankTransactionRefId(ref: String): String {
|
||||||
|
val regex = """\b(\w+)\s+IBAN\b""".toRegex() // 匹配 string1 和 "IBAN"
|
||||||
|
val matchResult = regex.find(ref)
|
||||||
|
val refId = matchResult?.groupValues?.get(1) ?: ""
|
||||||
|
return refId
|
||||||
|
}
|
||||||
|
|
||||||
66
app/src/main/java/com/financialviewer/utils/Common.kt
Normal file
66
app/src/main/java/com/financialviewer/utils/Common.kt
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package com.financialviewer.utils
|
||||||
|
|
||||||
|
import com.financialviewer.constants.CATEGORY_LIST
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
|
||||||
|
fun pathJoin(vararg parts: String): String {
|
||||||
|
return Paths.get(parts[0], *parts.sliceArray(1 until parts.size)).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateYears(latestYearOfBankTransaction: String): List<String> {
|
||||||
|
return (2024..latestYearOfBankTransaction.toInt()).map { it.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertItemIntoList(list: List<String>, item: String, index: Int): List<String> {
|
||||||
|
val mutableList = list.toMutableList()
|
||||||
|
mutableList.add(index, item)
|
||||||
|
return mutableList.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateMonth(): List<String> {
|
||||||
|
return (1..12).map { it.toString().padStart(2, '0') }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateCategory(): List<String> {
|
||||||
|
val mutableList: MutableList<String> = mutableListOf()
|
||||||
|
for (firstLevel in CATEGORY_LIST) {
|
||||||
|
for (secondLevel in firstLevel.subList) {
|
||||||
|
mutableList.add(secondLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mutableList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateCategoryWithMainCategory(): List<String> {
|
||||||
|
val mutableList: MutableList<String> = mutableListOf()
|
||||||
|
for (firstLevel in CATEGORY_LIST) {
|
||||||
|
mutableList.add(firstLevel.title)
|
||||||
|
for (secondLevel in firstLevel.subList) {
|
||||||
|
mutableList.add(secondLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mutableList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateOnlyMainCategory(): List<String> {
|
||||||
|
val mutableList: MutableList<String> = mutableListOf()
|
||||||
|
for (firstLevel in CATEGORY_LIST) {
|
||||||
|
mutableList.add(firstLevel.title)
|
||||||
|
}
|
||||||
|
return mutableList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedCategory(selectedItem: String): MutableList<String> {
|
||||||
|
val categoryList: MutableList<String> = mutableListOf()
|
||||||
|
if (selectedItem.contains(".")) {
|
||||||
|
val firstLevelItem = CATEGORY_LIST.find { it.title == selectedItem }
|
||||||
|
if (firstLevelItem != null) {
|
||||||
|
categoryList.addAll(firstLevelItem.subList)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
categoryList.add(selectedItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryList
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.financialviewer.utils
|
||||||
|
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.text.DecimalFormatSymbols
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
fun convertStringToDouble(value: String): Double {
|
||||||
|
val cleanedValue = value.replace(".", "").replace(",", ".")
|
||||||
|
return cleanedValue.toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertInputStringToDouble(value: String): Double {
|
||||||
|
return if (value.contains(".") && value.contains(",")) {
|
||||||
|
convertStringToDouble(value)
|
||||||
|
} else {
|
||||||
|
value.replace(",", ".").toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertDoubleToString(value: Double): String {
|
||||||
|
val symbols = DecimalFormatSymbols(Locale.GERMAN) // 使用德语区域符号
|
||||||
|
val decimalFormat = DecimalFormat("#,##0.00", symbols) // 格式化规则
|
||||||
|
return decimalFormat.format(value) // 返回格式化后的字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertPercentageStringToDouble(value: String): Double {
|
||||||
|
val cleanedValue = value.replace("%", "")
|
||||||
|
val normalizedValue = cleanedValue.replace(",", ".")
|
||||||
|
return normalizedValue.toDouble() / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertPercentageToString(value: Double): String {
|
||||||
|
val symbols = DecimalFormatSymbols(Locale.GERMAN) // 使用德语区域符号
|
||||||
|
val decimalFormat = DecimalFormat("#0.00%", symbols)
|
||||||
|
return decimalFormat.format(value) // 返回格式化后的字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertStringToGermanDate(value: String): String {
|
||||||
|
val inputFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMAN)
|
||||||
|
val outputFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMAN)
|
||||||
|
val date = LocalDate.parse(value, inputFormatter)
|
||||||
|
return outputFormatter.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertGermanDateToStandard(dateString: String): String {
|
||||||
|
val germanFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMAN)
|
||||||
|
val standardFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
|
val localDate = LocalDate.parse(dateString, germanFormatter)
|
||||||
|
return localDate.format(standardFormatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertStandardToGermanDate(dateString: String): String {
|
||||||
|
val standardFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
|
val germanFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMANY)
|
||||||
|
val localDate = LocalDate.parse(dateString, standardFormatter)
|
||||||
|
return localDate.format(germanFormatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun windowsFileTimeToMillis(windowsFileTime: Long): Long {
|
||||||
|
return (windowsFileTime / 10000) - 11644473600000
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.financialviewer.utils
|
||||||
|
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
|
||||||
|
fun calculateRemainingLoan(loan: Loan, monthDiff: Int): String {
|
||||||
|
var remainingLoan = convertStringToDouble(loan.remainingLoan)
|
||||||
|
val payment = convertStringToDouble(loan.payment)
|
||||||
|
val rate = convertPercentageStringToDouble(loan.rate)
|
||||||
|
|
||||||
|
for (i in 0 until monthDiff) {
|
||||||
|
val interest = (remainingLoan * rate) / 12
|
||||||
|
val calResult = remainingLoan - payment + interest
|
||||||
|
remainingLoan = calResult
|
||||||
|
}
|
||||||
|
return convertDoubleToString(remainingLoan)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateNextInterest(loan: Loan, base: Double): Double {
|
||||||
|
val rate = convertPercentageStringToDouble(loan.rate)
|
||||||
|
return (base * rate) / 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateNextPrincipal(loan: Loan, base: Double): Double {
|
||||||
|
val payment = convertStringToDouble(loan.payment)
|
||||||
|
val interest = calculateNextInterest(loan, base)
|
||||||
|
return payment - interest
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateNextRemaining(loan: Loan, base: Double): Double {
|
||||||
|
val principal = calculateNextPrincipal(loan, base)
|
||||||
|
return base - principal
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextMonth(year: Int, month: Int): String {
|
||||||
|
var newYear = year
|
||||||
|
var newMonth = month
|
||||||
|
if (month == 12) {
|
||||||
|
newYear += 1
|
||||||
|
newMonth = 1
|
||||||
|
} else {
|
||||||
|
newMonth += 1
|
||||||
|
}
|
||||||
|
return newMonth.toString().padStart(2, '0') + "." + newYear.toString()
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.financialviewer.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.File
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class SharedPreferencesHelper(
|
||||||
|
private val context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val fileName = "app_prefs"
|
||||||
|
private val sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
private fun saveData(key: String, value: String) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.putString(key, value)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveDefaultYear(value: Int) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.putInt("default_year_of_bank_Transaction", value)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAuthStatus(key: String, value: Boolean) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.putBoolean(key, value)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveCurrentUpdateTimeToSharedPreferences() {
|
||||||
|
val currentDateTime = LocalDateTime.now()
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
|
val formattedDateTime = currentDateTime.format(formatter)
|
||||||
|
|
||||||
|
saveData("bank_transaction_last_Update_date", formattedDateTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveCalculateFixedCostTime() {
|
||||||
|
val currentDateTime = LocalDateTime.now()
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
|
val formattedDateTime = currentDateTime.format(formatter)
|
||||||
|
|
||||||
|
saveData("calculate_fixed_cost_time", formattedDateTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readData(key: String): String {
|
||||||
|
return sharedPreferences.getString(key, "") ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readDefaultYear(defaultValue: Int): Int {
|
||||||
|
return sharedPreferences.getInt("default_year_of_bank_Transaction", defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readLastUpdateTimeToSharedPreferences(): String {
|
||||||
|
return readData("bank_transaction_last_Update_date")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readCalculateFixedCostTime(): String {
|
||||||
|
return readData("calculate_fixed_cost_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllSharedPreferences(): MutableList<String> {
|
||||||
|
val allEntries: Map<String, *> = sharedPreferences.all
|
||||||
|
for ((key, value) in allEntries) {
|
||||||
|
Log.d("SharedPreferences", "$key: $value")
|
||||||
|
|
||||||
|
}
|
||||||
|
return sharedPreferences.all.keys.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSpecificSharedPreference(key: String) {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.remove(key)
|
||||||
|
editor.apply() // or editor.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAllSharedPreferences() {
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
editor.clear()
|
||||||
|
editor.apply() // or editor.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteSharedPreferencesFile(fileName: String) {
|
||||||
|
val dir = context.filesDir.parentFile
|
||||||
|
val sharedPrefsFile = File(dir, "shared_prefs/$fileName.xml")
|
||||||
|
if (sharedPrefsFile.exists()) {
|
||||||
|
sharedPrefsFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/src/main/java/com/financialviewer/work/StartupWorker.kt
Normal file
28
app/src/main/java/com/financialviewer/work/StartupWorker.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.financialviewer.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.financialviewer.repository.FixedCostRepository
|
||||||
|
|
||||||
|
class StartupWorker (context: Context, workerParams: WorkerParameters) :
|
||||||
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
|
||||||
|
val updateLoans = UpdateLoans(context = applicationContext)
|
||||||
|
updateLoans.initDB()
|
||||||
|
updateLoans.updateDB()
|
||||||
|
|
||||||
|
val updateBankTransactions = UpdateBankTransactions(context = applicationContext)
|
||||||
|
updateBankTransactions.initDB()
|
||||||
|
|
||||||
|
val fixedCostRepository = FixedCostRepository(context = applicationContext)
|
||||||
|
val updateFixedCosts = UpdateFixedCosts(context = applicationContext)
|
||||||
|
if (fixedCostRepository.getRowCount() == 0) {
|
||||||
|
updateFixedCosts.updateDB()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package com.financialviewer.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.constants.COMDIRECT_LIST
|
||||||
|
import com.financialviewer.constants.FIXED_COST_TYPE
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.network.SmbClientHelper
|
||||||
|
import com.financialviewer.repository.BankTransactionRepository
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import com.financialviewer.utils.convertGermanDateToStandard
|
||||||
|
import com.financialviewer.utils.convertStringToDouble
|
||||||
|
import com.financialviewer.utils.getBankTransactionRefId
|
||||||
|
import com.financialviewer.utils.getCategory
|
||||||
|
import com.financialviewer.utils.getCounterparty
|
||||||
|
import com.financialviewer.utils.windowsFileTimeToMillis
|
||||||
|
import com.hierynomus.smbj.share.File
|
||||||
|
import com.opencsv.CSVReaderBuilder
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class UpdateBankTransactions (context: Context) {
|
||||||
|
private val bankTransactionRepository = BankTransactionRepository(context)
|
||||||
|
private val appPreferences = SharedPreferencesHelper(context)
|
||||||
|
private var smbClientHelper = SmbClientHelper(context)
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
|
||||||
|
suspend fun initDB() {
|
||||||
|
if (bankTransactionRepository.getRowCount() == 0) {
|
||||||
|
for (comdirect in COMDIRECT_LIST) {
|
||||||
|
bankTransactionRepository.insertBankTransaction(comdirect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateDB() {
|
||||||
|
var isUpdated = false
|
||||||
|
var isConnect = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
isConnect = smbClientHelper.connect()
|
||||||
|
if (isConnect) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val csvFiles = smbClientHelper.getCSVFromShare()
|
||||||
|
if (csvFiles.isNotEmpty()) {
|
||||||
|
for (fileInfo in csvFiles) {
|
||||||
|
val file = smbClientHelper.getCSVFile(fileInfo.fileName)
|
||||||
|
|
||||||
|
val fileTime = fileInfo.lastWriteTime.windowsTimeStamp
|
||||||
|
if (updateNecessary(fileTime)) {
|
||||||
|
insertBankTransactionsIntoDB(file)
|
||||||
|
isUpdated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (isConnect) {
|
||||||
|
smbClientHelper.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUpdated) {
|
||||||
|
appPreferences.saveCurrentUpdateTimeToSharedPreferences()
|
||||||
|
val year = bankTransactionRepository.getLatestYearOfBankTransaction()
|
||||||
|
appPreferences.saveDefaultYear(year.toInt())
|
||||||
|
|
||||||
|
updateSummary(appContext)
|
||||||
|
UpdateFixedCosts(appContext).updateDB()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNecessary(fileTime: Long): Boolean {
|
||||||
|
var necessary = false
|
||||||
|
val dateString = appPreferences.readLastUpdateTimeToSharedPreferences()
|
||||||
|
|
||||||
|
if (dateString != "") {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
|
val localDateTime = LocalDateTime.parse(dateString, formatter)
|
||||||
|
val instantFromSharedPref = localDateTime.atZone(ZoneId.systemDefault()).toInstant()
|
||||||
|
|
||||||
|
val fileTimeMillis = windowsFileTimeToMillis(fileTime)
|
||||||
|
val instantFromFileTime = Instant.ofEpochMilli(fileTimeMillis)
|
||||||
|
|
||||||
|
if (instantFromSharedPref.isBefore(instantFromFileTime)) {
|
||||||
|
necessary = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
necessary = true
|
||||||
|
}
|
||||||
|
return necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun insertBankTransactionsIntoDB(file: File) {
|
||||||
|
file.inputStream.use { inputStream ->
|
||||||
|
val content = inputStream.readBytes()
|
||||||
|
val byteArrayInputStream = ByteArrayInputStream(content)
|
||||||
|
|
||||||
|
CSVReaderBuilder(InputStreamReader(byteArrayInputStream, Charsets.UTF_8))
|
||||||
|
.withCSVParser(com.opencsv.CSVParserBuilder().withSeparator(';').build()) // 指定分隔符
|
||||||
|
.build().use { csvReader ->
|
||||||
|
|
||||||
|
val rows = csvReader.readAll()
|
||||||
|
var account = ""
|
||||||
|
if (rows.isNotEmpty()) {
|
||||||
|
account = rows[0][1]
|
||||||
|
}
|
||||||
|
rows.forEach { row ->
|
||||||
|
val reference = row.last()
|
||||||
|
val refId = getBankTransactionRefId(reference)
|
||||||
|
if (bankTransactionRepository.getBankTransactionById(refId) == null) {
|
||||||
|
val counterparty = getCounterparty(reference)
|
||||||
|
if (counterparty != "") {
|
||||||
|
val date = row.first()
|
||||||
|
val standardDate = convertGermanDateToStandard(date)
|
||||||
|
val amount = convertStringToDouble(row[2])
|
||||||
|
val category = getCategory(reference)
|
||||||
|
bankTransactionRepository.insertBankTransaction(
|
||||||
|
BankTransaction(
|
||||||
|
refId,
|
||||||
|
account,
|
||||||
|
standardDate,
|
||||||
|
amount,
|
||||||
|
category,
|
||||||
|
counterparty,
|
||||||
|
reference,
|
||||||
|
FIXED_COST_TYPE.containsKey(category)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
app/src/main/java/com/financialviewer/work/UpdateFixedCosts.kt
Normal file
120
app/src/main/java/com/financialviewer/work/UpdateFixedCosts.kt
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package com.financialviewer.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.constants.FIXED_COST_COUNT
|
||||||
|
import com.financialviewer.constants.FIXED_COST_TYPE
|
||||||
|
import com.financialviewer.constants.MONTHLY
|
||||||
|
import com.financialviewer.constants.QUARTERLY
|
||||||
|
import com.financialviewer.constants.YEAR
|
||||||
|
import com.financialviewer.constants.YEARLY
|
||||||
|
import com.financialviewer.db.BankTransaction
|
||||||
|
import com.financialviewer.db.FixedCost
|
||||||
|
import com.financialviewer.repository.BankTransactionRepository
|
||||||
|
import com.financialviewer.repository.FixedCostRepository
|
||||||
|
import com.financialviewer.utils.SharedPreferencesHelper
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class UpdateFixedCosts(context: Context) {
|
||||||
|
private val bankTransactionRepository = BankTransactionRepository(context)
|
||||||
|
private val fixedCostRepository = FixedCostRepository(context)
|
||||||
|
private val appPreferences = SharedPreferencesHelper(context)
|
||||||
|
|
||||||
|
private var year = appPreferences.readDefaultYear(YEAR)
|
||||||
|
|
||||||
|
suspend fun updateDB() {
|
||||||
|
for (item in FIXED_COST_TYPE) {
|
||||||
|
val category = item.key
|
||||||
|
when (item.value) {
|
||||||
|
MONTHLY -> {
|
||||||
|
val bankTransactionList = getBankTransactionList(category, MONTHLY)
|
||||||
|
var sum = calculateAmount(bankTransactionList, category)
|
||||||
|
if (category == "手机费") {
|
||||||
|
sum += -10.0
|
||||||
|
}
|
||||||
|
val refIds = bankTransactionList.map(BankTransaction::refId)
|
||||||
|
updateFixedCost(category, sum, MONTHLY, refIds)
|
||||||
|
}
|
||||||
|
QUARTERLY -> {
|
||||||
|
val bankTransactionList = getBankTransactionList(category, QUARTERLY)
|
||||||
|
val sum = calculateAmount(bankTransactionList, category)
|
||||||
|
val refIds = bankTransactionList.map(BankTransaction::refId)
|
||||||
|
updateFixedCost(category, sum, QUARTERLY, refIds)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val bankTransactionList = getBankTransactionList(category, YEARLY)
|
||||||
|
val sum = calculateAmount(bankTransactionList, category)
|
||||||
|
val refIds = bankTransactionList.map(BankTransaction::refId)
|
||||||
|
updateFixedCost(category, sum, YEARLY, refIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appPreferences.saveCalculateFixedCostTime()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getBankTransactionList(category: String, type: String): MutableList<BankTransaction> {
|
||||||
|
var resultList: MutableList<BankTransaction>
|
||||||
|
var bankTransactionList =
|
||||||
|
bankTransactionRepository.getBankTransactionsByCategory(year.toString(), category)
|
||||||
|
if (bankTransactionList.isEmpty()) {
|
||||||
|
bankTransactionList =
|
||||||
|
bankTransactionRepository.getBankTransactionsByCategory((year - 1).toString(), category)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
YEARLY -> {
|
||||||
|
resultList = bankTransactionList
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
resultList = getBankTransactionsFromLastMonth(bankTransactionList)
|
||||||
|
while (resultList.size != FIXED_COST_COUNT[category] && bankTransactionList.size > 0) {
|
||||||
|
bankTransactionList.removeAll(resultList)
|
||||||
|
resultList = getBankTransactionsFromLastMonth(bankTransactionList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBankTransactionsFromLastMonth(bankTransactionList: MutableList<BankTransaction>): MutableList<BankTransaction> {
|
||||||
|
val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
|
|
||||||
|
val latestDate = LocalDate.parse(bankTransactionList.first().date, dateFormat)
|
||||||
|
val latestYear = latestDate.year
|
||||||
|
val latestMonth = latestDate.month
|
||||||
|
|
||||||
|
return bankTransactionList.filter {
|
||||||
|
val itemDate = LocalDate.parse(it.date, dateFormat)
|
||||||
|
itemDate.year == latestYear && itemDate.month == latestMonth && it.isFixedCost
|
||||||
|
}.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateAmount(bankTransactionList: MutableList<BankTransaction>, category: String): Double {
|
||||||
|
var sum = 0.0
|
||||||
|
if (bankTransactionList.size == FIXED_COST_COUNT[category]) {
|
||||||
|
for (bankTransaction in bankTransactionList) {
|
||||||
|
sum += bankTransaction.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateFixedCost(category: String, sum: Double, type: String, refIds: List<String>) {
|
||||||
|
val fixedCost = fixedCostRepository.getFixedCostByCategory(category)
|
||||||
|
if (fixedCost == null) {
|
||||||
|
fixedCostRepository.insertFixedCost(
|
||||||
|
FixedCost(
|
||||||
|
category = category,
|
||||||
|
amount = sum,
|
||||||
|
type = type,
|
||||||
|
refIds = refIds.joinToString(",")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fixedCost.amount = sum
|
||||||
|
fixedCost.refIds = refIds.joinToString(",")
|
||||||
|
fixedCostRepository.updateFixedCost(fixedCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
app/src/main/java/com/financialviewer/work/UpdateLoans.kt
Normal file
56
app/src/main/java/com/financialviewer/work/UpdateLoans.kt
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package com.financialviewer.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.data.YearMonthDay
|
||||||
|
import com.financialviewer.db.Loan
|
||||||
|
import com.financialviewer.repository.LoanRepository
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class UpdateLoans(context: Context) {
|
||||||
|
private val germanyTimeZone = TimeZone.getTimeZone("Europe/Berlin")
|
||||||
|
private val loanRepository = LoanRepository(context)
|
||||||
|
|
||||||
|
suspend fun initDB() {
|
||||||
|
if (loanRepository.getRowCount() == 0) {
|
||||||
|
loanRepository.initDB()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateDB() {
|
||||||
|
val allLoansInDB = loanRepository.getAllLoans()
|
||||||
|
|
||||||
|
val monthDiff = getMonthDiff(allLoansInDB[0])
|
||||||
|
if ( monthDiff > 0) {
|
||||||
|
for (loan in allLoansInDB) {
|
||||||
|
loanRepository.updateRemainingLoan(loan, monthDiff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLastUpdateDate(loan: Loan): YearMonthDay {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = loan.lastUpdate
|
||||||
|
return YearMonthDay(
|
||||||
|
calendar.get(Calendar.YEAR),
|
||||||
|
calendar.get(Calendar.MONTH) + 1,
|
||||||
|
calendar.get(Calendar.DAY_OF_MONTH)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMonthDiff(loan: Loan): Int {
|
||||||
|
val calendar = Calendar.getInstance(germanyTimeZone)
|
||||||
|
val currentDate = YearMonthDay(
|
||||||
|
calendar.get(Calendar.YEAR),
|
||||||
|
calendar.get(Calendar.MONTH) + 1,
|
||||||
|
calendar.get(Calendar.DAY_OF_MONTH)
|
||||||
|
)
|
||||||
|
val lastUpdateDate = getLastUpdateDate(loan)
|
||||||
|
|
||||||
|
val yearDiff = currentDate.year - lastUpdateDate.year
|
||||||
|
val monthDiff = currentDate.month - lastUpdateDate.month
|
||||||
|
|
||||||
|
return yearDiff * 12 + monthDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
84
app/src/main/java/com/financialviewer/work/UpdateSummary.kt
Normal file
84
app/src/main/java/com/financialviewer/work/UpdateSummary.kt
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package com.financialviewer.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.financialviewer.constants.CATEGORY_LIST
|
||||||
|
import com.financialviewer.db.MonthlySummary
|
||||||
|
import com.financialviewer.db.YearlySummary
|
||||||
|
import com.financialviewer.repository.BankTransactionRepository
|
||||||
|
import com.financialviewer.repository.MonthlySummaryRepository
|
||||||
|
import com.financialviewer.repository.YearlySummaryRepository
|
||||||
|
import com.financialviewer.utils.generateYears
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
suspend fun updateSummary(context: Context) {
|
||||||
|
val bankTransactionRepository = BankTransactionRepository(context)
|
||||||
|
val monthlySummaryRepository = MonthlySummaryRepository(context)
|
||||||
|
val yearlySummaryRepository = YearlySummaryRepository(context)
|
||||||
|
|
||||||
|
|
||||||
|
val latestYear = bankTransactionRepository.getLatestYearOfBankTransaction()
|
||||||
|
val yearList = generateYears(latestYear)
|
||||||
|
val categoryList = getCategoryList()
|
||||||
|
for (year in yearList) {
|
||||||
|
for (category in categoryList) {
|
||||||
|
var sum = BigDecimal("0.0")
|
||||||
|
val transactionList = bankTransactionRepository.getBankTransactionsByCategory(year, category)
|
||||||
|
for (transaction in transactionList) {
|
||||||
|
sum += BigDecimal(transaction.amount.toString())
|
||||||
|
}
|
||||||
|
val yearlySummary = yearlySummaryRepository.getYearlySummaryByCategory(year.toInt(), category)
|
||||||
|
if (yearlySummary == null) {
|
||||||
|
yearlySummaryRepository.insertYearlySummary(
|
||||||
|
YearlySummary(
|
||||||
|
year = year.toInt(),
|
||||||
|
category = category,
|
||||||
|
total = sum.toDouble()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
yearlySummary.total = sum.toDouble()
|
||||||
|
yearlySummaryRepository.updateYearlySummary(yearlySummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 1..12) {
|
||||||
|
var incomeSum = BigDecimal("0.0")
|
||||||
|
var costSum = BigDecimal("0.0")
|
||||||
|
val transactionList = bankTransactionRepository.getBankTransactionsByMonth(year, i.toString().padStart(2, '0'))
|
||||||
|
for (transaction in transactionList) {
|
||||||
|
if (transaction.amount > 0) {
|
||||||
|
incomeSum += BigDecimal(transaction.amount.toString())
|
||||||
|
} else {
|
||||||
|
costSum += BigDecimal(transaction.amount.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (incomeSum.toDouble() > 0 || costSum.toDouble() > 0) {
|
||||||
|
val monthlySummary = monthlySummaryRepository.getMonthlySummaryByMonth(year.toInt(), i)
|
||||||
|
if (monthlySummary == null) {
|
||||||
|
monthlySummaryRepository.insertMonthlySummary(
|
||||||
|
MonthlySummary(
|
||||||
|
year = year.toInt(),
|
||||||
|
month = i,
|
||||||
|
income = incomeSum.toDouble(),
|
||||||
|
cost = costSum.toDouble()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
monthlySummary.income = incomeSum.toDouble()
|
||||||
|
monthlySummary.cost = costSum.toDouble()
|
||||||
|
monthlySummaryRepository.updateMonthlySummary(monthlySummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCategoryList(): MutableList<String> {
|
||||||
|
val categoryList: MutableList<String> = mutableListOf()
|
||||||
|
for (firstLevel in CATEGORY_LIST) {
|
||||||
|
for (secondLevel in firstLevel.subList) {
|
||||||
|
categoryList.add(secondLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return categoryList
|
||||||
|
}
|
||||||
1
app/src/main/res/drawable/fixed_costs_24.xml
Normal file
1
app/src/main/res/drawable/fixed_costs_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="m5.5,10c-3.084,0-5.5,1.318-5.5,3v8c0,1.682,2.416,3,5.5,3s5.5-1.318,5.5-3v-8c0-1.682-2.416-3-5.5-3Zm0,13c-2.652,0-4.5-1.054-4.5-2v-2.254c.987.764,2.62,1.254,4.5,1.254s3.513-.49,4.5-1.254v2.254c0,.946-1.848,2-4.5,2Zm0-4c-2.652,0-4.5-1.054-4.5-2v-2.254c.987.764,2.62,1.254,4.5,1.254s3.513-.49,4.5-1.254v2.254c0,.946-1.848,2-4.5,2Zm0-4c-2.652,0-4.5-1.054-4.5-2s1.848-2,4.5-2,4.5,1.054,4.5,2-1.848,2-4.5,2Zm14.5-11h-12v5h12v-5Zm-1,4h-10v-3h10v3Zm1,5h-1v-2h1v2Zm-3,0h-1v-2h1v2Zm-3,0h-1v-2h1v2Zm-1,2h1v2h-1v-2Zm3,0h1v2h-1v-2Zm4,2h-1v-2h1v2Zm-7,2h7v1h-7v-1ZM24,2.5v21.5h-12.387c.329-.307.611-.639.828-1h10.559V2.5c0-.827-.673-1.5-1.5-1.5H6.5c-.827,0-1.5.673-1.5,1.5v5.515c-.341.013-.676.035-1,.074V2.5c0-1.378,1.121-2.5,2.5-2.5h15c1.379,0,2.5,1.122,2.5,2.5Z"/></vector>
|
||||||
74
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
74
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
android:height="108dp"
|
||||||
|
android:width="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#E7BD95"
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
</vector>
|
||||||
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
1
app/src/main/res/drawable/loan_overview_24.xml
Normal file
1
app/src/main/res/drawable/loan_overview_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="m22.275,6.833l-1.275-.999v-3.334c0-.276-.224-.5-.5-.5s-.5.224-.5.5v2.551L14.775.958c-1.634-1.28-3.916-1.28-5.55,0L1.725,6.833c-1.096.859-1.725,2.15-1.725,3.542v9.124c0,2.481,2.019,4.5,4.5,4.5h15c2.481,0,4.5-2.019,4.5-4.5v-9.124c0-1.393-.629-2.684-1.725-3.542Zm.725,12.667c0,1.93-1.57,3.5-3.5,3.5H4.5c-1.93,0-3.5-1.57-3.5-3.5v-9.124c0-1.083.489-2.087,1.342-2.755L9.841,1.745c.636-.498,1.397-.747,2.159-.747s1.523.249,2.158.747l7.5,5.876c.853.668,1.342,1.672,1.342,2.755v9.124Zm-8.063-9.757l-5,9c-.091.165-.262.257-.438.257-.082,0-.166-.02-.242-.063-.242-.134-.329-.438-.194-.68l5-9c.134-.241.439-.33.68-.194.242.134.329.438.194.68Zm.063,5.257c-1.103,0-2,.897-2,2s.897,2,2,2,2-.897,2-2-.897-2-2-2Zm0,3c-.551,0-1-.449-1-1s.449-1,1-1,1,.449,1,1-.449,1-1,1Zm-4-7c0-1.103-.897-2-2-2s-2,.897-2,2,.897,2,2,2,2-.897,2-2Zm-2,1c-.551,0-1-.449-1-1s.449-1,1-1,1,.449,1,1-.449,1-1,1Z"/></vector>
|
||||||
1
app/src/main/res/drawable/monthly_summary_24.xml
Normal file
1
app/src/main/res/drawable/monthly_summary_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="M23.961,10.306h0v-.002l-3.425-8.134c-.184-.436-.501-.779-.883-1.013-.046-.049-.102-.091-.169-.119-.026-.011-.054-.01-.082-.016-.448-.201-.96-.259-1.461-.125l-5.44,1.449V.5c0-.276-.224-.5-.5-.5s-.5,.224-.5,.5V2.613l-5.723,1.524c-1.042,.277-1.912,1.036-2.328,2.029L.039,14.307c-.026,.062-.039,.986-.039,.986,0,2.43,1.8,4.49,4.099,4.689,.137,.012,.272,.018,.407,.018,1.129,0,2.194-.412,3.034-1.183,.928-.851,1.46-2.06,1.46-3.317,0,0-.015-1.139-.043-1.203L5.088,5.592c.275-.227,.597-.396,.947-.489l5.465-1.456V23H4.5c-.276,0-.5,.224-.5,.5s.224,.5,.5,.5h15c.276,0,.5-.224,.5-.5s-.224-.5-.5-.5h-7V3.382l5.698-1.517c.137-.037,.275-.044,.409-.032l-3.568,8.474c-.026,.061-.039,.986-.039,.986,0,2.43,1.8,4.49,4.099,4.689,.137,.012,.272,.018,.407,.018,1.129,0,2.194-.412,3.034-1.183,.928-.851,1.46-2.06,1.46-3.317,0,0-.012-1.13-.039-1.194Zm-15.961,5.194c0,.979-.414,1.919-1.136,2.581-.731,.671-1.678,.992-2.679,.906-1.786-.155-3.186-1.778-3.186-3.694v-.292h7v.5Zm-.269-1.5H1.252l3.12-7.447,.024-.056,3.335,7.503ZM19.475,2.347c.048,.069,.105,.131,.138,.211l3.133,7.441h-6.494l3.222-7.653Zm3.525,9.153c0,.979-.414,1.919-1.136,2.581-.731,.67-1.679,.995-2.679,.906-1.786-.155-3.186-1.778-3.186-3.694v-.292h7v.5Z"/></vector>
|
||||||
1
app/src/main/res/drawable/settings_24.xml
Normal file
1
app/src/main/res/drawable/settings_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="M9.8,9.802c-.755,.753-1.122,1.476-1.122,2.208,0,.957,.64,1.706,1.122,2.187,.74,.739,1.47,1.108,2.2,1.108s1.46-.37,2.2-1.108c.755-.753,1.122-1.476,1.122-2.208,0-.957-.64-1.706-1.122-2.187-1.48-1.478-2.92-1.478-4.4,0Zm3.694,3.688c-1.251,1.248-2.042,.944-2.988,0-.565-.564-.828-1.034-.828-1.479,0-.454,.271-.944,.828-1.5,.583-.582,1.065-.826,1.511-.826,.511,0,.972,.322,1.478,.826,.565,.564,.828,1.034,.828,1.479,0,.454-.271,.944-.828,1.5Z"/><path android:fillColor="@android:color/white" android:pathData="M9.8,9.802c-.755,.753-1.122,1.476-1.122,2.208,0,.957,.64,1.706,1.122,2.187,.74,.739,1.47,1.108,2.2,1.108s1.46-.37,2.2-1.108c.755-.753,1.122-1.476,1.122-2.208,0-.957-.64-1.706-1.122-2.187-1.48-1.478-2.92-1.478-4.4,0Zm3.694,3.688c-1.251,1.248-2.042,.944-2.988,0-.565-.564-.828-1.034-.828-1.479,0-.454,.271-.944,.828-1.5,.583-.582,1.065-.826,1.511-.826,.511,0,.972,.322,1.478,.826,.565,.564,.828,1.034,.828,1.479,0,.454-.271,.944-.828,1.5Z"/><path android:fillColor="@android:color/white" android:pathData="M23.019,11.948c.008-1.595-.817-2.481-2.521-2.7-.188-.394-.417-.787-.694-1.188,1.205-1.456,1.21-2.694,.026-3.876-1.183-1.181-2.425-1.175-3.884,.029-.401-.274-.794-.501-1.188-.688-.217-1.706-1.134-2.524-2.705-2.524-1.602,.008-2.495,.849-2.725,2.569-.392,.189-.791,.421-1.19,.691-1.451-1.197-2.694-1.194-3.89,0-1.178,1.175-1.18,2.448-.001,3.882-.273,.4-.506,.798-.696,1.189-1.719,.229-2.56,1.122-2.568,2.721-.008,1.595,.817,2.481,2.521,2.7,.188,.394,.417,.787,.694,1.188-1.205,1.456-1.21,2.694-.026,3.876,1.184,1.182,2.425,1.176,3.884-.029,.401,.274,.794,.501,1.188,.688,.216,1.697,1.113,2.524,2.682,2.524,1.605,0,2.519-.849,2.749-2.569,.392-.189,.791-.421,1.19-.691,1.451,1.197,2.694,1.193,3.89,0,1.178-1.175,1.18-2.448,.001-3.882,.273-.4,.506-.798,.696-1.189,1.719-.229,2.56-1.122,2.568-2.721Zm-3.36,2.053c-.227,.515-.544,1.051-.942,1.594-.142,.193-.127,.459,.036,.635,1.373,1.487,.898,2.2,.295,2.802-.63,.628-1.321,1.078-2.812-.295-.175-.162-.44-.176-.634-.036-.541,.395-1.077,.71-1.595,.937-.167,.073-.282,.232-.297,.415-.139,1.615-.779,1.942-1.766,1.947-1.008-.027-1.609-.304-1.734-1.9-.015-.186-.131-.348-.302-.421-.521-.222-1.041-.526-1.588-.929-.089-.065-.193-.097-.296-.097-.122,0-.243,.044-.338,.132-1.503,1.383-2.211,.919-2.807,.324-.595-.594-1.06-1.299,.323-2.797,.163-.177,.178-.444,.035-.637-.4-.541-.716-1.075-.937-1.588-.073-.17-.234-.286-.419-.301-1.587-.126-1.903-.755-1.898-1.729,.005-.984,.333-1.622,1.946-1.761,.182-.016,.341-.129,.415-.296,.227-.515,.544-1.051,.942-1.594,.142-.193,.127-.459-.036-.635-1.373-1.487-.898-2.2-.295-2.802,.606-.604,1.321-1.08,2.812,.295,.176,.163,.441,.178,.634,.036,.541-.395,1.077-.71,1.595-.937,.167-.073,.282-.232,.297-.415,.139-1.615,.779-1.942,1.766-1.947,.99-.027,1.608,.304,1.734,1.9,.015,.186,.131,.348,.302,.421,.521,.222,1.041,.526,1.588,.929,.193,.142,.458,.127,.635-.035,1.504-1.382,2.212-.918,2.807-.324,.595,.594,1.06,1.299-.323,2.797-.163,.177-.178,.444-.035,.637,.4,.541,.716,1.075,.937,1.588,.073,.17,.234,.286,.419,.301,1.587,.126,1.903,.755,1.898,1.729-.005,.984-.333,1.622-1.946,1.761-.182,.016-.341,.129-.415,.296Z"/></vector>
|
||||||
1
app/src/main/res/drawable/transaction_24.xml
Normal file
1
app/src/main/res/drawable/transaction_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="M24.026,3.5c0,.357-.139,.692-.392,.945l-2.409,2.409c-.195,.195-.512,.195-.707,0s-.195-.512,0-.707l2.146-2.146H12c-4.411,0-8,3.589-8,8,0,1.896,.675,3.734,1.9,5.176,.179,.211,.153,.526-.057,.705-.209,.178-.526,.154-.705-.057-1.379-1.623-2.139-3.691-2.139-5.824C3,7.038,7.038,3,12,3h10.666l-2.146-2.146c-.195-.195-.195-.512,0-.707s.512-.195,.707,0l2.409,2.409c.252,.252,.392,.588,.392,.945Zm-5.848,2.641c-.211,.178-.238,.494-.06,.705,1.213,1.439,1.882,3.27,1.882,5.155,0,4.411-3.589,8-8,8H1.334l2.147-2.146c.195-.195,.195-.512,0-.707s-.512-.195-.707,0L.366,19.554c-.522,.521-.522,1.371,0,1.893l2.408,2.407c.195,.195,.512,.195,.707,0s.195-.512,0-.707l-2.147-2.146H12c4.962,0,9-4.037,9-9,0-2.121-.752-4.18-2.117-5.799-.179-.211-.493-.239-.705-.06Zm-10.178,6.359c-.276,0-.5,.224-.5,.5s.224,.5,.5,.5h1c0,1.93,1.57,3.5,3.5,3.5,1.481,0,2.808-.938,3.301-2.333,.092-.261-.045-.547-.305-.639-.261-.092-.546,.045-.638,.305-.352,.997-1.299,1.667-2.357,1.667-1.378,0-2.5-1.121-2.5-2.5h2c.276,0,.5-.224,.5-.5s-.224-.5-.5-.5h-2v-1h2c.276,0,.5-.224,.5-.5s-.224-.5-.5-.5h-2c0-1.378,1.122-2.5,2.5-2.5,1.058,0,2.005,.67,2.357,1.667,.093,.261,.378,.396,.638,.305,.26-.092,.397-.378,.305-.638-.493-1.396-1.82-2.333-3.301-2.333-1.93,0-3.5,1.57-3.5,3.5h-1c-.276,0-.5,.224-.5,.5s.224,.5,.5,.5h1v1h-1Z"/></vector>
|
||||||
1
app/src/main/res/drawable/update_transaction_24.xml
Normal file
1
app/src/main/res/drawable/update_transaction_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="m24,.5v2.5c0,.551-.448,1-1,1h-2.5c-.276,0-.5-.224-.5-.5s.224-.5.5-.5h1.492c-.935-1.247-2.396-2-3.992-2-2.605,0-4.746,1.954-4.979,4.545-.023.26-.242.455-.498.455-.015,0-.03,0-.045-.002-.275-.025-.478-.268-.453-.543.28-3.11,2.849-5.455,5.976-5.455,2.04,0,3.895,1.022,5,2.698V.5c0-.276.224-.5.5-.5s.5.224.5.5Zm-7,14v5.571c0,2.203-3.733,3.929-8.5,3.929S0,22.274,0,20.071V4.5C0,1.977,3.733,0,8.5,0c1.072,0,2.114.101,3.099.3.271.055.446.319.391.589-.048.237-.257.401-.49.401-.033,0-.066-.003-.1-.01-.92-.187-1.896-.281-2.901-.281C4.435,1,1,2.603,1,4.5s3.435,3.5,7.5,3.5c.329,0,.652-.01.97-.03.279-.012.513.193.529.469.017.275-.193.513-.469.529-.337.021-.681.032-1.03.032-3.287,0-6.081-.94-7.5-2.351v3.117c0,1.562,3.014,3.233,7.5,3.233.655,0,1.308-.038,1.941-.114.273-.036.523.164.556.437.033.274-.163.523-.438.556-.672.08-1.365.121-2.059.121-3.287,0-6.081-.884-7.5-2.211v2.978c0,1.562,3.014,3.233,7.5,3.233s7.5-1.672,7.5-3.233v-.267c0-.276.224-.5.5-.5s.5.224.5.5Zm-1,2.289c-1.419,1.327-4.213,2.211-7.5,2.211s-6.081-.884-7.5-2.211v3.283c0,1.385,3.08,2.929,7.5,2.929s7.5-1.544,7.5-2.929v-3.283Zm7.522-10.787c-.277-.03-.519.177-.543.453-.233,2.591-2.374,4.545-4.979,4.545-1.596,0-3.057-.753-3.992-2h1.492c.276,0,.5-.224.5-.5s-.224-.5-.5-.5h-2.5c-.551,0-1,.449-1,1v2.5c0,.276.224.5.5.5s.5-.224.5-.5v-2.198c1.105,1.675,2.96,2.698,5,2.698,3.126,0,5.695-2.345,5.976-5.455.024-.275-.178-.518-.453-.543Z"/></vector>
|
||||||
1
app/src/main/res/drawable/yearly_summary_24.xml
Normal file
1
app/src/main/res/drawable/yearly_summary_24.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"><path android:fillColor="@android:color/white" android:pathData="m24,23.524c0,.276-.225.5-.5.5h0l-20-.024c-1.929,0-3.5-1.57-3.5-3.5V.5C0,.224.224,0,.5,0s.5.224.5.5v20c0,1.378,1.122,2.5,2.5,2.5l20.001.024c.275,0,.499.225.499.5ZM5,12.524v6.976c0,.276.224.5.5.5s.5-.224.5-.5v-6.976c0-.276-.224-.5-.5-.5s-.5.224-.5.5Zm5-2v8.976c0,.276.224.5.5.5s.5-.224.5-.5v-8.976c0-.276-.224-.5-.5-.5s-.5.224-.5.5Zm5,3v5.976c0,.276.224.5.5.5s.5-.224.5-.5v-5.976c0-.276-.224-.5-.5-.5s-.5.224-.5.5Zm5-4.024v10c0,.276.224.5.5.5s.5-.224.5-.5v-10c0-.276-.224-.5-.5-.5s-.5.224-.5.5Zm-15.5-.5c.128,0,.256-.049.354-.146l4.474-4.474c.371-.371.974-.371,1.345,0l2.948,2.949c.762.759,1.998.759,2.76,0L22.854.854c.195-.195.195-.512,0-.707s-.512-.195-.707,0l-6.474,6.474c-.371.371-.975.371-1.346,0l-2.948-2.948c-.761-.761-1.998-.761-2.759,0l-4.474,4.474c-.195.195-.195.512,0,.707.098.098.226.146.354.146Z"/></vector>
|
||||||
145
app/src/main/res/layout/activity_bank_transaction_details.xml
Normal file
145
app/src/main/res/layout/activity_bank_transaction_details.xml
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="24dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.6"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/account" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/account"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.4"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.6"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/date" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/first"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.4"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.6"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/amount" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/second"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.4"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.6"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/counterparty" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/third"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.4"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.6"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/reference" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/reference"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.4"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/isFixedCostTextView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/fixed_costs"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/isFixedCostSwitch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:textOn="@string/yes"
|
||||||
|
android:textOff="@string/no"
|
||||||
|
android:checked="false"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
141
app/src/main/res/layout/activity_bank_transactions.xml
Normal file
141
app/src/main/res/layout/activity_bank_transactions.xml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:text="@string/year"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/yearSpinner"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/yearLabel" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/monthLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:text="@string/month"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/yearLabel"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/monthSpinner"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/yearLabel"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/monthLabel" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/categoryLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:text="@string/category"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/monthLabel"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/categorySpinner"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/monthLabel"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/categoryLabel" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/categoryLabel">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/first"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/date" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/second"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/category"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/third"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/amount"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/header"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/footer"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/recyclerView"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumAmountLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_amount"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumAmountValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
59
app/src/main/res/layout/activity_fixed_cost_details.xml
Normal file
59
app/src/main/res/layout/activity_fixed_cost_details.xml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/fixed_cost_source"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/perl_gray">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/date" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/category"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/amount"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/sourceRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
154
app/src/main/res/layout/activity_fixed_costs.xml
Normal file
154
app/src/main/res/layout/activity_fixed_costs.xml
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lastUpdateLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/last_update"
|
||||||
|
android:textSize="18sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lastUpdateValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/last_update"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/lastUpdateLabel"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/recalculateButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/recalculate"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/monthly"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/monthly"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="24dp"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/monthlyRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:nestedScrollingEnabled="false"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/quarterly"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/quarterly"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="24dp"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/quarterlyRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:nestedScrollingEnabled="false"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearly"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/yearly"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="24dp"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/yearlyRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:nestedScrollingEnabled="false"/>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/perl_gray">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/monthlyFixedCostLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/monthly_fixed_costs"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/monthlyFixedCostValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/red"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/monthlyFixedCostLabel"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearlyFixedCostLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/yearly_fixed_costs"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/monthlyFixedCostLabel"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearlyFixedCostValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/red"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/yearlyFixedCostLabel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/monthlyFixedCostValue"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
105
app/src/main/res/layout/activity_home.xml
Normal file
105
app/src/main/res/layout/activity_home.xml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.MainActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanOverview"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/loan_overview"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
android:paddingHorizontal="50dp"
|
||||||
|
android:paddingVertical="32dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
app:drawableStartCompat="@drawable/loan_overview_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fixedCosts"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/fixed_costs"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingHorizontal="50dp"
|
||||||
|
android:paddingVertical="32dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
app:drawableStartCompat="@drawable/fixed_costs_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/loanOverview"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bankTransaction"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/bank_Transaction"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingHorizontal="50dp"
|
||||||
|
android:paddingVertical="32dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
app:drawableStartCompat="@drawable/transaction_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/fixedCosts"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/monthlySummary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/monthly_summary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingHorizontal="50dp"
|
||||||
|
android:paddingVertical="32dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
app:drawableStartCompat="@drawable/monthly_summary_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/bankTransaction"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearlySummary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/yearly_summary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:paddingHorizontal="50dp"
|
||||||
|
android:paddingVertical="32dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
app:drawableStartCompat="@drawable/yearly_summary_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/monthlySummary"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/updateTransaction"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/update_transaction"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:paddingHorizontal="50dp"
|
||||||
|
android:paddingVertical="32dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
app:drawableStartCompat="@drawable/update_transaction_24"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/progressBarContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
142
app/src/main/res/layout/activity_loan_details.xml
Normal file
142
app/src/main/res/layout/activity_loan_details.xml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/remaining"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/remaining_loan"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/loanTitle"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/payment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/monthly_payment"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/remaining"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/rate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/fixed_rate"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/payment"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumInterest"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_interest"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/rate"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/specialRepayment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/special_repayment"
|
||||||
|
android:layout_marginTop="50dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/refresh"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/refresh_list"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/specialRepayment"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sumInterest"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingTop="32dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/monthText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="月份"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/interestText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="利息"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/monthText"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/principalText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="本金"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/interestText"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/remainingDebtText"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="剩余贷款"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/principalText"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/header"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
64
app/src/main/res/layout/activity_loan_overview.xml
Normal file
64
app/src/main/res/layout/activity_loan_overview.xml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumLoanTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_loan"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" >
|
||||||
|
</TextView>
|
||||||
|
<!-- Placeholder for the dynamically added TableLayouts -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumLoanValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_loan"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/sumLoanTitle" >
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumPaymentTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_payment"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sumLoanTitle"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" >
|
||||||
|
</TextView>
|
||||||
|
<!-- Placeholder for the dynamically added TableLayouts -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumPaymentValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_payment"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sumLoanValue"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/sumPaymentTitle" >
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sumPaymentTitle"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
36
app/src/main/res/layout/activity_login.xml
Normal file
36
app/src/main/res/layout/activity_login.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/password"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ems="10"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:hint="@string/password"
|
||||||
|
android:autofillHints="password"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/enter"
|
||||||
|
android:layout_marginTop="16dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
128
app/src/main/res/layout/activity_monthly_summary.xml
Normal file
128
app/src/main/res/layout/activity_monthly_summary.xml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:text="@string/year"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/yearSpinner"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/yearLabel" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/yearLabel">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/month"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/month" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/income"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/income"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/cost"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/cost"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/header" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumIncomeLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_income"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumIncomeValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/green"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/sumIncomeLabel"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumCostLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_cost"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sumIncomeLabel"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumCostValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/red"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/sumCostLabel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/sumIncomeValue"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
93
app/src/main/res/layout/activity_yearly_summary.xml
Normal file
93
app/src/main/res/layout/activity_yearly_summary.xml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/yearLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:text="@string/year"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/yearSpinner"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/yearLabel" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/yearLabel">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/third"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/category" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/second"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
|
android:text="@string/amount"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/header"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/footer"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/footer"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/perl_gray"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/recyclerView"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumAmountLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sum_amount"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sumAmountValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
49
app/src/main/res/layout/item_four_equal_texts.xml
Normal file
49
app/src/main/res/layout/item_four_equal_texts.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/first"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/first"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/second"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/second"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/first"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/third"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/third"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/second"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fourth"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/fourth"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/third"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
121
app/src/main/res/layout/item_loan_overview.xml
Normal file
121
app/src/main/res/layout/item_loan_overview.xml
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- Title with large bold text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
|
<!-- TableLayout for two columns of fields -->
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.7"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/loan_amount" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanAmount"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.3"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.7"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/remaining_loan" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanRemainingLoan"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.3"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.7"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/fixed_rate" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanRate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.3"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.7"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/monthly_payment" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanPayment"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.3"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0.7"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/fixed_rate_maturity" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loanMaturity"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.3"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true" />
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
</TableLayout>
|
||||||
|
|
||||||
|
<!-- Add some space between loan items -->
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="16dp" />
|
||||||
|
</LinearLayout>
|
||||||
45
app/src/main/res/layout/item_three_equal_texts.xml
Normal file
45
app/src/main/res/layout/item_three_equal_texts.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/first"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:text="@string/first"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/second"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:text="@string/second"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/third"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:text="@string/third"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
26
app/src/main/res/layout/item_two_equal_texts.xml
Normal file
26
app/src/main/res/layout/item_two_equal_texts.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/first"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:text="@string/first" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/second"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:text="@string/second"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 730 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user