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