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 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.fromtimestamp(
561                    next_reward_ad_time, datetime.UTC
562                )
563            now = utc_now()
564            if plus.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 (
609                    diff / 50.0
610                    if diff >= 1500
611                    else diff / 30.0 if diff >= 500 else diff / 15.0
612                )
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
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
660        # locally).
661        app = bui.app
662        assert app.classic is not None
663        bui.app.classic.master_server_v1_get(
664            'bsAccountPurchaseCheck',
665            {
666                'item': item,
667                'platform': app.classic.platform,
668                'subplatform': app.classic.subplatform,
669                'version': app.env.version,
670                'buildNumber': app.env.build_number,
671            },
672            callback=bui.WeakCall(self._purchase_check_result, item),
673        )
674
675    def _purchase_check_result(
676        self, item: str, result: dict[str, Any] | None
677    ) -> None:
678        if result is None:
679            bui.getsound('error').play()
680            bui.screenmessage(
681                bui.Lstr(resource='internal.unavailableNoConnectionText'),
682                color=(1, 0, 0),
683            )
684        else:
685            if result['allow']:
686                self._do_purchase(item)
687            else:
688                if result['reason'] == 'versionTooOld':
689                    bui.getsound('error').play()
690                    bui.screenmessage(
691                        bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
692                        color=(1, 0, 0),
693                    )
694                else:
695                    bui.getsound('error').play()
696                    bui.screenmessage(
697                        bui.Lstr(resource='getTicketsWindow.unavailableText'),
698                        color=(1, 0, 0),
699                    )
700
701    # Actually start the purchase locally.
702    def _do_purchase(self, item: str) -> None:
703        plus = bui.app.plus
704        assert plus is not None
705
706        if item == 'ad':
707            import datetime
708
709            # If ads are disabled until some time, error.
710            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
711                'nextRewardAdTime', None
712            )
713            if next_reward_ad_time is not None:
714                next_reward_ad_time = datetime.datetime.fromtimestamp(
715                    next_reward_ad_time, datetime.UTC
716                )
717            now = utc_now()
718            if (
719                next_reward_ad_time is not None and next_reward_ad_time > now
720            ) or self._ad_button_greyed:
721                bui.getsound('error').play()
722                bui.screenmessage(
723                    bui.Lstr(
724                        resource='getTicketsWindow.unavailableTemporarilyText'
725                    ),
726                    color=(1, 0, 0),
727                )
728            elif self._enable_ad_button:
729                assert bui.app.classic is not None
730                bui.app.classic.ads.show_ad('tickets')
731        else:
732            plus.purchase(item)
733
734    def _back(self) -> None:
735        from bauiv1lib.store import browser
736
737        # No-op if our underlying widget is dead or on its way out.
738        if not self._root_widget or self._root_widget.transitioning_out:
739            return
740
741        if self._transitioning_out:
742            return
743
744        bui.containerwidget(
745            edit=self._root_widget, transition=self._transition_out
746        )
747        if not self._modal:
748            window = browser.StoreBrowserWindow(
749                transition='in_left',
750                modal=self._from_modal_store,
751                back_location=self._store_back_location,
752            ).get_root_widget()
753            if not self._from_modal_store:
754                assert bui.app.classic is not None
755                bui.app.ui_v1.set_main_menu_window(
756                    window, from_window=self._root_widget
757                )
758        self._transitioning_out = True
759
760
761def show_get_tickets_prompt() -> None:
762    """Show a 'not enough tickets' prompt with an option to purchase more.
763
764    Note that the purchase option may not always be available
765    depending on the build of the game.
766    """
767    from bauiv1lib.confirm import ConfirmWindow
768
769    assert bui.app.classic is not None
770    if bui.app.classic.allow_ticket_purchases:
771        ConfirmWindow(
772            bui.Lstr(
773                translate=(
774                    'serverResponses',
775                    'You don\'t have enough tickets for this!',
776                )
777            ),
778            lambda: GetCurrencyWindow(modal=True),
779            ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
780            width=460,
781            height=130,
782        )
783    else:
784        ConfirmWindow(
785            bui.Lstr(
786                translate=(
787                    'serverResponses',
788                    'You don\'t have enough tickets for this!',
789                )
790            ),
791            cancel_button=False,
792            width=460,
793            height=130,
794        )
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 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.fromtimestamp(
562                    next_reward_ad_time, datetime.UTC
563                )
564            now = utc_now()
565            if plus.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 (
610                    diff / 50.0
611                    if diff >= 1500
612                    else diff / 30.0 if diff >= 500 else diff / 15.0
613                )
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
659        # Here we ping the server to ask if it's valid for us to
660        # purchase this.. (better to fail now than after we've paid
661        # locally).
662        app = bui.app
663        assert app.classic is not None
664        bui.app.classic.master_server_v1_get(
665            'bsAccountPurchaseCheck',
666            {
667                'item': item,
668                'platform': app.classic.platform,
669                'subplatform': app.classic.subplatform,
670                'version': app.env.version,
671                'buildNumber': app.env.build_number,
672            },
673            callback=bui.WeakCall(self._purchase_check_result, item),
674        )
675
676    def _purchase_check_result(
677        self, item: str, result: dict[str, Any] | None
678    ) -> None:
679        if result is None:
680            bui.getsound('error').play()
681            bui.screenmessage(
682                bui.Lstr(resource='internal.unavailableNoConnectionText'),
683                color=(1, 0, 0),
684            )
685        else:
686            if result['allow']:
687                self._do_purchase(item)
688            else:
689                if result['reason'] == 'versionTooOld':
690                    bui.getsound('error').play()
691                    bui.screenmessage(
692                        bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
693                        color=(1, 0, 0),
694                    )
695                else:
696                    bui.getsound('error').play()
697                    bui.screenmessage(
698                        bui.Lstr(resource='getTicketsWindow.unavailableText'),
699                        color=(1, 0, 0),
700                    )
701
702    # Actually start the purchase locally.
703    def _do_purchase(self, item: str) -> None:
704        plus = bui.app.plus
705        assert plus is not None
706
707        if item == 'ad':
708            import datetime
709
710            # If ads are disabled until some time, error.
711            next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
712                'nextRewardAdTime', None
713            )
714            if next_reward_ad_time is not None:
715                next_reward_ad_time = datetime.datetime.fromtimestamp(
716                    next_reward_ad_time, datetime.UTC
717                )
718            now = utc_now()
719            if (
720                next_reward_ad_time is not None and next_reward_ad_time > now
721            ) or self._ad_button_greyed:
722                bui.getsound('error').play()
723                bui.screenmessage(
724                    bui.Lstr(
725                        resource='getTicketsWindow.unavailableTemporarilyText'
726                    ),
727                    color=(1, 0, 0),
728                )
729            elif self._enable_ad_button:
730                assert bui.app.classic is not None
731                bui.app.classic.ads.show_ad('tickets')
732        else:
733            plus.purchase(item)
734
735    def _back(self) -> None:
736        from bauiv1lib.store import browser
737
738        # No-op if our underlying widget is dead or on its way out.
739        if not self._root_widget or self._root_widget.transitioning_out:
740            return
741
742        if self._transitioning_out:
743            return
744
745        bui.containerwidget(
746            edit=self._root_widget, transition=self._transition_out
747        )
748        if not self._modal:
749            window = browser.StoreBrowserWindow(
750                transition='in_left',
751                modal=self._from_modal_store,
752                back_location=self._store_back_location,
753            ).get_root_widget()
754            if not self._from_modal_store:
755                assert bui.app.classic is not None
756                bui.app.ui_v1.set_main_menu_window(
757                    window, from_window=self._root_widget
758                )
759        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 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:
762def show_get_tickets_prompt() -> None:
763    """Show a 'not enough tickets' prompt with an option to purchase more.
764
765    Note that the purchase option may not always be available
766    depending on the build of the game.
767    """
768    from bauiv1lib.confirm import ConfirmWindow
769
770    assert bui.app.classic is not None
771    if bui.app.classic.allow_ticket_purchases:
772        ConfirmWindow(
773            bui.Lstr(
774                translate=(
775                    'serverResponses',
776                    'You don\'t have enough tickets for this!',
777                )
778            ),
779            lambda: GetCurrencyWindow(modal=True),
780            ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
781            width=460,
782            height=130,
783        )
784    else:
785        ConfirmWindow(
786            bui.Lstr(
787                translate=(
788                    'serverResponses',
789                    'You don\'t have enough tickets for this!',
790                )
791            ),
792            cancel_button=False,
793            width=460,
794            height=130,
795        )

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.