bauiv1lib.getcurrency

UI functionality for purchasing/acquiring currency.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""UI functionality for purchasing/acquiring currency."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING
  8
  9from efro.util import utc_now
 10
 11import bauiv1 as bui
 12
 13if TYPE_CHECKING:
 14    from typing import Any
 15
 16
 17class GetCurrencyWindow(bui.Window):
 18    """Window for purchasing/acquiring currency."""
 19
 20    def __init__(
 21        self,
 22        transition: str = 'in_right',
 23        from_modal_store: bool = False,
 24        modal: bool = False,
 25        origin_widget: bui.Widget | None = None,
 26        store_back_location: str | None = None,
 27    ):
 28        # pylint: disable=too-many-statements
 29        # pylint: disable=too-many-locals
 30
 31        plus = bui.app.plus
 32        assert plus is not None
 33
 34        bui.set_analytics_screen('Get Tickets Window')
 35
 36        self._transitioning_out = False
 37        self._store_back_location = store_back_location  # ew.
 38
 39        self._ad_button_greyed = False
 40        self._smooth_update_timer: bui.AppTimer | None = None
 41        self._ad_button = None
 42        self._ad_label = None
 43        self._ad_image = None
 44        self._ad_time_text = None
 45
 46        # If they provided an origin-widget, scale up from that.
 47        scale_origin: tuple[float, float] | None
 48        if origin_widget is not None:
 49            self._transition_out = 'out_scale'
 50            scale_origin = origin_widget.get_screen_space_center()
 51            transition = 'in_scale'
 52        else:
 53            self._transition_out = 'out_right'
 54            scale_origin = None
 55
 56        assert bui.app.classic is not None
 57        uiscale = bui.app.ui_v1.uiscale
 58        self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
 59        x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
 60        self._height = 480.0
 61
 62        self._modal = modal
 63        self._from_modal_store = from_modal_store
 64        self._r = 'getTicketsWindow'
 65
 66        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 67
 68        super().__init__(
 69            root_widget=bui.containerwidget(
 70                size=(self._width, self._height + top_extra),
 71                transition=transition,
 72                scale_origin_stack_offset=scale_origin,
 73                color=(0.4, 0.37, 0.55),
 74                scale=(
 75                    1.63
 76                    if uiscale is bui.UIScale.SMALL
 77                    else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
 78                ),
 79                stack_offset=(
 80                    (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
 81                ),
 82            )
 83        )
 84
 85        btn = bui.buttonwidget(
 86            parent=self._root_widget,
 87            position=(55 + x_inset, self._height - 79),
 88            size=(140, 60),
 89            scale=1.0,
 90            autoselect=True,
 91            label=bui.Lstr(resource='doneText' if modal else 'backText'),
 92            button_type='regular' if modal else 'back',
 93            on_activate_call=self._back,
 94        )
 95
 96        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 97
 98        bui.textwidget(
 99            parent=self._root_widget,
100            position=(self._width * 0.5, self._height - 55),
101            size=(0, 0),
102            color=bui.app.ui_v1.title_color,
103            scale=1.2,
104            h_align='center',
105            v_align='center',
106            text=bui.Lstr(resource=self._r + '.titleText'),
107            maxwidth=290,
108        )
109
110        if not modal:
111            bui.buttonwidget(
112                edit=btn,
113                button_type='backSmall',
114                size=(60, 60),
115                label=bui.charstr(bui.SpecialChar.BACK),
116            )
117
118        b_size = (220.0, 180.0)
119        v = self._height - b_size[1] - 80
120        spacing = 1
121
122        self._ad_button = None
123
124        def _add_button(
125            item: str,
126            position: tuple[float, float],
127            size: tuple[float, float],
128            label: bui.Lstr,
129            price: str | None = None,
130            tex_name: str | None = None,
131            tex_opacity: float = 1.0,
132            tex_scale: float = 1.0,
133            enabled: bool = True,
134            text_scale: float = 1.0,
135        ) -> bui.Widget:
136            btn2 = bui.buttonwidget(
137                parent=self._root_widget,
138                position=position,
139                button_type='square',
140                size=size,
141                label='',
142                autoselect=True,
143                color=None if enabled else (0.5, 0.5, 0.5),
144                on_activate_call=(
145                    bui.Call(self._purchase, item)
146                    if enabled
147                    else self._disabled_press
148                ),
149            )
150            txt = bui.textwidget(
151                parent=self._root_widget,
152                text=label,
153                position=(
154                    position[0] + size[0] * 0.5,
155                    position[1] + size[1] * 0.3,
156                ),
157                scale=text_scale,
158                maxwidth=size[0] * 0.75,
159                size=(0, 0),
160                h_align='center',
161                v_align='center',
162                draw_controller=btn2,
163                color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2),
164            )
165            if price is not None and enabled:
166                bui.textwidget(
167                    parent=self._root_widget,
168                    text=price,
169                    position=(
170                        position[0] + size[0] * 0.5,
171                        position[1] + size[1] * 0.17,
172                    ),
173                    scale=0.7,
174                    maxwidth=size[0] * 0.75,
175                    size=(0, 0),
176                    h_align='center',
177                    v_align='center',
178                    draw_controller=btn2,
179                    color=(0.4, 0.9, 0.4, 1.0),
180                )
181            i = None
182            if tex_name is not None:
183                tex_size = 90.0 * tex_scale
184                i = bui.imagewidget(
185                    parent=self._root_widget,
186                    texture=bui.gettexture(tex_name),
187                    position=(
188                        position[0] + size[0] * 0.5 - tex_size * 0.5,
189                        position[1] + size[1] * 0.66 - tex_size * 0.5,
190                    ),
191                    size=(tex_size, tex_size),
192                    draw_controller=btn2,
193                    opacity=tex_opacity * (1.0 if enabled else 0.25),
194                )
195            if item == 'ad':
196                self._ad_button = btn2
197                self._ad_label = txt
198                assert i is not None
199                self._ad_image = i
200                self._ad_time_text = bui.textwidget(
201                    parent=self._root_widget,
202                    text='1m 10s',
203                    position=(
204                        position[0] + size[0] * 0.5,
205                        position[1] + size[1] * 0.5,
206                    ),
207                    scale=text_scale * 1.2,
208                    maxwidth=size[0] * 0.85,
209                    size=(0, 0),
210                    h_align='center',
211                    v_align='center',
212                    draw_controller=btn2,
213                    color=(0.4, 0.9, 0.4, 1.0),
214                )
215            return btn2
216
217        rsrc = self._r + '.ticketsText'
218
219        c2txt = bui.Lstr(
220            resource=rsrc,
221            subs=[
222                (
223                    '${COUNT}',
224                    str(
225                        plus.get_v1_account_misc_read_val('tickets2Amount', 500)
226                    ),
227                )
228            ],
229        )
230        c3txt = bui.Lstr(
231            resource=rsrc,
232            subs=[
233                (
234                    '${COUNT}',
235                    str(
236                        plus.get_v1_account_misc_read_val(
237                            'tickets3Amount', 1500
238                        )
239                    ),
240                )
241            ],
242        )
243        c4txt = bui.Lstr(
244            resource=rsrc,
245            subs=[
246                (
247                    '${COUNT}',
248                    str(
249                        plus.get_v1_account_misc_read_val(
250                            'tickets4Amount', 5000
251                        )
252                    ),
253                )
254            ],
255        )
256        c5txt = bui.Lstr(
257            resource=rsrc,
258            subs=[
259                (
260                    '${COUNT}',
261                    str(
262                        plus.get_v1_account_misc_read_val(
263                            'tickets5Amount', 15000
264                        )
265                    ),
266                )
267            ],
268        )
269
270        h = 110.0
271
272        # Enable buttons if we have prices.
273        tickets2_price = plus.get_price('tickets2')
274        tickets3_price = plus.get_price('tickets3')
275        tickets4_price = plus.get_price('tickets4')
276        tickets5_price = plus.get_price('tickets5')
277
278        # TEMP
279        # tickets1_price = '$0.99'
280        # tickets2_price = '$4.99'
281        # tickets3_price = '$9.99'
282        # tickets4_price = '$19.99'
283        # tickets5_price = '$49.99'
284
285        _add_button(
286            'tickets2',
287            enabled=(tickets2_price is not None),
288            position=(
289                self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
290                v,
291            ),
292            size=b_size,
293            label=c2txt,
294            price=tickets2_price,
295            tex_name='ticketsMore',
296        )  # 0.99-ish
297        _add_button(
298            'tickets3',
299            enabled=(tickets3_price is not None),
300            position=(
301                self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
302                v,
303            ),
304            size=b_size,
305            label=c3txt,
306            price=tickets3_price,
307            tex_name='ticketRoll',
308        )  # 4.99-ish
309        v -= b_size[1] - 5
310        _add_button(
311            'tickets4',
312            enabled=(tickets4_price is not None),
313            position=(
314                self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
315                v,
316            ),
317            size=b_size,
318            label=c4txt,
319            price=tickets4_price,
320            tex_name='ticketRollBig',
321            tex_scale=1.2,
322        )  # 9.99-ish
323        _add_button(
324            'tickets5',
325            enabled=(tickets5_price is not None),
326            position=(
327                self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
328                v,
329            ),
330            size=b_size,
331            label=c5txt,
332            price=tickets5_price,
333            tex_name='ticketRolls',
334            tex_scale=1.2,
335        )  # 19.99-ish
336
337        self._enable_ad_button = plus.has_video_ads()
338        h = self._width * 0.5 + 110.0
339        v = self._height - b_size[1] - 115.0
340
341        if self._enable_ad_button:
342            h_offs = 35
343            b_size_3 = (150, 120)
344            cdb = _add_button(
345                'ad',
346                position=(h + h_offs, v),
347                size=b_size_3,
348                label=bui.Lstr(
349                    resource=self._r + '.ticketsFromASponsorText',
350                    subs=[
351                        (
352                            '${COUNT}',
353                            str(
354                                plus.get_v1_account_misc_read_val(
355                                    'sponsorTickets', 5
356                                )
357                            ),
358                        )
359                    ],
360                ),
361                tex_name='ticketsMore',
362                enabled=self._enable_ad_button,
363                tex_opacity=0.6,
364                tex_scale=0.7,
365                text_scale=0.7,
366            )
367            bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
368
369            self._ad_free_text = bui.textwidget(
370                parent=self._root_widget,
371                text=bui.Lstr(resource=self._r + '.freeText'),
372                position=(
373                    h + h_offs + b_size_3[0] * 0.5,
374                    v + b_size_3[1] * 0.5 + 25,
375                ),
376                size=(0, 0),
377                color=(1, 1, 0, 1.0),
378                draw_controller=cdb,
379                rotate=15,
380                shadow=1.0,
381                maxwidth=150,
382                h_align='center',
383                v_align='center',
384                scale=1.0,
385            )
386            v -= 125
387        else:
388            v -= 20
389
390        if bool(True):
391            h_offs = 35
392            b_size_3 = (150, 120)
393            cdb = _add_button(
394                'app_invite',
395                position=(h + h_offs, v),
396                size=b_size_3,
397                label=bui.Lstr(
398                    resource='gatherWindow.earnTicketsForRecommendingText',
399                    subs=[
400                        (
401                            '${COUNT}',
402                            str(
403                                plus.get_v1_account_misc_read_val(
404                                    'sponsorTickets', 5
405                                )
406                            ),
407                        )
408                    ],
409                ),
410                tex_name='ticketsMore',
411                enabled=True,
412                tex_opacity=0.6,
413                tex_scale=0.7,
414                text_scale=0.7,
415            )
416            bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
417
418            bui.textwidget(
419                parent=self._root_widget,
420                text=bui.Lstr(resource=self._r + '.freeText'),
421                position=(
422                    h + h_offs + b_size_3[0] * 0.5,
423                    v + b_size_3[1] * 0.5 + 25,
424                ),
425                size=(0, 0),
426                color=(1, 1, 0, 1.0),
427                draw_controller=cdb,
428                rotate=15,
429                shadow=1.0,
430                maxwidth=150,
431                h_align='center',
432                v_align='center',
433                scale=1.0,
434            )
435            tc_y_offs = 0
436        else:
437            tc_y_offs = 0
438
439        h = self._width - (185 + x_inset)
440        v = self._height - 95 + tc_y_offs
441
442        txt1 = (
443            bui.Lstr(resource=self._r + '.youHaveText')
444            .evaluate()
445            .partition('${COUNT}')[0]
446            .strip()
447        )
448        txt2 = (
449            bui.Lstr(resource=self._r + '.youHaveText')
450            .evaluate()
451            .rpartition('${COUNT}')[-1]
452            .strip()
453        )
454
455        bui.textwidget(
456            parent=self._root_widget,
457            text=txt1,
458            position=(h, v),
459            size=(0, 0),
460            color=(0.5, 0.5, 0.6),
461            maxwidth=200,
462            h_align='center',
463            v_align='center',
464            scale=0.8,
465        )
466        v -= 30
467        self._ticket_count_text = bui.textwidget(
468            parent=self._root_widget,
469            position=(h, v),
470            size=(0, 0),
471            color=(0.2, 1.0, 0.2),
472            maxwidth=200,
473            h_align='center',
474            v_align='center',
475            scale=1.6,
476        )
477        v -= 30
478        bui.textwidget(
479            parent=self._root_widget,
480            text=txt2,
481            position=(h, v),
482            size=(0, 0),
483            color=(0.5, 0.5, 0.6),
484            maxwidth=200,
485            h_align='center',
486            v_align='center',
487            scale=0.8,
488        )
489
490        self._ticking_sound: bui.Sound | None = None
491        self._smooth_ticket_count: float | None = None
492        self._ticket_count = 0
493        self._update()
494        self._update_timer = bui.AppTimer(
495            1.0, bui.WeakCall(self._update), repeat=True
496        )
497        self._smooth_increase_speed = 1.0
498
499    def __del__(self) -> None:
500        if self._ticking_sound is not None:
501            self._ticking_sound.stop()
502            self._ticking_sound = None
503
504    def _smooth_update(self) -> None:
505        if not self._ticket_count_text:
506            self._smooth_update_timer = None
507            return
508
509        finished = False
510
511        # If we're going down, do it immediately.
512        assert self._smooth_ticket_count is not None
513        if int(self._smooth_ticket_count) >= self._ticket_count:
514            self._smooth_ticket_count = float(self._ticket_count)
515            finished = True
516        else:
517            # We're going up; start a sound if need be.
518            self._smooth_ticket_count = min(
519                self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
520                self._ticket_count,
521            )
522            if int(self._smooth_ticket_count) >= self._ticket_count:
523                finished = True
524                self._smooth_ticket_count = float(self._ticket_count)
525            elif self._ticking_sound is None:
526                self._ticking_sound = bui.getsound('scoreIncrease')
527                self._ticking_sound.play()
528
529        bui.textwidget(
530            edit=self._ticket_count_text,
531            text=str(int(self._smooth_ticket_count)),
532        )
533
534        # If we've reached the target, kill the timer/sound/etc.
535        if finished:
536            self._smooth_update_timer = None
537            if self._ticking_sound is not None:
538                self._ticking_sound.stop()
539                self._ticking_sound = None
540                bui.getsound('cashRegister2').play()
541
542    def _update(self) -> None:
543        import datetime
544
545        plus = bui.app.plus
546        assert plus is not None
547
548        # If we somehow get signed out, just die.
549        if plus.get_v1_account_state() != 'signed_in':
550            self._back()
551            return
552
553        self._ticket_count = plus.get_v1_account_ticket_count()
554
555        # Update our incentivized ad button depending on whether ads are
556        # available.
557        if self._ad_button is not None:
558            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
559                'nextRewardAdTime', None
560            )
561            if next_reward_ad_time is not None:
562                next_reward_ad_time = datetime.datetime.fromtimestamp(
563                    next_reward_ad_time, datetime.UTC
564                )
565            now = utc_now()
566            if plus.have_incentivized_ad() and (
567                next_reward_ad_time is None or next_reward_ad_time <= now
568            ):
569                self._ad_button_greyed = False
570                bui.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
571                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
572                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1))
573                bui.imagewidget(edit=self._ad_image, opacity=0.6)
574                bui.textwidget(edit=self._ad_time_text, text='')
575            else:
576                self._ad_button_greyed = True
577                bui.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5))
578                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2))
579                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
580                bui.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
581                sval: str | bui.Lstr
582                if (
583                    next_reward_ad_time is not None
584                    and next_reward_ad_time > now
585                ):
586                    sval = bui.timestring(
587                        (next_reward_ad_time - now).total_seconds(), centi=False
588                    )
589                else:
590                    sval = ''
591                bui.textwidget(edit=self._ad_time_text, text=sval)
592
593        # If this is our first update, assign immediately; otherwise kick
594        # off a smooth transition if the value has changed.
595        if self._smooth_ticket_count is None:
596            self._smooth_ticket_count = float(self._ticket_count)
597            self._smooth_update()  # will set the text widget
598
599        elif (
600            self._ticket_count != int(self._smooth_ticket_count)
601            and self._smooth_update_timer is None
602        ):
603            self._smooth_update_timer = bui.AppTimer(
604                0.05, bui.WeakCall(self._smooth_update), repeat=True
605            )
606            diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
607            self._smooth_increase_speed = (
608                diff / 100.0
609                if diff >= 5000
610                else (
611                    diff / 50.0
612                    if diff >= 1500
613                    else diff / 30.0 if diff >= 500 else diff / 15.0
614                )
615            )
616
617    def _disabled_press(self) -> None:
618        plus = bui.app.plus
619        assert plus is not None
620
621        # If we're on a platform without purchases, inform the user they
622        # can link their accounts and buy stuff elsewhere.
623        app = bui.app
624        assert app.classic is not None
625        if (
626            app.env.test
627            or (
628                app.classic.platform == 'android'
629                and app.classic.subplatform in ['oculus', 'cardboard']
630            )
631        ) and plus.get_v1_account_misc_read_val('allowAccountLinking2', False):
632            bui.screenmessage(
633                bui.Lstr(resource=self._r + '.unavailableLinkAccountText'),
634                color=(1, 0.5, 0),
635            )
636        else:
637            bui.screenmessage(
638                bui.Lstr(resource=self._r + '.unavailableText'),
639                color=(1, 0.5, 0),
640            )
641        bui.getsound('error').play()
642
643    def _purchase(self, item: str) -> None:
644        from bauiv1lib import account
645        from bauiv1lib import appinvite
646
647        plus = bui.app.plus
648        assert plus is not None
649
650        if bui.app.classic is None:
651            raise RuntimeError('This requires classic support.')
652
653        if item == 'app_invite':
654            if plus.get_v1_account_state() != 'signed_in':
655                account.show_sign_in_prompt()
656                return
657            appinvite.handle_app_invites_press()
658            return
659
660        # Here we ping the server to ask if it's valid for us to
661        # purchase this.. (better to fail now than after we've paid
662        # locally).
663        app = bui.app
664        assert app.classic is not None
665        bui.app.classic.master_server_v1_get(
666            'bsAccountPurchaseCheck',
667            {
668                'item': item,
669                'platform': app.classic.platform,
670                'subplatform': app.classic.subplatform,
671                'version': app.env.engine_version,
672                'buildNumber': app.env.engine_build_number,
673            },
674            callback=bui.WeakCall(self._purchase_check_result, item),
675        )
676
677    def _purchase_check_result(
678        self, item: str, result: dict[str, Any] | None
679    ) -> None:
680        if result is None:
681            bui.getsound('error').play()
682            bui.screenmessage(
683                bui.Lstr(resource='internal.unavailableNoConnectionText'),
684                color=(1, 0, 0),
685            )
686        else:
687            if result['allow']:
688                self._do_purchase(item)
689            else:
690                if result['reason'] == 'versionTooOld':
691                    bui.getsound('error').play()
692                    bui.screenmessage(
693                        bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
694                        color=(1, 0, 0),
695                    )
696                else:
697                    bui.getsound('error').play()
698                    bui.screenmessage(
699                        bui.Lstr(resource='getTicketsWindow.unavailableText'),
700                        color=(1, 0, 0),
701                    )
702
703    # Actually start the purchase locally.
704    def _do_purchase(self, item: str) -> None:
705        plus = bui.app.plus
706        assert plus is not None
707
708        if item == 'ad':
709            import datetime
710
711            # If ads are disabled until some time, error.
712            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
713                'nextRewardAdTime', None
714            )
715            if next_reward_ad_time is not None:
716                next_reward_ad_time = datetime.datetime.fromtimestamp(
717                    next_reward_ad_time, datetime.UTC
718                )
719            now = utc_now()
720            if (
721                next_reward_ad_time is not None and next_reward_ad_time > now
722            ) or self._ad_button_greyed:
723                bui.getsound('error').play()
724                bui.screenmessage(
725                    bui.Lstr(
726                        resource='getTicketsWindow.unavailableTemporarilyText'
727                    ),
728                    color=(1, 0, 0),
729                )
730            elif self._enable_ad_button:
731                assert bui.app.classic is not None
732                bui.app.classic.ads.show_ad('tickets')
733        else:
734            plus.purchase(item)
735
736    def _back(self) -> None:
737        from bauiv1lib.store import browser
738
739        # No-op if our underlying widget is dead or on its way out.
740        if not self._root_widget or self._root_widget.transitioning_out:
741            return
742
743        if self._transitioning_out:
744            return
745
746        bui.containerwidget(
747            edit=self._root_widget, transition=self._transition_out
748        )
749        if not self._modal:
750            window = browser.StoreBrowserWindow(
751                transition='in_left',
752                modal=self._from_modal_store,
753                back_location=self._store_back_location,
754            ).get_root_widget()
755            if not self._from_modal_store:
756                assert bui.app.classic is not None
757                bui.app.ui_v1.set_main_menu_window(
758                    window, from_window=self._root_widget
759                )
760        self._transitioning_out = True
761
762
763def show_get_tickets_prompt() -> None:
764    """Show a 'not enough tickets' prompt with an option to purchase more.
765
766    Note that the purchase option may not always be available
767    depending on the build of the game.
768    """
769    from bauiv1lib.confirm import ConfirmWindow
770
771    assert bui.app.classic is not None
772    if bui.app.classic.allow_ticket_purchases:
773        ConfirmWindow(
774            bui.Lstr(
775                translate=(
776                    'serverResponses',
777                    'You don\'t have enough tickets for this!',
778                )
779            ),
780            lambda: GetCurrencyWindow(modal=True),
781            ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
782            width=460,
783            height=130,
784        )
785    else:
786        ConfirmWindow(
787            bui.Lstr(
788                translate=(
789                    'serverResponses',
790                    'You don\'t have enough tickets for this!',
791                )
792            ),
793            cancel_button=False,
794            width=460,
795            height=130,
796        )
class GetCurrencyWindow(bauiv1._uitypes.Window):
 18class GetCurrencyWindow(bui.Window):
 19    """Window for purchasing/acquiring currency."""
 20
 21    def __init__(
 22        self,
 23        transition: str = 'in_right',
 24        from_modal_store: bool = False,
 25        modal: bool = False,
 26        origin_widget: bui.Widget | None = None,
 27        store_back_location: str | None = None,
 28    ):
 29        # pylint: disable=too-many-statements
 30        # pylint: disable=too-many-locals
 31
 32        plus = bui.app.plus
 33        assert plus is not None
 34
 35        bui.set_analytics_screen('Get Tickets Window')
 36
 37        self._transitioning_out = False
 38        self._store_back_location = store_back_location  # ew.
 39
 40        self._ad_button_greyed = False
 41        self._smooth_update_timer: bui.AppTimer | None = None
 42        self._ad_button = None
 43        self._ad_label = None
 44        self._ad_image = None
 45        self._ad_time_text = None
 46
 47        # If they provided an origin-widget, scale up from that.
 48        scale_origin: tuple[float, float] | None
 49        if origin_widget is not None:
 50            self._transition_out = 'out_scale'
 51            scale_origin = origin_widget.get_screen_space_center()
 52            transition = 'in_scale'
 53        else:
 54            self._transition_out = 'out_right'
 55            scale_origin = None
 56
 57        assert bui.app.classic is not None
 58        uiscale = bui.app.ui_v1.uiscale
 59        self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
 60        x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
 61        self._height = 480.0
 62
 63        self._modal = modal
 64        self._from_modal_store = from_modal_store
 65        self._r = 'getTicketsWindow'
 66
 67        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 68
 69        super().__init__(
 70            root_widget=bui.containerwidget(
 71                size=(self._width, self._height + top_extra),
 72                transition=transition,
 73                scale_origin_stack_offset=scale_origin,
 74                color=(0.4, 0.37, 0.55),
 75                scale=(
 76                    1.63
 77                    if uiscale is bui.UIScale.SMALL
 78                    else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
 79                ),
 80                stack_offset=(
 81                    (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
 82                ),
 83            )
 84        )
 85
 86        btn = bui.buttonwidget(
 87            parent=self._root_widget,
 88            position=(55 + x_inset, self._height - 79),
 89            size=(140, 60),
 90            scale=1.0,
 91            autoselect=True,
 92            label=bui.Lstr(resource='doneText' if modal else 'backText'),
 93            button_type='regular' if modal else 'back',
 94            on_activate_call=self._back,
 95        )
 96
 97        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 98
 99        bui.textwidget(
100            parent=self._root_widget,
101            position=(self._width * 0.5, self._height - 55),
102            size=(0, 0),
103            color=bui.app.ui_v1.title_color,
104            scale=1.2,
105            h_align='center',
106            v_align='center',
107            text=bui.Lstr(resource=self._r + '.titleText'),
108            maxwidth=290,
109        )
110
111        if not modal:
112            bui.buttonwidget(
113                edit=btn,
114                button_type='backSmall',
115                size=(60, 60),
116                label=bui.charstr(bui.SpecialChar.BACK),
117            )
118
119        b_size = (220.0, 180.0)
120        v = self._height - b_size[1] - 80
121        spacing = 1
122
123        self._ad_button = None
124
125        def _add_button(
126            item: str,
127            position: tuple[float, float],
128            size: tuple[float, float],
129            label: bui.Lstr,
130            price: str | None = None,
131            tex_name: str | None = None,
132            tex_opacity: float = 1.0,
133            tex_scale: float = 1.0,
134            enabled: bool = True,
135            text_scale: float = 1.0,
136        ) -> bui.Widget:
137            btn2 = bui.buttonwidget(
138                parent=self._root_widget,
139                position=position,
140                button_type='square',
141                size=size,
142                label='',
143                autoselect=True,
144                color=None if enabled else (0.5, 0.5, 0.5),
145                on_activate_call=(
146                    bui.Call(self._purchase, item)
147                    if enabled
148                    else self._disabled_press
149                ),
150            )
151            txt = bui.textwidget(
152                parent=self._root_widget,
153                text=label,
154                position=(
155                    position[0] + size[0] * 0.5,
156                    position[1] + size[1] * 0.3,
157                ),
158                scale=text_scale,
159                maxwidth=size[0] * 0.75,
160                size=(0, 0),
161                h_align='center',
162                v_align='center',
163                draw_controller=btn2,
164                color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2),
165            )
166            if price is not None and enabled:
167                bui.textwidget(
168                    parent=self._root_widget,
169                    text=price,
170                    position=(
171                        position[0] + size[0] * 0.5,
172                        position[1] + size[1] * 0.17,
173                    ),
174                    scale=0.7,
175                    maxwidth=size[0] * 0.75,
176                    size=(0, 0),
177                    h_align='center',
178                    v_align='center',
179                    draw_controller=btn2,
180                    color=(0.4, 0.9, 0.4, 1.0),
181                )
182            i = None
183            if tex_name is not None:
184                tex_size = 90.0 * tex_scale
185                i = bui.imagewidget(
186                    parent=self._root_widget,
187                    texture=bui.gettexture(tex_name),
188                    position=(
189                        position[0] + size[0] * 0.5 - tex_size * 0.5,
190                        position[1] + size[1] * 0.66 - tex_size * 0.5,
191                    ),
192                    size=(tex_size, tex_size),
193                    draw_controller=btn2,
194                    opacity=tex_opacity * (1.0 if enabled else 0.25),
195                )
196            if item == 'ad':
197                self._ad_button = btn2
198                self._ad_label = txt
199                assert i is not None
200                self._ad_image = i
201                self._ad_time_text = bui.textwidget(
202                    parent=self._root_widget,
203                    text='1m 10s',
204                    position=(
205                        position[0] + size[0] * 0.5,
206                        position[1] + size[1] * 0.5,
207                    ),
208                    scale=text_scale * 1.2,
209                    maxwidth=size[0] * 0.85,
210                    size=(0, 0),
211                    h_align='center',
212                    v_align='center',
213                    draw_controller=btn2,
214                    color=(0.4, 0.9, 0.4, 1.0),
215                )
216            return btn2
217
218        rsrc = self._r + '.ticketsText'
219
220        c2txt = bui.Lstr(
221            resource=rsrc,
222            subs=[
223                (
224                    '${COUNT}',
225                    str(
226                        plus.get_v1_account_misc_read_val('tickets2Amount', 500)
227                    ),
228                )
229            ],
230        )
231        c3txt = bui.Lstr(
232            resource=rsrc,
233            subs=[
234                (
235                    '${COUNT}',
236                    str(
237                        plus.get_v1_account_misc_read_val(
238                            'tickets3Amount', 1500
239                        )
240                    ),
241                )
242            ],
243        )
244        c4txt = bui.Lstr(
245            resource=rsrc,
246            subs=[
247                (
248                    '${COUNT}',
249                    str(
250                        plus.get_v1_account_misc_read_val(
251                            'tickets4Amount', 5000
252                        )
253                    ),
254                )
255            ],
256        )
257        c5txt = bui.Lstr(
258            resource=rsrc,
259            subs=[
260                (
261                    '${COUNT}',
262                    str(
263                        plus.get_v1_account_misc_read_val(
264                            'tickets5Amount', 15000
265                        )
266                    ),
267                )
268            ],
269        )
270
271        h = 110.0
272
273        # Enable buttons if we have prices.
274        tickets2_price = plus.get_price('tickets2')
275        tickets3_price = plus.get_price('tickets3')
276        tickets4_price = plus.get_price('tickets4')
277        tickets5_price = plus.get_price('tickets5')
278
279        # TEMP
280        # tickets1_price = '$0.99'
281        # tickets2_price = '$4.99'
282        # tickets3_price = '$9.99'
283        # tickets4_price = '$19.99'
284        # tickets5_price = '$49.99'
285
286        _add_button(
287            'tickets2',
288            enabled=(tickets2_price is not None),
289            position=(
290                self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
291                v,
292            ),
293            size=b_size,
294            label=c2txt,
295            price=tickets2_price,
296            tex_name='ticketsMore',
297        )  # 0.99-ish
298        _add_button(
299            'tickets3',
300            enabled=(tickets3_price is not None),
301            position=(
302                self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
303                v,
304            ),
305            size=b_size,
306            label=c3txt,
307            price=tickets3_price,
308            tex_name='ticketRoll',
309        )  # 4.99-ish
310        v -= b_size[1] - 5
311        _add_button(
312            'tickets4',
313            enabled=(tickets4_price is not None),
314            position=(
315                self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
316                v,
317            ),
318            size=b_size,
319            label=c4txt,
320            price=tickets4_price,
321            tex_name='ticketRollBig',
322            tex_scale=1.2,
323        )  # 9.99-ish
324        _add_button(
325            'tickets5',
326            enabled=(tickets5_price is not None),
327            position=(
328                self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
329                v,
330            ),
331            size=b_size,
332            label=c5txt,
333            price=tickets5_price,
334            tex_name='ticketRolls',
335            tex_scale=1.2,
336        )  # 19.99-ish
337
338        self._enable_ad_button = plus.has_video_ads()
339        h = self._width * 0.5 + 110.0
340        v = self._height - b_size[1] - 115.0
341
342        if self._enable_ad_button:
343            h_offs = 35
344            b_size_3 = (150, 120)
345            cdb = _add_button(
346                'ad',
347                position=(h + h_offs, v),
348                size=b_size_3,
349                label=bui.Lstr(
350                    resource=self._r + '.ticketsFromASponsorText',
351                    subs=[
352                        (
353                            '${COUNT}',
354                            str(
355                                plus.get_v1_account_misc_read_val(
356                                    'sponsorTickets', 5
357                                )
358                            ),
359                        )
360                    ],
361                ),
362                tex_name='ticketsMore',
363                enabled=self._enable_ad_button,
364                tex_opacity=0.6,
365                tex_scale=0.7,
366                text_scale=0.7,
367            )
368            bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
369
370            self._ad_free_text = bui.textwidget(
371                parent=self._root_widget,
372                text=bui.Lstr(resource=self._r + '.freeText'),
373                position=(
374                    h + h_offs + b_size_3[0] * 0.5,
375                    v + b_size_3[1] * 0.5 + 25,
376                ),
377                size=(0, 0),
378                color=(1, 1, 0, 1.0),
379                draw_controller=cdb,
380                rotate=15,
381                shadow=1.0,
382                maxwidth=150,
383                h_align='center',
384                v_align='center',
385                scale=1.0,
386            )
387            v -= 125
388        else:
389            v -= 20
390
391        if bool(True):
392            h_offs = 35
393            b_size_3 = (150, 120)
394            cdb = _add_button(
395                'app_invite',
396                position=(h + h_offs, v),
397                size=b_size_3,
398                label=bui.Lstr(
399                    resource='gatherWindow.earnTicketsForRecommendingText',
400                    subs=[
401                        (
402                            '${COUNT}',
403                            str(
404                                plus.get_v1_account_misc_read_val(
405                                    'sponsorTickets', 5
406                                )
407                            ),
408                        )
409                    ],
410                ),
411                tex_name='ticketsMore',
412                enabled=True,
413                tex_opacity=0.6,
414                tex_scale=0.7,
415                text_scale=0.7,
416            )
417            bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
418
419            bui.textwidget(
420                parent=self._root_widget,
421                text=bui.Lstr(resource=self._r + '.freeText'),
422                position=(
423                    h + h_offs + b_size_3[0] * 0.5,
424                    v + b_size_3[1] * 0.5 + 25,
425                ),
426                size=(0, 0),
427                color=(1, 1, 0, 1.0),
428                draw_controller=cdb,
429                rotate=15,
430                shadow=1.0,
431                maxwidth=150,
432                h_align='center',
433                v_align='center',
434                scale=1.0,
435            )
436            tc_y_offs = 0
437        else:
438            tc_y_offs = 0
439
440        h = self._width - (185 + x_inset)
441        v = self._height - 95 + tc_y_offs
442
443        txt1 = (
444            bui.Lstr(resource=self._r + '.youHaveText')
445            .evaluate()
446            .partition('${COUNT}')[0]
447            .strip()
448        )
449        txt2 = (
450            bui.Lstr(resource=self._r + '.youHaveText')
451            .evaluate()
452            .rpartition('${COUNT}')[-1]
453            .strip()
454        )
455
456        bui.textwidget(
457            parent=self._root_widget,
458            text=txt1,
459            position=(h, v),
460            size=(0, 0),
461            color=(0.5, 0.5, 0.6),
462            maxwidth=200,
463            h_align='center',
464            v_align='center',
465            scale=0.8,
466        )
467        v -= 30
468        self._ticket_count_text = bui.textwidget(
469            parent=self._root_widget,
470            position=(h, v),
471            size=(0, 0),
472            color=(0.2, 1.0, 0.2),
473            maxwidth=200,
474            h_align='center',
475            v_align='center',
476            scale=1.6,
477        )
478        v -= 30
479        bui.textwidget(
480            parent=self._root_widget,
481            text=txt2,
482            position=(h, v),
483            size=(0, 0),
484            color=(0.5, 0.5, 0.6),
485            maxwidth=200,
486            h_align='center',
487            v_align='center',
488            scale=0.8,
489        )
490
491        self._ticking_sound: bui.Sound | None = None
492        self._smooth_ticket_count: float | None = None
493        self._ticket_count = 0
494        self._update()
495        self._update_timer = bui.AppTimer(
496            1.0, bui.WeakCall(self._update), repeat=True
497        )
498        self._smooth_increase_speed = 1.0
499
500    def __del__(self) -> None:
501        if self._ticking_sound is not None:
502            self._ticking_sound.stop()
503            self._ticking_sound = None
504
505    def _smooth_update(self) -> None:
506        if not self._ticket_count_text:
507            self._smooth_update_timer = None
508            return
509
510        finished = False
511
512        # If we're going down, do it immediately.
513        assert self._smooth_ticket_count is not None
514        if int(self._smooth_ticket_count) >= self._ticket_count:
515            self._smooth_ticket_count = float(self._ticket_count)
516            finished = True
517        else:
518            # We're going up; start a sound if need be.
519            self._smooth_ticket_count = min(
520                self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
521                self._ticket_count,
522            )
523            if int(self._smooth_ticket_count) >= self._ticket_count:
524                finished = True
525                self._smooth_ticket_count = float(self._ticket_count)
526            elif self._ticking_sound is None:
527                self._ticking_sound = bui.getsound('scoreIncrease')
528                self._ticking_sound.play()
529
530        bui.textwidget(
531            edit=self._ticket_count_text,
532            text=str(int(self._smooth_ticket_count)),
533        )
534
535        # If we've reached the target, kill the timer/sound/etc.
536        if finished:
537            self._smooth_update_timer = None
538            if self._ticking_sound is not None:
539                self._ticking_sound.stop()
540                self._ticking_sound = None
541                bui.getsound('cashRegister2').play()
542
543    def _update(self) -> None:
544        import datetime
545
546        plus = bui.app.plus
547        assert plus is not None
548
549        # If we somehow get signed out, just die.
550        if plus.get_v1_account_state() != 'signed_in':
551            self._back()
552            return
553
554        self._ticket_count = plus.get_v1_account_ticket_count()
555
556        # Update our incentivized ad button depending on whether ads are
557        # available.
558        if self._ad_button is not None:
559            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
560                'nextRewardAdTime', None
561            )
562            if next_reward_ad_time is not None:
563                next_reward_ad_time = datetime.datetime.fromtimestamp(
564                    next_reward_ad_time, datetime.UTC
565                )
566            now = utc_now()
567            if plus.have_incentivized_ad() and (
568                next_reward_ad_time is None or next_reward_ad_time <= now
569            ):
570                self._ad_button_greyed = False
571                bui.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
572                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
573                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1))
574                bui.imagewidget(edit=self._ad_image, opacity=0.6)
575                bui.textwidget(edit=self._ad_time_text, text='')
576            else:
577                self._ad_button_greyed = True
578                bui.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5))
579                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2))
580                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
581                bui.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
582                sval: str | bui.Lstr
583                if (
584                    next_reward_ad_time is not None
585                    and next_reward_ad_time > now
586                ):
587                    sval = bui.timestring(
588                        (next_reward_ad_time - now).total_seconds(), centi=False
589                    )
590                else:
591                    sval = ''
592                bui.textwidget(edit=self._ad_time_text, text=sval)
593
594        # If this is our first update, assign immediately; otherwise kick
595        # off a smooth transition if the value has changed.
596        if self._smooth_ticket_count is None:
597            self._smooth_ticket_count = float(self._ticket_count)
598            self._smooth_update()  # will set the text widget
599
600        elif (
601            self._ticket_count != int(self._smooth_ticket_count)
602            and self._smooth_update_timer is None
603        ):
604            self._smooth_update_timer = bui.AppTimer(
605                0.05, bui.WeakCall(self._smooth_update), repeat=True
606            )
607            diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
608            self._smooth_increase_speed = (
609                diff / 100.0
610                if diff >= 5000
611                else (
612                    diff / 50.0
613                    if diff >= 1500
614                    else diff / 30.0 if diff >= 500 else diff / 15.0
615                )
616            )
617
618    def _disabled_press(self) -> None:
619        plus = bui.app.plus
620        assert plus is not None
621
622        # If we're on a platform without purchases, inform the user they
623        # can link their accounts and buy stuff elsewhere.
624        app = bui.app
625        assert app.classic is not None
626        if (
627            app.env.test
628            or (
629                app.classic.platform == 'android'
630                and app.classic.subplatform in ['oculus', 'cardboard']
631            )
632        ) and plus.get_v1_account_misc_read_val('allowAccountLinking2', False):
633            bui.screenmessage(
634                bui.Lstr(resource=self._r + '.unavailableLinkAccountText'),
635                color=(1, 0.5, 0),
636            )
637        else:
638            bui.screenmessage(
639                bui.Lstr(resource=self._r + '.unavailableText'),
640                color=(1, 0.5, 0),
641            )
642        bui.getsound('error').play()
643
644    def _purchase(self, item: str) -> None:
645        from bauiv1lib import account
646        from bauiv1lib import appinvite
647
648        plus = bui.app.plus
649        assert plus is not None
650
651        if bui.app.classic is None:
652            raise RuntimeError('This requires classic support.')
653
654        if item == 'app_invite':
655            if plus.get_v1_account_state() != 'signed_in':
656                account.show_sign_in_prompt()
657                return
658            appinvite.handle_app_invites_press()
659            return
660
661        # Here we ping the server to ask if it's valid for us to
662        # purchase this.. (better to fail now than after we've paid
663        # locally).
664        app = bui.app
665        assert app.classic is not None
666        bui.app.classic.master_server_v1_get(
667            'bsAccountPurchaseCheck',
668            {
669                'item': item,
670                'platform': app.classic.platform,
671                'subplatform': app.classic.subplatform,
672                'version': app.env.engine_version,
673                'buildNumber': app.env.engine_build_number,
674            },
675            callback=bui.WeakCall(self._purchase_check_result, item),
676        )
677
678    def _purchase_check_result(
679        self, item: str, result: dict[str, Any] | None
680    ) -> None:
681        if result is None:
682            bui.getsound('error').play()
683            bui.screenmessage(
684                bui.Lstr(resource='internal.unavailableNoConnectionText'),
685                color=(1, 0, 0),
686            )
687        else:
688            if result['allow']:
689                self._do_purchase(item)
690            else:
691                if result['reason'] == 'versionTooOld':
692                    bui.getsound('error').play()
693                    bui.screenmessage(
694                        bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
695                        color=(1, 0, 0),
696                    )
697                else:
698                    bui.getsound('error').play()
699                    bui.screenmessage(
700                        bui.Lstr(resource='getTicketsWindow.unavailableText'),
701                        color=(1, 0, 0),
702                    )
703
704    # Actually start the purchase locally.
705    def _do_purchase(self, item: str) -> None:
706        plus = bui.app.plus
707        assert plus is not None
708
709        if item == 'ad':
710            import datetime
711
712            # If ads are disabled until some time, error.
713            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
714                'nextRewardAdTime', None
715            )
716            if next_reward_ad_time is not None:
717                next_reward_ad_time = datetime.datetime.fromtimestamp(
718                    next_reward_ad_time, datetime.UTC
719                )
720            now = utc_now()
721            if (
722                next_reward_ad_time is not None and next_reward_ad_time > now
723            ) or self._ad_button_greyed:
724                bui.getsound('error').play()
725                bui.screenmessage(
726                    bui.Lstr(
727                        resource='getTicketsWindow.unavailableTemporarilyText'
728                    ),
729                    color=(1, 0, 0),
730                )
731            elif self._enable_ad_button:
732                assert bui.app.classic is not None
733                bui.app.classic.ads.show_ad('tickets')
734        else:
735            plus.purchase(item)
736
737    def _back(self) -> None:
738        from bauiv1lib.store import browser
739
740        # No-op if our underlying widget is dead or on its way out.
741        if not self._root_widget or self._root_widget.transitioning_out:
742            return
743
744        if self._transitioning_out:
745            return
746
747        bui.containerwidget(
748            edit=self._root_widget, transition=self._transition_out
749        )
750        if not self._modal:
751            window = browser.StoreBrowserWindow(
752                transition='in_left',
753                modal=self._from_modal_store,
754                back_location=self._store_back_location,
755            ).get_root_widget()
756            if not self._from_modal_store:
757                assert bui.app.classic is not None
758                bui.app.ui_v1.set_main_menu_window(
759                    window, from_window=self._root_widget
760                )
761        self._transitioning_out = True

Window for purchasing/acquiring currency.

GetCurrencyWindow( transition: str = 'in_right', from_modal_store: bool = False, modal: bool = False, origin_widget: _bauiv1.Widget | None = None, store_back_location: str | None = None)
 21    def __init__(
 22        self,
 23        transition: str = 'in_right',
 24        from_modal_store: bool = False,
 25        modal: bool = False,
 26        origin_widget: bui.Widget | None = None,
 27        store_back_location: str | None = None,
 28    ):
 29        # pylint: disable=too-many-statements
 30        # pylint: disable=too-many-locals
 31
 32        plus = bui.app.plus
 33        assert plus is not None
 34
 35        bui.set_analytics_screen('Get Tickets Window')
 36
 37        self._transitioning_out = False
 38        self._store_back_location = store_back_location  # ew.
 39
 40        self._ad_button_greyed = False
 41        self._smooth_update_timer: bui.AppTimer | None = None
 42        self._ad_button = None
 43        self._ad_label = None
 44        self._ad_image = None
 45        self._ad_time_text = None
 46
 47        # If they provided an origin-widget, scale up from that.
 48        scale_origin: tuple[float, float] | None
 49        if origin_widget is not None:
 50            self._transition_out = 'out_scale'
 51            scale_origin = origin_widget.get_screen_space_center()
 52            transition = 'in_scale'
 53        else:
 54            self._transition_out = 'out_right'
 55            scale_origin = None
 56
 57        assert bui.app.classic is not None
 58        uiscale = bui.app.ui_v1.uiscale
 59        self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
 60        x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
 61        self._height = 480.0
 62
 63        self._modal = modal
 64        self._from_modal_store = from_modal_store
 65        self._r = 'getTicketsWindow'
 66
 67        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 68
 69        super().__init__(
 70            root_widget=bui.containerwidget(
 71                size=(self._width, self._height + top_extra),
 72                transition=transition,
 73                scale_origin_stack_offset=scale_origin,
 74                color=(0.4, 0.37, 0.55),
 75                scale=(
 76                    1.63
 77                    if uiscale is bui.UIScale.SMALL
 78                    else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
 79                ),
 80                stack_offset=(
 81                    (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
 82                ),
 83            )
 84        )
 85
 86        btn = bui.buttonwidget(
 87            parent=self._root_widget,
 88            position=(55 + x_inset, self._height - 79),
 89            size=(140, 60),
 90            scale=1.0,
 91            autoselect=True,
 92            label=bui.Lstr(resource='doneText' if modal else 'backText'),
 93            button_type='regular' if modal else 'back',
 94            on_activate_call=self._back,
 95        )
 96
 97        bui.containerwidget(edit=self._root_widget, cancel_button=btn)
 98
 99        bui.textwidget(
100            parent=self._root_widget,
101            position=(self._width * 0.5, self._height - 55),
102            size=(0, 0),
103            color=bui.app.ui_v1.title_color,
104            scale=1.2,
105            h_align='center',
106            v_align='center',
107            text=bui.Lstr(resource=self._r + '.titleText'),
108            maxwidth=290,
109        )
110
111        if not modal:
112            bui.buttonwidget(
113                edit=btn,
114                button_type='backSmall',
115                size=(60, 60),
116                label=bui.charstr(bui.SpecialChar.BACK),
117            )
118
119        b_size = (220.0, 180.0)
120        v = self._height - b_size[1] - 80
121        spacing = 1
122
123        self._ad_button = None
124
125        def _add_button(
126            item: str,
127            position: tuple[float, float],
128            size: tuple[float, float],
129            label: bui.Lstr,
130            price: str | None = None,
131            tex_name: str | None = None,
132            tex_opacity: float = 1.0,
133            tex_scale: float = 1.0,
134            enabled: bool = True,
135            text_scale: float = 1.0,
136        ) -> bui.Widget:
137            btn2 = bui.buttonwidget(
138                parent=self._root_widget,
139                position=position,
140                button_type='square',
141                size=size,
142                label='',
143                autoselect=True,
144                color=None if enabled else (0.5, 0.5, 0.5),
145                on_activate_call=(
146                    bui.Call(self._purchase, item)
147                    if enabled
148                    else self._disabled_press
149                ),
150            )
151            txt = bui.textwidget(
152                parent=self._root_widget,
153                text=label,
154                position=(
155                    position[0] + size[0] * 0.5,
156                    position[1] + size[1] * 0.3,
157                ),
158                scale=text_scale,
159                maxwidth=size[0] * 0.75,
160                size=(0, 0),
161                h_align='center',
162                v_align='center',
163                draw_controller=btn2,
164                color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2),
165            )
166            if price is not None and enabled:
167                bui.textwidget(
168                    parent=self._root_widget,
169                    text=price,
170                    position=(
171                        position[0] + size[0] * 0.5,
172                        position[1] + size[1] * 0.17,
173                    ),
174                    scale=0.7,
175                    maxwidth=size[0] * 0.75,
176                    size=(0, 0),
177                    h_align='center',
178                    v_align='center',
179                    draw_controller=btn2,
180                    color=(0.4, 0.9, 0.4, 1.0),
181                )
182            i = None
183            if tex_name is not None:
184                tex_size = 90.0 * tex_scale
185                i = bui.imagewidget(
186                    parent=self._root_widget,
187                    texture=bui.gettexture(tex_name),
188                    position=(
189                        position[0] + size[0] * 0.5 - tex_size * 0.5,
190                        position[1] + size[1] * 0.66 - tex_size * 0.5,
191                    ),
192                    size=(tex_size, tex_size),
193                    draw_controller=btn2,
194                    opacity=tex_opacity * (1.0 if enabled else 0.25),
195                )
196            if item == 'ad':
197                self._ad_button = btn2
198                self._ad_label = txt
199                assert i is not None
200                self._ad_image = i
201                self._ad_time_text = bui.textwidget(
202                    parent=self._root_widget,
203                    text='1m 10s',
204                    position=(
205                        position[0] + size[0] * 0.5,
206                        position[1] + size[1] * 0.5,
207                    ),
208                    scale=text_scale * 1.2,
209                    maxwidth=size[0] * 0.85,
210                    size=(0, 0),
211                    h_align='center',
212                    v_align='center',
213                    draw_controller=btn2,
214                    color=(0.4, 0.9, 0.4, 1.0),
215                )
216            return btn2
217
218        rsrc = self._r + '.ticketsText'
219
220        c2txt = bui.Lstr(
221            resource=rsrc,
222            subs=[
223                (
224                    '${COUNT}',
225                    str(
226                        plus.get_v1_account_misc_read_val('tickets2Amount', 500)
227                    ),
228                )
229            ],
230        )
231        c3txt = bui.Lstr(
232            resource=rsrc,
233            subs=[
234                (
235                    '${COUNT}',
236                    str(
237                        plus.get_v1_account_misc_read_val(
238                            'tickets3Amount', 1500
239                        )
240                    ),
241                )
242            ],
243        )
244        c4txt = bui.Lstr(
245            resource=rsrc,
246            subs=[
247                (
248                    '${COUNT}',
249                    str(
250                        plus.get_v1_account_misc_read_val(
251                            'tickets4Amount', 5000
252                        )
253                    ),
254                )
255            ],
256        )
257        c5txt = bui.Lstr(
258            resource=rsrc,
259            subs=[
260                (
261                    '${COUNT}',
262                    str(
263                        plus.get_v1_account_misc_read_val(
264                            'tickets5Amount', 15000
265                        )
266                    ),
267                )
268            ],
269        )
270
271        h = 110.0
272
273        # Enable buttons if we have prices.
274        tickets2_price = plus.get_price('tickets2')
275        tickets3_price = plus.get_price('tickets3')
276        tickets4_price = plus.get_price('tickets4')
277        tickets5_price = plus.get_price('tickets5')
278
279        # TEMP
280        # tickets1_price = '$0.99'
281        # tickets2_price = '$4.99'
282        # tickets3_price = '$9.99'
283        # tickets4_price = '$19.99'
284        # tickets5_price = '$49.99'
285
286        _add_button(
287            'tickets2',
288            enabled=(tickets2_price is not None),
289            position=(
290                self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
291                v,
292            ),
293            size=b_size,
294            label=c2txt,
295            price=tickets2_price,
296            tex_name='ticketsMore',
297        )  # 0.99-ish
298        _add_button(
299            'tickets3',
300            enabled=(tickets3_price is not None),
301            position=(
302                self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
303                v,
304            ),
305            size=b_size,
306            label=c3txt,
307            price=tickets3_price,
308            tex_name='ticketRoll',
309        )  # 4.99-ish
310        v -= b_size[1] - 5
311        _add_button(
312            'tickets4',
313            enabled=(tickets4_price is not None),
314            position=(
315                self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
316                v,
317            ),
318            size=b_size,
319            label=c4txt,
320            price=tickets4_price,
321            tex_name='ticketRollBig',
322            tex_scale=1.2,
323        )  # 9.99-ish
324        _add_button(
325            'tickets5',
326            enabled=(tickets5_price is not None),
327            position=(
328                self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
329                v,
330            ),
331            size=b_size,
332            label=c5txt,
333            price=tickets5_price,
334            tex_name='ticketRolls',
335            tex_scale=1.2,
336        )  # 19.99-ish
337
338        self._enable_ad_button = plus.has_video_ads()
339        h = self._width * 0.5 + 110.0
340        v = self._height - b_size[1] - 115.0
341
342        if self._enable_ad_button:
343            h_offs = 35
344            b_size_3 = (150, 120)
345            cdb = _add_button(
346                'ad',
347                position=(h + h_offs, v),
348                size=b_size_3,
349                label=bui.Lstr(
350                    resource=self._r + '.ticketsFromASponsorText',
351                    subs=[
352                        (
353                            '${COUNT}',
354                            str(
355                                plus.get_v1_account_misc_read_val(
356                                    'sponsorTickets', 5
357                                )
358                            ),
359                        )
360                    ],
361                ),
362                tex_name='ticketsMore',
363                enabled=self._enable_ad_button,
364                tex_opacity=0.6,
365                tex_scale=0.7,
366                text_scale=0.7,
367            )
368            bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
369
370            self._ad_free_text = bui.textwidget(
371                parent=self._root_widget,
372                text=bui.Lstr(resource=self._r + '.freeText'),
373                position=(
374                    h + h_offs + b_size_3[0] * 0.5,
375                    v + b_size_3[1] * 0.5 + 25,
376                ),
377                size=(0, 0),
378                color=(1, 1, 0, 1.0),
379                draw_controller=cdb,
380                rotate=15,
381                shadow=1.0,
382                maxwidth=150,
383                h_align='center',
384                v_align='center',
385                scale=1.0,
386            )
387            v -= 125
388        else:
389            v -= 20
390
391        if bool(True):
392            h_offs = 35
393            b_size_3 = (150, 120)
394            cdb = _add_button(
395                'app_invite',
396                position=(h + h_offs, v),
397                size=b_size_3,
398                label=bui.Lstr(
399                    resource='gatherWindow.earnTicketsForRecommendingText',
400                    subs=[
401                        (
402                            '${COUNT}',
403                            str(
404                                plus.get_v1_account_misc_read_val(
405                                    'sponsorTickets', 5
406                                )
407                            ),
408                        )
409                    ],
410                ),
411                tex_name='ticketsMore',
412                enabled=True,
413                tex_opacity=0.6,
414                tex_scale=0.7,
415                text_scale=0.7,
416            )
417            bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
418
419            bui.textwidget(
420                parent=self._root_widget,
421                text=bui.Lstr(resource=self._r + '.freeText'),
422                position=(
423                    h + h_offs + b_size_3[0] * 0.5,
424                    v + b_size_3[1] * 0.5 + 25,
425                ),
426                size=(0, 0),
427                color=(1, 1, 0, 1.0),
428                draw_controller=cdb,
429                rotate=15,
430                shadow=1.0,
431                maxwidth=150,
432                h_align='center',
433                v_align='center',
434                scale=1.0,
435            )
436            tc_y_offs = 0
437        else:
438            tc_y_offs = 0
439
440        h = self._width - (185 + x_inset)
441        v = self._height - 95 + tc_y_offs
442
443        txt1 = (
444            bui.Lstr(resource=self._r + '.youHaveText')
445            .evaluate()
446            .partition('${COUNT}')[0]
447            .strip()
448        )
449        txt2 = (
450            bui.Lstr(resource=self._r + '.youHaveText')
451            .evaluate()
452            .rpartition('${COUNT}')[-1]
453            .strip()
454        )
455
456        bui.textwidget(
457            parent=self._root_widget,
458            text=txt1,
459            position=(h, v),
460            size=(0, 0),
461            color=(0.5, 0.5, 0.6),
462            maxwidth=200,
463            h_align='center',
464            v_align='center',
465            scale=0.8,
466        )
467        v -= 30
468        self._ticket_count_text = bui.textwidget(
469            parent=self._root_widget,
470            position=(h, v),
471            size=(0, 0),
472            color=(0.2, 1.0, 0.2),
473            maxwidth=200,
474            h_align='center',
475            v_align='center',
476            scale=1.6,
477        )
478        v -= 30
479        bui.textwidget(
480            parent=self._root_widget,
481            text=txt2,
482            position=(h, v),
483            size=(0, 0),
484            color=(0.5, 0.5, 0.6),
485            maxwidth=200,
486            h_align='center',
487            v_align='center',
488            scale=0.8,
489        )
490
491        self._ticking_sound: bui.Sound | None = None
492        self._smooth_ticket_count: float | None = None
493        self._ticket_count = 0
494        self._update()
495        self._update_timer = bui.AppTimer(
496            1.0, bui.WeakCall(self._update), repeat=True
497        )
498        self._smooth_increase_speed = 1.0
Inherited Members
bauiv1._uitypes.Window
get_root_widget
def show_get_tickets_prompt() -> None:
764def show_get_tickets_prompt() -> None:
765    """Show a 'not enough tickets' prompt with an option to purchase more.
766
767    Note that the purchase option may not always be available
768    depending on the build of the game.
769    """
770    from bauiv1lib.confirm import ConfirmWindow
771
772    assert bui.app.classic is not None
773    if bui.app.classic.allow_ticket_purchases:
774        ConfirmWindow(
775            bui.Lstr(
776                translate=(
777                    'serverResponses',
778                    'You don\'t have enough tickets for this!',
779                )
780            ),
781            lambda: GetCurrencyWindow(modal=True),
782            ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
783            width=460,
784            height=130,
785        )
786    else:
787        ConfirmWindow(
788            bui.Lstr(
789                translate=(
790                    'serverResponses',
791                    'You don\'t have enough tickets for this!',
792                )
793            ),
794            cancel_button=False,
795            width=460,
796            height=130,
797        )

Show a 'not enough tickets' prompt with an option to purchase more.

Note that the purchase option may not always be available depending on the build of the game.