86 Commits

Author SHA1 Message Date
64620d99c9 Update dependency dart to ^3.9.2
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-09-06 00:36:35 +00:00
77c0640ec0 Merge pull request 'Update dependency ts-pattern to ^5.8.0' (#97) from renovate/ts-pattern-5.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-05 00:22:01 +00:00
96c80eb18c Update dependency ts-pattern to ^5.8.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-09-04 00:29:07 +00:00
0cde9f5635 Merge pull request 'Update dependency eslint-plugin-react-x to ^1.52.9' (#96) from renovate/eslint-plugin-react-x-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-03 00:27:57 +00:00
5e35dae02f Update dependency eslint-plugin-react-x to ^1.52.9
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-09-02 00:28:11 +00:00
f321376990 Merge pull request 'Update dependency dayjs to ^1.11.18' (#95) from renovate/dayjs-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-09-01 00:28:05 +00:00
86b86d4d68 Update dependency dayjs to ^1.11.18
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-31 00:26:59 +00:00
35c629a339 Merge pull request 'Update react' (#94) from renovate/react into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-30 00:27:37 +00:00
da0d5adcb9 Update react
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-29 00:28:23 +00:00
2214387010 Merge pull request 'Update dependency scanbot_sdk to ^7.0.1' (#93) from renovate/scanbot_sdk-7.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-26 00:27:16 +00:00
667ce69be8 Update dependency scanbot_sdk to ^7.0.1
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2025-08-26 00:27:14 +00:00
57f4ed53f6 Merge pull request 'Update dependency flutter_hooks to ^0.21.3+1' (#92) from renovate/flutter_hooks-0.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-25 00:27:49 +00:00
deb884a1f0 Update dependency flutter_hooks to ^0.21.3+1
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-25 00:27:44 +00:00
0e5d878e30 Merge pull request 'Update dependency @types/react to ^19.1.11' (#91) from renovate/react into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-24 00:27:01 +00:00
b266cbcadb Update dependency @types/react to ^19.1.11
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-23 00:28:07 +00:00
91bca2b6b1 Merge pull request 'Update dependency @mui/x-charts to ^8.10.2' (#90) from renovate/mui-x-charts-8.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-22 00:27:27 +00:00
a741662251 Update dependency @mui/x-charts to ^8.10.2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-21 00:27:57 +00:00
34f0493c51 Merge pull request 'Update dependency eslint-plugin-react-x to ^1.52.6' (#89) from renovate/eslint-plugin-react-x-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-20 00:27:47 +00:00
b538b6fcb3 Update dependency eslint-plugin-react-x to ^1.52.6
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-19 00:27:39 +00:00
9d18d975d0 Merge pull request 'Update dependency @mui/x-charts to ^8.10.1' (#87) from renovate/mui-x-charts-8.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-17 00:27:47 +00:00
43049bc229 Update dependency @mui/x-charts to ^8.10.1
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-16 00:26:40 +00:00
19ca17b43a Merge pull request 'Update dependency eslint-plugin-react-x to ^1.52.4' (#86) from renovate/eslint-plugin-react-x-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-15 00:28:03 +00:00
572046f418 Merge pull request 'Update dependency eslint-plugin-react-dom to ^1.52.4' (#85) from renovate/eslint-plugin-react-dom-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-15 00:28:02 +00:00
24af473dd3 Update dependency eslint-plugin-react-x to ^1.52.4
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-14 00:27:47 +00:00
8a57c57ec4 Update dependency eslint-plugin-react-dom to ^1.52.4
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-14 00:27:44 +00:00
50a5e7745f Merge pull request 'Update Rust crate anyhow to 1.0.99' (#84) from renovate/anyhow-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-13 00:29:06 +00:00
675e4d9ecd Merge pull request 'Update dependency @types/react to ^19.1.10' (#83) from renovate/react into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-13 00:28:05 +00:00
83c214af7d Update Rust crate anyhow to 1.0.99
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-12 00:28:57 +00:00
bfa6af5749 Update dependency @types/react to ^19.1.10
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-12 00:27:22 +00:00
6ab157504c Merge pull request 'Update dependency @mui/x-charts to ^8.10.0' (#82) from renovate/mui-x-charts-8.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-11 00:28:40 +00:00
bb98ea5e46 Merge pull request 'Update dependency @eslint/js to ^9.33.0' (#81) from renovate/eslint-js-9.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-11 00:28:37 +00:00
2d104a54b5 Update dependency @mui/x-charts to ^8.10.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-10 00:28:25 +00:00
3b0ff29bc8 Update dependency @eslint/js to ^9.33.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-10 00:28:22 +00:00
4ade72a0ee Merge pull request 'Update dependency build_runner to ^2.6.1' (#80) from renovate/build_runner-2.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-09 00:27:37 +00:00
6021b44a13 Update dependency build_runner to ^2.6.1
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-09 00:27:30 +00:00
eb92e8c0c5 Merge pull request 'Update dependency eslint to ^9.32.0' (#79) from renovate/eslint-9.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-08 00:16:25 +00:00
d98305908c Merge pull request 'Update Rust crate clap to 4.5.43' (#78) from renovate/clap-4.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-08 00:16:22 +00:00
ae5ef99e3a Update dependency eslint to ^9.32.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-07 00:17:08 +00:00
2f592183e4 Update Rust crate clap to 4.5.43
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-07 00:17:06 +00:00
74291a258c Merge pull request 'Update Rust crate serde_json to 1.0.142' (#77) from renovate/serde_json-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-06 00:16:05 +00:00
df8cd6a046 Merge pull request 'Update dependency @mui/x-date-pickers to ^8.9.2' (#76) from renovate/mui-x-date-pickers-8.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-06 00:15:04 +00:00
079fbbf154 Update Rust crate serde_json to 1.0.142
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-05 00:16:16 +00:00
ba443629e6 Update dependency @mui/x-date-pickers to ^8.9.2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-05 00:14:32 +00:00
2c07a69b90 Merge pull request 'Update dependency @mui/x-data-grid to ^8.9.2' (#75) from renovate/mui-x-data-grid-8.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-04 00:15:26 +00:00
0de551f1de Update dependency @mui/x-data-grid to ^8.9.2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-04 00:15:21 +00:00
2488ef0125 Merge pull request 'Update dependency @mui/x-charts to ^8.9.2' (#74) from renovate/mui-x-charts-8.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-03 00:17:29 +00:00
aa2e764262 Update dependency @mui/x-charts to ^8.9.2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-08-02 00:17:07 +00:00
1202219e98 Merge pull request 'Update Rust crate clap to 4.5.42' (#73) from renovate/clap-4.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-01 00:20:44 +00:00
112597084c Merge pull request 'Update react' (#72) from renovate/react into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-08-01 00:20:43 +00:00
4f64404ffa Update Rust crate clap to 4.5.42
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-07-31 00:15:45 +00:00
c39b53c721 Update react
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-07-31 00:15:41 +00:00
bd2e343601 Show API URL when a token has been created
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-30 21:39:32 +02:00
85ee2b2549 Ignore auto login variable if it is empty
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-30 19:47:34 +02:00
154551aeaf Merge pull request 'Update dependency build_runner to ^2.6.0' (#71) from renovate/build_runner-2.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-30 00:15:47 +00:00
7b10c3508a Update dependency build_runner to ^2.6.0
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-07-30 00:15:41 +00:00
61c96629a1 Merge pull request 'Update dependency eslint-plugin-react-dom to ^1.52.3' (#37) from renovate/eslint-plugin-react-dom-1.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-30 00:14:59 +00:00
8644075a09 Update dependency eslint-plugin-react-dom to ^1.52.3
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-07-29 00:17:21 +00:00
81bfa75eec Redirect to list screens after a successful scan
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-28 21:47:00 +02:00
de0dd4e36a Merge pull request 'Update dependency @vitejs/plugin-react to ^4.7.0' (#70) from renovate/vitejs-plugin-react-4.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-28 00:16:36 +00:00
f9d7a63738 Update dependency @vitejs/plugin-react to ^4.7.0
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2025-07-28 00:16:33 +00:00
0ef6f8288f Merge pull request 'Update dependency @mui/x-date-pickers to ^8.9.0' (#69) from renovate/mui-x-date-pickers-8.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-27 00:17:07 +00:00
2f23e4dadb Update dependency @mui/x-date-pickers to ^8.9.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2025-07-27 00:17:04 +00:00
5cf5fac8f4 Merge pull request 'Update dependency @eslint/js to ^9.32.0' (#68) from renovate/eslint-js-9.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-26 00:16:48 +00:00
8e143db354 Update dependency @eslint/js to ^9.32.0
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2025-07-26 00:16:46 +00:00
1237c9706e Merge pull request 'Update dependency @mui/x-data-grid to ^8.9.1' (#67) from renovate/mui-x-data-grid-8.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-25 00:17:11 +00:00
1add0b4cfe Update dependency @mui/x-data-grid to ^8.9.1
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2025-07-25 00:17:07 +00:00
6920d6d9b0 Merge pull request 'Update dependency @mui/x-charts to ^8.9.0' (#66) from renovate/mui-x-charts-8.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-24 00:17:01 +00:00
27e92660f1 Update dependency @mui/x-charts to ^8.9.0
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2025-07-24 00:16:58 +00:00
743e5ba410 Merge pull request 'Update Rust crate rand to 0.9.2' (#65) from renovate/rand-0.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-23 00:17:12 +00:00
8039b1c807 Update Rust crate rand to 0.9.2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2025-07-23 00:17:07 +00:00
9ef84ba63a Merge pull request 'Update dependency eslint to ^9.31.0' (#8) from renovate/eslint-9.x into main
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-23 00:16:37 +00:00
56e5ae6629 Update dependency eslint to ^9.31.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-07-22 00:19:56 +00:00
4443131516 Can download APK from web app
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-21 19:48:32 +02:00
365d7589b1 Force refresh of expenses editor
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-20 19:55:18 +02:00
23cc189e53 Fix date extraction
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-07-20 19:17:47 +02:00
3098d12e8a Support short dates
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-07-20 18:32:02 +02:00
0943104cc8 Can show expense in full screen
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-20 18:23:37 +02:00
3beaba806a Can clear cost value quickly 2025-07-20 18:18:20 +02:00
1788e7f184 Can disable dates extraction 2025-07-20 18:14:03 +02:00
71d32d72ef Can extract date of expenses
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-20 18:07:22 +02:00
28f61a3099 Improve regular expression
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-20 17:44:46 +02:00
f61e3541fb Perform first OCR extraction 2025-07-20 17:25:52 +02:00
fb7891d913 Merge pull request 'Update Rust crate serde_json to 1.0.141' (#63) from renovate/serde_json-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-20 00:15:22 +00:00
d9ede224cf Update Rust crate serde_json to 1.0.141
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-07-20 00:15:16 +00:00
fc9334b20b Merge pull request 'Update dependency dart to ^3.8.2' (#62) from renovate/dart-3.x into main
Some checks failed
continuous-integration/drone/push Build is failing
2025-07-19 00:18:13 +00:00
c4cbd7ec8b Update dependency dart to ^3.8.2
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-07-19 00:17:10 +00:00
19 changed files with 1687 additions and 1468 deletions

View File

@@ -87,7 +87,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rand 0.9.1", "rand 0.9.2",
"sha1", "sha1",
"smallvec", "smallvec",
"tokio", "tokio",
@@ -431,9 +431,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.98" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -724,9 +724,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.41" version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -734,9 +734,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.41" version = "4.5.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -2298,7 +2298,7 @@ dependencies = [
"light-openid", "light-openid",
"log", "log",
"mime_guess", "mime_guess",
"rand 0.9.1", "rand 0.9.2",
"rust-embed", "rust-embed",
"rust-s3", "rust-s3",
"rust_xlsxwriter", "rust_xlsxwriter",
@@ -2741,9 +2741,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.1" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"rand_core 0.9.3", "rand_core 0.9.3",
@@ -3232,9 +3232,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",

View File

@@ -8,7 +8,7 @@ env_logger = "0.11.8"
log = "0.4.27" log = "0.4.27"
diesel = { version = "2.2.12", features = ["postgres", "r2d2"] } diesel = { version = "2.2.12", features = ["postgres", "r2d2"] }
diesel_migrations = "2.2.0" diesel_migrations = "2.2.0"
clap = { version = "4.5.41", features = ["env", "derive"] } clap = { version = "4.5.43", features = ["env", "derive"] }
actix-web = "4.11.0" actix-web = "4.11.0"
actix-cors = "0.7.1" actix-cors = "0.7.1"
actix-multipart = "0.7.2" actix-multipart = "0.7.2"
@@ -16,15 +16,15 @@ actix-remote-ip = "0.1.0"
actix-session = { version = "0.10.1", features = ["redis-session"] } actix-session = { version = "0.10.1", features = ["redis-session"] }
actix-files = "0.6.6" actix-files = "0.6.6"
lazy_static = "1.5.0" lazy_static = "1.5.0"
anyhow = "1.0.98" anyhow = "1.0.99"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
rust-s3 = "0.36.0-beta.2" rust-s3 = "0.36.0-beta.2"
thiserror = "2.0.12" thiserror = "2.0.12"
tokio = "1.45.1" tokio = "1.45.1"
futures-util = "0.3.31" futures-util = "0.3.31"
serde_json = "1.0.140" serde_json = "1.0.142"
light-openid = "1.0.4" light-openid = "1.0.4"
rand = "0.9.1" rand = "0.9.2"
ipnet = { version = "2.11.0", features = ["serde"] } ipnet = { version = "2.11.0", features = ["serde"] }
lazy-regex = "3.4.1" lazy-regex = "3.4.1"
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] } jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }

View File

@@ -29,7 +29,7 @@ pub struct AppConfig {
/// Unsecure : for development, bypass authentication, using the account with the given /// Unsecure : for development, bypass authentication, using the account with the given
/// email address by default /// email address by default
#[clap(long, env)] #[clap(long, env)]
pub unsecure_auto_login_email: Option<String>, unsecure_auto_login_email: Option<String>,
/// PostgreSQL database host /// PostgreSQL database host
#[clap(long, env, default_value = "localhost")] #[clap(long, env, default_value = "localhost")]
@@ -126,6 +126,14 @@ pub struct AppConfig {
/// Redis password /// Redis password
#[clap(long, env, default_value = "secretredis")] #[clap(long, env, default_value = "secretredis")]
redis_password: String, redis_password: String,
/// Application download URL
#[clap(
long,
env,
default_value = "https://gitea.communiquons.org/pierre/MoneyMgr/releases/download/latest/moneymgr_mobile_arm64-v8a.apk"
)]
pub apk_download_url: String,
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
@@ -140,9 +148,17 @@ impl AppConfig {
&ARGS &ARGS
} }
/// Get auto login email (if not empty)
pub fn unsecure_auto_login_email(&self) -> Option<&str> {
match self.unsecure_auto_login_email.as_deref() {
None | Some("") => None,
s => s,
}
}
/// Check if auth is disabled /// Check if auth is disabled
pub fn is_auth_disabled(&self) -> bool { pub fn is_auth_disabled(&self) -> bool {
self.unsecure_auto_login_email.is_some() self.unsecure_auto_login_email().is_some()
} }
/// Get auth cookie domain /// Get auth cookie domain

View File

@@ -70,6 +70,7 @@ impl Default for ServerConstraints {
struct ServerConfig { struct ServerConfig {
auth_disabled: bool, auth_disabled: bool,
oidc_provider_name: &'static str, oidc_provider_name: &'static str,
apk_download_url: &'static str,
accounts_types: &'static [AccountTypeDesc], accounts_types: &'static [AccountTypeDesc],
constraints: ServerConstraints, constraints: ServerConstraints,
} }
@@ -79,6 +80,7 @@ impl Default for ServerConfig {
Self { Self {
auth_disabled: AppConfig::get().is_auth_disabled(), auth_disabled: AppConfig::get().is_auth_disabled(),
oidc_provider_name: AppConfig::get().openid_provider().name, oidc_provider_name: AppConfig::get().openid_provider().name,
apk_download_url: AppConfig::get().apk_download_url.as_str(),
constraints: Default::default(), constraints: Default::default(),
accounts_types: &ACCOUNT_TYPES, accounts_types: &ACCOUNT_TYPES,
} }

View File

@@ -182,7 +182,7 @@ impl FromRequest for AuthExtractor {
} }
// Check if login is hard-coded as program argument // Check if login is hard-coded as program argument
if let Some(email) = &AppConfig::get().unsecure_auto_login_email { if let Some(email) = &AppConfig::get().unsecure_auto_login_email() {
let user = users_service::get_user_by_email(email).map_err(|e| { let user = users_service::get_user_by_email(email).map_err(|e| {
log::error!("Failed to retrieve dev user: {e}"); log::error!("Failed to retrieve dev user: {e}");
ErrorPreconditionFailed("Unable to retrieve dev user!") ErrorPreconditionFailed("Unable to retrieve dev user!")

View File

@@ -38,7 +38,7 @@ async fn main() -> std::io::Result<()> {
db_connection::initialize_conn().expect("Failed to connect to PostgresSQL database!"); db_connection::initialize_conn().expect("Failed to connect to PostgresSQL database!");
// Auto create default account, if requested // Auto create default account, if requested
if let Some(mail) = &AppConfig::get().unsecure_auto_login_email { if let Some(mail) = &AppConfig::get().unsecure_auto_login_email() {
users_service::create_or_update_user(mail, "Anonymous") users_service::create_or_update_user(mail, "Anonymous")
.await .await
.expect("Failed to create default account!"); .expect("Failed to create default account!");

View File

@@ -1,45 +1,31 @@
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:moneymgr_mobile/services/router/routes_list.dart';
import 'package:moneymgr_mobile/services/storage/expenses.dart'; import 'package:moneymgr_mobile/services/storage/expenses.dart';
import 'package:moneymgr_mobile/services/storage/prefs.dart';
import 'package:moneymgr_mobile/utils/ocr_utils.dart';
import 'package:moneymgr_mobile/utils/pdf_utils.dart';
import 'package:moneymgr_mobile/widgets/expense_editor.dart'; import 'package:moneymgr_mobile/widgets/expense_editor.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:scanbot_sdk/scanbot_sdk.dart';
import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart' hide IconButton, EdgeInsets;
part 'scan_screen.g.dart'; part 'scan_screen.g.dart';
/// Scan a document & return generated PDF as byte file /// Scan a document & return generated PDF as byte file
@riverpod @riverpod
Future<Uint8List?> _scanDocument(Ref ref) async { Future<(Uint8List?, BaseExpenseInfo?)> _scanDocument(Ref ref) async {
var configuration = DocumentScanningFlow( final prefs = ref.watch(prefsProvider).requireValue;
appearance: DocumentFlowAppearanceConfiguration(
statusBarMode: StatusBarMode.DARK,
),
cleanScanningSession: true,
outputSettings: DocumentScannerOutputSettings(pagesScanLimit: 1),
screens: DocumentScannerScreens(
review: ReviewScreenConfiguration(enabled: false),
),
);
var documentResult = await ScanbotSdkUiV2.startDocumentScanner(configuration);
if (documentResult.status != OperationStatus.OK) { final pdf = await scanDocAsPDF();
throw Exception("Scanner failed with status ${documentResult.status}"); final img = await renderPdf(pdfBytes: pdf);
} final amount = await extractInfoFromBill(
imgBuff: img,
// Convert result to PDF extractDates: !prefs.disableExtractDates(),
var result = await ScanbotSdk.document.createPDFForDocument(
PDFFromDocumentParams(
documentID: documentResult.data!.uuid,
pdfConfiguration: PdfConfiguration(),
),
); );
final pdfPath = result.pdfFileUri.replaceFirst("file://", ""); return (pdf, amount);
return File(pdfPath).readAsBytes();
} }
class ScanScreen extends HookConsumerWidget { class ScanScreen extends HookConsumerWidget {
@@ -52,8 +38,8 @@ class ScanScreen extends HookConsumerWidget {
restartScan() async { restartScan() async {
try { try {
final val = ref.refresh(_scanDocumentProvider); ref.invalidate(_scanDocumentProvider);
Logger.root.info("Load again startup result: $val"); Logger.root.info("Load again startup");
} catch (e, s) { } catch (e, s) {
Logger.root.shout("Failed to try again startup loading! $e $s"); Logger.root.shout("Failed to try again startup loading! $e $s");
} }
@@ -62,21 +48,24 @@ class ScanScreen extends HookConsumerWidget {
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: switch (scanDocProvider) { child: switch (scanDocProvider) {
AsyncData(:final value) when value != null => ExpenseEditor( AsyncData(:final value) when value.$1 != null => ExpenseEditor(
file: value, file: value.$1!,
initialData: value.$2,
onFinished: (expense) async { onFinished: (expense) async {
await expenses.add( await expenses.add(
info: expense, info: expense,
fileContent: value, fileContent: value.$1!,
fileMimeType: "application/pdf", fileMimeType: "application/pdf",
); );
restartScan(); if (context.mounted) {
context.pushReplacement(scansPage);
}
}, },
onRescan: restartScan, onRescan: restartScan,
), ),
// No data // No data
AsyncData(:final value) when value == null => ScanErrorScreen( AsyncData(:final value) when value.$1 == null => ScanErrorScreen(
message: "No document scanned!", message: "No document scanned!",
onTryAgain: restartScan, onTryAgain: restartScan,
), ),

View File

@@ -22,6 +22,11 @@ class SettingsScreen extends ConsumerWidget {
ref.invalidate(prefsProvider); ref.invalidate(prefsProvider);
} }
handleToggleDisableExtractDate(v) async {
await prefs.setDisableExtractDates(v);
ref.invalidate(prefsProvider);
}
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Settings')), appBar: AppBar(title: const Text('Settings')),
body: ListView( body: ListView(
@@ -40,6 +45,14 @@ class SettingsScreen extends ConsumerWidget {
"Do not start camera automatically on application startup", "Do not start camera automatically on application startup",
), ),
), ),
SwitchListTile(
value: prefs.disableExtractDates(),
onChanged: handleToggleDisableExtractDate,
title: Text("Do not extract dates"),
subtitle: Text(
"Do not attempt to extract dates from scanned expenses",
),
),
const Divider(), const Divider(),
ListTile( ListTile(
leading: const Icon(Icons.info_outline), leading: const Icon(Icons.info_outline),

View File

@@ -23,6 +23,14 @@ extension MoneyMgrSharedPreferences on SharedPreferencesWithCache {
await setBool("startOnScansListScreen", start); await setBool("startOnScansListScreen", start);
} }
bool disableExtractDates() {
return getBool("disableExtractDates") ?? false;
}
Future<void> setDisableExtractDates(bool disable) async {
await setBool("disableExtractDates", disable);
}
ServerConfig? serverConfig() { ServerConfig? serverConfig() {
final json = getString("serverConfig"); final json = getString("serverConfig");
if (json != null) return ServerConfig.fromJson(jsonDecode(json)); if (json != null) return ServerConfig.fromJson(jsonDecode(json));

View File

@@ -0,0 +1,86 @@
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:logging/logging.dart';
import 'package:moneymgr_mobile/services/storage/expenses.dart';
/// Attempt to extract information from invoice image
Future<BaseExpenseInfo?> extractInfoFromBill({
required Uint8List imgBuff,
required bool extractDates,
}) async {
final decodedImage = await decodeImageFromList(imgBuff);
final byteData = await decodedImage.toByteData(
format: ui.ImageByteFormat.rawRgba,
);
final image = InputImage.fromBitmap(
bitmap: byteData!.buffer.asUint8List(),
width: decodedImage.width,
height: decodedImage.height,
);
final textRecognizer = TextRecognizer(script: TextRecognitionScript.latin);
final extractionResult = await textRecognizer.processImage(image);
Logger.root.fine("Expense text: ${extractionResult.text}");
// Check for highestCost amount on invoice
final costRegexp = RegExp(
r'([0-9]+([ ]*(\\.|,)[ ]*[0-9]{1,2}){0,1})([ \\t\\n]*(EUR|eur|€)|E)',
multiLine: true,
caseSensitive: false,
);
var highestCost = 0.0;
for (final match in costRegexp.allMatches(extractionResult.text)) {
if (match.groupCount == 0) continue;
// Process only numeric value
final value = (match.group(1) ?? "").replaceAll(",", ".");
highestCost = max(highestCost, double.tryParse(value) ?? 0.0);
}
// Check for highestCost amount on invoice
final dateRegexp = RegExp(
r'([0-3][0-9])(\/|-)([0-1][0-9])(\/|-)((20|)[0-9]{2})',
multiLine: false,
caseSensitive: false,
);
final currDate = DateTime.now();
DateTime? newest;
for (final match in dateRegexp.allMatches(extractionResult.text)) {
if (match.groupCount < 6) continue;
int year = int.tryParse(match.group(5)!) ?? currDate.year;
try {
final date = DateTime(
year > 99 ? year : (2000 + year),
int.tryParse(match.group(3)!) ?? currDate.month,
int.tryParse(match.group(1)!) ?? currDate.day,
);
if (newest == null) {
newest = date;
} else {
newest = DateTime.fromMillisecondsSinceEpoch(
max(newest.millisecondsSinceEpoch, date.millisecondsSinceEpoch),
);
}
} catch (e, s) {
Logger.root.warning("Failed to parse date! $e$s");
}
}
return BaseExpenseInfo(
label: null,
cost: highestCost,
time: extractDates && (newest?.isBefore(currDate) ?? false)
? newest!
: currDate,
);
}

View File

@@ -6,14 +6,40 @@ import 'package:flutter/material.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:pdf_image_renderer/pdf_image_renderer.dart'; import 'package:pdf_image_renderer/pdf_image_renderer.dart';
import 'package:scanbot_sdk/scanbot_sdk.dart';
import 'package:scanbot_sdk/scanbot_sdk_ui_v2.dart' hide IconButton, EdgeInsets;
/// Scan document as PDF
Future<Uint8List> scanDocAsPDF() async {
var configuration = DocumentScanningFlow(
appearance: DocumentFlowAppearanceConfiguration(
statusBarMode: StatusBarMode.DARK,
),
cleanScanningSession: true,
outputSettings: DocumentScannerOutputSettings(pagesScanLimit: 1),
screens: DocumentScannerScreens(
review: ReviewScreenConfiguration(enabled: false),
),
);
var documentResult = await ScanbotSdkUiV2.startDocumentScanner(configuration);
if (documentResult.status != OperationStatus.OK) {
throw Exception("Scanner failed with status ${documentResult.status}");
}
// Convert result to PDF
var result = await ScanbotSdk.document.createPDFForDocument(
PDFFromDocumentParams(
documentID: documentResult.data!.uuid,
pdfConfiguration: PdfConfiguration(),
),
);
final pdfPath = result.pdfFileUri.replaceFirst("file://", "");
return File(pdfPath).readAsBytes();
}
/// Render PDF to image bits /// Render PDF to image bits
Future<Uint8List> renderPdf( Future<Uint8List> renderPdf({String? path, Uint8List? pdfBytes}) async {
{
String? path,
Uint8List? pdfBytes,
}) async {
assert(path != null || pdfBytes != null); assert(path != null || pdfBytes != null);
// Create temporary file if required // Create temporary file if required

View File

@@ -40,6 +40,20 @@ class ExpenseEditor extends HookConsumerWidget {
final (:pending, :snapshot, :hasError) = useAsyncTask(); final (:pending, :snapshot, :hasError) = useAsyncTask();
// Force refresh of field if required
final previousData = useState<BaseExpenseInfo?>(null);
if (initialData != previousData.value) {
previousData.value = initialData;
labelController.text = initialData?.label ?? "";
costController.text = initialData?.cost.toString() ?? "";
timeController.value = initialData?.time ?? DateTime.now();
}
// Clear cost value
handleClearCost() {
costController.text = "";
}
// Pick a new date // Pick a new date
handlePickDate() async { handlePickDate() async {
final date = await showDatePicker( final date = await showDatePicker(
@@ -94,6 +108,19 @@ class ExpenseEditor extends HookConsumerWidget {
} }
} }
// Open invoice in full screen
handleFullScreenInvoice() {
showDialog(
context: context,
builder: (c) => Scaffold(
appBar: AppBar(title: Text("Expense")),
body: SingleChildScrollView(
child: PDFViewer(pdfBytes: file, fit: BoxFit.fitWidth),
),
),
);
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Expense info"), title: Text("Expense info"),
@@ -125,8 +152,11 @@ class ExpenseEditor extends HookConsumerWidget {
children: [ children: [
// Expense preview // Expense preview
Expanded( Expanded(
child: GestureDetector(
onTap: handleFullScreenInvoice,
child: PDFViewer(pdfBytes: file, fit: BoxFit.contain), child: PDFViewer(pdfBytes: file, fit: BoxFit.contain),
), ),
),
SizedBox(height: 10), SizedBox(height: 10),
@@ -137,7 +167,13 @@ class ExpenseEditor extends HookConsumerWidget {
decimal: true, decimal: true,
signed: false, signed: false,
), ),
decoration: const InputDecoration(labelText: 'Cost'), decoration: InputDecoration(
labelText: 'Cost',
suffixIcon: IconButton(
onPressed: handleClearCost,
icon: const Icon(Icons.clear),
),
),
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
), ),

View File

@@ -488,6 +488,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "16.0.0" version: "16.0.0"
google_mlkit_commons:
dependency: transitive
description:
name: google_mlkit_commons
sha256: "8f40fbac10685cad4715d11e6a0d86837d9ad7168684dfcad29610282a88e67a"
url: "https://pub.dev"
source: hosted
version: "0.11.0"
google_mlkit_text_recognition:
dependency: "direct main"
description:
name: google_mlkit_text_recognition
sha256: "96173ad4dd7fd06c660e22ac3f9e9f1798a517fe7e48bee68eeec83853224224"
url: "https://pub.dev"
source: hosted
version: "0.15.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:

View File

@@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ^3.8.1 sdk: ^3.9.2
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@@ -49,7 +49,7 @@ dependencies:
riverpod_annotation: ^2.6.1 riverpod_annotation: ^2.6.1
# Implement React hooks in Flutter # Implement React hooks in Flutter
flutter_hooks: ^0.21.2 flutter_hooks: ^0.21.3+1
# Router # Router
go_router: ^16.0.0 go_router: ^16.0.0
@@ -84,7 +84,7 @@ dependencies:
# Document scanner # Document scanner
# flutter_doc_scanner: ^0.0.16 # no bundled support yet # flutter_doc_scanner: ^0.0.16 # no bundled support yet
# https://developers.google.com/ml-kit/tips/installation-paths # https://developers.google.com/ml-kit/tips/installation-paths
scanbot_sdk: ^7.0.0 scanbot_sdk: ^7.0.1
# Get documents path # Get documents path
path_provider: ^2.1.5 path_provider: ^2.1.5
@@ -93,6 +93,9 @@ dependencies:
# PDF renderer # PDF renderer
pdf_image_renderer: ^1.0.1 pdf_image_renderer: ^1.0.1
# Text extraction
google_mlkit_text_recognition: ^0.15.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@@ -108,7 +111,7 @@ dev_dependencies:
flutter_launcher_icons: ^0.14.4 flutter_launcher_icons: ^0.14.4
# Generate source code # Generate source code
build_runner: ^2.5.4 build_runner: ^2.6.1
# Riverpod code generation # Riverpod code generation
riverpod_generator: ^2.6.5 riverpod_generator: ^2.6.5

File diff suppressed because it is too large Load Diff

View File

@@ -18,29 +18,29 @@
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mui/icons-material": "^7.1.2", "@mui/icons-material": "^7.1.2",
"@mui/material": "^7.1.2", "@mui/material": "^7.1.2",
"@mui/x-charts": "^8.8.0", "@mui/x-charts": "^8.10.2",
"@mui/x-data-grid": "^8.8.0", "@mui/x-data-grid": "^8.9.2",
"@mui/x-date-pickers": "^8.8.0", "@mui/x-date-pickers": "^8.9.2",
"date-and-time": "^3.6.0", "date-and-time": "^3.6.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.18",
"filesize": "^10.1.6", "filesize": "^10.1.6",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^19.1.0", "react": "^19.1.1",
"react-dom": "^19.1.0", "react-dom": "^19.1.1",
"react-router": "^7.6.3", "react-router": "^7.6.3",
"react-router-dom": "^7.6.3", "react-router-dom": "^7.6.3",
"ts-pattern": "^5.7.1" "ts-pattern": "^5.8.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.31.0", "@eslint/js": "^9.33.0",
"@types/react": "^19.1.8", "@types/react": "^19.1.12",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^4.6.0", "@vitejs/plugin-react": "^4.7.0",
"eslint": "^9.26.0", "eslint": "^9.32.0",
"eslint-plugin-react-dom": "^1.49.0", "eslint-plugin-react-dom": "^1.52.4",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^00.4.20", "eslint-plugin-react-refresh": "^00.4.20",
"eslint-plugin-react-x": "^1.52.3", "eslint-plugin-react-x": "^1.52.9",
"globals": "^16.3.0", "globals": "^16.3.0",
"typescript": "~5.8.3", "typescript": "~5.8.3",
"typescript-eslint": "^8.32.1", "typescript-eslint": "^8.32.1",

View File

@@ -3,6 +3,7 @@ import { APIClient } from "./ApiClient";
export interface ServerConfig { export interface ServerConfig {
auth_disabled: boolean; auth_disabled: boolean;
oidc_provider_name: string; oidc_provider_name: string;
apk_download_url: string;
accounts_types: AccountType[]; accounts_types: AccountType[];
constraints: ServerConstraints; constraints: ServerConstraints;
} }

View File

@@ -280,6 +280,8 @@ function CreatedToken(p: { token: TokenWithSecret }): React.ReactElement {
The API token was successfully created. Please note the following The API token was successfully created. Please note the following
information as they won't be available next. information as they won't be available next.
<br /> <br />
API URL : <CopyTextChip text={APIClient.ActualBackendURL()} />
<br />
Token ID: <CopyTextChip text={p.token.id.toString()} /> Token ID: <CopyTextChip text={p.token.id.toString()} />
<br /> <br />
Token value: <CopyTextChip text={p.token.token} /> Token value: <CopyTextChip text={p.token.token} />

View File

@@ -1,5 +1,6 @@
import { mdiApi, mdiCash } from "@mdi/js"; import { mdiApi, mdiCash } from "@mdi/js";
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import AndroidIcon from "@mui/icons-material/Android";
import CloudDownloadIcon from "@mui/icons-material/CloudDownload"; import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
import LogoutIcon from "@mui/icons-material/Logout"; import LogoutIcon from "@mui/icons-material/Logout";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
@@ -10,6 +11,7 @@ import MenuItem from "@mui/material/MenuItem";
import Toolbar from "@mui/material/Toolbar"; import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import * as React from "react"; import * as React from "react";
import { ServerApi } from "../api/ServerApi";
import { useAuthInfo } from "./BaseAuthenticatedPage"; import { useAuthInfo } from "./BaseAuthenticatedPage";
import { DarkThemeButton } from "./DarkThemeButtonWidget"; import { DarkThemeButton } from "./DarkThemeButtonWidget";
import { PublicModeButton } from "./PublicModeButtonWidget"; import { PublicModeButton } from "./PublicModeButtonWidget";
@@ -100,6 +102,18 @@ export function MoneyWebAppBar(p: {
</MenuItem> </MenuItem>
</RouterLink> </RouterLink>
{/* APK download */}
<RouterLink to={ServerApi.Config.apk_download_url}>
<MenuItem>
<ListItemIcon>
<AndroidIcon />
</ListItemIcon>
<ListItemText secondary="Scan expenses from your smartphone">
Mobile Application
</ListItemText>
</MenuItem>
</RouterLink>
<Divider /> <Divider />
{/* Sign out */} {/* Sign out */}