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.
183 lines
5.7 KiB
GDScript
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))
|