From 0fea61da3f5e43fa51c59cf83df120b3a413767d Mon Sep 17 00:00:00 2001 From: Sassan Haradji Date: Thu, 18 Jul 2024 04:56:39 +0400 Subject: [PATCH] feat(notifications): add `progress` and `progress_weight` properties to `Notification` object and show the progress on the header of the app --- CHANGELOG.md | 4 ++ poetry.lock | 70 +++++++++---------- pyproject.toml | 5 +- tests/fixtures/app.py | 1 + .../store-desktop-000.jsonc | 16 ++++- .../app_runs_and_exits/store-rpi-000.jsonc | 16 ++++- .../store-desktop-000.jsonc | 46 +++++++++--- .../all_services_register/store-rpi-000.jsonc | 46 +++++++++--- ubo_app/menu_app/home_page.kv | 1 + ubo_app/menu_app/home_page.py | 1 - ubo_app/menu_app/menu.py | 7 +- ubo_app/menu_app/menu_central.py | 10 +-- ubo_app/menu_app/menu_header.py | 69 ++++++++++++++++++ ubo_app/services/010-notifications/reducer.py | 42 ++++++++--- ubo_app/services/030-wifi/wifi_manager.py | 4 +- ubo_app/services/080-docker/setup.py | 35 ++++------ ubo_app/setup.py | 17 +++-- ubo_app/store/core/_menus.py | 1 + ubo_app/store/services/notifications.py | 5 +- ubo_app/system/install.sh | 1 - ubo_app/utils/async_.py | 2 - ubo_app/utils/loop.py | 1 + 22 files changed, 288 insertions(+), 112 deletions(-) create mode 100644 ubo_app/menu_app/menu_header.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e0f124..22babb5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Version 0.15.5 + +- feat(notifications): add `progress` and `progress_weight` properties to `Notification` object and show the progress on the header of the app + ## Version 0.15.4 - fix(core): add `rpi-lgpio` to dependencies to make the LCD work on RPi5 diff --git a/poetry.lock b/poetry.lock index a5af0bdb..99433f62 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1454,13 +1454,13 @@ files = [ [[package]] name = "pyright" -version = "1.1.371" +version = "1.1.372" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.371-py3-none-any.whl", hash = "sha256:cce52e42ff73943243e7e5e24f2a59dee81b97d99f4e3cf97370b27e8a1858cd"}, - {file = "pyright-1.1.371.tar.gz", hash = "sha256:777b508b92dda2db476214c400ce043aad8d8f3dd0e10d284c96e79f298308b5"}, + {file = "pyright-1.1.372-py3-none-any.whl", hash = "sha256:25b15fb8967740f0949fd35b963777187f0a0404c0bd753cc966ec139f3eaa0b"}, + {file = "pyright-1.1.372.tar.gz", hash = "sha256:a9f5e0daa955daaa17e3d1ef76d3623e75f8afd5e37b437d3ff84d5b38c15420"}, ] [package.dependencies] @@ -1506,13 +1506,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.23.7" +version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, - {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, ] [package.dependencies] @@ -1783,29 +1783,29 @@ files = [ [[package]] name = "ruff" -version = "0.5.1" +version = "0.5.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"}, - {file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"}, - {file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"}, - {file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"}, - {file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"}, - {file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"}, - {file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"}, - {file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"}, - {file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"}, - {file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"}, - {file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"}, - {file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"}, + {file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"}, + {file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"}, + {file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"}, + {file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"}, + {file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"}, + {file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"}, + {file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"}, ] [[package]] @@ -1814,14 +1814,10 @@ version = "0.12.0" description = "Modern Python D-Bus library. Based on sd-bus from libsystemd." optional = false python-versions = ">=3.7" -files = [] -develop = false - -[package.source] -type = "git" -url = "https://github.com/python-sdbus/python-sdbus.git" -reference = "split-sdbus-utils" -resolved_reference = "498663fc1c93b5b4ed8dbbc383442953f1bb42fa" +files = [ + {file = "sdbus-0.12.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d918c8ad14ef00e589d6752ac0f2b6540a20c625e85000c152376945fca14209"}, + {file = "sdbus-0.12.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d545e65637536a63898e2366b2a74617aa1a2215b1c16b23475f912e3407838c"}, +] [[package]] name = "sdbus-networkmanager" @@ -2004,13 +2000,13 @@ files = [ [[package]] name = "ubo-gui" -version = "0.11.17" +version = "0.12.0" description = "GUI sdk for Ubo Pod" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "ubo_gui-0.11.17-py3-none-any.whl", hash = "sha256:0bb0254bc34d343cc8e0fd0b866bbb641ccb71ad8cb47688dce41753b2833756"}, - {file = "ubo_gui-0.11.17.tar.gz", hash = "sha256:0bab6b9ac4540ccc1f95769d64f2b427bef9b85c9ea87ce64a4aebb26641ea5d"}, + {file = "ubo_gui-0.12.0-py3-none-any.whl", hash = "sha256:9b7660f37fdb1eb6e3fc2a12d7c335f7c680597923e9487e2f011c48bf88e04c"}, + {file = "ubo_gui-0.12.0.tar.gz", hash = "sha256:a503872b50d00464f8d690b4fec35f1ffddb88efaafac3b02f6476ab2ffb7c0c"}, ] [package.dependencies] @@ -2156,4 +2152,4 @@ dev = ["headless-kivy", "headless-kivy"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "d914cdc10d8cb0bf8cd784cea907cb2511c2388909c2294b626e2b5b71341696" +content-hash = "4019c09da2575a29028d1b5311551975b8493086d7b40fc66a461b0b06ecc673" diff --git a/pyproject.toml b/pyproject.toml index 465cef93..1a58e7f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ubo-app" -version = "0.15.4" +version = "0.15.5" description = "Ubo main app, running on device initialization. A platform for running other apps." authors = ["Sassan Haradji "] license = "Apache-2.0" @@ -17,7 +17,7 @@ priority = "primary" [tool.poetry.dependencies] python = "^3.11" psutil = "^6.0.0" -ubo-gui = "^0.11.17" +ubo-gui = "^0.12.0" headless-kivy = [ { version = "^0.9.4", markers = "extra=='default'", extras = [ "default", @@ -28,7 +28,6 @@ headless-kivy = [ ] python-redux = "^0.15.9" pyzbar = "^0.1.9" -sdbus = { git = "https://github.com/python-sdbus/python-sdbus.git", branch = "split-sdbus-utils", markers = "platform_machine=='aarch64'" } sdbus-networkmanager = { version = "^2.0.0", markers = "platform_machine=='aarch64'" } rpi_ws281x = { version = "^5.0.0", markers = "platform_machine=='aarch64'" } python-debouncer = "^0.1.4" diff --git a/tests/fixtures/app.py b/tests/fixtures/app.py index ddde11f2..c190b2d6 100644 --- a/tests/fixtures/app.py +++ b/tests/fixtures/app.py @@ -255,6 +255,7 @@ async def app_context( _monkeypatch: pytest.MonkeyPatch, ) -> AsyncGenerator[AppContext, None]: """Create the application.""" + _ = _monkeypatch setup() _setup_headless_kivy() diff --git a/tests/integration/results/test_core/app_runs_and_exits/store-desktop-000.jsonc b/tests/integration/results/test_core/app_runs_and_exits/store-desktop-000.jsonc index 2f70edb1..17d9907a 100644 --- a/tests/integration/results/test_core/app_runs_and_exits/store-desktop-000.jsonc +++ b/tests/integration/results/test_core/app_runs_and_exits/store-desktop-000.jsonc @@ -16,6 +16,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -30,6 +31,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No apps", @@ -48,6 +50,7 @@ "is_short": false, "label": "Settings", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -62,6 +65,7 @@ "is_short": false, "label": "Network", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -80,6 +84,7 @@ "is_short": false, "label": "Accessibility", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -98,6 +103,7 @@ "is_short": false, "label": "Utilities", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -116,6 +122,7 @@ "is_short": false, "label": "Remote", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -134,6 +141,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -157,6 +165,7 @@ "is_short": false, "label": "About", "opacity": null, + "progress": null, "sub_menu": { "heading": "Ubo v0.0.0", "items": [ @@ -166,7 +175,8 @@ "icon": "󰄬", "is_short": false, "label": "Already up to date!", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -186,6 +196,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No notifications", @@ -204,7 +215,8 @@ "icon": "󰐥", "is_short": true, "label": "Turn off", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, diff --git a/tests/integration/results/test_core/app_runs_and_exits/store-rpi-000.jsonc b/tests/integration/results/test_core/app_runs_and_exits/store-rpi-000.jsonc index 444137b6..b21aa984 100644 --- a/tests/integration/results/test_core/app_runs_and_exits/store-rpi-000.jsonc +++ b/tests/integration/results/test_core/app_runs_and_exits/store-rpi-000.jsonc @@ -16,6 +16,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -30,6 +31,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No apps", @@ -48,6 +50,7 @@ "is_short": false, "label": "Settings", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -62,6 +65,7 @@ "is_short": false, "label": "Network", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -80,6 +84,7 @@ "is_short": false, "label": "Accessibility", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -98,6 +103,7 @@ "is_short": false, "label": "Utilities", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -116,6 +122,7 @@ "is_short": false, "label": "Remote", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -134,6 +141,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No settings in this category", @@ -157,6 +165,7 @@ "is_short": false, "label": "About", "opacity": null, + "progress": null, "sub_menu": { "heading": "Ubo v0.0.0", "items": [ @@ -166,7 +175,8 @@ "icon": "󰄬", "is_short": false, "label": "Already up to date!", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -186,6 +196,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No notifications", @@ -204,7 +215,8 @@ "icon": "󰐥", "is_short": true, "label": "Turn off", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, diff --git a/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc b/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc index b03667c2..db70edc3 100644 --- a/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc +++ b/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc @@ -92,6 +92,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -106,6 +107,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -120,7 +122,8 @@ "icon": "󰡨", "is_short": false, "label": "Docker", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No apps", @@ -139,6 +142,7 @@ "is_short": false, "label": "Settings", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -153,6 +157,7 @@ "is_short": false, "label": "Network", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -167,6 +172,7 @@ "is_short": false, "label": "WiFi", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -181,7 +187,8 @@ "icon": "󱛃", "is_short": false, "label": "Add", - "opacity": null + "opacity": null, + "progress": null }, { "action": "", @@ -195,7 +202,8 @@ "icon": "󱖫", "is_short": false, "label": "Select", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -214,6 +222,7 @@ "is_short": false, "label": "IP Addresses", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -228,6 +237,7 @@ "is_short": false, "label": "eth0", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -241,7 +251,8 @@ "icon": "󰩠", "is_short": false, "label": "192.168.1.1", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No IP addresses", @@ -270,6 +281,7 @@ "is_short": false, "label": "Accessibility", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -284,6 +296,7 @@ "is_short": false, "label": "Voice", "opacity": null, + "progress": null, "sub_menu": { "heading": "󰔊 Picovoice", "items": [ @@ -299,7 +312,8 @@ "icon": "󰐲", "is_short": false, "label": "Set Access Key", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -324,6 +338,7 @@ "is_short": false, "label": "Utilities", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -338,7 +353,8 @@ "icon": "[color=#008000]󰪥[/color]", "is_short": false, "label": "LightDM", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No settings in this category", @@ -357,6 +373,7 @@ "is_short": false, "label": "Remote", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -371,7 +388,8 @@ "icon": "[color=#008000]󰪥[/color]", "is_short": false, "label": "SSH", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No settings in this category", @@ -390,6 +408,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -404,6 +423,7 @@ "is_short": false, "label": "Docker", "opacity": null, + "progress": null, "sub_menu": { "heading": "󰡨 Docker", "items": [ @@ -419,7 +439,8 @@ "icon": "󰌉", "is_short": false, "label": "Add Registry", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -449,6 +470,7 @@ "is_short": false, "label": "About", "opacity": null, + "progress": null, "sub_menu": { "heading": "Ubo v0.0.0", "items": [ @@ -458,7 +480,8 @@ "icon": "󰄬", "is_short": false, "label": "Already up to date!", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -478,6 +501,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No notifications", @@ -496,7 +520,8 @@ "icon": "󰐥", "is_short": true, "label": "Turn off", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -516,6 +541,7 @@ }, "notifications": { "notifications": [], + "progress": null, "unread_count": 0 }, "rgb_ring": { diff --git a/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc b/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc index 24e72dd2..323eb5ca 100644 --- a/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc +++ b/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc @@ -92,6 +92,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -106,6 +107,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -120,7 +122,8 @@ "icon": "󰡨", "is_short": false, "label": "Docker", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No apps", @@ -139,6 +142,7 @@ "is_short": false, "label": "Settings", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -153,6 +157,7 @@ "is_short": false, "label": "Network", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -167,6 +172,7 @@ "is_short": false, "label": "WiFi", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -181,7 +187,8 @@ "icon": "󱛃", "is_short": false, "label": "Add", - "opacity": null + "opacity": null, + "progress": null }, { "action": "", @@ -195,7 +202,8 @@ "icon": "󱖫", "is_short": false, "label": "Select", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -214,6 +222,7 @@ "is_short": false, "label": "IP Addresses", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -228,6 +237,7 @@ "is_short": false, "label": "eth0", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -241,7 +251,8 @@ "icon": "󰩠", "is_short": false, "label": "192.168.1.1", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No IP addresses", @@ -270,6 +281,7 @@ "is_short": false, "label": "Accessibility", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -284,6 +296,7 @@ "is_short": false, "label": "Voice", "opacity": null, + "progress": null, "sub_menu": { "heading": "󰔊 Picovoice", "items": [ @@ -299,7 +312,8 @@ "icon": "󰐲", "is_short": false, "label": "Set Access Key", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -324,6 +338,7 @@ "is_short": false, "label": "Utilities", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -338,7 +353,8 @@ "icon": "[color=#ffff00]󰝦[/color]", "is_short": false, "label": "LightDM", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No settings in this category", @@ -357,6 +373,7 @@ "is_short": false, "label": "Remote", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -371,7 +388,8 @@ "icon": "[color=#ffff00]󰝦[/color]", "is_short": false, "label": "SSH", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": "No settings in this category", @@ -390,6 +408,7 @@ "is_short": false, "label": "Apps", "opacity": null, + "progress": null, "sub_menu": { "items": [ { @@ -404,6 +423,7 @@ "is_short": false, "label": "Docker", "opacity": null, + "progress": null, "sub_menu": { "heading": "󰡨 Docker", "items": [ @@ -419,7 +439,8 @@ "icon": "󰌉", "is_short": false, "label": "Add Registry", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -449,6 +470,7 @@ "is_short": false, "label": "About", "opacity": null, + "progress": null, "sub_menu": { "heading": "Ubo v0.0.0", "items": [ @@ -458,7 +480,8 @@ "icon": "󰄬", "is_short": false, "label": "Already up to date!", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -478,6 +501,7 @@ "is_short": true, "label": "", "opacity": null, + "progress": null, "sub_menu": { "items": [], "placeholder": "No notifications", @@ -496,7 +520,8 @@ "icon": "󰐥", "is_short": true, "label": "Turn off", - "opacity": null + "opacity": null, + "progress": null } ], "placeholder": null, @@ -516,6 +541,7 @@ }, "notifications": { "notifications": [], + "progress": null, "unread_count": 0 }, "rgb_ring": { diff --git a/ubo_app/menu_app/home_page.kv b/ubo_app/menu_app/home_page.kv index 7365cf42..03535888 100644 --- a/ubo_app/menu_app/home_page.kv +++ b/ubo_app/menu_app/home_page.kv @@ -17,6 +17,7 @@ offset: root.offset padding_bottom: root.padding_bottom padding_top: root.padding_top + render_surroundings: root.render_surroundings BoxLayout: id: central_column diff --git a/ubo_app/menu_app/home_page.py b/ubo_app/menu_app/home_page.py index 52762306..ab9ca282 100644 --- a/ubo_app/menu_app/home_page.py +++ b/ubo_app/menu_app/home_page.py @@ -32,7 +32,6 @@ def __init__( *args, count=PAGE_SIZE + 2, offset=1, - render_surroundings=True, **kwargs, ) diff --git a/ubo_app/menu_app/menu.py b/ubo_app/menu_app/menu.py index 16d58108..acff7cb6 100644 --- a/ubo_app/menu_app/menu.py +++ b/ubo_app/menu_app/menu.py @@ -4,11 +4,12 @@ from ubo_gui.app import UboApp -from .menu_central import MenuAppCentral -from .menu_footer import MenuAppFooter +from ubo_app.menu_app.menu_central import MenuAppCentral +from ubo_app.menu_app.menu_footer import MenuAppFooter +from ubo_app.menu_app.menu_header import MenuAppHeader -class MenuApp(MenuAppCentral, MenuAppFooter, UboApp): +class MenuApp(MenuAppCentral, MenuAppFooter, MenuAppHeader, UboApp): """Menu application.""" def on_start(self: MenuApp) -> None: diff --git a/ubo_app/menu_app/menu_central.py b/ubo_app/menu_app/menu_central.py index bc787a48..142f6fcd 100644 --- a/ubo_app/menu_app/menu_central.py +++ b/ubo_app/menu_app/menu_central.py @@ -38,14 +38,15 @@ def home_page(self: MenuWidgetWithHomePage) -> HomePage: name='Page 1 0', padding_bottom=self.padding_bottom, padding_top=self.padding_top, + render_surroundings=self.render_surroundings, ) - def _render_items(self: MenuWidgetWithHomePage, *_: object) -> None: + def _render_menu(self: MenuWidgetWithHomePage, *_: object) -> PageWidget | None: if self.depth <= 1: self.home_page.set_items(self.current_menu_items) self.current_screen = self.home_page - else: - super()._render_items() + return self.home_page + return super()._render_menu() def set_path(menu_widget: MenuWidget, _: list[tuple[Menu, int] | PageWidget]) -> None: @@ -59,7 +60,7 @@ def set_path(menu_widget: MenuWidget, _: list[tuple[Menu, int] | PageWidget]) -> class MenuAppCentral(MenuNotificationHandler, UboApp): def __init__(self: MenuAppCentral, **kwargs: object) -> None: super().__init__(**kwargs) - self.menu_widget = MenuWidgetWithHomePage() + self.menu_widget = MenuWidgetWithHomePage(render_surroundings=True) _self = weakref.ref(self) @@ -78,6 +79,7 @@ def build(self: UboApp) -> Widget | None: root = super().build() self.menu_widget.padding_top = root.ids.header_layout.height self.menu_widget.padding_bottom = root.ids.footer_layout.height + return root def handle_page_index_change( diff --git a/ubo_app/menu_app/menu_header.py b/ubo_app/menu_app/menu_header.py new file mode 100644 index 00000000..905caef1 --- /dev/null +++ b/ubo_app/menu_app/menu_header.py @@ -0,0 +1,69 @@ +# ruff: noqa: D100, D101, D102, D103, D104, D107 +from __future__ import annotations + +from functools import cached_property +from typing import TYPE_CHECKING + +from kivy.metrics import dp +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.relativelayout import RelativeLayout +from ubo_gui.app import UboApp +from ubo_gui.progress_ring import ProgressRingWidget + +from ubo_app.store.main import autorun + +if TYPE_CHECKING: + from kivy.uix.widget import Widget + + from ubo_app.store.services.notifications import Notification + + +class MenuAppHeader(UboApp): + @cached_property + def header(self: MenuAppHeader) -> Widget | None: + self.header_layout = RelativeLayout() + + original_header = super().header + + if not original_header: + return None + original_header.pos = (0, 0) + self.header_layout.add_widget(original_header) + + progress_layout = BoxLayout( + orientation='horizontal', + padding=dp(4), + spacing=dp(2), + ) + self.header_layout.add_widget(progress_layout) + + @autorun( + lambda state: [ + notification + for notification in state.notifications.notifications + if notification.progress is not None + ], + ) + def _(notifications: list[Notification]) -> None: + for i in range(len(notifications)): + if i < len(progress_layout.children): + progress_layout.children[i].progress = notifications[i].progress + progress_layout.children[i].color = notifications[i].color + else: + progress_layout.add_widget( + ProgressRingWidget( + background_color=(0.3, 0.3, 0.3, 1), + color=notifications[i].color, + progress=notifications[i].progress, + height=dp(16), + band_width=dp(7), + size_hint=(None, None), + pos_hint={'center_y': 0.5}, + ), + ) + for _ in range(len(notifications), len(progress_layout.children)): + progress_layout.remove_widget(progress_layout.children[-1]) + + progress_layout.width = progress_layout.minimum_width + + return self.header_layout diff --git a/ubo_app/services/010-notifications/reducer.py b/ubo_app/services/010-notifications/reducer.py index 6b8b52b6..a3a1468c 100644 --- a/ubo_app/services/010-notifications/reducer.py +++ b/ubo_app/services/010-notifications/reducer.py @@ -52,18 +52,36 @@ def reducer( if action.notification in state.notifications: return CompleteReducerResult(state=state, events=events) kivy_color = get_color_from_hex(action.notification.color) + new_notifications = ( + [ + action.notification + if notification.id == action.notification.id + else notification + for notification in state.notifications + ] + if any( + notification.id == action.notification.id + for notification in state.notifications + ) + else [action.notification, *state.notifications] + ) return CompleteReducerResult( state=replace( state, - notifications=[ - *[ - notification - for notification in state.notifications - if notification.id != action.notification.id - ], - action.notification, - ], - unread_count=state.unread_count + 1, + notifications=new_notifications, + unread_count=sum( + 1 for notification in new_notifications if not notification.is_read + ), + progress=sum( + notification.progress * notification.progress_weight + for notification in new_notifications + if notification.progress is not None + ) + if any( + notification.progress is not None + for notification in new_notifications + ) + else None, ), actions=[ RgbRingBlinkAction( @@ -80,7 +98,11 @@ def reducer( }[action.notification.importance], wait=400, ), - SoundPlayChimeAction(name=action.notification.chime), + *( + [SoundPlayChimeAction(name=action.notification.chime)] + if action.notification.chime + else [] + ), ], events=events, ) diff --git a/ubo_app/services/030-wifi/wifi_manager.py b/ubo_app/services/030-wifi/wifi_manager.py index 14c7bad8..ac95889e 100644 --- a/ubo_app/services/030-wifi/wifi_manager.py +++ b/ubo_app/services/030-wifi/wifi_manager.py @@ -30,7 +30,9 @@ from collections.abc import Coroutine from sdbus import dbus_exceptions -from sdbus.utils.inspect import inspect_dbus_path +from sdbus.utils.inspect import ( # pyright: ignore [reportMissingImports] + inspect_dbus_path, +) from sdbus_async.networkmanager import ( AccessPoint, ActiveConnection, diff --git a/ubo_app/services/080-docker/setup.py b/ubo_app/services/080-docker/setup.py index 28dbaea1..2c3b3b48 100644 --- a/ubo_app/services/080-docker/setup.py +++ b/ubo_app/services/080-docker/setup.py @@ -130,16 +130,15 @@ async def check_docker() -> None: def setup_menu(status: DockerStatus) -> HeadedMenu: """Get the menu items for the Docker service.""" title = 'Setup Docker' - if status == DockerStatus.UNKNOWN: - return HeadedMenu( + return { + DockerStatus.UNKNOWN: HeadedMenu( title=title, heading='Checking', sub_heading='Checking Docker service status', items=[], placeholder='', - ) - if status == DockerStatus.NOT_INSTALLED: - return HeadedMenu( + ), + DockerStatus.NOT_INSTALLED: HeadedMenu( title=title, heading='Docker is not Installed', sub_heading='Install it to enjoy the power of Docker on your Ubo pod', @@ -150,17 +149,15 @@ def setup_menu(status: DockerStatus) -> HeadedMenu: action=install_docker, ), ], - ) - if status == DockerStatus.INSTALLING: - return HeadedMenu( + ), + DockerStatus.INSTALLING: HeadedMenu( title=title, heading='Installing...', sub_heading='Docker is being installed', items=[], placeholder='', - ) - if status == DockerStatus.NOT_RUNNING: - return HeadedMenu( + ), + DockerStatus.NOT_RUNNING: HeadedMenu( title=title, heading='Docker is not Running', sub_heading='Run it to enjoy the power of Docker on your Ubo pod', @@ -171,9 +168,8 @@ def setup_menu(status: DockerStatus) -> HeadedMenu: action=run_docker, ), ], - ) - if status == DockerStatus.RUNNING: - return HeadedMenu( + ), + DockerStatus.RUNNING: HeadedMenu( title=title, heading='Docker is Running', sub_heading='Enjoy the power of Docker on your Ubo pod', @@ -184,18 +180,15 @@ def setup_menu(status: DockerStatus) -> HeadedMenu: action=stop_docker, ), ], - ) - if status == DockerStatus.ERROR: - return HeadedMenu( + ), + DockerStatus.ERROR: HeadedMenu( title=title, heading='Docker Error', sub_heading='Please check the logs for more information', items=[], placeholder='', - ) - - msg = f'Unknown status: {status}' - raise ValueError(msg) + ), + }[status] def setup_menu_action() -> Callable[[], HeadedMenu]: diff --git a/ubo_app/setup.py b/ubo_app/setup.py index a059f3d4..139f0d16 100644 --- a/ubo_app/setup.py +++ b/ubo_app/setup.py @@ -32,14 +32,24 @@ def setup_hostname() -> None: def setup() -> None: """Set up for different environments.""" dotenv.load_dotenv(Path(__file__).parent / '.env') + import sys + + # it should be changed to `Fake()` and moved inside the `if not IS_RPI` when the + # new sdbus is released {- + from ubo_app.utils.fake import Fake + + sys.modules['sdbus.utils.inspect'] = Fake( + _Fake__props={ + 'inspect_dbus_path': lambda obj: obj._dbus.object_path, # noqa: SLF001 + }, + ) + # -} + from ubo_app.utils import IS_RPI if not IS_RPI: import asyncio import subprocess - import sys - - from ubo_app.utils.fake import Fake sys.modules['adafruit_rgb_display.st7789'] = Fake() sys.modules['alsaaudio'] = Fake() @@ -48,7 +58,6 @@ def setup() -> None: sys.modules['pulsectl'] = Fake() sys.modules['sdbus'] = Fake() sys.modules['sdbus.utils'] = Fake() - sys.modules['sdbus.utils.inspect'] = Fake() sys.modules['sdbus_async'] = Fake() sys.modules['sdbus_async.networkmanager'] = Fake() sys.modules['sdbus_async.networkmanager.enums'] = Fake() diff --git a/ubo_app/store/core/_menus.py b/ubo_app/store/core/_menus.py index 24a06d06..adc0f717 100644 --- a/ubo_app/store/core/_menus.py +++ b/ubo_app/store/core/_menus.py @@ -109,6 +109,7 @@ def notifications_menu_items(notifications: Sequence[Notification]) -> list[Item count=len(notifications), ), ), + progress=notification.progress, ) for index, notification in enumerate(notifications) if notification.expiry_date is None diff --git a/ubo_app/store/services/notifications.py b/ubo_app/store/services/notifications.py index abacca54..7191c912 100644 --- a/ubo_app/store/services/notifications.py +++ b/ubo_app/store/services/notifications.py @@ -89,7 +89,7 @@ class Notification(Immutable): content: str extra_information: str | None = None importance: Importance = Importance.LOW - chime: Chime = Chime.DONE + chime: Chime | None = Chime.DONE timestamp: datetime = field(default_factory=lambda: datetime.now(tz=UTC)) is_read: bool = False sender: str | None = None @@ -102,6 +102,8 @@ class Notification(Immutable): dismissable: bool = True dismiss_on_close: bool = False on_close: Callable[[], Any] | None = None + progress: float | None = None + progress_weight: float = 1 class NotificationsAction(BaseAction): ... @@ -134,3 +136,4 @@ class NotificationsDisplayEvent(NotificationsEvent): class NotificationsState(Immutable): notifications: Sequence[Notification] unread_count: int + progress: float | None = None diff --git a/ubo_app/system/install.sh b/ubo_app/system/install.sh index 196f618f..eb2995db 100755 --- a/ubo_app/system/install.sh +++ b/ubo_app/system/install.sh @@ -90,7 +90,6 @@ apt-get -y install \ python3-pip \ python3-pyaudio \ python3-virtualenv \ - libsystemd-dev \ --no-install-recommends --no-install-suggests apt-get -y clean diff --git a/ubo_app/utils/async_.py b/ubo_app/utils/async_.py index 588082b7..ec5d89dd 100644 --- a/ubo_app/utils/async_.py +++ b/ubo_app/utils/async_.py @@ -27,8 +27,6 @@ async def wrapper() -> None: logger = get_logger('ubo-app') - if awaitable is None: - return try: thread = current_thread() logger.verbose( diff --git a/ubo_app/utils/loop.py b/ubo_app/utils/loop.py index 51865526..ab14c743 100644 --- a/ubo_app/utils/loop.py +++ b/ubo_app/utils/loop.py @@ -33,6 +33,7 @@ def __init__(self: WorkerThread) -> None: self.loop = asyncio.get_event_loop() except RuntimeError: self.loop = asyncio.new_event_loop() + self.loop.set_exception_handler(loop_exception_handler) asyncio.set_event_loop(self.loop)