bauiv1lib.league.rankwindow

UI related to league rank.

   1# Released under the MIT License. See LICENSE for details.
   2#
   3"""UI related to league rank."""
   4# pylint: disable=too-many-lines
   5
   6from __future__ import annotations
   7
   8import copy
   9import logging
  10from typing import TYPE_CHECKING, override
  11
  12from bauiv1lib.popup import PopupMenu
  13import bauiv1 as bui
  14
  15if TYPE_CHECKING:
  16    from typing import Any
  17
  18
  19class LeagueRankWindow(bui.MainWindow):
  20    """Window for showing league rank."""
  21
  22    def __init__(
  23        self,
  24        transition: str | None = 'in_right',
  25        origin_widget: bui.Widget | None = None,
  26    ):
  27        # pylint: disable=too-many-statements
  28        plus = bui.app.plus
  29        assert plus is not None
  30
  31        bui.set_analytics_screen('League Rank Window')
  32
  33        self._league_rank_data: dict[str, Any] | None = None
  34
  35        self._power_ranking_achievements_button: bui.Widget | None = None
  36        self._up_to_date_bonus_button: bui.Widget | None = None
  37        self._power_ranking_trophies_button: bui.Widget | None = None
  38        self._league_title_text: bui.Widget | None = None
  39        self._league_text: bui.Widget | None = None
  40        self._league_number_text: bui.Widget | None = None
  41        self._your_power_ranking_text: bui.Widget | None = None
  42        self._loading_spinner: bui.Widget | None = None
  43        self._season_ends_text: bui.Widget | None = None
  44        self._power_ranking_rank_text: bui.Widget | None = None
  45        self._to_ranked_text: bui.Widget | None = None
  46        self._trophy_counts_reset_text: bui.Widget | None = None
  47
  48        assert bui.app.classic is not None
  49        uiscale = bui.app.ui_v1.uiscale
  50        self._width = 1500 if uiscale is bui.UIScale.SMALL else 1120
  51        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
  52        self._height = (
  53            1000
  54            if uiscale is bui.UIScale.SMALL
  55            else 710 if uiscale is bui.UIScale.MEDIUM else 800
  56        )
  57        self._r = 'coopSelectWindow'
  58        self._rdict = bui.app.lang.get_resource(self._r)
  59        # top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
  60
  61        # self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0
  62        self._xoffs = 40
  63
  64        self._league_url_arg = ''
  65
  66        self._is_current_season = False
  67        self._can_do_more_button = True
  68
  69        # Do some fancy math to fill all available screen area up to the
  70        # size of our backing container. This lets us fit to the exact
  71        # screen shape at small ui scale.
  72        screensize = bui.get_virtual_screen_size()
  73        scale = (
  74            1.3
  75            if uiscale is bui.UIScale.SMALL
  76            else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8
  77        )
  78        # Calc screen size in our local container space and clamp to a
  79        # bit smaller than our container size.
  80        target_width = min(self._width - 130, screensize[0] / scale)
  81        target_height = min(self._height - 130, screensize[1] / scale)
  82
  83        # To get top/left coords, go to the center of our window and
  84        # offset by half the width/height of our target area.
  85        yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
  86
  87        self._scroll_width = target_width
  88        self._scroll_height = target_height - 35
  89        scroll_bottom = yoffs - 80 - self._scroll_height
  90
  91        super().__init__(
  92            root_widget=bui.containerwidget(
  93                size=(self._width, self._height),
  94                stack_offset=(
  95                    (0, 0)
  96                    if uiscale is bui.UIScale.SMALL
  97                    else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0)
  98                ),
  99                scale=scale,
 100                toolbar_visibility=(
 101                    'menu_minimal'
 102                    if uiscale is bui.UIScale.SMALL
 103                    else 'menu_full'
 104                ),
 105            ),
 106            transition=transition,
 107            origin_widget=origin_widget,
 108            # We're affected by screen size only at small ui-scale.
 109            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 110        )
 111
 112        if uiscale is bui.UIScale.SMALL:
 113            self._back_button = bui.get_special_widget('back_button')
 114            bui.containerwidget(
 115                edit=self._root_widget, on_cancel_call=self.main_window_back
 116            )
 117        else:
 118            self._back_button = bui.buttonwidget(
 119                parent=self._root_widget,
 120                position=(75 + x_inset, yoffs - 60),
 121                size=(60, 55),
 122                scale=1.2,
 123                autoselect=True,
 124                label=bui.charstr(bui.SpecialChar.BACK),
 125                button_type='backSmall',
 126                on_activate_call=self.main_window_back,
 127            )
 128            bui.containerwidget(
 129                edit=self._root_widget,
 130                cancel_button=self._back_button,
 131                selected_child=self._back_button,
 132            )
 133
 134        self._title_text = bui.textwidget(
 135            parent=self._root_widget,
 136            position=(
 137                self._width * 0.5,
 138                yoffs - (55 if uiscale is bui.UIScale.SMALL else 30),
 139            ),
 140            size=(0, 0),
 141            text=bui.Lstr(
 142                resource='league.leagueRankText',
 143                fallback_resource='coopSelectWindow.powerRankingText',
 144            ),
 145            h_align='center',
 146            color=bui.app.ui_v1.title_color,
 147            scale=1.2 if uiscale is bui.UIScale.SMALL else 1.3,
 148            maxwidth=600,
 149            v_align='center',
 150        )
 151
 152        self._scrollwidget = bui.scrollwidget(
 153            parent=self._root_widget,
 154            highlight=False,
 155            size=(self._scroll_width, self._scroll_height),
 156            position=(
 157                self._width * 0.5 - self._scroll_width * 0.5,
 158                scroll_bottom,
 159            ),
 160            center_small_content=True,
 161            center_small_content_horizontally=True,
 162            border_opacity=0.4,
 163        )
 164        bui.widget(edit=self._scrollwidget, autoselect=True)
 165        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
 166
 167        self._last_power_ranking_query_time: float | None = None
 168        self._doing_power_ranking_query = False
 169
 170        self._subcontainer: bui.Widget | None = None
 171        self._subcontainerwidth = 1024
 172        self._subcontainerheight = 483
 173        self._power_ranking_score_widgets: list[bui.Widget] = []
 174
 175        self._season_popup_menu: PopupMenu | None = None
 176        self._requested_season: str | None = None
 177        self._season: str | None = None
 178
 179        # Take note of our account state; we'll refresh later if this
 180        # changes.
 181        self._account_state = plus.get_v1_account_state()
 182
 183        self._refresh()
 184        self._restore_state()
 185
 186        # If we've got cached power-ranking data already, display it.
 187        assert bui.app.classic is not None
 188        info = bui.app.classic.accounts.get_cached_league_rank_data()
 189        if info is not None:
 190            self._update_for_league_rank_data(info)
 191
 192        self._update_timer = bui.AppTimer(
 193            1.0, bui.WeakCall(self._update), repeat=True
 194        )
 195        self._update(show=info is None)
 196
 197    @override
 198    def get_main_window_state(self) -> bui.MainWindowState:
 199        # Support recreating our window for back/refresh purposes.
 200        cls = type(self)
 201        return bui.BasicMainWindowState(
 202            create_call=lambda transition, origin_widget: cls(
 203                transition=transition, origin_widget=origin_widget
 204            )
 205        )
 206
 207    @override
 208    def on_main_window_close(self) -> None:
 209        self._save_state()
 210
 211    def _on_achievements_press(self) -> None:
 212        from bauiv1lib.achievements import AchievementsWindow
 213
 214        # Only allow this for all-time or the current season (we
 215        # currently don't keep specific achievement data for old
 216        # seasons).
 217        if self._season == 'a' or self._is_current_season:
 218            prab = self._power_ranking_achievements_button
 219            assert prab is not None
 220            self.main_window_replace(AchievementsWindow(origin_widget=prab))
 221        else:
 222            bui.screenmessage(
 223                bui.Lstr(
 224                    resource='achievementsUnavailableForOldSeasonsText',
 225                    fallback_resource='unavailableText',
 226                ),
 227                color=(1, 0, 0),
 228            )
 229            bui.getsound('error').play()
 230
 231    def _on_activity_mult_press(self) -> None:
 232        from bauiv1lib import confirm
 233
 234        plus = bui.app.plus
 235        assert plus is not None
 236
 237        txt = bui.Lstr(
 238            resource=(
 239                'coopSelectWindow.activenessAllTimeInfoText'
 240                if self._season == 'a'
 241                else 'coopSelectWindow.activenessInfoText'
 242            ),
 243            subs=[
 244                (
 245                    '${MAX}',
 246                    str(
 247                        plus.get_v1_account_misc_read_val('activenessMax', 1.0)
 248                    ),
 249                )
 250            ],
 251        )
 252        confirm.ConfirmWindow(
 253            txt,
 254            cancel_button=False,
 255            width=460,
 256            height=150,
 257            origin_widget=self._activity_mult_button,
 258        )
 259
 260    def _on_up_to_date_bonus_press(self) -> None:
 261        from bauiv1lib import confirm
 262
 263        plus = bui.app.plus
 264        assert plus is not None
 265
 266        txt = bui.Lstr(
 267            resource='league.upToDateBonusDescriptionText',
 268            subs=[
 269                (
 270                    '${PERCENT}',
 271                    str(
 272                        plus.get_v1_account_misc_read_val(
 273                            'proPowerRankingBoost', 10
 274                        )
 275                    ),
 276                ),
 277            ],
 278        )
 279        confirm.ConfirmWindow(
 280            txt,
 281            cancel_button=False,
 282            width=460,
 283            height=130,
 284            origin_widget=self._up_to_date_bonus_button,
 285        )
 286
 287    def _on_trophies_press(self) -> None:
 288        from bauiv1lib.trophies import TrophiesWindow
 289
 290        info = self._league_rank_data
 291        if info is not None:
 292            prtb = self._power_ranking_trophies_button
 293            assert prtb is not None
 294            TrophiesWindow(
 295                position=prtb.get_screen_space_center(),
 296                data=info,
 297            )
 298        else:
 299            bui.getsound('error').play()
 300
 301    def _on_power_ranking_query_response(
 302        self, data: dict[str, Any] | None
 303    ) -> None:
 304        self._doing_power_ranking_query = False
 305
 306        # Important: *only* cache this if we requested the current
 307        # season.
 308        if data is not None and data.get('s', None) is None:
 309            assert bui.app.classic is not None
 310            bui.app.classic.accounts.cache_league_rank_data(data)
 311
 312        # Always store a copy locally though (even for other seasons).
 313        self._league_rank_data = copy.deepcopy(data)
 314        self._update_for_league_rank_data(data)
 315
 316    def _restore_state(self) -> None:
 317        pass
 318
 319    def _update(self, show: bool = False) -> None:
 320        plus = bui.app.plus
 321        assert plus is not None
 322
 323        cur_time = bui.apptime()
 324
 325        # If our account state has changed, refresh our UI.
 326        account_state = plus.get_v1_account_state()
 327        if account_state != self._account_state:
 328            self._account_state = account_state
 329            self._save_state()
 330            self._refresh()
 331
 332            # And power ranking too.
 333            if not self._doing_power_ranking_query:
 334                self._last_power_ranking_query_time = None
 335
 336        # Send off a new power-ranking query if its been long enough or
 337        # our requested season has changed or whatnot.
 338        if not self._doing_power_ranking_query and (
 339            self._last_power_ranking_query_time is None
 340            or cur_time - self._last_power_ranking_query_time > 30.0
 341        ):
 342            try:
 343                if show:
 344                    bui.textwidget(edit=self._league_title_text, text='')
 345                    bui.textwidget(edit=self._league_text, text='')
 346                    bui.textwidget(edit=self._league_number_text, text='')
 347                    bui.textwidget(edit=self._your_power_ranking_text, text='')
 348                    bui.spinnerwidget(edit=self._loading_spinner, visible=True)
 349                    bui.textwidget(edit=self._to_ranked_text, text='')
 350                    bui.textwidget(edit=self._power_ranking_rank_text, text='')
 351                    bui.textwidget(edit=self._season_ends_text, text='')
 352                    bui.textwidget(edit=self._trophy_counts_reset_text, text='')
 353            except Exception:
 354                logging.exception('Error showing updated rank info.')
 355
 356            self._last_power_ranking_query_time = cur_time
 357            self._doing_power_ranking_query = True
 358            plus.power_ranking_query(
 359                season=self._requested_season,
 360                callback=bui.WeakCall(self._on_power_ranking_query_response),
 361            )
 362
 363    def _refresh(self) -> None:
 364        # pylint: disable=too-many-statements
 365
 366        plus = bui.app.plus
 367        assert plus is not None
 368
 369        # (Re)create the sub-container if need be.
 370        if self._subcontainer is not None:
 371            self._subcontainer.delete()
 372        self._subcontainer = bui.containerwidget(
 373            parent=self._scrollwidget,
 374            size=(self._subcontainerwidth, self._subcontainerheight),
 375            background=False,
 376        )
 377
 378        w_parent = self._subcontainer
 379        v = self._subcontainerheight - 20
 380
 381        v -= 0
 382
 383        h2 = 80
 384        v2 = v - 60
 385        worth_color = (0.6, 0.6, 0.65)
 386        tally_color = (0.5, 0.6, 0.8)
 387        spc = 43
 388
 389        h_offs_tally = 150
 390        tally_maxwidth = 120
 391        v2 -= 70
 392
 393        bui.textwidget(
 394            parent=w_parent,
 395            position=(self._xoffs + h2 - 60, v2 + 106),
 396            size=(0, 0),
 397            flatness=1.0,
 398            shadow=0.0,
 399            text=bui.Lstr(resource='coopSelectWindow.pointsText'),
 400            h_align='left',
 401            v_align='center',
 402            scale=0.8,
 403            color=(1, 1, 1, 0.3),
 404            maxwidth=200,
 405        )
 406
 407        self._power_ranking_achievements_button = bui.buttonwidget(
 408            parent=w_parent,
 409            position=(self._xoffs + h2 - 60, v2 + 10),
 410            size=(200, 80),
 411            icon=bui.gettexture('achievementsIcon'),
 412            autoselect=True,
 413            on_activate_call=bui.WeakCall(self._on_achievements_press),
 414            up_widget=self._back_button,
 415            left_widget=self._back_button,
 416            color=(0.5, 0.5, 0.6),
 417            textcolor=(0.7, 0.7, 0.8),
 418            label='',
 419        )
 420
 421        self._power_ranking_achievement_total_text = bui.textwidget(
 422            parent=w_parent,
 423            position=(self._xoffs + h2 + h_offs_tally, v2 + 45),
 424            size=(0, 0),
 425            flatness=1.0,
 426            shadow=0.0,
 427            text='-',
 428            h_align='left',
 429            v_align='center',
 430            scale=0.8,
 431            color=tally_color,
 432            maxwidth=tally_maxwidth,
 433        )
 434
 435        v2 -= 80
 436
 437        self._power_ranking_trophies_button = bui.buttonwidget(
 438            parent=w_parent,
 439            position=(self._xoffs + h2 - 60, v2 + 10),
 440            size=(200, 80),
 441            icon=bui.gettexture('medalSilver'),
 442            autoselect=True,
 443            on_activate_call=bui.WeakCall(self._on_trophies_press),
 444            left_widget=self._back_button,
 445            color=(0.5, 0.5, 0.6),
 446            textcolor=(0.7, 0.7, 0.8),
 447            label='',
 448        )
 449        self._power_ranking_trophies_total_text = bui.textwidget(
 450            parent=w_parent,
 451            position=(self._xoffs + h2 + h_offs_tally, v2 + 45),
 452            size=(0, 0),
 453            flatness=1.0,
 454            shadow=0.0,
 455            text='-',
 456            h_align='left',
 457            v_align='center',
 458            scale=0.8,
 459            color=tally_color,
 460            maxwidth=tally_maxwidth,
 461        )
 462
 463        v2 -= 100
 464
 465        bui.textwidget(
 466            parent=w_parent,
 467            position=(self._xoffs + h2 - 60, v2 + 86),
 468            size=(0, 0),
 469            flatness=1.0,
 470            shadow=0.0,
 471            text=bui.Lstr(resource='coopSelectWindow.multipliersText'),
 472            h_align='left',
 473            v_align='center',
 474            scale=0.8,
 475            color=(1, 1, 1, 0.3),
 476            maxwidth=200,
 477        )
 478
 479        self._activity_mult_button: bui.Widget | None
 480        if plus.get_v1_account_misc_read_val('act', False):
 481            self._activity_mult_button = bui.buttonwidget(
 482                parent=w_parent,
 483                position=(self._xoffs + h2 - 60, v2 + 10),
 484                size=(200, 60),
 485                icon=bui.gettexture('heart'),
 486                icon_color=(0.5, 0, 0.5),
 487                label=bui.Lstr(resource='coopSelectWindow.activityText'),
 488                autoselect=True,
 489                on_activate_call=bui.WeakCall(self._on_activity_mult_press),
 490                left_widget=self._back_button,
 491                color=(0.5, 0.5, 0.6),
 492                textcolor=(0.7, 0.7, 0.8),
 493            )
 494
 495            self._activity_mult_text = bui.textwidget(
 496                parent=w_parent,
 497                position=(self._xoffs + h2 + h_offs_tally, v2 + 40),
 498                size=(0, 0),
 499                flatness=1.0,
 500                shadow=0.0,
 501                text='-',
 502                h_align='left',
 503                v_align='center',
 504                scale=0.8,
 505                color=tally_color,
 506                maxwidth=tally_maxwidth,
 507            )
 508            v2 -= 65
 509        else:
 510            self._activity_mult_button = None
 511
 512        self._up_to_date_bonus_button = bui.buttonwidget(
 513            parent=w_parent,
 514            position=(self._xoffs + h2 - 60, v2 + 10),
 515            size=(200, 60),
 516            icon=bui.gettexture('logo'),
 517            icon_color=(0.3, 0, 0.3),
 518            label=bui.Lstr(resource='league.upToDateBonusText'),
 519            autoselect=True,
 520            on_activate_call=bui.WeakCall(self._on_up_to_date_bonus_press),
 521            left_widget=self._back_button,
 522            color=(0.5, 0.5, 0.6),
 523            textcolor=(0.7, 0.7, 0.8),
 524        )
 525
 526        self._up_to_date_bonus_text = bui.textwidget(
 527            parent=w_parent,
 528            position=(self._xoffs + h2 + h_offs_tally, v2 + 40),
 529            size=(0, 0),
 530            flatness=1.0,
 531            shadow=0.0,
 532            text='-',
 533            h_align='left',
 534            v_align='center',
 535            scale=0.8,
 536            color=tally_color,
 537            maxwidth=tally_maxwidth,
 538        )
 539        v2 -= 30
 540
 541        v2 -= spc
 542        bui.textwidget(
 543            parent=w_parent,
 544            position=(self._xoffs + h2 + h_offs_tally - 10 - 40, v2 + 35),
 545            size=(0, 0),
 546            flatness=1.0,
 547            shadow=0.0,
 548            text=bui.Lstr(resource='finalScoreText'),
 549            h_align='right',
 550            v_align='center',
 551            scale=0.9,
 552            color=worth_color,
 553            maxwidth=150,
 554        )
 555        self._power_ranking_total_text = bui.textwidget(
 556            parent=w_parent,
 557            position=(self._xoffs + h2 + h_offs_tally - 40, v2 + 35),
 558            size=(0, 0),
 559            flatness=1.0,
 560            shadow=0.0,
 561            text='-',
 562            h_align='left',
 563            v_align='center',
 564            scale=0.9,
 565            color=tally_color,
 566            maxwidth=tally_maxwidth,
 567        )
 568
 569        self._season_show_text = bui.textwidget(
 570            parent=w_parent,
 571            position=(self._xoffs + 390 - 15, v - 20),
 572            size=(0, 0),
 573            color=(0.6, 0.6, 0.7),
 574            maxwidth=200,
 575            text=bui.Lstr(resource='showText'),
 576            h_align='right',
 577            v_align='center',
 578            scale=0.8,
 579            shadow=0,
 580            flatness=1.0,
 581        )
 582
 583        self._league_title_text = bui.textwidget(
 584            parent=w_parent,
 585            position=(self._xoffs + 470, v - 97),
 586            size=(0, 0),
 587            color=(0.6, 0.6, 0.7),
 588            maxwidth=230,
 589            text='',
 590            h_align='center',
 591            v_align='center',
 592            scale=0.9,
 593            shadow=0,
 594            flatness=1.0,
 595        )
 596
 597        self._league_text_scale = 1.8
 598        self._league_text_maxwidth = 210
 599        self._league_text = bui.textwidget(
 600            parent=w_parent,
 601            position=(self._xoffs + 470, v - 140),
 602            size=(0, 0),
 603            color=(1, 1, 1),
 604            maxwidth=self._league_text_maxwidth,
 605            text='-',
 606            h_align='center',
 607            v_align='center',
 608            scale=self._league_text_scale,
 609            shadow=1.0,
 610            flatness=1.0,
 611        )
 612        self._league_number_base_pos = (470, v - 140)
 613        self._league_number_text = bui.textwidget(
 614            parent=w_parent,
 615            position=(self._xoffs + 470, v - 140),
 616            size=(0, 0),
 617            color=(1, 1, 1),
 618            maxwidth=100,
 619            text='',
 620            h_align='left',
 621            v_align='center',
 622            scale=0.8,
 623            shadow=1.0,
 624            flatness=1.0,
 625        )
 626
 627        self._loading_spinner = bui.spinnerwidget(
 628            parent=w_parent,
 629            position=(
 630                self._subcontainerwidth * 0.5,
 631                self._subcontainerheight * 0.5,
 632            ),
 633            style='bomb',
 634            size=64,
 635        )
 636        self._your_power_ranking_text = bui.textwidget(
 637            parent=w_parent,
 638            position=(self._xoffs + 470, v - 142 - 70),
 639            size=(0, 0),
 640            color=(0.6, 0.6, 0.7),
 641            maxwidth=230,
 642            text='',
 643            h_align='center',
 644            v_align='center',
 645            scale=0.9,
 646            shadow=0,
 647            flatness=1.0,
 648        )
 649
 650        self._to_ranked_text = bui.textwidget(
 651            parent=w_parent,
 652            position=(self._xoffs + 470, v - 250 - 70),
 653            size=(0, 0),
 654            color=(0.6, 0.6, 0.7),
 655            maxwidth=230,
 656            text='',
 657            h_align='center',
 658            v_align='center',
 659            scale=0.8,
 660            shadow=0,
 661            flatness=1.0,
 662        )
 663
 664        self._power_ranking_rank_text = bui.textwidget(
 665            parent=w_parent,
 666            position=(self._xoffs + 473, v - 210 - 70),
 667            size=(0, 0),
 668            big=False,
 669            text='-',
 670            h_align='center',
 671            v_align='center',
 672            scale=1.0,
 673        )
 674
 675        self._season_ends_text = bui.textwidget(
 676            parent=w_parent,
 677            position=(self._xoffs + 470, v - 380),
 678            size=(0, 0),
 679            color=(0.6, 0.6, 0.6),
 680            maxwidth=230,
 681            text='',
 682            h_align='center',
 683            v_align='center',
 684            scale=0.9,
 685            shadow=0,
 686            flatness=1.0,
 687        )
 688        self._trophy_counts_reset_text = bui.textwidget(
 689            parent=w_parent,
 690            position=(self._xoffs + 470, v - 410),
 691            size=(0, 0),
 692            color=(0.5, 0.5, 0.5),
 693            maxwidth=230,
 694            text='Trophy counts will reset next season.',
 695            h_align='center',
 696            v_align='center',
 697            scale=0.8,
 698            shadow=0,
 699            flatness=1.0,
 700        )
 701
 702        self._power_ranking_score_widgets = []
 703
 704        self._power_ranking_score_v = v - 56
 705
 706        h = 707
 707        v -= 451
 708
 709        self._see_more_button = bui.buttonwidget(
 710            parent=w_parent,
 711            label=self._rdict.seeMoreText,
 712            position=(self._xoffs + h, v),
 713            color=(0.5, 0.5, 0.6),
 714            textcolor=(0.7, 0.7, 0.8),
 715            size=(230, 60),
 716            autoselect=True,
 717            on_activate_call=bui.WeakCall(self._on_more_press),
 718        )
 719
 720    def _on_more_press(self) -> None:
 721        plus = bui.app.plus
 722        assert plus is not None
 723
 724        our_login_id = plus.get_v1_account_public_login_id()
 725        if not self._can_do_more_button or our_login_id is None:
 726            bui.getsound('error').play()
 727            bui.screenmessage(
 728                bui.Lstr(resource='unavailableText'), color=(1, 0, 0)
 729            )
 730            return
 731        if self._season is None:
 732            season_str = ''
 733        else:
 734            season_str = '&season=' + (
 735                'all_time' if self._season == 'a' else self._season
 736            )
 737        if self._league_url_arg != '':
 738            league_str = '&league=' + self._league_url_arg
 739        else:
 740            league_str = ''
 741        bui.open_url(
 742            plus.get_master_server_address()
 743            + '/highscores?list=powerRankings&v=2'
 744            + league_str
 745            + season_str
 746            + '&player='
 747            + our_login_id
 748        )
 749
 750    def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None:
 751        # pylint: disable=too-many-statements
 752        # pylint: disable=too-many-branches
 753        # pylint: disable=too-many-locals
 754        if not self._root_widget:
 755            return
 756        plus = bui.app.plus
 757        uiscale = bui.app.ui_v1.uiscale
 758        assert plus is not None
 759        assert bui.app.classic is not None
 760        accounts = bui.app.classic.accounts
 761        in_top = data is not None and data['rank'] is not None
 762        eq_text = self._rdict.powerRankingPointsEqualsText
 763        pts_txt = self._rdict.powerRankingPointsText
 764        num_text = bui.Lstr(resource='numberText').evaluate()
 765        do_percent = False
 766        finished_season_unranked = False
 767        self._can_do_more_button = True
 768        extra_text = ''
 769        if plus.get_v1_account_state() != 'signed_in':
 770            status_text = (
 771                '(' + bui.Lstr(resource='notSignedInText').evaluate() + ')'
 772            )
 773        elif in_top:
 774            assert data is not None
 775            status_text = num_text.replace('${NUMBER}', str(data['rank']))
 776        elif data is not None:
 777            try:
 778                # Handle old seasons where we didn't wind up ranked at
 779                # the end.
 780                if not data['scores']:
 781                    status_text = (
 782                        self._rdict.powerRankingFinishedSeasonUnrankedText
 783                    )
 784                    extra_text = ''
 785                    finished_season_unranked = True
 786                    self._can_do_more_button = False
 787                else:
 788                    our_points = accounts.get_league_rank_points(data)
 789                    progress = float(our_points) / max(1, data['scores'][-1][1])
 790                    status_text = str(int(progress * 100.0)) + '%'
 791                    extra_text = (
 792                        '\n'
 793                        + self._rdict.powerRankingPointsToRankedText.replace(
 794                            '${CURRENT}', str(our_points)
 795                        ).replace('${REMAINING}', str(data['scores'][-1][1]))
 796                    )
 797                    do_percent = True
 798            except Exception:
 799                logging.exception('Error updating power ranking.')
 800                status_text = self._rdict.powerRankingNotInTopText.replace(
 801                    '${NUMBER}', str(data['listSize'])
 802                )
 803                extra_text = ''
 804        else:
 805            status_text = '-'
 806
 807        self._season = data['s'] if data is not None else None
 808
 809        v = self._subcontainerheight - 20
 810        popup_was_selected = False
 811        if self._season_popup_menu is not None:
 812            btn = self._season_popup_menu.get_button()
 813            assert self._subcontainer
 814            if self._subcontainer.get_selected_child() == btn:
 815                popup_was_selected = True
 816            btn.delete()
 817        season_choices = []
 818        season_choices_display = []
 819        did_first = False
 820        self._is_current_season = False
 821        if data is not None:
 822            # Build our list of seasons we have available.
 823            for ssn in data['sl']:
 824                season_choices.append(ssn)
 825                if ssn != 'a' and not did_first:
 826                    season_choices_display.append(
 827                        bui.Lstr(
 828                            resource='league.currentSeasonText',
 829                            subs=[('${NUMBER}', ssn)],
 830                        )
 831                    )
 832                    did_first = True
 833
 834                    # If we either did not specify a season or specified
 835                    # the first, we're looking at the current.
 836                    if self._season in [ssn, None]:
 837                        self._is_current_season = True
 838                elif ssn == 'a':
 839                    season_choices_display.append(
 840                        bui.Lstr(resource='league.allTimeText')
 841                    )
 842                else:
 843                    season_choices_display.append(
 844                        bui.Lstr(
 845                            resource='league.seasonText',
 846                            subs=[('${NUMBER}', ssn)],
 847                        )
 848                    )
 849            assert self._subcontainer
 850            self._season_popup_menu = PopupMenu(
 851                parent=self._subcontainer,
 852                position=(self._xoffs + 390, v - 45),
 853                width=150,
 854                button_size=(200, 50),
 855                choices=season_choices,
 856                on_value_change_call=bui.WeakCall(self._on_season_change),
 857                choices_display=season_choices_display,
 858                current_choice=self._season,
 859            )
 860            if popup_was_selected:
 861                bui.containerwidget(
 862                    edit=self._subcontainer,
 863                    selected_child=self._season_popup_menu.get_button(),
 864                )
 865            bui.widget(edit=self._see_more_button, show_buffer_bottom=100)
 866            bui.widget(
 867                edit=self._season_popup_menu.get_button(),
 868                up_widget=self._back_button,
 869            )
 870            if uiscale is not bui.UIScale.SMALL:
 871                bui.widget(
 872                    edit=self._back_button,
 873                    down_widget=self._power_ranking_achievements_button,
 874                    right_widget=self._season_popup_menu.get_button(),
 875                )
 876
 877        bui.textwidget(
 878            edit=self._league_title_text,
 879            text=(
 880                ''
 881                if self._season == 'a'
 882                else bui.Lstr(resource='league.leagueText')
 883            ),
 884        )
 885
 886        if data is None:
 887            lname = ''
 888            lnum = ''
 889            lcolor = (1, 1, 1)
 890            self._league_url_arg = ''
 891        elif self._season == 'a':
 892            lname = bui.Lstr(resource='league.allTimeText').evaluate()
 893            lnum = ''
 894            lcolor = (1, 1, 1)
 895            self._league_url_arg = ''
 896        else:
 897            lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else ''
 898            lname = bui.Lstr(
 899                translate=('leagueNames', data['l']['n'])
 900            ).evaluate()
 901            lcolor = data['l']['c']
 902            self._league_url_arg = (
 903                data['l']['n'] + '_' + str(data['l']['i'])
 904            ).lower()
 905
 906        to_end_string: bui.Lstr | str
 907        if data is None or self._season == 'a' or data['se'] is None:
 908            to_end_string = ''
 909            show_season_end = False
 910        else:
 911            show_season_end = True
 912            days_to_end = data['se'][0]
 913            minutes_to_end = data['se'][1]
 914            if days_to_end > 0:
 915                to_end_string = bui.Lstr(
 916                    resource='league.seasonEndsDaysText',
 917                    subs=[('${NUMBER}', str(days_to_end))],
 918                )
 919            elif days_to_end == 0 and minutes_to_end >= 60:
 920                to_end_string = bui.Lstr(
 921                    resource='league.seasonEndsHoursText',
 922                    subs=[('${NUMBER}', str(minutes_to_end // 60))],
 923                )
 924            elif days_to_end == 0 and minutes_to_end >= 0:
 925                to_end_string = bui.Lstr(
 926                    resource='league.seasonEndsMinutesText',
 927                    subs=[('${NUMBER}', str(minutes_to_end))],
 928                )
 929            else:
 930                to_end_string = bui.Lstr(
 931                    resource='league.seasonEndedDaysAgoText',
 932                    subs=[('${NUMBER}', str(-(days_to_end + 1)))],
 933                )
 934
 935        bui.textwidget(edit=self._season_ends_text, text=to_end_string)
 936        bui.textwidget(
 937            edit=self._trophy_counts_reset_text,
 938            text=(
 939                bui.Lstr(resource='league.trophyCountsResetText')
 940                if self._is_current_season and show_season_end
 941                else ''
 942            ),
 943        )
 944
 945        bui.textwidget(edit=self._league_text, text=lname, color=lcolor)
 946        l_text_width = min(
 947            self._league_text_maxwidth,
 948            bui.get_string_width(lname, suppress_warning=True)
 949            * self._league_text_scale,
 950        )
 951        bui.textwidget(
 952            edit=self._league_number_text,
 953            text=lnum,
 954            color=lcolor,
 955            position=(
 956                self._xoffs
 957                + self._league_number_base_pos[0]
 958                + l_text_width * 0.5
 959                + 8,
 960                self._league_number_base_pos[1] + 10,
 961            ),
 962        )
 963        bui.textwidget(
 964            edit=self._to_ranked_text,
 965            text=(
 966                bui.Lstr(resource='coopSelectWindow.toRankedText').evaluate()
 967                + ''
 968                + extra_text
 969                if do_percent
 970                else ''
 971            ),
 972        )
 973
 974        bui.textwidget(
 975            edit=self._your_power_ranking_text,
 976            text=(
 977                bui.Lstr(
 978                    resource='rankText',
 979                    fallback_resource='coopSelectWindow.yourPowerRankingText',
 980                )
 981                if (not do_percent)
 982                else ''
 983            ),
 984        )
 985        bui.spinnerwidget(edit=self._loading_spinner, visible=False)
 986
 987        bui.textwidget(
 988            edit=self._power_ranking_rank_text,
 989            position=(self._xoffs + 473, v - 70 - (170 if do_percent else 220)),
 990            text=status_text,
 991            big=(in_top or do_percent),
 992            scale=(
 993                3.0
 994                if (in_top or do_percent)
 995                else 0.7 if finished_season_unranked else 1.0
 996            ),
 997        )
 998
 999        if self._activity_mult_button is not None:
1000            if data is None or data['act'] is None:
1001                bui.buttonwidget(
1002                    edit=self._activity_mult_button,
1003                    textcolor=(0.7, 0.7, 0.8, 0.5),
1004                    icon_color=(0.5, 0, 0.5, 0.3),
1005                )
1006                bui.textwidget(edit=self._activity_mult_text, text='     -')
1007            else:
1008                bui.buttonwidget(
1009                    edit=self._activity_mult_button,
1010                    textcolor=(0.7, 0.7, 0.8, 1.0),
1011                    icon_color=(0.5, 0, 0.5, 1.0),
1012                )
1013                bui.textwidget(
1014                    edit=self._activity_mult_text,
1015                    text=f'x {data['act']:.2f}',
1016                )
1017
1018        # This used to be a bonus for 'BombSquad Pro' holders, but since
1019        # we're transitioning away from that it is now a bonus for
1020        # everyone running a recent-ish version of the game.
1021
1022        have_up_to_date_bonus = data is not None
1023        pro_mult = (
1024            1.0
1025            + float(
1026                plus.get_v1_account_misc_read_val('proPowerRankingBoost', 0.0)
1027            )
1028            * 0.01
1029        )
1030        bui.textwidget(
1031            edit=self._up_to_date_bonus_text,
1032            text=(
1033                '     -'
1034                if (data is None or not have_up_to_date_bonus)
1035                else f'x {pro_mult:.2f}'
1036            ),
1037        )
1038        bui.buttonwidget(
1039            edit=self._up_to_date_bonus_button,
1040            textcolor=(0.7, 0.7, 0.8, (1.0 if have_up_to_date_bonus else 0.5)),
1041            icon_color=(
1042                (0.5, 0, 0.5) if have_up_to_date_bonus else (0.5, 0, 0.5, 0.2)
1043            ),
1044        )
1045        bui.buttonwidget(
1046            edit=self._power_ranking_achievements_button,
1047            label=('' if data is None else str(data['a']) + ' ')
1048            + bui.Lstr(resource='achievementsText').evaluate(),
1049        )
1050
1051        # For the achievement value, use the number they gave us for
1052        # non-current seasons; otherwise calc our own.
1053        total_ach_value = 0
1054        for ach in bui.app.classic.ach.achievements:
1055            if ach.complete:
1056                total_ach_value += ach.power_ranking_value
1057        if self._season != 'a' and not self._is_current_season:
1058            if data is not None and 'at' in data:
1059                total_ach_value = data['at']
1060
1061        bui.textwidget(
1062            edit=self._power_ranking_achievement_total_text,
1063            text=(
1064                '-'
1065                if data is None
1066                else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value)))
1067            ),
1068        )
1069
1070        total_trophies_count = accounts.get_league_rank_points(
1071            data, 'trophyCount'
1072        )
1073        total_trophies_value = accounts.get_league_rank_points(data, 'trophies')
1074        bui.buttonwidget(
1075            edit=self._power_ranking_trophies_button,
1076            label=('' if data is None else str(total_trophies_count) + ' ')
1077            + bui.Lstr(resource='trophiesText').evaluate(),
1078        )
1079        bui.textwidget(
1080            edit=self._power_ranking_trophies_total_text,
1081            text=(
1082                '-'
1083                if data is None
1084                else (
1085                    '+ '
1086                    + pts_txt.replace('${NUMBER}', str(total_trophies_value))
1087                )
1088            ),
1089        )
1090
1091        bui.textwidget(
1092            edit=self._power_ranking_total_text,
1093            text=(
1094                '-'
1095                if data is None
1096                else eq_text.replace(
1097                    '${NUMBER}', str(accounts.get_league_rank_points(data))
1098                )
1099            ),
1100        )
1101        for widget in self._power_ranking_score_widgets:
1102            widget.delete()
1103        self._power_ranking_score_widgets = []
1104
1105        scores = data['scores'] if data is not None else []
1106        tally_color = (0.5, 0.6, 0.8)
1107        w_parent = self._subcontainer
1108        v2 = self._power_ranking_score_v
1109
1110        for score in scores:
1111            h2 = 680
1112            is_us = score[3]
1113            self._power_ranking_score_widgets.append(
1114                bui.textwidget(
1115                    parent=w_parent,
1116                    position=(self._xoffs + h2 - 20, v2),
1117                    size=(0, 0),
1118                    color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
1119                    maxwidth=40,
1120                    flatness=1.0,
1121                    shadow=0.0,
1122                    text=num_text.replace('${NUMBER}', str(score[0])),
1123                    h_align='right',
1124                    v_align='center',
1125                    scale=0.5,
1126                )
1127            )
1128            self._power_ranking_score_widgets.append(
1129                bui.textwidget(
1130                    parent=w_parent,
1131                    position=(self._xoffs + h2 + 20, v2),
1132                    size=(0, 0),
1133                    color=(1, 1, 1) if is_us else tally_color,
1134                    maxwidth=60,
1135                    text=str(score[1]),
1136                    flatness=1.0,
1137                    shadow=0.0,
1138                    h_align='center',
1139                    v_align='center',
1140                    scale=0.7,
1141                )
1142            )
1143            txt = bui.textwidget(
1144                parent=w_parent,
1145                position=(self._xoffs + h2 + 60, v2 - (28 * 0.5) / 0.9),
1146                size=(210 / 0.9, 28),
1147                color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
1148                maxwidth=210,
1149                flatness=1.0,
1150                shadow=0.0,
1151                autoselect=True,
1152                selectable=True,
1153                click_activate=True,
1154                text=score[2],
1155                h_align='left',
1156                v_align='center',
1157                scale=0.9,
1158            )
1159            self._power_ranking_score_widgets.append(txt)
1160            bui.textwidget(
1161                edit=txt,
1162                on_activate_call=bui.Call(
1163                    self._show_account_info, score[4], txt
1164                ),
1165            )
1166            assert self._season_popup_menu is not None
1167            bui.widget(
1168                edit=txt, left_widget=self._season_popup_menu.get_button()
1169            )
1170            v2 -= 28
1171
1172    def _show_account_info(
1173        self, account_id: str, textwidget: bui.Widget
1174    ) -> None:
1175        from bauiv1lib.account.viewer import AccountViewerWindow
1176
1177        bui.getsound('swish').play()
1178        AccountViewerWindow(
1179            account_id=account_id, position=textwidget.get_screen_space_center()
1180        )
1181
1182    def _on_season_change(self, value: str) -> None:
1183        self._requested_season = value
1184        self._last_power_ranking_query_time = None  # Update asap.
1185        self._update(show=True)
1186
1187    def _save_state(self) -> None:
1188        pass
class LeagueRankWindow(bauiv1._uitypes.MainWindow):
  20class LeagueRankWindow(bui.MainWindow):
  21    """Window for showing league rank."""
  22
  23    def __init__(
  24        self,
  25        transition: str | None = 'in_right',
  26        origin_widget: bui.Widget | None = None,
  27    ):
  28        # pylint: disable=too-many-statements
  29        plus = bui.app.plus
  30        assert plus is not None
  31
  32        bui.set_analytics_screen('League Rank Window')
  33
  34        self._league_rank_data: dict[str, Any] | None = None
  35
  36        self._power_ranking_achievements_button: bui.Widget | None = None
  37        self._up_to_date_bonus_button: bui.Widget | None = None
  38        self._power_ranking_trophies_button: bui.Widget | None = None
  39        self._league_title_text: bui.Widget | None = None
  40        self._league_text: bui.Widget | None = None
  41        self._league_number_text: bui.Widget | None = None
  42        self._your_power_ranking_text: bui.Widget | None = None
  43        self._loading_spinner: bui.Widget | None = None
  44        self._season_ends_text: bui.Widget | None = None
  45        self._power_ranking_rank_text: bui.Widget | None = None
  46        self._to_ranked_text: bui.Widget | None = None
  47        self._trophy_counts_reset_text: bui.Widget | None = None
  48
  49        assert bui.app.classic is not None
  50        uiscale = bui.app.ui_v1.uiscale
  51        self._width = 1500 if uiscale is bui.UIScale.SMALL else 1120
  52        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
  53        self._height = (
  54            1000
  55            if uiscale is bui.UIScale.SMALL
  56            else 710 if uiscale is bui.UIScale.MEDIUM else 800
  57        )
  58        self._r = 'coopSelectWindow'
  59        self._rdict = bui.app.lang.get_resource(self._r)
  60        # top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
  61
  62        # self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0
  63        self._xoffs = 40
  64
  65        self._league_url_arg = ''
  66
  67        self._is_current_season = False
  68        self._can_do_more_button = True
  69
  70        # Do some fancy math to fill all available screen area up to the
  71        # size of our backing container. This lets us fit to the exact
  72        # screen shape at small ui scale.
  73        screensize = bui.get_virtual_screen_size()
  74        scale = (
  75            1.3
  76            if uiscale is bui.UIScale.SMALL
  77            else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8
  78        )
  79        # Calc screen size in our local container space and clamp to a
  80        # bit smaller than our container size.
  81        target_width = min(self._width - 130, screensize[0] / scale)
  82        target_height = min(self._height - 130, screensize[1] / scale)
  83
  84        # To get top/left coords, go to the center of our window and
  85        # offset by half the width/height of our target area.
  86        yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
  87
  88        self._scroll_width = target_width
  89        self._scroll_height = target_height - 35
  90        scroll_bottom = yoffs - 80 - self._scroll_height
  91
  92        super().__init__(
  93            root_widget=bui.containerwidget(
  94                size=(self._width, self._height),
  95                stack_offset=(
  96                    (0, 0)
  97                    if uiscale is bui.UIScale.SMALL
  98                    else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0)
  99                ),
 100                scale=scale,
 101                toolbar_visibility=(
 102                    'menu_minimal'
 103                    if uiscale is bui.UIScale.SMALL
 104                    else 'menu_full'
 105                ),
 106            ),
 107            transition=transition,
 108            origin_widget=origin_widget,
 109            # We're affected by screen size only at small ui-scale.
 110            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
 111        )
 112
 113        if uiscale is bui.UIScale.SMALL:
 114            self._back_button = bui.get_special_widget('back_button')
 115            bui.containerwidget(
 116                edit=self._root_widget, on_cancel_call=self.main_window_back
 117            )
 118        else:
 119            self._back_button = bui.buttonwidget(
 120                parent=self._root_widget,
 121                position=(75 + x_inset, yoffs - 60),
 122                size=(60, 55),
 123                scale=1.2,
 124                autoselect=True,
 125                label=bui.charstr(bui.SpecialChar.BACK),
 126                button_type='backSmall',
 127                on_activate_call=self.main_window_back,
 128            )
 129            bui.containerwidget(
 130                edit=self._root_widget,
 131                cancel_button=self._back_button,
 132                selected_child=self._back_button,
 133            )
 134
 135        self._title_text = bui.textwidget(
 136            parent=self._root_widget,
 137            position=(
 138                self._width * 0.5,
 139                yoffs - (55 if uiscale is bui.UIScale.SMALL else 30),
 140            ),
 141            size=(0, 0),
 142            text=bui.Lstr(
 143                resource='league.leagueRankText',
 144                fallback_resource='coopSelectWindow.powerRankingText',
 145            ),
 146            h_align='center',
 147            color=bui.app.ui_v1.title_color,
 148            scale=1.2 if uiscale is bui.UIScale.SMALL else 1.3,
 149            maxwidth=600,
 150            v_align='center',
 151        )
 152
 153        self._scrollwidget = bui.scrollwidget(
 154            parent=self._root_widget,
 155            highlight=False,
 156            size=(self._scroll_width, self._scroll_height),
 157            position=(
 158                self._width * 0.5 - self._scroll_width * 0.5,
 159                scroll_bottom,
 160            ),
 161            center_small_content=True,
 162            center_small_content_horizontally=True,
 163            border_opacity=0.4,
 164        )
 165        bui.widget(edit=self._scrollwidget, autoselect=True)
 166        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
 167
 168        self._last_power_ranking_query_time: float | None = None
 169        self._doing_power_ranking_query = False
 170
 171        self._subcontainer: bui.Widget | None = None
 172        self._subcontainerwidth = 1024
 173        self._subcontainerheight = 483
 174        self._power_ranking_score_widgets: list[bui.Widget] = []
 175
 176        self._season_popup_menu: PopupMenu | None = None
 177        self._requested_season: str | None = None
 178        self._season: str | None = None
 179
 180        # Take note of our account state; we'll refresh later if this
 181        # changes.
 182        self._account_state = plus.get_v1_account_state()
 183
 184        self._refresh()
 185        self._restore_state()
 186
 187        # If we've got cached power-ranking data already, display it.
 188        assert bui.app.classic is not None
 189        info = bui.app.classic.accounts.get_cached_league_rank_data()
 190        if info is not None:
 191            self._update_for_league_rank_data(info)
 192
 193        self._update_timer = bui.AppTimer(
 194            1.0, bui.WeakCall(self._update), repeat=True
 195        )
 196        self._update(show=info is None)
 197
 198    @override
 199    def get_main_window_state(self) -> bui.MainWindowState:
 200        # Support recreating our window for back/refresh purposes.
 201        cls = type(self)
 202        return bui.BasicMainWindowState(
 203            create_call=lambda transition, origin_widget: cls(
 204                transition=transition, origin_widget=origin_widget
 205            )
 206        )
 207
 208    @override
 209    def on_main_window_close(self) -> None:
 210        self._save_state()
 211
 212    def _on_achievements_press(self) -> None:
 213        from bauiv1lib.achievements import AchievementsWindow
 214
 215        # Only allow this for all-time or the current season (we
 216        # currently don't keep specific achievement data for old
 217        # seasons).
 218        if self._season == 'a' or self._is_current_season:
 219            prab = self._power_ranking_achievements_button
 220            assert prab is not None
 221            self.main_window_replace(AchievementsWindow(origin_widget=prab))
 222        else:
 223            bui.screenmessage(
 224                bui.Lstr(
 225                    resource='achievementsUnavailableForOldSeasonsText',
 226                    fallback_resource='unavailableText',
 227                ),
 228                color=(1, 0, 0),
 229            )
 230            bui.getsound('error').play()
 231
 232    def _on_activity_mult_press(self) -> None:
 233        from bauiv1lib import confirm
 234
 235        plus = bui.app.plus
 236        assert plus is not None
 237
 238        txt = bui.Lstr(
 239            resource=(
 240                'coopSelectWindow.activenessAllTimeInfoText'
 241                if self._season == 'a'
 242                else 'coopSelectWindow.activenessInfoText'
 243            ),
 244            subs=[
 245                (
 246                    '${MAX}',
 247                    str(
 248                        plus.get_v1_account_misc_read_val('activenessMax', 1.0)
 249                    ),
 250                )
 251            ],
 252        )
 253        confirm.ConfirmWindow(
 254            txt,
 255            cancel_button=False,
 256            width=460,
 257            height=150,
 258            origin_widget=self._activity_mult_button,
 259        )
 260
 261    def _on_up_to_date_bonus_press(self) -> None:
 262        from bauiv1lib import confirm
 263
 264        plus = bui.app.plus
 265        assert plus is not None
 266
 267        txt = bui.Lstr(
 268            resource='league.upToDateBonusDescriptionText',
 269            subs=[
 270                (
 271                    '${PERCENT}',
 272                    str(
 273                        plus.get_v1_account_misc_read_val(
 274                            'proPowerRankingBoost', 10
 275                        )
 276                    ),
 277                ),
 278            ],
 279        )
 280        confirm.ConfirmWindow(
 281            txt,
 282            cancel_button=False,
 283            width=460,
 284            height=130,
 285            origin_widget=self._up_to_date_bonus_button,
 286        )
 287
 288    def _on_trophies_press(self) -> None:
 289        from bauiv1lib.trophies import TrophiesWindow
 290
 291        info = self._league_rank_data
 292        if info is not None:
 293            prtb = self._power_ranking_trophies_button
 294            assert prtb is not None
 295            TrophiesWindow(
 296                position=prtb.get_screen_space_center(),
 297                data=info,
 298            )
 299        else:
 300            bui.getsound('error').play()
 301
 302    def _on_power_ranking_query_response(
 303        self, data: dict[str, Any] | None
 304    ) -> None:
 305        self._doing_power_ranking_query = False
 306
 307        # Important: *only* cache this if we requested the current
 308        # season.
 309        if data is not None and data.get('s', None) is None:
 310            assert bui.app.classic is not None
 311            bui.app.classic.accounts.cache_league_rank_data(data)
 312
 313        # Always store a copy locally though (even for other seasons).
 314        self._league_rank_data = copy.deepcopy(data)
 315        self._update_for_league_rank_data(data)
 316
 317    def _restore_state(self) -> None:
 318        pass
 319
 320    def _update(self, show: bool = False) -> None:
 321        plus = bui.app.plus
 322        assert plus is not None
 323
 324        cur_time = bui.apptime()
 325
 326        # If our account state has changed, refresh our UI.
 327        account_state = plus.get_v1_account_state()
 328        if account_state != self._account_state:
 329            self._account_state = account_state
 330            self._save_state()
 331            self._refresh()
 332
 333            # And power ranking too.
 334            if not self._doing_power_ranking_query:
 335                self._last_power_ranking_query_time = None
 336
 337        # Send off a new power-ranking query if its been long enough or
 338        # our requested season has changed or whatnot.
 339        if not self._doing_power_ranking_query and (
 340            self._last_power_ranking_query_time is None
 341            or cur_time - self._last_power_ranking_query_time > 30.0
 342        ):
 343            try:
 344                if show:
 345                    bui.textwidget(edit=self._league_title_text, text='')
 346                    bui.textwidget(edit=self._league_text, text='')
 347                    bui.textwidget(edit=self._league_number_text, text='')
 348                    bui.textwidget(edit=self._your_power_ranking_text, text='')
 349                    bui.spinnerwidget(edit=self._loading_spinner, visible=True)
 350                    bui.textwidget(edit=self._to_ranked_text, text='')
 351                    bui.textwidget(edit=self._power_ranking_rank_text, text='')
 352                    bui.textwidget(edit=self._season_ends_text, text='')
 353                    bui.textwidget(edit=self._trophy_counts_reset_text, text='')
 354            except Exception:
 355                logging.exception('Error showing updated rank info.')
 356
 357            self._last_power_ranking_query_time = cur_time
 358            self._doing_power_ranking_query = True
 359            plus.power_ranking_query(
 360                season=self._requested_season,
 361                callback=bui.WeakCall(self._on_power_ranking_query_response),
 362            )
 363
 364    def _refresh(self) -> None:
 365        # pylint: disable=too-many-statements
 366
 367        plus = bui.app.plus
 368        assert plus is not None
 369
 370        # (Re)create the sub-container if need be.
 371        if self._subcontainer is not None:
 372            self._subcontainer.delete()
 373        self._subcontainer = bui.containerwidget(
 374            parent=self._scrollwidget,
 375            size=(self._subcontainerwidth, self._subcontainerheight),
 376            background=False,
 377        )
 378
 379        w_parent = self._subcontainer
 380        v = self._subcontainerheight - 20
 381
 382        v -= 0
 383
 384        h2 = 80
 385        v2 = v - 60
 386        worth_color = (0.6, 0.6, 0.65)
 387        tally_color = (0.5, 0.6, 0.8)
 388        spc = 43
 389
 390        h_offs_tally = 150
 391        tally_maxwidth = 120
 392        v2 -= 70
 393
 394        bui.textwidget(
 395            parent=w_parent,
 396            position=(self._xoffs + h2 - 60, v2 + 106),
 397            size=(0, 0),
 398            flatness=1.0,
 399            shadow=0.0,
 400            text=bui.Lstr(resource='coopSelectWindow.pointsText'),
 401            h_align='left',
 402            v_align='center',
 403            scale=0.8,
 404            color=(1, 1, 1, 0.3),
 405            maxwidth=200,
 406        )
 407
 408        self._power_ranking_achievements_button = bui.buttonwidget(
 409            parent=w_parent,
 410            position=(self._xoffs + h2 - 60, v2 + 10),
 411            size=(200, 80),
 412            icon=bui.gettexture('achievementsIcon'),
 413            autoselect=True,
 414            on_activate_call=bui.WeakCall(self._on_achievements_press),
 415            up_widget=self._back_button,
 416            left_widget=self._back_button,
 417            color=(0.5, 0.5, 0.6),
 418            textcolor=(0.7, 0.7, 0.8),
 419            label='',
 420        )
 421
 422        self._power_ranking_achievement_total_text = bui.textwidget(
 423            parent=w_parent,
 424            position=(self._xoffs + h2 + h_offs_tally, v2 + 45),
 425            size=(0, 0),
 426            flatness=1.0,
 427            shadow=0.0,
 428            text='-',
 429            h_align='left',
 430            v_align='center',
 431            scale=0.8,
 432            color=tally_color,
 433            maxwidth=tally_maxwidth,
 434        )
 435
 436        v2 -= 80
 437
 438        self._power_ranking_trophies_button = bui.buttonwidget(
 439            parent=w_parent,
 440            position=(self._xoffs + h2 - 60, v2 + 10),
 441            size=(200, 80),
 442            icon=bui.gettexture('medalSilver'),
 443            autoselect=True,
 444            on_activate_call=bui.WeakCall(self._on_trophies_press),
 445            left_widget=self._back_button,
 446            color=(0.5, 0.5, 0.6),
 447            textcolor=(0.7, 0.7, 0.8),
 448            label='',
 449        )
 450        self._power_ranking_trophies_total_text = bui.textwidget(
 451            parent=w_parent,
 452            position=(self._xoffs + h2 + h_offs_tally, v2 + 45),
 453            size=(0, 0),
 454            flatness=1.0,
 455            shadow=0.0,
 456            text='-',
 457            h_align='left',
 458            v_align='center',
 459            scale=0.8,
 460            color=tally_color,
 461            maxwidth=tally_maxwidth,
 462        )
 463
 464        v2 -= 100
 465
 466        bui.textwidget(
 467            parent=w_parent,
 468            position=(self._xoffs + h2 - 60, v2 + 86),
 469            size=(0, 0),
 470            flatness=1.0,
 471            shadow=0.0,
 472            text=bui.Lstr(resource='coopSelectWindow.multipliersText'),
 473            h_align='left',
 474            v_align='center',
 475            scale=0.8,
 476            color=(1, 1, 1, 0.3),
 477            maxwidth=200,
 478        )
 479
 480        self._activity_mult_button: bui.Widget | None
 481        if plus.get_v1_account_misc_read_val('act', False):
 482            self._activity_mult_button = bui.buttonwidget(
 483                parent=w_parent,
 484                position=(self._xoffs + h2 - 60, v2 + 10),
 485                size=(200, 60),
 486                icon=bui.gettexture('heart'),
 487                icon_color=(0.5, 0, 0.5),
 488                label=bui.Lstr(resource='coopSelectWindow.activityText'),
 489                autoselect=True,
 490                on_activate_call=bui.WeakCall(self._on_activity_mult_press),
 491                left_widget=self._back_button,
 492                color=(0.5, 0.5, 0.6),
 493                textcolor=(0.7, 0.7, 0.8),
 494            )
 495
 496            self._activity_mult_text = bui.textwidget(
 497                parent=w_parent,
 498                position=(self._xoffs + h2 + h_offs_tally, v2 + 40),
 499                size=(0, 0),
 500                flatness=1.0,
 501                shadow=0.0,
 502                text='-',
 503                h_align='left',
 504                v_align='center',
 505                scale=0.8,
 506                color=tally_color,
 507                maxwidth=tally_maxwidth,
 508            )
 509            v2 -= 65
 510        else:
 511            self._activity_mult_button = None
 512
 513        self._up_to_date_bonus_button = bui.buttonwidget(
 514            parent=w_parent,
 515            position=(self._xoffs + h2 - 60, v2 + 10),
 516            size=(200, 60),
 517            icon=bui.gettexture('logo'),
 518            icon_color=(0.3, 0, 0.3),
 519            label=bui.Lstr(resource='league.upToDateBonusText'),
 520            autoselect=True,
 521            on_activate_call=bui.WeakCall(self._on_up_to_date_bonus_press),
 522            left_widget=self._back_button,
 523            color=(0.5, 0.5, 0.6),
 524            textcolor=(0.7, 0.7, 0.8),
 525        )
 526
 527        self._up_to_date_bonus_text = bui.textwidget(
 528            parent=w_parent,
 529            position=(self._xoffs + h2 + h_offs_tally, v2 + 40),
 530            size=(0, 0),
 531            flatness=1.0,
 532            shadow=0.0,
 533            text='-',
 534            h_align='left',
 535            v_align='center',
 536            scale=0.8,
 537            color=tally_color,
 538            maxwidth=tally_maxwidth,
 539        )
 540        v2 -= 30
 541
 542        v2 -= spc
 543        bui.textwidget(
 544            parent=w_parent,
 545            position=(self._xoffs + h2 + h_offs_tally - 10 - 40, v2 + 35),
 546            size=(0, 0),
 547            flatness=1.0,
 548            shadow=0.0,
 549            text=bui.Lstr(resource='finalScoreText'),
 550            h_align='right',
 551            v_align='center',
 552            scale=0.9,
 553            color=worth_color,
 554            maxwidth=150,
 555        )
 556        self._power_ranking_total_text = bui.textwidget(
 557            parent=w_parent,
 558            position=(self._xoffs + h2 + h_offs_tally - 40, v2 + 35),
 559            size=(0, 0),
 560            flatness=1.0,
 561            shadow=0.0,
 562            text='-',
 563            h_align='left',
 564            v_align='center',
 565            scale=0.9,
 566            color=tally_color,
 567            maxwidth=tally_maxwidth,
 568        )
 569
 570        self._season_show_text = bui.textwidget(
 571            parent=w_parent,
 572            position=(self._xoffs + 390 - 15, v - 20),
 573            size=(0, 0),
 574            color=(0.6, 0.6, 0.7),
 575            maxwidth=200,
 576            text=bui.Lstr(resource='showText'),
 577            h_align='right',
 578            v_align='center',
 579            scale=0.8,
 580            shadow=0,
 581            flatness=1.0,
 582        )
 583
 584        self._league_title_text = bui.textwidget(
 585            parent=w_parent,
 586            position=(self._xoffs + 470, v - 97),
 587            size=(0, 0),
 588            color=(0.6, 0.6, 0.7),
 589            maxwidth=230,
 590            text='',
 591            h_align='center',
 592            v_align='center',
 593            scale=0.9,
 594            shadow=0,
 595            flatness=1.0,
 596        )
 597
 598        self._league_text_scale = 1.8
 599        self._league_text_maxwidth = 210
 600        self._league_text = bui.textwidget(
 601            parent=w_parent,
 602            position=(self._xoffs + 470, v - 140),
 603            size=(0, 0),
 604            color=(1, 1, 1),
 605            maxwidth=self._league_text_maxwidth,
 606            text='-',
 607            h_align='center',
 608            v_align='center',
 609            scale=self._league_text_scale,
 610            shadow=1.0,
 611            flatness=1.0,
 612        )
 613        self._league_number_base_pos = (470, v - 140)
 614        self._league_number_text = bui.textwidget(
 615            parent=w_parent,
 616            position=(self._xoffs + 470, v - 140),
 617            size=(0, 0),
 618            color=(1, 1, 1),
 619            maxwidth=100,
 620            text='',
 621            h_align='left',
 622            v_align='center',
 623            scale=0.8,
 624            shadow=1.0,
 625            flatness=1.0,
 626        )
 627
 628        self._loading_spinner = bui.spinnerwidget(
 629            parent=w_parent,
 630            position=(
 631                self._subcontainerwidth * 0.5,
 632                self._subcontainerheight * 0.5,
 633            ),
 634            style='bomb',
 635            size=64,
 636        )
 637        self._your_power_ranking_text = bui.textwidget(
 638            parent=w_parent,
 639            position=(self._xoffs + 470, v - 142 - 70),
 640            size=(0, 0),
 641            color=(0.6, 0.6, 0.7),
 642            maxwidth=230,
 643            text='',
 644            h_align='center',
 645            v_align='center',
 646            scale=0.9,
 647            shadow=0,
 648            flatness=1.0,
 649        )
 650
 651        self._to_ranked_text = bui.textwidget(
 652            parent=w_parent,
 653            position=(self._xoffs + 470, v - 250 - 70),
 654            size=(0, 0),
 655            color=(0.6, 0.6, 0.7),
 656            maxwidth=230,
 657            text='',
 658            h_align='center',
 659            v_align='center',
 660            scale=0.8,
 661            shadow=0,
 662            flatness=1.0,
 663        )
 664
 665        self._power_ranking_rank_text = bui.textwidget(
 666            parent=w_parent,
 667            position=(self._xoffs + 473, v - 210 - 70),
 668            size=(0, 0),
 669            big=False,
 670            text='-',
 671            h_align='center',
 672            v_align='center',
 673            scale=1.0,
 674        )
 675
 676        self._season_ends_text = bui.textwidget(
 677            parent=w_parent,
 678            position=(self._xoffs + 470, v - 380),
 679            size=(0, 0),
 680            color=(0.6, 0.6, 0.6),
 681            maxwidth=230,
 682            text='',
 683            h_align='center',
 684            v_align='center',
 685            scale=0.9,
 686            shadow=0,
 687            flatness=1.0,
 688        )
 689        self._trophy_counts_reset_text = bui.textwidget(
 690            parent=w_parent,
 691            position=(self._xoffs + 470, v - 410),
 692            size=(0, 0),
 693            color=(0.5, 0.5, 0.5),
 694            maxwidth=230,
 695            text='Trophy counts will reset next season.',
 696            h_align='center',
 697            v_align='center',
 698            scale=0.8,
 699            shadow=0,
 700            flatness=1.0,
 701        )
 702
 703        self._power_ranking_score_widgets = []
 704
 705        self._power_ranking_score_v = v - 56
 706
 707        h = 707
 708        v -= 451
 709
 710        self._see_more_button = bui.buttonwidget(
 711            parent=w_parent,
 712            label=self._rdict.seeMoreText,
 713            position=(self._xoffs + h, v),
 714            color=(0.5, 0.5, 0.6),
 715            textcolor=(0.7, 0.7, 0.8),
 716            size=(230, 60),
 717            autoselect=True,
 718            on_activate_call=bui.WeakCall(self._on_more_press),
 719        )
 720
 721    def _on_more_press(self) -> None:
 722        plus = bui.app.plus
 723        assert plus is not None
 724
 725        our_login_id = plus.get_v1_account_public_login_id()
 726        if not self._can_do_more_button or our_login_id is None:
 727            bui.getsound('error').play()
 728            bui.screenmessage(
 729                bui.Lstr(resource='unavailableText'), color=(1, 0, 0)
 730            )
 731            return
 732        if self._season is None:
 733            season_str = ''
 734        else:
 735            season_str = '&season=' + (
 736                'all_time' if self._season == 'a' else self._season
 737            )
 738        if self._league_url_arg != '':
 739            league_str = '&league=' + self._league_url_arg
 740        else:
 741            league_str = ''
 742        bui.open_url(
 743            plus.get_master_server_address()
 744            + '/highscores?list=powerRankings&v=2'
 745            + league_str
 746            + season_str
 747            + '&player='
 748            + our_login_id
 749        )
 750
 751    def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None:
 752        # pylint: disable=too-many-statements
 753        # pylint: disable=too-many-branches
 754        # pylint: disable=too-many-locals
 755        if not self._root_widget:
 756            return
 757        plus = bui.app.plus
 758        uiscale = bui.app.ui_v1.uiscale
 759        assert plus is not None
 760        assert bui.app.classic is not None
 761        accounts = bui.app.classic.accounts
 762        in_top = data is not None and data['rank'] is not None
 763        eq_text = self._rdict.powerRankingPointsEqualsText
 764        pts_txt = self._rdict.powerRankingPointsText
 765        num_text = bui.Lstr(resource='numberText').evaluate()
 766        do_percent = False
 767        finished_season_unranked = False
 768        self._can_do_more_button = True
 769        extra_text = ''
 770        if plus.get_v1_account_state() != 'signed_in':
 771            status_text = (
 772                '(' + bui.Lstr(resource='notSignedInText').evaluate() + ')'
 773            )
 774        elif in_top:
 775            assert data is not None
 776            status_text = num_text.replace('${NUMBER}', str(data['rank']))
 777        elif data is not None:
 778            try:
 779                # Handle old seasons where we didn't wind up ranked at
 780                # the end.
 781                if not data['scores']:
 782                    status_text = (
 783                        self._rdict.powerRankingFinishedSeasonUnrankedText
 784                    )
 785                    extra_text = ''
 786                    finished_season_unranked = True
 787                    self._can_do_more_button = False
 788                else:
 789                    our_points = accounts.get_league_rank_points(data)
 790                    progress = float(our_points) / max(1, data['scores'][-1][1])
 791                    status_text = str(int(progress * 100.0)) + '%'
 792                    extra_text = (
 793                        '\n'
 794                        + self._rdict.powerRankingPointsToRankedText.replace(
 795                            '${CURRENT}', str(our_points)
 796                        ).replace('${REMAINING}', str(data['scores'][-1][1]))
 797                    )
 798                    do_percent = True
 799            except Exception:
 800                logging.exception('Error updating power ranking.')
 801                status_text = self._rdict.powerRankingNotInTopText.replace(
 802                    '${NUMBER}', str(data['listSize'])
 803                )
 804                extra_text = ''
 805        else:
 806            status_text = '-'
 807
 808        self._season = data['s'] if data is not None else None
 809
 810        v = self._subcontainerheight - 20
 811        popup_was_selected = False
 812        if self._season_popup_menu is not None:
 813            btn = self._season_popup_menu.get_button()
 814            assert self._subcontainer
 815            if self._subcontainer.get_selected_child() == btn:
 816                popup_was_selected = True
 817            btn.delete()
 818        season_choices = []
 819        season_choices_display = []
 820        did_first = False
 821        self._is_current_season = False
 822        if data is not None:
 823            # Build our list of seasons we have available.
 824            for ssn in data['sl']:
 825                season_choices.append(ssn)
 826                if ssn != 'a' and not did_first:
 827                    season_choices_display.append(
 828                        bui.Lstr(
 829                            resource='league.currentSeasonText',
 830                            subs=[('${NUMBER}', ssn)],
 831                        )
 832                    )
 833                    did_first = True
 834
 835                    # If we either did not specify a season or specified
 836                    # the first, we're looking at the current.
 837                    if self._season in [ssn, None]:
 838                        self._is_current_season = True
 839                elif ssn == 'a':
 840                    season_choices_display.append(
 841                        bui.Lstr(resource='league.allTimeText')
 842                    )
 843                else:
 844                    season_choices_display.append(
 845                        bui.Lstr(
 846                            resource='league.seasonText',
 847                            subs=[('${NUMBER}', ssn)],
 848                        )
 849                    )
 850            assert self._subcontainer
 851            self._season_popup_menu = PopupMenu(
 852                parent=self._subcontainer,
 853                position=(self._xoffs + 390, v - 45),
 854                width=150,
 855                button_size=(200, 50),
 856                choices=season_choices,
 857                on_value_change_call=bui.WeakCall(self._on_season_change),
 858                choices_display=season_choices_display,
 859                current_choice=self._season,
 860            )
 861            if popup_was_selected:
 862                bui.containerwidget(
 863                    edit=self._subcontainer,
 864                    selected_child=self._season_popup_menu.get_button(),
 865                )
 866            bui.widget(edit=self._see_more_button, show_buffer_bottom=100)
 867            bui.widget(
 868                edit=self._season_popup_menu.get_button(),
 869                up_widget=self._back_button,
 870            )
 871            if uiscale is not bui.UIScale.SMALL:
 872                bui.widget(
 873                    edit=self._back_button,
 874                    down_widget=self._power_ranking_achievements_button,
 875                    right_widget=self._season_popup_menu.get_button(),
 876                )
 877
 878        bui.textwidget(
 879            edit=self._league_title_text,
 880            text=(
 881                ''
 882                if self._season == 'a'
 883                else bui.Lstr(resource='league.leagueText')
 884            ),
 885        )
 886
 887        if data is None:
 888            lname = ''
 889            lnum = ''
 890            lcolor = (1, 1, 1)
 891            self._league_url_arg = ''
 892        elif self._season == 'a':
 893            lname = bui.Lstr(resource='league.allTimeText').evaluate()
 894            lnum = ''
 895            lcolor = (1, 1, 1)
 896            self._league_url_arg = ''
 897        else:
 898            lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else ''
 899            lname = bui.Lstr(
 900                translate=('leagueNames', data['l']['n'])
 901            ).evaluate()
 902            lcolor = data['l']['c']
 903            self._league_url_arg = (
 904                data['l']['n'] + '_' + str(data['l']['i'])
 905            ).lower()
 906
 907        to_end_string: bui.Lstr | str
 908        if data is None or self._season == 'a' or data['se'] is None:
 909            to_end_string = ''
 910            show_season_end = False
 911        else:
 912            show_season_end = True
 913            days_to_end = data['se'][0]
 914            minutes_to_end = data['se'][1]
 915            if days_to_end > 0:
 916                to_end_string = bui.Lstr(
 917                    resource='league.seasonEndsDaysText',
 918                    subs=[('${NUMBER}', str(days_to_end))],
 919                )
 920            elif days_to_end == 0 and minutes_to_end >= 60:
 921                to_end_string = bui.Lstr(
 922                    resource='league.seasonEndsHoursText',
 923                    subs=[('${NUMBER}', str(minutes_to_end // 60))],
 924                )
 925            elif days_to_end == 0 and minutes_to_end >= 0:
 926                to_end_string = bui.Lstr(
 927                    resource='league.seasonEndsMinutesText',
 928                    subs=[('${NUMBER}', str(minutes_to_end))],
 929                )
 930            else:
 931                to_end_string = bui.Lstr(
 932                    resource='league.seasonEndedDaysAgoText',
 933                    subs=[('${NUMBER}', str(-(days_to_end + 1)))],
 934                )
 935
 936        bui.textwidget(edit=self._season_ends_text, text=to_end_string)
 937        bui.textwidget(
 938            edit=self._trophy_counts_reset_text,
 939            text=(
 940                bui.Lstr(resource='league.trophyCountsResetText')
 941                if self._is_current_season and show_season_end
 942                else ''
 943            ),
 944        )
 945
 946        bui.textwidget(edit=self._league_text, text=lname, color=lcolor)
 947        l_text_width = min(
 948            self._league_text_maxwidth,
 949            bui.get_string_width(lname, suppress_warning=True)
 950            * self._league_text_scale,
 951        )
 952        bui.textwidget(
 953            edit=self._league_number_text,
 954            text=lnum,
 955            color=lcolor,
 956            position=(
 957                self._xoffs
 958                + self._league_number_base_pos[0]
 959                + l_text_width * 0.5
 960                + 8,
 961                self._league_number_base_pos[1] + 10,
 962            ),
 963        )
 964        bui.textwidget(
 965            edit=self._to_ranked_text,
 966            text=(
 967                bui.Lstr(resource='coopSelectWindow.toRankedText').evaluate()
 968                + ''
 969                + extra_text
 970                if do_percent
 971                else ''
 972            ),
 973        )
 974
 975        bui.textwidget(
 976            edit=self._your_power_ranking_text,
 977            text=(
 978                bui.Lstr(
 979                    resource='rankText',
 980                    fallback_resource='coopSelectWindow.yourPowerRankingText',
 981                )
 982                if (not do_percent)
 983                else ''
 984            ),
 985        )
 986        bui.spinnerwidget(edit=self._loading_spinner, visible=False)
 987
 988        bui.textwidget(
 989            edit=self._power_ranking_rank_text,
 990            position=(self._xoffs + 473, v - 70 - (170 if do_percent else 220)),
 991            text=status_text,
 992            big=(in_top or do_percent),
 993            scale=(
 994                3.0
 995                if (in_top or do_percent)
 996                else 0.7 if finished_season_unranked else 1.0
 997            ),
 998        )
 999
1000        if self._activity_mult_button is not None:
1001            if data is None or data['act'] is None:
1002                bui.buttonwidget(
1003                    edit=self._activity_mult_button,
1004                    textcolor=(0.7, 0.7, 0.8, 0.5),
1005                    icon_color=(0.5, 0, 0.5, 0.3),
1006                )
1007                bui.textwidget(edit=self._activity_mult_text, text='     -')
1008            else:
1009                bui.buttonwidget(
1010                    edit=self._activity_mult_button,
1011                    textcolor=(0.7, 0.7, 0.8, 1.0),
1012                    icon_color=(0.5, 0, 0.5, 1.0),
1013                )
1014                bui.textwidget(
1015                    edit=self._activity_mult_text,
1016                    text=f'x {data['act']:.2f}',
1017                )
1018
1019        # This used to be a bonus for 'BombSquad Pro' holders, but since
1020        # we're transitioning away from that it is now a bonus for
1021        # everyone running a recent-ish version of the game.
1022
1023        have_up_to_date_bonus = data is not None
1024        pro_mult = (
1025            1.0
1026            + float(
1027                plus.get_v1_account_misc_read_val('proPowerRankingBoost', 0.0)
1028            )
1029            * 0.01
1030        )
1031        bui.textwidget(
1032            edit=self._up_to_date_bonus_text,
1033            text=(
1034                '     -'
1035                if (data is None or not have_up_to_date_bonus)
1036                else f'x {pro_mult:.2f}'
1037            ),
1038        )
1039        bui.buttonwidget(
1040            edit=self._up_to_date_bonus_button,
1041            textcolor=(0.7, 0.7, 0.8, (1.0 if have_up_to_date_bonus else 0.5)),
1042            icon_color=(
1043                (0.5, 0, 0.5) if have_up_to_date_bonus else (0.5, 0, 0.5, 0.2)
1044            ),
1045        )
1046        bui.buttonwidget(
1047            edit=self._power_ranking_achievements_button,
1048            label=('' if data is None else str(data['a']) + ' ')
1049            + bui.Lstr(resource='achievementsText').evaluate(),
1050        )
1051
1052        # For the achievement value, use the number they gave us for
1053        # non-current seasons; otherwise calc our own.
1054        total_ach_value = 0
1055        for ach in bui.app.classic.ach.achievements:
1056            if ach.complete:
1057                total_ach_value += ach.power_ranking_value
1058        if self._season != 'a' and not self._is_current_season:
1059            if data is not None and 'at' in data:
1060                total_ach_value = data['at']
1061
1062        bui.textwidget(
1063            edit=self._power_ranking_achievement_total_text,
1064            text=(
1065                '-'
1066                if data is None
1067                else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value)))
1068            ),
1069        )
1070
1071        total_trophies_count = accounts.get_league_rank_points(
1072            data, 'trophyCount'
1073        )
1074        total_trophies_value = accounts.get_league_rank_points(data, 'trophies')
1075        bui.buttonwidget(
1076            edit=self._power_ranking_trophies_button,
1077            label=('' if data is None else str(total_trophies_count) + ' ')
1078            + bui.Lstr(resource='trophiesText').evaluate(),
1079        )
1080        bui.textwidget(
1081            edit=self._power_ranking_trophies_total_text,
1082            text=(
1083                '-'
1084                if data is None
1085                else (
1086                    '+ '
1087                    + pts_txt.replace('${NUMBER}', str(total_trophies_value))
1088                )
1089            ),
1090        )
1091
1092        bui.textwidget(
1093            edit=self._power_ranking_total_text,
1094            text=(
1095                '-'
1096                if data is None
1097                else eq_text.replace(
1098                    '${NUMBER}', str(accounts.get_league_rank_points(data))
1099                )
1100            ),
1101        )
1102        for widget in self._power_ranking_score_widgets:
1103            widget.delete()
1104        self._power_ranking_score_widgets = []
1105
1106        scores = data['scores'] if data is not None else []
1107        tally_color = (0.5, 0.6, 0.8)
1108        w_parent = self._subcontainer
1109        v2 = self._power_ranking_score_v
1110
1111        for score in scores:
1112            h2 = 680
1113            is_us = score[3]
1114            self._power_ranking_score_widgets.append(
1115                bui.textwidget(
1116                    parent=w_parent,
1117                    position=(self._xoffs + h2 - 20, v2),
1118                    size=(0, 0),
1119                    color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
1120                    maxwidth=40,
1121                    flatness=1.0,
1122                    shadow=0.0,
1123                    text=num_text.replace('${NUMBER}', str(score[0])),
1124                    h_align='right',
1125                    v_align='center',
1126                    scale=0.5,
1127                )
1128            )
1129            self._power_ranking_score_widgets.append(
1130                bui.textwidget(
1131                    parent=w_parent,
1132                    position=(self._xoffs + h2 + 20, v2),
1133                    size=(0, 0),
1134                    color=(1, 1, 1) if is_us else tally_color,
1135                    maxwidth=60,
1136                    text=str(score[1]),
1137                    flatness=1.0,
1138                    shadow=0.0,
1139                    h_align='center',
1140                    v_align='center',
1141                    scale=0.7,
1142                )
1143            )
1144            txt = bui.textwidget(
1145                parent=w_parent,
1146                position=(self._xoffs + h2 + 60, v2 - (28 * 0.5) / 0.9),
1147                size=(210 / 0.9, 28),
1148                color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
1149                maxwidth=210,
1150                flatness=1.0,
1151                shadow=0.0,
1152                autoselect=True,
1153                selectable=True,
1154                click_activate=True,
1155                text=score[2],
1156                h_align='left',
1157                v_align='center',
1158                scale=0.9,
1159            )
1160            self._power_ranking_score_widgets.append(txt)
1161            bui.textwidget(
1162                edit=txt,
1163                on_activate_call=bui.Call(
1164                    self._show_account_info, score[4], txt
1165                ),
1166            )
1167            assert self._season_popup_menu is not None
1168            bui.widget(
1169                edit=txt, left_widget=self._season_popup_menu.get_button()
1170            )
1171            v2 -= 28
1172
1173    def _show_account_info(
1174        self, account_id: str, textwidget: bui.Widget
1175    ) -> None:
1176        from bauiv1lib.account.viewer import AccountViewerWindow
1177
1178        bui.getsound('swish').play()
1179        AccountViewerWindow(
1180            account_id=account_id, position=textwidget.get_screen_space_center()
1181        )
1182
1183    def _on_season_change(self, value: str) -> None:
1184        self._requested_season = value
1185        self._last_power_ranking_query_time = None  # Update asap.
1186        self._update(show=True)
1187
1188    def _save_state(self) -> None:
1189        pass

Window for showing league rank.

LeagueRankWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 23    def __init__(
 24        self,
 25        transition: str | None = 'in_right',
 26        origin_widget: bui.Widget | None = None,
 27    ):
 28        # pylint: disable=too-many-statements
 29        plus = bui.app.plus
 30        assert plus is not None
 31
 32        bui.set_analytics_screen('League Rank Window')
 33
 34        self._league_rank_data: dict[str, Any] | None = None
 35
 36        self._power_ranking_achievements_button: bui.Widget | None = None
 37        self._up_to_date_bonus_button: bui.Widget | None = None
 38        self._power_ranking_trophies_button: bui.Widget | None = None
 39        self._league_title_text: bui.Widget | None = None
 40        self._league_text: bui.Widget | None = None
 41        self._league_number_text: bui.Widget | None = None
 42        self._your_power_ranking_text: bui.Widget | None = None
 43        self._loading_spinner: bui.Widget | None = None
 44        self._season_ends_text: bui.Widget | None = None
 45        self._power_ranking_rank_text: bui.Widget | None = None
 46        self._to_ranked_text: bui.Widget | None = None
 47        self._trophy_counts_reset_text: bui.Widget | None = None
 48
 49        assert bui.app.classic is not None
 50        uiscale = bui.app.ui_v1.uiscale
 51        self._width = 1500 if uiscale is bui.UIScale.SMALL else 1120
 52        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 53        self._height = (
 54            1000
 55            if uiscale is bui.UIScale.SMALL
 56            else 710 if uiscale is bui.UIScale.MEDIUM else 800
 57        )
 58        self._r = 'coopSelectWindow'
 59        self._rdict = bui.app.lang.get_resource(self._r)
 60        # top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
 61
 62        # self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0
 63        self._xoffs = 40
 64
 65        self._league_url_arg = ''
 66
 67        self._is_current_season = False
 68        self._can_do_more_button = True
 69
 70        # Do some fancy math to fill all available screen area up to the
 71        # size of our backing container. This lets us fit to the exact
 72        # screen shape at small ui scale.
 73        screensize = bui.get_virtual_screen_size()
 74        scale = (
 75            1.3
 76            if uiscale is bui.UIScale.SMALL
 77            else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8
 78        )
 79        # Calc screen size in our local container space and clamp to a
 80        # bit smaller than our container size.
 81        target_width = min(self._width - 130, screensize[0] / scale)
 82        target_height = min(self._height - 130, screensize[1] / scale)
 83
 84        # To get top/left coords, go to the center of our window and
 85        # offset by half the width/height of our target area.
 86        yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
 87
 88        self._scroll_width = target_width
 89        self._scroll_height = target_height - 35
 90        scroll_bottom = yoffs - 80 - self._scroll_height
 91
 92        super().__init__(
 93            root_widget=bui.containerwidget(
 94                size=(self._width, self._height),
 95                stack_offset=(
 96                    (0, 0)
 97                    if uiscale is bui.UIScale.SMALL
 98                    else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0)
 99                ),
100                scale=scale,
101                toolbar_visibility=(
102                    'menu_minimal'
103                    if uiscale is bui.UIScale.SMALL
104                    else 'menu_full'
105                ),
106            ),
107            transition=transition,
108            origin_widget=origin_widget,
109            # We're affected by screen size only at small ui-scale.
110            refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
111        )
112
113        if uiscale is bui.UIScale.SMALL:
114            self._back_button = bui.get_special_widget('back_button')
115            bui.containerwidget(
116                edit=self._root_widget, on_cancel_call=self.main_window_back
117            )
118        else:
119            self._back_button = bui.buttonwidget(
120                parent=self._root_widget,
121                position=(75 + x_inset, yoffs - 60),
122                size=(60, 55),
123                scale=1.2,
124                autoselect=True,
125                label=bui.charstr(bui.SpecialChar.BACK),
126                button_type='backSmall',
127                on_activate_call=self.main_window_back,
128            )
129            bui.containerwidget(
130                edit=self._root_widget,
131                cancel_button=self._back_button,
132                selected_child=self._back_button,
133            )
134
135        self._title_text = bui.textwidget(
136            parent=self._root_widget,
137            position=(
138                self._width * 0.5,
139                yoffs - (55 if uiscale is bui.UIScale.SMALL else 30),
140            ),
141            size=(0, 0),
142            text=bui.Lstr(
143                resource='league.leagueRankText',
144                fallback_resource='coopSelectWindow.powerRankingText',
145            ),
146            h_align='center',
147            color=bui.app.ui_v1.title_color,
148            scale=1.2 if uiscale is bui.UIScale.SMALL else 1.3,
149            maxwidth=600,
150            v_align='center',
151        )
152
153        self._scrollwidget = bui.scrollwidget(
154            parent=self._root_widget,
155            highlight=False,
156            size=(self._scroll_width, self._scroll_height),
157            position=(
158                self._width * 0.5 - self._scroll_width * 0.5,
159                scroll_bottom,
160            ),
161            center_small_content=True,
162            center_small_content_horizontally=True,
163            border_opacity=0.4,
164        )
165        bui.widget(edit=self._scrollwidget, autoselect=True)
166        bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
167
168        self._last_power_ranking_query_time: float | None = None
169        self._doing_power_ranking_query = False
170
171        self._subcontainer: bui.Widget | None = None
172        self._subcontainerwidth = 1024
173        self._subcontainerheight = 483
174        self._power_ranking_score_widgets: list[bui.Widget] = []
175
176        self._season_popup_menu: PopupMenu | None = None
177        self._requested_season: str | None = None
178        self._season: str | None = None
179
180        # Take note of our account state; we'll refresh later if this
181        # changes.
182        self._account_state = plus.get_v1_account_state()
183
184        self._refresh()
185        self._restore_state()
186
187        # If we've got cached power-ranking data already, display it.
188        assert bui.app.classic is not None
189        info = bui.app.classic.accounts.get_cached_league_rank_data()
190        if info is not None:
191            self._update_for_league_rank_data(info)
192
193        self._update_timer = bui.AppTimer(
194            1.0, bui.WeakCall(self._update), repeat=True
195        )
196        self._update(show=info is None)

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
198    @override
199    def get_main_window_state(self) -> bui.MainWindowState:
200        # Support recreating our window for back/refresh purposes.
201        cls = type(self)
202        return bui.BasicMainWindowState(
203            create_call=lambda transition, origin_widget: cls(
204                transition=transition, origin_widget=origin_widget
205            )
206        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
208    @override
209    def on_main_window_close(self) -> None:
210        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.