bastd.ui.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 9from typing import TYPE_CHECKING 10 11import _ba 12import ba 13 14if TYPE_CHECKING: 15 pass 16 17 18class AccountSettingsWindow(ba.Window): 19 """Window for account related functionality.""" 20 21 def __init__(self, 22 transition: str = 'in_right', 23 modal: bool = False, 24 origin_widget: ba.Widget | None = None, 25 close_once_signed_in: bool = False): 26 # pylint: disable=too-many-statements 27 28 self._sign_in_game_circle_button: ba.Widget | None = None 29 self._sign_in_v2_button: ba.Widget | None = None 30 self._sign_in_device_button: ba.Widget | None = None 31 32 self._close_once_signed_in = close_once_signed_in 33 ba.set_analytics_screen('Account Window') 34 35 # If they provided an origin-widget, scale up from that. 36 scale_origin: tuple[float, float] | None 37 if origin_widget is not None: 38 self._transition_out = 'out_scale' 39 scale_origin = origin_widget.get_screen_space_center() 40 transition = 'in_scale' 41 else: 42 self._transition_out = 'out_right' 43 scale_origin = None 44 45 self._r = 'accountSettingsWindow' 46 self._modal = modal 47 self._needs_refresh = False 48 self._signed_in = (_ba.get_v1_account_state() == 'signed_in') 49 self._account_state_num = _ba.get_v1_account_state_num() 50 self._show_linked = (self._signed_in 51 and _ba.get_v1_account_misc_read_val( 52 'allowAccountLinking2', False)) 53 self._check_sign_in_timer = ba.Timer(1.0, 54 ba.WeakCall(self._update), 55 timetype=ba.TimeType.REAL, 56 repeat=True) 57 58 # Currently we can only reset achievements on game-center. 59 account_type: str | None 60 if self._signed_in: 61 account_type = _ba.get_v1_account_type() 62 else: 63 account_type = None 64 self._can_reset_achievements = (account_type == 'Game Center') 65 66 app = ba.app 67 uiscale = app.ui.uiscale 68 69 self._width = 760 if uiscale is ba.UIScale.SMALL else 660 70 x_offs = 50 if uiscale is ba.UIScale.SMALL else 0 71 self._height = (390 if uiscale is ba.UIScale.SMALL else 72 430 if uiscale is ba.UIScale.MEDIUM else 490) 73 74 self._sign_in_button = None 75 self._sign_in_text = None 76 77 self._scroll_width = self._width - (100 + x_offs * 2) 78 self._scroll_height = self._height - 120 79 self._sub_width = self._scroll_width - 20 80 81 # Determine which sign-in/sign-out buttons we should show. 82 self._show_sign_in_buttons: list[str] = [] 83 84 if app.platform == 'android' and app.subplatform == 'google': 85 self._show_sign_in_buttons.append('Google Play') 86 87 elif app.platform == 'android' and app.subplatform == 'amazon': 88 self._show_sign_in_buttons.append('Game Circle') 89 90 # Local accounts are generally always available with a few key 91 # exceptions. 92 self._show_sign_in_buttons.append('Local') 93 94 # Ditto with shiny new V2 ones. 95 if bool(True): 96 self._show_sign_in_buttons.append('V2') 97 98 top_extra = 15 if uiscale is ba.UIScale.SMALL else 0 99 super().__init__(root_widget=ba.containerwidget( 100 size=(self._width, self._height + top_extra), 101 transition=transition, 102 toolbar_visibility='menu_minimal', 103 scale_origin_stack_offset=scale_origin, 104 scale=(2.09 if uiscale is ba.UIScale.SMALL else 105 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), 106 stack_offset=(0, -19) if uiscale is ba.UIScale.SMALL else (0, 0))) 107 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: 108 self._back_button = None 109 ba.containerwidget(edit=self._root_widget, 110 on_cancel_call=self._back) 111 else: 112 self._back_button = btn = ba.buttonwidget( 113 parent=self._root_widget, 114 position=(51 + x_offs, self._height - 62), 115 size=(120, 60), 116 scale=0.8, 117 text_scale=1.2, 118 autoselect=True, 119 label=ba.Lstr( 120 resource='doneText' if self._modal else 'backText'), 121 button_type='regular' if self._modal else 'back', 122 on_activate_call=self._back) 123 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 124 if not self._modal: 125 ba.buttonwidget(edit=btn, 126 button_type='backSmall', 127 size=(60, 56), 128 label=ba.charstr(ba.SpecialChar.BACK)) 129 130 ba.textwidget(parent=self._root_widget, 131 position=(self._width * 0.5, self._height - 41), 132 size=(0, 0), 133 text=ba.Lstr(resource=self._r + '.titleText'), 134 color=ba.app.ui.title_color, 135 maxwidth=self._width - 340, 136 h_align='center', 137 v_align='center') 138 139 self._scrollwidget = ba.scrollwidget( 140 parent=self._root_widget, 141 highlight=False, 142 position=((self._width - self._scroll_width) * 0.5, 143 self._height - 65 - self._scroll_height), 144 size=(self._scroll_width, self._scroll_height), 145 claims_left_right=True, 146 claims_tab=True, 147 selection_loops_to_parent=True) 148 self._subcontainer: ba.Widget | None = None 149 self._refresh() 150 self._restore_state() 151 152 def _update(self) -> None: 153 154 # If they want us to close once we're signed in, do so. 155 if self._close_once_signed_in and self._signed_in: 156 self._back() 157 return 158 159 # Hmm should update this to use get_account_state_num. 160 # Theoretically if we switch from one signed-in account to another 161 # in the background this would break. 162 account_state_num = _ba.get_v1_account_state_num() 163 account_state = _ba.get_v1_account_state() 164 165 show_linked = (self._signed_in and _ba.get_v1_account_misc_read_val( 166 'allowAccountLinking2', False)) 167 168 if (account_state_num != self._account_state_num 169 or self._show_linked != show_linked or self._needs_refresh): 170 self._show_linked = show_linked 171 self._account_state_num = account_state_num 172 self._signed_in = (account_state == 'signed_in') 173 self._refresh() 174 175 # Go ahead and refresh some individual things 176 # that may change under us. 177 self._update_linked_accounts_text() 178 self._update_unlink_accounts_button() 179 self._refresh_campaign_progress_text() 180 self._refresh_achievements() 181 self._refresh_tickets_text() 182 self._refresh_account_name_text() 183 184 def _get_sign_in_text(self) -> ba.Lstr: 185 return ba.Lstr(resource=self._r + '.signInText') 186 187 def _refresh(self) -> None: 188 # pylint: disable=too-many-statements 189 # pylint: disable=too-many-branches 190 # pylint: disable=too-many-locals 191 # pylint: disable=cyclic-import 192 from bastd.ui import confirm 193 194 account_state = _ba.get_v1_account_state() 195 account_type = (_ba.get_v1_account_type() 196 if account_state == 'signed_in' else 'unknown') 197 198 is_google = account_type == 'Google Play' 199 200 show_local_signed_in_as = False 201 local_signed_in_as_space = 50.0 202 203 show_signed_in_as = self._signed_in 204 signed_in_as_space = 95.0 205 206 show_sign_in_benefits = not self._signed_in 207 sign_in_benefits_space = 80.0 208 209 show_signing_in_text = account_state == 'signing_in' 210 signing_in_text_space = 80.0 211 212 show_google_play_sign_in_button = (account_state == 'signed_out' 213 and 'Google Play' 214 in self._show_sign_in_buttons) 215 show_game_circle_sign_in_button = (account_state == 'signed_out' 216 and 'Game Circle' 217 in self._show_sign_in_buttons) 218 show_device_sign_in_button = (account_state == 'signed_out' and 'Local' 219 in self._show_sign_in_buttons) 220 show_v2_sign_in_button = (account_state == 'signed_out' 221 and 'V2' in self._show_sign_in_buttons) 222 sign_in_button_space = 70.0 223 224 show_game_service_button = (self._signed_in and account_type 225 in ['Game Center', 'Game Circle']) 226 game_service_button_space = 60.0 227 228 show_linked_accounts_text = (self._signed_in 229 and _ba.get_v1_account_misc_read_val( 230 'allowAccountLinking2', False)) 231 linked_accounts_text_space = 60.0 232 233 show_achievements_button = ( 234 self._signed_in 235 and account_type in ('Google Play', 'Alibaba', 'Local', 'OUYA')) 236 achievements_button_space = 60.0 237 238 show_achievements_text = (self._signed_in 239 and not show_achievements_button) 240 achievements_text_space = 27.0 241 242 show_leaderboards_button = (self._signed_in and is_google) 243 leaderboards_button_space = 60.0 244 245 show_campaign_progress = self._signed_in 246 campaign_progress_space = 27.0 247 248 show_tickets = self._signed_in 249 tickets_space = 27.0 250 251 show_reset_progress_button = False 252 reset_progress_button_space = 70.0 253 254 show_player_profiles_button = self._signed_in 255 player_profiles_button_space = 100.0 256 257 show_link_accounts_button = (self._signed_in 258 and _ba.get_v1_account_misc_read_val( 259 'allowAccountLinking2', False)) 260 link_accounts_button_space = 70.0 261 262 show_unlink_accounts_button = show_link_accounts_button 263 unlink_accounts_button_space = 90.0 264 265 show_sign_out_button = (self._signed_in and account_type 266 in ['Local', 'Google Play', 'V2']) 267 sign_out_button_space = 70.0 268 269 show_cancel_v2_sign_in_button = ( 270 account_state == 'signing_in' 271 and ba.app.accounts_v2.have_primary_credentials()) 272 cancel_v2_sign_in_button_space = 70.0 273 274 if self._subcontainer is not None: 275 self._subcontainer.delete() 276 self._sub_height = 60.0 277 if show_local_signed_in_as: 278 self._sub_height += local_signed_in_as_space 279 if show_signed_in_as: 280 self._sub_height += signed_in_as_space 281 if show_signing_in_text: 282 self._sub_height += signing_in_text_space 283 if show_google_play_sign_in_button: 284 self._sub_height += sign_in_button_space 285 if show_game_circle_sign_in_button: 286 self._sub_height += sign_in_button_space 287 if show_device_sign_in_button: 288 self._sub_height += sign_in_button_space 289 if show_v2_sign_in_button: 290 self._sub_height += sign_in_button_space 291 if show_game_service_button: 292 self._sub_height += game_service_button_space 293 if show_linked_accounts_text: 294 self._sub_height += linked_accounts_text_space 295 if show_achievements_text: 296 self._sub_height += achievements_text_space 297 if show_achievements_button: 298 self._sub_height += achievements_button_space 299 if show_leaderboards_button: 300 self._sub_height += leaderboards_button_space 301 if show_campaign_progress: 302 self._sub_height += campaign_progress_space 303 if show_tickets: 304 self._sub_height += tickets_space 305 if show_sign_in_benefits: 306 self._sub_height += sign_in_benefits_space 307 if show_reset_progress_button: 308 self._sub_height += reset_progress_button_space 309 if show_player_profiles_button: 310 self._sub_height += player_profiles_button_space 311 if show_link_accounts_button: 312 self._sub_height += link_accounts_button_space 313 if show_unlink_accounts_button: 314 self._sub_height += unlink_accounts_button_space 315 if show_sign_out_button: 316 self._sub_height += sign_out_button_space 317 if show_cancel_v2_sign_in_button: 318 self._sub_height += cancel_v2_sign_in_button_space 319 self._subcontainer = ba.containerwidget(parent=self._scrollwidget, 320 size=(self._sub_width, 321 self._sub_height), 322 background=False, 323 claims_left_right=True, 324 claims_tab=True, 325 selection_loops_to_parent=True) 326 327 first_selectable = None 328 v = self._sub_height - 10.0 329 330 if show_local_signed_in_as: 331 v -= local_signed_in_as_space * 0.6 332 ba.textwidget( 333 parent=self._subcontainer, 334 position=(self._sub_width * 0.5, v), 335 size=(0, 0), 336 text=ba.Lstr( 337 resource='accountSettingsWindow.deviceSpecificAccountText', 338 subs=[('${NAME}', _ba.get_v1_account_display_string())]), 339 scale=0.7, 340 color=(0.5, 0.5, 0.6), 341 maxwidth=self._sub_width * 0.9, 342 flatness=1.0, 343 h_align='center', 344 v_align='center') 345 v -= local_signed_in_as_space * 0.4 346 347 self._account_name_text: ba.Widget | None 348 if show_signed_in_as: 349 v -= signed_in_as_space * 0.2 350 txt = ba.Lstr( 351 resource='accountSettingsWindow.youAreSignedInAsText', 352 fallback_resource='accountSettingsWindow.youAreLoggedInAsText') 353 ba.textwidget(parent=self._subcontainer, 354 position=(self._sub_width * 0.5, v), 355 size=(0, 0), 356 text=txt, 357 scale=0.9, 358 color=ba.app.ui.title_color, 359 maxwidth=self._sub_width * 0.9, 360 h_align='center', 361 v_align='center') 362 v -= signed_in_as_space * 0.4 363 self._account_name_text = ba.textwidget( 364 parent=self._subcontainer, 365 position=(self._sub_width * 0.5, v), 366 size=(0, 0), 367 scale=1.5, 368 maxwidth=self._sub_width * 0.9, 369 res_scale=1.5, 370 color=(1, 1, 1, 1), 371 h_align='center', 372 v_align='center') 373 self._refresh_account_name_text() 374 v -= signed_in_as_space * 0.4 375 else: 376 self._account_name_text = None 377 378 if self._back_button is None: 379 bbtn = _ba.get_special_widget('back_button') 380 else: 381 bbtn = self._back_button 382 383 if show_sign_in_benefits: 384 v -= sign_in_benefits_space 385 app = ba.app 386 extra: str | ba.Lstr | None 387 if (app.platform in ['mac', 'ios'] 388 and app.subplatform == 'appstore'): 389 extra = ba.Lstr( 390 value='\n${S}', 391 subs=[('${S}', 392 ba.Lstr(resource='signInWithGameCenterText'))]) 393 else: 394 extra = '' 395 396 ba.textwidget(parent=self._subcontainer, 397 position=(self._sub_width * 0.5, 398 v + sign_in_benefits_space * 0.4), 399 size=(0, 0), 400 text=ba.Lstr(value='${A}${B}', 401 subs=[('${A}', 402 ba.Lstr(resource=self._r + 403 '.signInInfoText')), 404 ('${B}', extra)]), 405 max_height=sign_in_benefits_space * 0.9, 406 scale=0.9, 407 color=(0.75, 0.7, 0.8), 408 maxwidth=self._sub_width * 0.8, 409 h_align='center', 410 v_align='center') 411 412 if show_signing_in_text: 413 v -= signing_in_text_space 414 415 ba.textwidget( 416 parent=self._subcontainer, 417 position=(self._sub_width * 0.5, 418 v + signing_in_text_space * 0.5), 419 size=(0, 0), 420 text=ba.Lstr(resource='accountSettingsWindow.signingInText'), 421 scale=0.9, 422 color=(0, 1, 0), 423 maxwidth=self._sub_width * 0.8, 424 h_align='center', 425 v_align='center') 426 427 if show_google_play_sign_in_button: 428 button_width = 350 429 v -= sign_in_button_space 430 self._sign_in_google_play_button = btn = ba.buttonwidget( 431 parent=self._subcontainer, 432 position=((self._sub_width - button_width) * 0.5, v - 20), 433 autoselect=True, 434 size=(button_width, 60), 435 label=ba.Lstr( 436 value='${A}${B}', 437 subs=[('${A}', 438 ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO)), 439 ('${B}', 440 ba.Lstr(resource=self._r + 441 '.signInWithGooglePlayText'))]), 442 on_activate_call=lambda: self._sign_in_press('Google Play')) 443 if first_selectable is None: 444 first_selectable = btn 445 if ba.app.ui.use_toolbars: 446 ba.widget(edit=btn, 447 right_widget=_ba.get_special_widget('party_button')) 448 ba.widget(edit=btn, left_widget=bbtn) 449 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 450 self._sign_in_text = None 451 452 if show_game_circle_sign_in_button: 453 button_width = 350 454 v -= sign_in_button_space 455 self._sign_in_game_circle_button = btn = ba.buttonwidget( 456 parent=self._subcontainer, 457 position=((self._sub_width - button_width) * 0.5, v - 20), 458 autoselect=True, 459 size=(button_width, 60), 460 label=ba.Lstr(value='${A}${B}', 461 subs=[('${A}', 462 ba.charstr( 463 ba.SpecialChar.GAME_CIRCLE_LOGO)), 464 ('${B}', 465 ba.Lstr(resource=self._r + 466 '.signInWithGameCircleText'))]), 467 on_activate_call=lambda: self._sign_in_press('Game Circle')) 468 if first_selectable is None: 469 first_selectable = btn 470 if ba.app.ui.use_toolbars: 471 ba.widget(edit=btn, 472 right_widget=_ba.get_special_widget('party_button')) 473 ba.widget(edit=btn, left_widget=bbtn) 474 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 475 self._sign_in_text = None 476 477 if show_v2_sign_in_button: 478 button_width = 350 479 v -= sign_in_button_space 480 self._sign_in_v2_button = btn = ba.buttonwidget( 481 parent=self._subcontainer, 482 position=((self._sub_width - button_width) * 0.5, v - 20), 483 autoselect=True, 484 size=(button_width, 60), 485 label='', 486 on_activate_call=self._v2_sign_in_press) 487 ba.textwidget( 488 parent=self._subcontainer, 489 draw_controller=btn, 490 h_align='center', 491 v_align='center', 492 size=(0, 0), 493 position=(self._sub_width * 0.5, v + 17), 494 text=ba.Lstr( 495 value='${A}${B}', 496 subs=[('${A}', ba.charstr(ba.SpecialChar.V2_LOGO)), 497 ('${B}', 498 ba.Lstr(resource=self._r + '.signInWithV2Text'))]), 499 maxwidth=button_width * 0.8, 500 color=(0.75, 1.0, 0.7)) 501 ba.textwidget(parent=self._subcontainer, 502 draw_controller=btn, 503 h_align='center', 504 v_align='center', 505 size=(0, 0), 506 position=(self._sub_width * 0.5, v - 4), 507 text=ba.Lstr(resource=self._r + 508 '.signInWithV2InfoText'), 509 flatness=1.0, 510 scale=0.57, 511 maxwidth=button_width * 0.9, 512 color=(0.55, 0.8, 0.5)) 513 if first_selectable is None: 514 first_selectable = btn 515 if ba.app.ui.use_toolbars: 516 ba.widget(edit=btn, 517 right_widget=_ba.get_special_widget('party_button')) 518 ba.widget(edit=btn, left_widget=bbtn) 519 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 520 self._sign_in_text = None 521 522 if show_device_sign_in_button: 523 button_width = 350 524 v -= sign_in_button_space 525 self._sign_in_device_button = btn = ba.buttonwidget( 526 parent=self._subcontainer, 527 position=((self._sub_width - button_width) * 0.5, v - 20), 528 autoselect=True, 529 size=(button_width, 60), 530 label='', 531 on_activate_call=lambda: self._sign_in_press('Local')) 532 ba.textwidget(parent=self._subcontainer, 533 draw_controller=btn, 534 h_align='center', 535 v_align='center', 536 size=(0, 0), 537 position=(self._sub_width * 0.5, v + 17), 538 text=ba.Lstr( 539 value='${A}${B}', 540 subs=[('${A}', 541 ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)), 542 ('${B}', 543 ba.Lstr(resource=self._r + 544 '.signInWithDeviceText'))]), 545 maxwidth=button_width * 0.8, 546 color=(0.75, 1.0, 0.7)) 547 ba.textwidget(parent=self._subcontainer, 548 draw_controller=btn, 549 h_align='center', 550 v_align='center', 551 size=(0, 0), 552 position=(self._sub_width * 0.5, v - 4), 553 text=ba.Lstr(resource=self._r + 554 '.signInWithDeviceInfoText'), 555 flatness=1.0, 556 scale=0.57, 557 maxwidth=button_width * 0.9, 558 color=(0.55, 0.8, 0.5)) 559 if first_selectable is None: 560 first_selectable = btn 561 if ba.app.ui.use_toolbars: 562 ba.widget(edit=btn, 563 right_widget=_ba.get_special_widget('party_button')) 564 ba.widget(edit=btn, left_widget=bbtn) 565 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 566 self._sign_in_text = None 567 568 if show_player_profiles_button: 569 button_width = 300 570 v -= player_profiles_button_space 571 self._player_profiles_button = btn = ba.buttonwidget( 572 parent=self._subcontainer, 573 position=((self._sub_width - button_width) * 0.5, v + 30), 574 autoselect=True, 575 size=(button_width, 60), 576 label=ba.Lstr(resource='playerProfilesWindow.titleText'), 577 color=(0.55, 0.5, 0.6), 578 icon=ba.gettexture('cuteSpaz'), 579 textcolor=(0.75, 0.7, 0.8), 580 on_activate_call=self._player_profiles_press) 581 if first_selectable is None: 582 first_selectable = btn 583 if ba.app.ui.use_toolbars: 584 ba.widget(edit=btn, 585 right_widget=_ba.get_special_widget('party_button')) 586 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0) 587 588 # the button to go to OS-Specific leaderboards/high-score-lists/etc. 589 if show_game_service_button: 590 button_width = 300 591 v -= game_service_button_space * 0.85 592 account_type = _ba.get_v1_account_type() 593 if account_type == 'Game Center': 594 account_type_name = ba.Lstr(resource='gameCenterText') 595 elif account_type == 'Game Circle': 596 account_type_name = ba.Lstr(resource='gameCircleText') 597 else: 598 raise ValueError("unknown account type: '" + 599 str(account_type) + "'") 600 self._game_service_button = btn = ba.buttonwidget( 601 parent=self._subcontainer, 602 position=((self._sub_width - button_width) * 0.5, v), 603 color=(0.55, 0.5, 0.6), 604 textcolor=(0.75, 0.7, 0.8), 605 autoselect=True, 606 on_activate_call=_ba.show_online_score_ui, 607 size=(button_width, 50), 608 label=account_type_name) 609 if first_selectable is None: 610 first_selectable = btn 611 if ba.app.ui.use_toolbars: 612 ba.widget(edit=btn, 613 right_widget=_ba.get_special_widget('party_button')) 614 ba.widget(edit=btn, left_widget=bbtn) 615 v -= game_service_button_space * 0.15 616 else: 617 self.game_service_button = None 618 619 self._achievements_text: ba.Widget | None 620 if show_achievements_text: 621 v -= achievements_text_space * 0.5 622 self._achievements_text = ba.textwidget( 623 parent=self._subcontainer, 624 position=(self._sub_width * 0.5, v), 625 size=(0, 0), 626 scale=0.9, 627 color=(0.75, 0.7, 0.8), 628 maxwidth=self._sub_width * 0.8, 629 h_align='center', 630 v_align='center') 631 v -= achievements_text_space * 0.5 632 else: 633 self._achievements_text = None 634 635 self._achievements_button: ba.Widget | None 636 if show_achievements_button: 637 button_width = 300 638 v -= achievements_button_space * 0.85 639 self._achievements_button = btn = ba.buttonwidget( 640 parent=self._subcontainer, 641 position=((self._sub_width - button_width) * 0.5, v), 642 color=(0.55, 0.5, 0.6), 643 textcolor=(0.75, 0.7, 0.8), 644 autoselect=True, 645 icon=ba.gettexture('googlePlayAchievementsIcon' 646 if is_google else 'achievementsIcon'), 647 icon_color=(0.8, 0.95, 0.7) if is_google else (0.85, 0.8, 0.9), 648 on_activate_call=self._on_achievements_press, 649 size=(button_width, 50), 650 label='') 651 if first_selectable is None: 652 first_selectable = btn 653 if ba.app.ui.use_toolbars: 654 ba.widget(edit=btn, 655 right_widget=_ba.get_special_widget('party_button')) 656 ba.widget(edit=btn, left_widget=bbtn) 657 v -= achievements_button_space * 0.15 658 else: 659 self._achievements_button = None 660 661 if show_achievements_text or show_achievements_button: 662 self._refresh_achievements() 663 664 self._leaderboards_button: ba.Widget | None 665 if show_leaderboards_button: 666 button_width = 300 667 v -= leaderboards_button_space * 0.85 668 self._leaderboards_button = btn = ba.buttonwidget( 669 parent=self._subcontainer, 670 position=((self._sub_width - button_width) * 0.5, v), 671 color=(0.55, 0.5, 0.6), 672 textcolor=(0.75, 0.7, 0.8), 673 autoselect=True, 674 icon=ba.gettexture('googlePlayLeaderboardsIcon'), 675 icon_color=(0.8, 0.95, 0.7), 676 on_activate_call=self._on_leaderboards_press, 677 size=(button_width, 50), 678 label=ba.Lstr(resource='leaderboardsText')) 679 if first_selectable is None: 680 first_selectable = btn 681 if ba.app.ui.use_toolbars: 682 ba.widget(edit=btn, 683 right_widget=_ba.get_special_widget('party_button')) 684 ba.widget(edit=btn, left_widget=bbtn) 685 v -= leaderboards_button_space * 0.15 686 else: 687 self._leaderboards_button = None 688 689 self._campaign_progress_text: ba.Widget | None 690 if show_campaign_progress: 691 v -= campaign_progress_space * 0.5 692 self._campaign_progress_text = ba.textwidget( 693 parent=self._subcontainer, 694 position=(self._sub_width * 0.5, v), 695 size=(0, 0), 696 scale=0.9, 697 color=(0.75, 0.7, 0.8), 698 maxwidth=self._sub_width * 0.8, 699 h_align='center', 700 v_align='center') 701 v -= campaign_progress_space * 0.5 702 self._refresh_campaign_progress_text() 703 else: 704 self._campaign_progress_text = None 705 706 self._tickets_text: ba.Widget | None 707 if show_tickets: 708 v -= tickets_space * 0.5 709 self._tickets_text = ba.textwidget(parent=self._subcontainer, 710 position=(self._sub_width * 0.5, 711 v), 712 size=(0, 0), 713 scale=0.9, 714 color=(0.75, 0.7, 0.8), 715 maxwidth=self._sub_width * 0.8, 716 flatness=1.0, 717 h_align='center', 718 v_align='center') 719 v -= tickets_space * 0.5 720 self._refresh_tickets_text() 721 722 else: 723 self._tickets_text = None 724 725 # bit of spacing before the reset/sign-out section 726 v -= 5 727 728 button_width = 250 729 if show_reset_progress_button: 730 confirm_text = (ba.Lstr(resource=self._r + 731 '.resetProgressConfirmText') 732 if self._can_reset_achievements else ba.Lstr( 733 resource=self._r + 734 '.resetProgressConfirmNoAchievementsText')) 735 v -= reset_progress_button_space 736 self._reset_progress_button = btn = ba.buttonwidget( 737 parent=self._subcontainer, 738 position=((self._sub_width - button_width) * 0.5, v), 739 color=(0.55, 0.5, 0.6), 740 textcolor=(0.75, 0.7, 0.8), 741 autoselect=True, 742 size=(button_width, 60), 743 label=ba.Lstr(resource=self._r + '.resetProgressText'), 744 on_activate_call=lambda: confirm.ConfirmWindow( 745 text=confirm_text, 746 width=500, 747 height=200, 748 action=self._reset_progress)) 749 if first_selectable is None: 750 first_selectable = btn 751 if ba.app.ui.use_toolbars: 752 ba.widget(edit=btn, 753 right_widget=_ba.get_special_widget('party_button')) 754 ba.widget(edit=btn, left_widget=bbtn) 755 756 self._linked_accounts_text: ba.Widget | None 757 if show_linked_accounts_text: 758 v -= linked_accounts_text_space * 0.8 759 self._linked_accounts_text = ba.textwidget( 760 parent=self._subcontainer, 761 position=(self._sub_width * 0.5, v), 762 size=(0, 0), 763 scale=0.9, 764 color=(0.75, 0.7, 0.8), 765 maxwidth=self._sub_width * 0.95, 766 h_align='center', 767 v_align='center') 768 v -= linked_accounts_text_space * 0.2 769 self._update_linked_accounts_text() 770 else: 771 self._linked_accounts_text = None 772 773 if show_link_accounts_button: 774 v -= link_accounts_button_space 775 self._link_accounts_button = btn = ba.buttonwidget( 776 parent=self._subcontainer, 777 position=((self._sub_width - button_width) * 0.5, v), 778 autoselect=True, 779 size=(button_width, 60), 780 label='', 781 color=(0.55, 0.5, 0.6), 782 on_activate_call=self._link_accounts_press) 783 ba.textwidget(parent=self._subcontainer, 784 draw_controller=btn, 785 h_align='center', 786 v_align='center', 787 size=(0, 0), 788 position=(self._sub_width * 0.5, v + 17 + 20), 789 text=ba.Lstr(resource=self._r + '.linkAccountsText'), 790 maxwidth=button_width * 0.8, 791 color=(0.75, 0.7, 0.8)) 792 ba.textwidget(parent=self._subcontainer, 793 draw_controller=btn, 794 h_align='center', 795 v_align='center', 796 size=(0, 0), 797 position=(self._sub_width * 0.5, v - 4 + 20), 798 text=ba.Lstr(resource=self._r + 799 '.linkAccountsInfoText'), 800 flatness=1.0, 801 scale=0.5, 802 maxwidth=button_width * 0.8, 803 color=(0.75, 0.7, 0.8)) 804 if first_selectable is None: 805 first_selectable = btn 806 if ba.app.ui.use_toolbars: 807 ba.widget(edit=btn, 808 right_widget=_ba.get_special_widget('party_button')) 809 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 810 811 self._unlink_accounts_button: ba.Widget | None 812 if show_unlink_accounts_button: 813 v -= unlink_accounts_button_space 814 self._unlink_accounts_button = btn = ba.buttonwidget( 815 parent=self._subcontainer, 816 position=((self._sub_width - button_width) * 0.5, v + 25), 817 autoselect=True, 818 size=(button_width, 60), 819 label='', 820 color=(0.55, 0.5, 0.6), 821 on_activate_call=self._unlink_accounts_press) 822 self._unlink_accounts_button_label = ba.textwidget( 823 parent=self._subcontainer, 824 draw_controller=btn, 825 h_align='center', 826 v_align='center', 827 size=(0, 0), 828 position=(self._sub_width * 0.5, v + 55), 829 text=ba.Lstr(resource=self._r + '.unlinkAccountsText'), 830 maxwidth=button_width * 0.8, 831 color=(0.75, 0.7, 0.8)) 832 if first_selectable is None: 833 first_selectable = btn 834 if ba.app.ui.use_toolbars: 835 ba.widget(edit=btn, 836 right_widget=_ba.get_special_widget('party_button')) 837 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 838 self._update_unlink_accounts_button() 839 else: 840 self._unlink_accounts_button = None 841 842 if show_sign_out_button: 843 v -= sign_out_button_space 844 self._sign_out_button = btn = ba.buttonwidget( 845 parent=self._subcontainer, 846 position=((self._sub_width - button_width) * 0.5, v), 847 size=(button_width, 60), 848 label=ba.Lstr(resource=self._r + '.signOutText'), 849 color=(0.55, 0.5, 0.6), 850 textcolor=(0.75, 0.7, 0.8), 851 autoselect=True, 852 on_activate_call=self._sign_out_press) 853 if first_selectable is None: 854 first_selectable = btn 855 if ba.app.ui.use_toolbars: 856 ba.widget(edit=btn, 857 right_widget=_ba.get_special_widget('party_button')) 858 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 859 860 if show_cancel_v2_sign_in_button: 861 v -= cancel_v2_sign_in_button_space 862 self._cancel_v2_sign_in_button = btn = ba.buttonwidget( 863 parent=self._subcontainer, 864 position=((self._sub_width - button_width) * 0.5, v), 865 size=(button_width, 60), 866 label=ba.Lstr(resource='cancelText'), 867 color=(0.55, 0.5, 0.6), 868 textcolor=(0.75, 0.7, 0.8), 869 autoselect=True, 870 on_activate_call=self._cancel_v2_sign_in_press) 871 if first_selectable is None: 872 first_selectable = btn 873 if ba.app.ui.use_toolbars: 874 ba.widget(edit=btn, 875 right_widget=_ba.get_special_widget('party_button')) 876 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 877 878 # Whatever the topmost selectable thing is, we want it to scroll all 879 # the way up when we select it. 880 if first_selectable is not None: 881 ba.widget(edit=first_selectable, 882 up_widget=bbtn, 883 show_buffer_top=400) 884 # (this should re-scroll us to the top..) 885 ba.containerwidget(edit=self._subcontainer, 886 visible_child=first_selectable) 887 self._needs_refresh = False 888 889 def _on_achievements_press(self) -> None: 890 # pylint: disable=cyclic-import 891 from bastd.ui import achievements 892 account_state = _ba.get_v1_account_state() 893 account_type = (_ba.get_v1_account_type() 894 if account_state == 'signed_in' else 'unknown') 895 # for google play we use the built-in UI; otherwise pop up our own 896 if account_type == 'Google Play': 897 ba.timer(0.15, 898 ba.Call(_ba.show_online_score_ui, 'achievements'), 899 timetype=ba.TimeType.REAL) 900 elif account_type != 'unknown': 901 assert self._achievements_button is not None 902 achievements.AchievementsWindow( 903 position=self._achievements_button.get_screen_space_center()) 904 else: 905 print('ERROR: unknown account type in on_achievements_press:', 906 account_type) 907 908 def _on_leaderboards_press(self) -> None: 909 ba.timer(0.15, 910 ba.Call(_ba.show_online_score_ui, 'leaderboards'), 911 timetype=ba.TimeType.REAL) 912 913 def _have_unlinkable_accounts(self) -> bool: 914 # if this is not present, we haven't had contact from the server so 915 # let's not proceed.. 916 if _ba.get_public_login_id() is None: 917 return False 918 accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', []) 919 return len(accounts) > 1 920 921 def _update_unlink_accounts_button(self) -> None: 922 if self._unlink_accounts_button is None: 923 return 924 if self._have_unlinkable_accounts(): 925 clr = (0.75, 0.7, 0.8, 1.0) 926 else: 927 clr = (1.0, 1.0, 1.0, 0.25) 928 ba.textwidget(edit=self._unlink_accounts_button_label, color=clr) 929 930 def _update_linked_accounts_text(self) -> None: 931 if self._linked_accounts_text is None: 932 return 933 934 # if this is not present, we haven't had contact from the server so 935 # let's not proceed.. 936 if _ba.get_public_login_id() is None: 937 num = int(time.time()) % 4 938 accounts_str = num * '.' + (4 - num) * ' ' 939 else: 940 accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', []) 941 # our_account = _bs.get_v1_account_display_string() 942 # accounts = [a for a in accounts if a != our_account] 943 # accounts_str = u', '.join(accounts) if accounts else 944 # ba.Lstr(translate=('settingNames', 'None')) 945 # UPDATE - we now just print the number here; not the actual 946 # accounts 947 # (they can see that in the unlink section if they're curious) 948 accounts_str = str(max(0, len(accounts) - 1)) 949 ba.textwidget(edit=self._linked_accounts_text, 950 text=ba.Lstr(value='${L} ${A}', 951 subs=[('${L}', 952 ba.Lstr(resource=self._r + 953 '.linkedAccountsText')), 954 ('${A}', accounts_str)])) 955 956 def _refresh_campaign_progress_text(self) -> None: 957 from ba.internal import getcampaign 958 if self._campaign_progress_text is None: 959 return 960 p_str: str | ba.Lstr 961 try: 962 campaign = getcampaign('Default') 963 levels = campaign.levels 964 levels_complete = sum((1 if l.complete else 0) for l in levels) 965 966 # Last level cant be completed; hence the -1; 967 progress = min(1.0, float(levels_complete) / (len(levels) - 1)) 968 p_str = ba.Lstr(resource=self._r + '.campaignProgressText', 969 subs=[('${PROGRESS}', 970 str(int(progress * 100.0)) + '%')]) 971 except Exception: 972 p_str = '?' 973 ba.print_exception('Error calculating co-op campaign progress.') 974 ba.textwidget(edit=self._campaign_progress_text, text=p_str) 975 976 def _refresh_tickets_text(self) -> None: 977 if self._tickets_text is None: 978 return 979 try: 980 tc_str = str(_ba.get_v1_account_ticket_count()) 981 except Exception: 982 ba.print_exception() 983 tc_str = '-' 984 ba.textwidget(edit=self._tickets_text, 985 text=ba.Lstr(resource=self._r + '.ticketsText', 986 subs=[('${COUNT}', tc_str)])) 987 988 def _refresh_account_name_text(self) -> None: 989 if self._account_name_text is None: 990 return 991 try: 992 name_str = _ba.get_v1_account_display_string() 993 except Exception: 994 ba.print_exception() 995 name_str = '??' 996 ba.textwidget(edit=self._account_name_text, text=name_str) 997 998 def _refresh_achievements(self) -> None: 999 if (self._achievements_text is None 1000 and self._achievements_button is None): 1001 return 1002 complete = sum(1 if a.complete else 0 for a in ba.app.ach.achievements) 1003 total = len(ba.app.ach.achievements) 1004 txt_final = ba.Lstr(resource=self._r + '.achievementProgressText', 1005 subs=[('${COUNT}', str(complete)), 1006 ('${TOTAL}', str(total))]) 1007 1008 if self._achievements_text is not None: 1009 ba.textwidget(edit=self._achievements_text, text=txt_final) 1010 if self._achievements_button is not None: 1011 ba.buttonwidget(edit=self._achievements_button, label=txt_final) 1012 1013 def _link_accounts_press(self) -> None: 1014 # pylint: disable=cyclic-import 1015 from bastd.ui.account import link 1016 link.AccountLinkWindow(origin_widget=self._link_accounts_button) 1017 1018 def _unlink_accounts_press(self) -> None: 1019 # pylint: disable=cyclic-import 1020 from bastd.ui.account import unlink 1021 if not self._have_unlinkable_accounts(): 1022 ba.playsound(ba.getsound('error')) 1023 return 1024 unlink.AccountUnlinkWindow(origin_widget=self._unlink_accounts_button) 1025 1026 def _player_profiles_press(self) -> None: 1027 # pylint: disable=cyclic-import 1028 from bastd.ui.profile import browser as pbrowser 1029 self._save_state() 1030 ba.containerwidget(edit=self._root_widget, transition='out_left') 1031 pbrowser.ProfileBrowserWindow( 1032 origin_widget=self._player_profiles_button) 1033 1034 def _cancel_v2_sign_in_press(self) -> None: 1035 # Just say we don't wanna be signed in anymore. 1036 ba.app.accounts_v2.set_primary_credentials(None) 1037 1038 # Speed UI updates along. 1039 ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) 1040 1041 def _sign_out_press(self) -> None: 1042 1043 if ba.app.accounts_v2.have_primary_credentials(): 1044 ba.app.accounts_v2.set_primary_credentials(None) 1045 else: 1046 _ba.sign_out_v1() 1047 1048 cfg = ba.app.config 1049 1050 # Also take note that its our *explicit* intention to not be 1051 # signed in at this point (affects v1 accounts). 1052 cfg['Auto Account State'] = 'signed_out' 1053 cfg.commit() 1054 ba.buttonwidget(edit=self._sign_out_button, 1055 label=ba.Lstr(resource=self._r + '.signingOutText')) 1056 1057 # Speed UI updates along. 1058 ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) 1059 1060 def _sign_in_press(self, 1061 account_type: str, 1062 show_test_warning: bool = True) -> None: 1063 del show_test_warning # unused 1064 _ba.sign_in_v1(account_type) 1065 1066 # Make note of the type account we're *wanting* to be signed in with. 1067 cfg = ba.app.config 1068 cfg['Auto Account State'] = account_type 1069 cfg.commit() 1070 self._needs_refresh = True 1071 ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) 1072 1073 def _v2_sign_in_press(self) -> None: 1074 # pylint: disable=cyclic-import 1075 from bastd.ui.account.v2 import V2SignInWindow 1076 assert self._sign_in_v2_button is not None 1077 V2SignInWindow(origin_widget=self._sign_in_v2_button) 1078 1079 def _reset_progress(self) -> None: 1080 try: 1081 from ba.internal import getcampaign 1082 # FIXME: This would need to happen server-side these days. 1083 if self._can_reset_achievements: 1084 ba.app.config['Achievements'] = {} 1085 _ba.reset_achievements() 1086 campaign = getcampaign('Default') 1087 campaign.reset() # also writes the config.. 1088 campaign = getcampaign('Challenges') 1089 campaign.reset() # also writes the config.. 1090 except Exception: 1091 ba.print_exception('Error resetting co-op campaign progress.') 1092 1093 ba.playsound(ba.getsound('shieldDown')) 1094 self._refresh() 1095 1096 def _back(self) -> None: 1097 # pylint: disable=cyclic-import 1098 from bastd.ui.mainmenu import MainMenuWindow 1099 self._save_state() 1100 ba.containerwidget(edit=self._root_widget, 1101 transition=self._transition_out) 1102 1103 if not self._modal: 1104 ba.app.ui.set_main_menu_window( 1105 MainMenuWindow(transition='in_left').get_root_widget()) 1106 1107 def _save_state(self) -> None: 1108 try: 1109 sel = self._root_widget.get_selected_child() 1110 if sel == self._back_button: 1111 sel_name = 'Back' 1112 elif sel == self._scrollwidget: 1113 sel_name = 'Scroll' 1114 else: 1115 raise ValueError('unrecognized selection') 1116 ba.app.ui.window_states[type(self)] = sel_name 1117 except Exception: 1118 ba.print_exception(f'Error saving state for {self}.') 1119 1120 def _restore_state(self) -> None: 1121 try: 1122 sel_name = ba.app.ui.window_states.get(type(self)) 1123 if sel_name == 'Back': 1124 sel = self._back_button 1125 elif sel_name == 'Scroll': 1126 sel = self._scrollwidget 1127 else: 1128 sel = self._back_button 1129 ba.containerwidget(edit=self._root_widget, selected_child=sel) 1130 except Exception: 1131 ba.print_exception(f'Error restoring state for {self}.')
class
AccountSettingsWindow(ba.ui.Window):
19class AccountSettingsWindow(ba.Window): 20 """Window for account related functionality.""" 21 22 def __init__(self, 23 transition: str = 'in_right', 24 modal: bool = False, 25 origin_widget: ba.Widget | None = None, 26 close_once_signed_in: bool = False): 27 # pylint: disable=too-many-statements 28 29 self._sign_in_game_circle_button: ba.Widget | None = None 30 self._sign_in_v2_button: ba.Widget | None = None 31 self._sign_in_device_button: ba.Widget | None = None 32 33 self._close_once_signed_in = close_once_signed_in 34 ba.set_analytics_screen('Account Window') 35 36 # If they provided an origin-widget, scale up from that. 37 scale_origin: tuple[float, float] | None 38 if origin_widget is not None: 39 self._transition_out = 'out_scale' 40 scale_origin = origin_widget.get_screen_space_center() 41 transition = 'in_scale' 42 else: 43 self._transition_out = 'out_right' 44 scale_origin = None 45 46 self._r = 'accountSettingsWindow' 47 self._modal = modal 48 self._needs_refresh = False 49 self._signed_in = (_ba.get_v1_account_state() == 'signed_in') 50 self._account_state_num = _ba.get_v1_account_state_num() 51 self._show_linked = (self._signed_in 52 and _ba.get_v1_account_misc_read_val( 53 'allowAccountLinking2', False)) 54 self._check_sign_in_timer = ba.Timer(1.0, 55 ba.WeakCall(self._update), 56 timetype=ba.TimeType.REAL, 57 repeat=True) 58 59 # Currently we can only reset achievements on game-center. 60 account_type: str | None 61 if self._signed_in: 62 account_type = _ba.get_v1_account_type() 63 else: 64 account_type = None 65 self._can_reset_achievements = (account_type == 'Game Center') 66 67 app = ba.app 68 uiscale = app.ui.uiscale 69 70 self._width = 760 if uiscale is ba.UIScale.SMALL else 660 71 x_offs = 50 if uiscale is ba.UIScale.SMALL else 0 72 self._height = (390 if uiscale is ba.UIScale.SMALL else 73 430 if uiscale is ba.UIScale.MEDIUM else 490) 74 75 self._sign_in_button = None 76 self._sign_in_text = None 77 78 self._scroll_width = self._width - (100 + x_offs * 2) 79 self._scroll_height = self._height - 120 80 self._sub_width = self._scroll_width - 20 81 82 # Determine which sign-in/sign-out buttons we should show. 83 self._show_sign_in_buttons: list[str] = [] 84 85 if app.platform == 'android' and app.subplatform == 'google': 86 self._show_sign_in_buttons.append('Google Play') 87 88 elif app.platform == 'android' and app.subplatform == 'amazon': 89 self._show_sign_in_buttons.append('Game Circle') 90 91 # Local accounts are generally always available with a few key 92 # exceptions. 93 self._show_sign_in_buttons.append('Local') 94 95 # Ditto with shiny new V2 ones. 96 if bool(True): 97 self._show_sign_in_buttons.append('V2') 98 99 top_extra = 15 if uiscale is ba.UIScale.SMALL else 0 100 super().__init__(root_widget=ba.containerwidget( 101 size=(self._width, self._height + top_extra), 102 transition=transition, 103 toolbar_visibility='menu_minimal', 104 scale_origin_stack_offset=scale_origin, 105 scale=(2.09 if uiscale is ba.UIScale.SMALL else 106 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), 107 stack_offset=(0, -19) if uiscale is ba.UIScale.SMALL else (0, 0))) 108 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: 109 self._back_button = None 110 ba.containerwidget(edit=self._root_widget, 111 on_cancel_call=self._back) 112 else: 113 self._back_button = btn = ba.buttonwidget( 114 parent=self._root_widget, 115 position=(51 + x_offs, self._height - 62), 116 size=(120, 60), 117 scale=0.8, 118 text_scale=1.2, 119 autoselect=True, 120 label=ba.Lstr( 121 resource='doneText' if self._modal else 'backText'), 122 button_type='regular' if self._modal else 'back', 123 on_activate_call=self._back) 124 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 125 if not self._modal: 126 ba.buttonwidget(edit=btn, 127 button_type='backSmall', 128 size=(60, 56), 129 label=ba.charstr(ba.SpecialChar.BACK)) 130 131 ba.textwidget(parent=self._root_widget, 132 position=(self._width * 0.5, self._height - 41), 133 size=(0, 0), 134 text=ba.Lstr(resource=self._r + '.titleText'), 135 color=ba.app.ui.title_color, 136 maxwidth=self._width - 340, 137 h_align='center', 138 v_align='center') 139 140 self._scrollwidget = ba.scrollwidget( 141 parent=self._root_widget, 142 highlight=False, 143 position=((self._width - self._scroll_width) * 0.5, 144 self._height - 65 - self._scroll_height), 145 size=(self._scroll_width, self._scroll_height), 146 claims_left_right=True, 147 claims_tab=True, 148 selection_loops_to_parent=True) 149 self._subcontainer: ba.Widget | None = None 150 self._refresh() 151 self._restore_state() 152 153 def _update(self) -> None: 154 155 # If they want us to close once we're signed in, do so. 156 if self._close_once_signed_in and self._signed_in: 157 self._back() 158 return 159 160 # Hmm should update this to use get_account_state_num. 161 # Theoretically if we switch from one signed-in account to another 162 # in the background this would break. 163 account_state_num = _ba.get_v1_account_state_num() 164 account_state = _ba.get_v1_account_state() 165 166 show_linked = (self._signed_in and _ba.get_v1_account_misc_read_val( 167 'allowAccountLinking2', False)) 168 169 if (account_state_num != self._account_state_num 170 or self._show_linked != show_linked or self._needs_refresh): 171 self._show_linked = show_linked 172 self._account_state_num = account_state_num 173 self._signed_in = (account_state == 'signed_in') 174 self._refresh() 175 176 # Go ahead and refresh some individual things 177 # that may change under us. 178 self._update_linked_accounts_text() 179 self._update_unlink_accounts_button() 180 self._refresh_campaign_progress_text() 181 self._refresh_achievements() 182 self._refresh_tickets_text() 183 self._refresh_account_name_text() 184 185 def _get_sign_in_text(self) -> ba.Lstr: 186 return ba.Lstr(resource=self._r + '.signInText') 187 188 def _refresh(self) -> None: 189 # pylint: disable=too-many-statements 190 # pylint: disable=too-many-branches 191 # pylint: disable=too-many-locals 192 # pylint: disable=cyclic-import 193 from bastd.ui import confirm 194 195 account_state = _ba.get_v1_account_state() 196 account_type = (_ba.get_v1_account_type() 197 if account_state == 'signed_in' else 'unknown') 198 199 is_google = account_type == 'Google Play' 200 201 show_local_signed_in_as = False 202 local_signed_in_as_space = 50.0 203 204 show_signed_in_as = self._signed_in 205 signed_in_as_space = 95.0 206 207 show_sign_in_benefits = not self._signed_in 208 sign_in_benefits_space = 80.0 209 210 show_signing_in_text = account_state == 'signing_in' 211 signing_in_text_space = 80.0 212 213 show_google_play_sign_in_button = (account_state == 'signed_out' 214 and 'Google Play' 215 in self._show_sign_in_buttons) 216 show_game_circle_sign_in_button = (account_state == 'signed_out' 217 and 'Game Circle' 218 in self._show_sign_in_buttons) 219 show_device_sign_in_button = (account_state == 'signed_out' and 'Local' 220 in self._show_sign_in_buttons) 221 show_v2_sign_in_button = (account_state == 'signed_out' 222 and 'V2' in self._show_sign_in_buttons) 223 sign_in_button_space = 70.0 224 225 show_game_service_button = (self._signed_in and account_type 226 in ['Game Center', 'Game Circle']) 227 game_service_button_space = 60.0 228 229 show_linked_accounts_text = (self._signed_in 230 and _ba.get_v1_account_misc_read_val( 231 'allowAccountLinking2', False)) 232 linked_accounts_text_space = 60.0 233 234 show_achievements_button = ( 235 self._signed_in 236 and account_type in ('Google Play', 'Alibaba', 'Local', 'OUYA')) 237 achievements_button_space = 60.0 238 239 show_achievements_text = (self._signed_in 240 and not show_achievements_button) 241 achievements_text_space = 27.0 242 243 show_leaderboards_button = (self._signed_in and is_google) 244 leaderboards_button_space = 60.0 245 246 show_campaign_progress = self._signed_in 247 campaign_progress_space = 27.0 248 249 show_tickets = self._signed_in 250 tickets_space = 27.0 251 252 show_reset_progress_button = False 253 reset_progress_button_space = 70.0 254 255 show_player_profiles_button = self._signed_in 256 player_profiles_button_space = 100.0 257 258 show_link_accounts_button = (self._signed_in 259 and _ba.get_v1_account_misc_read_val( 260 'allowAccountLinking2', False)) 261 link_accounts_button_space = 70.0 262 263 show_unlink_accounts_button = show_link_accounts_button 264 unlink_accounts_button_space = 90.0 265 266 show_sign_out_button = (self._signed_in and account_type 267 in ['Local', 'Google Play', 'V2']) 268 sign_out_button_space = 70.0 269 270 show_cancel_v2_sign_in_button = ( 271 account_state == 'signing_in' 272 and ba.app.accounts_v2.have_primary_credentials()) 273 cancel_v2_sign_in_button_space = 70.0 274 275 if self._subcontainer is not None: 276 self._subcontainer.delete() 277 self._sub_height = 60.0 278 if show_local_signed_in_as: 279 self._sub_height += local_signed_in_as_space 280 if show_signed_in_as: 281 self._sub_height += signed_in_as_space 282 if show_signing_in_text: 283 self._sub_height += signing_in_text_space 284 if show_google_play_sign_in_button: 285 self._sub_height += sign_in_button_space 286 if show_game_circle_sign_in_button: 287 self._sub_height += sign_in_button_space 288 if show_device_sign_in_button: 289 self._sub_height += sign_in_button_space 290 if show_v2_sign_in_button: 291 self._sub_height += sign_in_button_space 292 if show_game_service_button: 293 self._sub_height += game_service_button_space 294 if show_linked_accounts_text: 295 self._sub_height += linked_accounts_text_space 296 if show_achievements_text: 297 self._sub_height += achievements_text_space 298 if show_achievements_button: 299 self._sub_height += achievements_button_space 300 if show_leaderboards_button: 301 self._sub_height += leaderboards_button_space 302 if show_campaign_progress: 303 self._sub_height += campaign_progress_space 304 if show_tickets: 305 self._sub_height += tickets_space 306 if show_sign_in_benefits: 307 self._sub_height += sign_in_benefits_space 308 if show_reset_progress_button: 309 self._sub_height += reset_progress_button_space 310 if show_player_profiles_button: 311 self._sub_height += player_profiles_button_space 312 if show_link_accounts_button: 313 self._sub_height += link_accounts_button_space 314 if show_unlink_accounts_button: 315 self._sub_height += unlink_accounts_button_space 316 if show_sign_out_button: 317 self._sub_height += sign_out_button_space 318 if show_cancel_v2_sign_in_button: 319 self._sub_height += cancel_v2_sign_in_button_space 320 self._subcontainer = ba.containerwidget(parent=self._scrollwidget, 321 size=(self._sub_width, 322 self._sub_height), 323 background=False, 324 claims_left_right=True, 325 claims_tab=True, 326 selection_loops_to_parent=True) 327 328 first_selectable = None 329 v = self._sub_height - 10.0 330 331 if show_local_signed_in_as: 332 v -= local_signed_in_as_space * 0.6 333 ba.textwidget( 334 parent=self._subcontainer, 335 position=(self._sub_width * 0.5, v), 336 size=(0, 0), 337 text=ba.Lstr( 338 resource='accountSettingsWindow.deviceSpecificAccountText', 339 subs=[('${NAME}', _ba.get_v1_account_display_string())]), 340 scale=0.7, 341 color=(0.5, 0.5, 0.6), 342 maxwidth=self._sub_width * 0.9, 343 flatness=1.0, 344 h_align='center', 345 v_align='center') 346 v -= local_signed_in_as_space * 0.4 347 348 self._account_name_text: ba.Widget | None 349 if show_signed_in_as: 350 v -= signed_in_as_space * 0.2 351 txt = ba.Lstr( 352 resource='accountSettingsWindow.youAreSignedInAsText', 353 fallback_resource='accountSettingsWindow.youAreLoggedInAsText') 354 ba.textwidget(parent=self._subcontainer, 355 position=(self._sub_width * 0.5, v), 356 size=(0, 0), 357 text=txt, 358 scale=0.9, 359 color=ba.app.ui.title_color, 360 maxwidth=self._sub_width * 0.9, 361 h_align='center', 362 v_align='center') 363 v -= signed_in_as_space * 0.4 364 self._account_name_text = ba.textwidget( 365 parent=self._subcontainer, 366 position=(self._sub_width * 0.5, v), 367 size=(0, 0), 368 scale=1.5, 369 maxwidth=self._sub_width * 0.9, 370 res_scale=1.5, 371 color=(1, 1, 1, 1), 372 h_align='center', 373 v_align='center') 374 self._refresh_account_name_text() 375 v -= signed_in_as_space * 0.4 376 else: 377 self._account_name_text = None 378 379 if self._back_button is None: 380 bbtn = _ba.get_special_widget('back_button') 381 else: 382 bbtn = self._back_button 383 384 if show_sign_in_benefits: 385 v -= sign_in_benefits_space 386 app = ba.app 387 extra: str | ba.Lstr | None 388 if (app.platform in ['mac', 'ios'] 389 and app.subplatform == 'appstore'): 390 extra = ba.Lstr( 391 value='\n${S}', 392 subs=[('${S}', 393 ba.Lstr(resource='signInWithGameCenterText'))]) 394 else: 395 extra = '' 396 397 ba.textwidget(parent=self._subcontainer, 398 position=(self._sub_width * 0.5, 399 v + sign_in_benefits_space * 0.4), 400 size=(0, 0), 401 text=ba.Lstr(value='${A}${B}', 402 subs=[('${A}', 403 ba.Lstr(resource=self._r + 404 '.signInInfoText')), 405 ('${B}', extra)]), 406 max_height=sign_in_benefits_space * 0.9, 407 scale=0.9, 408 color=(0.75, 0.7, 0.8), 409 maxwidth=self._sub_width * 0.8, 410 h_align='center', 411 v_align='center') 412 413 if show_signing_in_text: 414 v -= signing_in_text_space 415 416 ba.textwidget( 417 parent=self._subcontainer, 418 position=(self._sub_width * 0.5, 419 v + signing_in_text_space * 0.5), 420 size=(0, 0), 421 text=ba.Lstr(resource='accountSettingsWindow.signingInText'), 422 scale=0.9, 423 color=(0, 1, 0), 424 maxwidth=self._sub_width * 0.8, 425 h_align='center', 426 v_align='center') 427 428 if show_google_play_sign_in_button: 429 button_width = 350 430 v -= sign_in_button_space 431 self._sign_in_google_play_button = btn = ba.buttonwidget( 432 parent=self._subcontainer, 433 position=((self._sub_width - button_width) * 0.5, v - 20), 434 autoselect=True, 435 size=(button_width, 60), 436 label=ba.Lstr( 437 value='${A}${B}', 438 subs=[('${A}', 439 ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO)), 440 ('${B}', 441 ba.Lstr(resource=self._r + 442 '.signInWithGooglePlayText'))]), 443 on_activate_call=lambda: self._sign_in_press('Google Play')) 444 if first_selectable is None: 445 first_selectable = btn 446 if ba.app.ui.use_toolbars: 447 ba.widget(edit=btn, 448 right_widget=_ba.get_special_widget('party_button')) 449 ba.widget(edit=btn, left_widget=bbtn) 450 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 451 self._sign_in_text = None 452 453 if show_game_circle_sign_in_button: 454 button_width = 350 455 v -= sign_in_button_space 456 self._sign_in_game_circle_button = btn = ba.buttonwidget( 457 parent=self._subcontainer, 458 position=((self._sub_width - button_width) * 0.5, v - 20), 459 autoselect=True, 460 size=(button_width, 60), 461 label=ba.Lstr(value='${A}${B}', 462 subs=[('${A}', 463 ba.charstr( 464 ba.SpecialChar.GAME_CIRCLE_LOGO)), 465 ('${B}', 466 ba.Lstr(resource=self._r + 467 '.signInWithGameCircleText'))]), 468 on_activate_call=lambda: self._sign_in_press('Game Circle')) 469 if first_selectable is None: 470 first_selectable = btn 471 if ba.app.ui.use_toolbars: 472 ba.widget(edit=btn, 473 right_widget=_ba.get_special_widget('party_button')) 474 ba.widget(edit=btn, left_widget=bbtn) 475 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 476 self._sign_in_text = None 477 478 if show_v2_sign_in_button: 479 button_width = 350 480 v -= sign_in_button_space 481 self._sign_in_v2_button = btn = ba.buttonwidget( 482 parent=self._subcontainer, 483 position=((self._sub_width - button_width) * 0.5, v - 20), 484 autoselect=True, 485 size=(button_width, 60), 486 label='', 487 on_activate_call=self._v2_sign_in_press) 488 ba.textwidget( 489 parent=self._subcontainer, 490 draw_controller=btn, 491 h_align='center', 492 v_align='center', 493 size=(0, 0), 494 position=(self._sub_width * 0.5, v + 17), 495 text=ba.Lstr( 496 value='${A}${B}', 497 subs=[('${A}', ba.charstr(ba.SpecialChar.V2_LOGO)), 498 ('${B}', 499 ba.Lstr(resource=self._r + '.signInWithV2Text'))]), 500 maxwidth=button_width * 0.8, 501 color=(0.75, 1.0, 0.7)) 502 ba.textwidget(parent=self._subcontainer, 503 draw_controller=btn, 504 h_align='center', 505 v_align='center', 506 size=(0, 0), 507 position=(self._sub_width * 0.5, v - 4), 508 text=ba.Lstr(resource=self._r + 509 '.signInWithV2InfoText'), 510 flatness=1.0, 511 scale=0.57, 512 maxwidth=button_width * 0.9, 513 color=(0.55, 0.8, 0.5)) 514 if first_selectable is None: 515 first_selectable = btn 516 if ba.app.ui.use_toolbars: 517 ba.widget(edit=btn, 518 right_widget=_ba.get_special_widget('party_button')) 519 ba.widget(edit=btn, left_widget=bbtn) 520 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 521 self._sign_in_text = None 522 523 if show_device_sign_in_button: 524 button_width = 350 525 v -= sign_in_button_space 526 self._sign_in_device_button = btn = ba.buttonwidget( 527 parent=self._subcontainer, 528 position=((self._sub_width - button_width) * 0.5, v - 20), 529 autoselect=True, 530 size=(button_width, 60), 531 label='', 532 on_activate_call=lambda: self._sign_in_press('Local')) 533 ba.textwidget(parent=self._subcontainer, 534 draw_controller=btn, 535 h_align='center', 536 v_align='center', 537 size=(0, 0), 538 position=(self._sub_width * 0.5, v + 17), 539 text=ba.Lstr( 540 value='${A}${B}', 541 subs=[('${A}', 542 ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)), 543 ('${B}', 544 ba.Lstr(resource=self._r + 545 '.signInWithDeviceText'))]), 546 maxwidth=button_width * 0.8, 547 color=(0.75, 1.0, 0.7)) 548 ba.textwidget(parent=self._subcontainer, 549 draw_controller=btn, 550 h_align='center', 551 v_align='center', 552 size=(0, 0), 553 position=(self._sub_width * 0.5, v - 4), 554 text=ba.Lstr(resource=self._r + 555 '.signInWithDeviceInfoText'), 556 flatness=1.0, 557 scale=0.57, 558 maxwidth=button_width * 0.9, 559 color=(0.55, 0.8, 0.5)) 560 if first_selectable is None: 561 first_selectable = btn 562 if ba.app.ui.use_toolbars: 563 ba.widget(edit=btn, 564 right_widget=_ba.get_special_widget('party_button')) 565 ba.widget(edit=btn, left_widget=bbtn) 566 ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) 567 self._sign_in_text = None 568 569 if show_player_profiles_button: 570 button_width = 300 571 v -= player_profiles_button_space 572 self._player_profiles_button = btn = ba.buttonwidget( 573 parent=self._subcontainer, 574 position=((self._sub_width - button_width) * 0.5, v + 30), 575 autoselect=True, 576 size=(button_width, 60), 577 label=ba.Lstr(resource='playerProfilesWindow.titleText'), 578 color=(0.55, 0.5, 0.6), 579 icon=ba.gettexture('cuteSpaz'), 580 textcolor=(0.75, 0.7, 0.8), 581 on_activate_call=self._player_profiles_press) 582 if first_selectable is None: 583 first_selectable = btn 584 if ba.app.ui.use_toolbars: 585 ba.widget(edit=btn, 586 right_widget=_ba.get_special_widget('party_button')) 587 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0) 588 589 # the button to go to OS-Specific leaderboards/high-score-lists/etc. 590 if show_game_service_button: 591 button_width = 300 592 v -= game_service_button_space * 0.85 593 account_type = _ba.get_v1_account_type() 594 if account_type == 'Game Center': 595 account_type_name = ba.Lstr(resource='gameCenterText') 596 elif account_type == 'Game Circle': 597 account_type_name = ba.Lstr(resource='gameCircleText') 598 else: 599 raise ValueError("unknown account type: '" + 600 str(account_type) + "'") 601 self._game_service_button = btn = ba.buttonwidget( 602 parent=self._subcontainer, 603 position=((self._sub_width - button_width) * 0.5, v), 604 color=(0.55, 0.5, 0.6), 605 textcolor=(0.75, 0.7, 0.8), 606 autoselect=True, 607 on_activate_call=_ba.show_online_score_ui, 608 size=(button_width, 50), 609 label=account_type_name) 610 if first_selectable is None: 611 first_selectable = btn 612 if ba.app.ui.use_toolbars: 613 ba.widget(edit=btn, 614 right_widget=_ba.get_special_widget('party_button')) 615 ba.widget(edit=btn, left_widget=bbtn) 616 v -= game_service_button_space * 0.15 617 else: 618 self.game_service_button = None 619 620 self._achievements_text: ba.Widget | None 621 if show_achievements_text: 622 v -= achievements_text_space * 0.5 623 self._achievements_text = ba.textwidget( 624 parent=self._subcontainer, 625 position=(self._sub_width * 0.5, v), 626 size=(0, 0), 627 scale=0.9, 628 color=(0.75, 0.7, 0.8), 629 maxwidth=self._sub_width * 0.8, 630 h_align='center', 631 v_align='center') 632 v -= achievements_text_space * 0.5 633 else: 634 self._achievements_text = None 635 636 self._achievements_button: ba.Widget | None 637 if show_achievements_button: 638 button_width = 300 639 v -= achievements_button_space * 0.85 640 self._achievements_button = btn = ba.buttonwidget( 641 parent=self._subcontainer, 642 position=((self._sub_width - button_width) * 0.5, v), 643 color=(0.55, 0.5, 0.6), 644 textcolor=(0.75, 0.7, 0.8), 645 autoselect=True, 646 icon=ba.gettexture('googlePlayAchievementsIcon' 647 if is_google else 'achievementsIcon'), 648 icon_color=(0.8, 0.95, 0.7) if is_google else (0.85, 0.8, 0.9), 649 on_activate_call=self._on_achievements_press, 650 size=(button_width, 50), 651 label='') 652 if first_selectable is None: 653 first_selectable = btn 654 if ba.app.ui.use_toolbars: 655 ba.widget(edit=btn, 656 right_widget=_ba.get_special_widget('party_button')) 657 ba.widget(edit=btn, left_widget=bbtn) 658 v -= achievements_button_space * 0.15 659 else: 660 self._achievements_button = None 661 662 if show_achievements_text or show_achievements_button: 663 self._refresh_achievements() 664 665 self._leaderboards_button: ba.Widget | None 666 if show_leaderboards_button: 667 button_width = 300 668 v -= leaderboards_button_space * 0.85 669 self._leaderboards_button = btn = ba.buttonwidget( 670 parent=self._subcontainer, 671 position=((self._sub_width - button_width) * 0.5, v), 672 color=(0.55, 0.5, 0.6), 673 textcolor=(0.75, 0.7, 0.8), 674 autoselect=True, 675 icon=ba.gettexture('googlePlayLeaderboardsIcon'), 676 icon_color=(0.8, 0.95, 0.7), 677 on_activate_call=self._on_leaderboards_press, 678 size=(button_width, 50), 679 label=ba.Lstr(resource='leaderboardsText')) 680 if first_selectable is None: 681 first_selectable = btn 682 if ba.app.ui.use_toolbars: 683 ba.widget(edit=btn, 684 right_widget=_ba.get_special_widget('party_button')) 685 ba.widget(edit=btn, left_widget=bbtn) 686 v -= leaderboards_button_space * 0.15 687 else: 688 self._leaderboards_button = None 689 690 self._campaign_progress_text: ba.Widget | None 691 if show_campaign_progress: 692 v -= campaign_progress_space * 0.5 693 self._campaign_progress_text = ba.textwidget( 694 parent=self._subcontainer, 695 position=(self._sub_width * 0.5, v), 696 size=(0, 0), 697 scale=0.9, 698 color=(0.75, 0.7, 0.8), 699 maxwidth=self._sub_width * 0.8, 700 h_align='center', 701 v_align='center') 702 v -= campaign_progress_space * 0.5 703 self._refresh_campaign_progress_text() 704 else: 705 self._campaign_progress_text = None 706 707 self._tickets_text: ba.Widget | None 708 if show_tickets: 709 v -= tickets_space * 0.5 710 self._tickets_text = ba.textwidget(parent=self._subcontainer, 711 position=(self._sub_width * 0.5, 712 v), 713 size=(0, 0), 714 scale=0.9, 715 color=(0.75, 0.7, 0.8), 716 maxwidth=self._sub_width * 0.8, 717 flatness=1.0, 718 h_align='center', 719 v_align='center') 720 v -= tickets_space * 0.5 721 self._refresh_tickets_text() 722 723 else: 724 self._tickets_text = None 725 726 # bit of spacing before the reset/sign-out section 727 v -= 5 728 729 button_width = 250 730 if show_reset_progress_button: 731 confirm_text = (ba.Lstr(resource=self._r + 732 '.resetProgressConfirmText') 733 if self._can_reset_achievements else ba.Lstr( 734 resource=self._r + 735 '.resetProgressConfirmNoAchievementsText')) 736 v -= reset_progress_button_space 737 self._reset_progress_button = btn = ba.buttonwidget( 738 parent=self._subcontainer, 739 position=((self._sub_width - button_width) * 0.5, v), 740 color=(0.55, 0.5, 0.6), 741 textcolor=(0.75, 0.7, 0.8), 742 autoselect=True, 743 size=(button_width, 60), 744 label=ba.Lstr(resource=self._r + '.resetProgressText'), 745 on_activate_call=lambda: confirm.ConfirmWindow( 746 text=confirm_text, 747 width=500, 748 height=200, 749 action=self._reset_progress)) 750 if first_selectable is None: 751 first_selectable = btn 752 if ba.app.ui.use_toolbars: 753 ba.widget(edit=btn, 754 right_widget=_ba.get_special_widget('party_button')) 755 ba.widget(edit=btn, left_widget=bbtn) 756 757 self._linked_accounts_text: ba.Widget | None 758 if show_linked_accounts_text: 759 v -= linked_accounts_text_space * 0.8 760 self._linked_accounts_text = ba.textwidget( 761 parent=self._subcontainer, 762 position=(self._sub_width * 0.5, v), 763 size=(0, 0), 764 scale=0.9, 765 color=(0.75, 0.7, 0.8), 766 maxwidth=self._sub_width * 0.95, 767 h_align='center', 768 v_align='center') 769 v -= linked_accounts_text_space * 0.2 770 self._update_linked_accounts_text() 771 else: 772 self._linked_accounts_text = None 773 774 if show_link_accounts_button: 775 v -= link_accounts_button_space 776 self._link_accounts_button = btn = ba.buttonwidget( 777 parent=self._subcontainer, 778 position=((self._sub_width - button_width) * 0.5, v), 779 autoselect=True, 780 size=(button_width, 60), 781 label='', 782 color=(0.55, 0.5, 0.6), 783 on_activate_call=self._link_accounts_press) 784 ba.textwidget(parent=self._subcontainer, 785 draw_controller=btn, 786 h_align='center', 787 v_align='center', 788 size=(0, 0), 789 position=(self._sub_width * 0.5, v + 17 + 20), 790 text=ba.Lstr(resource=self._r + '.linkAccountsText'), 791 maxwidth=button_width * 0.8, 792 color=(0.75, 0.7, 0.8)) 793 ba.textwidget(parent=self._subcontainer, 794 draw_controller=btn, 795 h_align='center', 796 v_align='center', 797 size=(0, 0), 798 position=(self._sub_width * 0.5, v - 4 + 20), 799 text=ba.Lstr(resource=self._r + 800 '.linkAccountsInfoText'), 801 flatness=1.0, 802 scale=0.5, 803 maxwidth=button_width * 0.8, 804 color=(0.75, 0.7, 0.8)) 805 if first_selectable is None: 806 first_selectable = btn 807 if ba.app.ui.use_toolbars: 808 ba.widget(edit=btn, 809 right_widget=_ba.get_special_widget('party_button')) 810 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 811 812 self._unlink_accounts_button: ba.Widget | None 813 if show_unlink_accounts_button: 814 v -= unlink_accounts_button_space 815 self._unlink_accounts_button = btn = ba.buttonwidget( 816 parent=self._subcontainer, 817 position=((self._sub_width - button_width) * 0.5, v + 25), 818 autoselect=True, 819 size=(button_width, 60), 820 label='', 821 color=(0.55, 0.5, 0.6), 822 on_activate_call=self._unlink_accounts_press) 823 self._unlink_accounts_button_label = ba.textwidget( 824 parent=self._subcontainer, 825 draw_controller=btn, 826 h_align='center', 827 v_align='center', 828 size=(0, 0), 829 position=(self._sub_width * 0.5, v + 55), 830 text=ba.Lstr(resource=self._r + '.unlinkAccountsText'), 831 maxwidth=button_width * 0.8, 832 color=(0.75, 0.7, 0.8)) 833 if first_selectable is None: 834 first_selectable = btn 835 if ba.app.ui.use_toolbars: 836 ba.widget(edit=btn, 837 right_widget=_ba.get_special_widget('party_button')) 838 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) 839 self._update_unlink_accounts_button() 840 else: 841 self._unlink_accounts_button = None 842 843 if show_sign_out_button: 844 v -= sign_out_button_space 845 self._sign_out_button = btn = ba.buttonwidget( 846 parent=self._subcontainer, 847 position=((self._sub_width - button_width) * 0.5, v), 848 size=(button_width, 60), 849 label=ba.Lstr(resource=self._r + '.signOutText'), 850 color=(0.55, 0.5, 0.6), 851 textcolor=(0.75, 0.7, 0.8), 852 autoselect=True, 853 on_activate_call=self._sign_out_press) 854 if first_selectable is None: 855 first_selectable = btn 856 if ba.app.ui.use_toolbars: 857 ba.widget(edit=btn, 858 right_widget=_ba.get_special_widget('party_button')) 859 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 860 861 if show_cancel_v2_sign_in_button: 862 v -= cancel_v2_sign_in_button_space 863 self._cancel_v2_sign_in_button = btn = ba.buttonwidget( 864 parent=self._subcontainer, 865 position=((self._sub_width - button_width) * 0.5, v), 866 size=(button_width, 60), 867 label=ba.Lstr(resource='cancelText'), 868 color=(0.55, 0.5, 0.6), 869 textcolor=(0.75, 0.7, 0.8), 870 autoselect=True, 871 on_activate_call=self._cancel_v2_sign_in_press) 872 if first_selectable is None: 873 first_selectable = btn 874 if ba.app.ui.use_toolbars: 875 ba.widget(edit=btn, 876 right_widget=_ba.get_special_widget('party_button')) 877 ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) 878 879 # Whatever the topmost selectable thing is, we want it to scroll all 880 # the way up when we select it. 881 if first_selectable is not None: 882 ba.widget(edit=first_selectable, 883 up_widget=bbtn, 884 show_buffer_top=400) 885 # (this should re-scroll us to the top..) 886 ba.containerwidget(edit=self._subcontainer, 887 visible_child=first_selectable) 888 self._needs_refresh = False 889 890 def _on_achievements_press(self) -> None: 891 # pylint: disable=cyclic-import 892 from bastd.ui import achievements 893 account_state = _ba.get_v1_account_state() 894 account_type = (_ba.get_v1_account_type() 895 if account_state == 'signed_in' else 'unknown') 896 # for google play we use the built-in UI; otherwise pop up our own 897 if account_type == 'Google Play': 898 ba.timer(0.15, 899 ba.Call(_ba.show_online_score_ui, 'achievements'), 900 timetype=ba.TimeType.REAL) 901 elif account_type != 'unknown': 902 assert self._achievements_button is not None 903 achievements.AchievementsWindow( 904 position=self._achievements_button.get_screen_space_center()) 905 else: 906 print('ERROR: unknown account type in on_achievements_press:', 907 account_type) 908 909 def _on_leaderboards_press(self) -> None: 910 ba.timer(0.15, 911 ba.Call(_ba.show_online_score_ui, 'leaderboards'), 912 timetype=ba.TimeType.REAL) 913 914 def _have_unlinkable_accounts(self) -> bool: 915 # if this is not present, we haven't had contact from the server so 916 # let's not proceed.. 917 if _ba.get_public_login_id() is None: 918 return False 919 accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', []) 920 return len(accounts) > 1 921 922 def _update_unlink_accounts_button(self) -> None: 923 if self._unlink_accounts_button is None: 924 return 925 if self._have_unlinkable_accounts(): 926 clr = (0.75, 0.7, 0.8, 1.0) 927 else: 928 clr = (1.0, 1.0, 1.0, 0.25) 929 ba.textwidget(edit=self._unlink_accounts_button_label, color=clr) 930 931 def _update_linked_accounts_text(self) -> None: 932 if self._linked_accounts_text is None: 933 return 934 935 # if this is not present, we haven't had contact from the server so 936 # let's not proceed.. 937 if _ba.get_public_login_id() is None: 938 num = int(time.time()) % 4 939 accounts_str = num * '.' + (4 - num) * ' ' 940 else: 941 accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', []) 942 # our_account = _bs.get_v1_account_display_string() 943 # accounts = [a for a in accounts if a != our_account] 944 # accounts_str = u', '.join(accounts) if accounts else 945 # ba.Lstr(translate=('settingNames', 'None')) 946 # UPDATE - we now just print the number here; not the actual 947 # accounts 948 # (they can see that in the unlink section if they're curious) 949 accounts_str = str(max(0, len(accounts) - 1)) 950 ba.textwidget(edit=self._linked_accounts_text, 951 text=ba.Lstr(value='${L} ${A}', 952 subs=[('${L}', 953 ba.Lstr(resource=self._r + 954 '.linkedAccountsText')), 955 ('${A}', accounts_str)])) 956 957 def _refresh_campaign_progress_text(self) -> None: 958 from ba.internal import getcampaign 959 if self._campaign_progress_text is None: 960 return 961 p_str: str | ba.Lstr 962 try: 963 campaign = getcampaign('Default') 964 levels = campaign.levels 965 levels_complete = sum((1 if l.complete else 0) for l in levels) 966 967 # Last level cant be completed; hence the -1; 968 progress = min(1.0, float(levels_complete) / (len(levels) - 1)) 969 p_str = ba.Lstr(resource=self._r + '.campaignProgressText', 970 subs=[('${PROGRESS}', 971 str(int(progress * 100.0)) + '%')]) 972 except Exception: 973 p_str = '?' 974 ba.print_exception('Error calculating co-op campaign progress.') 975 ba.textwidget(edit=self._campaign_progress_text, text=p_str) 976 977 def _refresh_tickets_text(self) -> None: 978 if self._tickets_text is None: 979 return 980 try: 981 tc_str = str(_ba.get_v1_account_ticket_count()) 982 except Exception: 983 ba.print_exception() 984 tc_str = '-' 985 ba.textwidget(edit=self._tickets_text, 986 text=ba.Lstr(resource=self._r + '.ticketsText', 987 subs=[('${COUNT}', tc_str)])) 988 989 def _refresh_account_name_text(self) -> None: 990 if self._account_name_text is None: 991 return 992 try: 993 name_str = _ba.get_v1_account_display_string() 994 except Exception: 995 ba.print_exception() 996 name_str = '??' 997 ba.textwidget(edit=self._account_name_text, text=name_str) 998 999 def _refresh_achievements(self) -> None: 1000 if (self._achievements_text is None 1001 and self._achievements_button is None): 1002 return 1003 complete = sum(1 if a.complete else 0 for a in ba.app.ach.achievements) 1004 total = len(ba.app.ach.achievements) 1005 txt_final = ba.Lstr(resource=self._r + '.achievementProgressText', 1006 subs=[('${COUNT}', str(complete)), 1007 ('${TOTAL}', str(total))]) 1008 1009 if self._achievements_text is not None: 1010 ba.textwidget(edit=self._achievements_text, text=txt_final) 1011 if self._achievements_button is not None: 1012 ba.buttonwidget(edit=self._achievements_button, label=txt_final) 1013 1014 def _link_accounts_press(self) -> None: 1015 # pylint: disable=cyclic-import 1016 from bastd.ui.account import link 1017 link.AccountLinkWindow(origin_widget=self._link_accounts_button) 1018 1019 def _unlink_accounts_press(self) -> None: 1020 # pylint: disable=cyclic-import 1021 from bastd.ui.account import unlink 1022 if not self._have_unlinkable_accounts(): 1023 ba.playsound(ba.getsound('error')) 1024 return 1025 unlink.AccountUnlinkWindow(origin_widget=self._unlink_accounts_button) 1026 1027 def _player_profiles_press(self) -> None: 1028 # pylint: disable=cyclic-import 1029 from bastd.ui.profile import browser as pbrowser 1030 self._save_state() 1031 ba.containerwidget(edit=self._root_widget, transition='out_left') 1032 pbrowser.ProfileBrowserWindow( 1033 origin_widget=self._player_profiles_button) 1034 1035 def _cancel_v2_sign_in_press(self) -> None: 1036 # Just say we don't wanna be signed in anymore. 1037 ba.app.accounts_v2.set_primary_credentials(None) 1038 1039 # Speed UI updates along. 1040 ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) 1041 1042 def _sign_out_press(self) -> None: 1043 1044 if ba.app.accounts_v2.have_primary_credentials(): 1045 ba.app.accounts_v2.set_primary_credentials(None) 1046 else: 1047 _ba.sign_out_v1() 1048 1049 cfg = ba.app.config 1050 1051 # Also take note that its our *explicit* intention to not be 1052 # signed in at this point (affects v1 accounts). 1053 cfg['Auto Account State'] = 'signed_out' 1054 cfg.commit() 1055 ba.buttonwidget(edit=self._sign_out_button, 1056 label=ba.Lstr(resource=self._r + '.signingOutText')) 1057 1058 # Speed UI updates along. 1059 ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) 1060 1061 def _sign_in_press(self, 1062 account_type: str, 1063 show_test_warning: bool = True) -> None: 1064 del show_test_warning # unused 1065 _ba.sign_in_v1(account_type) 1066 1067 # Make note of the type account we're *wanting* to be signed in with. 1068 cfg = ba.app.config 1069 cfg['Auto Account State'] = account_type 1070 cfg.commit() 1071 self._needs_refresh = True 1072 ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) 1073 1074 def _v2_sign_in_press(self) -> None: 1075 # pylint: disable=cyclic-import 1076 from bastd.ui.account.v2 import V2SignInWindow 1077 assert self._sign_in_v2_button is not None 1078 V2SignInWindow(origin_widget=self._sign_in_v2_button) 1079 1080 def _reset_progress(self) -> None: 1081 try: 1082 from ba.internal import getcampaign 1083 # FIXME: This would need to happen server-side these days. 1084 if self._can_reset_achievements: 1085 ba.app.config['Achievements'] = {} 1086 _ba.reset_achievements() 1087 campaign = getcampaign('Default') 1088 campaign.reset() # also writes the config.. 1089 campaign = getcampaign('Challenges') 1090 campaign.reset() # also writes the config.. 1091 except Exception: 1092 ba.print_exception('Error resetting co-op campaign progress.') 1093 1094 ba.playsound(ba.getsound('shieldDown')) 1095 self._refresh() 1096 1097 def _back(self) -> None: 1098 # pylint: disable=cyclic-import 1099 from bastd.ui.mainmenu import MainMenuWindow 1100 self._save_state() 1101 ba.containerwidget(edit=self._root_widget, 1102 transition=self._transition_out) 1103 1104 if not self._modal: 1105 ba.app.ui.set_main_menu_window( 1106 MainMenuWindow(transition='in_left').get_root_widget()) 1107 1108 def _save_state(self) -> None: 1109 try: 1110 sel = self._root_widget.get_selected_child() 1111 if sel == self._back_button: 1112 sel_name = 'Back' 1113 elif sel == self._scrollwidget: 1114 sel_name = 'Scroll' 1115 else: 1116 raise ValueError('unrecognized selection') 1117 ba.app.ui.window_states[type(self)] = sel_name 1118 except Exception: 1119 ba.print_exception(f'Error saving state for {self}.') 1120 1121 def _restore_state(self) -> None: 1122 try: 1123 sel_name = ba.app.ui.window_states.get(type(self)) 1124 if sel_name == 'Back': 1125 sel = self._back_button 1126 elif sel_name == 'Scroll': 1127 sel = self._scrollwidget 1128 else: 1129 sel = self._back_button 1130 ba.containerwidget(edit=self._root_widget, selected_child=sel) 1131 except Exception: 1132 ba.print_exception(f'Error restoring state for {self}.')
Window for account related functionality.
AccountSettingsWindow( transition: str = 'in_right', modal: bool = False, origin_widget: _ba.Widget | None = None, close_once_signed_in: bool = False)
22 def __init__(self, 23 transition: str = 'in_right', 24 modal: bool = False, 25 origin_widget: ba.Widget | None = None, 26 close_once_signed_in: bool = False): 27 # pylint: disable=too-many-statements 28 29 self._sign_in_game_circle_button: ba.Widget | None = None 30 self._sign_in_v2_button: ba.Widget | None = None 31 self._sign_in_device_button: ba.Widget | None = None 32 33 self._close_once_signed_in = close_once_signed_in 34 ba.set_analytics_screen('Account Window') 35 36 # If they provided an origin-widget, scale up from that. 37 scale_origin: tuple[float, float] | None 38 if origin_widget is not None: 39 self._transition_out = 'out_scale' 40 scale_origin = origin_widget.get_screen_space_center() 41 transition = 'in_scale' 42 else: 43 self._transition_out = 'out_right' 44 scale_origin = None 45 46 self._r = 'accountSettingsWindow' 47 self._modal = modal 48 self._needs_refresh = False 49 self._signed_in = (_ba.get_v1_account_state() == 'signed_in') 50 self._account_state_num = _ba.get_v1_account_state_num() 51 self._show_linked = (self._signed_in 52 and _ba.get_v1_account_misc_read_val( 53 'allowAccountLinking2', False)) 54 self._check_sign_in_timer = ba.Timer(1.0, 55 ba.WeakCall(self._update), 56 timetype=ba.TimeType.REAL, 57 repeat=True) 58 59 # Currently we can only reset achievements on game-center. 60 account_type: str | None 61 if self._signed_in: 62 account_type = _ba.get_v1_account_type() 63 else: 64 account_type = None 65 self._can_reset_achievements = (account_type == 'Game Center') 66 67 app = ba.app 68 uiscale = app.ui.uiscale 69 70 self._width = 760 if uiscale is ba.UIScale.SMALL else 660 71 x_offs = 50 if uiscale is ba.UIScale.SMALL else 0 72 self._height = (390 if uiscale is ba.UIScale.SMALL else 73 430 if uiscale is ba.UIScale.MEDIUM else 490) 74 75 self._sign_in_button = None 76 self._sign_in_text = None 77 78 self._scroll_width = self._width - (100 + x_offs * 2) 79 self._scroll_height = self._height - 120 80 self._sub_width = self._scroll_width - 20 81 82 # Determine which sign-in/sign-out buttons we should show. 83 self._show_sign_in_buttons: list[str] = [] 84 85 if app.platform == 'android' and app.subplatform == 'google': 86 self._show_sign_in_buttons.append('Google Play') 87 88 elif app.platform == 'android' and app.subplatform == 'amazon': 89 self._show_sign_in_buttons.append('Game Circle') 90 91 # Local accounts are generally always available with a few key 92 # exceptions. 93 self._show_sign_in_buttons.append('Local') 94 95 # Ditto with shiny new V2 ones. 96 if bool(True): 97 self._show_sign_in_buttons.append('V2') 98 99 top_extra = 15 if uiscale is ba.UIScale.SMALL else 0 100 super().__init__(root_widget=ba.containerwidget( 101 size=(self._width, self._height + top_extra), 102 transition=transition, 103 toolbar_visibility='menu_minimal', 104 scale_origin_stack_offset=scale_origin, 105 scale=(2.09 if uiscale is ba.UIScale.SMALL else 106 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), 107 stack_offset=(0, -19) if uiscale is ba.UIScale.SMALL else (0, 0))) 108 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: 109 self._back_button = None 110 ba.containerwidget(edit=self._root_widget, 111 on_cancel_call=self._back) 112 else: 113 self._back_button = btn = ba.buttonwidget( 114 parent=self._root_widget, 115 position=(51 + x_offs, self._height - 62), 116 size=(120, 60), 117 scale=0.8, 118 text_scale=1.2, 119 autoselect=True, 120 label=ba.Lstr( 121 resource='doneText' if self._modal else 'backText'), 122 button_type='regular' if self._modal else 'back', 123 on_activate_call=self._back) 124 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 125 if not self._modal: 126 ba.buttonwidget(edit=btn, 127 button_type='backSmall', 128 size=(60, 56), 129 label=ba.charstr(ba.SpecialChar.BACK)) 130 131 ba.textwidget(parent=self._root_widget, 132 position=(self._width * 0.5, self._height - 41), 133 size=(0, 0), 134 text=ba.Lstr(resource=self._r + '.titleText'), 135 color=ba.app.ui.title_color, 136 maxwidth=self._width - 340, 137 h_align='center', 138 v_align='center') 139 140 self._scrollwidget = ba.scrollwidget( 141 parent=self._root_widget, 142 highlight=False, 143 position=((self._width - self._scroll_width) * 0.5, 144 self._height - 65 - self._scroll_height), 145 size=(self._scroll_width, self._scroll_height), 146 claims_left_right=True, 147 claims_tab=True, 148 selection_loops_to_parent=True) 149 self._subcontainer: ba.Widget | None = None 150 self._refresh() 151 self._restore_state()
Inherited Members
- ba.ui.Window
- get_root_widget