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.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
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.