Compare commits
	
		
			1 Commits
		
	
	
		
			renovate/e
			...
			fc6edfc4d8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fc6edfc4d8 | 
							
								
								
									
										150
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										150
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -36,9 +36,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "actix-files"
 | 
			
		||||
version = "0.6.8"
 | 
			
		||||
version = "0.6.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6c0d87f10d70e2948ad40e8edea79c8e77c6c66e0250a4c1f09b690465199576"
 | 
			
		||||
checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "actix-http",
 | 
			
		||||
 "actix-service",
 | 
			
		||||
@@ -46,7 +46,7 @@ dependencies = [
 | 
			
		||||
 "actix-web",
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "derive_more 2.0.1",
 | 
			
		||||
 "derive_more 0.99.20",
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "http-range",
 | 
			
		||||
 "log",
 | 
			
		||||
@@ -87,7 +87,7 @@ dependencies = [
 | 
			
		||||
 "mime",
 | 
			
		||||
 "percent-encoding",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "rand 0.9.2",
 | 
			
		||||
 "rand 0.9.1",
 | 
			
		||||
 "sha1",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "tokio",
 | 
			
		||||
@@ -364,6 +364,12 @@ dependencies = [
 | 
			
		||||
 "alloc-no-stdlib",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "android-tzdata"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "android_system_properties"
 | 
			
		||||
version = "0.1.5"
 | 
			
		||||
@@ -425,9 +431,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "anyhow"
 | 
			
		||||
version = "1.0.100"
 | 
			
		||||
version = "1.0.98"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
 | 
			
		||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "arbitrary"
 | 
			
		||||
@@ -560,9 +566,9 @@ checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "binstring"
 | 
			
		||||
version = "0.1.7"
 | 
			
		||||
version = "0.1.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef"
 | 
			
		||||
checksum = "9a3a3c2603413428303761fae99d4b6d936404208221a44eba47d7c1e6dd03a3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
@@ -592,9 +598,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "block-buffer"
 | 
			
		||||
version = "0.11.0-rc.5"
 | 
			
		||||
version = "0.11.0-rc.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949"
 | 
			
		||||
checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "hybrid-array",
 | 
			
		||||
]
 | 
			
		||||
@@ -694,15 +700,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "chrono"
 | 
			
		||||
version = "0.4.42"
 | 
			
		||||
version = "0.4.41"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
 | 
			
		||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "android-tzdata",
 | 
			
		||||
 "iana-time-zone",
 | 
			
		||||
 "js-sys",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "wasm-bindgen",
 | 
			
		||||
 "windows-link 0.2.0",
 | 
			
		||||
 "windows-link",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -717,9 +724,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "clap"
 | 
			
		||||
version = "4.5.51"
 | 
			
		||||
version = "4.5.41"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
 | 
			
		||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "clap_builder",
 | 
			
		||||
 "clap_derive",
 | 
			
		||||
@@ -727,9 +734,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "clap_builder"
 | 
			
		||||
version = "4.5.51"
 | 
			
		||||
version = "4.5.41"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
 | 
			
		||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anstream",
 | 
			
		||||
 "anstyle",
 | 
			
		||||
@@ -739,9 +746,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "clap_derive"
 | 
			
		||||
version = "4.5.49"
 | 
			
		||||
version = "4.5.41"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
 | 
			
		||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "heck",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
@@ -941,18 +948,18 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crypto-common"
 | 
			
		||||
version = "0.2.0-rc.4"
 | 
			
		||||
version = "0.2.0-rc.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6"
 | 
			
		||||
checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "hybrid-array",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ct-codecs"
 | 
			
		||||
version = "1.1.6"
 | 
			
		||||
version = "1.1.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8"
 | 
			
		||||
checksum = "dd0d274c65cbc1c34703d2fc2ce0fb892ff68f4516b677671a2f238a30b9b2b2"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ctr"
 | 
			
		||||
@@ -1152,13 +1159,13 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "digest"
 | 
			
		||||
version = "0.11.0-rc.3"
 | 
			
		||||
version = "0.11.0-rc.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d"
 | 
			
		||||
checksum = "460dd7f37e4950526b54a5a6b1f41b6c8e763c58eb9a8fc8fc05ba5c2f44ca7b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "block-buffer 0.11.0-rc.5",
 | 
			
		||||
 "block-buffer 0.11.0-rc.4",
 | 
			
		||||
 "const-oid 0.10.1",
 | 
			
		||||
 "crypto-common 0.2.0-rc.4",
 | 
			
		||||
 "crypto-common 0.2.0-rc.3",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -1599,18 +1606,18 @@ checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hmac-sha256"
 | 
			
		||||
version = "1.1.12"
 | 
			
		||||
version = "1.1.8"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
 | 
			
		||||
checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "digest 0.10.7",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hmac-sha512"
 | 
			
		||||
version = "1.1.7"
 | 
			
		||||
version = "1.1.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280"
 | 
			
		||||
checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "digest 0.10.7",
 | 
			
		||||
]
 | 
			
		||||
@@ -1689,9 +1696,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "hybrid-array"
 | 
			
		||||
version = "0.4.5"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0"
 | 
			
		||||
checksum = "891d15931895091dea5c47afa5b3c9a01ba634b311919fd4d41388fa0e3d76af"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "typenum",
 | 
			
		||||
]
 | 
			
		||||
@@ -1998,9 +2005,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "jwt-simple"
 | 
			
		||||
version = "0.12.13"
 | 
			
		||||
version = "0.12.12"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a"
 | 
			
		||||
checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "binstring",
 | 
			
		||||
@@ -2018,7 +2025,7 @@ dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "superboring",
 | 
			
		||||
 "thiserror 2.0.17",
 | 
			
		||||
 "thiserror 2.0.12",
 | 
			
		||||
 "zeroize",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -2150,9 +2157,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "log"
 | 
			
		||||
version = "0.4.28"
 | 
			
		||||
version = "0.4.27"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
 | 
			
		||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "lzma-rs"
 | 
			
		||||
@@ -2291,15 +2298,15 @@ dependencies = [
 | 
			
		||||
 "light-openid",
 | 
			
		||||
 "log",
 | 
			
		||||
 "mime_guess",
 | 
			
		||||
 "rand 0.9.2",
 | 
			
		||||
 "rand 0.9.1",
 | 
			
		||||
 "rust-embed",
 | 
			
		||||
 "rust-s3",
 | 
			
		||||
 "rust_xlsxwriter",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "sha2 0.11.0-rc.2",
 | 
			
		||||
 "sha2 0.11.0-rc.0",
 | 
			
		||||
 "tempfile",
 | 
			
		||||
 "thiserror 2.0.17",
 | 
			
		||||
 "thiserror 2.0.12",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "zip",
 | 
			
		||||
]
 | 
			
		||||
@@ -2734,9 +2741,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rand"
 | 
			
		||||
version = "0.9.2"
 | 
			
		||||
version = "0.9.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
 | 
			
		||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "rand_chacha 0.9.0",
 | 
			
		||||
 "rand_core 0.9.3",
 | 
			
		||||
@@ -3205,28 +3212,18 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde"
 | 
			
		||||
version = "1.0.228"
 | 
			
		||||
version = "1.0.219"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde_core",
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_core"
 | 
			
		||||
version = "1.0.228"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
 | 
			
		||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_derive"
 | 
			
		||||
version = "1.0.228"
 | 
			
		||||
version = "1.0.219"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
 | 
			
		||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@@ -3235,15 +3232,14 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_json"
 | 
			
		||||
version = "1.0.145"
 | 
			
		||||
version = "1.0.140"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
 | 
			
		||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "ryu",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_core",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -3300,13 +3296,13 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sha2"
 | 
			
		||||
version = "0.11.0-rc.2"
 | 
			
		||||
version = "0.11.0-rc.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924"
 | 
			
		||||
checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "cpufeatures",
 | 
			
		||||
 "digest 0.11.0-rc.3",
 | 
			
		||||
 "digest 0.11.0-rc.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -3494,11 +3490,11 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror"
 | 
			
		||||
version = "2.0.17"
 | 
			
		||||
version = "2.0.12"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
 | 
			
		||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror-impl 2.0.17",
 | 
			
		||||
 "thiserror-impl 2.0.12",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -3514,9 +3510,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror-impl"
 | 
			
		||||
version = "2.0.17"
 | 
			
		||||
version = "2.0.12"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
 | 
			
		||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@@ -3998,7 +3994,7 @@ checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows-implement",
 | 
			
		||||
 "windows-interface",
 | 
			
		||||
 "windows-link 0.1.1",
 | 
			
		||||
 "windows-link",
 | 
			
		||||
 "windows-result",
 | 
			
		||||
 "windows-strings 0.4.1",
 | 
			
		||||
]
 | 
			
		||||
@@ -4031,12 +4027,6 @@ version = "0.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows-link"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows-registry"
 | 
			
		||||
version = "0.4.0"
 | 
			
		||||
@@ -4054,7 +4044,7 @@ version = "0.3.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows-link 0.1.1",
 | 
			
		||||
 "windows-link",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -4063,7 +4053,7 @@ version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows-link 0.1.1",
 | 
			
		||||
 "windows-link",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -4072,7 +4062,7 @@ version = "0.4.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows-link 0.1.1",
 | 
			
		||||
 "windows-link",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -4321,9 +4311,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "zeroize"
 | 
			
		||||
version = "1.8.2"
 | 
			
		||||
version = "1.8.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
 | 
			
		||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "zeroize_derive",
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -5,35 +5,35 @@ edition = "2024"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
env_logger = "0.11.8"
 | 
			
		||||
log = "0.4.28"
 | 
			
		||||
log = "0.4.27"
 | 
			
		||||
diesel = { version = "2.2.12", features = ["postgres", "r2d2"] }
 | 
			
		||||
diesel_migrations = "2.2.0"
 | 
			
		||||
clap = { version = "4.5.51", features = ["env", "derive"] }
 | 
			
		||||
clap = { version = "4.5.41", features = ["env", "derive"] }
 | 
			
		||||
actix-web = "4.11.0"
 | 
			
		||||
actix-cors = "0.7.1"
 | 
			
		||||
actix-multipart = "0.7.2"
 | 
			
		||||
actix-remote-ip = "0.1.0"
 | 
			
		||||
actix-session = { version = "0.10.1", features = ["redis-session"] }
 | 
			
		||||
actix-files = "0.6.8"
 | 
			
		||||
actix-files = "0.6.6"
 | 
			
		||||
lazy_static = "1.5.0"
 | 
			
		||||
anyhow = "1.0.100"
 | 
			
		||||
serde = { version = "1.0.228", features = ["derive"] }
 | 
			
		||||
anyhow = "1.0.98"
 | 
			
		||||
serde = { version = "1.0.219", features = ["derive"] }
 | 
			
		||||
rust-s3 = "0.36.0-beta.2"
 | 
			
		||||
thiserror = "2.0.17"
 | 
			
		||||
thiserror = "2.0.12"
 | 
			
		||||
tokio = "1.45.1"
 | 
			
		||||
futures-util = "0.3.31"
 | 
			
		||||
serde_json = "1.0.145"
 | 
			
		||||
serde_json = "1.0.140"
 | 
			
		||||
light-openid = "1.0.4"
 | 
			
		||||
rand = "0.9.2"
 | 
			
		||||
rand = "0.9.1"
 | 
			
		||||
ipnet = { version = "2.11.0", features = ["serde"] }
 | 
			
		||||
lazy-regex = "3.4.1"
 | 
			
		||||
jwt-simple = { version = "0.12.13", default-features = false, features = ["pure-rust"] }
 | 
			
		||||
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }
 | 
			
		||||
mime_guess = "2.0.5"
 | 
			
		||||
rust-embed = { version = "8.7.2" }
 | 
			
		||||
sha2 = "0.11.0-rc.2"
 | 
			
		||||
sha2 = "0.11.0-rc.0"
 | 
			
		||||
base16ct = "0.2.0"
 | 
			
		||||
httpdate = "1.0.3"
 | 
			
		||||
chrono = "0.4.42"
 | 
			
		||||
chrono = "0.4.41"
 | 
			
		||||
tempfile = "3.20.0"
 | 
			
		||||
zip = "3.0.0"
 | 
			
		||||
rust_xlsxwriter = "0.87.0"
 | 
			
		||||
@@ -22,10 +22,14 @@ pub struct AppConfig {
 | 
			
		||||
    #[clap(short = 'S', long, env, default_value = "")]
 | 
			
		||||
    secret: String,
 | 
			
		||||
 | 
			
		||||
    /// Specify whether the cookie should be transmitted only over secure connections
 | 
			
		||||
    #[clap(long, env)]
 | 
			
		||||
    pub cookie_secure: bool,
 | 
			
		||||
 | 
			
		||||
    /// Unsecure : for development, bypass authentication, using the account with the given
 | 
			
		||||
    /// email address by default
 | 
			
		||||
    #[clap(long, env)]
 | 
			
		||||
    unsecure_auto_login_email: Option<String>,
 | 
			
		||||
    pub unsecure_auto_login_email: Option<String>,
 | 
			
		||||
 | 
			
		||||
    /// PostgreSQL database host
 | 
			
		||||
    #[clap(long, env, default_value = "localhost")]
 | 
			
		||||
@@ -122,14 +126,6 @@ pub struct AppConfig {
 | 
			
		||||
    /// Redis password
 | 
			
		||||
    #[clap(long, env, default_value = "secretredis")]
 | 
			
		||||
    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! {
 | 
			
		||||
@@ -144,17 +140,26 @@ impl AppConfig {
 | 
			
		||||
        &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
 | 
			
		||||
    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
 | 
			
		||||
    pub fn cookie_domain(&self) -> Option<String> {
 | 
			
		||||
        if cfg!(debug_assertions) {
 | 
			
		||||
            let domain = self.website_origin.split_once("://")?.1;
 | 
			
		||||
            Some(
 | 
			
		||||
                domain
 | 
			
		||||
                    .split_once(':')
 | 
			
		||||
                    .map(|s| s.0)
 | 
			
		||||
                    .unwrap_or(domain)
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            // In release mode, the web app is hosted on the same origin as the API
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get app secret
 | 
			
		||||
 
 | 
			
		||||
@@ -63,23 +63,24 @@ pub async fn download(
 | 
			
		||||
pub async fn serve_file(req: HttpRequest, file: &File, download_file: bool) -> HttpResult {
 | 
			
		||||
    if !download_file {
 | 
			
		||||
        // Check if the browser already knows the etag
 | 
			
		||||
        if let Some(c) = req.headers().get(header::IF_NONE_MATCH)
 | 
			
		||||
            && c.to_str().unwrap_or("") == file.sha512.as_str()
 | 
			
		||||
        {
 | 
			
		||||
        if let Some(c) = req.headers().get(header::IF_NONE_MATCH) {
 | 
			
		||||
            if c.to_str().unwrap_or("") == file.sha512.as_str() {
 | 
			
		||||
                return Ok(HttpResponse::NotModified().finish());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check if the browser already knows the file by date
 | 
			
		||||
        if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
 | 
			
		||||
            let date_str = c.to_str().unwrap_or("");
 | 
			
		||||
            if let Ok(date) = httpdate::parse_http_date(date_str)
 | 
			
		||||
                && date.add(Duration::from_secs(1))
 | 
			
		||||
            if let Ok(date) = httpdate::parse_http_date(date_str) {
 | 
			
		||||
                if date.add(Duration::from_secs(1))
 | 
			
		||||
                    >= time_utils::unix_to_system_time(file.time_create as u64)
 | 
			
		||||
                {
 | 
			
		||||
                    return Ok(HttpResponse::NotModified().finish());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let mut res = HttpResponse::Ok();
 | 
			
		||||
    res.content_type(file.mime_type.as_str())
 | 
			
		||||
        .insert_header(("etag", file.sha512.as_str()))
 | 
			
		||||
 
 | 
			
		||||
@@ -64,11 +64,11 @@ pub async fn get_list_of_account(
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(limit) = query.limit
 | 
			
		||||
        && list.len() > limit
 | 
			
		||||
    {
 | 
			
		||||
    if let Some(limit) = query.limit {
 | 
			
		||||
        if list.len() > limit {
 | 
			
		||||
            list = list[..limit].to_vec();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok().json(list))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,6 @@ impl Default for ServerConstraints {
 | 
			
		||||
struct ServerConfig {
 | 
			
		||||
    auth_disabled: bool,
 | 
			
		||||
    oidc_provider_name: &'static str,
 | 
			
		||||
    apk_download_url: &'static str,
 | 
			
		||||
    accounts_types: &'static [AccountTypeDesc],
 | 
			
		||||
    constraints: ServerConstraints,
 | 
			
		||||
}
 | 
			
		||||
@@ -80,7 +79,6 @@ impl Default for ServerConfig {
 | 
			
		||||
        Self {
 | 
			
		||||
            auth_disabled: AppConfig::get().is_auth_disabled(),
 | 
			
		||||
            oidc_provider_name: AppConfig::get().openid_provider().name,
 | 
			
		||||
            apk_download_url: AppConfig::get().apk_download_url.as_str(),
 | 
			
		||||
            constraints: Default::default(),
 | 
			
		||||
            accounts_types: &ACCOUNT_TYPES,
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -120,9 +120,8 @@ impl FromRequest for AuthExtractor {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check IP restriction
 | 
			
		||||
                if let Some(net) = token.ip_net()
 | 
			
		||||
                    && !net.contains(&remote_ip.0)
 | 
			
		||||
                {
 | 
			
		||||
                if let Some(net) = token.ip_net() {
 | 
			
		||||
                    if !net.contains(&remote_ip.0) {
 | 
			
		||||
                        log::error!(
 | 
			
		||||
                            "Trying to use token {:?} from unauthorized IP address: {remote_ip:?}",
 | 
			
		||||
                            token.id()
 | 
			
		||||
@@ -131,6 +130,7 @@ impl FromRequest for AuthExtractor {
 | 
			
		||||
                            "This token cannot be used from this IP address!",
 | 
			
		||||
                        ));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check for write access
 | 
			
		||||
                if token.read_only && !req.method().is_safe() {
 | 
			
		||||
@@ -163,11 +163,11 @@ impl FromRequest for AuthExtractor {
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // Update last use (if needed)
 | 
			
		||||
                if token.shall_update_time_used()
 | 
			
		||||
                    && let Err(e) = tokens_service::update_time_used(&token).await
 | 
			
		||||
                {
 | 
			
		||||
                if token.shall_update_time_used() {
 | 
			
		||||
                    if let Err(e) = tokens_service::update_time_used(&token).await {
 | 
			
		||||
                        log::error!("Failed to refresh last usage of token! {e}");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Handle tokens expiration
 | 
			
		||||
                if token.is_expired() {
 | 
			
		||||
@@ -182,7 +182,7 @@ impl FromRequest for AuthExtractor {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 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| {
 | 
			
		||||
                    log::error!("Failed to retrieve dev user: {e}");
 | 
			
		||||
                    ErrorPreconditionFailed("Unable to retrieve dev user!")
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
    db_connection::initialize_conn().expect("Failed to connect to PostgresSQL database!");
 | 
			
		||||
 | 
			
		||||
    // 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")
 | 
			
		||||
            .await
 | 
			
		||||
            .expect("Failed to create default account!");
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,11 @@ impl UpdateInboxEntryQuery {
 | 
			
		||||
        let constraints = ServerConstraints::default();
 | 
			
		||||
 | 
			
		||||
        // Check inbox entry label
 | 
			
		||||
        if let Some(label) = &self.label
 | 
			
		||||
            && !constraints.inbox_entry_label.check_str(label)
 | 
			
		||||
        {
 | 
			
		||||
        if let Some(label) = &self.label {
 | 
			
		||||
            if !constraints.inbox_entry_label.check_str(label) {
 | 
			
		||||
                return Ok(Some("Invalid inbox entry label length!"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check the referenced movement
 | 
			
		||||
        if let Some(movement_id) = self.movement_id {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,12 +55,13 @@ impl UpdateMovementQuery {
 | 
			
		||||
        if let Ok(movement) =
 | 
			
		||||
            get_by_account_label_amount_time(self.account_id, &self.label, self.amount, self.time)
 | 
			
		||||
                .await
 | 
			
		||||
            && Some(movement.id()) != ref_movement
 | 
			
		||||
        {
 | 
			
		||||
            if Some(movement.id()) != ref_movement {
 | 
			
		||||
                return Ok(Some(
 | 
			
		||||
                    "A movement taken at the same time with the same label and the same amount already exists!",
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,45 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:go_router/go_router.dart';
 | 
			
		||||
import 'package:hooks_riverpod/hooks_riverpod.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/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: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';
 | 
			
		||||
 | 
			
		||||
/// Scan a document & return generated PDF as byte file
 | 
			
		||||
@riverpod
 | 
			
		||||
Future<(Uint8List?, BaseExpenseInfo?)> _scanDocument(Ref ref) async {
 | 
			
		||||
  final prefs = ref.watch(prefsProvider).requireValue;
 | 
			
		||||
 | 
			
		||||
  final pdf = await scanDocAsPDF();
 | 
			
		||||
  final img = await renderPdf(pdfBytes: pdf);
 | 
			
		||||
  final amount = await extractInfoFromBill(
 | 
			
		||||
    imgBuff: img,
 | 
			
		||||
    extractDates: !prefs.disableExtractDates(),
 | 
			
		||||
Future<Uint8List?> _scanDocument(Ref ref) async {
 | 
			
		||||
  var configuration = DocumentScanningFlow(
 | 
			
		||||
    appearance: DocumentFlowAppearanceConfiguration(
 | 
			
		||||
      statusBarMode: StatusBarMode.DARK,
 | 
			
		||||
    ),
 | 
			
		||||
    cleanScanningSession: true,
 | 
			
		||||
    outputSettings: DocumentScannerOutputSettings(pagesScanLimit: 1),
 | 
			
		||||
    screens: DocumentScannerScreens(
 | 
			
		||||
      review: ReviewScreenConfiguration(enabled: false),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  return (pdf, amount);
 | 
			
		||||
  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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ScanScreen extends HookConsumerWidget {
 | 
			
		||||
@@ -38,8 +52,8 @@ class ScanScreen extends HookConsumerWidget {
 | 
			
		||||
 | 
			
		||||
    restartScan() async {
 | 
			
		||||
      try {
 | 
			
		||||
        ref.invalidate(_scanDocumentProvider);
 | 
			
		||||
        Logger.root.info("Load again startup");
 | 
			
		||||
        final val = ref.refresh(_scanDocumentProvider);
 | 
			
		||||
        Logger.root.info("Load again startup result: $val");
 | 
			
		||||
      } catch (e, s) {
 | 
			
		||||
        Logger.root.shout("Failed to try again startup loading! $e $s");
 | 
			
		||||
      }
 | 
			
		||||
@@ -48,24 +62,21 @@ class ScanScreen extends HookConsumerWidget {
 | 
			
		||||
    return Padding(
 | 
			
		||||
      padding: const EdgeInsets.all(8.0),
 | 
			
		||||
      child: switch (scanDocProvider) {
 | 
			
		||||
        AsyncData(:final value) when value.$1 != null => ExpenseEditor(
 | 
			
		||||
          file: value.$1!,
 | 
			
		||||
          initialData: value.$2,
 | 
			
		||||
        AsyncData(:final value) when value != null => ExpenseEditor(
 | 
			
		||||
          file: value,
 | 
			
		||||
          onFinished: (expense) async {
 | 
			
		||||
            await expenses.add(
 | 
			
		||||
              info: expense,
 | 
			
		||||
              fileContent: value.$1!,
 | 
			
		||||
              fileContent: value,
 | 
			
		||||
              fileMimeType: "application/pdf",
 | 
			
		||||
            );
 | 
			
		||||
            if (context.mounted) {
 | 
			
		||||
              context.pushReplacement(scansPage);
 | 
			
		||||
            }
 | 
			
		||||
            restartScan();
 | 
			
		||||
          },
 | 
			
		||||
          onRescan: restartScan,
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // No data
 | 
			
		||||
        AsyncData(:final value) when value.$1 == null => ScanErrorScreen(
 | 
			
		||||
        AsyncData(:final value) when value == null => ScanErrorScreen(
 | 
			
		||||
          message: "No document scanned!",
 | 
			
		||||
          onTryAgain: restartScan,
 | 
			
		||||
        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -22,11 +22,6 @@ class SettingsScreen extends ConsumerWidget {
 | 
			
		||||
      ref.invalidate(prefsProvider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handleToggleDisableExtractDate(v) async {
 | 
			
		||||
      await prefs.setDisableExtractDates(v);
 | 
			
		||||
      ref.invalidate(prefsProvider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(title: const Text('Settings')),
 | 
			
		||||
      body: ListView(
 | 
			
		||||
@@ -45,14 +40,6 @@ class SettingsScreen extends ConsumerWidget {
 | 
			
		||||
              "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(),
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: const Icon(Icons.info_outline),
 | 
			
		||||
 
 | 
			
		||||
@@ -23,14 +23,6 @@ extension MoneyMgrSharedPreferences on SharedPreferencesWithCache {
 | 
			
		||||
    await setBool("startOnScansListScreen", start);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool disableExtractDates() {
 | 
			
		||||
    return getBool("disableExtractDates") ?? false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> setDisableExtractDates(bool disable) async {
 | 
			
		||||
    await setBool("disableExtractDates", disable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ServerConfig? serverConfig() {
 | 
			
		||||
    final json = getString("serverConfig");
 | 
			
		||||
    if (json != null) return ServerConfig.fromJson(jsonDecode(json));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,86 +0,0 @@
 | 
			
		||||
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,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -6,40 +6,14 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:path/path.dart' as p;
 | 
			
		||||
import 'package:path_provider/path_provider.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
 | 
			
		||||
Future<Uint8List> renderPdf({String? path, Uint8List? pdfBytes}) async {
 | 
			
		||||
Future<Uint8List> renderPdf(
 | 
			
		||||
     {
 | 
			
		||||
      String? path,
 | 
			
		||||
      Uint8List? pdfBytes,
 | 
			
		||||
    }) async {
 | 
			
		||||
  assert(path != null || pdfBytes != null);
 | 
			
		||||
 | 
			
		||||
  // Create temporary file if required
 | 
			
		||||
 
 | 
			
		||||
@@ -40,20 +40,6 @@ class ExpenseEditor extends HookConsumerWidget {
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    handlePickDate() async {
 | 
			
		||||
      final date = await showDatePicker(
 | 
			
		||||
@@ -108,19 +94,6 @@ 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(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text("Expense info"),
 | 
			
		||||
@@ -152,11 +125,8 @@ class ExpenseEditor extends HookConsumerWidget {
 | 
			
		||||
        children: [
 | 
			
		||||
          // Expense preview
 | 
			
		||||
          Expanded(
 | 
			
		||||
            child: GestureDetector(
 | 
			
		||||
              onTap: handleFullScreenInvoice,
 | 
			
		||||
            child: PDFViewer(pdfBytes: file, fit: BoxFit.contain),
 | 
			
		||||
          ),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          SizedBox(height: 10),
 | 
			
		||||
 | 
			
		||||
@@ -167,13 +137,7 @@ class ExpenseEditor extends HookConsumerWidget {
 | 
			
		||||
              decimal: true,
 | 
			
		||||
              signed: false,
 | 
			
		||||
            ),
 | 
			
		||||
            decoration: InputDecoration(
 | 
			
		||||
              labelText: 'Cost',
 | 
			
		||||
              suffixIcon: IconButton(
 | 
			
		||||
                onPressed: handleClearCost,
 | 
			
		||||
                icon: const Icon(Icons.clear),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            decoration: const InputDecoration(labelText: 'Cost'),
 | 
			
		||||
            textInputAction: TextInputAction.done,
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -488,22 +488,6 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
			
		||||
version: 1.0.0+1
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: ^3.8.1
 | 
			
		||||
  sdk: ^3.8.2
 | 
			
		||||
 | 
			
		||||
# Dependencies specify other packages that your package needs in order to work.
 | 
			
		||||
# To automatically upgrade your package dependencies to the latest versions
 | 
			
		||||
@@ -42,14 +42,14 @@ dependencies:
 | 
			
		||||
  flutter_secure_storage: ^9.2.4
 | 
			
		||||
 | 
			
		||||
  # Splash screen
 | 
			
		||||
  flutter_native_splash: ^2.4.7
 | 
			
		||||
  flutter_native_splash: ^2.4.6
 | 
			
		||||
 | 
			
		||||
  # A reactive caching and data-binding framework.
 | 
			
		||||
  hooks_riverpod: ^2.6.1
 | 
			
		||||
  riverpod_annotation: ^2.6.1
 | 
			
		||||
 | 
			
		||||
  # Implement React hooks in Flutter
 | 
			
		||||
  flutter_hooks: ^0.21.3+1
 | 
			
		||||
  flutter_hooks: ^0.21.2
 | 
			
		||||
 | 
			
		||||
  # Router
 | 
			
		||||
  go_router: ^16.0.0
 | 
			
		||||
@@ -68,10 +68,10 @@ dependencies:
 | 
			
		||||
  logging: ^1.3.0
 | 
			
		||||
 | 
			
		||||
  # API authentication
 | 
			
		||||
  dart_jsonwebtoken: 3.3.1
 | 
			
		||||
  dart_jsonwebtoken: ^3.2.0
 | 
			
		||||
 | 
			
		||||
  # API requests
 | 
			
		||||
  dio: 5.9.0
 | 
			
		||||
  dio: ^5.8.0+1
 | 
			
		||||
  http_parser: ^4.1.2
 | 
			
		||||
 | 
			
		||||
  # Qr Code library
 | 
			
		||||
@@ -84,7 +84,7 @@ dependencies:
 | 
			
		||||
  # Document scanner
 | 
			
		||||
  # flutter_doc_scanner: ^0.0.16 # no bundled support yet
 | 
			
		||||
  # https://developers.google.com/ml-kit/tips/installation-paths
 | 
			
		||||
  scanbot_sdk: ^7.0.1
 | 
			
		||||
  scanbot_sdk: ^7.0.0
 | 
			
		||||
 | 
			
		||||
  # Get documents path
 | 
			
		||||
  path_provider: ^2.1.5
 | 
			
		||||
@@ -93,9 +93,6 @@ dependencies:
 | 
			
		||||
  # PDF renderer
 | 
			
		||||
  pdf_image_renderer: ^1.0.1
 | 
			
		||||
 | 
			
		||||
  # Text extraction
 | 
			
		||||
  google_mlkit_text_recognition: ^0.15.0
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
@@ -111,7 +108,7 @@ dev_dependencies:
 | 
			
		||||
  flutter_launcher_icons: ^0.14.4
 | 
			
		||||
 | 
			
		||||
  # Generate source code
 | 
			
		||||
  build_runner: 2.10.1
 | 
			
		||||
  build_runner: ^2.5.4
 | 
			
		||||
 | 
			
		||||
  # Riverpod code generation
 | 
			
		||||
  riverpod_generator: ^2.6.5
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2499
									
								
								moneymgr_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2499
									
								
								moneymgr_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -12,38 +12,38 @@
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@emotion/react": "^11.14.0",
 | 
			
		||||
    "@emotion/styled": "^11.14.1",
 | 
			
		||||
    "@fontsource/roboto": "^5.2.8",
 | 
			
		||||
    "@fontsource/roboto": "^5.2.6",
 | 
			
		||||
    "@jsonjoy.com/base64": "^1.1.2",
 | 
			
		||||
    "@mdi/js": "^7.4.47",
 | 
			
		||||
    "@mdi/react": "^1.6.1",
 | 
			
		||||
    "@mui/icons-material": "^7.1.2",
 | 
			
		||||
    "@mui/material": "^7.1.2",
 | 
			
		||||
    "@mui/x-charts": "^8.16.0",
 | 
			
		||||
    "@mui/x-data-grid": "^8.15.0",
 | 
			
		||||
    "@mui/x-date-pickers": "^8.15.0",
 | 
			
		||||
    "@mui/x-charts": "^8.8.0",
 | 
			
		||||
    "@mui/x-data-grid": "^8.8.0",
 | 
			
		||||
    "@mui/x-date-pickers": "^8.8.0",
 | 
			
		||||
    "date-and-time": "^3.6.0",
 | 
			
		||||
    "dayjs": "^1.11.19",
 | 
			
		||||
    "dayjs": "^1.11.13",
 | 
			
		||||
    "filesize": "^10.1.6",
 | 
			
		||||
    "qrcode.react": "^4.2.0",
 | 
			
		||||
    "react": "^19.2.0",
 | 
			
		||||
    "react-dom": "^19.2.0",
 | 
			
		||||
    "react": "^19.1.0",
 | 
			
		||||
    "react-dom": "^19.1.0",
 | 
			
		||||
    "react-router": "^7.6.3",
 | 
			
		||||
    "react-router-dom": "^7.6.3",
 | 
			
		||||
    "ts-pattern": "^5.8.0"
 | 
			
		||||
    "ts-pattern": "^5.7.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@eslint/js": "^9.39.1",
 | 
			
		||||
    "@types/react": "^19.2.2",
 | 
			
		||||
    "@types/react-dom": "^19.2.2",
 | 
			
		||||
    "@vitejs/plugin-react": "^4.7.0",
 | 
			
		||||
    "eslint": "^9.32.0",
 | 
			
		||||
    "eslint-plugin-react-dom": "^1.52.4",
 | 
			
		||||
    "@eslint/js": "^9.31.0",
 | 
			
		||||
    "@types/react": "^19.1.8",
 | 
			
		||||
    "@types/react-dom": "^19.1.6",
 | 
			
		||||
    "@vitejs/plugin-react": "^4.6.0",
 | 
			
		||||
    "eslint": "^9.26.0",
 | 
			
		||||
    "eslint-plugin-react-dom": "^1.52.3",
 | 
			
		||||
    "eslint-plugin-react-hooks": "^5.2.0",
 | 
			
		||||
    "eslint-plugin-react-refresh": "^00.4.20",
 | 
			
		||||
    "eslint-plugin-react-x": "^1.52.9",
 | 
			
		||||
    "eslint-plugin-react-x": "^1.52.3",
 | 
			
		||||
    "globals": "^16.3.0",
 | 
			
		||||
    "typescript": "~5.8.3",
 | 
			
		||||
    "typescript-eslint": "^8.32.1",
 | 
			
		||||
    "vite": "^6.3.6"
 | 
			
		||||
    "vite": "^6.3.5"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import { APIClient } from "./ApiClient";
 | 
			
		||||
export interface ServerConfig {
 | 
			
		||||
  auth_disabled: boolean;
 | 
			
		||||
  oidc_provider_name: string;
 | 
			
		||||
  apk_download_url: string;
 | 
			
		||||
  accounts_types: AccountType[];
 | 
			
		||||
  constraints: ServerConstraints;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -280,8 +280,6 @@ function CreatedToken(p: { token: TokenWithSecret }): React.ReactElement {
 | 
			
		||||
          The API token was successfully created. Please note the following
 | 
			
		||||
          information as they won't be available next.
 | 
			
		||||
          <br />
 | 
			
		||||
          API URL : <CopyTextChip text={APIClient.ActualBackendURL()} />
 | 
			
		||||
          <br />
 | 
			
		||||
          Token ID: <CopyTextChip text={p.token.id.toString()} />
 | 
			
		||||
          <br />
 | 
			
		||||
          Token value: <CopyTextChip text={p.token.token} />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import { mdiApi, mdiCash } from "@mdi/js";
 | 
			
		||||
import Icon from "@mdi/react";
 | 
			
		||||
import AndroidIcon from "@mui/icons-material/Android";
 | 
			
		||||
import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
 | 
			
		||||
import LogoutIcon from "@mui/icons-material/Logout";
 | 
			
		||||
import SettingsIcon from "@mui/icons-material/Settings";
 | 
			
		||||
@@ -11,7 +10,6 @@ import MenuItem from "@mui/material/MenuItem";
 | 
			
		||||
import Toolbar from "@mui/material/Toolbar";
 | 
			
		||||
import Typography from "@mui/material/Typography";
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { ServerApi } from "../api/ServerApi";
 | 
			
		||||
import { useAuthInfo } from "./BaseAuthenticatedPage";
 | 
			
		||||
import { DarkThemeButton } from "./DarkThemeButtonWidget";
 | 
			
		||||
import { PublicModeButton } from "./PublicModeButtonWidget";
 | 
			
		||||
@@ -102,18 +100,6 @@ export function MoneyWebAppBar(p: {
 | 
			
		||||
              </MenuItem>
 | 
			
		||||
            </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 />
 | 
			
		||||
 | 
			
		||||
            {/* Sign out */}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user