using System; using System.Collections; using AppleHills.Core.Settings; using Core; using UnityEngine; using UnityEngine.UI; namespace Bootstrap { /// /// Specialized loading screen controller specifically for the initial boot sequence. /// This handles the combined progress of bootstrap initialization and main menu loading. /// public class InitialLoadingScreen : MonoBehaviour { [Header("UI References")] [SerializeField] private GameObject loadingScreenContainer; [SerializeField] private Image progressBarImage; [Header("Settings")] [SerializeField] private float minimumDisplayTime = 1.0f; [SerializeField] private float progressUpdateInterval = 0.1f; private float _displayStartTime; private Coroutine _progressCoroutine; private bool _loadingComplete = false; private bool _animationComplete = false; private Action _onLoadingScreenFullyHidden; /// /// Event that fires when the loading screen is fully hidden (both loading and animation completed) /// public event Action OnLoadingScreenFullyHidden; /// /// Delegate for providing progress values from different sources /// public delegate float ProgressProvider(); /// /// Current progress provider being used for the loading screen /// private ProgressProvider _currentProgressProvider; private LogVerbosity _logVerbosity = LogVerbosity.Warning; /// /// Default progress provider that returns 0 (or 1 if loading is complete) /// private float DefaultProgressProvider() => _loadingComplete ? 1f : 0f; /// /// Check if the loading screen is currently active /// public bool IsActive => loadingScreenContainer != null && loadingScreenContainer.activeSelf; private void Awake() { if (loadingScreenContainer == null) loadingScreenContainer = gameObject; // Ensure the loading screen is initially hidden if (loadingScreenContainer != null) { loadingScreenContainer.SetActive(false); } } private void Start() { _logVerbosity = DeveloperSettingsProvider.Instance.GetSettings().bootstrapLogVerbosity; } /// /// Shows the loading screen and resets the progress bar to zero /// /// Optional delegate to provide progress values (0-1). If null, uses default provider. /// Optional callback when loading screen is fully hidden public void ShowLoadingScreen(ProgressProvider progressProvider = null, Action onComplete = null) { // Store the completion callback _onLoadingScreenFullyHidden = onComplete; // Set the progress provider, use default if none provided _currentProgressProvider = progressProvider ?? DefaultProgressProvider; // Stop any existing progress coroutine if (_progressCoroutine != null) { StopCoroutine(_progressCoroutine); _progressCoroutine = null; } _displayStartTime = Time.time; _loadingComplete = false; _animationComplete = false; if (progressBarImage != null) { progressBarImage.fillAmount = 0f; } if (loadingScreenContainer != null) { loadingScreenContainer.SetActive(true); } // Start the progress filling coroutine _progressCoroutine = StartCoroutine(AnimateProgressBar()); } /// /// Animates the progress bar at a steady pace over the minimum display time, /// while also checking actual loading progress from the current progress provider /// private IEnumerator AnimateProgressBar() { float startTime = Time.time; // Continue until both animation and loading are complete while (!_animationComplete) { // Calculate the steady progress based on elapsed time float elapsedTime = Time.time - startTime; float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime); // Get the actual loading progress from the current provider float actualProgress = _currentProgressProvider(); // If loading is complete, actualProgress should be 1.0 if (_loadingComplete) { actualProgress = 1.0f; } // Use the minimum of steady progress and actual progress // This ensures we don't show more progress than actual loading float displayProgress = Mathf.Min(steadyProgress, actualProgress); // Log the progress values for debugging Logging.Debug($"Progress - Default: {steadyProgress:F2}, Actual: {actualProgress:F2}, Display: {displayProgress:F2}"); // Directly set the progress bar fill amount without smoothing if (progressBarImage != null) { progressBarImage.fillAmount = displayProgress; } // Check if the animation has completed // Animation is complete when we've reached the minimum display time AND we're at 100% progress if (steadyProgress >= 1.0f && displayProgress >= 1.0f) { _animationComplete = true; Logging.Debug("Animation complete"); break; } // Wait for the configured interval before updating again yield return new WaitForSeconds(progressUpdateInterval); } // Ensure we end at 100% progress if (progressBarImage != null) { progressBarImage.fillAmount = 1.0f; Logging.Debug("Final progress set to 1.0"); } // Hide the screen if loading is also complete if (_loadingComplete) { if (loadingScreenContainer != null) { loadingScreenContainer.SetActive(false); Logging.Debug("Animation AND loading complete, hiding screen"); // Invoke the callback when fully hidden _onLoadingScreenFullyHidden?.Invoke(); OnLoadingScreenFullyHidden?.Invoke(); _onLoadingScreenFullyHidden = null; } } _progressCoroutine = null; } /// /// Called when the actual loading process is complete /// public void HideLoadingScreen() { Logging.Debug("Loading complete, marking loading as finished"); // Mark that loading is complete _loadingComplete = true; // If animation is already complete, we can hide the screen now if (_animationComplete) { if (loadingScreenContainer != null) { loadingScreenContainer.SetActive(false); Logging.Debug("Animation already complete, hiding screen immediately"); // Invoke the callback when fully hidden _onLoadingScreenFullyHidden?.Invoke(); OnLoadingScreenFullyHidden?.Invoke(); _onLoadingScreenFullyHidden = null; } } else { Logging.Debug("Animation still in progress, waiting for it to complete"); // The coroutine will handle hiding when animation completes } } /// /// Waits until the loading screen is fully hidden before continuing /// /// Task that completes when the loading screen is hidden public System.Threading.Tasks.Task WaitForLoadingScreenToHideAsync() { var tcs = new System.Threading.Tasks.TaskCompletionSource(); // If the loading screen is not active, complete immediately if (!IsActive) { tcs.SetResult(true); return tcs.Task; } // Store existing callback to chain it Action existingCallback = _onLoadingScreenFullyHidden; // Set new callback _onLoadingScreenFullyHidden = () => { // Call existing callback if any existingCallback?.Invoke(); // Complete the task tcs.SetResult(true); }; return tcs.Task; } } }