offer_card.py (18942B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\omega\upsell\components\offer_card.py 2 import eveicon 3 import uthread2 4 from carbon.common.script.util.format import FmtAmt 5 from carbonui import Align, Density, uiconst 6 from carbonui import TextAlign, TextColor 7 from carbonui.text.styles import TextBody 8 from carbonui.text.custom import TextCustom 9 from carbonui.button.group import ButtonGroup, ButtonSizeMode 10 from carbonui.control.button import Button, ButtonFrameType, ButtonStyle, ButtonVariant 11 from carbonui.primitives.container import Container 12 from carbonui.primitives.containerAutoSize import ContainerAutoSize 13 from carbonui.primitives.fill import Fill 14 from carbonui.primitives.frame import Frame 15 from carbonui.primitives.layoutGrid import LayoutGrid 16 from carbonui.primitives.stretchspritehorizontal import StretchSpriteHorizontal 17 from carbonui.primitives.sprite import Sprite 18 from carbonui.uianimations import animations 19 from clonegrade.const import ALPHA_TRAINING_MULTIPLIER 20 from eve.client.script.ui import eveColor 21 from eve.client.script.ui.omega.upsell import omegaUpsellSignals 22 from eve.client.script.ui.omega.upsell.components.omega_frame import OmegaOfferFrame, OmegaOfferFrameBestDeal, OmegaFrameState 23 from eve.client.script.ui.omega.upsell.components.buy_plex_tooltip import BuyPlexTooltip 24 from eve.client.script.ui.omega.upsell.components.icons import PlexInfoIcon 25 from eve.client.script.ui.omega.upsell.omegaUpsellConst import OMEGA_SKILL_POINTS_PER_SECOND 26 from plex.client.api.repository import get_repository 27 from plex.client.api.signal import on_balance_changed 28 from vgs.client.currency import get_price_text, OFFER_CURRENCY_PLEX 29 from eve.common.lib import appConst 30 from localization import GetByLabel 31 from store.client.const import OfferLabel 32 33 class OfferCard(Container): 34 default_state = uiconst.UI_NORMAL 35 default_width = 280 36 default_height = 528 37 animation_duration = 0.4 38 39 def __init__(self, offer_id, offer, *args, **kwargs): 40 super(OfferCard, self).__init__(*args, **kwargs) 41 self._update_state_thread = None 42 self.offer_id = offer_id 43 self.offer = offer 44 self._show_price_in_plex = False 45 self.construct_layout() 46 self.update() 47 self.connect_signals() 48 49 def Close(self): 50 try: 51 self._kill_update_thread() 52 self.disconnect_signals() 53 finally: 54 super(OfferCard, self).Close() 55 56 def _kill_update_thread(self): 57 if self._update_state_thread: 58 self._update_state_thread.kill() 59 self._update_state_thread = None 60 61 def connect_signals(self): 62 omegaUpsellSignals.on_show_price_in_plex_change.connect(self.on_show_price_in_plex_change) 63 on_balance_changed.connect(self.on_plex_balance_changed) 64 65 def disconnect_signals(self): 66 omegaUpsellSignals.on_show_price_in_plex_change.disconnect(self.on_show_price_in_plex_change) 67 on_balance_changed.disconnect(self.on_plex_balance_changed) 68 69 def construct_layout(self): 70 self.construct_containers() 71 self.construct_frame() 72 self.construct_label() 73 self.construct_upgrade_button() 74 Spacer(parent=self.content_container) 75 self.construct_price_container() 76 Spacer(parent=self.content_container) 77 self.construct_savings_container() 78 Spacer(parent=self.content_container) 79 self.construct_skillpoints() 80 self.construct_duration() 81 82 def construct_containers(self): 83 self.tag_container = Container(name='tag_container', parent=self) 84 self.content_container = Container(name='content_container', parent=self, padding=(30, 0, 30, 36)) 85 self.frame_container = Container(name='frame_container', parent=self) 86 87 def construct_frame(self): 88 if self.ui_label == OfferLabel.UPSELL_BEST_DEAL: 89 self.frame = OmegaOfferFrameBestDeal(name='frame', parent=self.frame_container) 90 else: 91 self.frame = OmegaOfferFrame(name='frame', parent=self.frame_container) 92 93 def construct_label(self): 94 label = self.ui_label 95 if not label: 96 return 97 if label == OfferLabel.UPSELL_POPULAR_CHOICE: 98 OfferLabelPopularChoice(parent=self.content_container, align=Align.CENTERTOP) 99 elif label == OfferLabel.UPSELL_BEST_DEAL: 100 OfferLabelBestDeal(parent=self.content_container, align=Align.CENTERTOP) 101 102 def construct_duration(self): 103 duration_container = Container(name='duration_container', parent=self.content_container) 104 self.duration_label = TextCustom(name='duration_label', parent=duration_container, align=Align.CENTER, fontsize=40, color=eveColor.OMEGA_ACCENT) 105 106 def construct_skillpoints(self): 107 skillpoints_container = ContainerAutoSize(name='skillpoints_container', parent=self.content_container, align=Align.TOBOTTOM, width=220) 108 Frame(bgParent=skillpoints_container, color=eveColor.WHITE, opacity=0.1, frameConst=uiconst.FRAME_FILLED_CORNER8) 109 self.skillpoints_label = TextBody(name='skillpoints_label', parent=skillpoints_container, align=Align.TOTOP, textAlign=TextAlign.CENTER, bold=True, padding=(24, 8, 24, 8)) 110 111 def construct_savings_container(self): 112 self.savings_container = Container(name='savings_container', parent=self.content_container, align=Align.TOBOTTOM, height=26) 113 114 def construct_currency_savings(self): 115 CurrencySavingsDivider(parent=self.savings_container, align=Align.CENTER, offer=self.offer) 116 117 def construct_plex_savings(self): 118 PlexSavingsDivider(parent=self.savings_container, align=Align.CENTER, offer=self.offer) 119 120 def construct_no_savings(self): 121 NoSavingsDivider(parent=self.savings_container, align=Align.CENTER) 122 123 def construct_price_container(self): 124 self.price_container = Container(name='price_container', parent=self.content_container, align=Align.TOBOTTOM, height=89) 125 126 def construct_plex_price(self): 127 price_month_wrapper = Container(name='price_month_wrapper', parent=self.price_container, align=Align.TOTOP, height=40) 128 price_month_container = LayoutGrid(name='price_month_container', parent=price_month_wrapper, align=Align.CENTER, columns=2, contentSpacing=(8, 0)) 129 price_month_icon = PlexInfoIcon(name='plex_icon', parent=price_month_container, align=uiconst.CENTERLEFT, width=40, height=40) 130 price_month_label = TextCustom(name='price_plex_label', parent=price_month_container, align=Align.CENTERLEFT, text=self.monthly_price_text, color=TextColor.HIGHLIGHT, fontsize=40) 131 price_month_suffix_label = TextCustom(name='price_month_suffix_label', parent=self.price_container, align=Align.TOTOP, text=GetByLabel('UI/OmegaUpsell/PerMonth'), textAlign=TextAlign.CENTER, color=TextColor.NORMAL, fontsize=14, padTop=2) 132 price_total_container = ContainerAutoSize(name='price_total_wrapper', parent=self.price_container, align=Align.CENTERBOTTOM, height=19, top=-2) 133 price_total_icon = PlexInfoIcon(name='plex_icon', parent=price_total_container, align=uiconst.TOLEFT, width=14, height=14) 134 price_total_label = TextBody(name='price_total_label', parent=price_total_container, align=Align.TOLEFT, text=self.total_price_text, color=eveColor.PLEX_YELLOW, padLeft=2) 135 if not self.has_enough_plex: 136 price_month_icon.icon_color = TextColor.DISABLED 137 price_month_label.color = TextColor.DISABLED 138 price_month_suffix_label.color = TextColor.DISABLED 139 price_total_icon.icon_color = TextColor.DISABLED 140 price_total_label.color = TextColor.DISABLED 141 142 def construct_currency_price(self): 143 price_month_container = ContainerAutoSize(name='price_month_container', parent=self.price_container, align=Align.TOTOP, padTop=-8) 144 price_month_label = TextCustom(name='price_month_label', parent=price_month_container, align=Align.TOTOP, text=self.monthly_price_text, textAlign=TextAlign.CENTER, color=TextColor.HIGHLIGHT, fontsize=40) 145 price_month_suffix_label = TextCustom(name='price_month_suffix_label', parent=price_month_container, align=Align.TOTOP, text=GetByLabel('UI/OmegaUpsell/PerMonth'), textAlign=TextAlign.CENTER, color=TextColor.NORMAL, fontsize=14, padTop=-2) 146 price_total_label = TextBody(name='price_total_label', parent=self.price_container, align=Align.TOBOTTOM, textAlign=TextAlign.CENTER, text=self.total_price_text, color=eveColor.OMEGA_ACCENT, padBottom=-2) 147 148 def construct_upgrade_button(self): 149 button_group = ButtonGroup(name='button_group', parent=self.content_container, align=Align.TOBOTTOM, density=Density.EXPANDED, button_size_mode=ButtonSizeMode.STRETCH) 150 self.upgrade_button = Button(name='upgrade_button', label=GetByLabel('UI/OmegaUpsell/Upgrade'), texturePath=eveicon.omega, style=ButtonStyle.MONETIZATION, variant=ButtonVariant.PRIMARY, frame_type=ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT, func=self.on_upgrade_button_click) 151 button_group.add_button(self.upgrade_button) 152 153 def update(self): 154 self.duration_label.text = '%s %s' % (self.month_duration_amount, self.duration_unit_text.capitalize()) 155 self.update_skillpoints() 156 self.update_savings() 157 self.update_price() 158 self.update_button() 159 self.update_tooltip() 160 161 def update_skillpoints(self): 162 duration_seconds = self.offer.schedule_data.cycle_duration 163 extra_sp = FmtAmt(duration_seconds * OMEGA_SKILL_POINTS_PER_SECOND * ALPHA_TRAINING_MULTIPLIER) 164 self.skillpoints_label.text = GetByLabel('UI/OmegaUpsell/OfferSkillPointsLabel', sp_color=eveColor.CRYO_BLUE_HEX, sp_amount=extra_sp) 165 166 def update_savings(self): 167 self.savings_container.Flush() 168 if self.show_price_in_plex and self.offer.has_plex_discount: 169 self.construct_plex_savings() 170 elif not self.show_price_in_plex and self.offer.has_currency_discount: 171 self.construct_currency_savings() 172 else: 173 self.construct_no_savings() 174 animations.FadeTo(self.savings_container, 0.0, 1.0) 175 176 def update_price(self): 177 self.price_container.Flush() 178 if self.show_price_in_plex: 179 self.construct_plex_price() 180 else: 181 self.construct_currency_price() 182 animations.FadeTo(self.price_container, 0.0, 1.0) 183 184 def update_button(self): 185 if self.show_price_in_plex and not self.has_enough_plex: 186 self.upgrade_button.Disable() 187 else: 188 self.upgrade_button.Enable() 189 190 def update_tooltip(self): 191 if self.show_price_in_plex and not self.has_enough_plex: 192 self.upgrade_button.tooltipPanelClassInfo = BuyPlexTooltip(required_amount=int(self.total_price)) 193 else: 194 self.upgrade_button.tooltipPanelClassInfo = None 195 196 def on_upgrade_button_click(self, *args): 197 omegaUpsellSignals.upgrade_to_offer(self.offer_id, self.show_price_in_plex) 198 199 def on_show_price_in_plex_change(self, value): 200 self.show_price_in_plex = value 201 202 def on_plex_balance_changed(self, *args): 203 self.update() 204 205 def OnMouseEnter(self, *args): 206 self._kill_update_thread() 207 self._update_state_thread = uthread2.start_tasklet(self._update_state) 208 209 def OnMouseExit(self, *args): 210 self._kill_update_thread() 211 self.frame.set_state(OmegaFrameState.IDLE) 212 213 def _update_state(self): 214 if self.show_price_in_plex and not self.has_enough_plex: 215 self.frame.set_state(OmegaFrameState.IDLE) 216 else: 217 self.frame.set_state(OmegaFrameState.HOVER) 218 219 @property 220 def month_duration_amount(self): 221 return self.offer.schedule_data.cycle_duration * appConst.SEC / appConst.MONTH30 222 223 @property 224 def duration_unit_text(self): 225 return GetByLabel('/Carbon/UI/Common/WrittenDateTimeQuantity/MonthText', months=self.month_duration_amount) 226 227 @property 228 def monthly_price_text(self): 229 return get_price_text(price=self.total_price_in_cents / self.month_duration_amount / 100.0, currency=self.currency_code, useSpace=False) 230 231 @property 232 def total_price(self): 233 return self.total_price_in_cents / 100.0 234 235 @property 236 def total_price_in_cents(self): 237 if self.show_price_in_plex: 238 return self.offer.price_before_tax.plex_price_in_cents 239 else: 240 return self.offer.price_before_tax.currency_price_in_cents 241 242 @property 243 def total_price_text(self): 244 amount_text = get_price_text(price=self.total_price, currency=self.currency_code, useSpace=False) 245 return '%s %s' % (amount_text, GetByLabel('UI/Common/Total').lower()) 246 247 @property 248 def currency_code(self): 249 if self.show_price_in_plex: 250 return OFFER_CURRENCY_PLEX 251 else: 252 return self.offer.price_before_tax.currency_code 253 254 @property 255 def has_enough_plex(self): 256 return get_repository().get_balance() >= self.total_price 257 258 @property 259 def ui_label(self): 260 return self.offer.ui_annotations.get('label') 261 262 @property 263 def show_price_in_plex(self): 264 return self._show_price_in_plex 265 266 @show_price_in_plex.setter 267 def show_price_in_plex(self, value): 268 self._show_price_in_plex = value 269 self.update() 270 271 272 class HeaderFill(Fill): 273 default_color = eveColor.OMEGA_ACCENT 274 default_opacity = 0.1 275 276 def show(self, animate = True): 277 if animate: 278 animations.FadeTo(self, self.opacity, self.default_opacity) 279 else: 280 self.opacity = self.default_opacity 281 282 def hide(self, animate = True): 283 if animate: 284 animations.FadeTo(self, self.opacity, 0.0) 285 else: 286 self.opacity = 0.0 287 288 289 class Spacer(Container): 290 default_align = Align.TOBOTTOM 291 default_height = 40 292 293 294 class CurrencySavingsDivider(Container): 295 default_width = 220 296 default_height = 26 297 298 def __init__(self, offer, *args, **kwargs): 299 super(CurrencySavingsDivider, self).__init__(*args, **kwargs) 300 self.offer = offer 301 self.construct_layout() 302 self.update() 303 304 def construct_layout(self): 305 self.left_container = Container(name='left_container', parent=self, align=Align.TOLEFT) 306 Fill(parent=self.left_container, align=Align.VERTICALLY_CENTERED, height=1, color=eveColor.WHITE, opacity=0.4) 307 self.center_container = Container(name='center_container', parent=self, align=Align.TOLEFT, padding=(8, 0, 8, 0)) 308 self.construct_label() 309 self.right_container = Container(name='right_container', parent=self, align=Align.TOLEFT) 310 Fill(parent=self.right_container, align=Align.VERTICALLY_CENTERED, height=1, color=eveColor.WHITE, opacity=0.4) 311 312 def construct_label(self): 313 self.label = CurrencySavingsLabel(parent=self.center_container, align=Align.TOTOP, offer=self.offer) 314 315 def update(self): 316 side_container_width = (self.default_width - self.label.component_width) * 0.5 317 self.center_container.width = self.label.component_width 318 self.left_container.width = side_container_width 319 self.right_container.width = side_container_width 320 321 322 class PlexSavingsDivider(CurrencySavingsDivider): 323 324 def construct_label(self): 325 self.label = PlexSavingsLabel(parent=self.center_container, align=Align.TOTOP, offer=self.offer) 326 327 328 class NoSavingsDivider(Container): 329 default_width = 220 330 default_height = 26 331 332 def __init__(self, *args, **kwargs): 333 super(NoSavingsDivider, self).__init__(*args, **kwargs) 334 self.construct_layout() 335 336 def construct_layout(self): 337 Sprite(parent=self, align=Align.CENTER, texturePath='res:/UI/Texture/classes/OmegaUpsell/divider.png', pos=(0, 0, 220, 26)) 338 339 340 class CurrencySavingsLabel(Container): 341 default_height = 26 342 label_padding = 8 343 text_color = eveColor.OMEGA_TAG_TEXT_GREEN 344 345 def __init__(self, offer, *args, **kwargs): 346 super(CurrencySavingsLabel, self).__init__(*args, **kwargs) 347 self.offer = offer 348 self.construct_layout() 349 self.update() 350 351 def construct_layout(self): 352 Frame(bgParent=self, color=eveColor.COPPER_OXIDE_GREEN, frameConst=uiconst.FRAME_FILLED_CORNER4) 353 self.construct_label() 354 355 def construct_label(self): 356 self.label = TextCustom(name='label', parent=self, align=Align.TOPLEFT, color=self.text_color, fontSize=14, bold=True, padding=(self.label_padding, 357 3, 358 self.label_padding, 359 0)) 360 361 def update(self): 362 self.label.text = self.label_text 363 self.width = self.component_width 364 365 @property 366 def label_text(self): 367 return '%s %s%%' % (GetByLabel('UI/OmegaUpsell/Save'), FmtAmt(self.offer.currency_discount_percent)) 368 369 @property 370 def component_width(self): 371 w, _ = TextCustom.MeasureTextSize(self.label_text, fontSize=14, bold=True) 372 return w + self.label_padding * 2 373 374 375 class PlexSavingsLabel(CurrencySavingsLabel): 376 377 @property 378 def label_text(self): 379 return '%s %s%%' % (GetByLabel('UI/OmegaUpsell/Save'), FmtAmt(self.offer.plex_discount_percent)) 380 381 382 class OfferLabelPopularChoice(Container): 383 default_height = 24 384 default_padding = (0, -12, 0, 0) 385 edge_size = 14 386 387 def __init__(self, *args, **kwargs): 388 super(OfferLabelPopularChoice, self).__init__(*args, **kwargs) 389 self.construct_layout() 390 self.update() 391 392 def construct_layout(self): 393 self.label = TextCustom(parent=self, align=Align.CENTERTOP, color=eveColor.OMEGA_TAG_TEXT_RED, bold=True, padTop=2) 394 StretchSpriteHorizontal(parent=self, align=Align.TOTOP_NOPUSH, texturePath='res:/UI/Texture/classes/OmegaUpsell/offer_label_red.png', leftEdgeSize=self.edge_size, rightEdgeSize=self.edge_size, height=self.default_height) 395 396 def update(self): 397 text = GetByLabel('UI/OmegaUpsell/Labels/PopularChoice') 398 text_width, _ = TextCustom.MeasureTextSize(text, bold=True) 399 self.label.text = text 400 self.width = text_width + self.edge_size * 2 401 402 403 class OfferLabelBestDeal(Container): 404 default_height = 30 405 default_padding = (0, -8, 0, 0) 406 edge_size = 2 407 408 def __init__(self, *args, **kwargs): 409 super(OfferLabelBestDeal, self).__init__(*args, **kwargs) 410 self.construct_layout() 411 self.update() 412 413 def construct_layout(self): 414 self.label = TextCustom(parent=self, align=Align.CENTERTOP, color=eveColor.OMEGA_TAG_TEXT_BLUE, bold=True, padTop=5) 415 StretchSpriteHorizontal(parent=self, align=Align.TOTOP_NOPUSH, texturePath='res:/UI/Texture/classes/OmegaUpsell/offer_label_blue.png', leftEdgeSize=self.edge_size, rightEdgeSize=self.edge_size, height=self.default_height) 416 417 def update(self): 418 text = GetByLabel('UI/OmegaUpsell/Labels/BestDeal') 419 text_width, _ = TextCustom.MeasureTextSize(text, bold=True) 420 self.label.text = text 421 self.width = text_width + 12 + self.edge_size * 2