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

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.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
on_main_window_close
get_main_window_state
bauiv1._uitypes.Window
get_root_widget