Commit 1f5a8eb6 authored by saad's avatar saad

Merge branch 'NewUI' into pt_msq_opt

parents 88638ac0 2f3b6762
......@@ -6,6 +6,7 @@ using Supabase.Gotrue;
using Supabase.Gotrue.Interfaces;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
public class AppRouter : MonoBehaviour
{
......@@ -15,9 +16,14 @@ public class AppRouter : MonoBehaviour
[Header("Splash UI (Boot Scene Only)")]
[SerializeField] private GameObject splashScreen;
[SerializeField] private GameObject errorPanel;
[SerializeField] private TextMeshProUGUI statusText;
[Header("Settings")]
public TransitionSettings transitionSettings;
private bool _booted;
[Header("Debug Testing")]
[Tooltip("Check this in the Inspector to simulate network loss and test the 'Waiting' loop.")]
[SerializeField] private bool simulateNetworkFailure;
private void Awake()
{
......@@ -30,6 +36,7 @@ public class AppRouter : MonoBehaviour
_instance = this;
DontDestroyOnLoad(gameObject);
// Standard mobile optimizations
Application.targetFrameRate = Screen.currentResolution.refreshRate;
Screen.orientation = ScreenOrientation.Portrait;
}
......@@ -42,7 +49,7 @@ public class AppRouter : MonoBehaviour
private void HandleBackButton()
{
// Don't interrupt active challenges
// Safety: Don't let the back button break an active game session
if (ChallengeManager.Instance != null && ChallengeManager.Instance.IsChallengeRunning)
return;
......@@ -66,74 +73,119 @@ public class AppRouter : MonoBehaviour
var cached = UserService.Instance.LoadFromCache();
TransitionManager.Instance().onTransitionCutPointReached += HideSplash;
bool ready = await SupabaseManager.Instance.Initialize();
if (!ready)
{
if (cached != null) { GoToHome(); return; }
ShowError("Failed to connect");
return;
}
// --- STEP 1: Network Connection Loop ---
// Execution halts here until isConnected is true
await EnsureNetworkConnection();
// --- STEP 2: Authentication Session ---
UpdateStatus("Checking session...");
var authResult = await SupabaseAuthentication.Instance.EnsureSession();
if (authResult.IsT1)
if (authResult.IsT1) // No valid session found
{
if (cached != null) { GoToHome(); return; }
if (cached != null)
{
Debug.Log("[Boot] Offline/No session, using cache.");
GoToHome();
return;
}
GoToLogin();
return;
}
// --- STEP 3: Load User Profile ---
UpdateStatus("Fetching profile...");
var prof = await UserService.Instance.GetCurrentUser();
prof.Switch(
async success =>
{
TransitionManager.Instance().Transition("MainMenu", transitionSettings, 0f);
UserService.Instance.StartListening();
},
error =>
{
if (cached != null)
{
Debug.LogWarning($"[Boot] Network profile fetch failed, using cache");
GoToHome();
}
else
{
Debug.LogError($"[Boot] Failed to load user profile: {error}");
GoToLogin();
}
}
);
async success =>
{
UpdateStatus("Welcome back!");
TransitionManager.Instance().Transition("MainMenu", transitionSettings, 0.5f);
UserService.Instance.StartListening();
},
error =>
{
// If profile fetch fails but we have a cache, let them in
if (cached != null)
{
Debug.LogWarning($"[Boot] Profile fetch failed, falling back to cache.");
GoToHome();
}
else
{
Debug.LogError($"[Boot] Critical load error: {error}");
GoToLogin();
}
}
);
}
private async UniTask EnsureNetworkConnection()
{
bool isConnected = false;
int attempts = 0;
while (!isConnected)
{
attempts++;
UpdateStatus(attempts == 1 ? "Connecting..." : "Waiting for network...");
if (simulateNetworkFailure)
{
Debug.LogWarning("[TEST] simulateNetworkFailure is ON. Mocking connection failure.");
isConnected = false;
}
else
{
isConnected = await SupabaseManager.Instance.Initialize();
}
if (!isConnected)
{
// Wait 3 seconds before the next heartbeat
// Uses the object's token to stop if the user quits the app
await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: this.GetCancellationTokenOnDestroy());
}
}
UpdateStatus("Connected!");
await UniTask.Delay(500); // Visual polish
}
// ─── Auth State Listener (Safety Net Only) ───────────────────────
private void UpdateStatus(string message)
{
if (statusText != null)
statusText.text = message;
Debug.Log($"[AppRouter] {message}");
}
// ─── Auth State Listener (Safety Net) ───────────────────────────
private void OnAuthStateChanged(IGotrueClient<Supabase.Gotrue.User, Supabase.Gotrue.Session> sender, Constants.AuthState newState)
{
switch (newState)
{
case Constants.AuthState.SignedOut:
// Only react if WE didn't trigger the sign-out
if (!SupabaseAuthentication.Instance.IsLoading)
{
Debug.LogWarning("[Auth] Unexpected sign-out detected");
UserService.Instance.ClearUser();
GoToLogin();
}
break;
case Constants.AuthState.TokenRefreshed:
Debug.Log("[Auth] Token refreshed");
Debug.Log("[Auth] Session token refreshed.");
break;
}
}
// ─── Navigation ──────────────────────────────────────────────────
public async static void GoToLogin()
// ─── Navigation Logic ───────────────────────────────────────────
public static void GoToLogin()
{
TransitionManager.Instance().Transition("Login", _instance.transitionSettings, 0f);
}
public async static void GoToHome()
public static void GoToHome()
{
TransitionManager.Instance().Transition("MainMenu", _instance.transitionSettings, 0f);
UserService.Instance.StartListening();
......@@ -151,7 +203,7 @@ public class AppRouter : MonoBehaviour
try { SSAudioManager.Instance?.StopMusic(); } catch { }
}
// ─── Helpers ─────────────────────────────────────────────────────
// ─── UI Helpers ──────────────────────────────────────────────────
private static void HideSplash()
{
if (_instance == null) return;
......@@ -163,6 +215,6 @@ public class AppRouter : MonoBehaviour
{
if (splashScreen != null) splashScreen.SetActive(false);
if (errorPanel != null) errorPanel.SetActive(true);
Debug.LogError($"[Boot] {message}");
Debug.LogError($"[Boot Error] {message}");
}
}
\ No newline at end of file
......@@ -50,23 +50,23 @@ public class SupabaseManager
client.Auth.SetPersistence(new UnitySession());
client.Auth.Options.AllowUnconfirmedUserSessions = true;
// string url = $"{SupabaseSettings.SupabaseURL}/auth/v1/settings?apikey={SupabaseSettings.SupabaseAnonKey}";
string url = $"{SupabaseSettings.SupabaseURL}/auth/v1/settings?apikey={SupabaseSettings.SupabaseAnonKey}";
// await client.InitializeAsync();
// try
// {
// client.Auth.Online = await _networkStatus.StartAsync(url);
// }
// catch (NotSupportedException)
// {
// client.Auth.Online = true;
// }
// catch (Exception e)
// {
// Debug.LogWarning($"Network check failed: {e.Message}");
// client.Auth.Online = false;
// }
try
{
client.Auth.Online = await _networkStatus.StartAsync(url);
}
catch (NotSupportedException)
{
client.Auth.Online = true;
}
catch (Exception e)
{
Debug.LogWarning($"Network check failed: {e.Message}");
client.Auth.Online = false;
}
// if (client.Auth.Online)
// {
......
......@@ -6,7 +6,6 @@ using Newtonsoft.Json;
using OneOf;
using Supabase.Realtime;
using Supabase.Realtime.PostgresChanges;
using Unity.VisualScripting;
using UnityEngine;
public class UserService
......
......@@ -95,6 +95,7 @@ public class HomeController : MonoBehaviour
SetPlayButtonsEnabled(enough);
},
_ => SetPlayButtonsEnabled(false),
curriculumId: 1,
gradeId: user.Grade,
termId: user.Term
));
......
......@@ -166,7 +166,9 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::AppRouter
splashScreen: {fileID: 90795484556287063}
errorPanel: {fileID: 0}
statusText: {fileID: 0}
transitionSettings: {fileID: 11400000, guid: 91673b50629b1ae4f938d0b25135d085, type: 2}
simulateNetworkFailure: 0
--- !u!1 &787965442
GameObject:
m_ObjectHideFlags: 0
......
......@@ -1321,20 +1321,27 @@ MonoBehaviour:
_loadingUI: {fileID: 4105729685514656015}
_errorUI: {fileID: 8668403456112399721}
_resultsUI: {fileID: 8614017441097344094}
_feedbackGroup: {fileID: 5181556637890600282}
_scoreText: {fileID: 5935917613051726348}
_streakText: {fileID: 6655817245277015576}
_scoreLbl: {fileID: 0}
_loadingText: {fileID: 2750036649835959240}
_errorText: {fileID: 5202410726077148544}
_progressLabel: {fileID: 876496144990673683}
_resultTitle: {fileID: 7300533697225226430}
_resultScore: {fileID: 5737192048517657640}
_resultStats: {fileID: 1630567241621742439}
_feedbackText: {fileID: 2735330587039562112}
_feedbackBg: {fileID: 8779464442449399176}
_restartButton: {fileID: 7997833608038347806}
_returnToHomeButton: {fileID: 7997833608038347806}
_timerSlider: {fileID: 4514849613205295364}
_timerFill: {fileID: 4203006633935581820}
_timerText: {fileID: 1760636326841778451}
_maxTime: 30
_pointsContainer: {fileID: 0}
_activePointColor: {r: 0, g: 0, b: 0, a: 0}
_inactivePointColor: {r: 0, g: 0, b: 0, a: 0}
onRestartClicked:
m_PersistentCalls:
m_Calls: []
......
......@@ -33,10 +33,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5824509147393400757}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 22.572838, y: -25.96685}
m_SizeDelta: {x: 0, y: 51.9337}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 22.572838, y: 0}
m_SizeDelta: {x: 51.9337, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9055497536415541340
CanvasRenderer:
......@@ -868,10 +868,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5824509147393400757}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 144.8642, y: -25.96685}
m_SizeDelta: {x: 0, y: 51.9337}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 144.8642, y: 0}
m_SizeDelta: {x: 51.9337, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6316850651786239132
CanvasRenderer:
......@@ -1210,10 +1210,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5824509147393400757}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 83.71851, y: -25.96685}
m_SizeDelta: {x: 0, y: 51.9337}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 83.71851, y: 0}
m_SizeDelta: {x: 51.9337, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6319844450247911941
CanvasRenderer:
......@@ -2195,10 +2195,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5824509147393400757}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 206.00986, y: -25.96685}
m_SizeDelta: {x: 0, y: 51.9337}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 206.00986, y: 0}
m_SizeDelta: {x: 51.9337, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3778932895135096301
CanvasRenderer:
......@@ -2381,6 +2381,7 @@ MonoBehaviour:
_loadingUI: {fileID: 948396739458356726}
_errorUI: {fileID: 4448024252030771562}
_resultsUI: {fileID: 1265578760517736726}
_feedbackGroup: {fileID: 4497114863431471683}
_scoreText: {fileID: 7283828466168530179}
_streakText: {fileID: 3088443959907424236}
_scoreLbl: {fileID: 5924483057703210132}
......@@ -2390,6 +2391,8 @@ MonoBehaviour:
_resultTitle: {fileID: 3377821766872872317}
_resultScore: {fileID: 2058651398905202117}
_resultStats: {fileID: 2189195751882486028}
_feedbackText: {fileID: 2715045871399486055}
_feedbackBg: {fileID: 609508365388699558}
_restartButton: {fileID: 2142933248170327394}
_returnToHomeButton: {fileID: 6128910297614538796}
_timerSlider: {fileID: 3613290988306101471}
......@@ -2829,10 +2832,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5824509147393400757}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 267.15555, y: -25.96685}
m_SizeDelta: {x: 0, y: 51.9337}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 267.15555, y: 0}
m_SizeDelta: {x: 51.9337, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1293769170803587111
CanvasRenderer:
......
......@@ -349,6 +349,7 @@ namespace com.al_arcade.shared
// ═══════════════════════════════════════════════════
public IEnumerator GetChapters(Action<ChapterInfo[]> onSuccess,
Action<string> onError,
int curriculumId = 0,
int subjectId = 0,
int gradeId = 0,
int termId = 0)
......@@ -357,6 +358,7 @@ namespace com.al_arcade.shared
if (subjectId > 0) p["subject_id"] = subjectId.ToString();
if (gradeId > 0) p["grade_id"] = gradeId.ToString();
if (termId > 0) p["term_id"] = termId.ToString();
if (curriculumId > 0) p["curriculum_id"] = curriculumId.ToString();
yield return GetRequest("get_chapters", p,
json =>
......
......@@ -24,18 +24,24 @@ public class UIPause : MonoBehaviour
if (quitButton != null)
quitButton.onClick.AddListener(QuitGame);
pauseMenuCanvasGroup.gameObject.SetActive(false);
}
private void PauseGame()
{
Time.timeScale = 0f;
pauseMenuCanvasGroup.gameObject.SetActive(true);
pauseMenuCanvasGroup.DOFade(1f, 0.25f).From(0f).SetUpdate(true);
}
private void ResumeGame()
{
Time.timeScale = 1f;
pauseMenuCanvasGroup.DOFade(0f, 0.25f).From(1f).SetUpdate(true);
pauseMenuCanvasGroup.DOFade(0f, 0.25f).From(1f).SetUpdate(true).OnComplete(() =>
{
pauseMenuCanvasGroup.gameObject.SetActive(false);
});
}
private void QuitGame()
......
......@@ -200,12 +200,22 @@ namespace com.al_arcade.tf
_correctCount++;
_streak++;
_progress++;
_score += CalculateStreakScore();
int points = CalculateStreakScore();
_score += points;
// timer before productionLine to avoid Race Condition
UpdateTimerBy(4f);
uiManager?.UpdateTimer(_timeLeft, true);
if (handController != null) handController.PlayCorrectFeedback(playerSaidTrue);
if (productionLine != null) yield return productionLine.MoveForward(stepDistance);
if (questionScreen != null) questionScreen.ShowFeedback(true, _streak);
if (uiManager != null) uiManager.ShowFeedback($"ممتاز! {points}+", true);
var audio = SSAudioManager.Instance;
if (audio != null)
{ if (audio.sfxCorrect != null) audio.PlayCorrect(); else audio.PlayCorrectBeep(); }
......@@ -214,8 +224,7 @@ namespace com.al_arcade.tf
if (particles != null)
particles.PlayCorrectBurst(new Vector3(-2, 6f, 13));
UpdateTimerBy(4f);
uiManager?.UpdateTimer(_timeLeft, true);
}
else
{
......@@ -223,10 +232,17 @@ namespace com.al_arcade.tf
_streak = 0;
_progress = Mathf.Max(0, _progress - 1);
// timer before productionLine to avoid Race Condition
UpdateTimerBy(-2f);
uiManager?.UpdateTimer(_timeLeft, false);
if (handController != null) handController.PlayWrongFeedback(playerSaidTrue);
if (productionLine != null) yield return productionLine.MoveBackward(stepDistance);
if (questionScreen != null) questionScreen.ShowFeedback(false, 0);
if (uiManager != null) uiManager.ShowFeedback("حاول مرة أخرى", false);
var audio = SSAudioManager.Instance;
if (audio != null)
{ if (audio.sfxWrong != null) audio.PlayWrong(); else audio.PlayWrongBeep(); }
......@@ -238,8 +254,6 @@ namespace com.al_arcade.tf
.SetEase(Ease.OutQuad).SetId("camShake");
}
UpdateTimerBy(-2f);
uiManager?.UpdateTimer(_timeLeft, false);
}
onAnswerGiven?.Invoke(isCorrect);
......
......@@ -14,11 +14,13 @@ namespace com.al_arcade.tf
public class TfUIManager : MonoBehaviour
{
[SerializeField] private Canvas _canvas;
[SerializeField] private CanvasGroup _gameUI, _loadingUI, _errorUI, _resultsUI;
[SerializeField] private CanvasGroup _gameUI, _loadingUI, _errorUI, _resultsUI , _feedbackGroup;
[SerializeField] private UniText _scoreText, _streakText, _scoreLbl;
[SerializeField] private ArabicTextMeshProUGUI _loadingText, _errorText;
[SerializeField] private UniText _progressLabel;
[SerializeField] private UniText _resultTitle, _resultScore, _resultStats;
[SerializeField] private UniText _feedbackText;
[SerializeField] private Image _feedbackBg;
[SerializeField] private Button _restartButton;
[SerializeField] protected Button _returnToHomeButton;
......@@ -180,6 +182,27 @@ namespace com.al_arcade.tf
if (_progressLabel != null)
_progressLabel.Text = $"{cur} / {total}";
}
public void ShowFeedback(string message, bool isCorrect)
{
if (_feedbackText != null) _feedbackText.Text = message;
if (_feedbackBg != null)
_feedbackBg.color = SSColorPalette.WithAlpha(
isCorrect ? SSColorPalette.Success : SSColorPalette.Danger, 0.92f);
DOTween.Kill(_feedbackGroup, "fbAnim");
_feedbackGroup.alpha = 0;
var bgRect = _feedbackBg?.rectTransform;
if (bgRect != null)
{
bgRect.localScale = new Vector3(0.3f, 0f, 1f);
var seq = DOTween.Sequence().SetId("fbAnim");
seq.Append(_feedbackGroup.DOFade(1f, 0.12f));
seq.Join(bgRect.DOScaleX(1f, 0.25f).SetEase(Ease.OutBack));
seq.Join(bgRect.DOScaleY(1f, 0.2f).SetEase(Ease.OutBack).SetDelay(0.04f));
seq.AppendInterval(1f);
seq.Append(_feedbackGroup.DOFade(0f, 0.3f));
}
}
// ─── Loading / Error ──────────────────────────────────────────────────
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment