191 lines
7.2 KiB
GDScript
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
|