bauiv1lib.account.settings
Provides UI for account functionality.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for account functionality.""" 4# pylint: disable=too-many-lines 5 6from __future__ import annotations 7 8import time 9import logging 10 11from bacommon.login import LoginType 12import bacommon.cloud 13import bauiv1 as bui 14 15# These days we're directing people to the web based account settings 16# for V2 account linking and trying to get them to disconnect remaining 17# V1 links, but leaving this escape hatch here in case needed. 18FORCE_ENABLE_V1_LINKING = False 19 20 21class AccountSettingsWindow(bui.Window): 22 """Window for account related functionality.""" 23 24 def __init__( 25 self, 26 transition: str = 'in_right', 27 modal: bool = False, 28 origin_widget: bui.Widget | None = None, 29 close_once_signed_in: bool = False, 30 ): 31 # pylint: disable=too-many-statements 32 33 plus = bui.app.plus 34 assert plus is not None 35 36 self._sign_in_v2_proxy_button: bui.Widget | None = None 37 self._sign_in_device_button: bui.Widget | None = None 38 39 self._show_legacy_unlink_button = False 40 41 self._signing_in_adapter: bui.LoginAdapter | None = None 42 self._close_once_signed_in = close_once_signed_in 43 bui.set_analytics_screen('Account Window') 44 45 self._explicitly_signed_out_of_gpgs = False 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 self._r = 'accountSettingsWindow' 58 self._modal = modal 59 self._needs_refresh = False 60 self._v1_signed_in = plus.get_v1_account_state() == 'signed_in' 61 self._v1_account_state_num = plus.get_v1_account_state_num() 62 self._check_sign_in_timer = bui.AppTimer( 63 1.0, bui.WeakCall(self._update), repeat=True 64 ) 65 66 # Currently we can only reset achievements on game-center. 67 v1_account_type: str | None 68 if self._v1_signed_in: 69 v1_account_type = plus.get_v1_account_type() 70 else: 71 v1_account_type = None 72 self._can_reset_achievements = v1_account_type == 'Game Center' 73 74 app = bui.app 75 assert app.classic is not None 76 uiscale = app.ui_v1.uiscale 77 78 self._width = 760 if uiscale is bui.UIScale.SMALL else 660 79 x_offs = 50 if uiscale is bui.UIScale.SMALL else 0 80 self._height = ( 81 390 82 if uiscale is bui.UIScale.SMALL 83 else 430 84 if uiscale is bui.UIScale.MEDIUM 85 else 490 86 ) 87 88 self._sign_in_button = None 89 self._sign_in_text = None 90 91 self._scroll_width = self._width - (100 + x_offs * 2) 92 self._scroll_height = self._height - 120 93 self._sub_width = self._scroll_width - 20 94 95 # Determine which sign-in/sign-out buttons we should show. 96 self._show_sign_in_buttons: list[str] = [] 97 98 if LoginType.GPGS in plus.accounts.login_adapters: 99 self._show_sign_in_buttons.append('Google Play') 100 101 # Always want to show our web-based v2 login option. 102 self._show_sign_in_buttons.append('V2Proxy') 103 104 # Legacy v1 device accounts are currently always available 105 # (though we need to start phasing them out at some point). 106 self._show_sign_in_buttons.append('Device') 107 108 top_extra = 15 if uiscale is bui.UIScale.SMALL else 0 109 super().__init__( 110 root_widget=bui.containerwidget( 111 size=(self._width, self._height + top_extra), 112 transition=transition, 113 toolbar_visibility='menu_minimal', 114 scale_origin_stack_offset=scale_origin, 115 scale=( 116 2.09 117 if uiscale is bui.UIScale.SMALL 118 else 1.4 119 if uiscale is bui.UIScale.MEDIUM 120 else 1.0 121 ), 122 stack_offset=(0, -19) 123 if uiscale is bui.UIScale.SMALL 124 else (0, 0), 125 ) 126 ) 127 if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars: 128 self._back_button = None 129 bui.containerwidget( 130 edit=self._root_widget, on_cancel_call=self._back 131 ) 132 else: 133 self._back_button = btn = bui.buttonwidget( 134 parent=self._root_widget, 135 position=(51 + x_offs, self._height - 62), 136 size=(120, 60), 137 scale=0.8, 138 text_scale=1.2, 139 autoselect=True, 140 label=bui.Lstr( 141 resource='doneText' if self._modal else 'backText' 142 ), 143 button_type='regular' if self._modal else 'back', 144 on_activate_call=self._back, 145 ) 146 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 147 if not self._modal: 148 bui.buttonwidget( 149 edit=btn, 150 button_type='backSmall', 151 size=(60, 56), 152 label=bui.charstr(bui.SpecialChar.BACK), 153 ) 154 155 bui.textwidget( 156 parent=self._root_widget, 157 position=(self._width * 0.5, self._height - 41), 158 size=(0, 0), 159 text=bui.Lstr(resource=self._r + '.titleText'), 160 color=app.ui_v1.title_color, 161 maxwidth=self._width - 340, 162 h_align='center', 163 v_align='center', 164 ) 165 166 self._scrollwidget = bui.scrollwidget( 167 parent=self._root_widget, 168 highlight=False, 169 position=( 170 (self._width - self._scroll_width) * 0.5, 171 self._height - 65 - self._scroll_height, 172 ), 173 size=(self._scroll_width, self._scroll_height), 174 claims_left_right=True, 175 claims_tab=True, 176 selection_loops_to_parent=True, 177 ) 178 self._subcontainer: bui.Widget | None = None 179 self._refresh() 180 self._restore_state() 181 182 def _update(self) -> None: 183 plus = bui.app.plus 184 assert plus is not None 185 186 # If they want us to close once we're signed in, do so. 187 if self._close_once_signed_in and self._v1_signed_in: 188 self._back() 189 return 190 191 # Hmm should update this to use get_account_state_num. 192 # Theoretically if we switch from one signed-in account to another 193 # in the background this would break. 194 v1_account_state_num = plus.get_v1_account_state_num() 195 v1_account_state = plus.get_v1_account_state() 196 show_legacy_unlink_button = self._should_show_legacy_unlink_button() 197 198 if ( 199 v1_account_state_num != self._v1_account_state_num 200 or show_legacy_unlink_button != self._show_legacy_unlink_button 201 or self._needs_refresh 202 ): 203 self._v1_account_state_num = v1_account_state_num 204 self._v1_signed_in = v1_account_state == 'signed_in' 205 self._show_legacy_unlink_button = show_legacy_unlink_button 206 self._refresh() 207 208 # Go ahead and refresh some individual things 209 # that may change under us. 210 self._update_linked_accounts_text() 211 self._update_unlink_accounts_button() 212 self._refresh_campaign_progress_text() 213 self._refresh_achievements() 214 self._refresh_tickets_text() 215 self._refresh_account_name_text() 216 217 def _get_sign_in_text(self) -> bui.Lstr: 218 return bui.Lstr(resource=self._r + '.signInText') 219 220 def _refresh(self) -> None: 221 # pylint: disable=too-many-statements 222 # pylint: disable=too-many-branches 223 # pylint: disable=too-many-locals 224 # pylint: disable=cyclic-import 225 from bauiv1lib import confirm 226 227 plus = bui.app.plus 228 assert plus is not None 229 230 primary_v2_account = plus.accounts.primary 231 232 v1_state = plus.get_v1_account_state() 233 v1_account_type = ( 234 plus.get_v1_account_type() if v1_state == 'signed_in' else 'unknown' 235 ) 236 237 # We expose GPGS-specific functionality only if it is 'active' 238 # (meaning the current GPGS player matches one of our account's 239 # logins). 240 gpgs_adapter = plus.accounts.login_adapters.get(LoginType.GPGS) 241 is_gpgs = ( 242 False if gpgs_adapter is None else gpgs_adapter.is_back_end_active() 243 ) 244 245 show_signed_in_as = self._v1_signed_in 246 signed_in_as_space = 95.0 247 248 show_sign_in_benefits = not self._v1_signed_in 249 sign_in_benefits_space = 80.0 250 251 show_signing_in_text = ( 252 v1_state == 'signing_in' or self._signing_in_adapter is not None 253 ) 254 signing_in_text_space = 80.0 255 256 show_google_play_sign_in_button = ( 257 v1_state == 'signed_out' 258 and self._signing_in_adapter is None 259 and 'Google Play' in self._show_sign_in_buttons 260 ) 261 show_v2_proxy_sign_in_button = ( 262 v1_state == 'signed_out' 263 and self._signing_in_adapter is None 264 and 'V2Proxy' in self._show_sign_in_buttons 265 ) 266 show_device_sign_in_button = ( 267 v1_state == 'signed_out' 268 and self._signing_in_adapter is None 269 and 'Device' in self._show_sign_in_buttons 270 ) 271 sign_in_button_space = 70.0 272 deprecated_space = 60 273 274 show_game_service_button = self._v1_signed_in and v1_account_type in [ 275 'Game Center' 276 ] 277 game_service_button_space = 60.0 278 279 show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2' 280 281 show_linked_accounts_text = self._v1_signed_in 282 linked_accounts_text_space = 60.0 283 284 show_achievements_button = self._v1_signed_in and v1_account_type in ( 285 'Google Play', 286 'Local', 287 'V2', 288 ) 289 achievements_button_space = 60.0 290 291 show_achievements_text = ( 292 self._v1_signed_in and not show_achievements_button 293 ) 294 achievements_text_space = 27.0 295 296 show_leaderboards_button = self._v1_signed_in and is_gpgs 297 leaderboards_button_space = 60.0 298 299 show_campaign_progress = self._v1_signed_in 300 campaign_progress_space = 27.0 301 302 show_tickets = self._v1_signed_in 303 tickets_space = 27.0 304 305 show_reset_progress_button = False 306 reset_progress_button_space = 70.0 307 308 show_manage_v2_account_button = ( 309 self._v1_signed_in and v1_account_type == 'V2' 310 ) 311 manage_v2_account_button_space = 100.0 312 313 show_player_profiles_button = self._v1_signed_in 314 player_profiles_button_space = ( 315 70.0 if show_manage_v2_account_button else 100.0 316 ) 317 318 show_link_accounts_button = self._v1_signed_in and ( 319 primary_v2_account is None or FORCE_ENABLE_V1_LINKING 320 ) 321 link_accounts_button_space = 70.0 322 323 show_unlink_accounts_button = show_link_accounts_button 324 unlink_accounts_button_space = 90.0 325 326 show_v2_link_info = self._v1_signed_in and not show_link_accounts_button 327 v2_link_info_space = 70.0 328 329 legacy_unlink_button_space = 120.0 330 331 show_sign_out_button = self._v1_signed_in and v1_account_type in [ 332 'Local', 333 'Google Play', 334 'V2', 335 ] 336 sign_out_button_space = 70.0 337 338 # We can show cancel if we're either waiting on an adapter to 339 # provide us with v2 credentials or waiting for those credentials 340 # to be verified. 341 show_cancel_sign_in_button = self._signing_in_adapter is not None or ( 342 plus.accounts.have_primary_credentials() 343 and primary_v2_account is None 344 ) 345 cancel_sign_in_button_space = 70.0 346 347 if self._subcontainer is not None: 348 self._subcontainer.delete() 349 self._sub_height = 60.0 350 if show_signed_in_as: 351 self._sub_height += signed_in_as_space 352 if show_signing_in_text: 353 self._sub_height += signing_in_text_space 354 if show_google_play_sign_in_button: 355 self._sub_height += sign_in_button_space 356 if show_v2_proxy_sign_in_button: 357 self._sub_height += sign_in_button_space 358 if show_device_sign_in_button: 359 self._sub_height += sign_in_button_space + deprecated_space 360 if show_game_service_button: 361 self._sub_height += game_service_button_space 362 if show_linked_accounts_text: 363 self._sub_height += linked_accounts_text_space 364 if show_achievements_text: 365 self._sub_height += achievements_text_space 366 if show_achievements_button: 367 self._sub_height += achievements_button_space 368 if show_leaderboards_button: 369 self._sub_height += leaderboards_button_space 370 if show_campaign_progress: 371 self._sub_height += campaign_progress_space 372 if show_tickets: 373 self._sub_height += tickets_space 374 if show_sign_in_benefits: 375 self._sub_height += sign_in_benefits_space 376 if show_reset_progress_button: 377 self._sub_height += reset_progress_button_space 378 if show_manage_v2_account_button: 379 self._sub_height += manage_v2_account_button_space 380 if show_player_profiles_button: 381 self._sub_height += player_profiles_button_space 382 if show_link_accounts_button: 383 self._sub_height += link_accounts_button_space 384 if show_unlink_accounts_button: 385 self._sub_height += unlink_accounts_button_space 386 if show_v2_link_info: 387 self._sub_height += v2_link_info_space 388 if self._show_legacy_unlink_button: 389 self._sub_height += legacy_unlink_button_space 390 if show_sign_out_button: 391 self._sub_height += sign_out_button_space 392 if show_cancel_sign_in_button: 393 self._sub_height += cancel_sign_in_button_space 394 self._subcontainer = bui.containerwidget( 395 parent=self._scrollwidget, 396 size=(self._sub_width, self._sub_height), 397 background=False, 398 claims_left_right=True, 399 claims_tab=True, 400 selection_loops_to_parent=True, 401 ) 402 403 first_selectable = None 404 v = self._sub_height - 10.0 405 406 assert bui.app.classic is not None 407 self._account_name_what_is_text: bui.Widget | None 408 self._account_name_what_is_y = 0.0 409 self._account_name_text: bui.Widget | None 410 if show_signed_in_as: 411 v -= signed_in_as_space * 0.2 412 txt = bui.Lstr( 413 resource='accountSettingsWindow.youAreSignedInAsText', 414 fallback_resource='accountSettingsWindow.youAreLoggedInAsText', 415 ) 416 bui.textwidget( 417 parent=self._subcontainer, 418 position=(self._sub_width * 0.5, v), 419 size=(0, 0), 420 text=txt, 421 scale=0.9, 422 color=bui.app.ui_v1.title_color, 423 maxwidth=self._sub_width * 0.9, 424 h_align='center', 425 v_align='center', 426 ) 427 v -= signed_in_as_space * 0.5 428 self._account_name_text = bui.textwidget( 429 parent=self._subcontainer, 430 position=(self._sub_width * 0.5, v), 431 size=(0, 0), 432 scale=1.5, 433 maxwidth=self._sub_width * 0.9, 434 res_scale=1.5, 435 color=(1, 1, 1, 1), 436 h_align='center', 437 v_align='center', 438 ) 439 440 if show_what_is_v2: 441 self._account_name_what_is_y = v - 23.0 442 self._account_name_what_is_text = bui.textwidget( 443 parent=self._subcontainer, 444 position=(0.0, self._account_name_what_is_y), 445 size=(200.0, 60), 446 text=bui.Lstr( 447 value='${WHAT} -->', 448 subs=[('${WHAT}', bui.Lstr(resource='whatIsThisText'))], 449 ), 450 scale=0.6, 451 color=(0.3, 0.7, 0.05), 452 maxwidth=200.0, 453 h_align='right', 454 v_align='center', 455 autoselect=True, 456 selectable=True, 457 on_activate_call=show_what_is_v2_page, 458 click_activate=True, 459 ) 460 if first_selectable is None: 461 first_selectable = self._account_name_what_is_text 462 else: 463 self._account_name_what_is_text = None 464 465 self._refresh_account_name_text() 466 467 v -= signed_in_as_space * 0.4 468 469 else: 470 self._account_name_text = None 471 self._account_name_what_is_text = None 472 473 if self._back_button is None: 474 bbtn = bui.get_special_widget('back_button') 475 else: 476 bbtn = self._back_button 477 478 if show_sign_in_benefits: 479 v -= sign_in_benefits_space 480 app = bui.app 481 assert app.classic is not None 482 extra: str | bui.Lstr | None 483 if ( 484 app.classic.platform in ['mac', 'ios'] 485 and app.classic.subplatform == 'appstore' 486 ): 487 extra = bui.Lstr( 488 value='\n${S}', 489 subs=[ 490 ('${S}', bui.Lstr(resource='signInWithGameCenterText')) 491 ], 492 ) 493 else: 494 extra = '' 495 496 bui.textwidget( 497 parent=self._subcontainer, 498 position=( 499 self._sub_width * 0.5, 500 v + sign_in_benefits_space * 0.4, 501 ), 502 size=(0, 0), 503 text=bui.Lstr( 504 value='${A}${B}', 505 subs=[ 506 ( 507 '${A}', 508 bui.Lstr(resource=self._r + '.signInInfoText'), 509 ), 510 ('${B}', extra), 511 ], 512 ), 513 max_height=sign_in_benefits_space * 0.9, 514 scale=0.9, 515 color=(0.75, 0.7, 0.8), 516 maxwidth=self._sub_width * 0.8, 517 h_align='center', 518 v_align='center', 519 ) 520 521 if show_signing_in_text: 522 v -= signing_in_text_space 523 524 bui.textwidget( 525 parent=self._subcontainer, 526 position=( 527 self._sub_width * 0.5, 528 v + signing_in_text_space * 0.5, 529 ), 530 size=(0, 0), 531 text=bui.Lstr(resource='accountSettingsWindow.signingInText'), 532 scale=0.9, 533 color=(0, 1, 0), 534 maxwidth=self._sub_width * 0.8, 535 h_align='center', 536 v_align='center', 537 ) 538 539 if show_google_play_sign_in_button: 540 button_width = 350 541 v -= sign_in_button_space 542 self._sign_in_google_play_button = btn = bui.buttonwidget( 543 parent=self._subcontainer, 544 position=((self._sub_width - button_width) * 0.5, v - 20), 545 autoselect=True, 546 size=(button_width, 60), 547 label=bui.Lstr( 548 value='${A}${B}', 549 subs=[ 550 ( 551 '${A}', 552 bui.charstr(bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO), 553 ), 554 ( 555 '${B}', 556 bui.Lstr( 557 resource=self._r + '.signInWithGooglePlayText' 558 ), 559 ), 560 ], 561 ), 562 on_activate_call=lambda: self._sign_in_press(LoginType.GPGS), 563 ) 564 if first_selectable is None: 565 first_selectable = btn 566 if bui.app.ui_v1.use_toolbars: 567 bui.widget( 568 edit=btn, 569 right_widget=bui.get_special_widget('party_button'), 570 ) 571 bui.widget(edit=btn, left_widget=bbtn) 572 bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 573 self._sign_in_text = None 574 575 if show_v2_proxy_sign_in_button: 576 button_width = 350 577 v -= sign_in_button_space 578 self._sign_in_v2_proxy_button = btn = bui.buttonwidget( 579 parent=self._subcontainer, 580 position=((self._sub_width - button_width) * 0.5, v - 20), 581 autoselect=True, 582 size=(button_width, 60), 583 label='', 584 on_activate_call=self._v2_proxy_sign_in_press, 585 ) 586 bui.textwidget( 587 parent=self._subcontainer, 588 draw_controller=btn, 589 h_align='center', 590 v_align='center', 591 size=(0, 0), 592 position=(self._sub_width * 0.5, v + 17), 593 text=bui.Lstr( 594 value='${A}${B}', 595 subs=[ 596 ('${A}', bui.charstr(bui.SpecialChar.V2_LOGO)), 597 ( 598 '${B}', 599 bui.Lstr(resource=self._r + '.signInWithV2Text'), 600 ), 601 ], 602 ), 603 maxwidth=button_width * 0.8, 604 color=(0.75, 1.0, 0.7), 605 ) 606 bui.textwidget( 607 parent=self._subcontainer, 608 draw_controller=btn, 609 h_align='center', 610 v_align='center', 611 size=(0, 0), 612 position=(self._sub_width * 0.5, v - 4), 613 text=bui.Lstr(resource=self._r + '.signInWithV2InfoText'), 614 flatness=1.0, 615 scale=0.57, 616 maxwidth=button_width * 0.9, 617 color=(0.55, 0.8, 0.5), 618 ) 619 if first_selectable is None: 620 first_selectable = btn 621 if bui.app.ui_v1.use_toolbars: 622 bui.widget( 623 edit=btn, 624 right_widget=bui.get_special_widget('party_button'), 625 ) 626 bui.widget(edit=btn, left_widget=bbtn) 627 bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 628 self._sign_in_text = None 629 630 if show_device_sign_in_button: 631 button_width = 350 632 v -= sign_in_button_space + deprecated_space 633 self._sign_in_device_button = btn = bui.buttonwidget( 634 parent=self._subcontainer, 635 position=((self._sub_width - button_width) * 0.5, v - 20), 636 autoselect=True, 637 size=(button_width, 60), 638 label='', 639 on_activate_call=lambda: self._sign_in_press('Local'), 640 ) 641 bui.textwidget( 642 parent=self._subcontainer, 643 h_align='center', 644 v_align='center', 645 size=(0, 0), 646 position=(self._sub_width * 0.5, v + 60), 647 text=bui.Lstr(resource='deprecatedText'), 648 scale=0.8, 649 maxwidth=300, 650 color=(0.6, 0.55, 0.45), 651 ) 652 653 bui.textwidget( 654 parent=self._subcontainer, 655 draw_controller=btn, 656 h_align='center', 657 v_align='center', 658 size=(0, 0), 659 position=(self._sub_width * 0.5, v + 17), 660 text=bui.Lstr( 661 value='${A}${B}', 662 subs=[ 663 ('${A}', bui.charstr(bui.SpecialChar.LOCAL_ACCOUNT)), 664 ( 665 '${B}', 666 bui.Lstr( 667 resource=self._r + '.signInWithDeviceText' 668 ), 669 ), 670 ], 671 ), 672 maxwidth=button_width * 0.8, 673 color=(0.75, 1.0, 0.7), 674 ) 675 bui.textwidget( 676 parent=self._subcontainer, 677 draw_controller=btn, 678 h_align='center', 679 v_align='center', 680 size=(0, 0), 681 position=(self._sub_width * 0.5, v - 4), 682 text=bui.Lstr(resource=self._r + '.signInWithDeviceInfoText'), 683 flatness=1.0, 684 scale=0.57, 685 maxwidth=button_width * 0.9, 686 color=(0.55, 0.8, 0.5), 687 ) 688 if first_selectable is None: 689 first_selectable = btn 690 if bui.app.ui_v1.use_toolbars: 691 bui.widget( 692 edit=btn, 693 right_widget=bui.get_special_widget('party_button'), 694 ) 695 bui.widget(edit=btn, left_widget=bbtn) 696 bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 697 self._sign_in_text = None 698 699 if show_manage_v2_account_button: 700 button_width = 300 701 v -= manage_v2_account_button_space 702 self._manage_v2_button = btn = bui.buttonwidget( 703 parent=self._subcontainer, 704 position=((self._sub_width - button_width) * 0.5, v + 30), 705 autoselect=True, 706 size=(button_width, 60), 707 label=bui.Lstr(resource=self._r + '.manageAccountText'), 708 color=(0.55, 0.5, 0.6), 709 icon=bui.gettexture('settingsIcon'), 710 textcolor=(0.75, 0.7, 0.8), 711 on_activate_call=bui.WeakCall(self._on_manage_account_press), 712 ) 713 if first_selectable is None: 714 first_selectable = btn 715 if bui.app.ui_v1.use_toolbars: 716 bui.widget( 717 edit=btn, 718 right_widget=bui.get_special_widget('party_button'), 719 ) 720 bui.widget(edit=btn, left_widget=bbtn) 721 722 if show_player_profiles_button: 723 button_width = 300 724 v -= player_profiles_button_space 725 self._player_profiles_button = btn = bui.buttonwidget( 726 parent=self._subcontainer, 727 position=((self._sub_width - button_width) * 0.5, v + 30), 728 autoselect=True, 729 size=(button_width, 60), 730 label=bui.Lstr(resource='playerProfilesWindow.titleText'), 731 color=(0.55, 0.5, 0.6), 732 icon=bui.gettexture('cuteSpaz'), 733 textcolor=(0.75, 0.7, 0.8), 734 on_activate_call=self._player_profiles_press, 735 ) 736 if first_selectable is None: 737 first_selectable = btn 738 if bui.app.ui_v1.use_toolbars: 739 bui.widget( 740 edit=btn, 741 right_widget=bui.get_special_widget('party_button'), 742 ) 743 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0) 744 745 # the button to go to OS-Specific leaderboards/high-score-lists/etc. 746 if show_game_service_button: 747 button_width = 300 748 v -= game_service_button_space * 0.85 749 v1_account_type = plus.get_v1_account_type() 750 if v1_account_type == 'Game Center': 751 v1_account_type_name = bui.Lstr(resource='gameCenterText') 752 else: 753 raise ValueError( 754 "unknown account type: '" + str(v1_account_type) + "'" 755 ) 756 self._game_service_button = btn = bui.buttonwidget( 757 parent=self._subcontainer, 758 position=((self._sub_width - button_width) * 0.5, v), 759 color=(0.55, 0.5, 0.6), 760 textcolor=(0.75, 0.7, 0.8), 761 autoselect=True, 762 on_activate_call=self._on_game_service_button_press, 763 size=(button_width, 50), 764 label=v1_account_type_name, 765 ) 766 if first_selectable is None: 767 first_selectable = btn 768 if bui.app.ui_v1.use_toolbars: 769 bui.widget( 770 edit=btn, 771 right_widget=bui.get_special_widget('party_button'), 772 ) 773 bui.widget(edit=btn, left_widget=bbtn) 774 v -= game_service_button_space * 0.15 775 else: 776 self.game_service_button = None 777 778 self._achievements_text: bui.Widget | None 779 if show_achievements_text: 780 v -= achievements_text_space * 0.5 781 self._achievements_text = bui.textwidget( 782 parent=self._subcontainer, 783 position=(self._sub_width * 0.5, v), 784 size=(0, 0), 785 scale=0.9, 786 color=(0.75, 0.7, 0.8), 787 maxwidth=self._sub_width * 0.8, 788 h_align='center', 789 v_align='center', 790 ) 791 v -= achievements_text_space * 0.5 792 else: 793 self._achievements_text = None 794 795 self._achievements_button: bui.Widget | None 796 if show_achievements_button: 797 button_width = 300 798 v -= achievements_button_space * 0.85 799 self._achievements_button = btn = bui.buttonwidget( 800 parent=self._subcontainer, 801 position=((self._sub_width - button_width) * 0.5, v), 802 color=(0.55, 0.5, 0.6), 803 textcolor=(0.75, 0.7, 0.8), 804 autoselect=True, 805 icon=bui.gettexture( 806 'googlePlayAchievementsIcon' 807 if is_gpgs 808 else 'achievementsIcon' 809 ), 810 icon_color=(0.8, 0.95, 0.7) if is_gpgs else (0.85, 0.8, 0.9), 811 on_activate_call=( 812 self._on_custom_achievements_press 813 if is_gpgs 814 else self._on_achievements_press 815 ), 816 size=(button_width, 50), 817 label='', 818 ) 819 if first_selectable is None: 820 first_selectable = btn 821 if bui.app.ui_v1.use_toolbars: 822 bui.widget( 823 edit=btn, 824 right_widget=bui.get_special_widget('party_button'), 825 ) 826 bui.widget(edit=btn, left_widget=bbtn) 827 v -= achievements_button_space * 0.15 828 else: 829 self._achievements_button = None 830 831 if show_achievements_text or show_achievements_button: 832 self._refresh_achievements() 833 834 self._leaderboards_button: bui.Widget | None 835 if show_leaderboards_button: 836 button_width = 300 837 v -= leaderboards_button_space * 0.85 838 self._leaderboards_button = btn = bui.buttonwidget( 839 parent=self._subcontainer, 840 position=((self._sub_width - button_width) * 0.5, v), 841 color=(0.55, 0.5, 0.6), 842 textcolor=(0.75, 0.7, 0.8), 843 autoselect=True, 844 icon=bui.gettexture('googlePlayLeaderboardsIcon'), 845 icon_color=(0.8, 0.95, 0.7), 846 on_activate_call=self._on_leaderboards_press, 847 size=(button_width, 50), 848 label=bui.Lstr(resource='leaderboardsText'), 849 ) 850 if first_selectable is None: 851 first_selectable = btn 852 if bui.app.ui_v1.use_toolbars: 853 bui.widget( 854 edit=btn, 855 right_widget=bui.get_special_widget('party_button'), 856 ) 857 bui.widget(edit=btn, left_widget=bbtn) 858 v -= leaderboards_button_space * 0.15 859 else: 860 self._leaderboards_button = None 861 862 self._campaign_progress_text: bui.Widget | None 863 if show_campaign_progress: 864 v -= campaign_progress_space * 0.5 865 self._campaign_progress_text = bui.textwidget( 866 parent=self._subcontainer, 867 position=(self._sub_width * 0.5, v), 868 size=(0, 0), 869 scale=0.9, 870 color=(0.75, 0.7, 0.8), 871 maxwidth=self._sub_width * 0.8, 872 h_align='center', 873 v_align='center', 874 ) 875 v -= campaign_progress_space * 0.5 876 self._refresh_campaign_progress_text() 877 else: 878 self._campaign_progress_text = None 879 880 self._tickets_text: bui.Widget | None 881 if show_tickets: 882 v -= tickets_space * 0.5 883 self._tickets_text = bui.textwidget( 884 parent=self._subcontainer, 885 position=(self._sub_width * 0.5, v), 886 size=(0, 0), 887 scale=0.9, 888 color=(0.75, 0.7, 0.8), 889 maxwidth=self._sub_width * 0.8, 890 flatness=1.0, 891 h_align='center', 892 v_align='center', 893 ) 894 v -= tickets_space * 0.5 895 self._refresh_tickets_text() 896 897 else: 898 self._tickets_text = None 899 900 # bit of spacing before the reset/sign-out section 901 v -= 5 902 903 button_width = 250 904 if show_reset_progress_button: 905 confirm_text = ( 906 bui.Lstr(resource=self._r + '.resetProgressConfirmText') 907 if self._can_reset_achievements 908 else bui.Lstr( 909 resource=self._r + '.resetProgressConfirmNoAchievementsText' 910 ) 911 ) 912 v -= reset_progress_button_space 913 self._reset_progress_button = btn = bui.buttonwidget( 914 parent=self._subcontainer, 915 position=((self._sub_width - button_width) * 0.5, v), 916 color=(0.55, 0.5, 0.6), 917 textcolor=(0.75, 0.7, 0.8), 918 autoselect=True, 919 size=(button_width, 60), 920 label=bui.Lstr(resource=self._r + '.resetProgressText'), 921 on_activate_call=lambda: confirm.ConfirmWindow( 922 text=confirm_text, 923 width=500, 924 height=200, 925 action=self._reset_progress, 926 ), 927 ) 928 if first_selectable is None: 929 first_selectable = btn 930 if bui.app.ui_v1.use_toolbars: 931 bui.widget( 932 edit=btn, 933 right_widget=bui.get_special_widget('party_button'), 934 ) 935 bui.widget(edit=btn, left_widget=bbtn) 936 937 self._linked_accounts_text: bui.Widget | None 938 if show_linked_accounts_text: 939 v -= linked_accounts_text_space * 0.8 940 self._linked_accounts_text = bui.textwidget( 941 parent=self._subcontainer, 942 position=(self._sub_width * 0.5, v), 943 size=(0, 0), 944 scale=0.9, 945 color=(0.75, 0.7, 0.8), 946 maxwidth=self._sub_width * 0.95, 947 text=bui.Lstr(resource=self._r + '.linkedAccountsText'), 948 h_align='center', 949 v_align='center', 950 ) 951 v -= linked_accounts_text_space * 0.2 952 self._update_linked_accounts_text() 953 else: 954 self._linked_accounts_text = None 955 956 # Show link/unlink buttons only for V1 accounts. 957 958 if show_link_accounts_button: 959 v -= link_accounts_button_space 960 self._link_accounts_button = btn = bui.buttonwidget( 961 parent=self._subcontainer, 962 position=((self._sub_width - button_width) * 0.5, v), 963 autoselect=True, 964 size=(button_width, 60), 965 label='', 966 color=(0.55, 0.5, 0.6), 967 on_activate_call=self._link_accounts_press, 968 ) 969 bui.textwidget( 970 parent=self._subcontainer, 971 draw_controller=btn, 972 h_align='center', 973 v_align='center', 974 size=(0, 0), 975 position=(self._sub_width * 0.5, v + 17 + 20), 976 text=bui.Lstr(resource=self._r + '.linkAccountsText'), 977 maxwidth=button_width * 0.8, 978 color=(0.75, 0.7, 0.8), 979 ) 980 bui.textwidget( 981 parent=self._subcontainer, 982 draw_controller=btn, 983 h_align='center', 984 v_align='center', 985 size=(0, 0), 986 position=(self._sub_width * 0.5, v - 4 + 20), 987 text=bui.Lstr(resource=self._r + '.linkAccountsInfoText'), 988 flatness=1.0, 989 scale=0.5, 990 maxwidth=button_width * 0.8, 991 color=(0.75, 0.7, 0.8), 992 ) 993 if first_selectable is None: 994 first_selectable = btn 995 if bui.app.ui_v1.use_toolbars: 996 bui.widget( 997 edit=btn, 998 right_widget=bui.get_special_widget('party_button'), 999 ) 1000 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 1001 1002 self._unlink_accounts_button: bui.Widget | None 1003 if show_unlink_accounts_button: 1004 v -= unlink_accounts_button_space 1005 self._unlink_accounts_button = btn = bui.buttonwidget( 1006 parent=self._subcontainer, 1007 position=((self._sub_width - button_width) * 0.5, v + 25), 1008 autoselect=True, 1009 size=(button_width, 60), 1010 label='', 1011 color=(0.55, 0.5, 0.6), 1012 on_activate_call=self._unlink_accounts_press, 1013 ) 1014 self._unlink_accounts_button_label = bui.textwidget( 1015 parent=self._subcontainer, 1016 draw_controller=btn, 1017 h_align='center', 1018 v_align='center', 1019 size=(0, 0), 1020 position=(self._sub_width * 0.5, v + 55), 1021 text=bui.Lstr(resource=self._r + '.unlinkAccountsText'), 1022 maxwidth=button_width * 0.8, 1023 color=(0.75, 0.7, 0.8), 1024 ) 1025 if first_selectable is None: 1026 first_selectable = btn 1027 if bui.app.ui_v1.use_toolbars: 1028 bui.widget( 1029 edit=btn, 1030 right_widget=bui.get_special_widget('party_button'), 1031 ) 1032 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 1033 self._update_unlink_accounts_button() 1034 else: 1035 self._unlink_accounts_button = None 1036 1037 if show_v2_link_info: 1038 v -= v2_link_info_space 1039 bui.textwidget( 1040 parent=self._subcontainer, 1041 h_align='center', 1042 v_align='center', 1043 size=(0, 0), 1044 position=(self._sub_width * 0.5, v + v2_link_info_space - 20), 1045 text=bui.Lstr(resource='v2AccountLinkingInfoText'), 1046 flatness=1.0, 1047 scale=0.8, 1048 maxwidth=450, 1049 color=(0.5, 0.45, 0.55), 1050 ) 1051 1052 if self._show_legacy_unlink_button: 1053 v -= legacy_unlink_button_space 1054 button_width_w = button_width * 1.5 1055 bui.textwidget( 1056 parent=self._subcontainer, 1057 position=(self._sub_width * 0.5 - 150.0, v + 75), 1058 size=(300.0, 60), 1059 text=bui.Lstr(resource='whatIsThisText'), 1060 scale=0.8, 1061 color=(0.3, 0.7, 0.05), 1062 maxwidth=200.0, 1063 h_align='center', 1064 v_align='center', 1065 autoselect=True, 1066 selectable=True, 1067 on_activate_call=show_what_is_legacy_unlinking_page, 1068 click_activate=True, 1069 ) 1070 btn = bui.buttonwidget( 1071 parent=self._subcontainer, 1072 position=((self._sub_width - button_width_w) * 0.5, v + 25), 1073 autoselect=True, 1074 size=(button_width_w, 60), 1075 label=bui.Lstr( 1076 resource=self._r + '.unlinkLegacyV1AccountsText' 1077 ), 1078 textcolor=(0.8, 0.4, 0), 1079 color=(0.55, 0.5, 0.6), 1080 on_activate_call=self._unlink_accounts_press, 1081 ) 1082 1083 if show_sign_out_button: 1084 v -= sign_out_button_space 1085 self._sign_out_button = btn = bui.buttonwidget( 1086 parent=self._subcontainer, 1087 position=((self._sub_width - button_width) * 0.5, v), 1088 size=(button_width, 60), 1089 label=bui.Lstr(resource=self._r + '.signOutText'), 1090 color=(0.55, 0.5, 0.6), 1091 textcolor=(0.75, 0.7, 0.8), 1092 autoselect=True, 1093 on_activate_call=self._sign_out_press, 1094 ) 1095 if first_selectable is None: 1096 first_selectable = btn 1097 if bui.app.ui_v1.use_toolbars: 1098 bui.widget( 1099 edit=btn, 1100 right_widget=bui.get_special_widget('party_button'), 1101 ) 1102 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 1103 1104 if show_cancel_sign_in_button: 1105 v -= cancel_sign_in_button_space 1106 self._cancel_sign_in_button = btn = bui.buttonwidget( 1107 parent=self._subcontainer, 1108 position=((self._sub_width - button_width) * 0.5, v), 1109 size=(button_width, 60), 1110 label=bui.Lstr(resource='cancelText'), 1111 color=(0.55, 0.5, 0.6), 1112 textcolor=(0.75, 0.7, 0.8), 1113 autoselect=True, 1114 on_activate_call=self._cancel_sign_in_press, 1115 ) 1116 if first_selectable is None: 1117 first_selectable = btn 1118 if bui.app.ui_v1.use_toolbars: 1119 bui.widget( 1120 edit=btn, 1121 right_widget=bui.get_special_widget('party_button'), 1122 ) 1123 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 1124 1125 # Whatever the topmost selectable thing is, we want it to scroll all 1126 # the way up when we select it. 1127 if first_selectable is not None: 1128 bui.widget( 1129 edit=first_selectable, up_widget=bbtn, show_buffer_top=400 1130 ) 1131 # (this should re-scroll us to the top..) 1132 bui.containerwidget( 1133 edit=self._subcontainer, visible_child=first_selectable 1134 ) 1135 self._needs_refresh = False 1136 1137 def _on_game_service_button_press(self) -> None: 1138 if bui.app.classic is not None: 1139 bui.app.classic.show_online_score_ui() 1140 else: 1141 logging.warning('game service ui not available without classic.') 1142 1143 def _on_custom_achievements_press(self) -> None: 1144 if bui.app.classic is not None: 1145 bui.apptimer( 1146 0.15, 1147 bui.Call(bui.app.classic.show_online_score_ui, 'achievements'), 1148 ) 1149 else: 1150 logging.warning('show_online_score_ui requires classic') 1151 1152 def _on_achievements_press(self) -> None: 1153 # pylint: disable=cyclic-import 1154 from bauiv1lib import achievements 1155 1156 assert self._achievements_button is not None 1157 achievements.AchievementsWindow( 1158 position=self._achievements_button.get_screen_space_center() 1159 ) 1160 1161 def _on_what_is_v2_press(self) -> None: 1162 show_what_is_v2_page() 1163 1164 def _on_manage_account_press(self) -> None: 1165 bui.screenmessage(bui.Lstr(resource='oneMomentText')) 1166 1167 plus = bui.app.plus 1168 assert plus is not None 1169 1170 # We expect to have a v2 account signed in if we get here. 1171 if plus.accounts.primary is None: 1172 logging.exception( 1173 'got manage-account press without v2 account present' 1174 ) 1175 return 1176 1177 with plus.accounts.primary: 1178 plus.cloud.send_message_cb( 1179 bacommon.cloud.ManageAccountMessage(), 1180 on_response=bui.WeakCall(self._on_manage_account_response), 1181 ) 1182 1183 def _on_manage_account_response( 1184 self, response: bacommon.cloud.ManageAccountResponse | Exception 1185 ) -> None: 1186 if isinstance(response, Exception) or response.url is None: 1187 bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) 1188 bui.getsound('error').play() 1189 return 1190 1191 bui.open_url(response.url) 1192 1193 def _on_leaderboards_press(self) -> None: 1194 if bui.app.classic is not None: 1195 bui.apptimer( 1196 0.15, 1197 bui.Call(bui.app.classic.show_online_score_ui, 'leaderboards'), 1198 ) 1199 else: 1200 logging.warning('show_online_score_ui requires classic') 1201 1202 def _have_unlinkable_v1_accounts(self) -> bool: 1203 plus = bui.app.plus 1204 assert plus is not None 1205 1206 # if this is not present, we haven't had contact from the server so 1207 # let's not proceed.. 1208 if plus.get_v1_account_public_login_id() is None: 1209 return False 1210 accounts = plus.get_v1_account_misc_read_val_2('linkedAccounts', []) 1211 return len(accounts) > 1 1212 1213 def _update_unlink_accounts_button(self) -> None: 1214 if self._unlink_accounts_button is None: 1215 return 1216 if self._have_unlinkable_v1_accounts(): 1217 clr = (0.75, 0.7, 0.8, 1.0) 1218 else: 1219 clr = (1.0, 1.0, 1.0, 0.25) 1220 bui.textwidget(edit=self._unlink_accounts_button_label, color=clr) 1221 1222 def _should_show_legacy_unlink_button(self) -> bool: 1223 plus = bui.app.plus 1224 assert plus is not None 1225 1226 # Only show this when fully signed in to a v2 account. 1227 if not self._v1_signed_in or plus.accounts.primary is None: 1228 return False 1229 1230 out = self._have_unlinkable_v1_accounts() 1231 return out 1232 1233 def _update_linked_accounts_text(self) -> None: 1234 plus = bui.app.plus 1235 assert plus is not None 1236 1237 if self._linked_accounts_text is None: 1238 return 1239 1240 # Disable this by default when signed in to a V2 account 1241 # (since this shows V1 links which we should no longer care about). 1242 if plus.accounts.primary is not None and not FORCE_ENABLE_V1_LINKING: 1243 return 1244 1245 # if this is not present, we haven't had contact from the server so 1246 # let's not proceed.. 1247 if plus.get_v1_account_public_login_id() is None: 1248 num = int(time.time()) % 4 1249 accounts_str = num * '.' + (4 - num) * ' ' 1250 else: 1251 accounts = plus.get_v1_account_misc_read_val_2('linkedAccounts', []) 1252 # UPDATE - we now just print the number here; not the actual 1253 # accounts (they can see that in the unlink section if they're 1254 # curious) 1255 accounts_str = str(max(0, len(accounts) - 1)) 1256 bui.textwidget( 1257 edit=self._linked_accounts_text, 1258 text=bui.Lstr( 1259 value='${L} ${A}', 1260 subs=[ 1261 ( 1262 '${L}', 1263 bui.Lstr(resource=self._r + '.linkedAccountsText'), 1264 ), 1265 ('${A}', accounts_str), 1266 ], 1267 ), 1268 ) 1269 1270 def _refresh_campaign_progress_text(self) -> None: 1271 if self._campaign_progress_text is None: 1272 return 1273 p_str: str | bui.Lstr 1274 try: 1275 assert bui.app.classic is not None 1276 campaign = bui.app.classic.getcampaign('Default') 1277 levels = campaign.levels 1278 levels_complete = sum((1 if l.complete else 0) for l in levels) 1279 1280 # Last level cant be completed; hence the -1; 1281 progress = min(1.0, float(levels_complete) / (len(levels) - 1)) 1282 p_str = bui.Lstr( 1283 resource=self._r + '.campaignProgressText', 1284 subs=[('${PROGRESS}', str(int(progress * 100.0)) + '%')], 1285 ) 1286 except Exception: 1287 p_str = '?' 1288 logging.exception('Error calculating co-op campaign progress.') 1289 bui.textwidget(edit=self._campaign_progress_text, text=p_str) 1290 1291 def _refresh_tickets_text(self) -> None: 1292 plus = bui.app.plus 1293 assert plus is not None 1294 1295 if self._tickets_text is None: 1296 return 1297 try: 1298 tc_str = str(plus.get_v1_account_ticket_count()) 1299 except Exception: 1300 logging.exception('error refreshing tickets text') 1301 tc_str = '-' 1302 bui.textwidget( 1303 edit=self._tickets_text, 1304 text=bui.Lstr( 1305 resource=self._r + '.ticketsText', subs=[('${COUNT}', tc_str)] 1306 ), 1307 ) 1308 1309 def _refresh_account_name_text(self) -> None: 1310 plus = bui.app.plus 1311 assert plus is not None 1312 1313 if self._account_name_text is None: 1314 return 1315 try: 1316 name_str = plus.get_v1_account_display_string() 1317 except Exception: 1318 logging.exception('error refreshing tickets text') 1319 name_str = '??' 1320 1321 bui.textwidget(edit=self._account_name_text, text=name_str) 1322 if self._account_name_what_is_text is not None: 1323 swidth = bui.get_string_width(name_str, suppress_warning=True) 1324 # Eww; number-fudging. Need to recalibrate this if 1325 # account name scaling changes. 1326 x = self._sub_width * 0.5 - swidth * 0.75 - 170 1327 1328 bui.textwidget( 1329 edit=self._account_name_what_is_text, 1330 position=(x, self._account_name_what_is_y), 1331 ) 1332 1333 def _refresh_achievements(self) -> None: 1334 assert bui.app.classic is not None 1335 if ( 1336 self._achievements_text is None 1337 and self._achievements_button is None 1338 ): 1339 return 1340 complete = sum( 1341 1 if a.complete else 0 for a in bui.app.classic.ach.achievements 1342 ) 1343 total = len(bui.app.classic.ach.achievements) 1344 txt_final = bui.Lstr( 1345 resource=self._r + '.achievementProgressText', 1346 subs=[('${COUNT}', str(complete)), ('${TOTAL}', str(total))], 1347 ) 1348 1349 if self._achievements_text is not None: 1350 bui.textwidget(edit=self._achievements_text, text=txt_final) 1351 if self._achievements_button is not None: 1352 bui.buttonwidget(edit=self._achievements_button, label=txt_final) 1353 1354 def _link_accounts_press(self) -> None: 1355 # pylint: disable=cyclic-import 1356 from bauiv1lib.account.link import AccountLinkWindow 1357 1358 AccountLinkWindow(origin_widget=self._link_accounts_button) 1359 1360 def _unlink_accounts_press(self) -> None: 1361 # pylint: disable=cyclic-import 1362 from bauiv1lib.account.unlink import AccountUnlinkWindow 1363 1364 if not self._have_unlinkable_v1_accounts(): 1365 bui.getsound('error').play() 1366 return 1367 1368 AccountUnlinkWindow(origin_widget=self._unlink_accounts_button) 1369 1370 def _player_profiles_press(self) -> None: 1371 # pylint: disable=cyclic-import 1372 from bauiv1lib.profile.browser import ProfileBrowserWindow 1373 1374 self._save_state() 1375 bui.containerwidget(edit=self._root_widget, transition='out_left') 1376 ProfileBrowserWindow(origin_widget=self._player_profiles_button) 1377 1378 def _cancel_sign_in_press(self) -> None: 1379 # If we're waiting on an adapter to give us credentials, abort. 1380 self._signing_in_adapter = None 1381 1382 plus = bui.app.plus 1383 assert plus is not None 1384 1385 # Say we don't wanna be signed in anymore if we are. 1386 plus.accounts.set_primary_credentials(None) 1387 1388 self._needs_refresh = True 1389 1390 # Speed UI updates along. 1391 bui.apptimer(0.1, bui.WeakCall(self._update)) 1392 1393 def _sign_out_press(self) -> None: 1394 plus = bui.app.plus 1395 assert plus is not None 1396 1397 if plus.accounts.have_primary_credentials(): 1398 if ( 1399 plus.accounts.primary is not None 1400 and LoginType.GPGS in plus.accounts.primary.logins 1401 ): 1402 self._explicitly_signed_out_of_gpgs = True 1403 plus.accounts.set_primary_credentials(None) 1404 else: 1405 plus.sign_out_v1() 1406 1407 cfg = bui.app.config 1408 1409 # Also take note that its our *explicit* intention to not be 1410 # signed in at this point (affects v1 accounts). 1411 cfg['Auto Account State'] = 'signed_out' 1412 cfg.commit() 1413 bui.buttonwidget( 1414 edit=self._sign_out_button, 1415 label=bui.Lstr(resource=self._r + '.signingOutText'), 1416 ) 1417 1418 # Speed UI updates along. 1419 bui.apptimer(0.1, bui.WeakCall(self._update)) 1420 1421 def _sign_in_press(self, login_type: str | LoginType) -> None: 1422 plus = bui.app.plus 1423 assert plus is not None 1424 1425 # V1 login types are strings. 1426 if isinstance(login_type, str): 1427 plus.sign_in_v1(login_type) 1428 1429 # Make note of the type account we're *wanting* 1430 # to be signed in with. 1431 cfg = bui.app.config 1432 cfg['Auto Account State'] = login_type 1433 cfg.commit() 1434 self._needs_refresh = True 1435 bui.apptimer(0.1, bui.WeakCall(self._update)) 1436 return 1437 1438 # V2 login sign-in buttons generally go through adapters. 1439 adapter = plus.accounts.login_adapters.get(login_type) 1440 if adapter is not None: 1441 self._signing_in_adapter = adapter 1442 adapter.sign_in( 1443 result_cb=bui.WeakCall(self._on_adapter_sign_in_result), 1444 description='account settings button', 1445 ) 1446 # Will get 'Signing in...' to show. 1447 self._needs_refresh = True 1448 bui.apptimer(0.1, bui.WeakCall(self._update)) 1449 else: 1450 bui.screenmessage(f'Unsupported login_type: {login_type.name}') 1451 1452 def _on_adapter_sign_in_result( 1453 self, 1454 adapter: bui.LoginAdapter, 1455 result: bui.LoginAdapter.SignInResult | Exception, 1456 ) -> None: 1457 is_us = self._signing_in_adapter is adapter 1458 1459 # If this isn't our current one we don't care. 1460 if not is_us: 1461 return 1462 1463 # If it is us, note that we're done. 1464 self._signing_in_adapter = None 1465 1466 if isinstance(result, Exception): 1467 # For now just make a bit of noise if anything went wrong; 1468 # can get more specific as needed later. 1469 bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) 1470 bui.getsound('error').play() 1471 else: 1472 # Success! Plug in these credentials which will begin 1473 # verifying them and set our primary account-handle 1474 # when finished. 1475 plus = bui.app.plus 1476 assert plus is not None 1477 plus.accounts.set_primary_credentials(result.credentials) 1478 1479 # Special case - if the user has explicitly logged out and 1480 # logged in again with GPGS via this button, warn them that 1481 # they need to use the app if they want to switch to a 1482 # different GPGS account. 1483 if ( 1484 self._explicitly_signed_out_of_gpgs 1485 and adapter.login_type is LoginType.GPGS 1486 ): 1487 # Delay this slightly so it hopefully pops up after 1488 # credentials go through and the account name shows up. 1489 bui.apptimer( 1490 1.5, 1491 bui.Call( 1492 bui.screenmessage, 1493 bui.Lstr( 1494 resource=self._r 1495 + '.googlePlayGamesAccountSwitchText' 1496 ), 1497 ), 1498 ) 1499 1500 # Speed any UI updates along. 1501 self._needs_refresh = True 1502 bui.apptimer(0.1, bui.WeakCall(self._update)) 1503 1504 def _v2_proxy_sign_in_press(self) -> None: 1505 # pylint: disable=cyclic-import 1506 from bauiv1lib.account.v2proxy import V2ProxySignInWindow 1507 1508 assert self._sign_in_v2_proxy_button is not None 1509 V2ProxySignInWindow(origin_widget=self._sign_in_v2_proxy_button) 1510 1511 def _reset_progress(self) -> None: 1512 try: 1513 assert bui.app.classic is not None 1514 # FIXME: This would need to happen server-side these days. 1515 if self._can_reset_achievements: 1516 logging.warning('ach resets not wired up.') 1517 # bui.app.config['Achievements'] = {} 1518 # bui.reset_achievements() 1519 campaign = bui.app.classic.getcampaign('Default') 1520 campaign.reset() # also writes the config.. 1521 campaign = bui.app.classic.getcampaign('Challenges') 1522 campaign.reset() # also writes the config.. 1523 except Exception: 1524 logging.exception('Error resetting co-op campaign progress.') 1525 1526 bui.getsound('shieldDown').play() 1527 self._refresh() 1528 1529 def _back(self) -> None: 1530 # pylint: disable=cyclic-import 1531 from bauiv1lib.mainmenu import MainMenuWindow 1532 1533 self._save_state() 1534 bui.containerwidget( 1535 edit=self._root_widget, transition=self._transition_out 1536 ) 1537 1538 if not self._modal: 1539 assert bui.app.classic is not None 1540 bui.app.ui_v1.set_main_menu_window( 1541 MainMenuWindow(transition='in_left').get_root_widget() 1542 ) 1543 1544 def _save_state(self) -> None: 1545 try: 1546 sel = self._root_widget.get_selected_child() 1547 if sel == self._back_button: 1548 sel_name = 'Back' 1549 elif sel == self._scrollwidget: 1550 sel_name = 'Scroll' 1551 else: 1552 raise ValueError('unrecognized selection') 1553 assert bui.app.classic is not None 1554 bui.app.ui_v1.window_states[type(self)] = sel_name 1555 except Exception: 1556 logging.exception('Error saving state for %s.', self) 1557 1558 def _restore_state(self) -> None: 1559 try: 1560 assert bui.app.classic is not None 1561 sel_name = bui.app.ui_v1.window_states.get(type(self)) 1562 if sel_name == 'Back': 1563 sel = self._back_button 1564 elif sel_name == 'Scroll': 1565 sel = self._scrollwidget 1566 else: 1567 sel = self._back_button 1568 bui.containerwidget(edit=self._root_widget, selected_child=sel) 1569 except Exception: 1570 logging.exception('Error restoring state for %s.', self) 1571 1572 1573def show_what_is_v2_page() -> None: 1574 """Show the webpage describing V2 accounts.""" 1575 plus = bui.app.plus 1576 assert plus is not None 1577 1578 bamasteraddr = plus.get_master_server_address(version=2) 1579 bui.open_url(f'{bamasteraddr}/whatisv2') 1580 1581 1582def show_what_is_legacy_unlinking_page() -> None: 1583 """Show the webpage describing legacy unlinking.""" 1584 plus = bui.app.plus 1585 assert plus is not None 1586 1587 bamasteraddr = plus.get_master_server_address(version=2) 1588 bui.open_url(f'{bamasteraddr}/whatarev1links')
FORCE_ENABLE_V1_LINKING =
False
class
AccountSettingsWindow(bauiv1._uitypes.Window):
22class AccountSettingsWindow(bui.Window): 23 """Window for account related functionality.""" 24 25 def __init__( 26 self, 27 transition: str = 'in_right', 28 modal: bool = False, 29 origin_widget: bui.Widget | None = None, 30 close_once_signed_in: bool = False, 31 ): 32 # pylint: disable=too-many-statements 33 34 plus = bui.app.plus 35 assert plus is not None 36 37 self._sign_in_v2_proxy_button: bui.Widget | None = None 38 self._sign_in_device_button: bui.Widget | None = None 39 40 self._show_legacy_unlink_button = False 41 42 self._signing_in_adapter: bui.LoginAdapter | None = None 43 self._close_once_signed_in = close_once_signed_in 44 bui.set_analytics_screen('Account Window') 45 46 self._explicitly_signed_out_of_gpgs = False 47 48 # If they provided an origin-widget, scale up from that. 49 scale_origin: tuple[float, float] | None 50 if origin_widget is not None: 51 self._transition_out = 'out_scale' 52 scale_origin = origin_widget.get_screen_space_center() 53 transition = 'in_scale' 54 else: 55 self._transition_out = 'out_right' 56 scale_origin = None 57 58 self._r = 'accountSettingsWindow' 59 self._modal = modal 60 self._needs_refresh = False 61 self._v1_signed_in = plus.get_v1_account_state() == 'signed_in' 62 self._v1_account_state_num = plus.get_v1_account_state_num() 63 self._check_sign_in_timer = bui.AppTimer( 64 1.0, bui.WeakCall(self._update), repeat=True 65 ) 66 67 # Currently we can only reset achievements on game-center. 68 v1_account_type: str | None 69 if self._v1_signed_in: 70 v1_account_type = plus.get_v1_account_type() 71 else: 72 v1_account_type = None 73 self._can_reset_achievements = v1_account_type == 'Game Center' 74 75 app = bui.app 76 assert app.classic is not None 77 uiscale = app.ui_v1.uiscale 78 79 self._width = 760 if uiscale is bui.UIScale.SMALL else 660 80 x_offs = 50 if uiscale is bui.UIScale.SMALL else 0 81 self._height = ( 82 390 83 if uiscale is bui.UIScale.SMALL 84 else 430 85 if uiscale is bui.UIScale.MEDIUM 86 else 490 87 ) 88 89 self._sign_in_button = None 90 self._sign_in_text = None 91 92 self._scroll_width = self._width - (100 + x_offs * 2) 93 self._scroll_height = self._height - 120 94 self._sub_width = self._scroll_width - 20 95 96 # Determine which sign-in/sign-out buttons we should show. 97 self._show_sign_in_buttons: list[str] = [] 98 99 if LoginType.GPGS in plus.accounts.login_adapters: 100 self._show_sign_in_buttons.append('Google Play') 101 102 # Always want to show our web-based v2 login option. 103 self._show_sign_in_buttons.append('V2Proxy') 104 105 # Legacy v1 device accounts are currently always available 106 # (though we need to start phasing them out at some point). 107 self._show_sign_in_buttons.append('Device') 108 109 top_extra = 15 if uiscale is bui.UIScale.SMALL else 0 110 super().__init__( 111 root_widget=bui.containerwidget( 112 size=(self._width, self._height + top_extra), 113 transition=transition, 114 toolbar_visibility='menu_minimal', 115 scale_origin_stack_offset=scale_origin, 116 scale=( 117 2.09 118 if uiscale is bui.UIScale.SMALL 119 else 1.4 120 if uiscale is bui.UIScale.MEDIUM 121 else 1.0 122 ), 123 stack_offset=(0, -19) 124 if uiscale is bui.UIScale.SMALL 125 else (0, 0), 126 ) 127 ) 128 if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars: 129 self._back_button = None 130 bui.containerwidget( 131 edit=self._root_widget, on_cancel_call=self._back 132 ) 133 else: 134 self._back_button = btn = bui.buttonwidget( 135 parent=self._root_widget, 136 position=(51 + x_offs, self._height - 62), 137 size=(120, 60), 138 scale=0.8, 139 text_scale=1.2, 140 autoselect=True, 141 label=bui.Lstr( 142 resource='doneText' if self._modal else 'backText' 143 ), 144 button_type='regular' if self._modal else 'back', 145 on_activate_call=self._back, 146 ) 147 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 148 if not self._modal: 149 bui.buttonwidget( 150 edit=btn, 151 button_type='backSmall', 152 size=(60, 56), 153 label=bui.charstr(bui.SpecialChar.BACK), 154 ) 155 156 bui.textwidget( 157 parent=self._root_widget, 158 position=(self._width * 0.5, self._height - 41), 159 size=(0, 0), 160 text=bui.Lstr(resource=self._r + '.titleText'), 161 color=app.ui_v1.title_color, 162 maxwidth=self._width - 340, 163 h_align='center', 164 v_align='center', 165 ) 166 167 self._scrollwidget = bui.scrollwidget( 168 parent=self._root_widget, 169 highlight=False, 170 position=( 171 (self._width - self._scroll_width) * 0.5, 172 self._height - 65 - self._scroll_height, 173 ), 174 size=(self._scroll_width, self._scroll_height), 175 claims_left_right=True, 176 claims_tab=True, 177 selection_loops_to_parent=True, 178 ) 179 self._subcontainer: bui.Widget | None = None 180 self._refresh() 181 self._restore_state() 182 183 def _update(self) -> None: 184 plus = bui.app.plus 185 assert plus is not None 186 187 # If they want us to close once we're signed in, do so. 188 if self._close_once_signed_in and self._v1_signed_in: 189 self._back() 190 return 191 192 # Hmm should update this to use get_account_state_num. 193 # Theoretically if we switch from one signed-in account to another 194 # in the background this would break. 195 v1_account_state_num = plus.get_v1_account_state_num() 196 v1_account_state = plus.get_v1_account_state() 197 show_legacy_unlink_button = self._should_show_legacy_unlink_button() 198 199 if ( 200 v1_account_state_num != self._v1_account_state_num 201 or show_legacy_unlink_button != self._show_legacy_unlink_button 202 or self._needs_refresh 203 ): 204 self._v1_account_state_num = v1_account_state_num 205 self._v1_signed_in = v1_account_state == 'signed_in' 206 self._show_legacy_unlink_button = show_legacy_unlink_button 207 self._refresh() 208 209 # Go ahead and refresh some individual things 210 # that may change under us. 211 self._update_linked_accounts_text() 212 self._update_unlink_accounts_button() 213 self._refresh_campaign_progress_text() 214 self._refresh_achievements() 215 self._refresh_tickets_text() 216 self._refresh_account_name_text() 217 218 def _get_sign_in_text(self) -> bui.Lstr: 219 return bui.Lstr(resource=self._r + '.signInText') 220 221 def _refresh(self) -> None: 222 # pylint: disable=too-many-statements 223 # pylint: disable=too-many-branches 224 # pylint: disable=too-many-locals 225 # pylint: disable=cyclic-import 226 from bauiv1lib import confirm 227 228 plus = bui.app.plus 229 assert plus is not None 230 231 primary_v2_account = plus.accounts.primary 232 233 v1_state = plus.get_v1_account_state() 234 v1_account_type = ( 235 plus.get_v1_account_type() if v1_state == 'signed_in' else 'unknown' 236 ) 237 238 # We expose GPGS-specific functionality only if it is 'active' 239 # (meaning the current GPGS player matches one of our account's 240 # logins). 241 gpgs_adapter = plus.accounts.login_adapters.get(LoginType.GPGS) 242 is_gpgs = ( 243 False if gpgs_adapter is None else gpgs_adapter.is_back_end_active() 244 ) 245 246 show_signed_in_as = self._v1_signed_in 247 signed_in_as_space = 95.0 248 249 show_sign_in_benefits = not self._v1_signed_in 250 sign_in_benefits_space = 80.0 251 252 show_signing_in_text = ( 253 v1_state == 'signing_in' or self._signing_in_adapter is not None 254 ) 255 signing_in_text_space = 80.0 256 257 show_google_play_sign_in_button = ( 258 v1_state == 'signed_out' 259 and self._signing_in_adapter is None 260 and 'Google Play' in self._show_sign_in_buttons 261 ) 262 show_v2_proxy_sign_in_button = ( 263 v1_state == 'signed_out' 264 and self._signing_in_adapter is None 265 and 'V2Proxy' in self._show_sign_in_buttons 266 ) 267 show_device_sign_in_button = ( 268 v1_state == 'signed_out' 269 and self._signing_in_adapter is None 270 and 'Device' in self._show_sign_in_buttons 271 ) 272 sign_in_button_space = 70.0 273 deprecated_space = 60 274 275 show_game_service_button = self._v1_signed_in and v1_account_type in [ 276 'Game Center' 277 ] 278 game_service_button_space = 60.0 279 280 show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2' 281 282 show_linked_accounts_text = self._v1_signed_in 283 linked_accounts_text_space = 60.0 284 285 show_achievements_button = self._v1_signed_in and v1_account_type in ( 286 'Google Play', 287 'Local', 288 'V2', 289 ) 290 achievements_button_space = 60.0 291 292 show_achievements_text = ( 293 self._v1_signed_in and not show_achievements_button 294 ) 295 achievements_text_space = 27.0 296 297 show_leaderboards_button = self._v1_signed_in and is_gpgs 298 leaderboards_button_space = 60.0 299 300 show_campaign_progress = self._v1_signed_in 301 campaign_progress_space = 27.0 302 303 show_tickets = self._v1_signed_in 304 tickets_space = 27.0 305 306 show_reset_progress_button = False 307 reset_progress_button_space = 70.0 308 309 show_manage_v2_account_button = ( 310 self._v1_signed_in and v1_account_type == 'V2' 311 ) 312 manage_v2_account_button_space = 100.0 313 314 show_player_profiles_button = self._v1_signed_in 315 player_profiles_button_space = ( 316 70.0 if show_manage_v2_account_button else 100.0 317 ) 318 319 show_link_accounts_button = self._v1_signed_in and ( 320 primary_v2_account is None or FORCE_ENABLE_V1_LINKING 321 ) 322 link_accounts_button_space = 70.0 323 324 show_unlink_accounts_button = show_link_accounts_button 325 unlink_accounts_button_space = 90.0 326 327 show_v2_link_info = self._v1_signed_in and not show_link_accounts_button 328 v2_link_info_space = 70.0 329 330 legacy_unlink_button_space = 120.0 331 332 show_sign_out_button = self._v1_signed_in and v1_account_type in [ 333 'Local', 334 'Google Play', 335 'V2', 336 ] 337 sign_out_button_space = 70.0 338 339 # We can show cancel if we're either waiting on an adapter to 340 # provide us with v2 credentials or waiting for those credentials 341 # to be verified. 342 show_cancel_sign_in_button = self._signing_in_adapter is not None or ( 343 plus.accounts.have_primary_credentials() 344 and primary_v2_account is None 345 ) 346 cancel_sign_in_button_space = 70.0 347 348 if self._subcontainer is not None: 349 self._subcontainer.delete() 350 self._sub_height = 60.0 351 if show_signed_in_as: 352 self._sub_height += signed_in_as_space 353 if show_signing_in_text: 354 self._sub_height += signing_in_text_space 355 if show_google_play_sign_in_button: 356 self._sub_height += sign_in_button_space 357 if show_v2_proxy_sign_in_button: 358 self._sub_height += sign_in_button_space 359 if show_device_sign_in_button: 360 self._sub_height += sign_in_button_space + deprecated_space 361 if show_game_service_button: 362 self._sub_height += game_service_button_space 363 if show_linked_accounts_text: 364 self._sub_height += linked_accounts_text_space 365 if show_achievements_text: 366 self._sub_height += achievements_text_space 367 if show_achievements_button: 368 self._sub_height += achievements_button_space 369 if show_leaderboards_button: 370 self._sub_height += leaderboards_button_space 371 if show_campaign_progress: 372 self._sub_height += campaign_progress_space 373 if show_tickets: 374 self._sub_height += tickets_space 375 if show_sign_in_benefits: 376 self._sub_height += sign_in_benefits_space 377 if show_reset_progress_button: 378 self._sub_height += reset_progress_button_space 379 if show_manage_v2_account_button: 380 self._sub_height += manage_v2_account_button_space 381 if show_player_profiles_button: 382 self._sub_height += player_profiles_button_space 383 if show_link_accounts_button: 384 self._sub_height += link_accounts_button_space 385 if show_unlink_accounts_button: 386 self._sub_height += unlink_accounts_button_space 387 if show_v2_link_info: 388 self._sub_height += v2_link_info_space 389 if self._show_legacy_unlink_button: 390 self._sub_height += legacy_unlink_button_space 391 if show_sign_out_button: 392 self._sub_height += sign_out_button_space 393 if show_cancel_sign_in_button: 394 self._sub_height += cancel_sign_in_button_space 395 self._subcontainer = bui.containerwidget( 396 parent=self._scrollwidget, 397 size=(self._sub_width, self._sub_height), 398 background=False, 399 claims_left_right=True, 400 claims_tab=True, 401 selection_loops_to_parent=True, 402 ) 403 404 first_selectable = None 405 v = self._sub_height - 10.0 406 407 assert bui.app.classic is not None 408 self._account_name_what_is_text: bui.Widget | None 409 self._account_name_what_is_y = 0.0 410 self._account_name_text: bui.Widget | None 411 if show_signed_in_as: 412 v -= signed_in_as_space * 0.2 413 txt = bui.Lstr( 414 resource='accountSettingsWindow.youAreSignedInAsText', 415 fallback_resource='accountSettingsWindow.youAreLoggedInAsText', 416 ) 417 bui.textwidget( 418 parent=self._subcontainer, 419 position=(self._sub_width * 0.5, v), 420 size=(0, 0), 421 text=txt, 422 scale=0.9, 423 color=bui.app.ui_v1.title_color, 424 maxwidth=self._sub_width * 0.9, 425 h_align='center', 426 v_align='center', 427 ) 428 v -= signed_in_as_space * 0.5 429 self._account_name_text = bui.textwidget( 430 parent=self._subcontainer, 431 position=(self._sub_width * 0.5, v), 432 size=(0, 0), 433 scale=1.5, 434 maxwidth=self._sub_width * 0.9, 435 res_scale=1.5, 436 color=(1, 1, 1, 1), 437 h_align='center', 438 v_align='center', 439 ) 440 441 if show_what_is_v2: 442 self._account_name_what_is_y = v - 23.0 443 self._account_name_what_is_text = bui.textwidget( 444 parent=self._subcontainer, 445 position=(0.0, self._account_name_what_is_y), 446 size=(200.0, 60), 447 text=bui.Lstr( 448 value='${WHAT} -->', 449 subs=[('${WHAT}', bui.Lstr(resource='whatIsThisText'))], 450 ), 451 scale=0.6, 452 color=(0.3, 0.7, 0.05), 453 maxwidth=200.0, 454 h_align='right', 455 v_align='center', 456 autoselect=True, 457 selectable=True, 458 on_activate_call=show_what_is_v2_page, 459 click_activate=True, 460 ) 461 if first_selectable is None: 462 first_selectable = self._account_name_what_is_text 463 else: 464 self._account_name_what_is_text = None 465 466 self._refresh_account_name_text() 467 468 v -= signed_in_as_space * 0.4 469 470 else: 471 self._account_name_text = None 472 self._account_name_what_is_text = None 473 474 if self._back_button is None: 475 bbtn = bui.get_special_widget('back_button') 476 else: 477 bbtn = self._back_button 478 479 if show_sign_in_benefits: 480 v -= sign_in_benefits_space 481 app = bui.app 482 assert app.classic is not None 483 extra: str | bui.Lstr | None 484 if ( 485 app.classic.platform in ['mac', 'ios'] 486 and app.classic.subplatform == 'appstore' 487 ): 488 extra = bui.Lstr( 489 value='\n${S}', 490 subs=[ 491 ('${S}', bui.Lstr(resource='signInWithGameCenterText')) 492 ], 493 ) 494 else: 495 extra = '' 496 497 bui.textwidget( 498 parent=self._subcontainer, 499 position=( 500 self._sub_width * 0.5, 501 v + sign_in_benefits_space * 0.4, 502 ), 503 size=(0, 0), 504 text=bui.Lstr( 505 value='${A}${B}', 506 subs=[ 507 ( 508 '${A}', 509 bui.Lstr(resource=self._r + '.signInInfoText'), 510 ), 511 ('${B}', extra), 512 ], 513 ), 514 max_height=sign_in_benefits_space * 0.9, 515 scale=0.9, 516 color=(0.75, 0.7, 0.8), 517 maxwidth=self._sub_width * 0.8, 518 h_align='center', 519 v_align='center', 520 ) 521 522 if show_signing_in_text: 523 v -= signing_in_text_space 524 525 bui.textwidget( 526 parent=self._subcontainer, 527 position=( 528 self._sub_width * 0.5, 529 v + signing_in_text_space * 0.5, 530 ), 531 size=(0, 0), 532 text=bui.Lstr(resource='accountSettingsWindow.signingInText'), 533 scale=0.9, 534 color=(0, 1, 0), 535 maxwidth=self._sub_width * 0.8, 536 h_align='center', 537 v_align='center', 538 ) 539 540 if show_google_play_sign_in_button: 541 button_width = 350 542 v -= sign_in_button_space 543 self._sign_in_google_play_button = btn = bui.buttonwidget( 544 parent=self._subcontainer, 545 position=((self._sub_width - button_width) * 0.5, v - 20), 546 autoselect=True, 547 size=(button_width, 60), 548 label=bui.Lstr( 549 value='${A}${B}', 550 subs=[ 551 ( 552 '${A}', 553 bui.charstr(bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO), 554 ), 555 ( 556 '${B}', 557 bui.Lstr( 558 resource=self._r + '.signInWithGooglePlayText' 559 ), 560 ), 561 ], 562 ), 563 on_activate_call=lambda: self._sign_in_press(LoginType.GPGS), 564 ) 565 if first_selectable is None: 566 first_selectable = btn 567 if bui.app.ui_v1.use_toolbars: 568 bui.widget( 569 edit=btn, 570 right_widget=bui.get_special_widget('party_button'), 571 ) 572 bui.widget(edit=btn, left_widget=bbtn) 573 bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 574 self._sign_in_text = None 575 576 if show_v2_proxy_sign_in_button: 577 button_width = 350 578 v -= sign_in_button_space 579 self._sign_in_v2_proxy_button = btn = bui.buttonwidget( 580 parent=self._subcontainer, 581 position=((self._sub_width - button_width) * 0.5, v - 20), 582 autoselect=True, 583 size=(button_width, 60), 584 label='', 585 on_activate_call=self._v2_proxy_sign_in_press, 586 ) 587 bui.textwidget( 588 parent=self._subcontainer, 589 draw_controller=btn, 590 h_align='center', 591 v_align='center', 592 size=(0, 0), 593 position=(self._sub_width * 0.5, v + 17), 594 text=bui.Lstr( 595 value='${A}${B}', 596 subs=[ 597 ('${A}', bui.charstr(bui.SpecialChar.V2_LOGO)), 598 ( 599 '${B}', 600 bui.Lstr(resource=self._r + '.signInWithV2Text'), 601 ), 602 ], 603 ), 604 maxwidth=button_width * 0.8, 605 color=(0.75, 1.0, 0.7), 606 ) 607 bui.textwidget( 608 parent=self._subcontainer, 609 draw_controller=btn, 610 h_align='center', 611 v_align='center', 612 size=(0, 0), 613 position=(self._sub_width * 0.5, v - 4), 614 text=bui.Lstr(resource=self._r + '.signInWithV2InfoText'), 615 flatness=1.0, 616 scale=0.57, 617 maxwidth=button_width * 0.9, 618 color=(0.55, 0.8, 0.5), 619 ) 620 if first_selectable is None: 621 first_selectable = btn 622 if bui.app.ui_v1.use_toolbars: 623 bui.widget( 624 edit=btn, 625 right_widget=bui.get_special_widget('party_button'), 626 ) 627 bui.widget(edit=btn, left_widget=bbtn) 628 bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 629 self._sign_in_text = None 630 631 if show_device_sign_in_button: 632 button_width = 350 633 v -= sign_in_button_space + deprecated_space 634 self._sign_in_device_button = btn = bui.buttonwidget( 635 parent=self._subcontainer, 636 position=((self._sub_width - button_width) * 0.5, v - 20), 637 autoselect=True, 638 size=(button_width, 60), 639 label='', 640 on_activate_call=lambda: self._sign_in_press('Local'), 641 ) 642 bui.textwidget( 643 parent=self._subcontainer, 644 h_align='center', 645 v_align='center', 646 size=(0, 0), 647 position=(self._sub_width * 0.5, v + 60), 648 text=bui.Lstr(resource='deprecatedText'), 649 scale=0.8, 650 maxwidth=300, 651 color=(0.6, 0.55, 0.45), 652 ) 653 654 bui.textwidget( 655 parent=self._subcontainer, 656 draw_controller=btn, 657 h_align='center', 658 v_align='center', 659 size=(0, 0), 660 position=(self._sub_width * 0.5, v + 17), 661 text=bui.Lstr( 662 value='${A}${B}', 663 subs=[ 664 ('${A}', bui.charstr(bui.SpecialChar.LOCAL_ACCOUNT)), 665 ( 666 '${B}', 667 bui.Lstr( 668 resource=self._r + '.signInWithDeviceText' 669 ), 670 ), 671 ], 672 ), 673 maxwidth=button_width * 0.8, 674 color=(0.75, 1.0, 0.7), 675 ) 676 bui.textwidget( 677 parent=self._subcontainer, 678 draw_controller=btn, 679 h_align='center', 680 v_align='center', 681 size=(0, 0), 682 position=(self._sub_width * 0.5, v - 4), 683 text=bui.Lstr(resource=self._r + '.signInWithDeviceInfoText'), 684 flatness=1.0, 685 scale=0.57, 686 maxwidth=button_width * 0.9, 687 color=(0.55, 0.8, 0.5), 688 ) 689 if first_selectable is None: 690 first_selectable = btn 691 if bui.app.ui_v1.use_toolbars: 692 bui.widget( 693 edit=btn, 694 right_widget=bui.get_special_widget('party_button'), 695 ) 696 bui.widget(edit=btn, left_widget=bbtn) 697 bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 698 self._sign_in_text = None 699 700 if show_manage_v2_account_button: 701 button_width = 300 702 v -= manage_v2_account_button_space 703 self._manage_v2_button = btn = bui.buttonwidget( 704 parent=self._subcontainer, 705 position=((self._sub_width - button_width) * 0.5, v + 30), 706 autoselect=True, 707 size=(button_width, 60), 708 label=bui.Lstr(resource=self._r + '.manageAccountText'), 709 color=(0.55, 0.5, 0.6), 710 icon=bui.gettexture('settingsIcon'), 711 textcolor=(0.75, 0.7, 0.8), 712 on_activate_call=bui.WeakCall(self._on_manage_account_press), 713 ) 714 if first_selectable is None: 715 first_selectable = btn 716 if bui.app.ui_v1.use_toolbars: 717 bui.widget( 718 edit=btn, 719 right_widget=bui.get_special_widget('party_button'), 720 ) 721 bui.widget(edit=btn, left_widget=bbtn) 722 723 if show_player_profiles_button: 724 button_width = 300 725 v -= player_profiles_button_space 726 self._player_profiles_button = btn = bui.buttonwidget( 727 parent=self._subcontainer, 728 position=((self._sub_width - button_width) * 0.5, v + 30), 729 autoselect=True, 730 size=(button_width, 60), 731 label=bui.Lstr(resource='playerProfilesWindow.titleText'), 732 color=(0.55, 0.5, 0.6), 733 icon=bui.gettexture('cuteSpaz'), 734 textcolor=(0.75, 0.7, 0.8), 735 on_activate_call=self._player_profiles_press, 736 ) 737 if first_selectable is None: 738 first_selectable = btn 739 if bui.app.ui_v1.use_toolbars: 740 bui.widget( 741 edit=btn, 742 right_widget=bui.get_special_widget('party_button'), 743 ) 744 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0) 745 746 # the button to go to OS-Specific leaderboards/high-score-lists/etc. 747 if show_game_service_button: 748 button_width = 300 749 v -= game_service_button_space * 0.85 750 v1_account_type = plus.get_v1_account_type() 751 if v1_account_type == 'Game Center': 752 v1_account_type_name = bui.Lstr(resource='gameCenterText') 753 else: 754 raise ValueError( 755 "unknown account type: '" + str(v1_account_type) + "'" 756 ) 757 self._game_service_button = btn = bui.buttonwidget( 758 parent=self._subcontainer, 759 position=((self._sub_width - button_width) * 0.5, v), 760 color=(0.55, 0.5, 0.6), 761 textcolor=(0.75, 0.7, 0.8), 762 autoselect=True, 763 on_activate_call=self._on_game_service_button_press, 764 size=(button_width, 50), 765 label=v1_account_type_name, 766 ) 767 if first_selectable is None: 768 first_selectable = btn 769 if bui.app.ui_v1.use_toolbars: 770 bui.widget( 771 edit=btn, 772 right_widget=bui.get_special_widget('party_button'), 773 ) 774 bui.widget(edit=btn, left_widget=bbtn) 775 v -= game_service_button_space * 0.15 776 else: 777 self.game_service_button = None 778 779 self._achievements_text: bui.Widget | None 780 if show_achievements_text: 781 v -= achievements_text_space * 0.5 782 self._achievements_text = bui.textwidget( 783 parent=self._subcontainer, 784 position=(self._sub_width * 0.5, v), 785 size=(0, 0), 786 scale=0.9, 787 color=(0.75, 0.7, 0.8), 788 maxwidth=self._sub_width * 0.8, 789 h_align='center', 790 v_align='center', 791 ) 792 v -= achievements_text_space * 0.5 793 else: 794 self._achievements_text = None 795 796 self._achievements_button: bui.Widget | None 797 if show_achievements_button: 798 button_width = 300 799 v -= achievements_button_space * 0.85 800 self._achievements_button = btn = bui.buttonwidget( 801 parent=self._subcontainer, 802 position=((self._sub_width - button_width) * 0.5, v), 803 color=(0.55, 0.5, 0.6), 804 textcolor=(0.75, 0.7, 0.8), 805 autoselect=True, 806 icon=bui.gettexture( 807 'googlePlayAchievementsIcon' 808 if is_gpgs 809 else 'achievementsIcon' 810 ), 811 icon_color=(0.8, 0.95, 0.7) if is_gpgs else (0.85, 0.8, 0.9), 812 on_activate_call=( 813 self._on_custom_achievements_press 814 if is_gpgs 815 else self._on_achievements_press 816 ), 817 size=(button_width, 50), 818 label='', 819 ) 820 if first_selectable is None: 821 first_selectable = btn 822 if bui.app.ui_v1.use_toolbars: 823 bui.widget( 824 edit=btn, 825 right_widget=bui.get_special_widget('party_button'), 826 ) 827 bui.widget(edit=btn, left_widget=bbtn) 828 v -= achievements_button_space * 0.15 829 else: 830 self._achievements_button = None 831 832 if show_achievements_text or show_achievements_button: 833 self._refresh_achievements() 834 835 self._leaderboards_button: bui.Widget | None 836 if show_leaderboards_button: 837 button_width = 300 838 v -= leaderboards_button_space * 0.85 839 self._leaderboards_button = btn = bui.buttonwidget( 840 parent=self._subcontainer, 841 position=((self._sub_width - button_width) * 0.5, v), 842 color=(0.55, 0.5, 0.6), 843 textcolor=(0.75, 0.7, 0.8), 844 autoselect=True, 845 icon=bui.gettexture('googlePlayLeaderboardsIcon'), 846 icon_color=(0.8, 0.95, 0.7), 847 on_activate_call=self._on_leaderboards_press, 848 size=(button_width, 50), 849 label=bui.Lstr(resource='leaderboardsText'), 850 ) 851 if first_selectable is None: 852 first_selectable = btn 853 if bui.app.ui_v1.use_toolbars: 854 bui.widget( 855 edit=btn, 856 right_widget=bui.get_special_widget('party_button'), 857 ) 858 bui.widget(edit=btn, left_widget=bbtn) 859 v -= leaderboards_button_space * 0.15 860 else: 861 self._leaderboards_button = None 862 863 self._campaign_progress_text: bui.Widget | None 864 if show_campaign_progress: 865 v -= campaign_progress_space * 0.5 866 self._campaign_progress_text = bui.textwidget( 867 parent=self._subcontainer, 868 position=(self._sub_width * 0.5, v), 869 size=(0, 0), 870 scale=0.9, 871 color=(0.75, 0.7, 0.8), 872 maxwidth=self._sub_width * 0.8, 873 h_align='center', 874 v_align='center', 875 ) 876 v -= campaign_progress_space * 0.5 877 self._refresh_campaign_progress_text() 878 else: 879 self._campaign_progress_text = None 880 881 self._tickets_text: bui.Widget | None 882 if show_tickets: 883 v -= tickets_space * 0.5 884 self._tickets_text = bui.textwidget( 885 parent=self._subcontainer, 886 position=(self._sub_width * 0.5, v), 887 size=(0, 0), 888 scale=0.9, 889 color=(0.75, 0.7, 0.8), 890 maxwidth=self._sub_width * 0.8, 891 flatness=1.0, 892 h_align='center', 893 v_align='center', 894 ) 895 v -= tickets_space * 0.5 896 self._refresh_tickets_text() 897 898 else: 899 self._tickets_text = None 900 901 # bit of spacing before the reset/sign-out section 902 v -= 5 903 904 button_width = 250 905 if show_reset_progress_button: 906 confirm_text = ( 907 bui.Lstr(resource=self._r + '.resetProgressConfirmText') 908 if self._can_reset_achievements 909 else bui.Lstr( 910 resource=self._r + '.resetProgressConfirmNoAchievementsText' 911 ) 912 ) 913 v -= reset_progress_button_space 914 self._reset_progress_button = btn = bui.buttonwidget( 915 parent=self._subcontainer, 916 position=((self._sub_width - button_width) * 0.5, v), 917 color=(0.55, 0.5, 0.6), 918 textcolor=(0.75, 0.7, 0.8), 919 autoselect=True, 920 size=(button_width, 60), 921 label=bui.Lstr(resource=self._r + '.resetProgressText'), 922 on_activate_call=lambda: confirm.ConfirmWindow( 923 text=confirm_text, 924 width=500, 925 height=200, 926 action=self._reset_progress, 927 ), 928 ) 929 if first_selectable is None: 930 first_selectable = btn 931 if bui.app.ui_v1.use_toolbars: 932 bui.widget( 933 edit=btn, 934 right_widget=bui.get_special_widget('party_button'), 935 ) 936 bui.widget(edit=btn, left_widget=bbtn) 937 938 self._linked_accounts_text: bui.Widget | None 939 if show_linked_accounts_text: 940 v -= linked_accounts_text_space * 0.8 941 self._linked_accounts_text = bui.textwidget( 942 parent=self._subcontainer, 943 position=(self._sub_width * 0.5, v), 944 size=(0, 0), 945 scale=0.9, 946 color=(0.75, 0.7, 0.8), 947 maxwidth=self._sub_width * 0.95, 948 text=bui.Lstr(resource=self._r + '.linkedAccountsText'), 949 h_align='center', 950 v_align='center', 951 ) 952 v -= linked_accounts_text_space * 0.2 953 self._update_linked_accounts_text() 954 else: 955 self._linked_accounts_text = None 956 957 # Show link/unlink buttons only for V1 accounts. 958 959 if show_link_accounts_button: 960 v -= link_accounts_button_space 961 self._link_accounts_button = btn = bui.buttonwidget( 962 parent=self._subcontainer, 963 position=((self._sub_width - button_width) * 0.5, v), 964 autoselect=True, 965 size=(button_width, 60), 966 label='', 967 color=(0.55, 0.5, 0.6), 968 on_activate_call=self._link_accounts_press, 969 ) 970 bui.textwidget( 971 parent=self._subcontainer, 972 draw_controller=btn, 973 h_align='center', 974 v_align='center', 975 size=(0, 0), 976 position=(self._sub_width * 0.5, v + 17 + 20), 977 text=bui.Lstr(resource=self._r + '.linkAccountsText'), 978 maxwidth=button_width * 0.8, 979 color=(0.75, 0.7, 0.8), 980 ) 981 bui.textwidget( 982 parent=self._subcontainer, 983 draw_controller=btn, 984 h_align='center', 985 v_align='center', 986 size=(0, 0), 987 position=(self._sub_width * 0.5, v - 4 + 20), 988 text=bui.Lstr(resource=self._r + '.linkAccountsInfoText'), 989 flatness=1.0, 990 scale=0.5, 991 maxwidth=button_width * 0.8, 992 color=(0.75, 0.7, 0.8), 993 ) 994 if first_selectable is None: 995 first_selectable = btn 996 if bui.app.ui_v1.use_toolbars: 997 bui.widget( 998 edit=btn, 999 right_widget=bui.get_special_widget('party_button'), 1000 ) 1001 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 1002 1003 self._unlink_accounts_button: bui.Widget | None 1004 if show_unlink_accounts_button: 1005 v -= unlink_accounts_button_space 1006 self._unlink_accounts_button = btn = bui.buttonwidget( 1007 parent=self._subcontainer, 1008 position=((self._sub_width - button_width) * 0.5, v + 25), 1009 autoselect=True, 1010 size=(button_width, 60), 1011 label='', 1012 color=(0.55, 0.5, 0.6), 1013 on_activate_call=self._unlink_accounts_press, 1014 ) 1015 self._unlink_accounts_button_label = bui.textwidget( 1016 parent=self._subcontainer, 1017 draw_controller=btn, 1018 h_align='center', 1019 v_align='center', 1020 size=(0, 0), 1021 position=(self._sub_width * 0.5, v + 55), 1022 text=bui.Lstr(resource=self._r + '.unlinkAccountsText'), 1023 maxwidth=button_width * 0.8, 1024 color=(0.75, 0.7, 0.8), 1025 ) 1026 if first_selectable is None: 1027 first_selectable = btn 1028 if bui.app.ui_v1.use_toolbars: 1029 bui.widget( 1030 edit=btn, 1031 right_widget=bui.get_special_widget('party_button'), 1032 ) 1033 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 1034 self._update_unlink_accounts_button() 1035 else: 1036 self._unlink_accounts_button = None 1037 1038 if show_v2_link_info: 1039 v -= v2_link_info_space 1040 bui.textwidget( 1041 parent=self._subcontainer, 1042 h_align='center', 1043 v_align='center', 1044 size=(0, 0), 1045 position=(self._sub_width * 0.5, v + v2_link_info_space - 20), 1046 text=bui.Lstr(resource='v2AccountLinkingInfoText'), 1047 flatness=1.0, 1048 scale=0.8, 1049 maxwidth=450, 1050 color=(0.5, 0.45, 0.55), 1051 ) 1052 1053 if self._show_legacy_unlink_button: 1054 v -= legacy_unlink_button_space 1055 button_width_w = button_width * 1.5 1056 bui.textwidget( 1057 parent=self._subcontainer, 1058 position=(self._sub_width * 0.5 - 150.0, v + 75), 1059 size=(300.0, 60), 1060 text=bui.Lstr(resource='whatIsThisText'), 1061 scale=0.8, 1062 color=(0.3, 0.7, 0.05), 1063 maxwidth=200.0, 1064 h_align='center', 1065 v_align='center', 1066 autoselect=True, 1067 selectable=True, 1068 on_activate_call=show_what_is_legacy_unlinking_page, 1069 click_activate=True, 1070 ) 1071 btn = bui.buttonwidget( 1072 parent=self._subcontainer, 1073 position=((self._sub_width - button_width_w) * 0.5, v + 25), 1074 autoselect=True, 1075 size=(button_width_w, 60), 1076 label=bui.Lstr( 1077 resource=self._r + '.unlinkLegacyV1AccountsText' 1078 ), 1079 textcolor=(0.8, 0.4, 0), 1080 color=(0.55, 0.5, 0.6), 1081 on_activate_call=self._unlink_accounts_press, 1082 ) 1083 1084 if show_sign_out_button: 1085 v -= sign_out_button_space 1086 self._sign_out_button = btn = bui.buttonwidget( 1087 parent=self._subcontainer, 1088 position=((self._sub_width - button_width) * 0.5, v), 1089 size=(button_width, 60), 1090 label=bui.Lstr(resource=self._r + '.signOutText'), 1091 color=(0.55, 0.5, 0.6), 1092 textcolor=(0.75, 0.7, 0.8), 1093 autoselect=True, 1094 on_activate_call=self._sign_out_press, 1095 ) 1096 if first_selectable is None: 1097 first_selectable = btn 1098 if bui.app.ui_v1.use_toolbars: 1099 bui.widget( 1100 edit=btn, 1101 right_widget=bui.get_special_widget('party_button'), 1102 ) 1103 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 1104 1105 if show_cancel_sign_in_button: 1106 v -= cancel_sign_in_button_space 1107 self._cancel_sign_in_button = btn = bui.buttonwidget( 1108 parent=self._subcontainer, 1109 position=((self._sub_width - button_width) * 0.5, v), 1110 size=(button_width, 60), 1111 label=bui.Lstr(resource='cancelText'), 1112 color=(0.55, 0.5, 0.6), 1113 textcolor=(0.75, 0.7, 0.8), 1114 autoselect=True, 1115 on_activate_call=self._cancel_sign_in_press, 1116 ) 1117 if first_selectable is None: 1118 first_selectable = btn 1119 if bui.app.ui_v1.use_toolbars: 1120 bui.widget( 1121 edit=btn, 1122 right_widget=bui.get_special_widget('party_button'), 1123 ) 1124 bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 1125 1126 # Whatever the topmost selectable thing is, we want it to scroll all 1127 # the way up when we select it. 1128 if first_selectable is not None: 1129 bui.widget( 1130 edit=first_selectable, up_widget=bbtn, show_buffer_top=400 1131 ) 1132 # (this should re-scroll us to the top..) 1133 bui.containerwidget( 1134 edit=self._subcontainer, visible_child=first_selectable 1135 ) 1136 self._needs_refresh = False 1137 1138 def _on_game_service_button_press(self) -> None: 1139 if bui.app.classic is not None: 1140 bui.app.classic.show_online_score_ui() 1141 else: 1142 logging.warning('game service ui not available without classic.') 1143 1144 def _on_custom_achievements_press(self) -> None: 1145 if bui.app.classic is not None: 1146 bui.apptimer( 1147 0.15, 1148 bui.Call(bui.app.classic.show_online_score_ui, 'achievements'), 1149 ) 1150 else: 1151 logging.warning('show_online_score_ui requires classic') 1152 1153 def _on_achievements_press(self) -> None: 1154 # pylint: disable=cyclic-import 1155 from bauiv1lib import achievements 1156 1157 assert self._achievements_button is not None 1158 achievements.AchievementsWindow( 1159 position=self._achievements_button.get_screen_space_center() 1160 ) 1161 1162 def _on_what_is_v2_press(self) -> None: 1163 show_what_is_v2_page() 1164 1165 def _on_manage_account_press(self) -> None: 1166 bui.screenmessage(bui.Lstr(resource='oneMomentText')) 1167 1168 plus = bui.app.plus 1169 assert plus is not None 1170 1171 # We expect to have a v2 account signed in if we get here. 1172 if plus.accounts.primary is None: 1173 logging.exception( 1174 'got manage-account press without v2 account present' 1175 ) 1176 return 1177 1178 with plus.accounts.primary: 1179 plus.cloud.send_message_cb( 1180 bacommon.cloud.ManageAccountMessage(), 1181 on_response=bui.WeakCall(self._on_manage_account_response), 1182 ) 1183 1184 def _on_manage_account_response( 1185 self, response: bacommon.cloud.ManageAccountResponse | Exception 1186 ) -> None: 1187 if isinstance(response, Exception) or response.url is None: 1188 bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) 1189 bui.getsound('error').play() 1190 return 1191 1192 bui.open_url(response.url) 1193 1194 def _on_leaderboards_press(self) -> None: 1195 if bui.app.classic is not None: 1196 bui.apptimer( 1197 0.15, 1198 bui.Call(bui.app.classic.show_online_score_ui, 'leaderboards'), 1199 ) 1200 else: 1201 logging.warning('show_online_score_ui requires classic') 1202 1203 def _have_unlinkable_v1_accounts(self) -> bool: 1204 plus = bui.app.plus 1205 assert plus is not None 1206 1207 # if this is not present, we haven't had contact from the server so 1208 # let's not proceed.. 1209 if plus.get_v1_account_public_login_id() is None: 1210 return False 1211 accounts = plus.get_v1_account_misc_read_val_2('linkedAccounts', []) 1212 return len(accounts) > 1 1213 1214 def _update_unlink_accounts_button(self) -> None: 1215 if self._unlink_accounts_button is None: 1216 return 1217 if self._have_unlinkable_v1_accounts(): 1218 clr = (0.75, 0.7, 0.8, 1.0) 1219 else: 1220 clr = (1.0, 1.0, 1.0, 0.25) 1221 bui.textwidget(edit=self._unlink_accounts_button_label, color=clr) 1222 1223 def _should_show_legacy_unlink_button(self) -> bool: 1224 plus = bui.app.plus 1225 assert plus is not None 1226 1227 # Only show this when fully signed in to a v2 account. 1228 if not self._v1_signed_in or plus.accounts.primary is None: 1229 return False 1230 1231 out = self._have_unlinkable_v1_accounts() 1232 return out 1233 1234 def _update_linked_accounts_text(self) -> None: 1235 plus = bui.app.plus 1236 assert plus is not None 1237 1238 if self._linked_accounts_text is None: 1239 return 1240 1241 # Disable this by default when signed in to a V2 account 1242 # (since this shows V1 links which we should no longer care about). 1243 if plus.accounts.primary is not None and not FORCE_ENABLE_V1_LINKING: 1244 return 1245 1246 # if this is not present, we haven't had contact from the server so 1247 # let's not proceed.. 1248 if plus.get_v1_account_public_login_id() is None: 1249 num = int(time.time()) % 4 1250 accounts_str = num * '.' + (4 - num) * ' ' 1251 else: 1252 accounts = plus.get_v1_account_misc_read_val_2('linkedAccounts', []) 1253 # UPDATE - we now just print the number here; not the actual 1254 # accounts (they can see that in the unlink section if they're 1255 # curious) 1256 accounts_str = str(max(0, len(accounts) - 1)) 1257 bui.textwidget( 1258 edit=self._linked_accounts_text, 1259 text=bui.Lstr( 1260 value='${L} ${A}', 1261 subs=[ 1262 ( 1263 '${L}', 1264 bui.Lstr(resource=self._r + '.linkedAccountsText'), 1265 ), 1266 ('${A}', accounts_str), 1267 ], 1268 ), 1269 ) 1270 1271 def _refresh_campaign_progress_text(self) -> None: 1272 if self._campaign_progress_text is None: 1273 return 1274 p_str: str | bui.Lstr 1275 try: 1276 assert bui.app.classic is not None 1277 campaign = bui.app.classic.getcampaign('Default') 1278 levels = campaign.levels 1279 levels_complete = sum((1 if l.complete else 0) for l in levels) 1280 1281 # Last level cant be completed; hence the -1; 1282 progress = min(1.0, float(levels_complete) / (len(levels) - 1)) 1283 p_str = bui.Lstr( 1284 resource=self._r + '.campaignProgressText', 1285 subs=[('${PROGRESS}', str(int(progress * 100.0)) + '%')], 1286 ) 1287 except Exception: 1288 p_str = '?' 1289 logging.exception('Error calculating co-op campaign progress.') 1290 bui.textwidget(edit=self._campaign_progress_text, text=p_str) 1291 1292 def _refresh_tickets_text(self) -> None: 1293 plus = bui.app.plus 1294 assert plus is not None 1295 1296 if self._tickets_text is None: 1297 return 1298 try: 1299 tc_str = str(plus.get_v1_account_ticket_count()) 1300 except Exception: 1301 logging.exception('error refreshing tickets text') 1302 tc_str = '-' 1303 bui.textwidget( 1304 edit=self._tickets_text, 1305 text=bui.Lstr( 1306 resource=self._r + '.ticketsText', subs=[('${COUNT}', tc_str)] 1307 ), 1308 ) 1309 1310 def _refresh_account_name_text(self) -> None: 1311 plus = bui.app.plus 1312 assert plus is not None 1313 1314 if self._account_name_text is None: 1315 return 1316 try: 1317 name_str = plus.get_v1_account_display_string() 1318 except Exception: 1319 logging.exception('error refreshing tickets text') 1320 name_str = '??' 1321 1322 bui.textwidget(edit=self._account_name_text, text=name_str) 1323 if self._account_name_what_is_text is not None: 1324 swidth = bui.get_string_width(name_str, suppress_warning=True) 1325 # Eww; number-fudging. Need to recalibrate this if 1326 # account name scaling changes. 1327 x = self._sub_width * 0.5 - swidth * 0.75 - 170 1328 1329 bui.textwidget( 1330 edit=self._account_name_what_is_text, 1331 position=(x, self._account_name_what_is_y), 1332 ) 1333 1334 def _refresh_achievements(self) -> None: 1335 assert bui.app.classic is not None 1336 if ( 1337 self._achievements_text is None 1338 and self._achievements_button is None 1339 ): 1340 return 1341 complete = sum( 1342 1 if a.complete else 0 for a in bui.app.classic.ach.achievements 1343 ) 1344 total = len(bui.app.classic.ach.achievements) 1345 txt_final = bui.Lstr( 1346 resource=self._r + '.achievementProgressText', 1347 subs=[('${COUNT}', str(complete)), ('${TOTAL}', str(total))], 1348 ) 1349 1350 if self._achievements_text is not None: 1351 bui.textwidget(edit=self._achievements_text, text=txt_final) 1352 if self._achievements_button is not None: 1353 bui.buttonwidget(edit=self._achievements_button, label=txt_final) 1354 1355 def _link_accounts_press(self) -> None: 1356 # pylint: disable=cyclic-import 1357 from bauiv1lib.account.link import AccountLinkWindow 1358 1359 AccountLinkWindow(origin_widget=self._link_accounts_button) 1360 1361 def _unlink_accounts_press(self) -> None: 1362 # pylint: disable=cyclic-import 1363 from bauiv1lib.account.unlink import AccountUnlinkWindow 1364 1365 if not self._have_unlinkable_v1_accounts(): 1366 bui.getsound('error').play() 1367 return 1368 1369 AccountUnlinkWindow(origin_widget=self._unlink_accounts_button) 1370 1371 def _player_profiles_press(self) -> None: 1372 # pylint: disable=cyclic-import 1373 from bauiv1lib.profile.browser import ProfileBrowserWindow 1374 1375 self._save_state() 1376 bui.containerwidget(edit=self._root_widget, transition='out_left') 1377 ProfileBrowserWindow(origin_widget=self._player_profiles_button) 1378 1379 def _cancel_sign_in_press(self) -> None: 1380 # If we're waiting on an adapter to give us credentials, abort. 1381 self._signing_in_adapter = None 1382 1383 plus = bui.app.plus 1384 assert plus is not None 1385 1386 # Say we don't wanna be signed in anymore if we are. 1387 plus.accounts.set_primary_credentials(None) 1388 1389 self._needs_refresh = True 1390 1391 # Speed UI updates along. 1392 bui.apptimer(0.1, bui.WeakCall(self._update)) 1393 1394 def _sign_out_press(self) -> None: 1395 plus = bui.app.plus 1396 assert plus is not None 1397 1398 if plus.accounts.have_primary_credentials(): 1399 if ( 1400 plus.accounts.primary is not None 1401 and LoginType.GPGS in plus.accounts.primary.logins 1402 ): 1403 self._explicitly_signed_out_of_gpgs = True 1404 plus.accounts.set_primary_credentials(None) 1405 else: 1406 plus.sign_out_v1() 1407 1408 cfg = bui.app.config 1409 1410 # Also take note that its our *explicit* intention to not be 1411 # signed in at this point (affects v1 accounts). 1412 cfg['Auto Account State'] = 'signed_out' 1413 cfg.commit() 1414 bui.buttonwidget( 1415 edit=self._sign_out_button, 1416 label=bui.Lstr(resource=self._r + '.signingOutText'), 1417 ) 1418 1419 # Speed UI updates along. 1420 bui.apptimer(0.1, bui.WeakCall(self._update)) 1421 1422 def _sign_in_press(self, login_type: str | LoginType) -> None: 1423 plus = bui.app.plus 1424 assert plus is not None 1425 1426 # V1 login types are strings. 1427 if isinstance(login_type, str): 1428 plus.sign_in_v1(login_type) 1429 1430 # Make note of the type account we're *wanting* 1431 # to be signed in with. 1432 cfg = bui.app.config 1433 cfg['Auto Account State'] = login_type 1434 cfg.commit() 1435 self._needs_refresh = True 1436 bui.apptimer(0.1, bui.WeakCall(self._update)) 1437 return 1438 1439 # V2 login sign-in buttons generally go through adapters. 1440 adapter = plus.accounts.login_adapters.get(login_type) 1441 if adapter is not None: 1442 self._signing_in_adapter = adapter 1443 adapter.sign_in( 1444 result_cb=bui.WeakCall(self._on_adapter_sign_in_result), 1445 description='account settings button', 1446 ) 1447 # Will get 'Signing in...' to show. 1448 self._needs_refresh = True 1449 bui.apptimer(0.1, bui.WeakCall(self._update)) 1450 else: 1451 bui.screenmessage(f'Unsupported login_type: {login_type.name}') 1452 1453 def _on_adapter_sign_in_result( 1454 self, 1455 adapter: bui.LoginAdapter, 1456 result: bui.LoginAdapter.SignInResult | Exception, 1457 ) -> None: 1458 is_us = self._signing_in_adapter is adapter 1459 1460 # If this isn't our current one we don't care. 1461 if not is_us: 1462 return 1463 1464 # If it is us, note that we're done. 1465 self._signing_in_adapter = None 1466 1467 if isinstance(result, Exception): 1468 # For now just make a bit of noise if anything went wrong; 1469 # can get more specific as needed later. 1470 bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) 1471 bui.getsound('error').play() 1472 else: 1473 # Success! Plug in these credentials which will begin 1474 # verifying them and set our primary account-handle 1475 # when finished. 1476 plus = bui.app.plus 1477 assert plus is not None 1478 plus.accounts.set_primary_credentials(result.credentials) 1479 1480 # Special case - if the user has explicitly logged out and 1481 # logged in again with GPGS via this button, warn them that 1482 # they need to use the app if they want to switch to a 1483 # different GPGS account. 1484 if ( 1485 self._explicitly_signed_out_of_gpgs 1486 and adapter.login_type is LoginType.GPGS 1487 ): 1488 # Delay this slightly so it hopefully pops up after 1489 # credentials go through and the account name shows up. 1490 bui.apptimer( 1491 1.5, 1492 bui.Call( 1493 bui.screenmessage, 1494 bui.Lstr( 1495 resource=self._r 1496 + '.googlePlayGamesAccountSwitchText' 1497 ), 1498 ), 1499 ) 1500 1501 # Speed any UI updates along. 1502 self._needs_refresh = True 1503 bui.apptimer(0.1, bui.WeakCall(self._update)) 1504 1505 def _v2_proxy_sign_in_press(self) -> None: 1506 # pylint: disable=cyclic-import 1507 from bauiv1lib.account.v2proxy import V2ProxySignInWindow 1508 1509 assert self._sign_in_v2_proxy_button is not None 1510 V2ProxySignInWindow(origin_widget=self._sign_in_v2_proxy_button) 1511 1512 def _reset_progress(self) -> None: 1513 try: 1514 assert bui.app.classic is not None 1515 # FIXME: This would need to happen server-side these days. 1516 if self._can_reset_achievements: 1517 logging.warning('ach resets not wired up.') 1518 # bui.app.config['Achievements'] = {} 1519 # bui.reset_achievements() 1520 campaign = bui.app.classic.getcampaign('Default') 1521 campaign.reset() # also writes the config.. 1522 campaign = bui.app.classic.getcampaign('Challenges') 1523 campaign.reset() # also writes the config.. 1524 except Exception: 1525 logging.exception('Error resetting co-op campaign progress.') 1526 1527 bui.getsound('shieldDown').play() 1528 self._refresh() 1529 1530 def _back(self) -> None: 1531 # pylint: disable=cyclic-import 1532 from bauiv1lib.mainmenu import MainMenuWindow 1533 1534 self._save_state() 1535 bui.containerwidget( 1536 edit=self._root_widget, transition=self._transition_out 1537 ) 1538 1539 if not self._modal: 1540 assert bui.app.classic is not None 1541 bui.app.ui_v1.set_main_menu_window( 1542 MainMenuWindow(transition='in_left').get_root_widget() 1543 ) 1544 1545 def _save_state(self) -> None: 1546 try: 1547 sel = self._root_widget.get_selected_child() 1548 if sel == self._back_button: 1549 sel_name = 'Back' 1550 elif sel == self._scrollwidget: 1551 sel_name = 'Scroll' 1552 else: 1553 raise ValueError('unrecognized selection') 1554 assert bui.app.classic is not None 1555 bui.app.ui_v1.window_states[type(self)] = sel_name 1556 except Exception: 1557 logging.exception('Error saving state for %s.', self) 1558 1559 def _restore_state(self) -> None: 1560 try: 1561 assert bui.app.classic is not None 1562 sel_name = bui.app.ui_v1.window_states.get(type(self)) 1563 if sel_name == 'Back': 1564 sel = self._back_button 1565 elif sel_name == 'Scroll': 1566 sel = self._scrollwidget 1567 else: 1568 sel = self._back_button 1569 bui.containerwidget(edit=self._root_widget, selected_child=sel) 1570 except Exception: 1571 logging.exception('Error restoring state for %s.', self)
Window for account related functionality.
AccountSettingsWindow( transition: str = 'in_right', modal: bool = False, origin_widget: _bauiv1.Widget | None = None, close_once_signed_in: bool = False)
25 def __init__( 26 self, 27 transition: str = 'in_right', 28 modal: bool = False, 29 origin_widget: bui.Widget | None = None, 30 close_once_signed_in: bool = False, 31 ): 32 # pylint: disable=too-many-statements 33 34 plus = bui.app.plus 35 assert plus is not None 36 37 self._sign_in_v2_proxy_button: bui.Widget | None = None 38 self._sign_in_device_button: bui.Widget | None = None 39 40 self._show_legacy_unlink_button = False 41 42 self._signing_in_adapter: bui.LoginAdapter | None = None 43 self._close_once_signed_in = close_once_signed_in 44 bui.set_analytics_screen('Account Window') 45 46 self._explicitly_signed_out_of_gpgs = False 47 48 # If they provided an origin-widget, scale up from that. 49 scale_origin: tuple[float, float] | None 50 if origin_widget is not None: 51 self._transition_out = 'out_scale' 52 scale_origin = origin_widget.get_screen_space_center() 53 transition = 'in_scale' 54 else: 55 self._transition_out = 'out_right' 56 scale_origin = None 57 58 self._r = 'accountSettingsWindow' 59 self._modal = modal 60 self._needs_refresh = False 61 self._v1_signed_in = plus.get_v1_account_state() == 'signed_in' 62 self._v1_account_state_num = plus.get_v1_account_state_num() 63 self._check_sign_in_timer = bui.AppTimer( 64 1.0, bui.WeakCall(self._update), repeat=True 65 ) 66 67 # Currently we can only reset achievements on game-center. 68 v1_account_type: str | None 69 if self._v1_signed_in: 70 v1_account_type = plus.get_v1_account_type() 71 else: 72 v1_account_type = None 73 self._can_reset_achievements = v1_account_type == 'Game Center' 74 75 app = bui.app 76 assert app.classic is not None 77 uiscale = app.ui_v1.uiscale 78 79 self._width = 760 if uiscale is bui.UIScale.SMALL else 660 80 x_offs = 50 if uiscale is bui.UIScale.SMALL else 0 81 self._height = ( 82 390 83 if uiscale is bui.UIScale.SMALL 84 else 430 85 if uiscale is bui.UIScale.MEDIUM 86 else 490 87 ) 88 89 self._sign_in_button = None 90 self._sign_in_text = None 91 92 self._scroll_width = self._width - (100 + x_offs * 2) 93 self._scroll_height = self._height - 120 94 self._sub_width = self._scroll_width - 20 95 96 # Determine which sign-in/sign-out buttons we should show. 97 self._show_sign_in_buttons: list[str] = [] 98 99 if LoginType.GPGS in plus.accounts.login_adapters: 100 self._show_sign_in_buttons.append('Google Play') 101 102 # Always want to show our web-based v2 login option. 103 self._show_sign_in_buttons.append('V2Proxy') 104 105 # Legacy v1 device accounts are currently always available 106 # (though we need to start phasing them out at some point). 107 self._show_sign_in_buttons.append('Device') 108 109 top_extra = 15 if uiscale is bui.UIScale.SMALL else 0 110 super().__init__( 111 root_widget=bui.containerwidget( 112 size=(self._width, self._height + top_extra), 113 transition=transition, 114 toolbar_visibility='menu_minimal', 115 scale_origin_stack_offset=scale_origin, 116 scale=( 117 2.09 118 if uiscale is bui.UIScale.SMALL 119 else 1.4 120 if uiscale is bui.UIScale.MEDIUM 121 else 1.0 122 ), 123 stack_offset=(0, -19) 124 if uiscale is bui.UIScale.SMALL 125 else (0, 0), 126 ) 127 ) 128 if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars: 129 self._back_button = None 130 bui.containerwidget( 131 edit=self._root_widget, on_cancel_call=self._back 132 ) 133 else: 134 self._back_button = btn = bui.buttonwidget( 135 parent=self._root_widget, 136 position=(51 + x_offs, self._height - 62), 137 size=(120, 60), 138 scale=0.8, 139 text_scale=1.2, 140 autoselect=True, 141 label=bui.Lstr( 142 resource='doneText' if self._modal else 'backText' 143 ), 144 button_type='regular' if self._modal else 'back', 145 on_activate_call=self._back, 146 ) 147 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 148 if not self._modal: 149 bui.buttonwidget( 150 edit=btn, 151 button_type='backSmall', 152 size=(60, 56), 153 label=bui.charstr(bui.SpecialChar.BACK), 154 ) 155 156 bui.textwidget( 157 parent=self._root_widget, 158 position=(self._width * 0.5, self._height - 41), 159 size=(0, 0), 160 text=bui.Lstr(resource=self._r + '.titleText'), 161 color=app.ui_v1.title_color, 162 maxwidth=self._width - 340, 163 h_align='center', 164 v_align='center', 165 ) 166 167 self._scrollwidget = bui.scrollwidget( 168 parent=self._root_widget, 169 highlight=False, 170 position=( 171 (self._width - self._scroll_width) * 0.5, 172 self._height - 65 - self._scroll_height, 173 ), 174 size=(self._scroll_width, self._scroll_height), 175 claims_left_right=True, 176 claims_tab=True, 177 selection_loops_to_parent=True, 178 ) 179 self._subcontainer: bui.Widget | None = None 180 self._refresh() 181 self._restore_state()
Inherited Members
- bauiv1._uitypes.Window
- get_root_widget
def
show_what_is_v2_page() -> None:
1574def show_what_is_v2_page() -> None: 1575 """Show the webpage describing V2 accounts.""" 1576 plus = bui.app.plus 1577 assert plus is not None 1578 1579 bamasteraddr = plus.get_master_server_address(version=2) 1580 bui.open_url(f'{bamasteraddr}/whatisv2')
Show the webpage describing V2 accounts.
def
show_what_is_legacy_unlinking_page() -> None:
1583def show_what_is_legacy_unlinking_page() -> None: 1584 """Show the webpage describing legacy unlinking.""" 1585 plus = bui.app.plus 1586 assert plus is not None 1587 1588 bamasteraddr = plus.get_master_server_address(version=2) 1589 bui.open_url(f'{bamasteraddr}/whatarev1links')
Show the webpage describing legacy unlinking.