227 lines
7.8 KiB
GDScript
227 lines
7.8 KiB
GDScript
extends VBoxContainer
|
|
|
|
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
|
|
|
var main
|
|
var search_input: LineEdit
|
|
var system_filter: OptionButton
|
|
var discipline_filter: OptionButton
|
|
var sort_filter: OptionButton
|
|
var status_label: Label
|
|
var retry_button: Button
|
|
var list_container: VBoxContainer
|
|
var load_more_button: Button
|
|
var debounce_timer: Timer
|
|
var list_scroll: ScrollContainer
|
|
var saved_scroll_position := 0
|
|
var pending_open_question_id := ""
|
|
|
|
|
|
func _ready() -> void:
|
|
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
add_theme_constant_override("separation", 16)
|
|
_build_filters()
|
|
status_label = Label.new()
|
|
status_label.add_theme_font_override("font", Brand.font)
|
|
status_label.add_theme_color_override("font_color", Brand.muted_color(UIThemeService.is_dark_mode()))
|
|
add_child(status_label)
|
|
retry_button = Button.new()
|
|
retry_button.text = "Retry"
|
|
retry_button.visible = false
|
|
retry_button.custom_minimum_size = Vector2(100, 42)
|
|
retry_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
|
retry_button.pressed.connect(_retry)
|
|
add_child(retry_button)
|
|
list_scroll = ScrollContainer.new()
|
|
list_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
add_child(list_scroll)
|
|
list_container = VBoxContainer.new()
|
|
list_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
list_container.add_theme_constant_override("separation", 10)
|
|
list_scroll.add_child(list_container)
|
|
load_more_button = Button.new()
|
|
load_more_button.text = "Load more"
|
|
load_more_button.custom_minimum_size = Vector2(0, 44)
|
|
load_more_button.pressed.connect(func(): BookmarkService.load_bookmarks(false))
|
|
list_container.add_child(load_more_button)
|
|
debounce_timer = Timer.new()
|
|
debounce_timer.one_shot = true
|
|
debounce_timer.wait_time = 0.35
|
|
debounce_timer.timeout.connect(_reload)
|
|
add_child(debounce_timer)
|
|
BookmarkService.list_changed.connect(_render)
|
|
BookmarkService.list_failed.connect(_on_list_failed)
|
|
BookmarkService.question_loaded.connect(_on_question_loaded)
|
|
BookmarkService.question_failed.connect(_on_question_load_failed)
|
|
UIThemeService.theme_changed.connect(func(_dark: bool): _render())
|
|
|
|
|
|
func _build_filters() -> void:
|
|
var row := HFlowContainer.new()
|
|
row.add_theme_constant_override("separation", 10)
|
|
add_child(row)
|
|
search_input = LineEdit.new()
|
|
search_input.placeholder_text = "Search saved questions"
|
|
search_input.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
search_input.custom_minimum_size = Vector2(260, 44)
|
|
search_input.text_changed.connect(func(_value: String): debounce_timer.start())
|
|
row.add_child(search_input)
|
|
system_filter = _add_filter(row, "All systems")
|
|
discipline_filter = _add_filter(row, "All disciplines")
|
|
sort_filter = _add_filter(row, "Newest", ["Oldest"])
|
|
|
|
|
|
func _add_filter(parent: Container, first: String, extras: Array = []) -> OptionButton:
|
|
var control := OptionButton.new()
|
|
control.add_item(first)
|
|
for text in extras:
|
|
control.add_item(str(text))
|
|
control.custom_minimum_size = Vector2(150, 44)
|
|
control.item_selected.connect(func(_index: int): _reload())
|
|
parent.add_child(control)
|
|
return control
|
|
|
|
|
|
func on_show(_payload: Variant = null) -> void:
|
|
_populate_categories()
|
|
if not BookmarkService.remote_list_loaded:
|
|
BookmarkService.load_bookmarks(true, _filters())
|
|
_render()
|
|
|
|
|
|
func on_hide() -> void:
|
|
if list_scroll != null:
|
|
saved_scroll_position = int(list_scroll.scroll_vertical)
|
|
|
|
|
|
func _populate_categories() -> void:
|
|
_populate_filter(system_filter, "All systems", BookmarkService.available_systems)
|
|
_populate_filter(discipline_filter, "All disciplines", BookmarkService.available_disciplines)
|
|
|
|
|
|
func _populate_filter(control: OptionButton, first: String, values: Array) -> void:
|
|
var selected_text := control.get_item_text(control.selected) if control.item_count > 0 else first
|
|
control.clear()
|
|
control.add_item(first)
|
|
for value in values:
|
|
if str(value) != "" and str(value) != "All Systems":
|
|
control.add_item(str(value))
|
|
for index in range(control.item_count):
|
|
if control.get_item_text(index) == selected_text:
|
|
control.select(index)
|
|
break
|
|
|
|
|
|
func _filters() -> Dictionary:
|
|
return {
|
|
"query": search_input.text,
|
|
"system": "" if system_filter.selected == 0 else system_filter.get_item_text(system_filter.selected),
|
|
"discipline": "" if discipline_filter.selected == 0 else discipline_filter.get_item_text(discipline_filter.selected),
|
|
"sort": "oldest" if sort_filter.selected == 1 else "newest"
|
|
}
|
|
|
|
|
|
func _reload() -> void:
|
|
BookmarkService.load_bookmarks(true, _filters())
|
|
_render()
|
|
|
|
|
|
func _render() -> void:
|
|
if list_container == null:
|
|
return
|
|
if not visible:
|
|
return
|
|
_populate_categories()
|
|
for child in list_container.get_children():
|
|
if child != load_more_button:
|
|
child.queue_free()
|
|
status_label.text = "Loading bookmarks..." if BookmarkService.list_loading else BookmarkService.list_error
|
|
retry_button.visible = BookmarkService.list_error != ""
|
|
if not BookmarkService.list_loading and BookmarkService.items.is_empty() and BookmarkService.list_error == "":
|
|
status_label.text = "No saved questions match these filters."
|
|
var insert_index := 0
|
|
for item in BookmarkService.items:
|
|
var card := _bookmark_card(item)
|
|
list_container.add_child(card)
|
|
list_container.move_child(card, insert_index)
|
|
insert_index += 1
|
|
load_more_button.visible = BookmarkService.has_more
|
|
load_more_button.disabled = BookmarkService.list_loading
|
|
if list_scroll != null:
|
|
list_scroll.set_deferred("scroll_vertical", saved_scroll_position)
|
|
|
|
|
|
func _bookmark_card(item: Dictionary) -> Control:
|
|
var dark := UIThemeService.is_dark_mode()
|
|
var card := PanelContainer.new()
|
|
card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false, Brand.TEAL))
|
|
var row := HFlowContainer.new()
|
|
row.add_theme_constant_override("separation", 14)
|
|
card.add_child(row)
|
|
var content := VBoxContainer.new()
|
|
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
row.add_child(content)
|
|
var stem := Label.new()
|
|
stem.text = str(item.get("stem", ""))
|
|
stem.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
stem.add_theme_font_override("font", Brand.font)
|
|
stem.add_theme_font_size_override("font_size", 16)
|
|
content.add_child(stem)
|
|
var metadata := Label.new()
|
|
metadata.text = "%s | %s | Difficulty %d%s" % [
|
|
str(item.get("system", "")), str(item.get("discipline", "")),
|
|
int(item.get("difficulty", 1)), " | Image" if bool(item.get("has_media", false)) else ""]
|
|
metadata.add_theme_font_override("font", Brand.font)
|
|
metadata.add_theme_font_size_override("font_size", 13)
|
|
metadata.add_theme_color_override("font_color", Brand.muted_color(dark))
|
|
content.add_child(metadata)
|
|
var open := Button.new()
|
|
open.text = "Practice"
|
|
open.custom_minimum_size = Vector2(105, 42)
|
|
open.pressed.connect(_open_question.bind(str(item.get("question_id", ""))))
|
|
row.add_child(open)
|
|
var remove := Button.new()
|
|
remove.icon = preload("res://assets/icons/lucide/bookmark.svg")
|
|
remove.tooltip_text = "Remove bookmark"
|
|
remove.custom_minimum_size = Vector2(44, 42)
|
|
remove.pressed.connect(BookmarkService.set_bookmarked.bind(str(item.get("question_id", "")), false))
|
|
row.add_child(remove)
|
|
return card
|
|
|
|
|
|
func _open_question(question_id: String) -> void:
|
|
pending_open_question_id = question_id
|
|
status_label.text = "Loading question..."
|
|
retry_button.visible = false
|
|
BookmarkService.begin_practice(question_id)
|
|
|
|
|
|
func _on_question_loaded(question: Dictionary) -> void:
|
|
if not visible:
|
|
return
|
|
pending_open_question_id = ""
|
|
if BlockSessionService.start_bookmark_question(question):
|
|
main.show_screen("player")
|
|
|
|
|
|
func _on_list_failed(message: String) -> void:
|
|
status_label.text = message
|
|
retry_button.visible = true
|
|
|
|
|
|
func _on_question_load_failed(message: String) -> void:
|
|
if not visible:
|
|
return
|
|
status_label.text = message
|
|
retry_button.visible = true
|
|
|
|
|
|
func _retry() -> void:
|
|
retry_button.visible = false
|
|
if pending_open_question_id != "":
|
|
status_label.text = "Loading question..."
|
|
BookmarkService.begin_practice(pending_open_question_id)
|
|
else:
|
|
_reload()
|