fix: small changes

This commit is contained in:
omkar 2025-02-19 09:03:24 +05:30
parent 46571852de
commit fdd0e772cd
74 changed files with 1681 additions and 2414 deletions

7
.gitignore vendored
View file

@ -49,3 +49,10 @@ app.*.map.json
.env
node_modules
# Rust related
.cargo/
target/
# Generated messages
*/**/messages/

957
Cargo.lock generated Normal file
View file

@ -0,0 +1,957 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "allo-isolate"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f67642eb6773fb42a95dd3b348c305ee18dee6642274c6b412d67e985e3befc"
dependencies = [
"atomic",
]
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-oneshot"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae47de2a02d543205f3f5457a90b6ecbc9494db70557bd29590ec8f1ddff5463"
dependencies = [
"futures-micro",
]
[[package]]
name = "async-trait"
version = "0.1.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]]
name = "cc"
version = "1.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "flate2"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-micro"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b460264b3593d68b16a7bc35f7bc226ddfebdf9a1c8db1ed95d5cc6b7168c826"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "hub"
version = "0.1.0"
dependencies = [
"log",
"messages",
"prost",
"rinf",
"tokio",
]
[[package]]
name = "idna"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "log"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "messages"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02e0c66a1c30e448db540072c1b3811859ebeac2671f96bba20ed7932210205"
dependencies = [
"async-channel",
"async-oneshot",
"async-trait",
"futures",
"once_cell",
"tokio",
]
[[package]]
name = "miniz_oxide"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
dependencies = [
"adler2",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "os-thread-local"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7fc7fa9ea7dc8907f9b10e730106ed0011926e7f5abb382530ac91d1af2b7c"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "prost"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "protoc-prebuilt"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d85d4641fe3b8c6e853dfd09fe35379bc6b6e66bd692ac29ed4f7087de69ed5"
dependencies = [
"ureq",
"zip",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rinf"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37f9c0095f302b63d3e7641454abdd9996cafb737526f949c7c89695a1e6bdc6"
dependencies = [
"allo-isolate",
"home",
"js-sys",
"os-thread-local",
"protoc-prebuilt",
"wasm-bindgen",
"wasm-bindgen-futures",
"which",
]
[[package]]
name = "ring"
version = "0.17.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustls"
version = "0.23.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinyvec"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
dependencies = [
"base64",
"log",
"once_cell",
"rustls",
"rustls-pki-types",
"url",
"webpki-roots",
]
[[package]]
name = "url"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.26.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "which"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283"
dependencies = [
"either",
"env_home",
"rustix",
"winsafe",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
"flate2",
]

8
Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
# This file is used for telling Rust-related tools
# where various Rust crates are.
# This also unifies `./target` output folder and
# various Rust configurations.
[workspace]
members = ["./native/*"]
resolver = "2"

View file

@ -7,14 +7,9 @@ An open-source media manager app built with Flutter, designed to stream videos f
- **Cross-Platform Support**: Works on Android, iOS supported by Flutter.
- **Open Source**: Contributions are welcome!
## TODO
- [ ] Update dialog
## Screenshots
<img src="readme/image/home.jpg" width="250" title="Home Page">
<img alt="Application Screenshot" src="https://downloads.madari.media/madari_app_images/madari_5.jpeg" width="250" title="Home Page">
## Getting Started
@ -48,9 +43,52 @@ This application is designed to be an open source media player that can process
The developers of Madari:
Do not host, develop, or distribute any content
Do not host, develop, or distribute any content
Do not endorse or promote copyright infringement or illegal activities
Are not responsible for third-party add-ons or content accessed through them
Expect users to respect intellectual property rights and their local laws
Users are solely responsible for the add-ons they install and content they access through the application.
## Using Rust Inside Flutter
This project leverages Flutter for GUI and Rust for the backend logic,
utilizing the capabilities of the
[Rinf](https://pub.dev/packages/rinf) framework.
To run and build this app, you need to have
[Flutter SDK](https://docs.flutter.dev/get-started/install)
and [Rust toolchain](https://www.rust-lang.org/tools/install)
installed on your system.
You can check that your system is ready with the commands below.
Note that all the Flutter subcomponents should be installed.
```shell
rustc --version
flutter doctor
```
You also need to have the CLI tool for Rinf ready.
```shell
cargo install rinf
```
Messages sent between Dart and Rust are implemented using Protobuf.
If you have newly cloned the project repository
or made changes to the `.proto` files in the `./messages` directory,
run the following command:
```shell
rinf message
```
Now you can run and build this app just like any other Flutter projects.
```shell
flutter run
```
For detailed instructions on writing Rust and Flutter together,
please refer to Rinf's [documentation](https://rinf.cunarist.com).

File diff suppressed because one or more lines are too long

View file

@ -136,6 +136,8 @@ PODS:
- permission_handler_apple (9.3.0):
- Flutter
- PromisesObjC (2.4.0)
- rinf (0.1.0):
- Flutter
- SDWebImage (5.20.0):
- SDWebImage/Core (= 5.20.0)
- SDWebImage/Core (5.20.0)
@ -190,6 +192,7 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- rinf (from `.symlinks/plugins/rinf/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
@ -248,6 +251,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
rinf:
:path: ".symlinks/plugins/rinf/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
@ -293,6 +298,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
rinf: 75ee395a39eedbc6aa8c7698ad04faa0f47753c6
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d

View file

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:madari_client/features/pocketbase/service/pocketbase.service.dart';
import 'package:madari_client/features/video_player/container/state/video_settings.dart';
import 'package:provider/provider.dart';
import '../features/settings/service/selected_profile.dart';
import '../features/theme/provider/theme_provider.dart';
import 'app_router.dart';
@ -25,31 +24,33 @@ class _AppDefaultState extends State<AppDefault> {
void initState() {
_router = createRouterDesktop();
if (AppPocketBaseService.instance.pb.authStore.isValid) {
SelectedProfileService.instance.initialize();
}
super.initState();
}
@override
Widget build(BuildContext context) {
return Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
final theme = themeProvider.getTheme();
return ChangeNotifierProvider(
create: (context) => VideoSettingsProvider(),
child: MaterialApp.router(
routerConfig: _router,
title: "Madari",
theme: theme.copyWith(
textTheme: GoogleFonts.exo2TextTheme(theme.textTheme),
),
debugShowCheckedModeBanner: false, // comes in the way of the search
),
);
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
},
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
final theme = themeProvider.getTheme();
return ChangeNotifierProvider(
create: (context) => VideoSettingsProvider(),
child: MaterialApp.router(
routerConfig: _router,
title: "Madari",
theme: theme.copyWith(
textTheme: GoogleFonts.exo2TextTheme(theme.textTheme),
),
debugShowCheckedModeBanner:
false, // comes in the way of the search
),
);
},
),
);
}
}

View file

@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import 'package:madari_client/features/downloads/pages/downloads_page.dart';
import 'package:madari_client/features/offline_ratings/pages/offline_ratings.dart';
import 'package:madari_client/features/settings/pages/profile_page.dart';
import 'package:madari_engine/madari_engine.dart';
import '../features/accounts/pages/external_account.dart';
import '../features/auth/pages/forget_password_page.dart';
@ -14,7 +15,6 @@ import '../features/layout/widgets/scaffold_with_nav.dart';
import '../features/library/container/create_list_widget.dart';
import '../features/library/pages/library.page.dart';
import '../features/library/pages/list_detail_page.dart';
import '../features/library/types/library_types.dart';
import '../features/pocketbase/service/pocketbase.service.dart';
import '../features/settings/pages/appearance_page.dart';
import '../features/settings/pages/change_password_page.dart';
@ -27,7 +27,6 @@ import '../features/settings/pages/subprofiles_page.dart';
import '../features/streamio_addons/pages/stremio_addons_page.dart';
import '../features/video_player/container/video_player.dart';
import '../features/widgetter/plugins/stremio/pages/streamio_item_viewer.dart';
import '../features/zeku/pages/integration_page.dart';
final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<StatefulNavigationShellState> _shellNavigatorKey =
@ -46,7 +45,7 @@ final GlobalKey<NavigatorState> _exploreNavigatorKey =
GoRouter createRouterDesktop() {
return GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/',
initialLocation: '/profile',
refreshListenable: ValueNotifier(
AppPocketBaseService.instance.pb.authStore.onChange,
),
@ -83,10 +82,6 @@ GoRouter createRouterDesktop() {
path: "/downloads",
builder: (context, state) => const DownloadsPage(),
),
GoRoute(
path: "/settings/integration",
builder: (context, state) => const IntegrationPage(),
),
StatefulShellRoute.indexedStack(
key: _shellNavigatorKey,
builder: (context, state, navigationShell) {

View file

@ -1,234 +0,0 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import '../service/zeku_service.dart';
class ServicesGrid extends StatefulWidget {
const ServicesGrid({super.key});
@override
State<ServicesGrid> createState() => _ServicesGridState();
}
class _ServicesGridState extends State<ServicesGrid> {
final _zekuService = ZekuService();
late Future<List<ZekuServiceItem>> _servicesFuture;
@override
void initState() {
super.initState();
_servicesFuture = _zekuService.getServices();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return FutureBuilder<List<ZekuServiceItem>>(
future: _servicesFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(
color: theme.colorScheme.primary,
),
);
}
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
color: theme.colorScheme.error,
size: 48,
),
const SizedBox(height: 16),
Text(
'Failed to load services',
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.error,
),
),
const SizedBox(height: 8),
Text(
'${snapshot.error}',
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.error,
),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
setState(() {
_servicesFuture = _zekuService.getServices();
});
},
child: const Text('Retry'),
),
],
),
);
}
final services = snapshot.data ?? [];
if (services.isEmpty) {
return Center(
child: Text(
'No services available',
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(0),
itemCount: services.length,
itemBuilder: (context, index) {
final service = services[index];
return ServiceCard(
service: service,
onRefresh: () {
setState(() {
_servicesFuture = _zekuService.getServices();
});
},
);
},
);
},
);
}
}
class ServiceCard extends StatefulWidget {
final ZekuServiceItem service;
final VoidCallback onRefresh;
const ServiceCard({
super.key,
required this.service,
required this.onRefresh,
});
@override
State<ServiceCard> createState() => _ServiceCardState();
}
class _ServiceCardState extends State<ServiceCard> {
authenticate() async {
await ZekuService.instance.authenticate();
showAdaptiveDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Authenticated?"),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("Cancel"),
),
TextButton(
child: const Text("Refresh"),
onPressed: () {
widget.onRefresh();
Navigator.pop(context);
},
),
],
);
},
);
}
disconnect(String service) async {
await ZekuService.instance.removeSession(service);
widget.onRefresh();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox(
width: 60,
height: 60,
child: CachedNetworkImage(
imageUrl: widget.service.logo,
fit: BoxFit.cover,
placeholder: (context, url) => Center(
child: CircularProgressIndicator(
strokeWidth: 2,
color: theme.colorScheme.primary,
),
),
errorWidget: (context, url, error) => Container(
color: theme.colorScheme.surfaceContainerHighest,
child: Icon(
Icons.image_not_supported,
color: theme.colorScheme.onSurfaceVariant,
size: 24,
),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.service.name,
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
widget.service.website,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
TextButton(
onPressed: () {
if (widget.service.enabled) {
disconnect(widget.service.name);
} else {
authenticate();
}
},
child: widget.service.enabled
? const Text("Disconnect")
: const Text("Authenticate"),
),
],
),
),
);
}
}

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:madari_client/features/accounts/container/trakt.container.dart';
import '../../settings/widget/setting_wrapper.dart';
@ -14,8 +13,8 @@ class ExternalAccount extends StatelessWidget {
appBar: AppBar(
title: const Text("External Accounts"),
),
body: const SettingWrapper(
child: ServicesGrid(),
body: SettingWrapper(
child: Container(),
),
);
}

View file

@ -1,114 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';
import 'package:madari_client/features/pocketbase/service/pocketbase.service.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:url_launcher/url_launcher_string.dart';
part 'zeku_service.g.dart';
@JsonSerializable()
class ZekuServiceItem {
final String name;
final String logo;
final String website;
final bool enabled;
ZekuServiceItem({
required this.name,
required this.logo,
required this.website,
required this.enabled,
});
factory ZekuServiceItem.fromJson(Map<String, dynamic> json) {
return _$ZekuServiceItemFromJson(json);
}
Map<String, dynamic> toJson() => _$ZekuServiceItemToJson(this);
}
class ZekuService {
static final ZekuService _instance = ZekuService._internal();
final pocketbase = AppPocketBaseService.instance.pb;
final String endpoint =
kDebugMode ? 'http://100.64.0.1:3001' : 'https://zeku.madari.media';
authenticate() async {
final result = await http.get(
Uri.parse(
"$endpoint/${SelectedProfileService.instance.selectedProfileId}/session",
),
headers: {
"Authorization":
"Bearer ${AppPocketBaseService.instance.pb.authStore.token}",
},
);
final res = jsonDecode(result.body);
final id = res["data"]["id"];
await launchUrlString(
"$endpoint/$id/trakt/auth",
);
}
disconnect() async {}
factory ZekuService() {
return _instance;
}
ZekuService._internal();
static ZekuService get instance => _instance;
final List<ZekuServiceItem> _services = [];
Future<List<ZekuServiceItem>> getServices() async {
try {
final result = await http.get(
Uri.parse(
"$endpoint/${SelectedProfileService.instance.selectedProfileId}/services",
),
headers: {
"Authorization":
"Bearer ${AppPocketBaseService.instance.pb.authStore.token}",
},
);
final bodyParsed = jsonDecode(result.body);
final List<ZekuServiceItem> returnValue = [];
for (final item in bodyParsed["data"]) {
returnValue.add(ZekuServiceItem.fromJson(item));
}
return returnValue;
} catch (e) {
throw Exception('Failed to fetch services: $e');
}
}
Future<bool> removeSession(String service) async {
final result = await http.get(
Uri.parse(
"$endpoint/${SelectedProfileService.instance.selectedProfileId}/${service.toLowerCase()}/revoke",
),
headers: {
"Authorization":
"Bearer ${AppPocketBaseService.instance.pb.authStore.token}",
},
);
if (result.statusCode != 200) {
return false;
}
return true;
}
}

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/streamio_addons/service/stremio_addon_service.dart';
import 'package:pocketbase/pocketbase.dart';
@ -17,6 +17,7 @@ class SignInPage extends StatefulWidget {
class _SignInPageState extends State<SignInPage>
with SingleTickerProviderStateMixin {
final _logger = Logger("SignInPage");
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@ -195,18 +196,7 @@ class _SignInPageState extends State<SignInPage>
return Scaffold(
backgroundColor: colorScheme.surface,
body: RawKeyboardListener(
focusNode: FocusNode(),
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.select) {
// Handle select button press based on current focus
if (_signInButtonFocusNode.hasFocus) {
_signIn();
}
}
}
},
body: SafeArea(
child: Stack(
children: [
Positioned(
@ -461,7 +451,12 @@ class _SignInPageState extends State<SignInPage>
if (mounted) {
context.go('/profile');
}
} on ClientException catch (e) {
} on ClientException catch (e, stack) {
_logger.warning(
"Failed to login",
e,
stack,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(

View file

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/consts/data.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_client/features/streamio_addons/service/stremio_addon_service.dart';
import 'package:pocketbase/pocketbase.dart';
@ -468,10 +467,10 @@ class _SignUpPageState extends State<SignUpPage>
await pocketbase.collection('users').create(body: userData);
await pocketbase.collection('users').authWithPassword(
_emailController.text.trim(),
_passwordController.text,
);
await AppPocketBaseService.instance.engine.authService.signInWithEmail(
email: _emailController.text.trim(),
password: _passwordController.text.trim(),
);
final profile = await pocketbase.collection('account_profile').create(
body: {
@ -481,7 +480,8 @@ class _SignUpPageState extends State<SignUpPage>
},
);
await SelectedProfileService.instance.setSelectedProfile(profile.id);
await AppPocketBaseService.instance.engine.profileService
.setCurrentProfile(profile.id);
for (final defaultAddon in defaultAppAddons) {
final manifest = await StremioAddonService.instance

View file

@ -2,7 +2,6 @@ import 'package:cached_query_flutter/cached_query_flutter.dart';
import 'package:cached_storage/cached_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:media_kit/media_kit.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:window_manager/window_manager.dart';
@ -19,7 +18,6 @@ Future startupApp() async {
await AppPocketBaseService.ensureInitialized();
await AppTheme().ensureInitialized();
await SelectedProfileService.instance.initialize();
final pb = AppPocketBaseService.instance.pb;
final userCollection = pb.collection("users");

View file

@ -3,9 +3,9 @@ import 'package:flex_color_picker/flex_color_picker.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_client/features/streamio_addons/service/stremio_addon_service.dart';
import 'package:madari_client/utils/array-extension.dart';
import 'package:madari_engine/madari_engine.dart';
import '../../widgetter/plugins/stremio/widgets/catalog_grid_full.dart';
import '../../widgetter/plugins/stremio/widgets/error_card.dart';

View file

@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_client/features/streamio_addons/service/stremio_addon_service.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/widgets/error_card.dart';
import 'package:madari_engine/madari_engine.dart';
import '../../streamio_addons/models/stremio_base_types.dart';
import '../containers/explore_addon.dart';
class ExplorePage extends StatefulWidget {

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:provider/provider.dart';
import 'package:universal_platform/universal_platform.dart';
@ -26,8 +25,6 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> {
final _state = GlobalKey<LayoutManagerState>();
late StreamSubscription<String?> _selectedProfile;
Widget _buildLogo() {
return Image.asset(
'assets/icon/icon_mini.png',
@ -38,18 +35,12 @@ class _HomePageState extends State<HomePage> {
@override
void initState() {
_selectedProfile =
SelectedProfileService.instance.selectedProfileStream.listen((data) {
_state.currentState?.refresh();
});
super.initState();
}
@override
void dispose() {
super.dispose();
_selectedProfile.cancel();
}
@override

View file

@ -1,76 +0,0 @@
import 'package:flutter/material.dart';
import '../models/navigation.model.dart';
class TVNavigation extends StatefulWidget {
final List<NavigationItem> items;
final String currentLocation;
final ValueChanged<int> onNavigate;
final bool isTV;
const TVNavigation({
super.key,
required this.items,
required this.currentLocation,
required this.onNavigate,
this.isTV = false,
});
@override
State<TVNavigation> createState() => _TVNavigationState();
}
class _TVNavigationState extends State<TVNavigation> {
bool _isExpanded = true;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: _isExpanded ? 240.0 : 72.0,
child: Drawer(
elevation: 0,
child: Column(
crossAxisAlignment: _isExpanded
? CrossAxisAlignment.start
: CrossAxisAlignment.center,
children: [
if (!widget.isTV)
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
icon: Icon(
_isExpanded ? Icons.chevron_left : Icons.chevron_right),
onPressed: () => setState(() => _isExpanded = !_isExpanded),
),
),
Expanded(
child: ListView(
children: widget.items.map((item) {
final isSelected =
widget.currentLocation.startsWith(item.path);
return ListTile(
selected: isSelected,
leading: Icon(
isSelected ? item.selectedIcon ?? item.icon : item.icon,
color: isSelected ? theme.colorScheme.primary : null,
),
title: _isExpanded ? Text(item.label) : null,
onTap: () => widget.onNavigate(0),
autofocus: widget.isTV && isSelected,
selectedTileColor:
theme.colorScheme.primaryContainer.withValues(
alpha: 0.2,
),
);
}).toList(),
),
),
],
),
),
);
}
}

View file

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_engine/madari_engine.dart';
import '../../streamio_addons/models/stremio_base_types.dart';
import '../service/list_service.dart';
import '../types/library_types.dart';
import '../../pocketbase/service/pocketbase.service.dart';
class AddToListButton extends StatefulWidget {
final Meta meta;
@ -57,7 +56,8 @@ class _AddToListButtonState extends State<AddToListButton> {
try {
setState(() => _isLoading = true);
final lists = await ListsService.instance.getLists();
final lists =
await AppPocketBaseService.instance.engine.listService.getLists();
_existingList = lists.cast<ListModel?>().firstWhere(
(list) =>
list?.name.toLowerCase() == widget.listName?.toLowerCase(),
@ -65,8 +65,8 @@ class _AddToListButtonState extends State<AddToListButton> {
);
if (_existingList != null) {
final items =
await ListsService.instance.getListItems(_existingList!.id);
final items = await AppPocketBaseService.instance.engine.listService
.getListItems(_existingList!.id);
final existingItem = items.cast<ListItemModel?>().firstWhere(
(item) => item?.imdbId == widget.meta.imdbId,
orElse: () => null,
@ -89,7 +89,8 @@ class _AddToListButtonState extends State<AddToListButton> {
Future<void> _loadLists() async {
try {
setState(() => _isLoading = true);
_lists = await ListsService.instance.getLists();
_lists =
await AppPocketBaseService.instance.engine.listService.getLists();
setState(() => _isLoading = false);
} catch (e) {
_logger.severe('Error loading lists', e);
@ -102,7 +103,8 @@ class _AddToListButtonState extends State<AddToListButton> {
try {
setState(() => _isLoading = true);
final lists = await ListsService.instance.getLists();
final lists =
await AppPocketBaseService.instance.engine.listService.getLists();
ListModel? list = lists.cast<ListModel?>().firstWhere(
(list) => list?.name.toLowerCase() == listName.toLowerCase(),
orElse: () => null,
@ -113,9 +115,11 @@ class _AddToListButtonState extends State<AddToListButton> {
name: listName,
description: '',
);
await ListsService.instance.createList(request);
await AppPocketBaseService.instance.engine.listService
.createList(request);
final updatedLists = await ListsService.instance.getLists();
final updatedLists =
await AppPocketBaseService.instance.engine.listService.getLists();
list = updatedLists.firstWhere(
(l) => l.name.toLowerCase() == listName.toLowerCase(),
);
@ -159,7 +163,8 @@ class _AddToListButtonState extends State<AddToListButton> {
0.0,
);
await ListsService.instance.addListItem(list.id, item);
await AppPocketBaseService.instance.engine.listService
.addListItem(list.id, item);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@ -192,7 +197,8 @@ class _AddToListButtonState extends State<AddToListButton> {
BuildContext context, String itemId, ListModel list) async {
try {
setState(() => _isLoading = true);
await ListsService.instance.removeListItem(list.id, itemId);
await AppPocketBaseService.instance.engine.listService
.removeListItem(list.id, itemId);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(

View file

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_engine/madari_engine.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../service/list_service.dart';
import '../service/trakt_service.dart';
import '../types/library_types.dart';
class CreateListPage extends StatefulWidget {
const CreateListPage({super.key});
@ -57,7 +56,8 @@ class _CreateListPageState extends State<CreateListPage> {
name: _nameController.text,
description: _descriptionController.text,
);
await ListsService.instance.createList(request);
await AppPocketBaseService.instance.engine.listService
.createList(request);
if (mounted) {
context.pop(true);
}

View file

@ -3,12 +3,11 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:madari_client/features/settings/widget/setting_wrapper.dart';
import 'package:madari_engine/madari_engine.dart';
import '../service/list_service.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../service/trakt_service.dart';
import '../types/library_types.dart';
class LibraryPage extends StatefulWidget {
const LibraryPage({super.key});
@ -29,11 +28,6 @@ class _LibraryPageState extends State<LibraryPage> {
void initState() {
super.initState();
_loadLists();
_item =
SelectedProfileService.instance.selectedProfileStream.listen((item) {
_loadLists();
});
}
@override
@ -46,7 +40,8 @@ class _LibraryPageState extends State<LibraryPage> {
Future<void> _loadLists() async {
try {
setState(() => _isLoading = true);
final lists = await ListsService.instance.getLists();
final lists =
await AppPocketBaseService.instance.engine.listService.getLists();
setState(() {
_lists = lists;
_isLoading = false;
@ -60,7 +55,8 @@ class _LibraryPageState extends State<LibraryPage> {
Future<void> _refreshLists() async {
try {
setState(() => _isRefreshing = true);
final lists = await ListsService.instance.getLists();
final lists =
await AppPocketBaseService.instance.engine.listService.getLists();
setState(() {
_lists = lists;
_isRefreshing = false;
@ -73,7 +69,7 @@ class _LibraryPageState extends State<LibraryPage> {
Future<void> _deleteList(String listId) async {
try {
await ListsService.instance.deleteList(listId);
await AppPocketBaseService.instance.engine.listService.deleteList(listId);
_loadLists();
} catch (e) {
_logger.severe('Error deleting list', e);

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/pocketbase/service/pocketbase.service.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/utils/size.dart';
import 'package:madari_engine/madari_engine.dart';
import '../service/list_service.dart';
import '../service/trakt_service.dart';
import '../types/library_types.dart';
class ListDetailsPage extends StatefulWidget {
final ListModel list;
@ -35,7 +35,8 @@ class _ListDetailsPageState extends State<ListDetailsPage> {
Future<void> _loadItems() async {
try {
setState(() => _isLoading = true);
final items = await ListsService.instance.getListItems(widget.list.id);
final items = await AppPocketBaseService.instance.engine.listService
.getListItems(widget.list.id);
setState(() {
_items = items;
_isLoading = false;
@ -64,7 +65,8 @@ class _ListDetailsPageState extends State<ListDetailsPage> {
Future<void> _removeItem(ListItemModel item) async {
try {
await ListsService.instance.removeListItem(widget.list.id, item.id);
await AppPocketBaseService.instance.engine.listService
.removeListItem(widget.list.id, item.id);
_loadItems();
} catch (e) {
_logger.severe('Error removing list item', e);

View file

@ -1,114 +0,0 @@
import 'package:logging/logging.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../../settings/service/selected_profile.dart';
import '../types/library_types.dart';
class ListsService {
static final ListsService instance = ListsService._internal();
final _logger = Logger('ListsService');
ListsService._internal();
Future<List<ListModel>> getLists() async {
try {
final records =
await AppPocketBaseService.instance.pb.collection('list').getFullList(
filter:
"account_profile = '${SelectedProfileService.instance.selectedProfileId}'",
);
return records
.map((record) => ListModel.fromJson(record.toJson()))
.toList();
} catch (e) {
_logger.severe('Error fetching lists', e);
rethrow;
}
}
Future<void> createList(CreateListRequest request) async {
try {
await AppPocketBaseService.instance.pb.collection('list').create(
body: request.toJson(),
);
} catch (e) {
_logger.severe('Error creating list', e);
rethrow;
}
}
Future<void> importTraktList(ListModel traktList) async {
try {
await AppPocketBaseService.instance.pb.collection('list').create(
body: traktList.toJson(),
);
} catch (e) {
_logger.severe('Error importing Trakt list', e);
rethrow;
}
}
Future<void> updateList(String id, UpdateListRequest request) async {
try {
await AppPocketBaseService.instance.pb.collection('list').update(
id,
body: request.toJson(),
);
} catch (e) {
_logger.severe('Error updating list', e);
rethrow;
}
}
Future<void> deleteList(String id) async {
try {
await AppPocketBaseService.instance.pb.collection('list').delete(id);
} catch (e) {
_logger.severe('Error deleting list', e);
rethrow;
}
}
Future<void> addListItem(String listId, ListItemModel item) async {
try {
final itemData = item.toJson();
itemData['list'] = listId;
await AppPocketBaseService.instance.pb.collection('list_item').create(
body: itemData,
);
} catch (e) {
_logger.severe('Error adding list item', e);
rethrow;
}
}
Future<List<ListItemModel>> getListItems(String listId) async {
try {
final records = await AppPocketBaseService.instance.pb
.collection('list_item')
.getFullList(
filter: 'list = "$listId"',
sort: '-created',
);
return records
.map((record) => ListItemModel.fromJson(record.toJson()))
.toList();
} catch (e) {
_logger.severe('Error fetching list items', e);
rethrow;
}
}
Future<void> removeListItem(String listId, String itemId) async {
try {
await AppPocketBaseService.instance.pb
.collection('list_item')
.delete(itemId);
} catch (e) {
_logger.severe('Error removing list item', e);
rethrow;
}
}
}

View file

@ -5,8 +5,7 @@ import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../types/library_types.dart';
import 'list_service.dart';
import '../../widgetter/plugins/stremio/models/cast_info.dart';
final _logger = Logger('TraktService');
@ -142,7 +141,7 @@ class TraktService {
final items = await getListItems(listId);
for (final item in items) {
await ListsService.instance.addListItem(
await AppPocketBaseService.instance.engine.listService.addListItem(
listId,
ListItemModel(
id: '',

View file

@ -1,148 +1 @@
import 'dart:convert';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import '../../pocketbase/service/pocketbase.service.dart';
class ListModel {
final String id;
final String name;
final String description;
final int order;
final bool sync;
final String? traktListId;
ListModel({
required this.id,
required this.name,
required this.description,
required this.order,
required this.sync,
this.traktListId,
});
factory ListModel.fromJson(Map<String, dynamic> json) {
return ListModel(
id: json['id'],
name: json['name'],
description: json['description'],
order: json['order'],
sync: json['sync'],
traktListId: json['trakt_list_id'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'order': order,
'sync': sync,
'trakt_list_id': traktListId,
};
}
ListModel copyWith({
String? name,
String? description,
int? order,
bool? sync,
String? traktListId,
}) {
return ListModel(
id: id,
name: name ?? this.name,
description: description ?? this.description,
order: order ?? this.order,
sync: sync ?? this.sync,
traktListId: traktListId ?? this.traktListId,
);
}
}
class ListItemModel {
final String id;
final String type;
final String imdbId;
final Map<String, dynamic> ids;
final String title;
final String description;
final String poster;
final double rating;
ListItemModel({
required this.id,
required this.type,
required this.imdbId,
required this.ids,
required this.title,
required this.description,
required this.poster,
required this.rating,
});
factory ListItemModel.fromJson(Map<String, dynamic> json) {
return ListItemModel(
id: json['id'],
type: json['type'],
imdbId: json['imdb_id'],
ids: json['ids'] is String ? jsonDecode(json['ids']) : json['ids'],
title: json['title'],
description: json['description'],
poster: json['poster'],
rating: (json['rating'] as num).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'type': type,
'imdb_id': imdbId,
'ids': ids is String ? ids : jsonEncode(ids),
'title': title,
'description': description,
'poster': poster,
'rating': rating,
};
}
}
class CreateListRequest {
final String name;
final String description;
CreateListRequest({
required this.name,
required this.description,
});
Map<String, dynamic> toJson() {
return {
'name': name,
'description': description,
'order': 0,
'sync': false,
'user': AppPocketBaseService.instance.pb.authStore.record!.id,
'account_profile': SelectedProfileService.instance.selectedProfileId,
};
}
}
class UpdateListRequest {
final String name;
final String description;
UpdateListRequest({
required this.name,
required this.description,
});
Map<String, dynamic> toJson() {
return {
'name': name,
'description': description,
};
}
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -8,6 +9,7 @@ class AppPocketBaseService {
static AppPocketBaseService? _instance;
late final PocketBase pb;
late final MadariEngine engine;
static AppPocketBaseService get instance {
if (_instance == null) {
@ -37,5 +39,6 @@ class AppPocketBaseService {
kDebugMode ? 'http://100.64.0.1:8090' : 'https://api-v2.madari.media',
authStore: authStore,
);
engine = MadariEngine(pb: pb);
}
}

View file

@ -3,9 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:madari_client/features/pocketbase/service/pocketbase.service.dart';
import 'package:pocketbase/pocketbase.dart';
import '../service/selected_profile.dart';
import 'package:madari_engine/madari_engine.dart';
class FullProfileSelectorPage extends StatefulWidget {
const FullProfileSelectorPage({super.key});
@ -16,34 +14,26 @@ class FullProfileSelectorPage extends StatefulWidget {
}
class _FullProfileSelectorPageState extends State<FullProfileSelectorPage> {
final _selectedProfileService = SelectedProfileService.instance;
late Future<ResultList<RecordModel>> _future;
late StreamSubscription<String?> _listener;
late Future<List<UserProfile>> _future;
final profileService = AppPocketBaseService.instance.engine.profileService;
String? selectedProfileId;
@override
void initState() {
super.initState();
_future = AppPocketBaseService.instance.pb
.collection('account_profile')
.getList();
_future =
AppPocketBaseService.instance.engine.profileService.getAllProfiles();
_listener = _selectedProfileService.selectedProfileStream.listen((item) {
if (mounted) {
setState(() {
_future = AppPocketBaseService.instance.pb
.collection('account_profile')
.getList();
});
}
AppPocketBaseService.instance.engine.profileService
.getCurrentProfile()
.then((item) {
if (item != null) selectedProfileId = item.id;
});
}
@override
void dispose() {
_listener.cancel();
super.dispose();
}
@ -128,16 +118,11 @@ class _FullProfileSelectorPageState extends State<FullProfileSelectorPage> {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator());
child: CircularProgressIndicator(),
);
}
final profiles = snapshot.data!.items;
if (_selectedProfileService.selectedProfileId == null &&
profiles.isNotEmpty) {
_selectedProfileService
.setSelectedProfile(profiles[0].id);
}
final profiles = snapshot.data;
return GridView.builder(
padding: EdgeInsets.all(padding),
@ -148,147 +133,118 @@ class _FullProfileSelectorPageState extends State<FullProfileSelectorPage> {
crossAxisSpacing: spacing,
mainAxisExtent: isDesktop ? 200 : 160,
),
itemCount: profiles.length,
itemCount: profiles!.length,
itemBuilder: (context, index) {
final profile = profiles[index];
return StreamBuilder<String?>(
stream:
_selectedProfileService.selectedProfileStream,
builder: (context, snapshot) {
final isSelected = snapshot.data == profile.id;
final isSelected = snapshot.data == profile.id;
return InkWell(
onTap: () async {
final currentSelectedId =
_selectedProfileService
.selectedProfileId;
final newSelectedId =
currentSelectedId == profile.id
? profile.id
: profile.id;
await _selectedProfileService
.setSelectedProfile(newSelectedId);
return InkWell(
onTap: () async {
profileService.setCurrentProfile(profile.id);
if (context.mounted) context.push("/");
},
if (context.mounted) context.push("/");
},
borderRadius: BorderRadius.circular(12),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: isSelected
? colorScheme.primaryContainer
.withOpacity(0.3)
: Colors.transparent,
border: Border.all(
color: isSelected
? colorScheme.primary
: colorScheme.outlineVariant,
width: isSelected ? 2 : 1,
),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
color: isSelected
? colorScheme.primaryContainer
.withOpacity(0.3)
: Colors.transparent,
border: Border.all(
color: isSelected
? colorScheme.primary
: colorScheme.outlineVariant,
width: isSelected ? 2 : 1,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
if (profile.data['profile_image'] !=
null &&
profile.data['profile_image'] !=
"")
CircleAvatar(
radius: avatarSize,
backgroundImage: NetworkImage(
AppPocketBaseService
.instance.pb.files
.getUrl(
profile,
profile.data[
'profile_image'],
)
.toString(),
),
)
else
CircleAvatar(
radius: avatarSize,
backgroundColor: isSelected
? colorScheme.primary
: colorScheme
.surfaceVariant,
child: Text(
profile.data['name'][0]
.toUpperCase(),
style: TextStyle(
color: isSelected
? colorScheme.onPrimary
: colorScheme
.onSurfaceVariant,
fontSize: avatarSize * 0.75,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
),
if (isSelected)
Positioned(
right: 0,
bottom: 0,
child: Container(
padding: EdgeInsets.all(
isDesktop ? 6 : 4),
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
border: Border.all(
color:
colorScheme.surface,
width: 2,
),
),
child: Icon(
Icons.check,
color:
colorScheme.onPrimary,
size: isDesktop ? 24 : 20,
),
),
),
],
),
const SizedBox(height: 16),
Padding(
padding: EdgeInsets.symmetric(
horizontal: isDesktop ? 16 : 8,
),
child: Text(
profile.data['name'],
style: (isDesktop
? theme.textTheme.titleLarge
: theme
.textTheme.titleMedium)
?.copyWith(
color: isSelected
? colorScheme.primary
: null,
fontWeight: isSelected
? FontWeight.bold
: null,
if (profile.profileImage != null)
CircleAvatar(
radius: avatarSize,
backgroundImage: NetworkImage(
profile.profileImage!,
),
)
else
CircleAvatar(
radius: avatarSize,
backgroundColor: isSelected
? colorScheme.primary
: colorScheme
.surfaceContainerHighest,
child: Text(
profile.name[0].toUpperCase(),
style: TextStyle(
color: isSelected
? colorScheme.onPrimary
: colorScheme
.onSurfaceVariant,
fontSize: avatarSize * 0.75,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
),
if (isSelected)
Positioned(
right: 0,
bottom: 0,
child: Container(
padding: EdgeInsets.all(
isDesktop ? 6 : 4),
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
border: Border.all(
color: colorScheme.surface,
width: 2,
),
),
child: Icon(
Icons.check,
color: colorScheme.onPrimary,
size: isDesktop ? 24 : 20,
),
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
},
const SizedBox(height: 16),
Padding(
padding: EdgeInsets.symmetric(
horizontal: isDesktop ? 16 : 8,
),
child: Text(
profile.name,
style: (isDesktop
? theme.textTheme.titleLarge
: theme.textTheme.titleMedium)
?.copyWith(
color: isSelected
? colorScheme.primary
: null,
fontWeight: isSelected
? FontWeight.bold
: null,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
},
);

View file

@ -2,8 +2,8 @@ import 'package:cached_query/cached_query.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/home/pages/home_page.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/utils/size.dart';
import 'package:pocketbase/pocketbase.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../../widgetter/plugin_base.dart';
@ -31,25 +31,35 @@ class _LayoutPageState extends State<LayoutPage> with TickerProviderStateMixin {
final double _minCellWidth = 150;
final ScrollController _scrollController = ScrollController();
bool _isLoading = false;
final appProfile = AppPocketBaseService.instance.engine.profileService;
final query = Query(
key: "home_layout",
queryFn: () async {
return await AppPocketBaseService.instance.pb
.collection('home_layout')
.getFullList(
sort: 'order',
filter:
'profiles = \'${SelectedProfileService.instance.selectedProfileId}\'',
);
},
);
Query<List<RecordModel>>? query;
@override
void initState() {
super.initState();
_logger.info('Initializing LayoutPage');
(() async {
final query = await AppPocketBaseService.instance.engine.profileService
.getCurrentProfile();
this.query = Query(
key: "home_layout${query!.id}",
queryFn: () async {
return await AppPocketBaseService.instance.pb
.collection('home_layout')
.getFullList(
sort: 'order',
filter:
'profiles = \'${(await appProfile.getCurrentProfile())!.id}\'',
);
},
);
setState(() {});
})();
loadData();
}
@ -135,7 +145,7 @@ class _LayoutPageState extends State<LayoutPage> with TickerProviderStateMixin {
newWidget,
);
query.refetch();
query?.refetch();
if (success) {
await loadData();
@ -159,7 +169,7 @@ class _LayoutPageState extends State<LayoutPage> with TickerProviderStateMixin {
layoutWidgets.remove(widget);
});
await HomeLayoutService.instance.updateLayoutOrder(layoutWidgets);
query.refetch();
query?.refetch();
_showSuccess('Removed widget successfully');
} else {
_showError('Failed to remove widget. Please try again.');
@ -195,7 +205,7 @@ class _LayoutPageState extends State<LayoutPage> with TickerProviderStateMixin {
final success =
await HomeLayoutService.instance.saveLayoutWidget(newWidget);
query.refetch();
query?.refetch();
if (success) {
successCount++;
}
@ -229,7 +239,7 @@ class _LayoutPageState extends State<LayoutPage> with TickerProviderStateMixin {
final success =
await HomeLayoutService.instance.updateLayoutOrder(layoutWidgets);
query.refetch();
query?.refetch();
if (!success) {
_showError('Failed to save new order. Please try again.');
}

View file

@ -5,7 +5,6 @@ import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart';
import 'package:image_picker/image_picker.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../widget/language_selector.dart';
@ -108,10 +107,6 @@ class _ProfilePageState extends State<ProfilePage> {
.update(user.id, body: data);
}
SelectedProfileService.instance.setSelectedProfile(
SelectedProfileService.instance.selectedProfileId,
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Profile updated successfully')),
@ -169,8 +164,10 @@ class _ProfilePageState extends State<ProfilePage> {
),
),
filled: true,
fillColor:
Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
fillColor: Theme.of(context)
.colorScheme
.surfaceContainerHighest
.withOpacity(0.3),
hoverColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.08),
focusColor: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
),

View file

@ -2,12 +2,9 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:madari_client/features/pocketbase/service/pocketbase.service.dart';
import 'package:pocketbase/src/dtos/record_model.dart';
import 'package:pocketbase/src/dtos/result_list.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:shimmer/shimmer.dart';
import '../../service/selected_profile.dart';
class ProfileSelector extends StatefulWidget {
const ProfileSelector({super.key});
@ -16,37 +13,33 @@ class ProfileSelector extends StatefulWidget {
}
class _ProfileSelectorState extends State<ProfileSelector> {
final _selectedProfileService = SelectedProfileService.instance;
final profileService = AppPocketBaseService.instance.engine.profileService;
late Future<List<UserProfile>> _future;
late String selectedProfileId;
late Future<ResultList<RecordModel>> _future;
late StreamSubscription<String?> _listener;
late StreamSubscription<bool> _listenerNew;
@override
void initState() {
super.initState();
_future = AppPocketBaseService.instance.pb
.collection('account_profile')
.getList();
_future = profileService.getAllProfiles();
_listener = _selectedProfileService.selectedProfileStream.listen(
(item) {
if (mounted) {
setState(() {
_future = AppPocketBaseService.instance.pb
.collection('account_profile')
.getList();
});
}
},
);
_listenerNew = profileService.onProfileUpdate.listen((item) {
setState(() {
_future = profileService.getAllProfiles();
});
});
profileService.getCurrentProfile().then((item) {
selectedProfileId = item!.id;
});
}
@override
void dispose() {
super.dispose();
_listener.cancel();
_listenerNew.cancel();
}
Widget _buildShimmerLoading() {
@ -115,7 +108,7 @@ class _ProfileSelectorState extends State<ProfileSelector> {
return _buildShimmerLoading();
}
final profiles = snapshot.data!.items;
final profiles = snapshot.data!;
return GridView.builder(
shrinkWrap: true,
@ -131,89 +124,76 @@ class _ProfileSelectorState extends State<ProfileSelector> {
itemBuilder: (context, index) {
final profile = profiles[index];
return StreamBuilder<String?>(
stream: _selectedProfileService.selectedProfileStream,
builder: (context, snapshot) {
final isSelected = snapshot.data == profile.id;
final isSelected = selectedProfileId == profile.id;
return InkWell(
onTap: () async {
final currentSelectedId =
_selectedProfileService.selectedProfileId;
final newSelectedId = currentSelectedId == profile.id
? profile.id
: profile.id;
await _selectedProfileService
.setSelectedProfile(newSelectedId);
},
return InkWell(
onTap: () async {
profileService.setCurrentProfile(profile.id);
setState(() {
selectedProfileId = profile.id;
});
},
borderRadius: BorderRadius.circular(8),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: isSelected
? colorScheme.primaryContainer.withOpacity(0.3)
: Colors.transparent,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
color: isSelected
? colorScheme.primaryContainer.withOpacity(0.3)
: Colors.transparent,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
if (profile.data['profile_image'] != null &&
profile.data['profile_image'] != "")
CircleAvatar(
radius: 24,
backgroundImage: NetworkImage(
AppPocketBaseService.instance.pb.files
.getUrl(
profile,
profile.data['profile_image'],
)
.toString(),
),
)
else
CircleAvatar(
radius: 24,
backgroundColor: isSelected
? colorScheme.primary
: colorScheme.surfaceVariant,
child: Text(
profile.data['name'][0].toUpperCase(),
style: TextStyle(
color: isSelected
? colorScheme.onPrimary
: colorScheme.onSurfaceVariant,
fontSize: 18,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
),
],
),
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
profile.data['name'],
style: theme.textTheme.bodySmall?.copyWith(
color: isSelected ? colorScheme.primary : null,
fontWeight: isSelected ? FontWeight.bold : null,
if (profile.profileImage != null)
CircleAvatar(
radius: 24,
backgroundImage: NetworkImage(
profile.profileImage!,
),
)
else
CircleAvatar(
radius: 24,
backgroundColor: isSelected
? colorScheme.primary
: colorScheme.surfaceContainerHighest,
child: Text(
profile.name[0].toUpperCase(),
style: TextStyle(
color: isSelected
? colorScheme.onPrimary
: colorScheme.onSurfaceVariant,
fontSize: 18,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
},
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
profile.name,
style: theme.textTheme.bodySmall?.copyWith(
color: isSelected ? colorScheme.primary : null,
fontWeight: isSelected ? FontWeight.bold : null,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
},
);

View file

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:madari_client/features/pocketbase/service/pocketbase.service.dart';
import 'package:madari_client/features/settings/pages/settings/profile_selector.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({
@ -48,7 +47,6 @@ class SettingsPage extends StatelessWidget {
deleteStorage: true,
);
AppPocketBaseService.instance.pb.authStore.clear();
SelectedProfileService.instance.setSelectedProfile(null);
context.go("/signin");
},
),

View file

@ -7,7 +7,6 @@ import 'package:pocketbase/pocketbase.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../service/account_profile_service.dart';
import '../service/selected_profile.dart';
import '../widget/profile_dialog.dart';
class SubprofilesPage extends StatefulWidget {
@ -20,29 +19,32 @@ class SubprofilesPage extends StatefulWidget {
class _SubprofilesPageState extends State<SubprofilesPage> {
final _logger = Logger('SubprofilesPage');
final _profileService = AccountProfileService.instance;
final _selectedProfileService = SelectedProfileService.instance;
final _selectedProfileService =
AppPocketBaseService.instance.engine.profileService;
List<RecordModel> _profiles = [];
bool _isLoading = true;
String? _error;
Timer? _retryTimer;
int _retryAttempts = 0;
static const int _maxRetryAttempts = 3;
late final StreamSubscription<String?> _selectedProfileSubscription;
String? selectedProfile;
late StreamSubscription<bool> _listener;
@override
void initState() {
super.initState();
_selectedProfileSubscription = _selectedProfileService.selectedProfileStream
.listen((_) => setState(() {}));
_loadProfiles();
_listener = _selectedProfileService.onProfileUpdate.listen((item) async {
selectedProfile = (await _selectedProfileService.getCurrentProfile())!.id;
});
}
@override
void dispose() {
_selectedProfileSubscription.cancel();
_retryTimer?.cancel();
super.dispose();
_listener.cancel();
}
Future<void> _loadProfiles({bool isRetry = false}) async {
@ -56,10 +58,6 @@ class _SubprofilesPageState extends State<SubprofilesPage> {
final profiles = await _profileService.getProfiles();
_selectedProfileService.setSelectedProfile(
_selectedProfileService.selectedProfileId,
);
if (!mounted) return;
setState(() {
@ -113,10 +111,11 @@ class _SubprofilesPageState extends State<SubprofilesPage> {
Future<void> _handleProfileSelection(RecordModel profile) async {
try {
final currentSelectedId = _selectedProfileService.selectedProfileId;
final currentSelectedId =
(await _selectedProfileService.getCurrentProfile())!.id;
final newSelectedId =
currentSelectedId == profile.id ? profile.id : profile.id;
await _selectedProfileService.setSelectedProfile(newSelectedId);
await _selectedProfileService.setCurrentProfile(newSelectedId);
if (!mounted) return;
} catch (e, stackTrace) {
@ -200,7 +199,6 @@ class _SubprofilesPageState extends State<SubprofilesPage> {
Widget _buildProfileCard(RecordModel profile) {
return ProfileCard(
profile: profile,
selectedProfileId: _selectedProfileService.selectedProfileId,
onTap: () => _handleProfileSelection(profile),
onEdit: () => _showProfileDialog(context, profile: profile),
onDelete: () => _showDeleteDialog(profile),

View file

@ -10,6 +10,7 @@ class AccountProfileService {
static final AccountProfileService instance =
AccountProfileService._internal();
final _logger = Logger('AccountProfileService');
final profileService = AppPocketBaseService.instance.engine.profileService;
AccountProfileService._internal();

View file

@ -1,51 +0,0 @@
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SelectedProfileService {
static final SelectedProfileService instance =
SelectedProfileService._internal();
final _logger = Logger('SelectedProfileService');
static const String _selectedProfileKey = 'selected_profile_id';
final _selectedProfileSubject = BehaviorSubject<String?>();
SharedPreferences? _prefs;
SelectedProfileService._internal();
Future<void> initialize() async {
try {
_prefs = await SharedPreferences.getInstance();
final storedId = _prefs?.getString(_selectedProfileKey);
_selectedProfileSubject.add(storedId);
_logger.info('Initialized with stored profile ID: $storedId');
} catch (e, stack) {
_logger.severe('Error initializing SelectedProfileService', e, stack);
rethrow;
}
}
String? get selectedProfileId => _selectedProfileSubject.valueOrNull;
Stream<String?> get selectedProfileStream => _selectedProfileSubject.stream;
Future<void> setSelectedProfile(String? profileId) async {
try {
if (profileId != null) {
await _prefs?.setString(_selectedProfileKey, profileId);
} else {
await _prefs?.remove(_selectedProfileKey);
}
_selectedProfileSubject.add(profileId);
_logger.info('Selected profile updated: $profileId');
} catch (e, stack) {
_logger.severe('Error setting selected profile', e, stack);
rethrow;
}
}
void dispose() {
_selectedProfileSubject.close();
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:madari_engine/madari_engine.dart';
import '../../settings/widget/setting_wrapper.dart';
import '../models/stremio_base_types.dart';
import '../service/stremio_addon_service.dart';
import '../widget/add_addon_sheet.dart';
import '../widget/stremio_addons_list.dart';

View file

@ -6,11 +6,11 @@ import 'package:logging/logging.dart';
import 'package:madari_client/data/db.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_client/utils/array-extension.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:pocketbase/pocketbase.dart';
import '../../pocketbase/service/pocketbase.service.dart';
import '../../widgetter/plugins/stremio/models/cast_info.dart';
import '../models/stremio_base_types.dart';
typedef OnStreamCallback = void Function(
List<VideoStream>? items,
@ -22,6 +22,7 @@ class StremioAddonService {
final _logger = Logger('StremioAddonService');
static final StremioAddonService instance = StremioAddonService._internal();
final _manifestQueryConfig = QueryConfig(
cacheDuration: const Duration(hours: 8),
);
@ -586,13 +587,3 @@ enum ConnectionFilterType {
text,
options,
}
class ConnectionFilterItem {
final String title;
final dynamic value;
ConnectionFilterItem({
required this.title,
required this.value,
});
}

View file

@ -1,9 +1,9 @@
import 'package:cached_query_flutter/cached_query_flutter.dart';
import 'package:flutter/material.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:pocketbase/pocketbase.dart';
import '../models/stremio_base_types.dart';
import '../service/stremio_addon_service.dart';
class AddAddonSheet extends StatefulWidget {

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import '../models/stremio_base_types.dart';
import 'package:madari_engine/madari_engine.dart';
class ManifestPreview extends StatelessWidget {
final StremioManifest manifest;

View file

@ -1,8 +1,8 @@
import 'package:cached_query_flutter/cached_query_flutter.dart';
import 'package:flutter/material.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:shimmer/shimmer.dart';
import '../models/stremio_base_types.dart';
import '../pages/stremio_addons_page.dart';
import 'add_addon_sheet.dart';

View file

@ -28,13 +28,6 @@ class _AlwaysOnTopButtonState extends State<AlwaysOnTopButton> {
@override
void dispose() {
super.dispose();
windowManager.setAlwaysOnTop(false);
windowManager.setTitleBarStyle(TitleBarStyle.normal);
setState(() {
alwaysOnTop = false;
});
windowManager.setVisibleOnAllWorkspaces(false);
}
@override

View file

@ -4,12 +4,12 @@ import 'package:madari_client/features/video_player/container/options/settings_s
import 'package:madari_client/features/video_player/container/state/video_settings.dart';
import 'package:madari_client/features/video_player/container/video_mobile.dart';
import 'package:madari_client/features/video_player/container/video_play.dart';
import 'package:madari_engine/madari_engine.dart' as types;
import 'package:media_kit_video/media_kit_video.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:universal_platform/universal_platform.dart';
import '../../streamio_addons/models/stremio_base_types.dart' as types;
import '../widgets/video_selector.dart';
import 'options/always_on_top.dart';
import 'options/audio_track_selector.dart';

View file

@ -5,11 +5,11 @@ import 'package:go_router/go_router.dart';
import 'package:madari_client/features/video_player/container/options/settings_sheet.dart';
import 'package:madari_client/features/video_player/container/state/video_settings.dart';
import 'package:madari_client/features/video_player/container/video_play.dart';
import 'package:madari_engine/madari_engine.dart' as types;
import 'package:media_kit_video/media_kit_video.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/src/subjects/behavior_subject.dart';
import '../../streamio_addons/models/stremio_base_types.dart' as types;
import '../widgets/video_selector.dart';
import 'options/audio_track_selector.dart';
import 'options/scale_option.dart';

View file

@ -6,13 +6,14 @@ import 'package:madari_client/features/video_player/container/native.dart';
import 'package:madari_client/features/video_player/container/state/video_settings.dart';
import 'package:madari_client/features/video_player/container/video_desktop.dart';
import 'package:madari_client/features/video_player/container/video_mobile.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/src/subjects/behavior_subject.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:window_manager/window_manager.dart';
import '../../streamio_addons/models/stremio_base_types.dart';
import '../service/video_eventer_default_track.dart';
typedef OnVideoChangeCallback = Future<bool> Function(
@ -130,6 +131,12 @@ class _VideoPlayState extends State<VideoPlay> {
_settings.removeListener(_onSettingsChanged);
setter.dispose();
player.dispose();
if (UniversalPlatform.isDesktop) {
windowManager.setAlwaysOnTop(false);
windowManager.setTitleBarStyle(TitleBarStyle.normal);
windowManager.setVisibleOnAllWorkspaces(false);
}
}
late int selectedVideo = widget.index;

View file

@ -4,9 +4,9 @@ import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/settings/model/playback_settings_model.dart';
import 'package:madari_client/features/settings/service/playback_setting_service.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_client/features/video_player/container/video_play.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/containers/streamio_background.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:rxdart/rxdart.dart';
class VideoPlayer extends StatefulWidget {

View file

@ -2,11 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:madari_client/features/video_player/container/video_play.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:rxdart/src/subjects/behavior_subject.dart';
import '../../streamio_addons/models/stremio_base_types.dart';
class SeasonSource extends StatefulWidget {
final Meta meta;
final bool isMobile;

View file

@ -1,8 +1,9 @@
import 'dart:async';
import 'package:cached_query/cached_query.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_client/features/widgetter/plugin_base.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/widgets/catalog_featured_shimmer.dart';
@ -32,16 +33,24 @@ class LayoutManagerState extends State<LayoutManager> {
List<HomeLayoutModel> _layouts = [];
List<HomeLayoutModel> _filteredLayouts = [];
bool _isLoading = true;
final profileService = AppPocketBaseService.instance.engine.profileService;
late StreamSubscription<bool> _listener;
@override
void initState() {
super.initState();
_loadLayouts();
_listener = profileService.onProfileUpdate.listen((_) {
refresh();
});
}
@override
void dispose() {
_scrollController.dispose();
_listener.cancel();
super.dispose();
}
@ -65,9 +74,10 @@ class LayoutManagerState extends State<LayoutManager> {
}) async {
try {
_logger.info('Loading layouts');
print((await profileService.getCurrentProfile())!.id);
final query = Query(
key:
"home_layout_${SelectedProfileService.instance.selectedProfileId}${widget.hasSearch}",
"home_layout_${(await profileService.getCurrentProfile())!.id}${widget.hasSearch}",
config: QueryConfig(
ignoreCacheDuration: refresh,
cacheDuration: const Duration(hours: 8),
@ -79,7 +89,7 @@ class LayoutManagerState extends State<LayoutManager> {
.getFullList(
sort: 'order',
filter:
"profiles = '${SelectedProfileService.instance.selectedProfileId}'",
"profiles = '${(await profileService.getCurrentProfile())!.id}'",
);
},
);

View file

@ -4,8 +4,8 @@ import 'package:logging/logging.dart';
import 'package:madari_client/features/external_player/service/external_player.dart';
import 'package:madari_client/features/settings/service/playback_setting_service.dart';
import 'package:madari_client/features/streamio_addons/extension/query_extension.dart';
import 'package:madari_engine/madari_engine.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
import '../../../../streamio_addons/service/stremio_addon_service.dart';
final _logger = Logger('StreamioStreamList');

View file

@ -1,8 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_client/utils/array-extension.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:universal_platform/universal_platform.dart';
import '../../../../library/container/add_to_list_button.dart';

View file

@ -1,8 +1,8 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:universal_platform/universal_platform.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
import 'cast_info.dart';
class StreamioCastSection extends StatelessWidget {

View file

@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_engine/madari_engine.dart';
final _logger = Logger('StreamioEpisodeList');

View file

@ -1,11 +1,10 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
final _logger = Logger('StreamioTrailerSection');
class StreamioTrailerSection extends StatelessWidget {

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_engine/madari_engine.dart';
class StremioVideoList extends StatelessWidget {
final List<Video>? videos;

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/containers/streamio_background.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/containers/streamio_cast_section.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/containers/streamio_trailer_section.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/containers/streamio_video_list.dart';
import 'package:madari_engine/madari_engine.dart';
final _logger = Logger('StreamioViewerContent');

View file

@ -1,94 +1 @@
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
enum ContentType { movie, series, channel, tv }
class SocialLinks {
final String? instagram;
final String? twitter;
final String? facebook;
final String? website;
const SocialLinks({
this.instagram,
this.twitter,
this.facebook,
this.website,
});
factory SocialLinks.fromJson(Map<String, dynamic> json) {
return SocialLinks(
instagram: json['instagram'] as String?,
twitter: json['twitter'] as String?,
facebook: json['facebook'] as String?,
website: json['website'] as String?,
);
}
Map<String, dynamic> toJson() => {
'instagram': instagram,
'twitter': twitter,
'facebook': facebook,
'website': website,
};
}
class CastMember {
final String id;
final String name;
final String? profilePath;
final String? biography;
final String? birthDate;
final String? birthPlace;
final SocialLinks socialLinks;
final List<Meta> knownFor;
final double? popularity;
final String? department;
final String? character;
const CastMember({
required this.id,
required this.name,
this.profilePath,
this.biography,
this.birthDate,
this.birthPlace,
this.socialLinks = const SocialLinks(),
this.knownFor = const [],
this.popularity,
this.department,
this.character,
});
factory CastMember.fromJson(Map<String, dynamic> json) {
return CastMember(
id: json['id'] as String,
name: json['name'] as String,
profilePath: json['profilePath'] as String?,
biography: json['biography'] as String?,
birthDate: json['birthDate'] as String?,
birthPlace: json['birthPlace'] as String?,
socialLinks: SocialLinks.fromJson(json['socialLinks'] ?? {}),
knownFor: (json['knownFor'] as List<dynamic>?)
?.map((e) => Meta.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
popularity: json['popularity'] as double?,
department: json['department'] as String?,
character: json['character'] as String?,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'profile_path': profilePath,
'biography': biography,
'birth_date': birthDate,
'birth_place': birthPlace,
'social_links': socialLinks.toJson(),
'known_for': knownFor.map((e) => e.toJson()).toList(),
'popularity': popularity,
'department': department,
'character': character,
};
}
export 'package:madari_engine/madari_engine.dart';

View file

@ -1,8 +1,8 @@
import 'package:cached_query_flutter/cached_query_flutter.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/streamio_addons/models/stremio_base_types.dart';
import 'package:madari_client/features/streamio_addons/service/stremio_addon_service.dart';
import 'package:madari_engine/madari_engine.dart';
import '../containers/shimmer.dart';
import '../containers/streamio_viewer_content.dart';

View file

@ -2,10 +2,9 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:universal_platform/universal_platform.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
class CatalogFeatured extends StatefulWidget {
final List<Meta> meta;
final VoidCallback? onTap;

View file

@ -11,10 +11,9 @@ import 'package:madari_client/features/widgetter/plugin_base.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/widgets/catalog_featured.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/widgets/stremio_card.dart';
import 'package:madari_client/utils/array-extension.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
import '../../../../streamio_addons/service/stremio_addon_service.dart';
import '../../../interface/widgets.dart';
import '../../../service/home_layout_service.dart';
@ -137,31 +136,9 @@ class _CatalogGridState extends State<CatalogGrid> implements Refreshable {
Widget _buildShimmerCard(BuildContext context, StremioCardSize cardSize) {
final theme = Theme.of(context);
return Container(
margin: const EdgeInsets.only(right: 12),
child: Shimmer.fromColors(
baseColor: theme.brightness == Brightness.light
? Colors.grey[300]!
: Colors.grey[800]!,
highlightColor: theme.brightness == Brightness.light
? Colors.grey[100]!
: Colors.grey[700]!,
child: Container(
width: cardSize.width,
height: cardSize.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.red,
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
),
),
return PulseLoadingCard(
width: cardSize.width,
height: cardSize.height,
);
}
@ -508,3 +485,78 @@ class _CatalogGridState extends State<CatalogGrid> implements Refreshable {
return _query.refetch();
}
}
class PulseLoadingCard extends StatefulWidget {
final double width;
final double height;
const PulseLoadingCard({
super.key,
required this.width,
required this.height,
});
@override
State<PulseLoadingCard> createState() => _PulseLoadingCardState();
}
class _PulseLoadingCardState extends State<PulseLoadingCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat(reverse: true);
_animation = Tween<double>(
begin: 0.5,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final baseColor = theme.brightness == Brightness.light
? Colors.grey[300]!
: Colors.grey[800]!;
return Container(
margin: const EdgeInsets.only(right: 12),
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: widget.width,
height: widget.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: baseColor.withOpacity(_animation.value),
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
);
},
),
);
}
}

View file

@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/widgetter/plugins/stremio/widgets/stremio_card.dart';
import 'package:madari_engine/madari_engine.dart';
import 'package:shimmer/shimmer.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
import '../utils/size.dart';
typedef GetQuery = InfiniteQuery<List<Meta>, int> Function();

View file

@ -2,8 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../../streamio_addons/models/stremio_base_types.dart';
import 'package:madari_engine/madari_engine.dart';
typedef ImageCallback = Function(String? image);

View file

@ -1,6 +1,5 @@
import 'package:cached_query_flutter/cached_query_flutter.dart';
import 'package:logging/logging.dart';
import 'package:madari_client/features/settings/service/selected_profile.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:rxdart/subjects.dart';
@ -12,6 +11,7 @@ final _logger = Logger('HomeLayoutService');
class HomeLayoutService {
static final HomeLayoutService instance = HomeLayoutService._internal();
HomeLayoutService._internal();
final profileService = AppPocketBaseService.instance.engine.profileService;
final BehaviorSubject refreshWidgets = BehaviorSubject();
@ -23,7 +23,7 @@ class HomeLayoutService {
.getFullList(
sort: 'order',
filter:
'profiles = \'${SelectedProfileService.instance.selectedProfileId}\'',
'profiles = \'${(await profileService.getCurrentProfile())!.id}\'',
);
return records
@ -36,10 +36,6 @@ class HomeLayoutService {
}
void clearCache() {
SelectedProfileService.instance.setSelectedProfile(
SelectedProfileService.instance.selectedProfileId,
);
CachedQuery.instance.invalidateCache(filterFn: (item, key) {
return key.startsWith("home_layout");
});
@ -49,7 +45,7 @@ class HomeLayoutService {
try {
_logger.info('Saving layout widget: ${widget.pluginId}');
await AppPocketBaseService.instance.pb.collection('home_layout').create(
body: widget.toJson(),
body: await widget.toJson(),
);
clearCache();
return true;
@ -148,14 +144,16 @@ class LayoutWidgetConfig {
);
}
Map<String, dynamic> toJson() {
Future<Map<String, dynamic>> toJson() async {
return {
'title': title,
'config': config,
'order': order,
'plugin_id': pluginId,
'type': widgetType,
'profiles': SelectedProfileService.instance.selectedProfileId,
'profiles': (await AppPocketBaseService.instance.engine.profileService
.getCurrentProfile())!
.id,
'user': AppPocketBaseService.instance.pb.authStore.record!.id,
};
}

View file

@ -1,7 +0,0 @@
class Integration {
final String id;
Integration({
required this.id,
});
}

View file

@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
class IntegrationPage extends StatelessWidget {
const IntegrationPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Integrations"),
),
body: ListView(
children: [
TextButton(
onPressed: () {},
child: const Text("Choose account"),
),
],
),
);
}
}

View file

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rinf/rinf.dart';
import './messages/all.dart';
import 'app/app.dart';
import 'features/common/utils/startup_app.dart';
import 'features/logger/service/logger.service.dart';
@ -8,6 +10,7 @@ import 'features/theme/theme/app_theme.dart';
import 'features/widgetter/state/widget_state_provider.dart';
void main() async {
await initializeRust(assignRustSignal);
WidgetsFlutterBinding.ensureInitialized();
setupLogger();

View file

@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST
media_kit_native_event_loop
rinf
)
set(PLUGIN_BUNDLED_LIBRARIES)

View file

@ -23,6 +23,8 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- rinf (0.1.0):
- FlutterMacOS
- screen_retriever_macos (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
@ -68,6 +70,7 @@ DEPENDENCIES:
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- rinf (from `Flutter/ephemeral/.symlinks/plugins/rinf/macos`)
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
@ -102,6 +105,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
rinf:
:path: Flutter/ephemeral/.symlinks/plugins/rinf/macos
screen_retriever_macos:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
shared_preferences_foundation:
@ -129,6 +134,7 @@ SPEC CHECKSUMS:
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
rinf: 4ecc3f564099eabf18209471f569e1a5b8c7b6b3
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d

19
messages/torrent.proto Normal file
View file

@ -0,0 +1,19 @@
syntax = "proto3";
package torrent;
// [RUST-SIGNAL]
message ServerEngine {
ServerStatus status = 1;
int32 port = 2;
}
// [DART-SIGNAL]
message ServerAction {
ServerStatus status = 1;
string path = 2;
}
enum ServerStatus {
Active = 0;
Stop = 1;
}

18
native/hub/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "hub"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
rinf = "7.1.1"
prost = "0.13.0"
tokio = { version = "1", features = ["rt", "macros"] }
log = "0.4.25"
messages = "0.3.1"
# Uncomment below to target the web.
# tokio_with_wasm = { version = "0.7.2", features = ["rt", "macros"] }
# wasm-bindgen = "0.2.95"

18
native/hub/src/lib.rs Normal file
View file

@ -0,0 +1,18 @@
use log::{info, warn};
use rinf::debug_print;
mod messages;
mod torrent_server;
// use tokio_with_wasm::alias as tokio; // web
rinf::write_interface!();
#[tokio::main(flavor = "current_thread")]
async fn main() {
tokio::spawn(torrent_server::communicate());
debug_print!("Hello world");
rinf::dart_shutdown().await;
}

View file

@ -0,0 +1,112 @@
use crate::messages::*;
use messages::prelude::*;
use rinf::debug_print;
struct StartServer;
struct StopServer;
struct TorrentServer {
state: ServerStatus,
}
impl Actor for TorrentServer {}
#[async_trait]
impl Handler<StartServer> for TorrentServer {
type Result = Result<(), String>;
async fn handle(&mut self, _: StartServer, ctx: &Context<Self>) -> Self::Result {
self.handle_start().await
}
}
#[async_trait]
impl Handler<StopServer> for TorrentServer {
type Result = Result<(), String>;
async fn handle(&mut self, _: StopServer, ctx: &Context<Self>) -> Self::Result {
self.handle_stop().await
}
}
impl TorrentServer {
pub fn new() -> Self {
Self {
state: ServerStatus::Stop,
}
}
pub fn start() -> Address<Self> {
let context = Context::new();
let actor = Self::new();
let addr = context.address();
tokio::spawn(context.run(actor));
addr
}
async fn handle_start(&mut self) -> Result<(), String> {
match self.state {
ServerStatus::Active => {
debug_print!("Server already active");
Ok(())
}
ServerStatus::Stop => {
self.start_server().await?;
self.state = ServerStatus::Active;
debug_print!("Server activated");
Ok(())
}
}
}
async fn handle_stop(&mut self) -> Result<(), String> {
match self.state {
ServerStatus::Stop => {
debug_print!("Server already stopped");
Ok(())
}
ServerStatus::Active => {
self.stop_server().await?;
self.state = ServerStatus::Stop;
debug_print!("Server deactivated");
Ok(())
}
}
}
async fn start_server(&mut self) -> Result<(), String> {
ServerEngine { status: 0, port: 1212 }.send_signal_to_dart();
debug_print!("started server");
Ok(())
}
async fn stop_server(&mut self) -> Result<(), String> {
ServerEngine { status: 1, port: 1212 }.send_signal_to_dart();
debug_print!("stopped server");
Ok(())
}
}
pub async fn communicate() {
let receiver = ServerAction::get_dart_signal_receiver();
let mut server = TorrentServer::start();
while let Some(dart_signal) = receiver.recv().await {
let message: ServerAction = dart_signal.message;
let status = ServerStatus::try_from(message.status);
match status.unwrap() {
ServerStatus::Active => {
if let Err(e) = server.send(StartServer).await {
debug_print!("Failed to start server: {}", e);
}
}
ServerStatus::Stop => {
if let Err(e) = server.send(StopServer).await {
debug_print!("Failed to stop server: {}", e);
}
}
}
}
}

View file

@ -246,6 +246,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
chalkdart:
dependency: transitive
description:
name: chalkdart
sha256: "08c910ee341fcdd1e46f20ddce59b13c1d85f5d97f2fd2f12014c46ede670e40"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
characters:
dependency: transitive
description:
@ -721,10 +729,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.3.0"
http_multi_server:
dependency: transitive
description:
@ -901,6 +909,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
madari_engine:
dependency: "direct main"
description:
name: madari_engine
sha256: ba432da6be58a82395082559aa1d0b633230455ec4e71792ede107f419bffb89
url: "https://pub.dev"
source: hosted
version: "1.0.6"
markdown:
dependency: transitive
description:
@ -1216,7 +1232,7 @@ packages:
source: hosted
version: "6.0.1"
protobuf:
dependency: transitive
dependency: "direct main"
description:
name: protobuf
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
@ -1255,6 +1271,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.0"
rinf:
dependency: "direct main"
description:
name: rinf
sha256: b221072fd5d2a30f8244ee04d4991230b9bb8640cf991bb8d1f16894192d617b
url: "https://pub.dev"
source: hosted
version: "7.1.1"
riverpod:
dependency: transitive
description:
@ -1837,5 +1861,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.5.3 <4.0.0"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"

View file

@ -55,6 +55,9 @@ dependencies:
permission_handler: ^11.3.1
android_intent_plus: ^5.2.1
flex_color_picker: ^3.7.0
rinf: ^7.1.1
protobuf: ^3.1.0
madari_engine: ^1.0.6
dependency_overrides:
media_kit:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 KiB

View file

@ -18,6 +18,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST
media_kit_native_event_loop
rinf
)
set(PLUGIN_BUNDLED_LIBRARIES)