Skip to content

Commit dde2c48

Browse files
matthaneclaude
andcommitted
Return to settings when the manage view closes and add a labeled exit row
The manage button closes the settings dialog (<close>true</close>), so exiting the view dropped the user back into Kodi; the router now reopens the settings dialog after the view returns. The select dialog's built-in Cancel button is skin-owned wording the Python API cannot change, so the view appends its own "Back to settings" row (32134) as the properly worded exit. Co-authored-by: Claude <noreply@anthropic.com>
1 parent caf0ede commit dde2c48

5 files changed

Lines changed: 49 additions & 12 deletions

File tree

resources/language/resource.language.en_gb/strings.po

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,8 @@ msgstr ""
231231

232232
msgctxt "#32133"
233233
msgid "Reset to 0 ms — nothing stored for this stream"
234+
msgstr ""
235+
236+
msgctxt "#32134"
237+
msgid "Back to settings"
234238
msgstr ""

resources/lib/aom/script_router.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
delete/clear, P6), reached from the settings dialog's action button.
1414
- anything else / no argument — open the addon settings (D13: launching
1515
the addon opens the full settings dialog, the natural hub).
16+
17+
Every route ends in the settings dialog: the manage button closes it on
18+
press (``<close>true</close>``, the write-ordering doctrine), so reopening
19+
after the view exits returns the user to where they came from instead of
20+
dropping them back into Kodi.
1621
"""
1722

1823
import sys
@@ -39,7 +44,8 @@ def handle_script_call(argv=None):
3944
route = args[1] if len(args) > 1 else ''
4045
if route == 'manage_offsets':
4146
_manage_offsets()
42-
return
47+
# ...then fall through: the manage button closed the settings
48+
# dialog, so every exit from the view lands back in it.
4349
xbmcaddon.Addon(ADDON_ID).openSettings()
4450

4551

resources/lib/aom/view/manage.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
_MSG_UNREADABLE = 32127 # StoreUnreadable (corrupt: will be quarantined)
4444
_MSG_MUTATION_FAILED = 32128
4545
_MSG_FUTURE = 32131 # StoreUnreadable(future=True): preserved, not shown
46+
_LABEL_BACK = 32134 # labeled exit row ("Back to settings")
4647

4748
# English fallbacks for the dialogs whose ENTIRE content is one localized
4849
# string: localized() degrades to '' on a transient failure, and a blank
@@ -112,9 +113,15 @@ def run(self):
112113

113114
options = [row.label for row in rows]
114115
options.append(self._gui.localized(_LABEL_CLEAR_ALL))
116+
# The select dialog's built-in button says "Cancel" (skin-owned
117+
# wording the Python API cannot change), which misreads for a
118+
# view where leaving is the normal outcome — so the labeled exit
119+
# is a row. Both paths return; the router then reopens the
120+
# settings dialog the manage button closed.
121+
options.append(self._gui.localized(_LABEL_BACK))
115122

116123
choice = self._gui.select(heading, options)
117-
if choice < 0:
124+
if choice < 0 or choice == len(rows) + 1:
118125
return
119126

120127
if choice == len(rows):

tests/unit/test_manage_view.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def test_rows_render_verbatim_signed_milliseconds():
120120
assert any("-2500 ms" in opt for opt in options)
121121

122122

123-
def test_rows_sorted_by_label_with_clear_all_last():
123+
def test_rows_sorted_by_label_with_clear_all_then_back_last():
124124
entries = {HLG: _entry(1), DV: _entry(2), HDR10: _entry(3)}
125125
view, gui, _ = _build(entries)
126126
view.run()
@@ -130,6 +130,9 @@ def test_rows_sorted_by_label_with_clear_all_last():
130130
assert options[1].startswith("HDR10")
131131
assert options[2].startswith("HLG")
132132
assert options[3] == "#32126"
133+
# The labeled exit row: the select dialog's built-in Cancel wording is
134+
# skin-owned, so the view provides its own properly-worded way out.
135+
assert options[4] == "#32134"
133136

134137

135138
def test_malformed_updated_omits_date_without_crashing():
@@ -151,6 +154,19 @@ def test_cancel_exits_without_channel_traffic():
151154
assert len(gui.selects) == 1
152155

153156

157+
def test_back_row_exits_without_channel_traffic():
158+
# The labeled exit (last row, after clear-all) behaves exactly like
159+
# Cancel: no confirmation, no channel traffic, single render.
160+
entries = {DV: _entry(-115)}
161+
gui = FakeGui()
162+
gui.select_answers = [2] # rows=1, clear=1 -> back row is index 2
163+
view, gui, service = _build(entries, gui=gui)
164+
view.run()
165+
assert service.calls == []
166+
assert gui.yesnos == []
167+
assert len(gui.selects) == 1
168+
169+
154170
def test_delete_flow_sends_exact_key_and_re_reads():
155171
entries = {DV: _entry(-115), HDR10: _entry(200)}
156172
gui = FakeGui()
@@ -161,10 +177,10 @@ def test_delete_flow_sends_exact_key_and_re_reads():
161177

162178
assert service.calls == [("delete", DV)]
163179
# The store was re-read after the mutation: two renders, the second with
164-
# one fewer entry row (plus the clear-all row).
180+
# one fewer entry row (plus the clear-all and back rows).
165181
assert len(gui.selects) == 2
166-
assert len(gui.selects[0][1]) == 3 # 2 entries + clear
167-
assert len(gui.selects[1][1]) == 2 # 1 entry + clear
182+
assert len(gui.selects[0][1]) == 4 # 2 entries + clear + back
183+
assert len(gui.selects[1][1]) == 3 # 1 entry + clear + back
168184
assert not any(opt.startswith("Dolby Vision") for opt in gui.selects[1][1])
169185

170186

tests/unit/test_script_router.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,20 @@ def test_unknown_route_falls_back_to_settings():
6969
assert RecordingAddon.instances[0].opened == 1
7070

7171

72-
def test_manage_offsets_routes_to_the_view(monkeypatch):
72+
def test_manage_offsets_runs_the_view_then_returns_to_settings(monkeypatch):
73+
# Record how many openSettings() calls had happened when the view ran:
74+
# the settings dialog must reopen AFTER the view exits (the manage
75+
# button closed it via <close>true</close>), never before — the view
76+
# must not render on top of a pending save-on-close.
7377
calls = []
74-
monkeypatch.setattr(script_router, '_manage_offsets',
75-
lambda: calls.append(True))
78+
monkeypatch.setattr(
79+
script_router, '_manage_offsets',
80+
lambda: calls.append(sum(a.opened for a in RecordingAddon.instances)))
7681

7782
script_router.handle_script_call(['script.py', 'manage_offsets'])
7883

79-
assert calls == [True]
80-
# D13: the view route must NOT bounce through the settings dialog.
81-
assert all(a.opened == 0 for a in RecordingAddon.instances)
84+
assert calls == [0] # view ran, settings still closed
85+
assert sum(a.opened for a in RecordingAddon.instances) == 1
8286

8387

8488
def test_manage_offsets_composition(monkeypatch, tmp_path):

0 commit comments

Comments
 (0)