bastd.ui.account.v2proxy

V2 account ui bits.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""V2 account ui bits."""
  4
  5from __future__ import annotations
  6
  7import logging
  8from typing import TYPE_CHECKING
  9
 10import ba
 11import ba.internal
 12
 13from efro.error import CommunicationError
 14import bacommon.cloud
 15
 16if TYPE_CHECKING:
 17    pass
 18
 19STATUS_CHECK_INTERVAL_SECONDS = 2.0
 20
 21
 22class V2ProxySignInWindow(ba.Window):
 23    """A window allowing signing in to a v2 account."""
 24
 25    def __init__(self, origin_widget: ba.Widget):
 26        self._width = 600
 27        self._height = 550
 28        self._proxyid: str | None = None
 29        self._proxykey: str | None = None
 30
 31        uiscale = ba.app.ui.uiscale
 32        super().__init__(
 33            root_widget=ba.containerwidget(
 34                size=(self._width, self._height),
 35                transition='in_scale',
 36                scale_origin_stack_offset=(
 37                    origin_widget.get_screen_space_center()
 38                ),
 39                scale=(
 40                    1.25
 41                    if uiscale is ba.UIScale.SMALL
 42                    else 1.05
 43                    if uiscale is ba.UIScale.MEDIUM
 44                    else 0.9
 45                ),
 46            )
 47        )
 48
 49        self._loading_text = ba.textwidget(
 50            parent=self._root_widget,
 51            position=(self._width * 0.5, self._height * 0.5),
 52            h_align='center',
 53            v_align='center',
 54            size=(0, 0),
 55            maxwidth=0.9 * self._width,
 56            text=ba.Lstr(
 57                value='${A}...',
 58                subs=[('${A}', ba.Lstr(resource='loadingText'))],
 59            ),
 60        )
 61
 62        self._cancel_button = ba.buttonwidget(
 63            parent=self._root_widget,
 64            position=(30, self._height - 65),
 65            size=(130, 50),
 66            scale=0.8,
 67            label=ba.Lstr(resource='cancelText'),
 68            on_activate_call=self._done,
 69            autoselect=True,
 70            color=(0.55, 0.5, 0.6),
 71            textcolor=(0.75, 0.7, 0.8),
 72        )
 73
 74        if bool(False):
 75            ba.containerwidget(
 76                edit=self._root_widget, cancel_button=self._cancel_button
 77            )
 78
 79        self._update_timer: ba.Timer | None = None
 80
 81        # Ask the cloud for a proxy login id.
 82        ba.app.cloud.send_message_cb(
 83            bacommon.cloud.LoginProxyRequestMessage(),
 84            on_response=ba.WeakCall(self._on_proxy_request_response),
 85        )
 86
 87    def _on_proxy_request_response(
 88        self, response: bacommon.cloud.LoginProxyRequestResponse | Exception
 89    ) -> None:
 90        from ba.internal import is_browser_likely_available
 91
 92        # Something went wrong. Show an error message and that's it.
 93        if isinstance(response, Exception):
 94            ba.textwidget(
 95                edit=self._loading_text,
 96                text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
 97                color=(1, 0, 0),
 98            )
 99            return
100
101        # Show link(s) the user can use to sign in.
102        address = (
103            ba.internal.get_master_server_address(version=2) + response.url
104        )
105        address_pretty = address.removeprefix('https://')
106
107        ba.textwidget(
108            parent=self._root_widget,
109            position=(self._width * 0.5, self._height - 95),
110            size=(0, 0),
111            text=ba.Lstr(
112                resource='accountSettingsWindow.v2LinkInstructionsText'
113            ),
114            color=ba.app.ui.title_color,
115            maxwidth=self._width * 0.9,
116            h_align='center',
117            v_align='center',
118        )
119        button_width = 450
120        if is_browser_likely_available():
121            ba.buttonwidget(
122                parent=self._root_widget,
123                position=(
124                    (self._width * 0.5 - button_width * 0.5),
125                    self._height - 185,
126                ),
127                autoselect=True,
128                size=(button_width, 60),
129                label=ba.Lstr(value=address_pretty),
130                color=(0.55, 0.5, 0.6),
131                textcolor=(0.75, 0.7, 0.8),
132                on_activate_call=lambda: ba.open_url(address),
133            )
134            qroffs = 0.0
135        else:
136            ba.textwidget(
137                parent=self._root_widget,
138                position=(self._width * 0.5, self._height - 145),
139                size=(0, 0),
140                text=ba.Lstr(value=address_pretty),
141                flatness=1.0,
142                maxwidth=self._width,
143                scale=0.75,
144                h_align='center',
145                v_align='center',
146            )
147            qroffs = 20.0
148
149        qr_size = 270
150        ba.imagewidget(
151            parent=self._root_widget,
152            position=(
153                self._width * 0.5 - qr_size * 0.5,
154                self._height * 0.36 + qroffs - qr_size * 0.5,
155            ),
156            size=(qr_size, qr_size),
157            texture=ba.internal.get_qrcode_texture(address),
158        )
159
160        # Start querying for results.
161        self._proxyid = response.proxyid
162        self._proxykey = response.proxykey
163        ba.timer(
164            STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)
165        )
166
167    def _ask_for_status(self) -> None:
168        assert self._proxyid is not None
169        assert self._proxykey is not None
170        ba.app.cloud.send_message_cb(
171            bacommon.cloud.LoginProxyStateQueryMessage(
172                proxyid=self._proxyid, proxykey=self._proxykey
173            ),
174            on_response=ba.WeakCall(self._got_status),
175        )
176
177    def _got_status(
178        self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception
179    ) -> None:
180
181        # For now, if anything goes wrong on the server-side, just abort
182        # with a vague error message. Can be more verbose later if need be.
183        if (
184            isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
185            and response.state is response.State.FAIL
186        ):
187            ba.playsound(ba.getsound('error'))
188            ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
189            self._done()
190            return
191
192        # If we got a token, set ourself as signed in. Hooray!
193        if (
194            isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
195            and response.state is response.State.SUCCESS
196        ):
197            assert response.credentials is not None
198            ba.app.accounts_v2.set_primary_credentials(response.credentials)
199
200            # As a courtesy, tell the server we're done with this proxy
201            # so it can clean up (not a huge deal if this fails)
202            assert self._proxyid is not None
203            try:
204                ba.app.cloud.send_message_cb(
205                    bacommon.cloud.LoginProxyCompleteMessage(
206                        proxyid=self._proxyid
207                    ),
208                    on_response=ba.WeakCall(self._proxy_complete_response),
209                )
210            except CommunicationError:
211                pass
212            except Exception:
213                logging.warning(
214                    'Unexpected error sending login-proxy-complete message',
215                    exc_info=True,
216                )
217
218            self._done()
219            return
220
221        # If we're still waiting, ask again soon.
222        if (
223            isinstance(response, Exception)
224            or response.state is response.State.WAITING
225        ):
226            ba.timer(
227                STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)
228            )
229
230    def _proxy_complete_response(self, response: None | Exception) -> None:
231        del response  # Not used.
232        # We could do something smart like retry on exceptions here, but
233        # this isn't critical so we'll just let anything slide.
234
235    def _done(self) -> None:
236        ba.containerwidget(edit=self._root_widget, transition='out_scale')
class V2ProxySignInWindow(ba.ui.Window):
 23class V2ProxySignInWindow(ba.Window):
 24    """A window allowing signing in to a v2 account."""
 25
 26    def __init__(self, origin_widget: ba.Widget):
 27        self._width = 600
 28        self._height = 550
 29        self._proxyid: str | None = None
 30        self._proxykey: str | None = None
 31
 32        uiscale = ba.app.ui.uiscale
 33        super().__init__(
 34            root_widget=ba.containerwidget(
 35                size=(self._width, self._height),
 36                transition='in_scale',
 37                scale_origin_stack_offset=(
 38                    origin_widget.get_screen_space_center()
 39                ),
 40                scale=(
 41                    1.25
 42                    if uiscale is ba.UIScale.SMALL
 43                    else 1.05
 44                    if uiscale is ba.UIScale.MEDIUM
 45                    else 0.9
 46                ),
 47            )
 48        )
 49
 50        self._loading_text = ba.textwidget(
 51            parent=self._root_widget,
 52            position=(self._width * 0.5, self._height * 0.5),
 53            h_align='center',
 54            v_align='center',
 55            size=(0, 0),
 56            maxwidth=0.9 * self._width,
 57            text=ba.Lstr(
 58                value='${A}...',
 59                subs=[('${A}', ba.Lstr(resource='loadingText'))],
 60            ),
 61        )
 62
 63        self._cancel_button = ba.buttonwidget(
 64            parent=self._root_widget,
 65            position=(30, self._height - 65),
 66            size=(130, 50),
 67            scale=0.8,
 68            label=ba.Lstr(resource='cancelText'),
 69            on_activate_call=self._done,
 70            autoselect=True,
 71            color=(0.55, 0.5, 0.6),
 72            textcolor=(0.75, 0.7, 0.8),
 73        )
 74
 75        if bool(False):
 76            ba.containerwidget(
 77                edit=self._root_widget, cancel_button=self._cancel_button
 78            )
 79
 80        self._update_timer: ba.Timer | None = None
 81
 82        # Ask the cloud for a proxy login id.
 83        ba.app.cloud.send_message_cb(
 84            bacommon.cloud.LoginProxyRequestMessage(),
 85            on_response=ba.WeakCall(self._on_proxy_request_response),
 86        )
 87
 88    def _on_proxy_request_response(
 89        self, response: bacommon.cloud.LoginProxyRequestResponse | Exception
 90    ) -> None:
 91        from ba.internal import is_browser_likely_available
 92
 93        # Something went wrong. Show an error message and that's it.
 94        if isinstance(response, Exception):
 95            ba.textwidget(
 96                edit=self._loading_text,
 97                text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
 98                color=(1, 0, 0),
 99            )
100            return
101
102        # Show link(s) the user can use to sign in.
103        address = (
104            ba.internal.get_master_server_address(version=2) + response.url
105        )
106        address_pretty = address.removeprefix('https://')
107
108        ba.textwidget(
109            parent=self._root_widget,
110            position=(self._width * 0.5, self._height - 95),
111            size=(0, 0),
112            text=ba.Lstr(
113                resource='accountSettingsWindow.v2LinkInstructionsText'
114            ),
115            color=ba.app.ui.title_color,
116            maxwidth=self._width * 0.9,
117            h_align='center',
118            v_align='center',
119        )
120        button_width = 450
121        if is_browser_likely_available():
122            ba.buttonwidget(
123                parent=self._root_widget,
124                position=(
125                    (self._width * 0.5 - button_width * 0.5),
126                    self._height - 185,
127                ),
128                autoselect=True,
129                size=(button_width, 60),
130                label=ba.Lstr(value=address_pretty),
131                color=(0.55, 0.5, 0.6),
132                textcolor=(0.75, 0.7, 0.8),
133                on_activate_call=lambda: ba.open_url(address),
134            )
135            qroffs = 0.0
136        else:
137            ba.textwidget(
138                parent=self._root_widget,
139                position=(self._width * 0.5, self._height - 145),
140                size=(0, 0),
141                text=ba.Lstr(value=address_pretty),
142                flatness=1.0,
143                maxwidth=self._width,
144                scale=0.75,
145                h_align='center',
146                v_align='center',
147            )
148            qroffs = 20.0
149
150        qr_size = 270
151        ba.imagewidget(
152            parent=self._root_widget,
153            position=(
154                self._width * 0.5 - qr_size * 0.5,
155                self._height * 0.36 + qroffs - qr_size * 0.5,
156            ),
157            size=(qr_size, qr_size),
158            texture=ba.internal.get_qrcode_texture(address),
159        )
160
161        # Start querying for results.
162        self._proxyid = response.proxyid
163        self._proxykey = response.proxykey
164        ba.timer(
165            STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)
166        )
167
168    def _ask_for_status(self) -> None:
169        assert self._proxyid is not None
170        assert self._proxykey is not None
171        ba.app.cloud.send_message_cb(
172            bacommon.cloud.LoginProxyStateQueryMessage(
173                proxyid=self._proxyid, proxykey=self._proxykey
174            ),
175            on_response=ba.WeakCall(self._got_status),
176        )
177
178    def _got_status(
179        self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception
180    ) -> None:
181
182        # For now, if anything goes wrong on the server-side, just abort
183        # with a vague error message. Can be more verbose later if need be.
184        if (
185            isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
186            and response.state is response.State.FAIL
187        ):
188            ba.playsound(ba.getsound('error'))
189            ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
190            self._done()
191            return
192
193        # If we got a token, set ourself as signed in. Hooray!
194        if (
195            isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
196            and response.state is response.State.SUCCESS
197        ):
198            assert response.credentials is not None
199            ba.app.accounts_v2.set_primary_credentials(response.credentials)
200
201            # As a courtesy, tell the server we're done with this proxy
202            # so it can clean up (not a huge deal if this fails)
203            assert self._proxyid is not None
204            try:
205                ba.app.cloud.send_message_cb(
206                    bacommon.cloud.LoginProxyCompleteMessage(
207                        proxyid=self._proxyid
208                    ),
209                    on_response=ba.WeakCall(self._proxy_complete_response),
210                )
211            except CommunicationError:
212                pass
213            except Exception:
214                logging.warning(
215                    'Unexpected error sending login-proxy-complete message',
216                    exc_info=True,
217                )
218
219            self._done()
220            return
221
222        # If we're still waiting, ask again soon.
223        if (
224            isinstance(response, Exception)
225            or response.state is response.State.WAITING
226        ):
227            ba.timer(
228                STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)
229            )
230
231    def _proxy_complete_response(self, response: None | Exception) -> None:
232        del response  # Not used.
233        # We could do something smart like retry on exceptions here, but
234        # this isn't critical so we'll just let anything slide.
235
236    def _done(self) -> None:
237        ba.containerwidget(edit=self._root_widget, transition='out_scale')

A window allowing signing in to a v2 account.

V2ProxySignInWindow(origin_widget: _ba.Widget)
26    def __init__(self, origin_widget: ba.Widget):
27        self._width = 600
28        self._height = 550
29        self._proxyid: str | None = None
30        self._proxykey: str | None = None
31
32        uiscale = ba.app.ui.uiscale
33        super().__init__(
34            root_widget=ba.containerwidget(
35                size=(self._width, self._height),
36                transition='in_scale',
37                scale_origin_stack_offset=(
38                    origin_widget.get_screen_space_center()
39                ),
40                scale=(
41                    1.25
42                    if uiscale is ba.UIScale.SMALL
43                    else 1.05
44                    if uiscale is ba.UIScale.MEDIUM
45                    else 0.9
46                ),
47            )
48        )
49
50        self._loading_text = ba.textwidget(
51            parent=self._root_widget,
52            position=(self._width * 0.5, self._height * 0.5),
53            h_align='center',
54            v_align='center',
55            size=(0, 0),
56            maxwidth=0.9 * self._width,
57            text=ba.Lstr(
58                value='${A}...',
59                subs=[('${A}', ba.Lstr(resource='loadingText'))],
60            ),
61        )
62
63        self._cancel_button = ba.buttonwidget(
64            parent=self._root_widget,
65            position=(30, self._height - 65),
66            size=(130, 50),
67            scale=0.8,
68            label=ba.Lstr(resource='cancelText'),
69            on_activate_call=self._done,
70            autoselect=True,
71            color=(0.55, 0.5, 0.6),
72            textcolor=(0.75, 0.7, 0.8),
73        )
74
75        if bool(False):
76            ba.containerwidget(
77                edit=self._root_widget, cancel_button=self._cancel_button
78            )
79
80        self._update_timer: ba.Timer | None = None
81
82        # Ask the cloud for a proxy login id.
83        ba.app.cloud.send_message_cb(
84            bacommon.cloud.LoginProxyRequestMessage(),
85            on_response=ba.WeakCall(self._on_proxy_request_response),
86        )
Inherited Members
ba.ui.Window
get_root_widget