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.