Add daily study missions

This commit is contained in:
hz
2026-08-01 04:02:24 -05:00
parent 2bd2628009
commit ecd5da9162
20 changed files with 619 additions and 6 deletions
+93
View File
@@ -23,6 +23,7 @@ var focus_content: VBoxContainer
var review_list: VBoxContainer
var recent_list: VBoxContainer
var weekly_summary: VBoxContainer
var mission_content: VBoxContainer
var metric_values: Dictionary = {}
var metric_details: Dictionary = {}
var metric_trends: Dictionary = {}
@@ -34,6 +35,7 @@ var pending_review_launch := false
var review_queue_service: Variant
var trend_mode := "rating"
var mastery_rows: Array[BoxContainer] = []
var mission_rows: Array[BoxContainer] = []
func _ready() -> void:
@@ -41,6 +43,8 @@ func _ready() -> void:
review_queue_service = get_node("/root/ReviewQueueService")
DashboardService.dashboard_changed.connect(_render)
DashboardService.dashboard_failed.connect(_show_error)
DailyMissionService.missions_changed.connect(_render_missions)
DailyMissionService.missions_failed.connect(_show_mission_error)
review_queue_service.question_loaded.connect(_on_review_question_loaded)
review_queue_service.question_failed.connect(_on_review_question_failed)
review_queue_service.pass_completed.connect(_on_review_pass_completed)
@@ -55,6 +59,7 @@ func on_show(_payload: Variant = null) -> void:
else:
_render()
DashboardService.refresh()
DailyMissionService.refresh()
func _build() -> void:
@@ -65,6 +70,7 @@ func _build() -> void:
metric_details.clear()
metric_trends.clear()
mastery_rows.clear()
mission_rows.clear()
scroll = ScrollContainer.new()
scroll.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
@@ -82,6 +88,9 @@ func _build() -> void:
stat_card_grid.add_theme_constant_override("h_separation", 12)
stat_card_grid.add_theme_constant_override("v_separation", 12)
content.add_child(stat_card_grid)
mission_content = VBoxContainer.new()
mission_content.add_theme_constant_override("separation", 10)
content.add_child(_card(mission_content, 16))
qbank_sparkline = DashboardSparkline.new()
qbank_sparkline.custom_minimum_size = Vector2(0, 34)
@@ -211,6 +220,88 @@ func _render() -> void:
activity_chart.set_days(weekly, UIThemeService.is_dark_mode())
_render_weekly(data, weekly)
_render_recent(data.get("recentActivity", []))
_render_missions()
func _render_missions() -> void:
if mission_content == null:
return
_clear(mission_content)
mission_rows.clear()
var header := HBoxContainer.new()
header.add_child(_section_title("Daily missions"))
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header.add_child(spacer)
header.add_child(_label("Resets at 00:00 UTC", 11, Brand.muted_color(UIThemeService.is_dark_mode())))
mission_content.add_child(header)
var missions: Array = DailyMissionService.state.get("missions", [])
if missions.is_empty():
mission_content.add_child(_label("Loading today's focused study missions...", 14, Brand.muted_color(UIThemeService.is_dark_mode())))
return
var replacement_available := bool(DailyMissionService.state.get("replacementAvailable", false))
for mission_value in missions:
var mission: Dictionary = mission_value
var row := BoxContainer.new()
row.vertical = size.x < 700.0
mission_rows.append(row)
row.add_theme_constant_override("separation", 10)
var details := VBoxContainer.new()
details.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var completed := bool(mission.get("completed", false))
var title := _label(str(mission.get("title", "Study mission")), 14,
Brand.SUCCESS if completed else Brand.text_color(UIThemeService.is_dark_mode()), true)
title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
details.add_child(title)
var progress_value := mini(int(mission.get("progress", 0)), int(mission.get("target", 1)))
var target := maxi(1, int(mission.get("target", 1)))
var progress := ProgressBar.new()
progress.show_percentage = false
progress.max_value = target
progress.value = progress_value
progress.custom_minimum_size.y = 7
_style_progress(progress, Brand.SUCCESS if completed else Brand.ROYAL_BLUE)
details.add_child(progress)
details.add_child(_label("Complete - 3 RZ Coins earned" if completed else "%d of %d" % [progress_value, target],
11, Brand.muted_color(UIThemeService.is_dark_mode())))
row.add_child(details)
var launch := _button("Review" if str(mission.get("type", "")) == "due_reviews" else "Start")
launch.disabled = completed
launch.pressed.connect(_launch_mission.bind(mission))
row.add_child(launch)
if replacement_available and not completed:
var replace := Button.new()
replace.text = "Replace"
replace.flat = true
replace.pressed.connect(_replace_mission.bind(str(mission.get("id", ""))))
row.add_child(replace)
mission_content.add_child(row)
func _launch_mission(mission: Dictionary) -> void:
if str(mission.get("type", "")) == "due_reviews":
_start_review_queue()
return
main.show_screen("setup", {
"mastery_target": true,
"step3_system": str(mission.get("step3System", "")),
"question_count": 10,
"difficulty_min": int(mission.get("difficultyMin", 0)),
"difficulty_max": 5 if int(mission.get("difficultyMin", 0)) > 0 else 0
})
func _replace_mission(mission_id: String) -> void:
DailyMissionService.replace(mission_id)
_render_missions()
func _show_mission_error(message: String) -> void:
if mission_content == null:
return
_clear(mission_content)
mission_content.add_child(_section_title("Daily missions"))
mission_content.add_child(_label(message, 13, Brand.MAGENTA))
func _trend_series(trend: Array, field: String) -> Array:
@@ -837,6 +928,8 @@ func _update_responsive() -> void:
for child in row.get_children():
if child is Button:
child.custom_minimum_size.x = 0.0 if narrow else 155.0
for row in mission_rows:
row.vertical = narrow
func _clear(container: Container) -> void: