# Released under the MIT License. See LICENSE for details.
#
"""UI functionality for entering promo codes."""
from __future__ import annotations
import time
import logging
from typing import TYPE_CHECKING, override
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any
[docs]
class SendInfoWindow(bui.MainWindow):
"""Window for sending info to the developer."""
def __init__(
self,
modal: bool = False,
legacy_code_mode: bool = False,
transition: str | None = 'in_scale',
origin_widget: bui.Widget | None = None,
):
self._legacy_code_mode = legacy_code_mode
# Need to wrangle our own transition-out in modal mode.
if origin_widget is not None:
self._transition_out = 'out_scale'
else:
self._transition_out = 'out_right'
width = 450 if legacy_code_mode else 600
height = 200 if legacy_code_mode else 300
self._modal = modal
self._r = 'promoCodeWindow'
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
toolbar_visibility=(
'menu_minimal_no_back'
if uiscale is bui.UIScale.SMALL or modal
else 'menu_full'
),
scale=(
2.0
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
),
transition=transition,
origin_widget=origin_widget,
)
btn = bui.buttonwidget(
parent=self._root_widget,
scale=0.5,
position=(40, height - 40),
size=(60, 60),
label='',
on_activate_call=self._do_back,
autoselect=True,
color=(0.55, 0.5, 0.6),
icon=bui.gettexture('crossOut'),
iconscale=1.2,
)
v = height - 74
if legacy_code_mode:
v -= 20
else:
v -= 20
bui.textwidget(
parent=self._root_widget,
text=bui.Lstr(resource='sendInfoDescriptionText'),
maxwidth=width * 0.9,
position=(width * 0.5, v),
color=(0.7, 0.7, 0.7, 1.0),
size=(0, 0),
scale=0.8,
h_align='center',
v_align='center',
)
v -= 20
# bui.textwidget(
# parent=self._root_widget,
# text=bui.Lstr(
# resource='supportEmailText',
# subs=[('${EMAIL}', 'support@froemling.net')],
# ),
# maxwidth=width * 0.9,
# position=(width * 0.5, v),
# color=(0.7, 0.7, 0.7, 1.0),
# size=(0, 0),
# scale=0.65,
# h_align='center',
# v_align='center',
# )
v -= 80
bui.textwidget(
parent=self._root_widget,
text=bui.Lstr(
resource=(
f'{self._r}.codeText'
if legacy_code_mode
else 'descriptionText'
)
),
position=(22, v),
color=(0.8, 0.8, 0.8, 1.0),
size=(90, 30),
h_align='right',
maxwidth=100,
)
v -= 8
self._text_field = bui.textwidget(
parent=self._root_widget,
position=(125, v),
size=(280 if legacy_code_mode else 380, 46),
text='',
h_align='left',
v_align='center',
max_chars=64,
color=(0.9, 0.9, 0.9, 1.0),
description=bui.Lstr(
resource=(
f'{self._r}.codeText'
if legacy_code_mode
else 'descriptionText'
)
),
editable=True,
padding=4,
on_return_press_call=self._activate_enter_button,
)
bui.widget(edit=btn, down_widget=self._text_field)
v -= 79
b_width = 200
self._enter_button = btn2 = bui.buttonwidget(
parent=self._root_widget,
position=(width * 0.5 - b_width * 0.5, v),
size=(b_width, 60),
scale=1.0,
label=bui.Lstr(
resource='submitText', fallback_resource=f'{self._r}.enterText'
),
on_activate_call=self._do_enter,
)
bui.containerwidget(
edit=self._root_widget,
cancel_button=btn,
start_button=btn2,
selected_child=self._text_field,
)
[docs]
@override
def get_main_window_state(self) -> bui.MainWindowState:
# Support recreating our window for back/refresh purposes.
cls = type(self)
assert not self._modal
# Pull stuff out of self here; if we do it in the lambda we'll
# keep self alive which we don't want.
legacy_code_mode = self._legacy_code_mode
return bui.BasicMainWindowState(
create_call=lambda transition, origin_widget: cls(
legacy_code_mode=legacy_code_mode,
transition=transition,
origin_widget=origin_widget,
)
)
def _do_back(self) -> None:
# pylint: disable=cyclic-import
if not self._modal:
self.main_window_back()
return
# Handle modal case:
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
def _activate_enter_button(self) -> None:
self._enter_button.activate()
def _do_enter(self) -> None:
# pylint: disable=cyclic-import
# from bauiv1lib.settings.advanced import AdvancedSettingsWindow
plus = bui.app.plus
assert plus is not None
description: Any = bui.textwidget(query=self._text_field)
assert isinstance(description, str)
if self._modal:
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
else:
# no-op if we're not in control.
if not self.main_window_has_control():
return
self.main_window_back()
# Used for things like unlocking shared playlists or linking
# accounts: talk directly to V1 server via transactions.
if self._legacy_code_mode:
if plus.get_v1_account_state() != 'signed_in':
bui.screenmessage(
bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
)
bui.getsound('error').play()
else:
plus.add_v1_account_transaction(
{
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,
'code': description,
}
)
plus.run_v1_account_transactions()
else:
bui.app.create_async_task(_send_info(description))
async def _send_info(description: str) -> None:
from bacommon.cloud import SendInfoMessage
plus = bui.app.plus
assert plus is not None
try:
# Don't allow *anything* if our V2 transport connection isn't up.
if not plus.cloud.connected:
bui.screenmessage(
bui.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
return
# Ship to V2 server, with or without account info.
if plus.accounts.primary is not None:
with plus.accounts.primary:
response = await plus.cloud.send_message_async(
SendInfoMessage(description)
)
else:
response = await plus.cloud.send_message_async(
SendInfoMessage(description)
)
# Support simple message printing from v2 server.
if response.message is not None:
bui.screenmessage(response.message, color=(0, 1, 0))
# If V2 handled it, we're done.
if response.handled:
return
# Ok; V2 didn't handle it. Try V1 if we're signed in there.
if plus.get_v1_account_state() != 'signed_in':
bui.screenmessage(
bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
)
bui.getsound('error').play()
return
# Push it along to v1 as an old style code. Allow v2 response to
# sub in its own code.
plus.add_v1_account_transaction(
{
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,
'code': (
description
if response.legacy_code is None
else response.legacy_code
),
}
)
plus.run_v1_account_transactions()
except Exception:
logging.exception('Error sending promo code.')
bui.screenmessage('Error sending code (see log).', color=(1, 0, 0))
bui.getsound('error').play()