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        )
 66
 67        bui.containerwidget(
 68            edit=self._root_widget, cancel_button=self._cancel_button
 69        )
 70
 71        self._update_timer: bui.AppTimer | None = None
 72
 73        # Ask the cloud for a proxy login id.
 74        assert bui.app.plus is not None
 75        bui.app.plus.cloud.send_message_cb(
 76            bacommon.cloud.LoginProxyRequestMessage(),
 77            on_response=bui.WeakCall(self._on_proxy_request_response),
 78        )
 79
 80    def _on_proxy_request_response(
 81        self, response: bacommon.cloud.LoginProxyRequestResponse | Exception
 82    ) -> None:
 83        plus = bui.app.plus
 84        assert plus is not None
 85
 86        # Something went wrong. Show an error message and that's it.
 87        if isinstance(response, Exception):
 88            bui.textwidget(
 89                edit=self._loading_text,
 90                text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
 91                color=(1, 0, 0),
 92            )
 93            return
 94
 95        # Show link(s) the user can use to sign in.
 96        address = plus.get_master_server_address(version=2) + response.url
 97        address_pretty = address.removeprefix('https://')
 98
 99        assert bui.app.classic is not None
100        bui.textwidget(
101            parent=self._root_widget,
102            position=(self._width * 0.5, self._height - 95),
103            size=(0, 0),
104            text=bui.Lstr(
105                resource='accountSettingsWindow.v2LinkInstructionsText'
106            ),
107            color=bui.app.ui_v1.title_color,
108            maxwidth=self._width * 0.9,
109            h_align='center',
110            v_align='center',
111        )
112        button_width = 450
113        if bui.is_browser_likely_available():
114            bui.buttonwidget(
115                parent=self._root_widget,
116                position=(
117                    (self._width * 0.5 - button_width * 0.5),
118                    self._height - 185,
119                ),
120                autoselect=True,
121                size=(button_width, 60),
122                label=bui.Lstr(value=address_pretty),
123                color=(0.55, 0.5, 0.6),
124                textcolor=(0.75, 0.7, 0.8),
125                on_activate_call=lambda: bui.open_url(address),
126            )
127            qroffs = 0.0
128        else:
129            bui.textwidget(
130                parent=self._root_widget,
131                position=(self._width * 0.5 - 200, self._height - 180),
132                size=(button_width - 50, 50),
133                text=bui.Lstr(value=address_pretty),
134                flatness=1.0,
135                maxwidth=self._width,
136                scale=0.75,
137                h_align='center',
138                v_align='center',
139                autoselect=True,
140                on_activate_call=bui.Call(self._copy_link, address_pretty),
141                selectable=True,
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 _copy_link(self, link: str) -> None:
235        if bui.clipboard_is_supported():
236            bui.clipboard_set_text(link)
237            bui.screenmessage(
238                bui.Lstr(resource='copyConfirmText'), color=(0, 1, 0)
239            )
240
241    def _done(self) -> None:
242        # no-op if our underlying widget is dead or on its way out.
243        if not self._root_widget or self._root_widget.transitioning_out:
244            return
245        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        )
 67
 68        bui.containerwidget(
 69            edit=self._root_widget, cancel_button=self._cancel_button
 70        )
 71
 72        self._update_timer: bui.AppTimer | None = None
 73
 74        # Ask the cloud for a proxy login id.
 75        assert bui.app.plus is not None
 76        bui.app.plus.cloud.send_message_cb(
 77            bacommon.cloud.LoginProxyRequestMessage(),
 78            on_response=bui.WeakCall(self._on_proxy_request_response),
 79        )
 80
 81    def _on_proxy_request_response(
 82        self, response: bacommon.cloud.LoginProxyRequestResponse | Exception
 83    ) -> None:
 84        plus = bui.app.plus
 85        assert plus is not None
 86
 87        # Something went wrong. Show an error message and that's it.
 88        if isinstance(response, Exception):
 89            bui.textwidget(
 90                edit=self._loading_text,
 91                text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
 92                color=(1, 0, 0),
 93            )
 94            return
 95
 96        # Show link(s) the user can use to sign in.
 97        address = plus.get_master_server_address(version=2) + response.url
 98        address_pretty = address.removeprefix('https://')
 99
100        assert bui.app.classic is not None
101        bui.textwidget(
102            parent=self._root_widget,
103            position=(self._width * 0.5, self._height - 95),
104            size=(0, 0),
105            text=bui.Lstr(
106                resource='accountSettingsWindow.v2LinkInstructionsText'
107            ),
108            color=bui.app.ui_v1.title_color,
109            maxwidth=self._width * 0.9,
110            h_align='center',
111            v_align='center',
112        )
113        button_width = 450
114        if bui.is_browser_likely_available():
115            bui.buttonwidget(
116                parent=self._root_widget,
117                position=(
118                    (self._width * 0.5 - button_width * 0.5),
119                    self._height - 185,
120                ),
121                autoselect=True,
122                size=(button_width, 60),
123                label=bui.Lstr(value=address_pretty),
124                color=(0.55, 0.5, 0.6),
125                textcolor=(0.75, 0.7, 0.8),
126                on_activate_call=lambda: bui.open_url(address),
127            )
128            qroffs = 0.0
129        else:
130            bui.textwidget(
131                parent=self._root_widget,
132                position=(self._width * 0.5 - 200, self._height - 180),
133                size=(button_width - 50, 50),
134                text=bui.Lstr(value=address_pretty),
135                flatness=1.0,
136                maxwidth=self._width,
137                scale=0.75,
138                h_align='center',
139                v_align='center',
140                autoselect=True,
141                on_activate_call=bui.Call(self._copy_link, address_pretty),
142                selectable=True,
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 _copy_link(self, link: str) -> None:
236        if bui.clipboard_is_supported():
237            bui.clipboard_set_text(link)
238            bui.screenmessage(
239                bui.Lstr(resource='copyConfirmText'), color=(0, 1, 0)
240            )
241
242    def _done(self) -> None:
243        # no-op if our underlying widget is dead or on its way out.
244        if not self._root_widget or self._root_widget.transitioning_out:
245            return
246        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        )
67
68        bui.containerwidget(
69            edit=self._root_widget, cancel_button=self._cancel_button
70        )
71
72        self._update_timer: bui.AppTimer | None = None
73
74        # Ask the cloud for a proxy login id.
75        assert bui.app.plus is not None
76        bui.app.plus.cloud.send_message_cb(
77            bacommon.cloud.LoginProxyRequestMessage(),
78            on_response=bui.WeakCall(self._on_proxy_request_response),
79        )
Inherited Members
bauiv1._uitypes.Window
get_root_widget