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
  9import bauiv1 as bui
 10
 11if TYPE_CHECKING:
 12    from typing import Any
 13
 14
 15class GetCurrencyWindow(bui.Window):
 16    """Window for purchasing/acquiring currency."""
 17
 18    def __init__(
 19        self,
 20        transition: str = 'in_right',
 21        from_modal_store: bool = False,
 22        modal: bool = False,
 23        origin_widget: bui.Widget | None = None,
 24        store_back_location: str | None = None,
 25    ):
 26        # pylint: disable=too-many-statements
 27        # pylint: disable=too-many-locals
 28
 29        plus = bui.app.plus
 30        assert plus is not None
 31
 32        bui.set_analytics_screen('Get Tickets Window')
 33
 34        self._transitioning_out = False
 35        self._store_back_location = store_back_location  # ew.
 36
 37        self._ad_button_greyed = False
 38        self._smooth_update_timer: bui.AppTimer | None = None
 39        self._ad_button = None
 40        self._ad_label = None
 41        self._ad_image = None
 42        self._ad_time_text = None
 43
 44        # If they provided an origin-widget, scale up from that.
 45        scale_origin: tuple[float, float] | None
 46        if origin_widget is not None:
 47            self._transition_out = 'out_scale'
 48            scale_origin = origin_widget.get_screen_space_center()
 49            transition = 'in_scale'
 50        else:
 51            self._transition_out = 'out_right'
 52            scale_origin = None
 53
 54        assert bui.app.classic is not None
 55        uiscale = bui.app.ui_v1.uiscale
 56        self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
 57        x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
 58        self._height = 480.0
 59
 60        self._modal = modal
 61        self._from_modal_store = from_modal_store
 62        self._r = 'getTicketsWindow'
 63
 64        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 65
 66        super().__init__(
 67            root_widget=bui.containerwidget(
 68                size=(self._width, self._height + top_extra),
 69                transition=transition,
 70                scale_origin_stack_offset=scale_origin,
 71                color=(0.4, 0.37, 0.55),
 72                scale=(
 73                    1.63
 74                    if uiscale is bui.UIScale.SMALL
 75                    else 1.2
 76                    if uiscale is bui.UIScale.MEDIUM
 77                    else 1.0
 78                ),
 79                stack_offset=(0, -3)
 80                if uiscale is bui.UIScale.SMALL
 81                else (0, 0),
 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 = bui.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 True:  # pylint: disable=using-constant-test
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
437        h = self._width - (185 + x_inset)
438        v = self._height - 95 + tc_y_offs
439
440        txt1 = (
441            bui.Lstr(resource=self._r + '.youHaveText')
442            .evaluate()
443            .partition('${COUNT}')[0]
444            .strip()
445        )
446        txt2 = (
447            bui.Lstr(resource=self._r + '.youHaveText')
448            .evaluate()
449            .rpartition('${COUNT}')[-1]
450            .strip()
451        )
452
453        bui.textwidget(
454            parent=self._root_widget,
455            text=txt1,
456            position=(h, v),
457            size=(0, 0),
458            color=(0.5, 0.5, 0.6),
459            maxwidth=200,
460            h_align='center',
461            v_align='center',
462            scale=0.8,
463        )
464        v -= 30
465        self._ticket_count_text = bui.textwidget(
466            parent=self._root_widget,
467            position=(h, v),
468            size=(0, 0),
469            color=(0.2, 1.0, 0.2),
470            maxwidth=200,
471            h_align='center',
472            v_align='center',
473            scale=1.6,
474        )
475        v -= 30
476        bui.textwidget(
477            parent=self._root_widget,
478            text=txt2,
479            position=(h, v),
480            size=(0, 0),
481            color=(0.5, 0.5, 0.6),
482            maxwidth=200,
483            h_align='center',
484            v_align='center',
485            scale=0.8,
486        )
487
488        self._ticking_sound: bui.Sound | None = None
489        self._smooth_ticket_count: float | None = None
490        self._ticket_count = 0
491        self._update()
492        self._update_timer = bui.AppTimer(
493            1.0, bui.WeakCall(self._update), repeat=True
494        )
495        self._smooth_increase_speed = 1.0
496
497    def __del__(self) -> None:
498        if self._ticking_sound is not None:
499            self._ticking_sound.stop()
500            self._ticking_sound = None
501
502    def _smooth_update(self) -> None:
503        if not self._ticket_count_text:
504            self._smooth_update_timer = None
505            return
506
507        finished = False
508
509        # If we're going down, do it immediately.
510        assert self._smooth_ticket_count is not None
511        if int(self._smooth_ticket_count) >= self._ticket_count:
512            self._smooth_ticket_count = float(self._ticket_count)
513            finished = True
514        else:
515            # We're going up; start a sound if need be.
516            self._smooth_ticket_count = min(
517                self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
518                self._ticket_count,
519            )
520            if int(self._smooth_ticket_count) >= self._ticket_count:
521                finished = True
522                self._smooth_ticket_count = float(self._ticket_count)
523            elif self._ticking_sound is None:
524                self._ticking_sound = bui.getsound('scoreIncrease')
525                self._ticking_sound.play()
526
527        bui.textwidget(
528            edit=self._ticket_count_text,
529            text=str(int(self._smooth_ticket_count)),
530        )
531
532        # If we've reached the target, kill the timer/sound/etc.
533        if finished:
534            self._smooth_update_timer = None
535            if self._ticking_sound is not None:
536                self._ticking_sound.stop()
537                self._ticking_sound = None
538                bui.getsound('cashRegister2').play()
539
540    def _update(self) -> None:
541        import datetime
542
543        plus = bui.app.plus
544        assert plus is not None
545
546        # if we somehow get signed out, just die..
547        if plus.get_v1_account_state() != 'signed_in':
548            self._back()
549            return
550
551        self._ticket_count = plus.get_v1_account_ticket_count()
552
553        # update our incentivized ad button depending on whether ads are
554        # available
555        if self._ad_button is not None:
556            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
557                'nextRewardAdTime', None
558            )
559            if next_reward_ad_time is not None:
560                next_reward_ad_time = datetime.datetime.utcfromtimestamp(
561                    next_reward_ad_time
562                )
563            now = datetime.datetime.utcnow()
564            if bui.have_incentivized_ad() and (
565                next_reward_ad_time is None or next_reward_ad_time <= now
566            ):
567                self._ad_button_greyed = False
568                bui.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
569                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
570                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1))
571                bui.imagewidget(edit=self._ad_image, opacity=0.6)
572                bui.textwidget(edit=self._ad_time_text, text='')
573            else:
574                self._ad_button_greyed = True
575                bui.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5))
576                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2))
577                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
578                bui.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
579                sval: str | bui.Lstr
580                if (
581                    next_reward_ad_time is not None
582                    and next_reward_ad_time > now
583                ):
584                    sval = bui.timestring(
585                        (next_reward_ad_time - now).total_seconds(), centi=False
586                    )
587                else:
588                    sval = ''
589                bui.textwidget(edit=self._ad_time_text, text=sval)
590
591        # if this is our first update, assign immediately; otherwise kick
592        # off a smooth transition if the value has changed
593        if self._smooth_ticket_count is None:
594            self._smooth_ticket_count = float(self._ticket_count)
595            self._smooth_update()  # will set the text widget
596
597        elif (
598            self._ticket_count != int(self._smooth_ticket_count)
599            and self._smooth_update_timer is None
600        ):
601            self._smooth_update_timer = bui.AppTimer(
602                0.05, bui.WeakCall(self._smooth_update), repeat=True
603            )
604            diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
605            self._smooth_increase_speed = (
606                diff / 100.0
607                if diff >= 5000
608                else diff / 50.0
609                if diff >= 1500
610                else diff / 30.0
611                if diff >= 500
612                else diff / 15.0
613            )
614
615    def _disabled_press(self) -> None:
616        plus = bui.app.plus
617        assert plus is not None
618
619        # if we're on a platform without purchases, inform the user they
620        # can link their accounts and buy stuff elsewhere
621        app = bui.app
622        assert app.classic is not None
623        if (
624            app.env.test
625            or (
626                app.classic.platform == 'android'
627                and app.classic.subplatform in ['oculus', 'cardboard']
628            )
629        ) and plus.get_v1_account_misc_read_val('allowAccountLinking2', False):
630            bui.screenmessage(
631                bui.Lstr(resource=self._r + '.unavailableLinkAccountText'),
632                color=(1, 0.5, 0),
633            )
634        else:
635            bui.screenmessage(
636                bui.Lstr(resource=self._r + '.unavailableText'),
637                color=(1, 0.5, 0),
638            )
639        bui.getsound('error').play()
640
641    def _purchase(self, item: str) -> None:
642        from bauiv1lib import account
643        from bauiv1lib import appinvite
644
645        plus = bui.app.plus
646        assert plus is not None
647
648        if bui.app.classic is None:
649            raise RuntimeError('This requires classic support.')
650
651        if item == 'app_invite':
652            if plus.get_v1_account_state() != 'signed_in':
653                account.show_sign_in_prompt()
654                return
655            appinvite.handle_app_invites_press()
656            return
657        # here we ping the server to ask if it's valid for us to
658        # purchase this.. (better to fail now than after we've paid locally)
659        app = bui.app
660        assert app.classic is not None
661        bui.app.classic.master_server_v1_get(
662            'bsAccountPurchaseCheck',
663            {
664                'item': item,
665                'platform': app.classic.platform,
666                'subplatform': app.classic.subplatform,
667                'version': app.env.version,
668                'buildNumber': app.env.build_number,
669            },
670            callback=bui.WeakCall(self._purchase_check_result, item),
671        )
672
673    def _purchase_check_result(
674        self, item: str, result: dict[str, Any] | None
675    ) -> None:
676        if result is None:
677            bui.getsound('error').play()
678            bui.screenmessage(
679                bui.Lstr(resource='internal.unavailableNoConnectionText'),
680                color=(1, 0, 0),
681            )
682        else:
683            if result['allow']:
684                self._do_purchase(item)
685            else:
686                if result['reason'] == 'versionTooOld':
687                    bui.getsound('error').play()
688                    bui.screenmessage(
689                        bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
690                        color=(1, 0, 0),
691                    )
692                else:
693                    bui.getsound('error').play()
694                    bui.screenmessage(
695                        bui.Lstr(resource='getTicketsWindow.unavailableText'),
696                        color=(1, 0, 0),
697                    )
698
699    # actually start the purchase locally..
700    def _do_purchase(self, item: str) -> None:
701        plus = bui.app.plus
702        assert plus is not None
703
704        if item == 'ad':
705            import datetime
706
707            # if ads are disabled until some time, error..
708            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
709                'nextRewardAdTime', None
710            )
711            if next_reward_ad_time is not None:
712                next_reward_ad_time = datetime.datetime.utcfromtimestamp(
713                    next_reward_ad_time
714                )
715            now = datetime.datetime.utcnow()
716            if (
717                next_reward_ad_time is not None and next_reward_ad_time > now
718            ) or self._ad_button_greyed:
719                bui.getsound('error').play()
720                bui.screenmessage(
721                    bui.Lstr(
722                        resource='getTicketsWindow.unavailableTemporarilyText'
723                    ),
724                    color=(1, 0, 0),
725                )
726            elif self._enable_ad_button:
727                assert bui.app.classic is not None
728                bui.app.classic.ads.show_ad('tickets')
729        else:
730            plus.purchase(item)
731
732    def _back(self) -> None:
733        from bauiv1lib.store import browser
734
735        if self._transitioning_out:
736            return
737        bui.containerwidget(
738            edit=self._root_widget, transition=self._transition_out
739        )
740        if not self._modal:
741            window = browser.StoreBrowserWindow(
742                transition='in_left',
743                modal=self._from_modal_store,
744                back_location=self._store_back_location,
745            ).get_root_widget()
746            if not self._from_modal_store:
747                assert bui.app.classic is not None
748                bui.app.ui_v1.set_main_menu_window(window)
749        self._transitioning_out = True
750
751
752def show_get_tickets_prompt() -> None:
753    """Show a 'not enough tickets' prompt with an option to purchase more.
754
755    Note that the purchase option may not always be available
756    depending on the build of the game.
757    """
758    from bauiv1lib.confirm import ConfirmWindow
759
760    assert bui.app.classic is not None
761    if bui.app.classic.allow_ticket_purchases:
762        ConfirmWindow(
763            bui.Lstr(
764                translate=(
765                    'serverResponses',
766                    'You don\'t have enough tickets for this!',
767                )
768            ),
769            lambda: GetCurrencyWindow(modal=True),
770            ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
771            width=460,
772            height=130,
773        )
774    else:
775        ConfirmWindow(
776            bui.Lstr(
777                translate=(
778                    'serverResponses',
779                    'You don\'t have enough tickets for this!',
780                )
781            ),
782            cancel_button=False,
783            width=460,
784            height=130,
785        )
class GetCurrencyWindow(bauiv1._uitypes.Window):
 16class GetCurrencyWindow(bui.Window):
 17    """Window for purchasing/acquiring currency."""
 18
 19    def __init__(
 20        self,
 21        transition: str = 'in_right',
 22        from_modal_store: bool = False,
 23        modal: bool = False,
 24        origin_widget: bui.Widget | None = None,
 25        store_back_location: str | None = None,
 26    ):
 27        # pylint: disable=too-many-statements
 28        # pylint: disable=too-many-locals
 29
 30        plus = bui.app.plus
 31        assert plus is not None
 32
 33        bui.set_analytics_screen('Get Tickets Window')
 34
 35        self._transitioning_out = False
 36        self._store_back_location = store_back_location  # ew.
 37
 38        self._ad_button_greyed = False
 39        self._smooth_update_timer: bui.AppTimer | None = None
 40        self._ad_button = None
 41        self._ad_label = None
 42        self._ad_image = None
 43        self._ad_time_text = None
 44
 45        # If they provided an origin-widget, scale up from that.
 46        scale_origin: tuple[float, float] | None
 47        if origin_widget is not None:
 48            self._transition_out = 'out_scale'
 49            scale_origin = origin_widget.get_screen_space_center()
 50            transition = 'in_scale'
 51        else:
 52            self._transition_out = 'out_right'
 53            scale_origin = None
 54
 55        assert bui.app.classic is not None
 56        uiscale = bui.app.ui_v1.uiscale
 57        self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
 58        x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
 59        self._height = 480.0
 60
 61        self._modal = modal
 62        self._from_modal_store = from_modal_store
 63        self._r = 'getTicketsWindow'
 64
 65        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 66
 67        super().__init__(
 68            root_widget=bui.containerwidget(
 69                size=(self._width, self._height + top_extra),
 70                transition=transition,
 71                scale_origin_stack_offset=scale_origin,
 72                color=(0.4, 0.37, 0.55),
 73                scale=(
 74                    1.63
 75                    if uiscale is bui.UIScale.SMALL
 76                    else 1.2
 77                    if uiscale is bui.UIScale.MEDIUM
 78                    else 1.0
 79                ),
 80                stack_offset=(0, -3)
 81                if uiscale is bui.UIScale.SMALL
 82                else (0, 0),
 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 = bui.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 True:  # pylint: disable=using-constant-test
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
438        h = self._width - (185 + x_inset)
439        v = self._height - 95 + tc_y_offs
440
441        txt1 = (
442            bui.Lstr(resource=self._r + '.youHaveText')
443            .evaluate()
444            .partition('${COUNT}')[0]
445            .strip()
446        )
447        txt2 = (
448            bui.Lstr(resource=self._r + '.youHaveText')
449            .evaluate()
450            .rpartition('${COUNT}')[-1]
451            .strip()
452        )
453
454        bui.textwidget(
455            parent=self._root_widget,
456            text=txt1,
457            position=(h, v),
458            size=(0, 0),
459            color=(0.5, 0.5, 0.6),
460            maxwidth=200,
461            h_align='center',
462            v_align='center',
463            scale=0.8,
464        )
465        v -= 30
466        self._ticket_count_text = bui.textwidget(
467            parent=self._root_widget,
468            position=(h, v),
469            size=(0, 0),
470            color=(0.2, 1.0, 0.2),
471            maxwidth=200,
472            h_align='center',
473            v_align='center',
474            scale=1.6,
475        )
476        v -= 30
477        bui.textwidget(
478            parent=self._root_widget,
479            text=txt2,
480            position=(h, v),
481            size=(0, 0),
482            color=(0.5, 0.5, 0.6),
483            maxwidth=200,
484            h_align='center',
485            v_align='center',
486            scale=0.8,
487        )
488
489        self._ticking_sound: bui.Sound | None = None
490        self._smooth_ticket_count: float | None = None
491        self._ticket_count = 0
492        self._update()
493        self._update_timer = bui.AppTimer(
494            1.0, bui.WeakCall(self._update), repeat=True
495        )
496        self._smooth_increase_speed = 1.0
497
498    def __del__(self) -> None:
499        if self._ticking_sound is not None:
500            self._ticking_sound.stop()
501            self._ticking_sound = None
502
503    def _smooth_update(self) -> None:
504        if not self._ticket_count_text:
505            self._smooth_update_timer = None
506            return
507
508        finished = False
509
510        # If we're going down, do it immediately.
511        assert self._smooth_ticket_count is not None
512        if int(self._smooth_ticket_count) >= self._ticket_count:
513            self._smooth_ticket_count = float(self._ticket_count)
514            finished = True
515        else:
516            # We're going up; start a sound if need be.
517            self._smooth_ticket_count = min(
518                self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
519                self._ticket_count,
520            )
521            if int(self._smooth_ticket_count) >= self._ticket_count:
522                finished = True
523                self._smooth_ticket_count = float(self._ticket_count)
524            elif self._ticking_sound is None:
525                self._ticking_sound = bui.getsound('scoreIncrease')
526                self._ticking_sound.play()
527
528        bui.textwidget(
529            edit=self._ticket_count_text,
530            text=str(int(self._smooth_ticket_count)),
531        )
532
533        # If we've reached the target, kill the timer/sound/etc.
534        if finished:
535            self._smooth_update_timer = None
536            if self._ticking_sound is not None:
537                self._ticking_sound.stop()
538                self._ticking_sound = None
539                bui.getsound('cashRegister2').play()
540
541    def _update(self) -> None:
542        import datetime
543
544        plus = bui.app.plus
545        assert plus is not None
546
547        # if we somehow get signed out, just die..
548        if plus.get_v1_account_state() != 'signed_in':
549            self._back()
550            return
551
552        self._ticket_count = plus.get_v1_account_ticket_count()
553
554        # update our incentivized ad button depending on whether ads are
555        # available
556        if self._ad_button is not None:
557            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
558                'nextRewardAdTime', None
559            )
560            if next_reward_ad_time is not None:
561                next_reward_ad_time = datetime.datetime.utcfromtimestamp(
562                    next_reward_ad_time
563                )
564            now = datetime.datetime.utcnow()
565            if bui.have_incentivized_ad() and (
566                next_reward_ad_time is None or next_reward_ad_time <= now
567            ):
568                self._ad_button_greyed = False
569                bui.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
570                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
571                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1))
572                bui.imagewidget(edit=self._ad_image, opacity=0.6)
573                bui.textwidget(edit=self._ad_time_text, text='')
574            else:
575                self._ad_button_greyed = True
576                bui.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5))
577                bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2))
578                bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
579                bui.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
580                sval: str | bui.Lstr
581                if (
582                    next_reward_ad_time is not None
583                    and next_reward_ad_time > now
584                ):
585                    sval = bui.timestring(
586                        (next_reward_ad_time - now).total_seconds(), centi=False
587                    )
588                else:
589                    sval = ''
590                bui.textwidget(edit=self._ad_time_text, text=sval)
591
592        # if this is our first update, assign immediately; otherwise kick
593        # off a smooth transition if the value has changed
594        if self._smooth_ticket_count is None:
595            self._smooth_ticket_count = float(self._ticket_count)
596            self._smooth_update()  # will set the text widget
597
598        elif (
599            self._ticket_count != int(self._smooth_ticket_count)
600            and self._smooth_update_timer is None
601        ):
602            self._smooth_update_timer = bui.AppTimer(
603                0.05, bui.WeakCall(self._smooth_update), repeat=True
604            )
605            diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
606            self._smooth_increase_speed = (
607                diff / 100.0
608                if diff >= 5000
609                else diff / 50.0
610                if diff >= 1500
611                else diff / 30.0
612                if diff >= 500
613                else diff / 15.0
614            )
615
616    def _disabled_press(self) -> None:
617        plus = bui.app.plus
618        assert plus is not None
619
620        # if we're on a platform without purchases, inform the user they
621        # can link their accounts and buy stuff elsewhere
622        app = bui.app
623        assert app.classic is not None
624        if (
625            app.env.test
626            or (
627                app.classic.platform == 'android'
628                and app.classic.subplatform in ['oculus', 'cardboard']
629            )
630        ) and plus.get_v1_account_misc_read_val('allowAccountLinking2', False):
631            bui.screenmessage(
632                bui.Lstr(resource=self._r + '.unavailableLinkAccountText'),
633                color=(1, 0.5, 0),
634            )
635        else:
636            bui.screenmessage(
637                bui.Lstr(resource=self._r + '.unavailableText'),
638                color=(1, 0.5, 0),
639            )
640        bui.getsound('error').play()
641
642    def _purchase(self, item: str) -> None:
643        from bauiv1lib import account
644        from bauiv1lib import appinvite
645
646        plus = bui.app.plus
647        assert plus is not None
648
649        if bui.app.classic is None:
650            raise RuntimeError('This requires classic support.')
651
652        if item == 'app_invite':
653            if plus.get_v1_account_state() != 'signed_in':
654                account.show_sign_in_prompt()
655                return
656            appinvite.handle_app_invites_press()
657            return
658        # here we ping the server to ask if it's valid for us to
659        # purchase this.. (better to fail now than after we've paid locally)
660        app = bui.app
661        assert app.classic is not None
662        bui.app.classic.master_server_v1_get(
663            'bsAccountPurchaseCheck',
664            {
665                'item': item,
666                'platform': app.classic.platform,
667                'subplatform': app.classic.subplatform,
668                'version': app.env.version,
669                'buildNumber': app.env.build_number,
670            },
671            callback=bui.WeakCall(self._purchase_check_result, item),
672        )
673
674    def _purchase_check_result(
675        self, item: str, result: dict[str, Any] | None
676    ) -> None:
677        if result is None:
678            bui.getsound('error').play()
679            bui.screenmessage(
680                bui.Lstr(resource='internal.unavailableNoConnectionText'),
681                color=(1, 0, 0),
682            )
683        else:
684            if result['allow']:
685                self._do_purchase(item)
686            else:
687                if result['reason'] == 'versionTooOld':
688                    bui.getsound('error').play()
689                    bui.screenmessage(
690                        bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
691                        color=(1, 0, 0),
692                    )
693                else:
694                    bui.getsound('error').play()
695                    bui.screenmessage(
696                        bui.Lstr(resource='getTicketsWindow.unavailableText'),
697                        color=(1, 0, 0),
698                    )
699
700    # actually start the purchase locally..
701    def _do_purchase(self, item: str) -> None:
702        plus = bui.app.plus
703        assert plus is not None
704
705        if item == 'ad':
706            import datetime
707
708            # if ads are disabled until some time, error..
709            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
710                'nextRewardAdTime', None
711            )
712            if next_reward_ad_time is not None:
713                next_reward_ad_time = datetime.datetime.utcfromtimestamp(
714                    next_reward_ad_time
715                )
716            now = datetime.datetime.utcnow()
717            if (
718                next_reward_ad_time is not None and next_reward_ad_time > now
719            ) or self._ad_button_greyed:
720                bui.getsound('error').play()
721                bui.screenmessage(
722                    bui.Lstr(
723                        resource='getTicketsWindow.unavailableTemporarilyText'
724                    ),
725                    color=(1, 0, 0),
726                )
727            elif self._enable_ad_button:
728                assert bui.app.classic is not None
729                bui.app.classic.ads.show_ad('tickets')
730        else:
731            plus.purchase(item)
732
733    def _back(self) -> None:
734        from bauiv1lib.store import browser
735
736        if self._transitioning_out:
737            return
738        bui.containerwidget(
739            edit=self._root_widget, transition=self._transition_out
740        )
741        if not self._modal:
742            window = browser.StoreBrowserWindow(
743                transition='in_left',
744                modal=self._from_modal_store,
745                back_location=self._store_back_location,
746            ).get_root_widget()
747            if not self._from_modal_store:
748                assert bui.app.classic is not None
749                bui.app.ui_v1.set_main_menu_window(window)
750        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)
 19    def __init__(
 20        self,
 21        transition: str = 'in_right',
 22        from_modal_store: bool = False,
 23        modal: bool = False,
 24        origin_widget: bui.Widget | None = None,
 25        store_back_location: str | None = None,
 26    ):
 27        # pylint: disable=too-many-statements
 28        # pylint: disable=too-many-locals
 29
 30        plus = bui.app.plus
 31        assert plus is not None
 32
 33        bui.set_analytics_screen('Get Tickets Window')
 34
 35        self._transitioning_out = False
 36        self._store_back_location = store_back_location  # ew.
 37
 38        self._ad_button_greyed = False
 39        self._smooth_update_timer: bui.AppTimer | None = None
 40        self._ad_button = None
 41        self._ad_label = None
 42        self._ad_image = None
 43        self._ad_time_text = None
 44
 45        # If they provided an origin-widget, scale up from that.
 46        scale_origin: tuple[float, float] | None
 47        if origin_widget is not None:
 48            self._transition_out = 'out_scale'
 49            scale_origin = origin_widget.get_screen_space_center()
 50            transition = 'in_scale'
 51        else:
 52            self._transition_out = 'out_right'
 53            scale_origin = None
 54
 55        assert bui.app.classic is not None
 56        uiscale = bui.app.ui_v1.uiscale
 57        self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
 58        x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
 59        self._height = 480.0
 60
 61        self._modal = modal
 62        self._from_modal_store = from_modal_store
 63        self._r = 'getTicketsWindow'
 64
 65        top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 66
 67        super().__init__(
 68            root_widget=bui.containerwidget(
 69                size=(self._width, self._height + top_extra),
 70                transition=transition,
 71                scale_origin_stack_offset=scale_origin,
 72                color=(0.4, 0.37, 0.55),
 73                scale=(
 74                    1.63
 75                    if uiscale is bui.UIScale.SMALL
 76                    else 1.2
 77                    if uiscale is bui.UIScale.MEDIUM
 78                    else 1.0
 79                ),
 80                stack_offset=(0, -3)
 81                if uiscale is bui.UIScale.SMALL
 82                else (0, 0),
 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 = bui.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 True:  # pylint: disable=using-constant-test
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
438        h = self._width - (185 + x_inset)
439        v = self._height - 95 + tc_y_offs
440
441        txt1 = (
442            bui.Lstr(resource=self._r + '.youHaveText')
443            .evaluate()
444            .partition('${COUNT}')[0]
445            .strip()
446        )
447        txt2 = (
448            bui.Lstr(resource=self._r + '.youHaveText')
449            .evaluate()
450            .rpartition('${COUNT}')[-1]
451            .strip()
452        )
453
454        bui.textwidget(
455            parent=self._root_widget,
456            text=txt1,
457            position=(h, v),
458            size=(0, 0),
459            color=(0.5, 0.5, 0.6),
460            maxwidth=200,
461            h_align='center',
462            v_align='center',
463            scale=0.8,
464        )
465        v -= 30
466        self._ticket_count_text = bui.textwidget(
467            parent=self._root_widget,
468            position=(h, v),
469            size=(0, 0),
470            color=(0.2, 1.0, 0.2),
471            maxwidth=200,
472            h_align='center',
473            v_align='center',
474            scale=1.6,
475        )
476        v -= 30
477        bui.textwidget(
478            parent=self._root_widget,
479            text=txt2,
480            position=(h, v),
481            size=(0, 0),
482            color=(0.5, 0.5, 0.6),
483            maxwidth=200,
484            h_align='center',
485            v_align='center',
486            scale=0.8,
487        )
488
489        self._ticking_sound: bui.Sound | None = None
490        self._smooth_ticket_count: float | None = None
491        self._ticket_count = 0
492        self._update()
493        self._update_timer = bui.AppTimer(
494            1.0, bui.WeakCall(self._update), repeat=True
495        )
496        self._smooth_increase_speed = 1.0
Inherited Members
bauiv1._uitypes.Window
get_root_widget
def show_get_tickets_prompt() -> None:
753def show_get_tickets_prompt() -> None:
754    """Show a 'not enough tickets' prompt with an option to purchase more.
755
756    Note that the purchase option may not always be available
757    depending on the build of the game.
758    """
759    from bauiv1lib.confirm import ConfirmWindow
760
761    assert bui.app.classic is not None
762    if bui.app.classic.allow_ticket_purchases:
763        ConfirmWindow(
764            bui.Lstr(
765                translate=(
766                    'serverResponses',
767                    'You don\'t have enough tickets for this!',
768                )
769            ),
770            lambda: GetCurrencyWindow(modal=True),
771            ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
772            width=460,
773            height=130,
774        )
775    else:
776        ConfirmWindow(
777            bui.Lstr(
778                translate=(
779                    'serverResponses',
780                    'You don\'t have enough tickets for this!',
781                )
782            ),
783            cancel_button=False,
784            width=460,
785            height=130,
786        )

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.