Files
step3/scripts/ui/access_store_screen.gd

191 lines
7.2 KiB
GDScript

extends MarginContainer
const Brand := preload("res://scripts/ui/medmind_style.gd")
var main: Control
var summary: Label
var quota: Label
var status_label: Label
var token_labels: Dictionary = {}
var action_buttons: Array[Button] = []
var reset_dialog: ConfirmationDialog
func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_build_ui()
AccessService.status_changed.connect(_render)
AccessService.operation_succeeded.connect(_on_success)
AccessService.operation_failed.connect(_on_failure)
AccessService.checkout_started.connect(_on_checkout_started)
UIThemeService.theme_changed.connect(refresh_theme_controls)
AccessService.refresh()
func on_show(_payload: Variant = null) -> void:
AccessService.refresh()
_render(AccessService.status)
func _build_ui() -> void:
var scroll := ScrollContainer.new()
scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
add_child(scroll)
var page := VBoxContainer.new()
page.size_flags_horizontal = Control.SIZE_EXPAND_FILL
page.add_theme_constant_override("separation", 20)
scroll.add_child(page)
var title := Label.new()
title.text = "Credits & Access"
title.add_theme_font_override("font", Brand.font)
title.add_theme_font_size_override("font_size", 36)
page.add_child(title)
var intro := Label.new()
intro.text = "Study 20 QBank questions in a rolling 24-hour window. Eligible first attempts earn credits; 100 credits unlocks unlimited QBank access for 24 hours."
intro.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
intro.add_theme_font_override("font", Brand.font)
intro.add_theme_font_size_override("font_size", 17)
page.add_child(intro)
var overview := _panel()
page.add_child(overview)
var overview_content := VBoxContainer.new()
overview_content.add_theme_constant_override("separation", 10)
overview.add_child(overview_content)
summary = _label("", 24)
quota = _label("", 17)
overview_content.add_child(summary)
overview_content.add_child(quota)
var redeem := _button("Redeem 100 credits for 24 hours", AccessService.redeem_credits)
overview_content.add_child(redeem)
var token_grid := GridContainer.new()
token_grid.columns = 3
token_grid.add_theme_constant_override("h_separation", 16)
token_grid.add_theme_constant_override("v_separation", 16)
page.add_child(token_grid)
_add_access_card(token_grid, "day", "1 day", "$1.00", "24 hours")
_add_access_card(token_grid, "week", "1 week", "$5.00", "168 hours")
_add_access_card(token_grid, "month", "1 month", "$20.00", "30 days")
var extras := _panel()
page.add_child(extras)
var extras_content := VBoxContainer.new()
extras_content.add_theme_constant_override("separation", 12)
extras.add_child(extras_content)
extras_content.add_child(_label("Study tools", 24))
token_labels["exam"] = _label("", 16)
extras_content.add_child(token_labels["exam"])
extras_content.add_child(_button("Buy exam simulation token — $1.99",
func(): AccessService.begin_checkout("exam_token_1")))
token_labels["reset"] = _label("", 16)
extras_content.add_child(token_labels["reset"])
extras_content.add_child(_button("Buy QBank reset token — $4.99",
func(): AccessService.begin_checkout("qbank_reset_1")))
extras_content.add_child(_button("Use QBank reset token", _confirm_reset))
status_label = _label("", 15)
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
page.add_child(status_label)
reset_dialog = ConfirmationDialog.new()
reset_dialog.title = "Reset QBank progress?"
reset_dialog.dialog_text = "This starts a new QBank cycle. Questions become unused and credit-eligible again. Historical aggregate samples, bookmarks, notes, and review content remain."
reset_dialog.ok_button_text = "Reset QBank"
reset_dialog.confirmed.connect(AccessService.reset_qbank)
add_child(reset_dialog)
refresh_theme_controls()
func _add_access_card(parent: GridContainer, token: String, heading: String, price: String, duration: String) -> void:
var panel := _panel()
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
parent.add_child(panel)
var content := VBoxContainer.new()
content.add_theme_constant_override("separation", 10)
panel.add_child(content)
content.add_child(_label(heading, 24))
content.add_child(_label("%s of unlimited QBank access" % duration, 15))
token_labels[token] = _label("", 15)
content.add_child(token_labels[token])
content.add_child(_button("Buy token — %s" % price,
func(): AccessService.begin_checkout("access_%s_1" % token)))
content.add_child(_button("Activate token",
func(): AccessService.activate_token(token)))
func _render(value: Dictionary) -> void:
if summary == null:
return
var credits := int(value.get("credits", 0))
var unlimited := bool(value.get("unlimited", false))
var expiry := str(value.get("unlimitedUntilUtc", ""))
summary.text = "%d credits • %s" % [
credits,
("Unlimited QBank active until %s" % expiry) if unlimited else "Free QBank access"
]
quota.text = "%d of %d free questions remaining in the rolling window" % [
int(value.get("questionsRemaining", 20)),
int(value.get("freeQuestionLimit", 20))
]
token_labels.get("day", _label()).text = "%d available" % int(value.get("dayTokens", 0))
token_labels.get("week", _label()).text = "%d available" % int(value.get("weekTokens", 0))
token_labels.get("month", _label()).text = "%d available" % int(value.get("monthTokens", 0))
token_labels.get("exam", _label()).text = "Exam tokens: %d" % int(value.get("examTokens", 0))
token_labels.get("reset", _label()).text = "QBank reset tokens: %d" % int(value.get("resetTokens", 0))
func _confirm_reset() -> void:
reset_dialog.popup_centered(Vector2i(620, 260))
func _on_success(message: String) -> void:
status_label.text = message
status_label.add_theme_color_override("font_color", Brand.SUCCESS)
func _on_failure(message: String) -> void:
status_label.text = message
status_label.add_theme_color_override("font_color", Color("ef4444"))
func _on_checkout_started(_purchase_id: String, _url: String) -> void:
status_label.text = "Complete checkout in the secure payment window."
func refresh_theme_controls() -> void:
for button in action_buttons:
var dark := UIThemeService.is_dark_mode()
button.add_theme_font_override("font", Brand.font)
button.add_theme_color_override("font_color", Brand.text_color(dark))
button.add_theme_stylebox_override("normal", Brand.secondary_button_box(dark))
button.add_theme_stylebox_override("hover", Brand.secondary_button_box(dark, true))
button.add_theme_stylebox_override("pressed", Brand.secondary_button_box(dark, true))
func _panel() -> PanelContainer:
var panel := PanelContainer.new()
panel.add_theme_stylebox_override("panel", Brand.panel_box(UIThemeService.is_dark_mode()))
return panel
func _label(text := "", size := 16) -> Label:
var label := Label.new()
label.text = text
label.add_theme_font_override("font", Brand.font)
label.add_theme_font_size_override("font_size", size)
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
return label
func _button(text: String, callback: Callable) -> Button:
var button := Button.new()
button.text = text
button.custom_minimum_size = Vector2(0, 48)
button.pressed.connect(callback)
action_buttons.append(button)
return button