difonlib 0.4.0__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {difonlib-0.4.0 → difonlib-0.4.2}/PKG-INFO +1 -1
  2. {difonlib-0.4.0 → difonlib-0.4.2}/pyproject.toml +1 -1
  3. difonlib-0.4.2/src/difonlib/ng_lib.py +262 -0
  4. difonlib-0.4.2/src/difonlib/ng_lib_test3.py +77 -0
  5. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/tuya_devs.py +9 -2
  6. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/PKG-INFO +1 -1
  7. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/SOURCES.txt +1 -0
  8. difonlib-0.4.0/src/difonlib/ng_lib.py +0 -130
  9. {difonlib-0.4.0 → difonlib-0.4.2}/README.md +0 -0
  10. {difonlib-0.4.0 → difonlib-0.4.2}/setup.cfg +0 -0
  11. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/__init__.py +0 -0
  12. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/bt_scanner.py +0 -0
  13. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/bt_utils.py +0 -0
  14. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/conn_kbd_devs.py +0 -0
  15. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/docker-android/get_tuya_preferences.py +0 -0
  16. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/input_devs.py +0 -0
  17. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/py.typed +0 -0
  18. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/remctrl.py +0 -0
  19. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib/utils.py +0 -0
  20. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/dependency_links.txt +0 -0
  21. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/requires.txt +0 -0
  22. {difonlib-0.4.0 → difonlib-0.4.2}/src/difonlib.egg-info/top_level.txt +0 -0
  23. {difonlib-0.4.0 → difonlib-0.4.2}/tests/test_bt_utils.py +0 -0
  24. {difonlib-0.4.0 → difonlib-0.4.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: difonlib
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: python libraries
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "difonlib"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "python libraries"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,262 @@
1
+ from nicegui import ui
2
+ import inspect
3
+ import asyncio
4
+ from typing import Any, Callable, Awaitable, Literal
5
+
6
+ from difonlib.utils import logdbg
7
+
8
+ dbg = logdbg
9
+ # https://claude.ai/share/227ce46f-f825-4783-8e0e-9e774963ec84
10
+ # Или точечнее — только ui.label внутри диалога:
11
+ # python.q-dialog .q-card .q-item__label {
12
+ # font-size: 20px !important;
13
+ # }
14
+ # Либо ещё проще — прямо в коде диалога:
15
+ # ui.label(text=text).classes("text-xl")
16
+ # text-xl — это Tailwind, соответствует 1.25rem. Если нужно крупнее — text-2xl, text-3xl.
17
+ ui.add_css(
18
+ """
19
+ .q-notification__message {
20
+ font-size: 20px !important;
21
+ }
22
+ .q-dialog .q-card {
23
+ font-size: 20px !important;
24
+ }
25
+ """,
26
+ shared=True,
27
+ )
28
+
29
+
30
+ class DialogBox:
31
+ async def dialog_ok(self, text: str = "Hello!)") -> None:
32
+ with ui.dialog().props("persistent") as dialog, ui.card():
33
+ ui.label(text=text)
34
+ ui.button("Close", on_click=dialog.close)
35
+ dialog.open()
36
+
37
+ async def dialog_ok_cancel(
38
+ self,
39
+ text: str,
40
+ on_click_ok: Callable,
41
+ on_click_cancel: Callable = lambda: None,
42
+ btn_ok: str = "OK",
43
+ btn_cancel: str = "Cancel",
44
+ ) -> None:
45
+ with ui.dialog().props("persistent") as dialog, ui.card():
46
+ ui.label(text=text)
47
+ with ui.row():
48
+ ui.button(
49
+ btn_ok,
50
+ on_click=lambda: (on_click_ok(), dialog.close()),
51
+ )
52
+ ui.button(btn_cancel, on_click=lambda: (on_click_cancel(), dialog.close()))
53
+ dialog.open()
54
+
55
+ async def dialog_confirm(
56
+ self, text: str = "Are you sure?", btn_ok: str = "Yes", btn_cancel: str = "No"
57
+ ) -> ui.dialog:
58
+ with ui.dialog().props("persistent") as dialog, ui.card():
59
+ ui.label(text=text)
60
+ with ui.row():
61
+ ui.button(btn_ok, on_click=lambda: dialog.submit(btn_ok))
62
+ ui.button(btn_cancel, on_click=lambda: dialog.submit(btn_cancel))
63
+ return await dialog
64
+
65
+ async def dialog_input(
66
+ self,
67
+ text: str,
68
+ input_prefix: str = "",
69
+ **kwrds: Any,
70
+ ) -> ui.dialog:
71
+ with ui.dialog().props("persistent") as dialog, ui.card():
72
+ ui.label(text=text)
73
+ with ui.row():
74
+ inp: ui.input = ui.input(value=input_prefix, **kwrds).on(
75
+ "keydown.enter", lambda: dialog.submit(inp.value)
76
+ )
77
+ ui.button(text="Enter", on_click=lambda: dialog.submit(inp.value))
78
+ ui.button(text="Cancel", on_click=dialog.close)
79
+ return await dialog
80
+
81
+
82
+ class CardTable:
83
+ _current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
84
+
85
+ def __init__(
86
+ self,
87
+ title: str,
88
+ columns: list,
89
+ rows: list[dict] | None = None,
90
+ selection: Literal["single", "multiple"] | None = None,
91
+ on_selection_change: list[Callable] | None = None,
92
+ marked_text_color: str = "yellow",
93
+ marked_field: str = "_marked",
94
+ ):
95
+ self.on_selection_change: list[Callable] = on_selection_change or []
96
+ self.buttons_on_row_select_changed: list = []
97
+ self.marked_text_color = marked_text_color
98
+ self.marked_field = marked_field
99
+
100
+ with ui.card().classes("p-4 shadow-lg") as self.card:
101
+ with ui.row().classes("items-center justify-between w-full mb-2"):
102
+ with ui.row().classes("gap-2") as self.left_table:
103
+ pass
104
+ ui.label(f"📋 {title}").classes("text-green-700 text-lg font-bold")
105
+ with ui.row().classes("gap-2") as self.top_table:
106
+ pass
107
+
108
+ self.table = ui.table(
109
+ columns=self._normalize_columns(columns),
110
+ rows=self.enum_data(rows or []),
111
+ row_key="sn",
112
+ selection=selection,
113
+ on_select=self._on_selection_change,
114
+ column_defaults={
115
+ "align": "left",
116
+ "headerClasses": "uppercase",
117
+ },
118
+ ).classes("w-full shadow-lg bg-black-900 text-gray-200")
119
+
120
+ with (
121
+ ui.dialog().props("persistent") as self.confirm_dialog,
122
+ ui.card().classes("p-4"),
123
+ ):
124
+ self.dialog_title = ui.label().classes("text-lg font-bold mb-4")
125
+ with ui.row().classes("justify-end w-full gap-2"):
126
+ ui.button("No", color="red", on_click=self.confirm_dialog.close)
127
+ ui.button("Yes", color="green", on_click=self._on_yes_clicked)
128
+
129
+ with (
130
+ ui.dialog().props("persistent") as self.processing_dialog,
131
+ ui.card().classes("p-4 items-center justify-center"),
132
+ ):
133
+ with ui.row().classes("items-center gap-3"):
134
+ self.processing_spinner = ui.spinner(size="md")
135
+ self.processing_label = ui.label("Processing...").classes("text-base")
136
+
137
+ if self.marked_field:
138
+ color_class = f"text-{self.marked_text_color}"
139
+ for column in self.table.columns:
140
+ cell = column["name"]
141
+ self.table.add_slot(
142
+ f"body-cell-{cell}",
143
+ rf"""
144
+ <q-td :props="props">
145
+ <span :class="props.row.{self.marked_field} ? 'font-bold {color_class}' : ''">
146
+ {{{{props.value}}}}
147
+ </span>
148
+ </q-td>
149
+ """,
150
+ )
151
+
152
+ def _normalize_columns(self, columns: list[dict]) -> list[dict]:
153
+ """Add field 'name', needed for Quasar GUI"""
154
+ return [{**col, "name": col.get("name", col["field"])} for col in columns]
155
+
156
+ def mark_row(self, mark: bool = True, **kwrds: Any) -> list[dict[str, Any]]:
157
+ """
158
+ card_table.mark_row(id="123456", name="ANNNNN")
159
+ card_table.mark_row(ip="192.168.0.18", mark=False)
160
+ """
161
+ found = []
162
+ for row in self.table.rows:
163
+ if all(row.get(k) == v for k, v in kwrds.items()):
164
+ row[self.marked_field] = mark
165
+ found.append(row)
166
+ if found:
167
+ self.table.update()
168
+ return found
169
+
170
+ def visible(self, state: bool) -> None:
171
+ self.table.visible = state
172
+ self.card.visible = state
173
+
174
+ async def _on_selection_change(self, e: Any) -> None:
175
+ selected = bool(e.selection)
176
+ for btn in self.buttons_on_row_select_changed:
177
+ if selected:
178
+ btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
179
+ else:
180
+ btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
181
+ for handler in self.on_selection_change:
182
+ await handler(e)
183
+
184
+ async def _run_with_processing(self, handler: Callable) -> None:
185
+ self.processing_dialog.open()
186
+ try:
187
+ if inspect.iscoroutinefunction(handler):
188
+ await handler()
189
+ else:
190
+ handler()
191
+ await asyncio.sleep(0.1)
192
+ finally:
193
+ self.processing_dialog.close()
194
+
195
+ async def _on_yes_clicked(self) -> None:
196
+ handler, self._current_yes_handler = self._current_yes_handler, None
197
+ self.confirm_dialog.close()
198
+ if not handler:
199
+ return
200
+ await self._run_with_processing(handler)
201
+
202
+ def enum_data(self, rows: list[dict]) -> list[dict]:
203
+ return [{"sn": i + 1, **row} for i, row in enumerate(rows)]
204
+
205
+ def set_rows(self, rows: list[dict]) -> None:
206
+ self.table.rows = self.enum_data(rows)
207
+ self.table.update()
208
+
209
+ def add_checkbox(
210
+ self,
211
+ chbox_txt: str,
212
+ on_change: Callable,
213
+ default_enable: bool = False,
214
+ ) -> ui.checkbox:
215
+ """
216
+ card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: ui.notify(v))
217
+ """
218
+ with self.top_table:
219
+ chbox = ui.checkbox(chbox_txt, value=default_enable).classes("font-bold")
220
+
221
+ async def handle_change(e: Any) -> None:
222
+ if inspect.iscoroutinefunction(on_change):
223
+ await on_change(e.sender.value)
224
+ else:
225
+ on_change(e.sender.value)
226
+
227
+ chbox.on("click", handle_change)
228
+ setattr(self, f"chbox{chbox_txt.replace(' ', '_')}", chbox)
229
+ return chbox
230
+
231
+ def add_button(
232
+ self,
233
+ btn_txt: str,
234
+ on_click: Callable,
235
+ default_enable: bool = True,
236
+ color: str = "blue",
237
+ active_on_rows_selected: bool = False,
238
+ use_dialog_confirm: bool = False,
239
+ confirm_title: str | None = None,
240
+ ) -> ui.button:
241
+ with self.top_table:
242
+ btn = ui.button(btn_txt, color=color)
243
+
244
+ if active_on_rows_selected:
245
+ self.buttons_on_row_select_changed.append(btn)
246
+
247
+ if default_enable:
248
+ btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
249
+ else:
250
+ btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
251
+
252
+ async def handle_click() -> None:
253
+ if use_dialog_confirm:
254
+ self.dialog_title.text = confirm_title or f"Confirm {btn_txt}?"
255
+ self._current_yes_handler = on_click
256
+ self.confirm_dialog.open()
257
+ else:
258
+ await self._run_with_processing(on_click)
259
+
260
+ btn.on_click(handle_click)
261
+ setattr(self, f"btn{btn_txt.replace(' ','_')}", btn)
262
+ return btn
@@ -0,0 +1,77 @@
1
+ from nicegui import ui
2
+ from ng_lib import CardTable, DialogBox
3
+ import functools
4
+
5
+ from difonlib.bt_utils import logdbg
6
+
7
+ dbg = logdbg
8
+
9
+
10
+ @ui.page("/", dark=True)
11
+ def main() -> None:
12
+
13
+ card_table = CardTable(
14
+ title="Connected Devices",
15
+ columns=[
16
+ {"name": "id", "label": "id", "field": "id"},
17
+ {"name": "name", "label": "Name", "field": "name"},
18
+ {"name": "ip", "label": "IP Address", "field": "ip"},
19
+ ],
20
+ selection="single",
21
+ rows=[
22
+ {"id": "123456", "name": "ANNNNN", "ip": "192.168.0.18"},
23
+ {"id": "987654", "name": "VNNNNN", "ip": "192.168.0.118"},
24
+ {"id": "123456", "name": "BNNNNN", "ip": "192.168.0.12"},
25
+ {"id": "003456", "name": "SNNNNN", "ip": "192.168.0.144"},
26
+ {"id": "129956", "name": "QNNNNN", "ip": "192.168.0.49"},
27
+ ],
28
+ )
29
+
30
+ found = card_table.mark_row(id="123456", mark=True)
31
+ print(f"card_table.table.rows:{card_table.table.rows}") # //Dima
32
+ found = card_table.mark_row(id="123456", name="ANNNNN", mark=False)
33
+ found = card_table.mark_row(id="129956")
34
+ print(f" ==> found: {found}") # //Dima
35
+
36
+ print(f"card_table.table.rows: {card_table.table.rows}")
37
+
38
+ card_table.add_checkbox(chbox_txt="Used Only", on_change=lambda v: ui.notify(v))
39
+
40
+ dialog_box = DialogBox()
41
+
42
+ def btnok() -> None:
43
+ ui.notify("You press OK")
44
+
45
+ def btncancel() -> None:
46
+ ui.notify("You press Cancel")
47
+
48
+ card_table.add_button(
49
+ "test_ok_cancel",
50
+ on_click=functools.partial(
51
+ dialog_box.dialog_ok_cancel,
52
+ text="OK or Cancel?",
53
+ on_click_ok=btnok,
54
+ on_click_cancel=btncancel,
55
+ ),
56
+ )
57
+
58
+ async def confirm() -> None:
59
+ result = await dialog_box.dialog_confirm()
60
+ ui.notify(f"You chose {result}")
61
+
62
+ card_table.add_button(
63
+ "test_confirm",
64
+ on_click=confirm,
65
+ )
66
+
67
+ async def dialog_input() -> None:
68
+ inp = await dialog_box.dialog_input("Input IR_KEY_NAME", input_prefix="IR_KEY_")
69
+ ui.notify(f"Your input: {inp}")
70
+
71
+ card_table.add_button(
72
+ "test_input",
73
+ on_click=dialog_input,
74
+ )
75
+
76
+
77
+ ui.run(port=1111)
@@ -29,6 +29,13 @@ tuya_xml_data_file = Path(
29
29
  )
30
30
 
31
31
 
32
+ class OutletDeviceM(OutletDevice):
33
+ def toggle(self) -> bool | Any:
34
+ data = self.status()
35
+ switch_state: bool = data["dps"]["1"]
36
+ return self.set_status(not switch_state)["dps"]["1"]
37
+
38
+
32
39
  class TuyaDevs:
33
40
  def __init__(
34
41
  self,
@@ -118,7 +125,7 @@ class TuyaDevs:
118
125
  # ]
119
126
  # return con_devs
120
127
 
121
- def dev_is_connected(self, dev_id: str, timeout: int = 8, rescan: bool = False) -> bool | None:
128
+ def dev_is_connected(self, dev_id: str, timeout: int = 8, rescan: bool = False) -> bool:
122
129
  if rescan:
123
130
  # update self.devs_online
124
131
  self.scan(timeout=timeout)
@@ -176,7 +183,7 @@ class TuyaDevs:
176
183
  odev.turn_off()
177
184
  """
178
185
  try:
179
- dev = OutletDevice(
186
+ dev = OutletDeviceM(
180
187
  dev_id=dev_id,
181
188
  local_key=local_key,
182
189
  persist=True,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: difonlib
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: python libraries
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -6,6 +6,7 @@ src/difonlib/bt_utils.py
6
6
  src/difonlib/conn_kbd_devs.py
7
7
  src/difonlib/input_devs.py
8
8
  src/difonlib/ng_lib.py
9
+ src/difonlib/ng_lib_test3.py
9
10
  src/difonlib/py.typed
10
11
  src/difonlib/remctrl.py
11
12
  src/difonlib/tuya_devs.py
@@ -1,130 +0,0 @@
1
- from nicegui import ui
2
- import inspect
3
- import asyncio
4
- from typing import Any, Callable, Awaitable, Literal
5
-
6
- from difonlib.utils import logdbg
7
-
8
- dbg = logdbg
9
-
10
-
11
- class CardTable:
12
- _current_yes_handler: Callable[[], None | Awaitable[None]] | None = None
13
-
14
- def __init__(
15
- self,
16
- title: str,
17
- columns: list,
18
- rows: list[dict] | None = None,
19
- selection: Literal["single", "multiple"] | None = None,
20
- on_selection_change: list[Callable] | None = None,
21
- ):
22
- self.on_selection_change: list[Callable] = on_selection_change or []
23
- self.buttons_on_row_select_changed: list = []
24
-
25
- with ui.card().classes("p-4 shadow-lg") as self.card:
26
- with ui.row().classes("items-center justify-between w-full mb-2"):
27
- ui.label(f"📋 {title}").classes("text-green-700 text-lg font-bold")
28
- with ui.row().classes("gap-2") as self.top_table:
29
- pass
30
-
31
- self.table = ui.table(
32
- columns=columns,
33
- rows=self.enum_data(rows or []),
34
- row_key="sn",
35
- selection=selection,
36
- on_select=self._on_selection_change,
37
- column_defaults={
38
- "align": "left",
39
- "headerClasses": "uppercase",
40
- },
41
- ).classes("w-full shadow-lg bg-black-900 text-gray-200")
42
-
43
- with (
44
- ui.dialog().props("persistent") as self.confirm_dialog,
45
- ui.card().classes("p-4"),
46
- ):
47
- self.dialog_title = ui.label().classes("text-lg font-bold mb-4")
48
- with ui.row().classes("justify-end w-full gap-2"):
49
- ui.button("No", color="red", on_click=self.confirm_dialog.close)
50
- ui.button("Yes", color="green", on_click=self._on_yes_clicked)
51
-
52
- with (
53
- ui.dialog().props("persistent") as self.processing_dialog,
54
- ui.card().classes("p-4 items-center justify-center"),
55
- ):
56
- with ui.row().classes("items-center gap-3"):
57
- self.processing_spinner = ui.spinner(size="md")
58
- self.processing_label = ui.label("Processing...").classes("text-base")
59
-
60
- def visible(self, state: bool) -> None:
61
- self.table.visible = state
62
- self.card.visible = state
63
-
64
- async def _on_selection_change(self, e: Any) -> None:
65
- selected = bool(e.selection)
66
- for btn in self.buttons_on_row_select_changed:
67
- if selected:
68
- btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
69
- else:
70
- btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
71
- for handler in self.on_selection_change:
72
- await handler(e)
73
-
74
- async def _run_with_processing(self, handler: Callable) -> None:
75
- self.processing_dialog.open()
76
- try:
77
- if inspect.iscoroutinefunction(handler):
78
- await handler()
79
- else:
80
- handler()
81
- await asyncio.sleep(0.5)
82
- finally:
83
- self.processing_dialog.close()
84
-
85
- async def _on_yes_clicked(self) -> None:
86
- handler, self._current_yes_handler = self._current_yes_handler, None
87
- self.confirm_dialog.close()
88
- if not handler:
89
- return
90
- await self._run_with_processing(handler)
91
-
92
- def enum_data(self, data: list[dict]) -> list[dict]:
93
- return [{"sn": i + 1, **row} for i, row in enumerate(data)]
94
-
95
- def set_rows(self, rows: list[dict]) -> None:
96
- self.table.rows = self.enum_data(rows)
97
- self.table.update()
98
-
99
- def add_button(
100
- self,
101
- btn_txt: str,
102
- on_click: Callable,
103
- default_enable: bool = True,
104
- color: str = "blue",
105
- active_on_rows_selected: bool = False,
106
- use_dialog_confirm: bool = False,
107
- confirm_title: str | None = None,
108
- ) -> ui.button:
109
- with self.top_table:
110
- btn = ui.button(btn_txt, color=color)
111
-
112
- if active_on_rows_selected:
113
- self.buttons_on_row_select_changed.append(btn)
114
-
115
- if default_enable:
116
- btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
117
- else:
118
- btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
119
-
120
- async def handle_click() -> None:
121
- if use_dialog_confirm:
122
- self.dialog_title.text = confirm_title or f"Confirm {btn_txt}?"
123
- self._current_yes_handler = on_click
124
- self.confirm_dialog.open()
125
- else:
126
- await self._run_with_processing(on_click)
127
-
128
- btn.on_click(handle_click)
129
- setattr(self, f"btn{btn_txt}", btn)
130
- return btn
File without changes
File without changes
File without changes
File without changes
File without changes