bauiv1lib.sendinfo
UI functionality for entering promo codes.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI functionality for entering promo codes.""" 4 5from __future__ import annotations 6 7import time 8import logging 9from typing import TYPE_CHECKING, override 10 11import bauiv1 as bui 12 13if TYPE_CHECKING: 14 from typing import Any 15 16 17class SendInfoWindow(bui.MainWindow): 18 """Window for sending info to the developer.""" 19 20 def __init__( 21 self, 22 modal: bool = False, 23 legacy_code_mode: bool = False, 24 transition: str | None = 'in_scale', 25 origin_widget: bui.Widget | None = None, 26 ): 27 self._legacy_code_mode = legacy_code_mode 28 29 # Need to wrangle our own transition-out in modal mode. 30 if origin_widget is not None: 31 self._transition_out = 'out_scale' 32 else: 33 self._transition_out = 'out_right' 34 35 width = 450 if legacy_code_mode else 600 36 height = 200 if legacy_code_mode else 300 37 38 self._modal = modal 39 self._r = 'promoCodeWindow' 40 41 assert bui.app.classic is not None 42 uiscale = bui.app.ui_v1.uiscale 43 super().__init__( 44 root_widget=bui.containerwidget( 45 size=(width, height), 46 toolbar_visibility=( 47 'menu_minimal_no_back' 48 if uiscale is bui.UIScale.SMALL or modal 49 else 'menu_full' 50 ), 51 scale=( 52 2.0 53 if uiscale is bui.UIScale.SMALL 54 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 55 ), 56 ), 57 transition=transition, 58 origin_widget=origin_widget, 59 ) 60 61 btn = bui.buttonwidget( 62 parent=self._root_widget, 63 scale=0.5, 64 position=(40, height - 40), 65 size=(60, 60), 66 label='', 67 on_activate_call=self._do_back, 68 autoselect=True, 69 color=(0.55, 0.5, 0.6), 70 icon=bui.gettexture('crossOut'), 71 iconscale=1.2, 72 ) 73 74 v = height - 74 75 76 if legacy_code_mode: 77 v -= 20 78 else: 79 v -= 20 80 bui.textwidget( 81 parent=self._root_widget, 82 text=bui.Lstr(resource='sendInfoDescriptionText'), 83 maxwidth=width * 0.9, 84 position=(width * 0.5, v), 85 color=(0.7, 0.7, 0.7, 1.0), 86 size=(0, 0), 87 scale=0.8, 88 h_align='center', 89 v_align='center', 90 ) 91 v -= 20 92 93 # bui.textwidget( 94 # parent=self._root_widget, 95 # text=bui.Lstr( 96 # resource='supportEmailText', 97 # subs=[('${EMAIL}', 'support@froemling.net')], 98 # ), 99 # maxwidth=width * 0.9, 100 # position=(width * 0.5, v), 101 # color=(0.7, 0.7, 0.7, 1.0), 102 # size=(0, 0), 103 # scale=0.65, 104 # h_align='center', 105 # v_align='center', 106 # ) 107 v -= 80 108 109 bui.textwidget( 110 parent=self._root_widget, 111 text=bui.Lstr( 112 resource=( 113 f'{self._r}.codeText' 114 if legacy_code_mode 115 else 'descriptionText' 116 ) 117 ), 118 position=(22, v), 119 color=(0.8, 0.8, 0.8, 1.0), 120 size=(90, 30), 121 h_align='right', 122 maxwidth=100, 123 ) 124 v -= 8 125 126 self._text_field = bui.textwidget( 127 parent=self._root_widget, 128 position=(125, v), 129 size=(280 if legacy_code_mode else 380, 46), 130 text='', 131 h_align='left', 132 v_align='center', 133 max_chars=64, 134 color=(0.9, 0.9, 0.9, 1.0), 135 description=bui.Lstr( 136 resource=( 137 f'{self._r}.codeText' 138 if legacy_code_mode 139 else 'descriptionText' 140 ) 141 ), 142 editable=True, 143 padding=4, 144 on_return_press_call=self._activate_enter_button, 145 ) 146 bui.widget(edit=btn, down_widget=self._text_field) 147 148 v -= 79 149 b_width = 200 150 self._enter_button = btn2 = bui.buttonwidget( 151 parent=self._root_widget, 152 position=(width * 0.5 - b_width * 0.5, v), 153 size=(b_width, 60), 154 scale=1.0, 155 label=bui.Lstr( 156 resource='submitText', fallback_resource=f'{self._r}.enterText' 157 ), 158 on_activate_call=self._do_enter, 159 ) 160 bui.containerwidget( 161 edit=self._root_widget, 162 cancel_button=btn, 163 start_button=btn2, 164 selected_child=self._text_field, 165 ) 166 167 @override 168 def get_main_window_state(self) -> bui.MainWindowState: 169 # Support recreating our window for back/refresh purposes. 170 cls = type(self) 171 172 assert not self._modal 173 174 # Pull stuff out of self here; if we do it in the lambda we'll 175 # keep self alive which we don't want. 176 legacy_code_mode = self._legacy_code_mode 177 178 return bui.BasicMainWindowState( 179 create_call=lambda transition, origin_widget: cls( 180 legacy_code_mode=legacy_code_mode, 181 transition=transition, 182 origin_widget=origin_widget, 183 ) 184 ) 185 186 def _do_back(self) -> None: 187 # pylint: disable=cyclic-import 188 189 if not self._modal: 190 self.main_window_back() 191 return 192 193 # Handle modal case: 194 195 # no-op if our underlying widget is dead or on its way out. 196 if not self._root_widget or self._root_widget.transitioning_out: 197 return 198 199 bui.containerwidget( 200 edit=self._root_widget, transition=self._transition_out 201 ) 202 203 def _activate_enter_button(self) -> None: 204 self._enter_button.activate() 205 206 def _do_enter(self) -> None: 207 # pylint: disable=cyclic-import 208 # from bauiv1lib.settings.advanced import AdvancedSettingsWindow 209 210 plus = bui.app.plus 211 assert plus is not None 212 213 description: Any = bui.textwidget(query=self._text_field) 214 assert isinstance(description, str) 215 216 if self._modal: 217 # no-op if our underlying widget is dead or on its way out. 218 if not self._root_widget or self._root_widget.transitioning_out: 219 return 220 bui.containerwidget( 221 edit=self._root_widget, transition=self._transition_out 222 ) 223 else: 224 # no-op if we're not in control. 225 if not self.main_window_has_control(): 226 return 227 self.main_window_back() 228 229 # Used for things like unlocking shared playlists or linking 230 # accounts: talk directly to V1 server via transactions. 231 if self._legacy_code_mode: 232 if plus.get_v1_account_state() != 'signed_in': 233 bui.screenmessage( 234 bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) 235 ) 236 bui.getsound('error').play() 237 else: 238 plus.add_v1_account_transaction( 239 { 240 'type': 'PROMO_CODE', 241 'expire_time': time.time() + 5, 242 'code': description, 243 } 244 ) 245 plus.run_v1_account_transactions() 246 else: 247 bui.app.create_async_task(_send_info(description)) 248 249 250async def _send_info(description: str) -> None: 251 from bacommon.cloud import SendInfoMessage 252 253 plus = bui.app.plus 254 assert plus is not None 255 256 try: 257 # Don't allow *anything* if our V2 transport connection isn't up. 258 if not plus.cloud.connected: 259 bui.screenmessage( 260 bui.Lstr(resource='internal.unavailableNoConnectionText'), 261 color=(1, 0, 0), 262 ) 263 bui.getsound('error').play() 264 return 265 266 # Ship to V2 server, with or without account info. 267 if plus.accounts.primary is not None: 268 with plus.accounts.primary: 269 response = await plus.cloud.send_message_async( 270 SendInfoMessage(description) 271 ) 272 else: 273 response = await plus.cloud.send_message_async( 274 SendInfoMessage(description) 275 ) 276 277 # Support simple message printing from v2 server. 278 if response.message is not None: 279 bui.screenmessage(response.message, color=(0, 1, 0)) 280 281 # If V2 handled it, we're done. 282 if response.handled: 283 return 284 285 # Ok; V2 didn't handle it. Try V1 if we're signed in there. 286 if plus.get_v1_account_state() != 'signed_in': 287 bui.screenmessage( 288 bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) 289 ) 290 bui.getsound('error').play() 291 return 292 293 # Push it along to v1 as an old style code. Allow v2 response to 294 # sub in its own code. 295 plus.add_v1_account_transaction( 296 { 297 'type': 'PROMO_CODE', 298 'expire_time': time.time() + 5, 299 'code': ( 300 description 301 if response.legacy_code is None 302 else response.legacy_code 303 ), 304 } 305 ) 306 plus.run_v1_account_transactions() 307 except Exception: 308 logging.exception('Error sending promo code.') 309 bui.screenmessage('Error sending code (see log).', color=(1, 0, 0)) 310 bui.getsound('error').play()
class
SendInfoWindow(bauiv1._uitypes.MainWindow):
18class SendInfoWindow(bui.MainWindow): 19 """Window for sending info to the developer.""" 20 21 def __init__( 22 self, 23 modal: bool = False, 24 legacy_code_mode: bool = False, 25 transition: str | None = 'in_scale', 26 origin_widget: bui.Widget | None = None, 27 ): 28 self._legacy_code_mode = legacy_code_mode 29 30 # Need to wrangle our own transition-out in modal mode. 31 if origin_widget is not None: 32 self._transition_out = 'out_scale' 33 else: 34 self._transition_out = 'out_right' 35 36 width = 450 if legacy_code_mode else 600 37 height = 200 if legacy_code_mode else 300 38 39 self._modal = modal 40 self._r = 'promoCodeWindow' 41 42 assert bui.app.classic is not None 43 uiscale = bui.app.ui_v1.uiscale 44 super().__init__( 45 root_widget=bui.containerwidget( 46 size=(width, height), 47 toolbar_visibility=( 48 'menu_minimal_no_back' 49 if uiscale is bui.UIScale.SMALL or modal 50 else 'menu_full' 51 ), 52 scale=( 53 2.0 54 if uiscale is bui.UIScale.SMALL 55 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 56 ), 57 ), 58 transition=transition, 59 origin_widget=origin_widget, 60 ) 61 62 btn = bui.buttonwidget( 63 parent=self._root_widget, 64 scale=0.5, 65 position=(40, height - 40), 66 size=(60, 60), 67 label='', 68 on_activate_call=self._do_back, 69 autoselect=True, 70 color=(0.55, 0.5, 0.6), 71 icon=bui.gettexture('crossOut'), 72 iconscale=1.2, 73 ) 74 75 v = height - 74 76 77 if legacy_code_mode: 78 v -= 20 79 else: 80 v -= 20 81 bui.textwidget( 82 parent=self._root_widget, 83 text=bui.Lstr(resource='sendInfoDescriptionText'), 84 maxwidth=width * 0.9, 85 position=(width * 0.5, v), 86 color=(0.7, 0.7, 0.7, 1.0), 87 size=(0, 0), 88 scale=0.8, 89 h_align='center', 90 v_align='center', 91 ) 92 v -= 20 93 94 # bui.textwidget( 95 # parent=self._root_widget, 96 # text=bui.Lstr( 97 # resource='supportEmailText', 98 # subs=[('${EMAIL}', 'support@froemling.net')], 99 # ), 100 # maxwidth=width * 0.9, 101 # position=(width * 0.5, v), 102 # color=(0.7, 0.7, 0.7, 1.0), 103 # size=(0, 0), 104 # scale=0.65, 105 # h_align='center', 106 # v_align='center', 107 # ) 108 v -= 80 109 110 bui.textwidget( 111 parent=self._root_widget, 112 text=bui.Lstr( 113 resource=( 114 f'{self._r}.codeText' 115 if legacy_code_mode 116 else 'descriptionText' 117 ) 118 ), 119 position=(22, v), 120 color=(0.8, 0.8, 0.8, 1.0), 121 size=(90, 30), 122 h_align='right', 123 maxwidth=100, 124 ) 125 v -= 8 126 127 self._text_field = bui.textwidget( 128 parent=self._root_widget, 129 position=(125, v), 130 size=(280 if legacy_code_mode else 380, 46), 131 text='', 132 h_align='left', 133 v_align='center', 134 max_chars=64, 135 color=(0.9, 0.9, 0.9, 1.0), 136 description=bui.Lstr( 137 resource=( 138 f'{self._r}.codeText' 139 if legacy_code_mode 140 else 'descriptionText' 141 ) 142 ), 143 editable=True, 144 padding=4, 145 on_return_press_call=self._activate_enter_button, 146 ) 147 bui.widget(edit=btn, down_widget=self._text_field) 148 149 v -= 79 150 b_width = 200 151 self._enter_button = btn2 = bui.buttonwidget( 152 parent=self._root_widget, 153 position=(width * 0.5 - b_width * 0.5, v), 154 size=(b_width, 60), 155 scale=1.0, 156 label=bui.Lstr( 157 resource='submitText', fallback_resource=f'{self._r}.enterText' 158 ), 159 on_activate_call=self._do_enter, 160 ) 161 bui.containerwidget( 162 edit=self._root_widget, 163 cancel_button=btn, 164 start_button=btn2, 165 selected_child=self._text_field, 166 ) 167 168 @override 169 def get_main_window_state(self) -> bui.MainWindowState: 170 # Support recreating our window for back/refresh purposes. 171 cls = type(self) 172 173 assert not self._modal 174 175 # Pull stuff out of self here; if we do it in the lambda we'll 176 # keep self alive which we don't want. 177 legacy_code_mode = self._legacy_code_mode 178 179 return bui.BasicMainWindowState( 180 create_call=lambda transition, origin_widget: cls( 181 legacy_code_mode=legacy_code_mode, 182 transition=transition, 183 origin_widget=origin_widget, 184 ) 185 ) 186 187 def _do_back(self) -> None: 188 # pylint: disable=cyclic-import 189 190 if not self._modal: 191 self.main_window_back() 192 return 193 194 # Handle modal case: 195 196 # no-op if our underlying widget is dead or on its way out. 197 if not self._root_widget or self._root_widget.transitioning_out: 198 return 199 200 bui.containerwidget( 201 edit=self._root_widget, transition=self._transition_out 202 ) 203 204 def _activate_enter_button(self) -> None: 205 self._enter_button.activate() 206 207 def _do_enter(self) -> None: 208 # pylint: disable=cyclic-import 209 # from bauiv1lib.settings.advanced import AdvancedSettingsWindow 210 211 plus = bui.app.plus 212 assert plus is not None 213 214 description: Any = bui.textwidget(query=self._text_field) 215 assert isinstance(description, str) 216 217 if self._modal: 218 # no-op if our underlying widget is dead or on its way out. 219 if not self._root_widget or self._root_widget.transitioning_out: 220 return 221 bui.containerwidget( 222 edit=self._root_widget, transition=self._transition_out 223 ) 224 else: 225 # no-op if we're not in control. 226 if not self.main_window_has_control(): 227 return 228 self.main_window_back() 229 230 # Used for things like unlocking shared playlists or linking 231 # accounts: talk directly to V1 server via transactions. 232 if self._legacy_code_mode: 233 if plus.get_v1_account_state() != 'signed_in': 234 bui.screenmessage( 235 bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) 236 ) 237 bui.getsound('error').play() 238 else: 239 plus.add_v1_account_transaction( 240 { 241 'type': 'PROMO_CODE', 242 'expire_time': time.time() + 5, 243 'code': description, 244 } 245 ) 246 plus.run_v1_account_transactions() 247 else: 248 bui.app.create_async_task(_send_info(description))
Window for sending info to the developer.
SendInfoWindow( modal: bool = False, legacy_code_mode: bool = False, transition: str | None = 'in_scale', origin_widget: _bauiv1.Widget | None = None)
21 def __init__( 22 self, 23 modal: bool = False, 24 legacy_code_mode: bool = False, 25 transition: str | None = 'in_scale', 26 origin_widget: bui.Widget | None = None, 27 ): 28 self._legacy_code_mode = legacy_code_mode 29 30 # Need to wrangle our own transition-out in modal mode. 31 if origin_widget is not None: 32 self._transition_out = 'out_scale' 33 else: 34 self._transition_out = 'out_right' 35 36 width = 450 if legacy_code_mode else 600 37 height = 200 if legacy_code_mode else 300 38 39 self._modal = modal 40 self._r = 'promoCodeWindow' 41 42 assert bui.app.classic is not None 43 uiscale = bui.app.ui_v1.uiscale 44 super().__init__( 45 root_widget=bui.containerwidget( 46 size=(width, height), 47 toolbar_visibility=( 48 'menu_minimal_no_back' 49 if uiscale is bui.UIScale.SMALL or modal 50 else 'menu_full' 51 ), 52 scale=( 53 2.0 54 if uiscale is bui.UIScale.SMALL 55 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 56 ), 57 ), 58 transition=transition, 59 origin_widget=origin_widget, 60 ) 61 62 btn = bui.buttonwidget( 63 parent=self._root_widget, 64 scale=0.5, 65 position=(40, height - 40), 66 size=(60, 60), 67 label='', 68 on_activate_call=self._do_back, 69 autoselect=True, 70 color=(0.55, 0.5, 0.6), 71 icon=bui.gettexture('crossOut'), 72 iconscale=1.2, 73 ) 74 75 v = height - 74 76 77 if legacy_code_mode: 78 v -= 20 79 else: 80 v -= 20 81 bui.textwidget( 82 parent=self._root_widget, 83 text=bui.Lstr(resource='sendInfoDescriptionText'), 84 maxwidth=width * 0.9, 85 position=(width * 0.5, v), 86 color=(0.7, 0.7, 0.7, 1.0), 87 size=(0, 0), 88 scale=0.8, 89 h_align='center', 90 v_align='center', 91 ) 92 v -= 20 93 94 # bui.textwidget( 95 # parent=self._root_widget, 96 # text=bui.Lstr( 97 # resource='supportEmailText', 98 # subs=[('${EMAIL}', 'support@froemling.net')], 99 # ), 100 # maxwidth=width * 0.9, 101 # position=(width * 0.5, v), 102 # color=(0.7, 0.7, 0.7, 1.0), 103 # size=(0, 0), 104 # scale=0.65, 105 # h_align='center', 106 # v_align='center', 107 # ) 108 v -= 80 109 110 bui.textwidget( 111 parent=self._root_widget, 112 text=bui.Lstr( 113 resource=( 114 f'{self._r}.codeText' 115 if legacy_code_mode 116 else 'descriptionText' 117 ) 118 ), 119 position=(22, v), 120 color=(0.8, 0.8, 0.8, 1.0), 121 size=(90, 30), 122 h_align='right', 123 maxwidth=100, 124 ) 125 v -= 8 126 127 self._text_field = bui.textwidget( 128 parent=self._root_widget, 129 position=(125, v), 130 size=(280 if legacy_code_mode else 380, 46), 131 text='', 132 h_align='left', 133 v_align='center', 134 max_chars=64, 135 color=(0.9, 0.9, 0.9, 1.0), 136 description=bui.Lstr( 137 resource=( 138 f'{self._r}.codeText' 139 if legacy_code_mode 140 else 'descriptionText' 141 ) 142 ), 143 editable=True, 144 padding=4, 145 on_return_press_call=self._activate_enter_button, 146 ) 147 bui.widget(edit=btn, down_widget=self._text_field) 148 149 v -= 79 150 b_width = 200 151 self._enter_button = btn2 = bui.buttonwidget( 152 parent=self._root_widget, 153 position=(width * 0.5 - b_width * 0.5, v), 154 size=(b_width, 60), 155 scale=1.0, 156 label=bui.Lstr( 157 resource='submitText', fallback_resource=f'{self._r}.enterText' 158 ), 159 on_activate_call=self._do_enter, 160 ) 161 bui.containerwidget( 162 edit=self._root_widget, 163 cancel_button=btn, 164 start_button=btn2, 165 selected_child=self._text_field, 166 )
Create a MainWindow given a root widget and transition info.
Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.
168 @override 169 def get_main_window_state(self) -> bui.MainWindowState: 170 # Support recreating our window for back/refresh purposes. 171 cls = type(self) 172 173 assert not self._modal 174 175 # Pull stuff out of self here; if we do it in the lambda we'll 176 # keep self alive which we don't want. 177 legacy_code_mode = self._legacy_code_mode 178 179 return bui.BasicMainWindowState( 180 create_call=lambda transition, origin_widget: cls( 181 legacy_code_mode=legacy_code_mode, 182 transition=transition, 183 origin_widget=origin_widget, 184 ) 185 )
Return a WindowState to recreate this window, if supported.