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