diff --git a/Cargo.lock b/Cargo.lock index 6bb1662..c0355bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -317,7 +317,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.7.1", + "zbus 5.8.0", ] [[package]] @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -701,9 +701,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel", "async-task", @@ -790,9 +790,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.28" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad45f4f74e4e20eaa392913b7b33a7091c87e59628f4dd27888205ad888843c" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -1054,7 +1054,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1070,13 +1070,13 @@ dependencies = [ "tokio", "tracing", "xdg", - "zbus 5.7.1", + "zbus 5.8.0", ] [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "quote", "syn 2.0.104", @@ -1112,7 +1112,7 @@ dependencies = [ [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#54b4418e1e7757d965166ae9dc00c522aebf4451" +source = "git+https://github.com/pop-os/cosmic-settings-daemon#a36683fd5b7dea4011da278c91d0645117dc39f8" dependencies = [ "cosmic-config", "ron", @@ -1128,13 +1128,13 @@ name = "cosmic-settings-daemon" version = "0.1.0" source = "git+https://github.com/pop-os/dbus-settings-bindings#3b86984332be2c930a3536ab714b843c851fa8ca" dependencies = [ - "zbus 5.7.1", + "zbus 5.8.0", ] [[package]] name = "cosmic-text" version = "0.14.2" -source = "git+https://github.com/pop-os/cosmic-text.git#d15011fba547ae6bd2cbc7591991ff74424c6db7" +source = "git+https://github.com/pop-os/cosmic-text.git#85b07b4c8c6b825aca984648fed7ee49b6591d97" dependencies = [ "bitflags 2.9.1", "fontdb 0.23.0", @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "almost", "cosmic-config", @@ -1170,6 +1170,16 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "cosmic-time" +version = "0.4.0" +source = "git+https://github.com/pop-os/cosmic-time.git#bbb9fb829b25e0bc2bbb382c25543646e82cda62" +dependencies = [ + "float-cmp", + "libcosmic", + "once_cell", +] + [[package]] name = "cosmicding" version = "2025.5.1" @@ -1177,6 +1187,7 @@ dependencies = [ "anyhow", "chrono", "constcat", + "cosmic-time", "directories", "env_logger 0.11.8", "futures", @@ -1378,9 +1389,9 @@ dependencies = [ [[package]] name = "derive_setters" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c848e86c87e5cc305313041c5677d4d95d60baa71cf95e5f6ea2554bb629ff" +checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9" dependencies = [ "darling", "proc-macro2", @@ -1818,6 +1829,9 @@ name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] [[package]] name = "float_next_after" @@ -2559,9 +2573,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64", "bytes", @@ -2585,9 +2599,9 @@ dependencies = [ [[package]] name = "i18n-config" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" +checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" dependencies = [ "basic-toml", "log", @@ -2677,7 +2691,7 @@ dependencies = [ [[package]] name = "iced" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "dnd", "iced_accessibility", @@ -2695,7 +2709,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "accesskit", "accesskit_winit", @@ -2704,7 +2718,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "bitflags 2.9.1", "bytes", @@ -2729,7 +2743,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "futures", "iced_core", @@ -2755,7 +2769,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "bitflags 2.9.1", "bytemuck", @@ -2777,7 +2791,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2789,7 +2803,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "bytes", "cosmic-client-toolkit", @@ -2805,7 +2819,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "bytemuck", "cosmic-text", @@ -2821,7 +2835,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "as-raw-xcb-connection", "bitflags 2.9.1", @@ -2852,7 +2866,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -2872,7 +2886,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -3384,7 +3398,7 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#aaa4b83577a70c15af8b91d1fb161e2a2931596b" +source = "git+https://github.com/pop-os/libcosmic#50367b96e3bb9a2a2a62a6c51cd71fc2858e61ed" dependencies = [ "apply", "ashpd", @@ -3429,7 +3443,7 @@ dependencies = [ "tracing", "unicode-segmentation", "url", - "zbus 5.7.1", + "zbus 5.8.0", ] [[package]] @@ -5055,9 +5069,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" dependencies = [ "bytemuck", ] @@ -5209,9 +5223,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "once_cell", "rustls-pki-types", @@ -5231,9 +5245,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -5301,9 +5315,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", @@ -5442,7 +5456,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.10.0", "schemars 0.9.0", - "schemars 1.0.3", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -6311,7 +6325,7 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.10.0", "toml_datetime", - "winnow 0.7.11", + "winnow 0.7.12", ] [[package]] @@ -7566,9 +7580,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -7677,9 +7691,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "xmlwriter" @@ -7761,9 +7775,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.7.1" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" +checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" dependencies = [ "async-broadcast 0.7.2", "async-executor", @@ -7787,10 +7801,10 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow 0.7.11", - "zbus_macros 5.7.1", + "winnow 0.7.12", + "zbus_macros 5.8.0", "zbus_names 4.2.0", - "zvariant 5.5.3", + "zvariant 5.6.0", ] [[package]] @@ -7809,16 +7823,16 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.7.1" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" +checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.104", "zbus_names 4.2.0", - "zvariant 5.5.3", + "zvariant 5.6.0", "zvariant_utils 3.2.0", ] @@ -7841,8 +7855,8 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" dependencies = [ "serde", "static_assertions", - "winnow 0.7.11", - "zvariant 5.5.3", + "winnow 0.7.12", + "zvariant 5.6.0", ] [[package]] @@ -7962,16 +7976,16 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.5.3" +version = "5.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d30786f75e393ee63a21de4f9074d4c038d52c5b1bb4471f955db249f9dffb1" +checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" dependencies = [ "endi", "enumflags2", "serde", "url", - "winnow 0.7.11", - "zvariant_derive 5.5.3", + "winnow 0.7.12", + "zvariant_derive 5.6.0", "zvariant_utils 3.2.0", ] @@ -7990,9 +8004,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.5.3" +version = "5.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75fda702cd42d735ccd48117b1630432219c0e9616bf6cb0f8350844ee4d9580" +checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -8023,5 +8037,5 @@ dependencies = [ "serde", "static_assertions", "syn 2.0.104", - "winnow 0.7.11", + "winnow 0.7.12", ] diff --git a/Cargo.toml b/Cargo.toml index 412477c..6b8f7e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" anyhow = "1.0.89" chrono = "0.4.38" constcat = "0.5.1" +cosmic-time = { git = "https://github.com/pop-os/cosmic-time.git", version = "0.4.0", features = ["once_cell"] } directories = "5.0.1" env_logger = "0.11.5" futures = "0.3.31" diff --git a/README.md b/README.md index 779766a..2c0fe32 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Dependencies (Linux): Dependencies (macOS): +- `brew` - `cargo` - `just` - `libxkbcommon` diff --git a/i18n/en/cosmicding.ftl b/i18n/en/cosmicding.ftl index 0dc0798..2e22701 100644 --- a/i18n/en/cosmicding.ftl +++ b/i18n/en/cosmicding.ftl @@ -36,6 +36,7 @@ enabled-public-sharing = Public bookmarks sharing enabled enabled-sharing = Bookmarks sharing enabled failed = failed failed-refreshing-accounts = Failed refreshing some accounts ({$accounts}) +failed-refreshing-all-accounts = Failed refreshing all accounts failed-refreshing-bookmarks-for-account = Failed refreshing account {$account} failed-to-add-account = Failed to add account {$acc}: {$err} failed-to-edit-account = Failed to edit account {$acc}: {$err} diff --git a/i18n/sv/cosmicding.ftl b/i18n/sv/cosmicding.ftl index 34a50ed..06bbd0a 100644 --- a/i18n/sv/cosmicding.ftl +++ b/i18n/sv/cosmicding.ftl @@ -36,6 +36,7 @@ enabled-public-sharing = Delning av offentliga bokmärken har aktiverats enabled-sharing = Bokmärkesdelning har aktiverats failed = misslyckades failed-refreshing-accounts = Misslyckades att uppdatera vissa konton ({$accounts}) +failed-refreshing-all-accounts = Misslyckades med att uppdatera alla konton failed-refreshing-bookmarks-for-account = Misslyckades att uppdatera konto {$account} failed-to-add-account = Misslyckades med att lägga till konto {$acc}: {$err} failed-to-edit-account = Misslyckades med att redigera konto {$acc}: {$err} diff --git a/justfile b/justfile index 537eaaa..c067ffe 100644 --- a/justfile +++ b/justfile @@ -48,6 +48,10 @@ build-release *args: if [ "$(uname)" = "Linux" ]; then just build-release-linux elif [ "$(uname)" = "Darwin" ]; then + export HOMEBREW_PREFIX="$(brew --prefix)" + export PKG_CONFIG_PATH="${HOMEBREW_PREFIX}/lib/pkgconfig" + export LIBRARY_PATH="${HOMEBREW_PREFIX}/lib" + export C_INCLUDE_PATH="${HOMEBREW_PREFIX}/include" if [ "$(uname -m)" = "arm64" ]; then just build-release-macos-aarch64 elif [ "$(uname -m)" = "x86_64" ]; then diff --git a/src/app.rs b/src/app.rs index ee67a3f..6f988df 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,13 +1,3 @@ -use crate::db::{self}; -use crate::fl; -use crate::http::{self}; -use crate::models::account::{Account, LinkdingAccountApiResponse}; -use crate::models::bookmarks::{ - Bookmark, BookmarkCheckDetailsResponse, BookmarkRemoveResponse, DetailedResponse, -}; -use crate::models::db_cursor::{AccountsPaginationCursor, BookmarksPaginationCursor, Pagination}; -use crate::pages::accounts::{add_account, edit_account, PageAccountsView}; -use crate::pages::bookmarks::{edit_bookmark, new_bookmark, view_notes, PageBookmarksView}; use crate::{ app::{ actions::ApplicationAction, @@ -17,29 +7,52 @@ use crate::{ menu as app_menu, nav::AppNavPage, }, - models::favicon_cache::Favicon, -}; -use cosmic::cosmic_config::{self, Update}; -use cosmic::cosmic_theme::{self, ThemeMode}; -use cosmic::iced::{ - event, - futures::executor::block_on, - keyboard::{Event as KeyEvent, Modifiers}, - Event, Length, Subscription, + db::{self}, + fl, + http::{self}, + models::{ + account::{Account, LinkdingAccountApiResponse}, + bookmarks::{ + Bookmark, BookmarkCheckDetailsResponse, BookmarkRemoveResponse, DetailedResponse, + }, + db_cursor::{AccountsPaginationCursor, BookmarksPaginationCursor, Pagination}, + favicon_cache::Favicon, + sync_status::SyncStatus, + }, + pages::{ + accounts::{add_account, edit_account, PageAccountsView}, + bookmarks::{edit_bookmark, new_bookmark, view_notes, PageBookmarksView}, + }, + style::animation::refresh, }; -use cosmic::iced_core::image::Bytes; -use cosmic::iced_widget::tooltip; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::{self, about::About, icon, nav_bar}; use cosmic::{ app::{context_drawer, Core, Task}, - widget::menu::Action, + cosmic_config::{self, Update}, + cosmic_theme::{self, ThemeMode}, + iced::{ + event, + futures::executor::block_on, + keyboard::{Event as KeyEvent, Modifiers}, + Event, Length, Subscription, + }, + iced_core::image::Bytes, + iced_widget::tooltip, + widget::{ + self, + about::About, + icon, + menu::{key_bind::KeyBind, Action}, + nav_bar, + }, + Application, ApplicationExt, Element, }; -use cosmic::{Application, ApplicationExt, Element}; +use cosmic_time::{chain, Timeline}; use key_bind::key_binds; -use std::any::TypeId; -use std::collections::{HashMap, VecDeque}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{ + any::TypeId, + collections::{HashMap, VecDeque}, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; pub mod actions; pub mod config; @@ -56,6 +69,9 @@ pub const APPID: &str = constcat::concat!(QUALIFIER, ".", ORG, ".", APP); const REPOSITORY: &str = "https://github.com/vkhitrin/cosmicding"; +pub static REFRESH_ICON: std::sync::LazyLock = + std::sync::LazyLock::new(refresh::Id::unique); + pub struct Flags { pub config_handler: Option, pub config: CosmicConfig, @@ -84,6 +100,8 @@ pub struct Cosmicding { pub config: CosmicConfig, pub state: ApplicationState, search_id: widget::Id, + timeline: Timeline, + sync_status: SyncStatus, toasts: widget::toaster::Toasts, } @@ -113,6 +131,7 @@ impl Application for Cosmicding { } fn init(core: Core, flags: Self::Flags) -> (Self, Task) { + let timeline = Timeline::new(); let db_pool = Some(block_on(async { db::SqliteDatabase::create().await.unwrap() })); @@ -181,6 +200,8 @@ impl Application for Cosmicding { placeholder_selected_account_index: 0, state: ApplicationState::NoEnabledAccounts, search_id: widget::Id::unique(), + timeline, + sync_status: SyncStatus::default(), toasts: widget::toaster::Toasts::new(ApplicationAction::CloseToast), }; @@ -200,6 +221,7 @@ impl Application for Cosmicding { tokio::runtime::Runtime::new().unwrap().block_on(async { app.bookmarks_cursor.refresh_count().await; }); + app.timeline.set_chain(chain![REFRESH_ICON]).start(); (app, Task::batch(commands)) } @@ -385,8 +407,12 @@ impl Application for Cosmicding { } ApplicationAction::SystemThemeModeChange }), + // NOTE: (vkhitrin) native implementation is too resource heavy. + // self.timeline + // .as_subscription() + // .map(|(_id, instant)| ApplicationAction::Tick(instant)), + cosmic::iced::time::every(Duration::from_millis(250)).map(ApplicationAction::Tick), ]; - Subscription::batch(subscriptions) } @@ -453,7 +479,9 @@ impl Application for Cosmicding { let account_page_entity = &self.nav.entity_at(0); self.nav.activate(account_page_entity.unwrap()); } - + ApplicationAction::Tick(now) => { + self.timeline.now(now); + } ApplicationAction::ToggleContextPage(context_page) => { if self.context_page == context_page { self.core.window.show_context = !self.core.window.show_context; @@ -461,7 +489,6 @@ impl Application for Cosmicding { self.context_page = context_page; self.core.window.show_context = true; } - //self.set_context_title(context_page.title()); } ApplicationAction::ContextClose => self.core.window.show_context = false, ApplicationAction::AccountsView(message) => { @@ -710,7 +737,7 @@ impl Application for Cosmicding { ApplicationAction::DoneRefreshBookmarksForAllAccounts(remote_responses) => { let mut failed_accounts: Vec = Vec::new(); if let Some(ref mut database) = &mut self.bookmarks_cursor.database { - for response in remote_responses { + for response in remote_responses.iter().cloned() { if !response.successful { failed_accounts.push(response.account.display_name.clone()); } @@ -729,12 +756,23 @@ impl Application for Cosmicding { commands.push(self.update(ApplicationAction::LoadBookmarks)); self.state = ApplicationState::Ready; if failed_accounts.is_empty() { + self.sync_status = SyncStatus::Successful; commands.push( self.toasts .push(widget::toaster::Toast::new(fl!("refreshed-bookmarks"))) .map(cosmic::Action::App), ); + } else if remote_responses.len() == failed_accounts.len() { + self.sync_status = SyncStatus::Failed; + commands.push( + self.toasts + .push(widget::toaster::Toast::new(fl!( + "failed-refreshing-all-accounts" + ))) + .map(cosmic::Action::App), + ); } else { + self.sync_status = SyncStatus::Warning; commands.push( self.toasts .push(widget::toaster::Toast::new(fl!( @@ -844,6 +882,7 @@ impl Application for Cosmicding { } } self.state = ApplicationState::Loading; + self.sync_status = SyncStatus::InProgress; } } ApplicationAction::AddBookmarkForm => { diff --git a/src/app/actions.rs b/src/app/actions.rs index 23a0d26..b753048 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -1,5 +1,7 @@ -use crate::models::account::{Account, LinkdingAccountApiResponse}; -use crate::models::bookmarks::{Bookmark, BookmarkRemoveResponse, DetailedResponse}; +use crate::models::{ + account::{Account, LinkdingAccountApiResponse}, + bookmarks::{Bookmark, BookmarkRemoveResponse, DetailedResponse}, +}; use crate::{ app::{ config::{AppTheme, CosmicConfig, SortOption}, @@ -8,12 +10,14 @@ use crate::{ }, models::bookmarks::BookmarkCheckDetailsResponse, }; -use cosmic::widget::{self}; use cosmic::{ iced::keyboard::{Key, Modifiers}, iced_core::image::Bytes, + widget::{self}, }; +use std::time::Instant; + #[derive(Debug, Clone)] pub enum ApplicationAction { AccountsView(AccountsAction), @@ -81,6 +85,7 @@ pub enum ApplicationAction { StartRemoveBookmark(i64, Bookmark), StartupCompleted, SystemThemeModeChange, + Tick(Instant), ToggleContextPage(ContextPage), UpdateConfig(CosmicConfig), ViewBookmarkNotes(Bookmark), diff --git a/src/app/dialog.rs b/src/app/dialog.rs index 8985872..a56bbd5 100644 --- a/src/app/dialog.rs +++ b/src/app/dialog.rs @@ -1,5 +1,4 @@ -use crate::models::account::Account; -use crate::models::bookmarks::Bookmark; +use crate::models::{account::Account, bookmarks::Bookmark}; #[derive(Clone, Debug, Eq, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum DialogPage { diff --git a/src/app/key_bind.rs b/src/app/key_bind.rs index 10474f1..5df8f1f 100644 --- a/src/app/key_bind.rs +++ b/src/app/key_bind.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; -use cosmic::iced::keyboard::Key; -use cosmic::widget::menu::key_bind::KeyBind; -use cosmic::widget::menu::key_bind::Modifier; +use cosmic::{ + iced::keyboard::Key, + widget::menu::key_bind::{KeyBind, Modifier}, +}; use crate::app::menu::MenuAction; diff --git a/src/app/menu.rs b/src/app/menu.rs index 0a86e8e..ba5abe1 100644 --- a/src/app/menu.rs +++ b/src/app/menu.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use crate::app::config::SortOption; -use cosmic::widget::RcElementWrapper; use cosmic::{ - widget::menu::{ - self, action::MenuAction as _MenuAction, key_bind::KeyBind, Item, ItemHeight, ItemWidth, + widget::{ + menu::{ + self, action::MenuAction as _MenuAction, key_bind::KeyBind, Item, ItemHeight, ItemWidth, + }, + RcElementWrapper, }, Element, }; diff --git a/src/app/nav.rs b/src/app/nav.rs index b632736..b207039 100644 --- a/src/app/nav.rs +++ b/src/app/nav.rs @@ -36,14 +36,21 @@ impl AppNavPage { match self { AppNavPage::AccountsView => app .accounts_view - .view(app.state, &app.accounts_cursor) + .view( + app.state, + app.sync_status, + &app.accounts_cursor, + &app.timeline, + ) .map(ApplicationAction::AccountsView), AppNavPage::BookmarksView => app .bookmarks_view .view( app.state, + app.sync_status, &app.bookmarks_cursor, app.accounts_cursor.total_entries == 0, + &app.timeline, ) .map(ApplicationAction::BookmarksView), } diff --git a/src/core/settings.rs b/src/core/settings.rs index 2b26a8c..24e842b 100644 --- a/src/core/settings.rs +++ b/src/core/settings.rs @@ -6,7 +6,7 @@ pub fn settings() -> cosmic::app::Settings { .min_width(360.0) .min_height(180.0), ) - // NOTE: An example of client decorations enabled. + // NOTE: (vkhitrin) An example of client decorations enabled. // Useless as long as we render the native // libcosmic menu // #[cfg(target_os = "macos")] diff --git a/src/db/mod.rs b/src/db/mod.rs index c8f337c..529f521 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,15 +1,12 @@ -use crate::app::config::SortOption; -use crate::models::favicon_cache::Favicon; +use crate::{ + app::{config::SortOption, APP, APPID, ORG, QUALIFIER}, + models::{account::Account, bookmarks::Bookmark, favicon_cache::Favicon}, +}; use anyhow::{anyhow, Result}; use std::path::Path; -use sqlx::sqlite::Sqlite; -use sqlx::{migrate::MigrateDatabase, prelude::*, SqlitePool}; - -use crate::app::{APP, APPID, ORG, QUALIFIER}; -use crate::models::account::Account; -use crate::models::bookmarks::Bookmark; +use sqlx::{migrate::MigrateDatabase, prelude::*, sqlite::Sqlite, SqlitePool}; const DB_PATH: &str = constcat::concat!(APPID, "-db", ".sqlite"); diff --git a/src/http/mod.rs b/src/http/mod.rs index d1fac8a..0f2ccb3 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,10 +1,14 @@ -use crate::fl; -use crate::models::account::{Account, LinkdingAccountApiResponse}; -use crate::models::bookmarks::{ - Bookmark, BookmarkCheckDetailsResponse, BookmarkRemoveResponse, DetailedResponse, - LinkdingBookmarksApiCheckResponse, LinkdingBookmarksApiResponse, +use crate::{ + fl, + models::{ + account::{Account, LinkdingAccountApiResponse}, + bookmarks::{ + Bookmark, BookmarkCheckDetailsResponse, BookmarkRemoveResponse, DetailedResponse, + LinkdingBookmarksApiCheckResponse, LinkdingBookmarksApiResponse, + }, + }, + utils::json::parse_serde_json_value_to_raw_string, }; -use crate::utils::json::parse_serde_json_value_to_raw_string; use anyhow::Result; use chrono::{DateTime, Utc}; use cosmic::iced_core::image::Bytes; @@ -13,9 +17,10 @@ use reqwest::{ ClientBuilder, StatusCode, }; use serde_json::Value; -use std::fmt::Write; -use std::time::Duration; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + fmt::Write, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; use urlencoding::encode; pub async fn fetch_bookmarks_from_all_accounts(accounts: Vec) -> Vec { diff --git a/src/models/db_cursor.rs b/src/models/db_cursor.rs index 6de921a..d909cf6 100644 --- a/src/models/db_cursor.rs +++ b/src/models/db_cursor.rs @@ -1,7 +1,6 @@ use crate::app::config::SortOption; use crate::db::SqliteDatabase; -use crate::models::account::Account; -use crate::models::bookmarks::Bookmark; +use crate::models::{account::Account, bookmarks::Bookmark}; pub trait Pagination { async fn refresh_count(&mut self); diff --git a/src/models/mod.rs b/src/models/mod.rs index cdc84cc..9e1321c 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,3 +2,4 @@ pub mod account; pub mod bookmarks; pub mod db_cursor; pub mod favicon_cache; +pub mod sync_status; diff --git a/src/models/sync_status.rs b/src/models/sync_status.rs new file mode 100644 index 0000000..d0b1e38 --- /dev/null +++ b/src/models/sync_status.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, Default)] +pub enum SyncStatus { + #[default] + None, + InProgress, + Successful, + Warning, + Failed, +} diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs index ae45318..18601d4 100644 --- a/src/pages/accounts.rs +++ b/src/pages/accounts.rs @@ -1,22 +1,23 @@ use crate::app::{ actions::{AccountsAction, ApplicationAction}, - ApplicationState, + ApplicationState, REFRESH_ICON, }; use crate::fl; -use crate::models::account::Account; -use crate::models::db_cursor::AccountsPaginationCursor; -use crate::style::button::ButtonStyle; +use crate::{ + models::{account::Account, db_cursor::AccountsPaginationCursor, sync_status::SyncStatus}, + style::button::ButtonStyle, +}; use chrono::{DateTime, Local}; -use cosmic::iced::Length; -use cosmic::iced_widget::tooltip; use cosmic::{ app::Task, cosmic_theme, - iced::Alignment, + iced::{Alignment, Length}, + iced_widget::tooltip, style, theme, widget::{self}, Apply, Element, }; +use cosmic_time::{anim, Timeline}; #[derive(Debug, Clone, Default)] pub struct PageAccountsView { @@ -29,7 +30,9 @@ impl PageAccountsView { pub fn view( &self, app_state: ApplicationState, + sync_status: SyncStatus, accounts_cursor: &AccountsPaginationCursor, + refresh_animation: &Timeline, ) -> Element<'_, AccountsAction> { let spacing = theme::active().cosmic().spacing; if self.accounts.is_empty() { @@ -251,6 +254,51 @@ impl PageAccountsView { .apply(widget::container) .into(), ])); + let animation_widget = match app_state { + ApplicationState::Refreshing => anim![REFRESH_ICON, &refresh_animation, 16], + _ => match sync_status { + SyncStatus::InProgress => anim![REFRESH_ICON, &refresh_animation, 16], + SyncStatus::Warning => cosmic::widget::icon( + widget::icon::from_name("dialog-warning-symbolic").handle(), + ) + .class(cosmic::theme::Svg::Custom(std::rc::Rc::new( + move |theme: &cosmic::theme::Theme| { + let cosmic = theme.cosmic(); + cosmic::iced_widget::svg::Style { + color: Some(cosmic.warning.base.into()), + } + }, + ))) + .size(16), + SyncStatus::Failed => widget::icon::from_name("dialog-error-symbolic") + .size(16) + .into(), + SyncStatus::Successful => cosmic::widget::icon( + widget::icon::from_name("checkbox-checked-symbolic").handle(), + ) + .class(cosmic::theme::Svg::Custom(std::rc::Rc::new( + move |theme: &cosmic::theme::Theme| { + let cosmic = theme.cosmic(); + cosmic::iced_widget::svg::Style { + color: Some(cosmic.success.base.into()), + } + }, + ))) + .size(16), + SyncStatus::None => cosmic::widget::icon( + widget::icon::from_name("media-record-symbolic").handle(), + ) + .class(cosmic::theme::Svg::Custom(std::rc::Rc::new( + move |theme: &cosmic::theme::Theme| { + let cosmic = theme.cosmic(); + cosmic::iced_widget::svg::Style { + color: Some(cosmic.background.base.into()), + } + }, + ))) + .size(16), + }, + }; widget::container( widget::column::with_children(vec![widget::row::with_capacity(2) @@ -266,6 +314,7 @@ impl PageAccountsView { spacing.space_xxs, spacing.space_none, ]) + .push(animation_widget) .push(widget::horizontal_space()) .push( widget::button::standard(fl!("add-account")) diff --git a/src/pages/bookmarks.rs b/src/pages/bookmarks.rs index 77d779a..75e2522 100644 --- a/src/pages/bookmarks.rs +++ b/src/pages/bookmarks.rs @@ -1,23 +1,26 @@ -use crate::app::{ - actions::{ApplicationAction, BookmarksAction}, - ApplicationState, +use crate::{ + app::{ + actions::{ApplicationAction, BookmarksAction}, + ApplicationState, REFRESH_ICON, + }, + fl, + models::{ + account::Account, bookmarks::Bookmark, db_cursor::BookmarksPaginationCursor, + sync_status::SyncStatus, + }, + style::{button::ButtonStyle, text_editor::text_editor_class}, }; -use crate::fl; -use crate::models::account::Account; -use crate::models::bookmarks::Bookmark; -use crate::models::db_cursor::BookmarksPaginationCursor; -use crate::style::{button::ButtonStyle, text_editor::text_editor_class}; use chrono::{DateTime, Local}; -use cosmic::iced::Length; -use cosmic::iced_core::text; use cosmic::{ app::Task, - iced::Alignment, - style, + cosmic_theme, + iced::{Alignment, Length}, + iced_core::text, + style, theme, widget::{self}, Apply, Element, }; -use cosmic::{cosmic_theme, theme}; +use cosmic_time::{anim, Timeline}; #[derive(Debug, Default, Clone)] pub struct PageBookmarksView { @@ -32,8 +35,10 @@ impl PageBookmarksView { pub fn view( &self, app_state: ApplicationState, + sync_status: SyncStatus, bookmarks_cursor: &BookmarksPaginationCursor, no_accounts: bool, + refresh_animation: &Timeline, ) -> Element<'_, BookmarksAction> { let spacing = theme::active().cosmic().spacing; if no_accounts { @@ -274,6 +279,52 @@ impl PageBookmarksView { }; let mut new_bookmark_button = widget::button::standard(fl!("add-bookmark")); + let animation_widget = match app_state { + ApplicationState::Refreshing => anim![REFRESH_ICON, &refresh_animation, 16], + _ => match sync_status { + SyncStatus::InProgress => anim![REFRESH_ICON, &refresh_animation, 16], + SyncStatus::Warning => cosmic::widget::icon( + widget::icon::from_name("dialog-warning-symbolic").handle(), + ) + .class(cosmic::theme::Svg::Custom(std::rc::Rc::new( + move |theme: &cosmic::theme::Theme| { + let cosmic = theme.cosmic(); + cosmic::iced_widget::svg::Style { + color: Some(cosmic.warning.base.into()), + } + }, + ))) + .size(16), + SyncStatus::Failed => widget::icon::from_name("dialog-error-symbolic") + .size(16) + .into(), + SyncStatus::Successful => cosmic::widget::icon( + widget::icon::from_name("checkbox-checked-symbolic").handle(), + ) + .class(cosmic::theme::Svg::Custom(std::rc::Rc::new( + move |theme: &cosmic::theme::Theme| { + let cosmic = theme.cosmic(); + cosmic::iced_widget::svg::Style { + color: Some(cosmic.success.base.into()), + } + }, + ))) + .size(16), + SyncStatus::None => cosmic::widget::icon( + widget::icon::from_name("media-record-symbolic").handle(), + ) + .class(cosmic::theme::Svg::Custom(std::rc::Rc::new( + move |theme: &cosmic::theme::Theme| { + let cosmic = theme.cosmic(); + cosmic::iced_widget::svg::Style { + color: Some(cosmic.background.base.into()), + } + }, + ))) + .size(16), + }, + }; + let mut search_input_widget = widget::text_input::search_input(fl!("search"), self.query_placeholder.clone()) .id(self.search_id.clone().unwrap()); @@ -348,6 +399,7 @@ impl PageBookmarksView { spacing.space_xxs, spacing.space_none, ]) + .push(animation_widget) .push(search_input_widget) .push(refresh_button) .push(new_bookmark_button) diff --git a/src/style/animation/mod.rs b/src/style/animation/mod.rs new file mode 100644 index 0000000..1d2e0f5 --- /dev/null +++ b/src/style/animation/mod.rs @@ -0,0 +1 @@ +pub mod refresh; diff --git a/src/style/animation/refresh.rs b/src/style/animation/refresh.rs new file mode 100644 index 0000000..e1cf62a --- /dev/null +++ b/src/style/animation/refresh.rs @@ -0,0 +1,113 @@ +use std::time::Duration; + +use cosmic::iced::Rotation; +use cosmic::widget::Icon; +use cosmic::widget::Id as CosmicId; +use cosmic::widget::{icon, icon::Handle}; +use cosmic_time::timeline::{self, Interped}; +use cosmic_time::{timeline::Frame, Timeline}; +use cosmic_time::{Cubic, Ease, MovementType, Repeat}; + +static REFRESH_ICON_HANDLE: std::sync::LazyLock = + std::sync::LazyLock::new(|| icon::from_name("view-refresh-symbolic").into()); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(CosmicId); + +impl Id { + pub fn new(id: impl Into>) -> Self { + Self(CosmicId::new(id)) + } + + pub fn unique() -> Self { + Self(CosmicId::unique()) + } + + pub fn into_chain(self) -> Chain { + Chain::new(self) + } + + pub fn as_widget(self, timeline: &Timeline, size: u16) -> Icon { + RefreshIcon::as_widget(&self, timeline, size) + } +} + +impl From for CosmicId { + fn from(value: Id) -> Self { + value.0 + } +} + +#[derive(Debug)] +pub struct Chain { + id: Id, + links: Vec, +} + +impl Chain { + pub fn new(id: Id) -> Self { + let links = (1..=72) // doubled the steps from 36 to 72 for smoother animation + .map(|i| { + let angle = i as f32 * 5.0; // reduced step size from 10 to 5 degrees + RefreshIcon::new(Duration::from_millis(50)) // shorter duration per frame + .angle(angle) + }) + .collect(); + + Chain { id, links } + } +} + +impl From for timeline::Chain { + fn from(chain: Chain) -> Self { + timeline::Chain::new( + chain.id.0, + Repeat::Forever, + chain + .links + .into_iter() + .map(std::convert::Into::into) + .collect::>(), + ) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct RefreshIcon { + at: MovementType, + ease: Ease, + angle: f32, +} + +impl RefreshIcon { + pub fn new(at: impl Into) -> RefreshIcon { + let at = at.into(); + RefreshIcon { + at, + ease: Cubic::InOut.into(), + angle: 0.0, + } + } + + pub fn angle(mut self, angle: f32) -> Self { + self.angle = angle; + self + } + + pub fn as_widget(id: &Id, timeline: &Timeline, size: u16) -> Icon { + let angle = if let Some(Interped { value, .. }) = timeline.get(&id.0, 0) { + value + } else { + 0.0 + }; + icon(REFRESH_ICON_HANDLE.clone()) + .rotation(Rotation::Floating((angle.to_radians()).into())) + .size(size) + } +} + +impl From for Vec> { + fn from(icon: RefreshIcon) -> Vec> { + vec![Some(Frame::lazy(icon.at, icon.angle, icon.ease))] + } +} diff --git a/src/style/mod.rs b/src/style/mod.rs index 2de1eb4..eb114a1 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -1,2 +1,3 @@ +pub mod animation; pub mod button; pub mod text_editor;