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