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