Merge pull request #1525 from ogayot/confirmation-overlay

Add helper to create confirmation dialog and use it for Ubuntu Pro
This commit is contained in:
Olivier Gayot 2023-01-05 10:50:54 +01:00 committed by GitHub
commit 69264ad7f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 37 deletions

View File

@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Module that defines the view class for Ubuntu Pro configuration. """ """ Module that defines the view class for Ubuntu Pro configuration. """
import asyncio
import logging import logging
import re import re
from typing import Callable, List from typing import Callable, List
@ -325,6 +326,9 @@ class UbuntuProView(BaseView):
connect_signal(self.upgrade_mode_form, connect_signal(self.upgrade_mode_form,
'cancel', on_upgrade_mode_cancel) 'cancel', on_upgrade_mode_cancel)
# Throwaway tasks
self.tasks: List[asyncio.Task] = []
super().__init__(self.upgrade_yes_no_screen()) super().__init__(self.upgrade_yes_no_screen())
def upgrade_mode_screen(self) -> Widget: def upgrade_mode_screen(self) -> Widget:
@ -644,7 +648,19 @@ class UbuntuProView(BaseView):
network connection, temporary service unavailability, API issue ... network connection, temporary service unavailability, API issue ...
The user is prompted to continue anyway or go back. The user is prompted to continue anyway or go back.
""" """
self.show_stretchy_overlay(ContinueAnywayWidget(self)) question = _("Unable to check your subscription information."
" Do you want to go back or continue anyway?")
async def confirm_continue_anyway() -> None:
confirmed = await self.ask_confirmation(
title=_("Unknown error"), question=question,
cancel_label=_("Back"), confirm_label=_("Continue anyway"))
if confirmed:
subform = self.upgrade_mode_form.with_contract_token_subform
self.controller.done(subform.value["token"])
self.tasks.append(asyncio.create_task(confirm_continue_anyway()))
def show_subscription(self, subscription: UbuntuProSubscription) -> None: def show_subscription(self, subscription: UbuntuProSubscription) -> None:
""" Display a screen with information about the subscription, including """ Display a screen with information about the subscription, including
@ -860,39 +876,3 @@ class HowToRegisterWidget(Stretchy):
def close(self) -> None: def close(self) -> None:
""" Close the overlay. """ """ Close the overlay. """
self.parent.remove_overlay() self.parent.remove_overlay()
class ContinueAnywayWidget(Stretchy):
""" Widget that requests the user if he wants to go back or continue
anyway.
+--------------------- Unknown error ---------------------+
| |
| Unable to check your subscription information. Do you |
| want to go back or continue anyway? |
| |
| [ Back ] |
| [ Continue anyway ] |
+---------------------------------------------------------+
"""
def __init__(self, parent: UbuntuProView) -> None:
""" Initializes the widget by showing two buttons, one to go back and
one to move forward anyway. """
self.parent = parent
back = back_btn(label=_("Back"), on_press=self.back)
cont = done_btn(label=_("Continue anyway"), on_press=self.cont)
widgets = [
Text("Unable to check your subscription information."
" Do you want to go back or continue anyway?"),
Text(""),
button_pile([back, cont]),
]
super().__init__("Unknown error", widgets, 0, 2)
def back(self, sender) -> None:
""" Close the overlay. """
self.parent.remove_overlay()
def cont(self, sender) -> None:
""" Move on to the next screen. """
subform = self.parent.upgrade_mode_form.with_contract_token_subform
self.parent.controller.done(subform.value["token"])

View File

@ -0,0 +1,65 @@
# Copyright 2023 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Callable
import urwid
from subiquitycore.ui.buttons import (
back_btn,
done_btn,
)
from subiquitycore.ui.utils import button_pile
from subiquitycore.ui.stretchy import Stretchy
class ConfirmationOverlay(Stretchy):
""" An overlay widget that asks the user to confirm or cancel an action.
"""
def __init__(self, title: str, question: str,
confirm_label: str, cancel_label: str,
on_confirm: Callable[[], None],
on_cancel: Callable[[], None]) -> None:
self.on_cancel_cb = on_cancel
self.on_confirm_cb = on_confirm
self.choice_made = False
widgets = [
urwid.Text(question),
urwid.Text(""),
button_pile([
back_btn(
label=cancel_label, on_press=lambda u: self.on_cancel()),
done_btn(
label=confirm_label, on_press=lambda u: self.on_confirm()),
]),
]
super().__init__(title, widgets, 0, 2)
def on_cancel(self) -> None:
self.choice_made = True
self.on_cancel_cb()
def on_confirm(self) -> None:
self.choice_made = True
self.on_confirm_cb()
def closed(self):
if self.choice_made:
return
# The caller should be careful not to close the overlay again.
self.on_cancel_cb()

View File

@ -18,6 +18,7 @@
Contains some default key navigations Contains some default key navigations
""" """
import asyncio
import logging import logging
from urwid import ( from urwid import (
@ -31,6 +32,8 @@ from subiquitycore.ui.container import (
Pile, Pile,
WidgetWrap, WidgetWrap,
) )
from subiquitycore.ui.confirmation import ConfirmationOverlay
from subiquitycore.ui.stretchy import StretchyOverlay from subiquitycore.ui.stretchy import StretchyOverlay
from subiquitycore.ui.utils import disabled, undisabled from subiquitycore.ui.utils import disabled, undisabled
@ -82,6 +85,37 @@ class BaseView(WidgetWrap):
stretchy.opened() stretchy.opened()
self._w = StretchyOverlay(disabled(self._w), stretchy) self._w = StretchyOverlay(disabled(self._w), stretchy)
async def ask_confirmation(self, title: str, question: str,
confirm_label: str, cancel_label: str) -> bool:
""" Open a confirmation dialog using a strechy overlay.
If the user selects the "yes" button, the function returns True.
If the user selects the "no" button or closes the dialog, the function
returns False.
"""
confirm_queue = asyncio.Queue(maxsize=1)
def on_confirm():
confirm_queue.put_nowait(True)
def on_cancel():
confirm_queue.put_nowait(False)
stretchy = ConfirmationOverlay(title=title, question=question,
confirm_label=confirm_label,
cancel_label=cancel_label,
on_confirm=on_confirm,
on_cancel=on_cancel)
self.show_stretchy_overlay(stretchy)
confirmed = await confirm_queue.get()
# The callback might have been called as the result of the overlay
# getting closed (when ESC is pressed). Therefore, the overlay may or
# may not still be opened.
self.remove_overlay(stretchy, not_found_ok=True)
return confirmed
def remove_overlay(self, stretchy=None, def remove_overlay(self, stretchy=None,
*, not_found_ok=False) -> None: *, not_found_ok=False) -> None:
""" Remove (frontmost) overlay from the view. """ """ Remove (frontmost) overlay from the view. """