default_offer_card.py (18629B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\omega\upsell\components\default_offer_card.py 2 import appConst 3 import eveicon 4 import evetypes 5 import math 6 import uthread2 7 from carbonui import Align, Axis, AxisAlignment, ButtonVariant, ButtonStyle, Density, PickState, uiconst 8 from carbonui import TextAlign, TextColor 9 from carbonui.text.styles import TextBody 10 from carbonui.text.custom import TextCustom 11 from carbonui.button.group import ButtonGroup 12 from carbonui.control.button import Button 13 from carbonui.control.scrollContainer import ScrollContainer 14 from carbonui.primitives.container import Container 15 from carbonui.primitives.containerAutoSize import ContainerAutoSize 16 from carbonui.primitives.flowcontainer import FlowContainer 17 from carbonui.primitives.frame import Frame 18 from carbonui.primitives.gradientSprite import GradientSprite 19 from carbonui.primitives.sprite import Sprite 20 from carbonui.uianimations import animations 21 from eve.client.script.ui import eveColor 22 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel 23 from eve.client.script.ui.control.infoIcon import InfoIcon 24 from eve.client.script.ui.control.itemIcon import ItemIcon 25 from player_messaging.client.ui_message import message_player 26 from eve.client.script.ui.omega.upsell import omegaUpsellSignals 27 from eve.client.script.ui.omega.upsell.components.buy_plex_tooltip import BuyPlexTooltip 28 from eve.client.script.ui.omega.upsell.components.currency_toggle import CurrencyToggle 29 from eve.client.script.ui.omega.upsell.components.icons import PlexInfoIcon 30 from eve.client.script.ui.omega.upsell.components.omega_frame import DefaultOfferBackgroundHighlight 31 from eve.client.script.ui.shared.neocom.wallet import walletSignals 32 from plex.client.api.repository import get_repository 33 from vgs.client.currency import get_price_text, CURRENCY_SYMBOL, CURRENCY_TEXT, OFFER_CURRENCY_PLEX 34 from localization import GetByLabel 35 from plex.client.api.signal import on_balance_changed 36 from stackless_response_router.exceptions import TimeoutException 37 from store.client.const import StoreProductType 38 from store.client.offers.offer_provider_svc import get_offer_provider_svc 39 from utillib import KeyVal 40 41 class DefaultOfferCard(Container): 42 default_align = Align.CENTERTOP 43 default_minWidth = 400 44 default_maxWidth = 800 45 46 def __init__(self, *args, **kwargs): 47 super(DefaultOfferCard, self).__init__(*args, **kwargs) 48 self.offer_id = None 49 self.offer = None 50 self.products = [] 51 self.show_price_in_plex = False 52 self._update_thread = None 53 self.construct_layout() 54 self.update() 55 on_balance_changed.connect(self.on_plex_balance_changed) 56 57 def Close(self): 58 try: 59 self.kill_update_thread() 60 self.disconnect_signals() 61 finally: 62 super(DefaultOfferCard, self).Close() 63 64 def on_plex_balance_changed(self, *args): 65 self.update() 66 67 def disconnect_signals(self): 68 on_balance_changed.disconnect(self.on_plex_balance_changed) 69 70 def kill_update_thread(self): 71 if self._update_thread is not None: 72 self._update_thread.kill() 73 self._update_thread = None 74 75 def construct_layout(self): 76 self.construct_containers() 77 self.construct_loading_wheel() 78 self.construct_background() 79 self.construct_frame() 80 self.construct_left_content() 81 self.construct_center_content() 82 self.construct_right_content() 83 84 def construct_containers(self): 85 self.loading_container = Container(name='loading_container', parent=self) 86 self.frame_container = Container(name='frame_container', parent=self) 87 self.content_container = Container(name='content_container', parent=self, opacity=0.0) 88 self.background_container = Container(name='background_container', parent=self) 89 90 def construct_loading_wheel(self): 91 self.loading_wheel = LoadingWheel(name='loading_wheel', parent=self.loading_container, align=Align.CENTER) 92 93 def construct_background(self): 94 DefaultOfferBackgroundHighlight(name='highlight', parent=self.background_container) 95 GradientSprite(name='background', parent=self.background_container, align=Align.TOALL, pickState=PickState.OFF, rgbData=eveColor.OMEGA_SECONDARY_GRADIENT_RGB_DATA, rotation=-math.pi / 2) 96 97 def construct_frame(self): 98 Frame(name='corners', parent=self.frame_container, texturePath='res:/UI/Texture/classes/OmegaUpsell/current_offer_frame.png', cornerSize=13) 99 Frame(name='stroke', parent=self.frame_container, color=eveColor.OMEGA_TERTIARY) 100 101 def construct_left_content(self): 102 left_prop_container = Container(name='left_prop_container', parent=self.content_container, align=Align.TOLEFT_PROP, width=0.33, padLeft=32) 103 self.left_content_container = ContainerAutoSize(name='left_content_container', parent=left_prop_container, align=Align.VERTICALLY_CENTERED) 104 self.description_container = ContainerAutoSize(name='description_container', parent=self.left_content_container, align=Align.TOTOP, width=168) 105 self.offer_name_label = TextBody(name='offer_name_label', parent=self.description_container, align=Align.TOTOP, color=eveColor.OMEGA_ACCENT, bold=True) 106 self.price_container = ContainerAutoSize(name='price_container', parent=self.description_container, align=Align.TOTOP, height=48, padTop=5) 107 self.construct_currency_toggle() 108 109 def construct_currency_price(self): 110 TextCustom(name='price_label', parent=self.price_container, align=Align.TOLEFT, text=self.total_price_text, color=TextColor.HIGHLIGHT, fontsize=40) 111 112 def construct_plex_price(self): 113 plex_icon = PlexInfoIcon(name='plex_icon', parent=self.price_container, align=Align.TOLEFT, width=40, height=40, padLeft=-8) 114 price_plex_label = TextCustom(name='price_label', parent=self.price_container, align=Align.TOLEFT, text=self.total_price_text, color=TextColor.HIGHLIGHT, fontsize=40, padLeft=-4) 115 if not self.has_enough_plex: 116 plex_icon.icon_color = TextColor.DISABLED 117 price_plex_label.color = TextColor.DISABLED 118 119 def construct_currency_toggle(self): 120 self.currency_container = ContainerAutoSize(name='currency_container', parent=self.left_content_container, align=Align.TOTOP, height=32, opacity=0.0) 121 self.currency_label = TextBody(name='currency_label', parent=self.currency_container, align=Align.TOLEFT, color=TextColor.HIGHLIGHT, padTop=6) 122 currency_toggle_container = Container(name='currency_toggle_container', parent=self.currency_container, align=Align.TOLEFT, width=38, padding=(8, 0, 8, 0)) 123 self.currency_toggle = CurrencyToggle(name='currency_toggle', parent=currency_toggle_container, align=Align.CENTER) 124 self.currency_toggle.on_switch_changed.connect(self.on_show_price_in_plex_change) 125 self.currency_toggle.Disable() 126 self.plex_label = TextBody(name='plex_label', parent=self.currency_container, align=Align.TOLEFT, text=GetByLabel('UI/Common/PLEX'), color=eveColor.PLEX_YELLOW, padTop=6) 127 plex_icon_container = Container(name='plex_icon_container', parent=self.currency_container, align=Align.TOLEFT, width=14) 128 PlexInfoIcon(name='plex_icon', parent=plex_icon_container, align=Align.CENTER, width=14, height=14, padLeft=6) 129 130 def construct_center_content(self): 131 self.center_container = Container(name='center_container', parent=self.content_container, align=Align.TOLEFT_PROP, width=0.34, padding=(0, 8, 0, 8)) 132 self.products_label = TextBody(name='products_label', parent=self.center_container, align=Align.TOTOP, textAlign=TextAlign.CENTER, color=eveColor.OMEGA_ACCENT, opacity=0.6) 133 self.products_scroll_container = ScrollContainer(name='products_scroll_container', parent=self.center_container, align=Align.TOTOP, height=92, padTop=12) 134 self.product_container = FlowContainer(name='product_container', parent=self.products_scroll_container, align=Align.TOTOP, contentAlignment=AxisAlignment.CENTER, contentSpacing=(8, 8)) 135 136 def construct_right_content(self): 137 button_group = ButtonGroup(name='button_group', parent=self.content_container, align=Align.CENTERRIGHT, orientation=Axis.VERTICAL, density=Density.EXPANDED, padRight=32) 138 self.upgrade_button = Button(name='upgrade_button', parent=button_group, label=GetByLabel('UI/OmegaUpsell/Upgrade'), texturePath=eveicon.omega, variant=ButtonVariant.PRIMARY, style=ButtonStyle.MONETIZATION, func=self.on_upgrade_button) 139 Button(name='more_offers_button', parent=button_group, label=GetByLabel('UI/OmegaUpsell/MoreOffers'), variant=ButtonVariant.GHOST, style=ButtonStyle.MONETIZATION, func=self.on_more_offers_button) 140 141 def update(self): 142 self.kill_update_thread() 143 self._update_thread = uthread2.start_tasklet(self.update_async) 144 145 def update_async(self): 146 self.load_offer() 147 if not self.offer: 148 return 149 self.offer_name_label.text = self.offer.localized_name 150 self.update_price() 151 self.update_currency() 152 self.update_products() 153 self.update_button() 154 if self.is_omega_offer: 155 self.products_label.text = None 156 else: 157 self.products_label.text = GetByLabel('UI/OmegaUpsell/PackContentsLabel') 158 animations.FadeOut(self.loading_wheel) 159 animations.FadeIn(self.content_container) 160 161 def update_price(self): 162 self.price_container.Flush() 163 if self.show_price_in_plex: 164 self.construct_plex_price() 165 else: 166 self.construct_currency_price() 167 animations.FadeTo(self.price_container, 0.0, 1.0) 168 169 def update_currency(self): 170 if not self.offer.has_plex_price: 171 self.currency_container.display = False 172 self.description_container.align = Align.CENTER 173 return 174 currency_code = self.monetary_currency_code 175 currency_symbol = CURRENCY_SYMBOL.get(currency_code, currency_code) 176 currency_text = CURRENCY_TEXT.get(currency_code, currency_code) 177 self.currency_label.text = '%s %s' % (currency_symbol, currency_text) 178 self.currency_toggle.Enable() 179 animations.FadeIn(self.currency_container) 180 181 def update_products(self): 182 self.product_container.Flush() 183 if self.is_omega_offer: 184 return 185 for product, quantity in self.products: 186 self.get_product_icon_class(product.product_type)(parent=self.product_container, product=product, quantity=quantity) 187 188 self.products_label.top = 0 if len(self.products) > 5 else 24 189 190 def get_product_icon_class(self, product_type): 191 if product_type == StoreProductType.ITEM: 192 return ProductItemIcon 193 if product_type == StoreProductType.LICENSE: 194 return ProductLicenseIcon 195 if product_type == StoreProductType.BPC: 196 return ProductBPCIcon 197 if product_type == StoreProductType.PLEX: 198 return ProductPlexIcon 199 if product_type == StoreProductType.CHAR_TRANSFER: 200 return ProductCharTransferIcon 201 raise RuntimeError('Unsupported store product type: %s' % product_type) 202 203 def update_button(self): 204 if self.show_price_in_plex and not self.has_enough_plex: 205 self.upgrade_button.Disable() 206 self.upgrade_button.tooltipPanelClassInfo = BuyPlexTooltip(required_amount=int(self.total_price)) 207 else: 208 self.upgrade_button.Enable() 209 self.upgrade_button.tooltipPanelClassInfo = None 210 211 def load_offer(self): 212 if self.offer_id and self.offer: 213 return 214 try: 215 self.offer_id = get_offer_provider_svc().get_default_omega_starter_pack_offer_id() 216 if self.offer_id is None: 217 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 218 raise RuntimeError('No default Omega Subscription offer provided') 219 self.offer = get_offer_provider_svc().get_omega_subscription_offer(self.offer_id) 220 self.load_products() 221 except TimeoutException: 222 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 223 except Exception: 224 message_player(GetByLabel('UI/Common/CannotConnectToServer')) 225 raise 226 227 def load_products(self): 228 self.products = [] 229 for product_id, quantity in self.offer.products: 230 product = get_offer_provider_svc().get_product_definition(product_id) 231 self.products.append((product, quantity)) 232 233 def on_show_price_in_plex_change(self, toggle, is_checked): 234 self.show_price_in_plex = is_checked 235 self.update() 236 237 def on_upgrade_button(self, *args): 238 if not self.offer_id: 239 return 240 omegaUpsellSignals.upgrade_to_offer(self.offer_id, self.show_price_in_plex) 241 242 def on_more_offers_button(self, *args): 243 omegaUpsellSignals.on_more_offers_button() 244 245 @property 246 def has_enough_plex(self): 247 return get_repository().get_balance() >= self.total_price 248 249 @property 250 def total_price_text(self): 251 return get_price_text(price=self.total_price, currency=self.currency_code, useSpace=False) 252 253 @property 254 def total_price(self): 255 return self.total_price_in_cents / 100.0 256 257 @property 258 def total_price_in_cents(self): 259 if self.show_price_in_plex: 260 return self.offer.price_before_tax.plex_price_in_cents 261 else: 262 return self.offer.price_before_tax.currency_price_in_cents 263 264 @property 265 def currency_code(self): 266 if self.show_price_in_plex: 267 return OFFER_CURRENCY_PLEX 268 else: 269 return self.monetary_currency_code 270 271 @property 272 def monetary_currency_code(self): 273 return self.offer.price_before_tax.currency_code 274 275 @property 276 def is_omega_offer(self): 277 if len(self.products) == 1: 278 product, quantity = self.products[0] 279 return product.product_type == StoreProductType.LICENSE 280 return False 281 282 283 class ProductItemIcon(ItemIcon): 284 default_align = Align.NOALIGN 285 default_width = 40 286 default_height = 40 287 288 def __init__(self, product, quantity, *args, **kwargs): 289 self.product = product 290 self.quantity = quantity 291 super(ProductItemIcon, self).__init__(typeID=self.product.type_id, **kwargs) 292 293 def LoadTooltipPanel(self, tooltip_panel, *args): 294 tooltip_panel.LoadGeneric2ColumnTemplate() 295 tooltip_panel.state = uiconst.UI_NORMAL 296 name = evetypes.GetName(self.typeID) 297 tooltip_panel.AddLabelMedium(text='%s x %s' % (self.quantity, name) if self.quantity > 1 else name, align=Align.CENTERLEFT) 298 tooltip_panel.AddInfoIcon(typeID=self.typeID, align=Align.CENTERRIGHT) 299 300 301 class ProductLicenseIcon(Container): 302 default_align = Align.NOALIGN 303 default_state = uiconst.UI_NORMAL 304 default_width = 40 305 default_height = 40 306 307 def __init__(self, product, quantity, *args, **kwargs): 308 super(ProductLicenseIcon, self).__init__(*args, **kwargs) 309 self.product = product 310 self.quantity = quantity 311 self.construct_layout() 312 313 def construct_layout(self): 314 Sprite(parent=self, align=Align.CENTER, state=uiconst.UI_DISABLED, texturePath=eveicon.omega, pos=(0, 315 0, 316 self.default_width, 317 self.default_height)) 318 319 def LoadTooltipPanel(self, tooltip_panel, *args): 320 tooltip_panel.LoadStandardSpacing() 321 tooltip_panel.state = uiconst.UI_NORMAL 322 tooltip_panel.AddLabelMedium(text=GetByLabel('UI/OmegaUpsell/LicenseProductTooltip', amount=self.product.duration * appConst.SEC / appConst.DAY), align=Align.CENTERLEFT) 323 324 325 class BPCItemIcon(ItemIcon): 326 327 def __init__(self, product, quantity, *args, **kwargs): 328 self.product = product 329 self.quantity = quantity 330 super(BPCItemIcon, self).__init__(*args, **kwargs) 331 332 def LoadTooltipPanel(self, tooltip_panel, *args): 333 tooltip_panel.LoadGeneric2ColumnTemplate() 334 tooltip_panel.state = uiconst.UI_NORMAL 335 tooltip_panel.AddLabelMedium(text=GetByLabel('UI/OmegaUpsell/BPCProductTooltip', name=evetypes.GetName(self.typeID), amount=self.product.nb_runs), align=Align.CENTERLEFT) 336 bp_data = sm.GetService('blueprintSvc').GetBlueprintTypeCopy(typeID=self.typeID, original=False) 337 infoIcon = InfoIcon(typeID=self.typeID, itemID=self.itemID, align=Align.CENTERRIGHT, left=0, top=0, abstractinfo=KeyVal(fullBlueprintData=bp_data)) 338 tooltip_panel.AddCell(infoIcon) 339 340 341 class ProductBPCIcon(Container): 342 default_align = Align.NOALIGN 343 default_width = 40 344 default_height = 40 345 346 def __init__(self, product, quantity, *args, **kwargs): 347 super(ProductBPCIcon, self).__init__(*args, **kwargs) 348 self.product = product 349 self.quantity = quantity 350 self.construct_layout() 351 352 def construct_layout(self): 353 icon = BPCItemIcon(parent=self, align=Align.CENTER, product=self.product, typeID=self.product.type_id, isCopy=True, pos=(0, 354 0, 355 self.default_width, 356 self.default_height)) 357 358 359 class ProductPlexIcon(Container): 360 default_align = Align.NOALIGN 361 default_width = 40 362 default_height = 40 363 364 def __init__(self, product, quantity, *args, **kwargs): 365 super(ProductPlexIcon, self).__init__(*args, **kwargs) 366 self.product = product 367 self.quantity = quantity 368 self.construct_layout() 369 370 def construct_layout(self): 371 PlexInfoIcon(parent=self, align=Align.CENTER, amount=self.product.plex, pos=(0, 372 0, 373 self.default_width, 374 self.default_height)) 375 376 377 class ProductCharTransferIcon(Container): 378 default_align = Align.NOALIGN 379 default_state = uiconst.UI_NORMAL 380 default_width = 40 381 default_height = 40 382 383 def __init__(self, product, quantity, *args, **kwargs): 384 super(ProductCharTransferIcon, self).__init__(*args, **kwargs) 385 self.product = product 386 self.quantity = quantity 387 self.construct_layout() 388 389 def construct_layout(self): 390 Sprite(parent=self, align=Align.CENTER, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/CharacterSelection/silhuette.png', color=eveColor.WHITE, pos=(0, 391 0, 392 self.default_width, 393 self.default_height)) 394 395 def LoadTooltipPanel(self, tooltip_panel, *args): 396 tooltip_panel.LoadStandardSpacing() 397 tooltip_panel.state = uiconst.UI_NORMAL 398 tooltip_panel.AddLabelMedium(text=self.product.localized_name, align=Align.CENTERLEFT)