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 time 8import logging 9 10from efro.error import CommunicationError 11import bacommon.cloud 12import bauiv1 as bui 13 14STATUS_CHECK_INTERVAL_SECONDS = 2.0 15 16 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 self._overlay_web_browser_open = False 26 27 assert bui.app.classic is not None 28 uiscale = bui.app.ui_v1.uiscale 29 super().__init__( 30 root_widget=bui.containerwidget( 31 size=(self._width, self._height), 32 transition='in_scale', 33 scale_origin_stack_offset=( 34 origin_widget.get_screen_space_center() 35 ), 36 scale=( 37 1.16 38 if uiscale is bui.UIScale.SMALL 39 else 1.0 if uiscale is bui.UIScale.MEDIUM else 0.9 40 ), 41 ) 42 ) 43 44 self._loading_spinner = bui.spinnerwidget( 45 parent=self._root_widget, 46 position=(self._width * 0.5, self._height * 0.5), 47 size=60, 48 style='bomb', 49 ) 50 self._state_text = bui.textwidget( 51 parent=self._root_widget, 52 position=(self._width * 0.5, self._height * 0.6), 53 h_align='center', 54 v_align='center', 55 size=(0, 0), 56 scale=1.4, 57 maxwidth=0.9 * self._width, 58 # text=bui.Lstr( 59 # value='${A}...', 60 # subs=[('${A}', bui.Lstr(resource='loadingText'))], 61 # ), 62 text='', 63 color=(1, 1, 1), 64 ) 65 self._sub_state_text = bui.textwidget( 66 parent=self._root_widget, 67 position=(self._width * 0.5, self._height * 0.55), 68 h_align='center', 69 v_align='top', 70 scale=0.85, 71 size=(0, 0), 72 maxwidth=0.9 * self._width, 73 text='', 74 ) 75 self._sub_state_text2 = bui.textwidget( 76 parent=self._root_widget, 77 position=(self._width * 0.1, self._height * 0.3), 78 h_align='left', 79 v_align='top', 80 scale=0.7, 81 size=(0, 0), 82 maxwidth=0.9 * self._width, 83 text='', 84 ) 85 86 self._cancel_button = bui.buttonwidget( 87 parent=self._root_widget, 88 position=(30, self._height - 65), 89 size=(130, 50), 90 scale=0.8, 91 label=bui.Lstr(resource='cancelText'), 92 on_activate_call=self._done, 93 autoselect=True, 94 ) 95 96 bui.containerwidget( 97 edit=self._root_widget, cancel_button=self._cancel_button 98 ) 99 100 self._message_in_flight = False 101 self._complete = False 102 self._connection_wait_timeout_time = time.monotonic() + 10.0 103 104 self._update_timer = bui.AppTimer( 105 0.371, bui.WeakCall(self._update), repeat=True 106 ) 107 bui.pushcall(bui.WeakCall(self._update)) 108 109 def _update(self) -> None: 110 111 plus = bui.app.plus 112 assert plus is not None 113 114 # If we've opened an overlay web browser, all we do is kill 115 # ourselves when it closes. 116 if self._overlay_web_browser_open: 117 if not bui.overlay_web_browser_is_open(): 118 self._overlay_web_browser_open = False 119 self._done() 120 return 121 122 if self._message_in_flight or self._complete: 123 return 124 125 now = time.monotonic() 126 127 # Spin for a moment if it looks like we have no server 128 # connection; it might still be getting on its feet. 129 if ( 130 not plus.cloud.connected 131 and now < self._connection_wait_timeout_time 132 ): 133 return 134 135 plus.cloud.send_message_cb( 136 bacommon.cloud.LoginProxyRequestMessage(), 137 on_response=bui.WeakCall(self._on_proxy_request_response), 138 ) 139 self._message_in_flight = True 140 141 def _get_server_address(self) -> str: 142 plus = bui.app.plus 143 assert plus is not None 144 out = plus.get_master_server_address(version=2) 145 assert isinstance(out, str) 146 return out 147 148 def _set_error_state(self, error_location: str) -> None: 149 msaddress = self._get_server_address() 150 addr = msaddress.removeprefix('https://') 151 bui.spinnerwidget(edit=self._loading_spinner, visible=False) 152 bui.textwidget( 153 edit=self._state_text, 154 text=f'Unable to connect to {addr}.', 155 color=(1, 0, 0), 156 ) 157 support_email = 'support@froemling.net' 158 bui.textwidget( 159 edit=self._sub_state_text, 160 text=( 161 f'Usually this means your internet is down.\n' 162 f'Please contact {support_email} if this is not the case.' 163 ), 164 color=(1, 0, 0), 165 ) 166 bui.textwidget( 167 edit=self._sub_state_text2, 168 text=( 169 f'debug-info:\n' 170 f' error-location: {error_location}\n' 171 f' connectivity: {bui.app.net.connectivity_state}\n' 172 f' transport: {bui.app.net.transport_state}' 173 ), 174 color=(0.8, 0.2, 0.3), 175 flatness=1.0, 176 shadow=0.0, 177 ) 178 179 def _on_proxy_request_response( 180 self, response: bacommon.cloud.LoginProxyRequestResponse | Exception 181 ) -> None: 182 plus = bui.app.plus 183 assert plus is not None 184 185 if not self._message_in_flight: 186 logging.warning( 187 'v2proxy got _on_proxy_request_response' 188 ' without _message_in_flight set; unexpected.' 189 ) 190 self._message_in_flight = False 191 192 # Something went wrong. Show an error message and schedule retry. 193 if isinstance(response, Exception): 194 self._set_error_state(f'response exc ({type(response).__name__})') 195 self._complete = True 196 return 197 198 self._complete = True 199 200 # Clear out stuff we use to show progress/errors. 201 self._loading_spinner.delete() 202 self._sub_state_text.delete() 203 self._sub_state_text2.delete() 204 205 # If we have overlay-web-browser functionality, bring up 206 # an inline sign-in dialog. 207 if bui.overlay_web_browser_is_supported(): 208 bui.textwidget( 209 edit=self._state_text, 210 text=bui.Lstr(resource='pleaseWaitText'), 211 ) 212 self._show_overlay_sign_in_ui(response) 213 self._overlay_web_browser_open = True 214 else: 215 # Otherwise just show link-button/qr-code for the sign-in. 216 self._state_text.delete() 217 self._show_standard_sign_in_ui(response) 218 219 # In either case, start querying for results now. 220 self._proxyid = response.proxyid 221 self._proxykey = response.proxykey 222 bui.apptimer( 223 STATUS_CHECK_INTERVAL_SECONDS, bui.WeakCall(self._ask_for_status) 224 ) 225 226 def _show_overlay_sign_in_ui( 227 self, response: bacommon.cloud.LoginProxyRequestResponse 228 ) -> None: 229 msaddress = self._get_server_address() 230 address = msaddress + response.url_overlay 231 bui.overlay_web_browser_open_url(address) 232 233 def _show_standard_sign_in_ui( 234 self, response: bacommon.cloud.LoginProxyRequestResponse 235 ) -> None: 236 msaddress = self._get_server_address() 237 238 # Show link(s) the user can use to sign in. 239 address = msaddress + response.url 240 address_pretty = address.removeprefix('https://') 241 242 assert bui.app.classic is not None 243 bui.textwidget( 244 parent=self._root_widget, 245 position=(self._width * 0.5, self._height - 95), 246 size=(0, 0), 247 text=bui.Lstr( 248 resource='accountSettingsWindow.v2LinkInstructionsText' 249 ), 250 color=bui.app.ui_v1.title_color, 251 maxwidth=self._width * 0.9, 252 h_align='center', 253 v_align='center', 254 ) 255 button_width = 450 256 if bui.is_browser_likely_available(): 257 bui.buttonwidget( 258 parent=self._root_widget, 259 position=( 260 (self._width * 0.5 - button_width * 0.5), 261 self._height - 185, 262 ), 263 autoselect=True, 264 size=(button_width, 60), 265 label=bui.Lstr(value=address_pretty), 266 color=(0.55, 0.5, 0.6), 267 textcolor=(0.75, 0.7, 0.8), 268 on_activate_call=lambda: bui.open_url(address), 269 ) 270 qroffs = 0.0 271 else: 272 bui.textwidget( 273 parent=self._root_widget, 274 position=(self._width * 0.5 - 200, self._height - 180), 275 size=(button_width - 50, 50), 276 text=bui.Lstr(value=address_pretty), 277 flatness=1.0, 278 maxwidth=self._width, 279 scale=0.75, 280 h_align='center', 281 v_align='center', 282 autoselect=True, 283 on_activate_call=bui.Call(self._copy_link, address_pretty), 284 selectable=True, 285 ) 286 qroffs = 20.0 287 288 qr_size = 270 289 bui.imagewidget( 290 parent=self._root_widget, 291 position=( 292 self._width * 0.5 - qr_size * 0.5, 293 self._height * 0.36 + qroffs - qr_size * 0.5, 294 ), 295 size=(qr_size, qr_size), 296 texture=bui.get_qrcode_texture(address), 297 ) 298 299 def _ask_for_status(self) -> None: 300 assert self._proxyid is not None 301 assert self._proxykey is not None 302 assert bui.app.plus is not None 303 bui.app.plus.cloud.send_message_cb( 304 bacommon.cloud.LoginProxyStateQueryMessage( 305 proxyid=self._proxyid, proxykey=self._proxykey 306 ), 307 on_response=bui.WeakCall(self._got_status), 308 ) 309 310 def _got_status( 311 self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception 312 ) -> None: 313 if ( 314 isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) 315 and response.state is response.State.FAIL 316 ): 317 logging.info('LoginProxy failed.') 318 bui.getsound('error').play() 319 bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) 320 self._done() 321 return 322 323 # If we got a token, set ourself as signed in. Hooray! 324 if ( 325 isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) 326 and response.state is response.State.SUCCESS 327 ): 328 plus = bui.app.plus 329 assert plus is not None 330 assert response.credentials is not None 331 plus.accounts.set_primary_credentials(response.credentials) 332 333 # As a courtesy, tell the server we're done with this proxy 334 # so it can clean up (not a huge deal if this fails) 335 assert self._proxyid is not None 336 try: 337 plus.cloud.send_message_cb( 338 bacommon.cloud.LoginProxyCompleteMessage( 339 proxyid=self._proxyid 340 ), 341 on_response=bui.WeakCall(self._proxy_complete_response), 342 ) 343 except CommunicationError: 344 pass 345 except Exception: 346 logging.warning( 347 'Unexpected error sending login-proxy-complete message', 348 exc_info=True, 349 ) 350 351 self._done() 352 return 353 354 # If we're still waiting, ask again soon. 355 if ( 356 isinstance(response, Exception) 357 or response.state is response.State.WAITING 358 ): 359 bui.apptimer( 360 STATUS_CHECK_INTERVAL_SECONDS, 361 bui.WeakCall(self._ask_for_status), 362 ) 363 364 def _proxy_complete_response(self, response: None | Exception) -> None: 365 del response # Not used. 366 # We could do something smart like retry on exceptions here, but 367 # this isn't critical so we'll just let anything slide. 368 369 def _copy_link(self, link: str) -> None: 370 if bui.clipboard_is_supported(): 371 bui.clipboard_set_text(link) 372 bui.screenmessage( 373 bui.Lstr(resource='copyConfirmText'), color=(0, 1, 0) 374 ) 375 376 def _done(self) -> None: 377 # no-op if our underlying widget is dead or on its way out. 378 if not self._root_widget or self._root_widget.transitioning_out: 379 return 380 381 # If we've got an inline browser up, tell it to close. 382 if self._overlay_web_browser_open: 383 bui.overlay_web_browser_close() 384 385 bui.containerwidget(edit=self._root_widget, transition='out_scale')
STATUS_CHECK_INTERVAL_SECONDS =
2.0
class
V2ProxySignInWindow(bauiv1._uitypes.Window):
18class V2ProxySignInWindow(bui.Window): 19 """A window allowing signing in to a v2 account.""" 20 21 def __init__(self, origin_widget: bui.Widget): 22 self._width = 600 23 self._height = 550 24 self._proxyid: str | None = None 25 self._proxykey: str | None = None 26 self._overlay_web_browser_open = False 27 28 assert bui.app.classic is not None 29 uiscale = bui.app.ui_v1.uiscale 30 super().__init__( 31 root_widget=bui.containerwidget( 32 size=(self._width, self._height), 33 transition='in_scale', 34 scale_origin_stack_offset=( 35 origin_widget.get_screen_space_center() 36 ), 37 scale=( 38 1.16 39 if uiscale is bui.UIScale.SMALL 40 else 1.0 if uiscale is bui.UIScale.MEDIUM else 0.9 41 ), 42 ) 43 ) 44 45 self._loading_spinner = bui.spinnerwidget( 46 parent=self._root_widget, 47 position=(self._width * 0.5, self._height * 0.5), 48 size=60, 49 style='bomb', 50 ) 51 self._state_text = bui.textwidget( 52 parent=self._root_widget, 53 position=(self._width * 0.5, self._height * 0.6), 54 h_align='center', 55 v_align='center', 56 size=(0, 0), 57 scale=1.4, 58 maxwidth=0.9 * self._width, 59 # text=bui.Lstr( 60 # value='${A}...', 61 # subs=[('${A}', bui.Lstr(resource='loadingText'))], 62 # ), 63 text='', 64 color=(1, 1, 1), 65 ) 66 self._sub_state_text = bui.textwidget( 67 parent=self._root_widget, 68 position=(self._width * 0.5, self._height * 0.55), 69 h_align='center', 70 v_align='top', 71 scale=0.85, 72 size=(0, 0), 73 maxwidth=0.9 * self._width, 74 text='', 75 ) 76 self._sub_state_text2 = bui.textwidget( 77 parent=self._root_widget, 78 position=(self._width * 0.1, self._height * 0.3), 79 h_align='left', 80 v_align='top', 81 scale=0.7, 82 size=(0, 0), 83 maxwidth=0.9 * self._width, 84 text='', 85 ) 86 87 self._cancel_button = bui.buttonwidget( 88 parent=self._root_widget, 89 position=(30, self._height - 65), 90 size=(130, 50), 91 scale=0.8, 92 label=bui.Lstr(resource='cancelText'), 93 on_activate_call=self._done, 94 autoselect=True, 95 ) 96 97 bui.containerwidget( 98 edit=self._root_widget, cancel_button=self._cancel_button 99 ) 100 101 self._message_in_flight = False 102 self._complete = False 103 self._connection_wait_timeout_time = time.monotonic() + 10.0 104 105 self._update_timer = bui.AppTimer( 106 0.371, bui.WeakCall(self._update), repeat=True 107 ) 108 bui.pushcall(bui.WeakCall(self._update)) 109 110 def _update(self) -> None: 111 112 plus = bui.app.plus 113 assert plus is not None 114 115 # If we've opened an overlay web browser, all we do is kill 116 # ourselves when it closes. 117 if self._overlay_web_browser_open: 118 if not bui.overlay_web_browser_is_open(): 119 self._overlay_web_browser_open = False 120 self._done() 121 return 122 123 if self._message_in_flight or self._complete: 124 return 125 126 now = time.monotonic() 127 128 # Spin for a moment if it looks like we have no server 129 # connection; it might still be getting on its feet. 130 if ( 131 not plus.cloud.connected 132 and now < self._connection_wait_timeout_time 133 ): 134 return 135 136 plus.cloud.send_message_cb( 137 bacommon.cloud.LoginProxyRequestMessage(), 138 on_response=bui.WeakCall(self._on_proxy_request_response), 139 ) 140 self._message_in_flight = True 141 142 def _get_server_address(self) -> str: 143 plus = bui.app.plus 144 assert plus is not None 145 out = plus.get_master_server_address(version=2) 146 assert isinstance(out, str) 147 return out 148 149 def _set_error_state(self, error_location: str) -> None: 150 msaddress = self._get_server_address() 151 addr = msaddress.removeprefix('https://') 152 bui.spinnerwidget(edit=self._loading_spinner, visible=False) 153 bui.textwidget( 154 edit=self._state_text, 155 text=f'Unable to connect to {addr}.', 156 color=(1, 0, 0), 157 ) 158 support_email = 'support@froemling.net' 159 bui.textwidget( 160 edit=self._sub_state_text, 161 text=( 162 f'Usually this means your internet is down.\n' 163 f'Please contact {support_email} if this is not the case.' 164 ), 165 color=(1, 0, 0), 166 ) 167 bui.textwidget( 168 edit=self._sub_state_text2, 169 text=( 170 f'debug-info:\n' 171 f' error-location: {error_location}\n' 172 f' connectivity: {bui.app.net.connectivity_state}\n' 173 f' transport: {bui.app.net.transport_state}' 174 ), 175 color=(0.8, 0.2, 0.3), 176 flatness=1.0, 177 shadow=0.0, 178 ) 179 180 def _on_proxy_request_response( 181 self, response: bacommon.cloud.LoginProxyRequestResponse | Exception 182 ) -> None: 183 plus = bui.app.plus 184 assert plus is not None 185 186 if not self._message_in_flight: 187 logging.warning( 188 'v2proxy got _on_proxy_request_response' 189 ' without _message_in_flight set; unexpected.' 190 ) 191 self._message_in_flight = False 192 193 # Something went wrong. Show an error message and schedule retry. 194 if isinstance(response, Exception): 195 self._set_error_state(f'response exc ({type(response).__name__})') 196 self._complete = True 197 return 198 199 self._complete = True 200 201 # Clear out stuff we use to show progress/errors. 202 self._loading_spinner.delete() 203 self._sub_state_text.delete() 204 self._sub_state_text2.delete() 205 206 # If we have overlay-web-browser functionality, bring up 207 # an inline sign-in dialog. 208 if bui.overlay_web_browser_is_supported(): 209 bui.textwidget( 210 edit=self._state_text, 211 text=bui.Lstr(resource='pleaseWaitText'), 212 ) 213 self._show_overlay_sign_in_ui(response) 214 self._overlay_web_browser_open = True 215 else: 216 # Otherwise just show link-button/qr-code for the sign-in. 217 self._state_text.delete() 218 self._show_standard_sign_in_ui(response) 219 220 # In either case, start querying for results now. 221 self._proxyid = response.proxyid 222 self._proxykey = response.proxykey 223 bui.apptimer( 224 STATUS_CHECK_INTERVAL_SECONDS, bui.WeakCall(self._ask_for_status) 225 ) 226 227 def _show_overlay_sign_in_ui( 228 self, response: bacommon.cloud.LoginProxyRequestResponse 229 ) -> None: 230 msaddress = self._get_server_address() 231 address = msaddress + response.url_overlay 232 bui.overlay_web_browser_open_url(address) 233 234 def _show_standard_sign_in_ui( 235 self, response: bacommon.cloud.LoginProxyRequestResponse 236 ) -> None: 237 msaddress = self._get_server_address() 238 239 # Show link(s) the user can use to sign in. 240 address = msaddress + response.url 241 address_pretty = address.removeprefix('https://') 242 243 assert bui.app.classic is not None 244 bui.textwidget( 245 parent=self._root_widget, 246 position=(self._width * 0.5, self._height - 95), 247 size=(0, 0), 248 text=bui.Lstr( 249 resource='accountSettingsWindow.v2LinkInstructionsText' 250 ), 251 color=bui.app.ui_v1.title_color, 252 maxwidth=self._width * 0.9, 253 h_align='center', 254 v_align='center', 255 ) 256 button_width = 450 257 if bui.is_browser_likely_available(): 258 bui.buttonwidget( 259 parent=self._root_widget, 260 position=( 261 (self._width * 0.5 - button_width * 0.5), 262 self._height - 185, 263 ), 264 autoselect=True, 265 size=(button_width, 60), 266 label=bui.Lstr(value=address_pretty), 267 color=(0.55, 0.5, 0.6), 268 textcolor=(0.75, 0.7, 0.8), 269 on_activate_call=lambda: bui.open_url(address), 270 ) 271 qroffs = 0.0 272 else: 273 bui.textwidget( 274 parent=self._root_widget, 275 position=(self._width * 0.5 - 200, self._height - 180), 276 size=(button_width - 50, 50), 277 text=bui.Lstr(value=address_pretty), 278 flatness=1.0, 279 maxwidth=self._width, 280 scale=0.75, 281 h_align='center', 282 v_align='center', 283 autoselect=True, 284 on_activate_call=bui.Call(self._copy_link, address_pretty), 285 selectable=True, 286 ) 287 qroffs = 20.0 288 289 qr_size = 270 290 bui.imagewidget( 291 parent=self._root_widget, 292 position=( 293 self._width * 0.5 - qr_size * 0.5, 294 self._height * 0.36 + qroffs - qr_size * 0.5, 295 ), 296 size=(qr_size, qr_size), 297 texture=bui.get_qrcode_texture(address), 298 ) 299 300 def _ask_for_status(self) -> None: 301 assert self._proxyid is not None 302 assert self._proxykey is not None 303 assert bui.app.plus is not None 304 bui.app.plus.cloud.send_message_cb( 305 bacommon.cloud.LoginProxyStateQueryMessage( 306 proxyid=self._proxyid, proxykey=self._proxykey 307 ), 308 on_response=bui.WeakCall(self._got_status), 309 ) 310 311 def _got_status( 312 self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception 313 ) -> None: 314 if ( 315 isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) 316 and response.state is response.State.FAIL 317 ): 318 logging.info('LoginProxy failed.') 319 bui.getsound('error').play() 320 bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) 321 self._done() 322 return 323 324 # If we got a token, set ourself as signed in. Hooray! 325 if ( 326 isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) 327 and response.state is response.State.SUCCESS 328 ): 329 plus = bui.app.plus 330 assert plus is not None 331 assert response.credentials is not None 332 plus.accounts.set_primary_credentials(response.credentials) 333 334 # As a courtesy, tell the server we're done with this proxy 335 # so it can clean up (not a huge deal if this fails) 336 assert self._proxyid is not None 337 try: 338 plus.cloud.send_message_cb( 339 bacommon.cloud.LoginProxyCompleteMessage( 340 proxyid=self._proxyid 341 ), 342 on_response=bui.WeakCall(self._proxy_complete_response), 343 ) 344 except CommunicationError: 345 pass 346 except Exception: 347 logging.warning( 348 'Unexpected error sending login-proxy-complete message', 349 exc_info=True, 350 ) 351 352 self._done() 353 return 354 355 # If we're still waiting, ask again soon. 356 if ( 357 isinstance(response, Exception) 358 or response.state is response.State.WAITING 359 ): 360 bui.apptimer( 361 STATUS_CHECK_INTERVAL_SECONDS, 362 bui.WeakCall(self._ask_for_status), 363 ) 364 365 def _proxy_complete_response(self, response: None | Exception) -> None: 366 del response # Not used. 367 # We could do something smart like retry on exceptions here, but 368 # this isn't critical so we'll just let anything slide. 369 370 def _copy_link(self, link: str) -> None: 371 if bui.clipboard_is_supported(): 372 bui.clipboard_set_text(link) 373 bui.screenmessage( 374 bui.Lstr(resource='copyConfirmText'), color=(0, 1, 0) 375 ) 376 377 def _done(self) -> None: 378 # no-op if our underlying widget is dead or on its way out. 379 if not self._root_widget or self._root_widget.transitioning_out: 380 return 381 382 # If we've got an inline browser up, tell it to close. 383 if self._overlay_web_browser_open: 384 bui.overlay_web_browser_close() 385 386 bui.containerwidget(edit=self._root_widget, transition='out_scale')
A window allowing signing in to a v2 account.
V2ProxySignInWindow(origin_widget: _bauiv1.Widget)
21 def __init__(self, origin_widget: bui.Widget): 22 self._width = 600 23 self._height = 550 24 self._proxyid: str | None = None 25 self._proxykey: str | None = None 26 self._overlay_web_browser_open = False 27 28 assert bui.app.classic is not None 29 uiscale = bui.app.ui_v1.uiscale 30 super().__init__( 31 root_widget=bui.containerwidget( 32 size=(self._width, self._height), 33 transition='in_scale', 34 scale_origin_stack_offset=( 35 origin_widget.get_screen_space_center() 36 ), 37 scale=( 38 1.16 39 if uiscale is bui.UIScale.SMALL 40 else 1.0 if uiscale is bui.UIScale.MEDIUM else 0.9 41 ), 42 ) 43 ) 44 45 self._loading_spinner = bui.spinnerwidget( 46 parent=self._root_widget, 47 position=(self._width * 0.5, self._height * 0.5), 48 size=60, 49 style='bomb', 50 ) 51 self._state_text = bui.textwidget( 52 parent=self._root_widget, 53 position=(self._width * 0.5, self._height * 0.6), 54 h_align='center', 55 v_align='center', 56 size=(0, 0), 57 scale=1.4, 58 maxwidth=0.9 * self._width, 59 # text=bui.Lstr( 60 # value='${A}...', 61 # subs=[('${A}', bui.Lstr(resource='loadingText'))], 62 # ), 63 text='', 64 color=(1, 1, 1), 65 ) 66 self._sub_state_text = bui.textwidget( 67 parent=self._root_widget, 68 position=(self._width * 0.5, self._height * 0.55), 69 h_align='center', 70 v_align='top', 71 scale=0.85, 72 size=(0, 0), 73 maxwidth=0.9 * self._width, 74 text='', 75 ) 76 self._sub_state_text2 = bui.textwidget( 77 parent=self._root_widget, 78 position=(self._width * 0.1, self._height * 0.3), 79 h_align='left', 80 v_align='top', 81 scale=0.7, 82 size=(0, 0), 83 maxwidth=0.9 * self._width, 84 text='', 85 ) 86 87 self._cancel_button = bui.buttonwidget( 88 parent=self._root_widget, 89 position=(30, self._height - 65), 90 size=(130, 50), 91 scale=0.8, 92 label=bui.Lstr(resource='cancelText'), 93 on_activate_call=self._done, 94 autoselect=True, 95 ) 96 97 bui.containerwidget( 98 edit=self._root_widget, cancel_button=self._cancel_button 99 ) 100 101 self._message_in_flight = False 102 self._complete = False 103 self._connection_wait_timeout_time = time.monotonic() + 10.0 104 105 self._update_timer = bui.AppTimer( 106 0.371, bui.WeakCall(self._update), repeat=True 107 ) 108 bui.pushcall(bui.WeakCall(self._update))