extends Node signal mail_updated signal unread_count_changed(count: int) var mails: Array = [] var claimed_ids: Array = [] var read_ids: Array = [] var _is_fetching: bool = false func _ready() -> void: if UserProfileManager: UserProfileManager.profile_loaded.connect(_on_profile_loaded) func _on_profile_loaded(_profile: Dictionary) -> void: fetch_mails() func fetch_mails() -> void: if _is_fetching or not NakamaManager.session: return _is_fetching = true var result = await BackendService.get_mail() _is_fetching = false if result.get("success", false) == false: push_error("[MailManager] Failed to fetch mails: " + str(result.get("error", ""))) return var payload = result.get("data", {}) if payload and payload is Dictionary: mails = payload.get("mails", []) var state = payload.get("state", {}) claimed_ids = state.get("claimed_ids", []) read_ids = state.get("read_ids", []) # Sort by date descending mails.sort_custom(func(a, b): return a.get("date", "") > b.get("date", "") ) mail_updated.emit() _update_unread_count() func _update_unread_count() -> void: var count = 0 for m in mails: if m.id not in read_ids: count += 1 unread_count_changed.emit(count) func claim_reward(mail_id: String) -> bool: var result = await BackendService.claim_mail_reward(mail_id) if result.get("success", false) == false: push_error("[MailManager] Claim failed: " + str(result.get("error", ""))) return false var payload = result.get("data", {}) if payload and payload.get("success"): claimed_ids = payload.get("claimed_ids", claimed_ids) if mail_id not in read_ids: read_ids.append(mail_id) mail_updated.emit() _update_unread_count() # Refresh wallet UserProfileManager.load_profile() return true return false func delete_mail(mail_id: String) -> bool: var result = await BackendService.delete_mail(mail_id) if result.get("success", false) == false: push_error("[MailManager] Delete failed: " + str(result.get("error", ""))) return false var payload = result.get("data", {}) if payload and payload.get("success"): var deleted_ids = payload.get("deleted_ids", []) # Remove from local array for i in range(mails.size() - 1, -1, -1): if mails[i].id in deleted_ids: mails.remove_at(i) if mail_id not in read_ids: read_ids.append(mail_id) mail_updated.emit() _update_unread_count() return true return false func mark_as_read(mail_id: String) -> void: if mail_id in read_ids: return read_ids.append(mail_id) _update_unread_count() # Persist read state to server via delete_mail RPC pattern (just saves state) _save_inbox_state() func _save_inbox_state() -> void: if not NakamaManager.session: return var state_payload = { "claimed_ids": claimed_ids, "deleted_ids": [], "read_ids": read_ids } var result = await BackendService.api_rpc_async("save_mail_state", JSON.stringify(state_payload)) if result.get("success", false) == false: push_warning("[MailManager] Could not save mail state: " + str(result.get("error", ""))) func read_all_and_claim_all() -> void: """Mark all mails as read and claim all unclaimed rewards.""" if mails.is_empty(): return # Mark all as read for mail in mails: var mid = mail.get("id", "") if mid not in read_ids: read_ids.append(mid) # Claim all unclaimed rewards var to_claim: Array = [] for mail in mails: var mid = mail.get("id", "") if mid in claimed_ids: continue var rewards = mail.get("rewards", []) var has_rewards = false if typeof(rewards) == TYPE_DICTIONARY: has_rewards = rewards.get("star", 0) > 0 or rewards.get("gold", 0) > 0 elif typeof(rewards) == TYPE_ARRAY: has_rewards = rewards.size() > 0 if has_rewards: to_claim.append(mid) for mid in to_claim: await claim_reward(mid) _update_unread_count() mail_updated.emit()