bauiv1lib.account.viewer

Provides a popup for displaying info about any account.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides a popup for displaying info about any account."""
  4
  5from __future__ import annotations
  6
  7from typing import TYPE_CHECKING, override
  8import logging
  9
 10import bauiv1 as bui
 11
 12from bauiv1lib.popup import PopupWindow, PopupMenuWindow
 13
 14if TYPE_CHECKING:
 15    from typing import Any
 16
 17    from bauiv1lib.popup import PopupMenu
 18
 19
 20class AccountViewerWindow(PopupWindow):
 21    """Popup window that displays info for an account."""
 22
 23    def __init__(
 24        self,
 25        account_id: str,
 26        *,
 27        profile_id: str | None = None,
 28        position: tuple[float, float] = (0.0, 0.0),
 29        scale: float | None = None,
 30        offset: tuple[float, float] = (0.0, 0.0),
 31    ):
 32        if bui.app.classic is None:
 33            raise RuntimeError('This requires classic support.')
 34
 35        plus = bui.app.plus
 36        assert plus is not None
 37
 38        self._account_id = account_id
 39        self._profile_id = profile_id
 40
 41        uiscale = bui.app.ui_v1.uiscale
 42        if scale is None:
 43            scale = (
 44                2.6
 45                if uiscale is bui.UIScale.SMALL
 46                else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4
 47            )
 48        self._transitioning_out = False
 49
 50        self._width = 400
 51        self._height = (
 52            300
 53            if uiscale is bui.UIScale.SMALL
 54            else 400 if uiscale is bui.UIScale.MEDIUM else 450
 55        )
 56        self._subcontainer: bui.Widget | None = None
 57
 58        bg_color = (0.5, 0.4, 0.6)
 59
 60        # Creates our _root_widget.
 61        super().__init__(
 62            position=position,
 63            size=(self._width, self._height),
 64            scale=scale,
 65            bg_color=bg_color,
 66            offset=offset,
 67        )
 68
 69        self._cancel_button = bui.buttonwidget(
 70            parent=self.root_widget,
 71            position=(50, self._height - 30),
 72            size=(50, 50),
 73            scale=0.5,
 74            label='',
 75            color=bg_color,
 76            on_activate_call=self._on_cancel_press,
 77            autoselect=True,
 78            icon=bui.gettexture('crossOut'),
 79            iconscale=1.2,
 80        )
 81
 82        self._title_text = bui.textwidget(
 83            parent=self.root_widget,
 84            position=(self._width * 0.5, self._height - 20),
 85            size=(0, 0),
 86            h_align='center',
 87            v_align='center',
 88            scale=0.6,
 89            text=bui.Lstr(resource='playerInfoText'),
 90            maxwidth=200,
 91            color=bui.app.ui_v1.title_color,
 92        )
 93
 94        self._scrollwidget = bui.scrollwidget(
 95            parent=self.root_widget,
 96            size=(self._width - 60, self._height - 70),
 97            position=(30, 30),
 98            capture_arrows=True,
 99            simple_culling_v=10,
100            border_opacity=0.4,
101        )
102        bui.widget(edit=self._scrollwidget, autoselect=True)
103
104        # Note to self: Make sure to always update loading text and
105        # spinner visibility together.
106        self._loading_text = bui.textwidget(
107            parent=self._scrollwidget,
108            scale=0.5,
109            text='',
110            size=(self._width - 60, 100),
111            h_align='center',
112            v_align='center',
113        )
114        self._loading_spinner = bui.spinnerwidget(
115            parent=self.root_widget,
116            position=(self._width * 0.5, self._height * 0.5),
117        )
118
119        # In cases where the user most likely has a browser/email, lets
120        # offer a 'report this user' button.
121        if (
122            bui.is_browser_likely_available()
123            and plus.get_v1_account_misc_read_val(
124                'showAccountExtrasMenu', False
125            )
126        ):
127            self._extras_menu_button = bui.buttonwidget(
128                parent=self.root_widget,
129                size=(20, 20),
130                position=(self._width - 60, self._height - 30),
131                autoselect=True,
132                label='...',
133                button_type='square',
134                color=(0.64, 0.52, 0.69),
135                textcolor=(0.57, 0.47, 0.57),
136                on_activate_call=self._on_extras_menu_press,
137            )
138
139        bui.containerwidget(
140            edit=self.root_widget, cancel_button=self._cancel_button
141        )
142
143        bui.app.classic.master_server_v1_get(
144            'bsAccountInfo',
145            {
146                'buildNumber': bui.app.env.engine_build_number,
147                'accountID': self._account_id,
148                'profileID': self._profile_id,
149            },
150            callback=bui.WeakCall(self._on_query_response),
151        )
152
153    def popup_menu_selected_choice(
154        self, window: PopupMenu, choice: str
155    ) -> None:
156        """Called when a menu entry is selected."""
157        del window  # Unused arg.
158        if choice == 'more':
159            self._on_more_press()
160        elif choice == 'report':
161            self._on_report_press()
162        elif choice == 'ban':
163            self._on_ban_press()
164        else:
165            print('ERROR: unknown account info extras menu item:', choice)
166
167    def popup_menu_closing(self, window: PopupMenu) -> None:
168        """Called when the popup menu is closing."""
169
170    def _on_extras_menu_press(self) -> None:
171        choices = ['more', 'report']
172        choices_display = [
173            bui.Lstr(resource='coopSelectWindow.seeMoreText'),
174            bui.Lstr(resource='reportThisPlayerText'),
175        ]
176        is_admin = False
177        if is_admin:
178            bui.screenmessage('TEMP FORCING ADMIN ON')
179            choices.append('ban')
180            choices_display.append(bui.Lstr(resource='banThisPlayerText'))
181
182        assert bui.app.classic is not None
183        uiscale = bui.app.ui_v1.uiscale
184        PopupMenuWindow(
185            position=self._extras_menu_button.get_screen_space_center(),
186            scale=(
187                2.3
188                if uiscale is bui.UIScale.SMALL
189                else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
190            ),
191            choices=choices,
192            choices_display=choices_display,
193            current_choice='more',
194            delegate=self,
195        )
196
197    def _on_ban_press(self) -> None:
198        plus = bui.app.plus
199        assert plus is not None
200
201        plus.add_v1_account_transaction(
202            {'type': 'BAN_ACCOUNT', 'account': self._account_id}
203        )
204        plus.run_v1_account_transactions()
205
206    def _on_report_press(self) -> None:
207        from bauiv1lib import report
208
209        report.ReportPlayerWindow(
210            self._account_id, origin_widget=self._extras_menu_button
211        )
212
213    def _on_more_press(self) -> None:
214        plus = bui.app.plus
215        assert plus is not None
216        bui.open_url(
217            plus.get_master_server_address()
218            + '/highscores?profile='
219            + self._account_id
220        )
221
222    def _on_query_response(self, data: dict[str, Any] | None) -> None:
223        # FIXME: Tidy this up.
224        # pylint: disable=too-many-locals
225        # pylint: disable=too-many-branches
226        # pylint: disable=too-many-statements
227        # pylint: disable=too-many-nested-blocks
228        assert bui.app.classic is not None
229        if data is None:
230            bui.textwidget(
231                edit=self._loading_text,
232                text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
233            )
234            bui.spinnerwidget(edit=self._loading_spinner, visible=False)
235        else:
236            try:
237                self._loading_text.delete()
238                self._loading_spinner.delete()
239                trophystr = ''
240                try:
241                    trophystr = data['trophies']
242                    num = 10
243                    chunks = [
244                        trophystr[i : i + num]
245                        for i in range(0, len(trophystr), num)
246                    ]
247                    trophystr = '\n\n'.join(chunks)
248                    if trophystr == '':
249                        trophystr = '-'
250                except Exception:
251                    logging.exception('Error displaying trophies.')
252                account_name_spacing = 15
253                tscale = 0.65
254                ts_height = bui.get_string_height(
255                    trophystr, suppress_warning=True
256                )
257                sub_width = self._width - 80
258                sub_height = (
259                    200
260                    + ts_height * tscale
261                    + account_name_spacing * len(data['accountDisplayStrings'])
262                )
263                self._subcontainer = bui.containerwidget(
264                    parent=self._scrollwidget,
265                    size=(sub_width, sub_height),
266                    background=False,
267                )
268                v = sub_height - 20
269
270                title_scale = 0.37
271                center = 0.3
272                maxwidth_scale = 0.45
273                showing_character = False
274                if data['profileDisplayString'] is not None:
275                    tint_color = (1, 1, 1)
276                    try:
277                        if data['profile'] is not None:
278                            profile = data['profile']
279                            assert bui.app.classic is not None
280                            character = bui.app.classic.spaz_appearances.get(
281                                profile['character'], None
282                            )
283                            if character is not None:
284                                tint_color = (
285                                    profile['color']
286                                    if 'color' in profile
287                                    else (1, 1, 1)
288                                )
289                                tint2_color = (
290                                    profile['highlight']
291                                    if 'highlight' in profile
292                                    else (1, 1, 1)
293                                )
294                                icon_tex = character.icon_texture
295                                tint_tex = character.icon_mask_texture
296                                mask_texture = bui.gettexture(
297                                    'characterIconMask'
298                                )
299                                bui.imagewidget(
300                                    parent=self._subcontainer,
301                                    position=(sub_width * center - 40, v - 80),
302                                    size=(80, 80),
303                                    color=(1, 1, 1),
304                                    mask_texture=mask_texture,
305                                    texture=bui.gettexture(icon_tex),
306                                    tint_texture=bui.gettexture(tint_tex),
307                                    tint_color=tint_color,
308                                    tint2_color=tint2_color,
309                                )
310                                v -= 95
311                    except Exception:
312                        logging.exception('Error displaying character.')
313                    bui.textwidget(
314                        parent=self._subcontainer,
315                        size=(0, 0),
316                        position=(sub_width * center, v),
317                        h_align='center',
318                        v_align='center',
319                        scale=0.9,
320                        color=bui.safecolor(tint_color, 0.7),
321                        shadow=1.0,
322                        text=bui.Lstr(value=data['profileDisplayString']),
323                        maxwidth=sub_width * maxwidth_scale * 0.75,
324                    )
325                    showing_character = True
326                    v -= 33
327
328                center = 0.75 if showing_character else 0.5
329                maxwidth_scale = 0.45 if showing_character else 0.9
330
331                v = sub_height - 20
332                if len(data['accountDisplayStrings']) <= 1:
333                    account_title = bui.Lstr(
334                        resource='settingsWindow.accountText'
335                    )
336                else:
337                    account_title = bui.Lstr(
338                        resource='accountSettingsWindow.accountsText',
339                        fallback_resource='settingsWindow.accountText',
340                    )
341                bui.textwidget(
342                    parent=self._subcontainer,
343                    size=(0, 0),
344                    position=(sub_width * center, v),
345                    flatness=1.0,
346                    h_align='center',
347                    v_align='center',
348                    scale=title_scale,
349                    color=bui.app.ui_v1.infotextcolor,
350                    text=account_title,
351                    maxwidth=sub_width * maxwidth_scale,
352                )
353                draw_small = (
354                    showing_character or len(data['accountDisplayStrings']) > 1
355                )
356                v -= 14 if draw_small else 20
357                for account_string in data['accountDisplayStrings']:
358                    bui.textwidget(
359                        parent=self._subcontainer,
360                        size=(0, 0),
361                        position=(sub_width * center, v),
362                        h_align='center',
363                        v_align='center',
364                        scale=0.55 if draw_small else 0.8,
365                        text=account_string,
366                        maxwidth=sub_width * maxwidth_scale,
367                    )
368                    v -= account_name_spacing
369
370                v += account_name_spacing
371                v -= 25 if showing_character else 29
372
373                bui.textwidget(
374                    parent=self._subcontainer,
375                    size=(0, 0),
376                    position=(sub_width * center, v),
377                    flatness=1.0,
378                    h_align='center',
379                    v_align='center',
380                    scale=title_scale,
381                    color=bui.app.ui_v1.infotextcolor,
382                    text=bui.Lstr(resource='rankText'),
383                    maxwidth=sub_width * maxwidth_scale,
384                )
385                v -= 14
386                if data['rank'] is None:
387                    rank_str = '-'
388                    suffix_offset = None
389                else:
390                    str_raw = bui.Lstr(
391                        resource='league.rankInLeagueText'
392                    ).evaluate()
393                    # FIXME: Would be nice to not have to eval this.
394                    rank_str = bui.Lstr(
395                        resource='league.rankInLeagueText',
396                        subs=[
397                            ('${RANK}', str(data['rank'][2])),
398                            (
399                                '${NAME}',
400                                bui.Lstr(
401                                    translate=('leagueNames', data['rank'][0])
402                                ),
403                            ),
404                            ('${SUFFIX}', ''),
405                        ],
406                    ).evaluate()
407                    rank_str_width = min(
408                        sub_width * maxwidth_scale,
409                        bui.get_string_width(rank_str, suppress_warning=True)
410                        * 0.55,
411                    )
412
413                    # Only tack our suffix on if its at the end and only for
414                    # non-diamond leagues.
415                    if (
416                        str_raw.endswith('${SUFFIX}')
417                        and data['rank'][0] != 'Diamond'
418                    ):
419                        suffix_offset = rank_str_width * 0.5 + 2
420                    else:
421                        suffix_offset = None
422
423                bui.textwidget(
424                    parent=self._subcontainer,
425                    size=(0, 0),
426                    position=(sub_width * center, v),
427                    h_align='center',
428                    v_align='center',
429                    scale=0.55,
430                    text=rank_str,
431                    maxwidth=sub_width * maxwidth_scale,
432                )
433                if suffix_offset is not None:
434                    assert data['rank'] is not None
435                    bui.textwidget(
436                        parent=self._subcontainer,
437                        size=(0, 0),
438                        position=(sub_width * center + suffix_offset, v + 3),
439                        h_align='left',
440                        v_align='center',
441                        scale=0.29,
442                        flatness=1.0,
443                        text='[' + str(data['rank'][1]) + ']',
444                    )
445                v -= 14
446
447                str_raw = bui.Lstr(
448                    resource='league.rankInLeagueText'
449                ).evaluate()
450                old_offs = -50
451                prev_ranks_shown = 0
452                for prev_rank in data['prevRanks']:
453                    rank_str = bui.Lstr(
454                        value='${S}:    ${I}',
455                        subs=[
456                            (
457                                '${S}',
458                                bui.Lstr(
459                                    resource='league.seasonText',
460                                    subs=[('${NUMBER}', str(prev_rank[0]))],
461                                ),
462                            ),
463                            (
464                                '${I}',
465                                bui.Lstr(
466                                    resource='league.rankInLeagueText',
467                                    subs=[
468                                        ('${RANK}', str(prev_rank[3])),
469                                        (
470                                            '${NAME}',
471                                            bui.Lstr(
472                                                translate=(
473                                                    'leagueNames',
474                                                    prev_rank[1],
475                                                )
476                                            ),
477                                        ),
478                                        ('${SUFFIX}', ''),
479                                    ],
480                                ),
481                            ),
482                        ],
483                    ).evaluate()
484                    rank_str_width = min(
485                        sub_width * maxwidth_scale,
486                        bui.get_string_width(rank_str, suppress_warning=True)
487                        * 0.3,
488                    )
489
490                    # Only tack our suffix on if its at the end and only for
491                    # non-diamond leagues.
492                    if (
493                        str_raw.endswith('${SUFFIX}')
494                        and prev_rank[1] != 'Diamond'
495                    ):
496                        suffix_offset = rank_str_width + 2
497                    else:
498                        suffix_offset = None
499                    bui.textwidget(
500                        parent=self._subcontainer,
501                        size=(0, 0),
502                        position=(sub_width * center + old_offs, v),
503                        h_align='left',
504                        v_align='center',
505                        scale=0.3,
506                        text=rank_str,
507                        flatness=1.0,
508                        maxwidth=sub_width * maxwidth_scale,
509                    )
510                    if suffix_offset is not None:
511                        bui.textwidget(
512                            parent=self._subcontainer,
513                            size=(0, 0),
514                            position=(
515                                sub_width * center + old_offs + suffix_offset,
516                                v + 1,
517                            ),
518                            h_align='left',
519                            v_align='center',
520                            scale=0.20,
521                            flatness=1.0,
522                            text='[' + str(prev_rank[2]) + ']',
523                        )
524                    prev_ranks_shown += 1
525                    v -= 10
526
527                v -= 13
528
529                bui.textwidget(
530                    parent=self._subcontainer,
531                    size=(0, 0),
532                    position=(sub_width * center, v),
533                    flatness=1.0,
534                    h_align='center',
535                    v_align='center',
536                    scale=title_scale,
537                    color=bui.app.ui_v1.infotextcolor,
538                    text=bui.Lstr(resource='achievementsText'),
539                    maxwidth=sub_width * maxwidth_scale,
540                )
541                v -= 14
542                bui.textwidget(
543                    parent=self._subcontainer,
544                    size=(0, 0),
545                    position=(sub_width * center, v),
546                    h_align='center',
547                    v_align='center',
548                    scale=0.55,
549                    text=str(data['achievementsCompleted'])
550                    + ' / '
551                    + str(len(bui.app.classic.ach.achievements)),
552                    maxwidth=sub_width * maxwidth_scale,
553                )
554                v -= 25
555
556                if prev_ranks_shown == 0 and showing_character:
557                    v -= 20
558                elif prev_ranks_shown == 1 and showing_character:
559                    v -= 10
560
561                center = 0.5
562                maxwidth_scale = 0.9
563
564                bui.textwidget(
565                    parent=self._subcontainer,
566                    size=(0, 0),
567                    position=(sub_width * center, v),
568                    h_align='center',
569                    v_align='center',
570                    scale=title_scale,
571                    color=bui.app.ui_v1.infotextcolor,
572                    flatness=1.0,
573                    text=bui.Lstr(
574                        resource='trophiesThisSeasonText',
575                        fallback_resource='trophiesText',
576                    ),
577                    maxwidth=sub_width * maxwidth_scale,
578                )
579                v -= 19
580                bui.textwidget(
581                    parent=self._subcontainer,
582                    size=(0, ts_height),
583                    position=(sub_width * 0.5, v - ts_height * tscale),
584                    h_align='center',
585                    v_align='top',
586                    corner_scale=tscale,
587                    text=trophystr,
588                )
589
590            except Exception:
591                logging.exception('Error displaying account info.')
592
593    def _on_cancel_press(self) -> None:
594        self._transition_out()
595
596    def _transition_out(self) -> None:
597        if not self._transitioning_out:
598            self._transitioning_out = True
599            bui.containerwidget(edit=self.root_widget, transition='out_scale')
600
601    @override
602    def on_popup_cancel(self) -> None:
603        bui.getsound('swish').play()
604        self._transition_out()
class AccountViewerWindow(bauiv1lib.popup.PopupWindow):
 21class AccountViewerWindow(PopupWindow):
 22    """Popup window that displays info for an account."""
 23
 24    def __init__(
 25        self,
 26        account_id: str,
 27        *,
 28        profile_id: str | None = None,
 29        position: tuple[float, float] = (0.0, 0.0),
 30        scale: float | None = None,
 31        offset: tuple[float, float] = (0.0, 0.0),
 32    ):
 33        if bui.app.classic is None:
 34            raise RuntimeError('This requires classic support.')
 35
 36        plus = bui.app.plus
 37        assert plus is not None
 38
 39        self._account_id = account_id
 40        self._profile_id = profile_id
 41
 42        uiscale = bui.app.ui_v1.uiscale
 43        if scale is None:
 44            scale = (
 45                2.6
 46                if uiscale is bui.UIScale.SMALL
 47                else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4
 48            )
 49        self._transitioning_out = False
 50
 51        self._width = 400
 52        self._height = (
 53            300
 54            if uiscale is bui.UIScale.SMALL
 55            else 400 if uiscale is bui.UIScale.MEDIUM else 450
 56        )
 57        self._subcontainer: bui.Widget | None = None
 58
 59        bg_color = (0.5, 0.4, 0.6)
 60
 61        # Creates our _root_widget.
 62        super().__init__(
 63            position=position,
 64            size=(self._width, self._height),
 65            scale=scale,
 66            bg_color=bg_color,
 67            offset=offset,
 68        )
 69
 70        self._cancel_button = bui.buttonwidget(
 71            parent=self.root_widget,
 72            position=(50, self._height - 30),
 73            size=(50, 50),
 74            scale=0.5,
 75            label='',
 76            color=bg_color,
 77            on_activate_call=self._on_cancel_press,
 78            autoselect=True,
 79            icon=bui.gettexture('crossOut'),
 80            iconscale=1.2,
 81        )
 82
 83        self._title_text = bui.textwidget(
 84            parent=self.root_widget,
 85            position=(self._width * 0.5, self._height - 20),
 86            size=(0, 0),
 87            h_align='center',
 88            v_align='center',
 89            scale=0.6,
 90            text=bui.Lstr(resource='playerInfoText'),
 91            maxwidth=200,
 92            color=bui.app.ui_v1.title_color,
 93        )
 94
 95        self._scrollwidget = bui.scrollwidget(
 96            parent=self.root_widget,
 97            size=(self._width - 60, self._height - 70),
 98            position=(30, 30),
 99            capture_arrows=True,
100            simple_culling_v=10,
101            border_opacity=0.4,
102        )
103        bui.widget(edit=self._scrollwidget, autoselect=True)
104
105        # Note to self: Make sure to always update loading text and
106        # spinner visibility together.
107        self._loading_text = bui.textwidget(
108            parent=self._scrollwidget,
109            scale=0.5,
110            text='',
111            size=(self._width - 60, 100),
112            h_align='center',
113            v_align='center',
114        )
115        self._loading_spinner = bui.spinnerwidget(
116            parent=self.root_widget,
117            position=(self._width * 0.5, self._height * 0.5),
118        )
119
120        # In cases where the user most likely has a browser/email, lets
121        # offer a 'report this user' button.
122        if (
123            bui.is_browser_likely_available()
124            and plus.get_v1_account_misc_read_val(
125                'showAccountExtrasMenu', False
126            )
127        ):
128            self._extras_menu_button = bui.buttonwidget(
129                parent=self.root_widget,
130                size=(20, 20),
131                position=(self._width - 60, self._height - 30),
132                autoselect=True,
133                label='...',
134                button_type='square',
135                color=(0.64, 0.52, 0.69),
136                textcolor=(0.57, 0.47, 0.57),
137                on_activate_call=self._on_extras_menu_press,
138            )
139
140        bui.containerwidget(
141            edit=self.root_widget, cancel_button=self._cancel_button
142        )
143
144        bui.app.classic.master_server_v1_get(
145            'bsAccountInfo',
146            {
147                'buildNumber': bui.app.env.engine_build_number,
148                'accountID': self._account_id,
149                'profileID': self._profile_id,
150            },
151            callback=bui.WeakCall(self._on_query_response),
152        )
153
154    def popup_menu_selected_choice(
155        self, window: PopupMenu, choice: str
156    ) -> None:
157        """Called when a menu entry is selected."""
158        del window  # Unused arg.
159        if choice == 'more':
160            self._on_more_press()
161        elif choice == 'report':
162            self._on_report_press()
163        elif choice == 'ban':
164            self._on_ban_press()
165        else:
166            print('ERROR: unknown account info extras menu item:', choice)
167
168    def popup_menu_closing(self, window: PopupMenu) -> None:
169        """Called when the popup menu is closing."""
170
171    def _on_extras_menu_press(self) -> None:
172        choices = ['more', 'report']
173        choices_display = [
174            bui.Lstr(resource='coopSelectWindow.seeMoreText'),
175            bui.Lstr(resource='reportThisPlayerText'),
176        ]
177        is_admin = False
178        if is_admin:
179            bui.screenmessage('TEMP FORCING ADMIN ON')
180            choices.append('ban')
181            choices_display.append(bui.Lstr(resource='banThisPlayerText'))
182
183        assert bui.app.classic is not None
184        uiscale = bui.app.ui_v1.uiscale
185        PopupMenuWindow(
186            position=self._extras_menu_button.get_screen_space_center(),
187            scale=(
188                2.3
189                if uiscale is bui.UIScale.SMALL
190                else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
191            ),
192            choices=choices,
193            choices_display=choices_display,
194            current_choice='more',
195            delegate=self,
196        )
197
198    def _on_ban_press(self) -> None:
199        plus = bui.app.plus
200        assert plus is not None
201
202        plus.add_v1_account_transaction(
203            {'type': 'BAN_ACCOUNT', 'account': self._account_id}
204        )
205        plus.run_v1_account_transactions()
206
207    def _on_report_press(self) -> None:
208        from bauiv1lib import report
209
210        report.ReportPlayerWindow(
211            self._account_id, origin_widget=self._extras_menu_button
212        )
213
214    def _on_more_press(self) -> None:
215        plus = bui.app.plus
216        assert plus is not None
217        bui.open_url(
218            plus.get_master_server_address()
219            + '/highscores?profile='
220            + self._account_id
221        )
222
223    def _on_query_response(self, data: dict[str, Any] | None) -> None:
224        # FIXME: Tidy this up.
225        # pylint: disable=too-many-locals
226        # pylint: disable=too-many-branches
227        # pylint: disable=too-many-statements
228        # pylint: disable=too-many-nested-blocks
229        assert bui.app.classic is not None
230        if data is None:
231            bui.textwidget(
232                edit=self._loading_text,
233                text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
234            )
235            bui.spinnerwidget(edit=self._loading_spinner, visible=False)
236        else:
237            try:
238                self._loading_text.delete()
239                self._loading_spinner.delete()
240                trophystr = ''
241                try:
242                    trophystr = data['trophies']
243                    num = 10
244                    chunks = [
245                        trophystr[i : i + num]
246                        for i in range(0, len(trophystr), num)
247                    ]
248                    trophystr = '\n\n'.join(chunks)
249                    if trophystr == '':
250                        trophystr = '-'
251                except Exception:
252                    logging.exception('Error displaying trophies.')
253                account_name_spacing = 15
254                tscale = 0.65
255                ts_height = bui.get_string_height(
256                    trophystr, suppress_warning=True
257                )
258                sub_width = self._width - 80
259                sub_height = (
260                    200
261                    + ts_height * tscale
262                    + account_name_spacing * len(data['accountDisplayStrings'])
263                )
264                self._subcontainer = bui.containerwidget(
265                    parent=self._scrollwidget,
266                    size=(sub_width, sub_height),
267                    background=False,
268                )
269                v = sub_height - 20
270
271                title_scale = 0.37
272                center = 0.3
273                maxwidth_scale = 0.45
274                showing_character = False
275                if data['profileDisplayString'] is not None:
276                    tint_color = (1, 1, 1)
277                    try:
278                        if data['profile'] is not None:
279                            profile = data['profile']
280                            assert bui.app.classic is not None
281                            character = bui.app.classic.spaz_appearances.get(
282                                profile['character'], None
283                            )
284                            if character is not None:
285                                tint_color = (
286                                    profile['color']
287                                    if 'color' in profile
288                                    else (1, 1, 1)
289                                )
290                                tint2_color = (
291                                    profile['highlight']
292                                    if 'highlight' in profile
293                                    else (1, 1, 1)
294                                )
295                                icon_tex = character.icon_texture
296                                tint_tex = character.icon_mask_texture
297                                mask_texture = bui.gettexture(
298                                    'characterIconMask'
299                                )
300                                bui.imagewidget(
301                                    parent=self._subcontainer,
302                                    position=(sub_width * center - 40, v - 80),
303                                    size=(80, 80),
304                                    color=(1, 1, 1),
305                                    mask_texture=mask_texture,
306                                    texture=bui.gettexture(icon_tex),
307                                    tint_texture=bui.gettexture(tint_tex),
308                                    tint_color=tint_color,
309                                    tint2_color=tint2_color,
310                                )
311                                v -= 95
312                    except Exception:
313                        logging.exception('Error displaying character.')
314                    bui.textwidget(
315                        parent=self._subcontainer,
316                        size=(0, 0),
317                        position=(sub_width * center, v),
318                        h_align='center',
319                        v_align='center',
320                        scale=0.9,
321                        color=bui.safecolor(tint_color, 0.7),
322                        shadow=1.0,
323                        text=bui.Lstr(value=data['profileDisplayString']),
324                        maxwidth=sub_width * maxwidth_scale * 0.75,
325                    )
326                    showing_character = True
327                    v -= 33
328
329                center = 0.75 if showing_character else 0.5
330                maxwidth_scale = 0.45 if showing_character else 0.9
331
332                v = sub_height - 20
333                if len(data['accountDisplayStrings']) <= 1:
334                    account_title = bui.Lstr(
335                        resource='settingsWindow.accountText'
336                    )
337                else:
338                    account_title = bui.Lstr(
339                        resource='accountSettingsWindow.accountsText',
340                        fallback_resource='settingsWindow.accountText',
341                    )
342                bui.textwidget(
343                    parent=self._subcontainer,
344                    size=(0, 0),
345                    position=(sub_width * center, v),
346                    flatness=1.0,
347                    h_align='center',
348                    v_align='center',
349                    scale=title_scale,
350                    color=bui.app.ui_v1.infotextcolor,
351                    text=account_title,
352                    maxwidth=sub_width * maxwidth_scale,
353                )
354                draw_small = (
355                    showing_character or len(data['accountDisplayStrings']) > 1
356                )
357                v -= 14 if draw_small else 20
358                for account_string in data['accountDisplayStrings']:
359                    bui.textwidget(
360                        parent=self._subcontainer,
361                        size=(0, 0),
362                        position=(sub_width * center, v),
363                        h_align='center',
364                        v_align='center',
365                        scale=0.55 if draw_small else 0.8,
366                        text=account_string,
367                        maxwidth=sub_width * maxwidth_scale,
368                    )
369                    v -= account_name_spacing
370
371                v += account_name_spacing
372                v -= 25 if showing_character else 29
373
374                bui.textwidget(
375                    parent=self._subcontainer,
376                    size=(0, 0),
377                    position=(sub_width * center, v),
378                    flatness=1.0,
379                    h_align='center',
380                    v_align='center',
381                    scale=title_scale,
382                    color=bui.app.ui_v1.infotextcolor,
383                    text=bui.Lstr(resource='rankText'),
384                    maxwidth=sub_width * maxwidth_scale,
385                )
386                v -= 14
387                if data['rank'] is None:
388                    rank_str = '-'
389                    suffix_offset = None
390                else:
391                    str_raw = bui.Lstr(
392                        resource='league.rankInLeagueText'
393                    ).evaluate()
394                    # FIXME: Would be nice to not have to eval this.
395                    rank_str = bui.Lstr(
396                        resource='league.rankInLeagueText',
397                        subs=[
398                            ('${RANK}', str(data['rank'][2])),
399                            (
400                                '${NAME}',
401                                bui.Lstr(
402                                    translate=('leagueNames', data['rank'][0])
403                                ),
404                            ),
405                            ('${SUFFIX}', ''),
406                        ],
407                    ).evaluate()
408                    rank_str_width = min(
409                        sub_width * maxwidth_scale,
410                        bui.get_string_width(rank_str, suppress_warning=True)
411                        * 0.55,
412                    )
413
414                    # Only tack our suffix on if its at the end and only for
415                    # non-diamond leagues.
416                    if (
417                        str_raw.endswith('${SUFFIX}')
418                        and data['rank'][0] != 'Diamond'
419                    ):
420                        suffix_offset = rank_str_width * 0.5 + 2
421                    else:
422                        suffix_offset = None
423
424                bui.textwidget(
425                    parent=self._subcontainer,
426                    size=(0, 0),
427                    position=(sub_width * center, v),
428                    h_align='center',
429                    v_align='center',
430                    scale=0.55,
431                    text=rank_str,
432                    maxwidth=sub_width * maxwidth_scale,
433                )
434                if suffix_offset is not None:
435                    assert data['rank'] is not None
436                    bui.textwidget(
437                        parent=self._subcontainer,
438                        size=(0, 0),
439                        position=(sub_width * center + suffix_offset, v + 3),
440                        h_align='left',
441                        v_align='center',
442                        scale=0.29,
443                        flatness=1.0,
444                        text='[' + str(data['rank'][1]) + ']',
445                    )
446                v -= 14
447
448                str_raw = bui.Lstr(
449                    resource='league.rankInLeagueText'
450                ).evaluate()
451                old_offs = -50
452                prev_ranks_shown = 0
453                for prev_rank in data['prevRanks']:
454                    rank_str = bui.Lstr(
455                        value='${S}:    ${I}',
456                        subs=[
457                            (
458                                '${S}',
459                                bui.Lstr(
460                                    resource='league.seasonText',
461                                    subs=[('${NUMBER}', str(prev_rank[0]))],
462                                ),
463                            ),
464                            (
465                                '${I}',
466                                bui.Lstr(
467                                    resource='league.rankInLeagueText',
468                                    subs=[
469                                        ('${RANK}', str(prev_rank[3])),
470                                        (
471                                            '${NAME}',
472                                            bui.Lstr(
473                                                translate=(
474                                                    'leagueNames',
475                                                    prev_rank[1],
476                                                )
477                                            ),
478                                        ),
479                                        ('${SUFFIX}', ''),
480                                    ],
481                                ),
482                            ),
483                        ],
484                    ).evaluate()
485                    rank_str_width = min(
486                        sub_width * maxwidth_scale,
487                        bui.get_string_width(rank_str, suppress_warning=True)
488                        * 0.3,
489                    )
490
491                    # Only tack our suffix on if its at the end and only for
492                    # non-diamond leagues.
493                    if (
494                        str_raw.endswith('${SUFFIX}')
495                        and prev_rank[1] != 'Diamond'
496                    ):
497                        suffix_offset = rank_str_width + 2
498                    else:
499                        suffix_offset = None
500                    bui.textwidget(
501                        parent=self._subcontainer,
502                        size=(0, 0),
503                        position=(sub_width * center + old_offs, v),
504                        h_align='left',
505                        v_align='center',
506                        scale=0.3,
507                        text=rank_str,
508                        flatness=1.0,
509                        maxwidth=sub_width * maxwidth_scale,
510                    )
511                    if suffix_offset is not None:
512                        bui.textwidget(
513                            parent=self._subcontainer,
514                            size=(0, 0),
515                            position=(
516                                sub_width * center + old_offs + suffix_offset,
517                                v + 1,
518                            ),
519                            h_align='left',
520                            v_align='center',
521                            scale=0.20,
522                            flatness=1.0,
523                            text='[' + str(prev_rank[2]) + ']',
524                        )
525                    prev_ranks_shown += 1
526                    v -= 10
527
528                v -= 13
529
530                bui.textwidget(
531                    parent=self._subcontainer,
532                    size=(0, 0),
533                    position=(sub_width * center, v),
534                    flatness=1.0,
535                    h_align='center',
536                    v_align='center',
537                    scale=title_scale,
538                    color=bui.app.ui_v1.infotextcolor,
539                    text=bui.Lstr(resource='achievementsText'),
540                    maxwidth=sub_width * maxwidth_scale,
541                )
542                v -= 14
543                bui.textwidget(
544                    parent=self._subcontainer,
545                    size=(0, 0),
546                    position=(sub_width * center, v),
547                    h_align='center',
548                    v_align='center',
549                    scale=0.55,
550                    text=str(data['achievementsCompleted'])
551                    + ' / '
552                    + str(len(bui.app.classic.ach.achievements)),
553                    maxwidth=sub_width * maxwidth_scale,
554                )
555                v -= 25
556
557                if prev_ranks_shown == 0 and showing_character:
558                    v -= 20
559                elif prev_ranks_shown == 1 and showing_character:
560                    v -= 10
561
562                center = 0.5
563                maxwidth_scale = 0.9
564
565                bui.textwidget(
566                    parent=self._subcontainer,
567                    size=(0, 0),
568                    position=(sub_width * center, v),
569                    h_align='center',
570                    v_align='center',
571                    scale=title_scale,
572                    color=bui.app.ui_v1.infotextcolor,
573                    flatness=1.0,
574                    text=bui.Lstr(
575                        resource='trophiesThisSeasonText',
576                        fallback_resource='trophiesText',
577                    ),
578                    maxwidth=sub_width * maxwidth_scale,
579                )
580                v -= 19
581                bui.textwidget(
582                    parent=self._subcontainer,
583                    size=(0, ts_height),
584                    position=(sub_width * 0.5, v - ts_height * tscale),
585                    h_align='center',
586                    v_align='top',
587                    corner_scale=tscale,
588                    text=trophystr,
589                )
590
591            except Exception:
592                logging.exception('Error displaying account info.')
593
594    def _on_cancel_press(self) -> None:
595        self._transition_out()
596
597    def _transition_out(self) -> None:
598        if not self._transitioning_out:
599            self._transitioning_out = True
600            bui.containerwidget(edit=self.root_widget, transition='out_scale')
601
602    @override
603    def on_popup_cancel(self) -> None:
604        bui.getsound('swish').play()
605        self._transition_out()

Popup window that displays info for an account.

AccountViewerWindow( account_id: str, *, profile_id: str | None = None, position: tuple[float, float] = (0.0, 0.0), scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0))
 24    def __init__(
 25        self,
 26        account_id: str,
 27        *,
 28        profile_id: str | None = None,
 29        position: tuple[float, float] = (0.0, 0.0),
 30        scale: float | None = None,
 31        offset: tuple[float, float] = (0.0, 0.0),
 32    ):
 33        if bui.app.classic is None:
 34            raise RuntimeError('This requires classic support.')
 35
 36        plus = bui.app.plus
 37        assert plus is not None
 38
 39        self._account_id = account_id
 40        self._profile_id = profile_id
 41
 42        uiscale = bui.app.ui_v1.uiscale
 43        if scale is None:
 44            scale = (
 45                2.6
 46                if uiscale is bui.UIScale.SMALL
 47                else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4
 48            )
 49        self._transitioning_out = False
 50
 51        self._width = 400
 52        self._height = (
 53            300
 54            if uiscale is bui.UIScale.SMALL
 55            else 400 if uiscale is bui.UIScale.MEDIUM else 450
 56        )
 57        self._subcontainer: bui.Widget | None = None
 58
 59        bg_color = (0.5, 0.4, 0.6)
 60
 61        # Creates our _root_widget.
 62        super().__init__(
 63            position=position,
 64            size=(self._width, self._height),
 65            scale=scale,
 66            bg_color=bg_color,
 67            offset=offset,
 68        )
 69
 70        self._cancel_button = bui.buttonwidget(
 71            parent=self.root_widget,
 72            position=(50, self._height - 30),
 73            size=(50, 50),
 74            scale=0.5,
 75            label='',
 76            color=bg_color,
 77            on_activate_call=self._on_cancel_press,
 78            autoselect=True,
 79            icon=bui.gettexture('crossOut'),
 80            iconscale=1.2,
 81        )
 82
 83        self._title_text = bui.textwidget(
 84            parent=self.root_widget,
 85            position=(self._width * 0.5, self._height - 20),
 86            size=(0, 0),
 87            h_align='center',
 88            v_align='center',
 89            scale=0.6,
 90            text=bui.Lstr(resource='playerInfoText'),
 91            maxwidth=200,
 92            color=bui.app.ui_v1.title_color,
 93        )
 94
 95        self._scrollwidget = bui.scrollwidget(
 96            parent=self.root_widget,
 97            size=(self._width - 60, self._height - 70),
 98            position=(30, 30),
 99            capture_arrows=True,
100            simple_culling_v=10,
101            border_opacity=0.4,
102        )
103        bui.widget(edit=self._scrollwidget, autoselect=True)
104
105        # Note to self: Make sure to always update loading text and
106        # spinner visibility together.
107        self._loading_text = bui.textwidget(
108            parent=self._scrollwidget,
109            scale=0.5,
110            text='',
111            size=(self._width - 60, 100),
112            h_align='center',
113            v_align='center',
114        )
115        self._loading_spinner = bui.spinnerwidget(
116            parent=self.root_widget,
117            position=(self._width * 0.5, self._height * 0.5),
118        )
119
120        # In cases where the user most likely has a browser/email, lets
121        # offer a 'report this user' button.
122        if (
123            bui.is_browser_likely_available()
124            and plus.get_v1_account_misc_read_val(
125                'showAccountExtrasMenu', False
126            )
127        ):
128            self._extras_menu_button = bui.buttonwidget(
129                parent=self.root_widget,
130                size=(20, 20),
131                position=(self._width - 60, self._height - 30),
132                autoselect=True,
133                label='...',
134                button_type='square',
135                color=(0.64, 0.52, 0.69),
136                textcolor=(0.57, 0.47, 0.57),
137                on_activate_call=self._on_extras_menu_press,
138            )
139
140        bui.containerwidget(
141            edit=self.root_widget, cancel_button=self._cancel_button
142        )
143
144        bui.app.classic.master_server_v1_get(
145            'bsAccountInfo',
146            {
147                'buildNumber': bui.app.env.engine_build_number,
148                'accountID': self._account_id,
149                'profileID': self._profile_id,
150            },
151            callback=bui.WeakCall(self._on_query_response),
152        )
def popup_menu_selected_choice(self, window: bauiv1lib.popup.PopupMenu, choice: str) -> None:
154    def popup_menu_selected_choice(
155        self, window: PopupMenu, choice: str
156    ) -> None:
157        """Called when a menu entry is selected."""
158        del window  # Unused arg.
159        if choice == 'more':
160            self._on_more_press()
161        elif choice == 'report':
162            self._on_report_press()
163        elif choice == 'ban':
164            self._on_ban_press()
165        else:
166            print('ERROR: unknown account info extras menu item:', choice)

Called when a menu entry is selected.

def popup_menu_closing(self, window: bauiv1lib.popup.PopupMenu) -> None:
168    def popup_menu_closing(self, window: PopupMenu) -> None:
169        """Called when the popup menu is closing."""

Called when the popup menu is closing.

@override
def on_popup_cancel(self) -> None:
602    @override
603    def on_popup_cancel(self) -> None:
604        bui.getsound('swish').play()
605        self._transition_out()

Called when the popup is canceled.

Cancels can occur due to clicking outside the window, hitting escape, etc.