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        # 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    def _do_back(self) -> None:
168        # pylint: disable=cyclic-import
169
170        if not self._modal:
171            self.main_window_back()
172            return
173
174        # Handle modal case:
175
176        # no-op if our underlying widget is dead or on its way out.
177        if not self._root_widget or self._root_widget.transitioning_out:
178            return
179
180        bui.containerwidget(
181            edit=self._root_widget, transition=self._transition_out
182        )
183
184    def _activate_enter_button(self) -> None:
185        self._enter_button.activate()
186
187    def _do_enter(self) -> None:
188        # pylint: disable=cyclic-import
189        # from bauiv1lib.settings.advanced import AdvancedSettingsWindow
190
191        plus = bui.app.plus
192        assert plus is not None
193
194        description: Any = bui.textwidget(query=self._text_field)
195        assert isinstance(description, str)
196
197        if self._modal:
198            # no-op if our underlying widget is dead or on its way out.
199            if not self._root_widget or self._root_widget.transitioning_out:
200                return
201            bui.containerwidget(
202                edit=self._root_widget, transition=self._transition_out
203            )
204        else:
205            # no-op if we're not in control.
206            if not self.main_window_has_control():
207                return
208            self.main_window_back()
209
210        # Used for things like unlocking shared playlists or linking
211        # accounts: talk directly to V1 server via transactions.
212        if self._legacy_code_mode:
213            if plus.get_v1_account_state() != 'signed_in':
214                bui.screenmessage(
215                    bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
216                )
217                bui.getsound('error').play()
218            else:
219                plus.add_v1_account_transaction(
220                    {
221                        'type': 'PROMO_CODE',
222                        'expire_time': time.time() + 5,
223                        'code': description,
224                    }
225                )
226                plus.run_v1_account_transactions()
227        else:
228            bui.app.create_async_task(_send_info(description))
229
230
231async def _send_info(description: str) -> None:
232    from bacommon.cloud import SendInfoMessage
233
234    plus = bui.app.plus
235    assert plus is not None
236
237    try:
238        # Don't allow *anything* if our V2 transport connection isn't up.
239        if not plus.cloud.connected:
240            bui.screenmessage(
241                bui.Lstr(resource='internal.unavailableNoConnectionText'),
242                color=(1, 0, 0),
243            )
244            bui.getsound('error').play()
245            return
246
247        # Ship to V2 server, with or without account info.
248        if plus.accounts.primary is not None:
249            with plus.accounts.primary:
250                response = await plus.cloud.send_message_async(
251                    SendInfoMessage(description)
252                )
253        else:
254            response = await plus.cloud.send_message_async(
255                SendInfoMessage(description)
256            )
257
258        # Support simple message printing from v2 server.
259        if response.message is not None:
260            bui.screenmessage(response.message, color=(0, 1, 0))
261
262        # If V2 handled it, we're done.
263        if response.handled:
264            return
265
266        # Ok; V2 didn't handle it. Try V1 if we're signed in there.
267        if plus.get_v1_account_state() != 'signed_in':
268            bui.screenmessage(
269                bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
270            )
271            bui.getsound('error').play()
272            return
273
274        # Push it along to v1 as an old style code. Allow v2 response to
275        # sub in its own code.
276        plus.add_v1_account_transaction(
277            {
278                'type': 'PROMO_CODE',
279                'expire_time': time.time() + 5,
280                'code': (
281                    description
282                    if response.legacy_code is None
283                    else response.legacy_code
284                ),
285            }
286        )
287        plus.run_v1_account_transactions()
288    except Exception:
289        logging.exception('Error sending promo code.')
290        bui.screenmessage('Error sending code (see log).', color=(1, 0, 0))
291        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    def _do_back(self) -> None:
169        # pylint: disable=cyclic-import
170
171        if not self._modal:
172            self.main_window_back()
173            return
174
175        # Handle modal case:
176
177        # no-op if our underlying widget is dead or on its way out.
178        if not self._root_widget or self._root_widget.transitioning_out:
179            return
180
181        bui.containerwidget(
182            edit=self._root_widget, transition=self._transition_out
183        )
184
185    def _activate_enter_button(self) -> None:
186        self._enter_button.activate()
187
188    def _do_enter(self) -> None:
189        # pylint: disable=cyclic-import
190        # from bauiv1lib.settings.advanced import AdvancedSettingsWindow
191
192        plus = bui.app.plus
193        assert plus is not None
194
195        description: Any = bui.textwidget(query=self._text_field)
196        assert isinstance(description, str)
197
198        if self._modal:
199            # no-op if our underlying widget is dead or on its way out.
200            if not self._root_widget or self._root_widget.transitioning_out:
201                return
202            bui.containerwidget(
203                edit=self._root_widget, transition=self._transition_out
204            )
205        else:
206            # no-op if we're not in control.
207            if not self.main_window_has_control():
208                return
209            self.main_window_back()
210
211        # Used for things like unlocking shared playlists or linking
212        # accounts: talk directly to V1 server via transactions.
213        if self._legacy_code_mode:
214            if plus.get_v1_account_state() != 'signed_in':
215                bui.screenmessage(
216                    bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
217                )
218                bui.getsound('error').play()
219            else:
220                plus.add_v1_account_transaction(
221                    {
222                        'type': 'PROMO_CODE',
223                        'expire_time': time.time() + 5,
224                        'code': description,
225                    }
226                )
227                plus.run_v1_account_transactions()
228        else:
229            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.

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