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