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

A window allowing signing in to a v2 account.

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