Files
step3/scripts/ui/session_expiry_dialog.gd
T
hz f17797becc Don't show the session-expiry warning to actively-used sessions
The warning was purely a token-expiry countdown with no concept of
user activity, so it interrupted people who had been clicking through
questions the whole time. AuthState now tracks real input activity
globally and silently refreshes the session in the background when
the user has interacted recently, only surfacing the "Stay logged in?"
dialog for sessions that are genuinely idle. A failed background
refresh still surfaces the dialog rather than silently logging the
user out.
2026-07-22 22:52:38 -05:00

183 lines
5.7 KiB
GDScript

extends Control
const Brand := preload("res://scripts/ui/medmind_style.gd")
signal logout_requested
var panel: PanelContainer
var title_label: Label
var message_label: Label
var error_label: Label
var stay_button: Button
var logout_button: Button
func _ready() -> void:
visible = false
top_level = true
mouse_filter = Control.MOUSE_FILTER_STOP
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_build_ui()
AuthState.session_warning.connect(_show_warning)
AuthState.session_countdown_changed.connect(_update_countdown)
AuthState.session_refresh_started.connect(_refresh_started)
AuthState.session_refreshed.connect(_refresh_succeeded)
AuthState.session_refresh_failed.connect(_refresh_failed)
UIThemeService.theme_changed.connect(func(_dark: bool): _apply_theme())
resized.connect(_refresh_size)
_refresh_size()
_apply_theme()
func _build_ui() -> void:
var dim := ColorRect.new()
dim.color = Color(0, 0, 0, 0.74)
dim.mouse_filter = Control.MOUSE_FILTER_STOP
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(dim)
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(center)
panel = PanelContainer.new()
panel.custom_minimum_size = Vector2(520, 280)
center.add_child(panel)
var content := VBoxContainer.new()
content.add_theme_constant_override("separation", 16)
panel.add_child(content)
title_label = Label.new()
title_label.text = "Stay logged in?"
title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title_label.add_theme_font_size_override("font_size", 28)
content.add_child(title_label)
message_label = Label.new()
message_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
message_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
message_label.add_theme_font_size_override("font_size", 17)
content.add_child(message_label)
error_label = Label.new()
error_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
error_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
error_label.add_theme_font_size_override("font_size", 14)
content.add_child(error_label)
var actions := HBoxContainer.new()
actions.add_theme_constant_override("separation", 12)
content.add_child(actions)
logout_button = Button.new()
logout_button.text = "Log out"
logout_button.custom_minimum_size = Vector2(150, 48)
logout_button.pressed.connect(_request_logout)
actions.add_child(logout_button)
stay_button = Button.new()
stay_button.text = "Stay logged in"
stay_button.custom_minimum_size = Vector2(0, 48)
stay_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
stay_button.pressed.connect(AuthState.request_session_refresh)
actions.add_child(stay_button)
func _show_warning(seconds_remaining: int) -> void:
error_label.text = ""
stay_button.disabled = false
logout_button.disabled = false
stay_button.text = "Stay logged in"
visible = true
_update_countdown(seconds_remaining)
move_to_front()
BlockSessionService.pause_current_question_timer()
func _update_countdown(seconds_remaining: int) -> void:
if not visible:
return
var minutes := floori(float(seconds_remaining) / 60.0)
var seconds := seconds_remaining % 60
message_label.text = "Your session expires in %02d:%02d. Renew it to continue studying." % [minutes, seconds]
func _refresh_started() -> void:
if not visible:
return
stay_button.disabled = true
logout_button.disabled = true
stay_button.text = "Renewing..."
error_label.text = ""
func _refresh_succeeded() -> void:
_hide_dialog()
func _refresh_failed(message: String, terminal: bool) -> void:
visible = true
move_to_front()
error_label.text = message
stay_button.text = "Stay logged in"
stay_button.disabled = terminal
logout_button.disabled = false
func _request_logout() -> void:
logout_requested.emit()
func _hide_dialog() -> void:
visible = false
error_label.text = ""
stay_button.text = "Stay logged in"
stay_button.disabled = false
logout_button.disabled = false
BlockSessionService.resume_current_question_timer()
func dismiss_for_logout() -> void:
if visible:
_hide_dialog()
func _unhandled_input(event: InputEvent) -> void:
if visible and event.is_action_pressed("ui_cancel"):
get_viewport().set_input_as_handled()
func _refresh_size() -> void:
if panel == null:
return
panel.custom_minimum_size = Vector2(
minf(520.0, maxf(288.0, size.x - 32.0)),
minf(280.0, maxf(250.0, size.y - 32.0)))
func _apply_theme() -> void:
if panel == null:
return
var dark := UIThemeService.is_dark_mode()
var panel_style := Brand.panel_box(dark)
panel_style.bg_color.a = 1.0
panel_style.content_margin_left = 32
panel_style.content_margin_right = 32
panel_style.content_margin_top = 30
panel_style.content_margin_bottom = 30
panel.add_theme_stylebox_override("panel", panel_style)
for label in [title_label, message_label]:
label.add_theme_color_override("font_color", Brand.text_color(dark))
error_label.add_theme_color_override("font_color", Color("fb7185") if dark else Color("be123c"))
stay_button.add_theme_stylebox_override("normal", Brand.gradient_button_box())
stay_button.add_theme_stylebox_override("hover", Brand.gradient_button_box())
stay_button.add_theme_stylebox_override("pressed", Brand.gradient_button_box())
stay_button.add_theme_color_override("font_color", Color.WHITE)
stay_button.add_theme_color_override("font_hover_color", Color.WHITE)
logout_button.add_theme_stylebox_override("normal", Brand.secondary_button_box(dark))
logout_button.add_theme_stylebox_override("hover", Brand.secondary_button_box(dark, true))
logout_button.add_theme_stylebox_override("pressed", Brand.secondary_button_box(dark, true))
logout_button.add_theme_color_override("font_color", Brand.text_color(dark))
logout_button.add_theme_color_override("font_hover_color", Brand.text_color(dark))