Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
SSBookMinigames
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
SSBookMinigames
Commits
ac3d4138
Commit
ac3d4138
authored
Apr 12, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: audio mute bypass, TF timer race condition, analytics resilience, and login/profile validation
parent
48c75590
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
227 additions
and
66 deletions
+227
-66
AppRouter.cs
My project/Assets/App/Infrastructure/Core/AppRouter.cs
+21
-14
UserService.cs
My project/Assets/App/Infrastructure/User/UserService.cs
+15
-6
UserModel.cs
My project/Assets/App/Models/UserModel.cs
+18
-6
LoginController.cs
My project/Assets/App/UI/LoginController.cs
+24
-15
ProfileController.cs
My project/Assets/App/UI/ProfileController.cs
+11
-2
CsUIManager.cs
My project/Assets/ScienceStreet/CS/Scripts/CsUIManager.cs
+1
-1
ChallengeCanvas.cs
...ssets/ScienceStreet/Features/Challenge/ChallengeCanvas.cs
+1
-1
McqGameManager.cs
...roject/Assets/ScienceStreet/MCQ/Scripts/McqGameManager.cs
+6
-3
McqPlayerRunner.cs
...oject/Assets/ScienceStreet/MCQ/Scripts/McqPlayerRunner.cs
+2
-2
McqUIManager.cs
My project/Assets/ScienceStreet/MCQ/Scripts/McqUIManager.cs
+1
-8
SSApiManager.cs
...oject/Assets/ScienceStreet/Shared/Scripts/SSApiManager.cs
+121
-1
TfGameManager.cs
My project/Assets/ScienceStreet/TF/Scripts/TfGameManager.cs
+4
-2
TfProductionLine.cs
...oject/Assets/ScienceStreet/TF/Scripts/TfProductionLine.cs
+1
-4
TfUIManager.cs
My project/Assets/ScienceStreet/TF/Scripts/TfUIManager.cs
+1
-1
No files found.
My project/Assets/App/Infrastructure/Core/AppRouter.cs
View file @
ac3d4138
using
System
;
using
System
;
using
com.al_arcade.shared
;
using
Cysharp.Threading.Tasks
;
using
Cysharp.Threading.Tasks
;
using
EasyTransition
;
using
EasyTransition
;
using
Supabase.Gotrue
;
using
Supabase.Gotrue
;
...
@@ -42,34 +43,26 @@ public class AppRouter : MonoBehaviour
...
@@ -42,34 +43,26 @@ public class AppRouter : MonoBehaviour
private
async
UniTask
Boot
()
private
async
UniTask
Boot
()
{
{
var
cached
=
UserService
.
Instance
.
LoadFromCache
();
// 1. Try cache first — instant
// var cached = UserService.Instance.LoadFromCache();
TransitionManager
.
Instance
().
onTransitionCutPointReached
+=
HideSplash
;
TransitionManager
.
Instance
().
onTransitionCutPointReached
+=
HideSplash
;
// 2. Init Supabase
bool
ready
=
await
SupabaseManager
.
Instance
.
Initialize
();
bool
ready
=
await
SupabaseManager
.
Instance
.
Initialize
();
if
(!
ready
)
if
(!
ready
)
{
{
// Offline but have cache — go to home
if
(
cached
!=
null
)
{
GoToHome
();
return
;
}
// if (cached != null) { GoToHome(); return; }
ShowError
(
"Failed to connect"
);
ShowError
(
"Failed to connect"
);
return
;
return
;
}
}
// 3. Ensure session
var
authResult
=
await
SupabaseAuthentication
.
Instance
.
EnsureSession
();
var
authResult
=
await
SupabaseAuthentication
.
Instance
.
EnsureSession
();
if
(
authResult
.
IsT1
)
if
(
authResult
.
IsT1
)
{
{
//
if (cached != null) { GoToHome(); return; }
if
(
cached
!=
null
)
{
GoToHome
();
return
;
}
GoToLogin
();
GoToLogin
();
return
;
return
;
}
}
// 4. Refresh from network
var
prof
=
await
UserService
.
Instance
.
GetCurrentUser
();
var
profileResult
=
UserService
.
Instance
.
GetCurrentUser
();
var
prof
=
await
profileResult
;
prof
.
Switch
(
prof
.
Switch
(
async
success
=>
async
success
=>
...
@@ -78,10 +71,18 @@ public class AppRouter : MonoBehaviour
...
@@ -78,10 +71,18 @@ public class AppRouter : MonoBehaviour
UserService
.
Instance
.
StartListening
();
UserService
.
Instance
.
StartListening
();
},
},
error
=>
error
=>
{
if
(
cached
!=
null
)
{
Debug
.
LogWarning
(
$"[Boot] Network profile fetch failed, using cache"
);
GoToHome
();
}
else
{
{
Debug
.
LogError
(
$"[Boot] Failed to load user profile:
{
error
}
"
);
Debug
.
LogError
(
$"[Boot] Failed to load user profile:
{
error
}
"
);
GoToLogin
();
GoToLogin
();
}
}
}
);
);
}
}
...
@@ -120,10 +121,16 @@ public class AppRouter : MonoBehaviour
...
@@ -120,10 +121,16 @@ public class AppRouter : MonoBehaviour
public
static
async
void
Logout
()
public
static
async
void
Logout
()
{
{
CleanupSingletons
();
await
SupabaseAuthentication
.
Instance
.
LogOut
();
await
SupabaseAuthentication
.
Instance
.
LogOut
();
GoToLogin
();
GoToLogin
();
}
}
private
static
void
CleanupSingletons
()
{
try
{
SSAudioManager
.
Instance
?.
StopMusic
();
}
catch
{
}
}
// ─── Helpers ─────────────────────────────────────────────────────
// ─── Helpers ─────────────────────────────────────────────────────
private
static
void
HideSplash
()
private
static
void
HideSplash
()
{
{
...
...
My project/Assets/App/Infrastructure/User/UserService.cs
View file @
ac3d4138
...
@@ -127,22 +127,31 @@ public class UserService
...
@@ -127,22 +127,31 @@ public class UserService
if
(
response
==
null
)
if
(
response
==
null
)
return
new
ErrorResult
(
"User profile not found"
);
return
new
ErrorResult
(
"User profile not found"
);
var
oldUser
=
CurrentUser
;
CurrentUser
=
response
;
CurrentUser
=
response
;
IsFromCache
=
false
;
IsFromCache
=
false
;
//
SaveToCache(CurrentUser);
SaveToCache
(
CurrentUser
);
//
if (oldUser != null && oldUser.Rank != CurrentUser.Rank)
if
(
oldUser
!=
null
&&
oldUser
.
Rank
!=
CurrentUser
.
Rank
)
//
OnRankChanged?.Invoke(oldUser.Rank, CurrentUser.Rank);
OnRankChanged
?.
Invoke
(
oldUser
.
Rank
,
CurrentUser
.
Rank
);
//
if (oldUser != null && oldUser.Points != CurrentUser.Points)
if
(
oldUser
!=
null
&&
oldUser
.
Points
!=
CurrentUser
.
Points
)
//
OnPointsChanged?.Invoke(oldUser.Points, CurrentUser.Points);
OnPointsChanged
?.
Invoke
(
oldUser
.
Points
,
CurrentUser
.
Points
);
OnUserChanged
?.
Invoke
(
CurrentUser
);
OnUserChanged
?.
Invoke
(
CurrentUser
);
return
new
UserResult
(
CurrentUser
);
return
new
UserResult
(
CurrentUser
);
}
}
catch
(
Exception
ex
)
catch
(
Exception
ex
)
{
{
Debug
.
LogError
(
$"[UserService] GetCurrentUser failed:
{
ex
.
Message
}
+
{
ex
.
StackTrace
}
"
);
Debug
.
LogWarning
(
$"[UserService] GetCurrentUser network failed:
{
ex
.
Message
}
"
);
var
fallback
=
LoadFromCache
();
if
(
fallback
!=
null
)
{
Debug
.
Log
(
"[UserService] Using cached user profile"
);
return
new
UserResult
(
fallback
);
}
return
new
ErrorResult
(
ex
.
Message
);
return
new
ErrorResult
(
ex
.
Message
);
}
}
}
}
...
...
My project/Assets/App/Models/UserModel.cs
View file @
ac3d4138
...
@@ -110,14 +110,26 @@ public static class EducationManager
...
@@ -110,14 +110,26 @@ public static class EducationManager
// --- Generic ID Lookups ---
// --- Generic ID Lookups ---
public
static
int
GetGradeId
(
string
name
)
=>
public
static
int
GetGradeId
(
string
name
)
GradeMap
.
FirstOrDefault
(
x
=>
x
.
Value
==
name
.
Trim
()).
Key
;
{
if
(
string
.
IsNullOrWhiteSpace
(
name
))
return
-
1
;
var
match
=
GradeMap
.
FirstOrDefault
(
x
=>
x
.
Value
==
name
.
Trim
());
return
GradeMap
.
ContainsKey
(
match
.
Key
)
?
match
.
Key
:
-
1
;
}
public
static
int
GetTermId
(
string
name
)
=>
public
static
int
GetTermId
(
string
name
)
TermMap
.
FirstOrDefault
(
x
=>
x
.
Value
==
name
.
Trim
()).
Key
;
{
if
(
string
.
IsNullOrWhiteSpace
(
name
))
return
-
1
;
var
match
=
TermMap
.
FirstOrDefault
(
x
=>
x
.
Value
==
name
.
Trim
());
return
TermMap
.
ContainsKey
(
match
.
Key
)
?
match
.
Key
:
-
1
;
}
public
static
int
GetCurriculumId
(
string
name
)
=>
public
static
int
GetCurriculumId
(
string
name
)
CurriculumMap
.
FirstOrDefault
(
x
=>
x
.
Value
==
name
.
Trim
()).
Key
;
{
if
(
string
.
IsNullOrWhiteSpace
(
name
))
return
-
1
;
var
match
=
CurriculumMap
.
FirstOrDefault
(
x
=>
x
.
Value
==
name
.
Trim
());
return
CurriculumMap
.
ContainsKey
(
match
.
Key
)
?
match
.
Key
:
-
1
;
}
// --- Lists for UI Dropdowns ---
// --- Lists for UI Dropdowns ---
public
static
List
<
string
>
GradeList
=>
GradeMap
.
Values
.
ToList
();
public
static
List
<
string
>
GradeList
=>
GradeMap
.
Values
.
ToList
();
...
...
My project/Assets/App/UI/LoginController.cs
View file @
ac3d4138
...
@@ -35,31 +35,42 @@ public class LoginController : MonoBehaviour
...
@@ -35,31 +35,42 @@ public class LoginController : MonoBehaviour
public
async
void
RegisterAnon
()
public
async
void
RegisterAnon
()
{
{
var
trimmed
=
username
.
text
?.
Trim
();
int
gradeId
=
EducationManager
.
GetGradeId
(
grade
.
value
);
int
termId
=
EducationManager
.
GetTermId
(
term
.
value
);
int
currId
=
EducationManager
.
GetCurriculumId
(
curriculum
.
value
);
if
(
string
.
IsNullOrEmpty
(
trimmed
)
||
trimmed
.
Length
<
2
||
trimmed
.
Length
>
30
||
gradeId
<
0
||
termId
<
0
||
currId
<
0
||
string
.
IsNullOrEmpty
(
sex
.
value
))
{
Debug
.
LogWarning
(
"[Login] Validation failed — fill all fields, username 2-30 chars"
);
return
;
}
register
.
text
=
"جاري التسجيل..."
;
register
.
text
=
"جاري التسجيل..."
;
register
.
SetEnabled
(
false
);
register
.
SetEnabled
(
false
);
var
auth
=
await
SupabaseAuthentication
.
Instance
.
LoginAnon
();
var
auth
=
await
SupabaseAuthentication
.
Instance
.
LoginAnon
();
if
(
auth
.
IsT1
)
if
(
auth
.
IsT1
)
{
{
Debug
.
LogError
(
$"Authentication failed"
);
Debug
.
LogError
(
"[Login] Authentication failed"
);
return
;
}
// Check if all fields are filled
if
(
string
.
IsNullOrEmpty
(
username
.
text
)
||
grade
.
value
==
null
||
sex
.
value
==
null
||
term
.
value
==
null
||
curriculum
.
value
==
null
)
{
Debug
.
LogError
(
"Please fill in all fields"
);
register
.
text
=
"تسجيل"
;
register
.
text
=
"تسجيل"
;
register
.
SetEnabled
(
true
);
register
.
SetEnabled
(
true
);
return
;
return
;
}
}
var
signUp
=
await
UserService
.
Instance
.
CreateProfile
(
var
signUp
=
await
UserService
.
Instance
.
CreateProfile
(
username
:
username
.
text
,
username
:
trimmed
,
grade
:
EducationManager
.
GetGradeId
(
grade
.
value
)
,
grade
:
gradeId
,
sex
:
sex
.
value
,
sex
:
sex
.
value
,
term
:
EducationManager
.
GetTermId
(
term
.
value
)
,
term
:
termId
,
curriculum
:
EducationManager
.
GetCurriculumId
(
curriculum
.
value
)
curriculum
:
currId
);
);
signUp
.
Switch
(
user
=>
signUp
.
Switch
(
user
=>
...
@@ -67,12 +78,10 @@ public class LoginController : MonoBehaviour
...
@@ -67,12 +78,10 @@ public class LoginController : MonoBehaviour
AppRouter
.
GoToHome
();
AppRouter
.
GoToHome
();
},
error
=>
},
error
=>
{
{
Debug
.
LogError
(
$"Failed to create user:
{
error
.
Message
}
"
);
Debug
.
LogError
(
$"[Login] Failed to create user:
{
error
.
Message
}
"
);
register
.
text
=
"تسجيل"
;
register
.
text
=
"تسجيل"
;
register
.
SetEnabled
(
true
);
register
.
SetEnabled
(
true
);
});
});
}
}
...
...
My project/Assets/App/UI/ProfileController.cs
View file @
ac3d4138
...
@@ -55,10 +55,19 @@ public class ProfileController : MonoBehaviour
...
@@ -55,10 +55,19 @@ public class ProfileController : MonoBehaviour
private
void
UpdateProfile
()
private
void
UpdateProfile
()
{
{
int
gradeId
=
EducationManager
.
GetGradeId
(
Grade
.
value
);
int
termId
=
EducationManager
.
GetTermId
(
Term
.
value
);
if
(
gradeId
<
0
||
termId
<
0
)
{
Debug
.
LogWarning
(
"[Profile] Invalid grade or term selection"
);
return
;
}
UserService
.
Instance
.
UpdateProfile
(
UserService
.
Instance
.
UpdateProfile
(
username
:
UsernameLabel
.
value
,
username
:
UsernameLabel
.
value
,
grade
:
EducationManager
.
GetGradeId
(
Grade
.
value
)
,
grade
:
gradeId
,
term
:
EducationManager
.
GetTermId
(
Term
.
value
)
term
:
termId
);
);
}
}
...
...
My project/Assets/ScienceStreet/CS/Scripts/CsUIManager.cs
View file @
ac3d4138
...
@@ -514,7 +514,7 @@ namespace com.al_arcade.cs
...
@@ -514,7 +514,7 @@ namespace com.al_arcade.cs
_scoreLbl
.
enabled
=
value
;
_scoreLbl
.
enabled
=
value
;
if
(
value
)
if
(
value
)
{
{
_scoreLbl
.
Text
=
"
الوقت الموفر
"
;
_scoreLbl
.
Text
=
"
نقاط إضافية
"
;
}
}
else
else
{
{
...
...
My project/Assets/ScienceStreet/Features/Challenge/ChallengeCanvas.cs
View file @
ac3d4138
...
@@ -87,7 +87,7 @@ public class ChallengeCanvas : MonoBehaviour
...
@@ -87,7 +87,7 @@ public class ChallengeCanvas : MonoBehaviour
public
void
ShowChallengeResult
(
bool
hasWon
,
int
timeSaved
,
int
pointsChange
)
public
void
ShowChallengeResult
(
bool
hasWon
,
int
timeSaved
,
int
pointsChange
)
{
{
statusText
.
Text
=
hasWon
?
"كسبت التحدي"
:
"خسرت التحدي"
;
statusText
.
Text
=
hasWon
?
"كسبت التحدي"
:
"خسرت التحدي"
;
timeSavedText
.
Text
=
hasWon
?
$"
الوقت الموفر:
{
timeSaved
}
ثانية
"
:
""
;
timeSavedText
.
Text
=
hasWon
?
$"
نقاط إضافية:
{
timeSaved
}
"
:
""
;
pointsEarnedText
.
Text
=
hasWon
?
$"النقاط المكتسبة:
{
pointsChange
}
"
:
$"خسرت
{
pointsChange
}
نقطة"
;
pointsEarnedText
.
Text
=
hasWon
?
$"النقاط المكتسبة:
{
pointsChange
}
"
:
$"خسرت
{
pointsChange
}
نقطة"
;
restartButton
.
gameObject
.
SetActive
(
true
);
restartButton
.
gameObject
.
SetActive
(
true
);
...
...
My project/Assets/ScienceStreet/MCQ/Scripts/McqGameManager.cs
View file @
ac3d4138
...
@@ -377,7 +377,8 @@ namespace com.al_arcade.mcq
...
@@ -377,7 +377,8 @@ namespace com.al_arcade.mcq
if
(
particles
!=
null
&&
player
!=
null
)
if
(
particles
!=
null
&&
player
!=
null
)
particles
.
PlayCorrectBurst
(
player
.
transform
.
position
+
Vector3
.
up
*
2f
);
particles
.
PlayCorrectBurst
(
player
.
transform
.
position
+
Vector3
.
up
*
2f
);
_mainCamera
.
DOColor
(
SSColorPalette
.
CorrectWord
,
1
).
SetEase
(
Ease
.
Flash
,
2
);
DOTween
.
Kill
(
_mainCamera
,
"camColor"
);
_mainCamera
.
DOColor
(
SSColorPalette
.
CorrectWord
,
1
).
SetEase
(
Ease
.
Flash
,
2
).
SetId
(
"camColor"
);
}
}
else
else
{
{
...
@@ -401,7 +402,8 @@ namespace com.al_arcade.mcq
...
@@ -401,7 +402,8 @@ namespace com.al_arcade.mcq
if
(
particles
!=
null
&&
player
!=
null
)
if
(
particles
!=
null
&&
player
!=
null
)
particles
.
PlayWrongBurst
(
player
.
transform
.
position
+
Vector3
.
up
*
2f
);
particles
.
PlayWrongBurst
(
player
.
transform
.
position
+
Vector3
.
up
*
2f
);
_mainCamera
.
DOColor
(
SSColorPalette
.
WrongWord
,
1
).
SetEase
(
Ease
.
Flash
,
2
);
DOTween
.
Kill
(
_mainCamera
,
"camColor"
);
_mainCamera
.
DOColor
(
SSColorPalette
.
WrongWord
,
1
).
SetEase
(
Ease
.
Flash
,
2
).
SetId
(
"camColor"
);
}
}
if
(
uiManager
!=
null
)
if
(
uiManager
!=
null
)
...
@@ -573,7 +575,8 @@ namespace com.al_arcade.mcq
...
@@ -573,7 +575,8 @@ namespace com.al_arcade.mcq
private
void
CameraFeedback
(
bool
correct
)
private
void
CameraFeedback
(
bool
correct
)
{
{
_mainCamera
.
DOFieldOfView
(
correct
?
78f
:
76f
,
0.2f
).
SetEase
(
Ease
.
OutQuad
);
DOTween
.
Kill
(
_mainCamera
,
"camFov"
);
_mainCamera
.
DOFieldOfView
(
correct
?
78f
:
76f
,
0.2f
).
SetEase
(
Ease
.
OutQuad
).
SetId
(
"camFov"
);
}
}
// ─── End Sequences ───────────────────────────────────────────────────
// ─── End Sequences ───────────────────────────────────────────────────
...
...
My project/Assets/ScienceStreet/MCQ/Scripts/McqPlayerRunner.cs
View file @
ac3d4138
...
@@ -81,8 +81,8 @@ namespace com.al_arcade.mcq
...
@@ -81,8 +81,8 @@ namespace com.al_arcade.mcq
runEffect
.
Play
();
runEffect
.
Play
();
transform
.
DOBlendableMoveBy
(
Vector3
.
forward
*
5
,
.
2f
);
transform
.
DOBlendableMoveBy
(
Vector3
.
forward
*
5
,
.
2f
);
audioSource
.
PlayOneShot
(
dashClip
);
SSAudioManager
.
Instance
?.
Play
(
dashClip
);
audioSource
.
PlayOneShot
(
correctAnswerClip
);
SSAudioManager
.
Instance
?.
Play
(
correctAnswerClip
);
// RunForward(maxForwardSpeed);
// RunForward(maxForwardSpeed);
}
}
...
...
My project/Assets/ScienceStreet/MCQ/Scripts/McqUIManager.cs
View file @
ac3d4138
...
@@ -121,14 +121,7 @@ namespace com.al_arcade.mcq
...
@@ -121,14 +121,7 @@ namespace com.al_arcade.mcq
{
{
_scoreText
.
enabled
=
value
;
_scoreText
.
enabled
=
value
;
_scoreLbl
.
enabled
=
value
;
_scoreLbl
.
enabled
=
value
;
if
(
value
)
_scoreLbl
.
Text
=
value
?
"نقاط إضافية"
:
"النقاط"
;
{
_scoreLbl
.
Text
=
"الوقت الموفر"
;
}
else
{
_scoreLbl
.
Text
=
"النقاط"
;
}
}
}
public
void
SetStreak
(
int
streak
)
public
void
SetStreak
(
int
streak
)
...
...
My project/Assets/ScienceStreet/Shared/Scripts/SSApiManager.cs
View file @
ac3d4138
using
System
;
using
System
;
using
System.Collections
;
using
System.Collections
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.Text
;
using
System.Text
;
using
UnityEngine
;
using
UnityEngine
;
using
UnityEngine.Networking
;
using
UnityEngine.Networking
;
...
@@ -46,9 +47,110 @@ namespace com.al_arcade.shared
...
@@ -46,9 +47,110 @@ namespace com.al_arcade.shared
return
Instance
;
return
Instance
;
}
}
// ═══════════════════════════════════════════════════
// OFFLINE ANALYTICS QUEUE
// ═══════════════════════════════════════════════════
private
List
<
AttemptData
>
_pendingAttempts
=
new
();
private
bool
_flushing
;
private
string
PendingPath
=>
Path
.
Combine
(
Application
.
persistentDataPath
,
"pending_attempts.json"
);
private
void
Start
()
{
LoadPendingFromDisk
();
StartCoroutine
(
AutoFlushLoop
());
}
private
void
LoadPendingFromDisk
()
{
try
{
if
(!
File
.
Exists
(
PendingPath
))
return
;
var
json
=
File
.
ReadAllText
(
PendingPath
);
var
loaded
=
JsonConvert
.
DeserializeObject
<
List
<
AttemptData
>>(
json
);
if
(
loaded
!=
null
&&
loaded
.
Count
>
0
)
{
_pendingAttempts
.
AddRange
(
loaded
);
Debug
.
Log
(
$"[SSApi] Loaded
{
loaded
.
Count
}
pending attempts from disk"
);
}
}
catch
(
Exception
e
)
{
Debug
.
LogWarning
(
$"[SSApi] Failed to load pending attempts:
{
e
.
Message
}
"
);
}
}
private
void
SavePendingToDisk
()
{
try
{
if
(
_pendingAttempts
.
Count
==
0
)
{
if
(
File
.
Exists
(
PendingPath
))
File
.
Delete
(
PendingPath
);
return
;
}
var
json
=
JsonConvert
.
SerializeObject
(
_pendingAttempts
);
File
.
WriteAllText
(
PendingPath
,
json
);
}
catch
(
Exception
e
)
{
Debug
.
LogWarning
(
$"[SSApi] Failed to save pending attempts:
{
e
.
Message
}
"
);
}
}
private
void
QueueAttempt
(
AttemptData
attempt
)
{
_pendingAttempts
.
Add
(
attempt
);
SavePendingToDisk
();
Debug
.
Log
(
$"[SSApi] Queued attempt (total pending:
{
_pendingAttempts
.
Count
}
)"
);
}
private
IEnumerator
AutoFlushLoop
()
{
while
(
true
)
{
yield
return
new
WaitForSeconds
(
60f
);
if
(
_pendingAttempts
.
Count
>
0
&&
!
_flushing
)
yield
return
FlushPendingAttempts
();
}
}
private
IEnumerator
FlushPendingAttempts
()
{
if
(
_pendingAttempts
.
Count
==
0
||
_flushing
)
yield
break
;
_flushing
=
true
;
var
batch
=
_pendingAttempts
.
ToArray
();
var
payload
=
new
BatchAttemptPayload
{
attempts
=
batch
};
var
json
=
JsonConvert
.
SerializeObject
(
payload
,
new
JsonSerializerSettings
{
NullValueHandling
=
NullValueHandling
.
Ignore
});
bool
success
=
false
;
yield
return
PostJson
(
"report_batch"
,
json
,
_
=>
success
=
true
,
err
=>
Debug
.
LogWarning
(
$"[SSApi] Flush failed:
{
err
}
"
));
if
(
success
)
{
Debug
.
Log
(
$"[SSApi] Flushed
{
batch
.
Length
}
pending attempts"
);
_pendingAttempts
.
Clear
();
SavePendingToDisk
();
}
_flushing
=
false
;
}
// ═══════════════════════════════════════════════════
// ═══════════════════════════════════════════════════
// LOW-LEVEL HTTP HELPERS
// LOW-LEVEL HTTP HELPERS
// ═══════════════════════════════════════════════════
// ═══════════════════════════════════════════════════
private
void
AttachAuth
(
UnityWebRequest
req
)
{
var
token
=
SupabaseManager
.
Instance
?.
Supabase
()?.
Auth
?.
CurrentSession
?.
AccessToken
;
if
(!
string
.
IsNullOrEmpty
(
token
))
req
.
SetRequestHeader
(
"Authorization"
,
"Bearer "
+
token
);
}
private
IEnumerator
GetRequest
(
string
action
,
private
IEnumerator
GetRequest
(
string
action
,
Dictionary
<
string
,
string
>
parameters
,
Dictionary
<
string
,
string
>
parameters
,
Action
<
string
>
onSuccess
,
Action
<
string
>
onSuccess
,
...
@@ -66,6 +168,7 @@ namespace com.al_arcade.shared
...
@@ -66,6 +168,7 @@ namespace com.al_arcade.shared
using
var
req
=
UnityWebRequest
.
Get
(
url
);
using
var
req
=
UnityWebRequest
.
Get
(
url
);
req
.
timeout
=
timeoutSeconds
;
req
.
timeout
=
timeoutSeconds
;
AttachAuth
(
req
);
yield
return
req
.
SendWebRequest
();
yield
return
req
.
SendWebRequest
();
...
@@ -95,6 +198,7 @@ namespace com.al_arcade.shared
...
@@ -95,6 +198,7 @@ namespace com.al_arcade.shared
using
var
req
=
UnityWebRequest
.
Post
(
baseUrl
,
form
);
using
var
req
=
UnityWebRequest
.
Post
(
baseUrl
,
form
);
req
.
timeout
=
timeoutSeconds
;
req
.
timeout
=
timeoutSeconds
;
AttachAuth
(
req
);
yield
return
req
.
SendWebRequest
();
yield
return
req
.
SendWebRequest
();
...
@@ -119,6 +223,7 @@ namespace com.al_arcade.shared
...
@@ -119,6 +223,7 @@ namespace com.al_arcade.shared
req
.
uploadHandler
=
new
UploadHandlerRaw
(
bodyRaw
);
req
.
uploadHandler
=
new
UploadHandlerRaw
(
bodyRaw
);
req
.
downloadHandler
=
new
DownloadHandlerBuffer
();
req
.
downloadHandler
=
new
DownloadHandlerBuffer
();
req
.
SetRequestHeader
(
"Content-Type"
,
"application/json"
);
req
.
SetRequestHeader
(
"Content-Type"
,
"application/json"
);
AttachAuth
(
req
);
req
.
timeout
=
timeoutSeconds
;
req
.
timeout
=
timeoutSeconds
;
yield
return
req
.
SendWebRequest
();
yield
return
req
.
SendWebRequest
();
...
@@ -420,16 +525,31 @@ namespace com.al_arcade.shared
...
@@ -420,16 +525,31 @@ namespace com.al_arcade.shared
string
json
=
JsonConvert
.
SerializeObject
(
attempt
,
string
json
=
JsonConvert
.
SerializeObject
(
attempt
,
new
JsonSerializerSettings
{
NullValueHandling
=
NullValueHandling
.
Ignore
});
new
JsonSerializerSettings
{
NullValueHandling
=
NullValueHandling
.
Ignore
});
bool
failed
=
false
;
yield
return
PostJson
(
"report_attempt"
,
json
,
yield
return
PostJson
(
"report_attempt"
,
json
,
text
=>
text
=>
{
{
var
r
=
Parse
<
ReportAttemptResponse
>(
text
);
var
r
=
Parse
<
ReportAttemptResponse
>(
text
);
if
(
r
is
{
success
:
true
})
if
(
r
is
{
success
:
true
})
{
onSuccess
?.
Invoke
(
r
);
onSuccess
?.
Invoke
(
r
);
if
(
_pendingAttempts
.
Count
>
0
&&
!
_flushing
)
StartCoroutine
(
FlushPendingAttempts
());
}
else
else
{
failed
=
true
;
onError
?.
Invoke
(
r
?.
error
??
"Failed to report attempt"
);
onError
?.
Invoke
(
r
?.
error
??
"Failed to report attempt"
);
}
},
},
onError
);
err
=>
{
failed
=
true
;
onError
?.
Invoke
(
err
);
});
if
(
failed
)
QueueAttempt
(
attempt
);
}
}
/// <summary>Quick overload: report with minimal data.</summary>
/// <summary>Quick overload: report with minimal data.</summary>
...
...
My project/Assets/ScienceStreet/TF/Scripts/TfGameManager.cs
View file @
ac3d4138
...
@@ -132,7 +132,7 @@ namespace com.al_arcade.tf
...
@@ -132,7 +132,7 @@ namespace com.al_arcade.tf
public
void
SubmitAnswer
(
bool
answer
)
public
void
SubmitAnswer
(
bool
answer
)
{
{
if
(!
_waitingForAnswer
)
return
;
if
(!
_waitingForAnswer
||
!
_timerRunning
)
return
;
_waitingForAnswer
=
false
;
_waitingForAnswer
=
false
;
_pendingAnswer
=
answer
?
1
:
0
;
_pendingAnswer
=
answer
?
1
:
0
;
}
}
...
@@ -171,7 +171,7 @@ namespace com.al_arcade.tf
...
@@ -171,7 +171,7 @@ namespace com.al_arcade.tf
if
(
handController
!=
null
)
handController
.
SetReady
(
true
);
if
(
handController
!=
null
)
handController
.
SetReady
(
true
);
while
(
_pendingAnswer
<
0
)
while
(
_pendingAnswer
<
0
&&
_timerRunning
)
{
{
if
(
Input
.
GetKeyDown
(
KeyCode
.
LeftArrow
)
||
Input
.
GetKeyDown
(
KeyCode
.
A
))
if
(
Input
.
GetKeyDown
(
KeyCode
.
LeftArrow
)
||
Input
.
GetKeyDown
(
KeyCode
.
A
))
SubmitAnswer
(
true
);
SubmitAnswer
(
true
);
...
@@ -180,6 +180,8 @@ namespace com.al_arcade.tf
...
@@ -180,6 +180,8 @@ namespace com.al_arcade.tf
yield
return
null
;
yield
return
null
;
}
}
if
(
_pendingAnswer
<
0
)
yield
break
;
if
(
handController
!=
null
)
handController
.
SetReady
(
false
);
if
(
handController
!=
null
)
handController
.
SetReady
(
false
);
_state
=
TfGameState
.
Feedback
;
_state
=
TfGameState
.
Feedback
;
...
...
My project/Assets/ScienceStreet/TF/Scripts/TfProductionLine.cs
View file @
ac3d4138
...
@@ -59,10 +59,7 @@ namespace com.al_arcade.tf
...
@@ -59,10 +59,7 @@ namespace com.al_arcade.tf
private
void
PlayMoveSound
()
private
void
PlayMoveSound
()
{
{
if
(
moveSound
==
null
)
return
;
SSAudioManager
.
Instance
?.
Play
(
moveSound
,
1f
,
1.2f
);
moveAudioSource
.
pitch
=
1.2f
;
moveAudioSource
.
PlayOneShot
(
moveSound
);
}
}
public
void
Build
(
int
stepsToWin
,
float
stepSize
,
Vector3
cameraPos
)
public
void
Build
(
int
stepsToWin
,
float
stepSize
,
Vector3
cameraPos
)
...
...
My project/Assets/ScienceStreet/TF/Scripts/TfUIManager.cs
View file @
ac3d4138
...
@@ -158,7 +158,7 @@ namespace com.al_arcade.tf
...
@@ -158,7 +158,7 @@ namespace com.al_arcade.tf
{
{
_scoreText
.
enabled
=
value
;
_scoreText
.
enabled
=
value
;
_scoreLbl
.
enabled
=
value
;
_scoreLbl
.
enabled
=
value
;
_scoreLbl
.
Text
=
value
?
"
الوقت الموفر
"
:
"النقاط"
;
_scoreLbl
.
Text
=
value
?
"
نقاط إضافية
"
:
"النقاط"
;
}
}
public
void
SetStreak
(
int
s
)
public
void
SetStreak
(
int
s
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment