1textEditor/WajibyDesign.cs
// Editor-only. Tools > Wajiby > Design submenu.
using System;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public static class WajibyDesign
{
const string PREF="wajiby_chrome";
[MenuItem("Tools/Wajiby/Design/Enable Sleek Chrome")] static void On(){ PlayerPrefs.SetInt(PREF,1); PlayerPrefs.Save(); Rescan(); Debug.Log("Wajiby: sleek chrome ON."); }
[MenuItem("Tools/Wajiby/Design/Disable (Classic)")] static void Off(){ PlayerPrefs.SetInt(PREF,0); PlayerPrefs.Save(); Debug.Log("Wajiby: chrome OFF. Restart app to fully restore classic frames."); }
[MenuItem("Tools/Wajiby/Design/Re-apply Now")] static void Rescan(){ try{ Type.GetType("WindowChrome")?.GetMethod("ForceRescan")?.Invoke(null,null); }catch{} }
[MenuItem("Tools/Wajiby/Design/Polish Scene (bg + 1080x2400)")]
static void Polish()
{
var sc=SceneManager.GetActiveScene(); if(!sc.IsValid()){ Debug.LogWarning("No scene open."); return; }
var cams=sc.GetRootGameObjects(); int n=0;
foreach(var go in cams){
var cam=go.GetComponentInChildren<Camera>(true); if(cam!=null){ cam.clearFlags=CameraClearFlags.SolidColor; cam.backgroundColor=new Color(10/255f,13/255f,15/255f,1f); n++; }
var cv=go.GetComponentInChildren<CanvasScaler>(true); if(cv!=null){ cv.referenceResolution=new Vector2(1080,2400); cv.matchWidthOrHeight=0.5f; }
foreach(var im in go.GetComponentsInChildren<Image>(true)) if(im.name=="Background"||im.name=="Bg"||im.name=="HomeBg"){ im.sprite=null; im.color=new Color(10/255f,13/255f,15/255f,1f); }
}
EditorSceneManager.MarkSceneDirty(sc); EditorSceneManager.SaveScene(sc); AssetDatabase.Refresh();
Debug.Log("Wajiby: polished "+n+" camera(s); canvas set to 1080x2400.");
EditorUtility.DisplayDialog("Wajiby Design","Scene polished (charcoal bg + 1080x2400 reference).","OK");
}
}2textEditor/WajibyDesign.cs.meta
fileFormatVersion: 2 guid: fd079775b1668404c839df20164fedfc
3textEditor/WajibyFlatten.cs
// Editor-only. Tools > Wajiby > Redesign > Flatten Borders (one click).
// V6: the gold is FETCHED AT RUNTIME from sprite assets/fields, so repainting on-screen
// copies loses. This changes the SOURCE: overwrites the pixels of every frame-sized sprite
// asset with flat charcoal (originals backed up to *.orig.png), repoints any field holding
// one, and flattens the few editor-placed frames. Detects frames by size/role, not by name.
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using TMPro;
public static class WajibyFlatten
{
static Color Panel=new Color(22/255f,29/255f,35/255f,1f), Sheet=new Color(15/255f,20/255f,24/255f,1f);
static Color Paper=new Color(236/255f,239/255f,241/255f,1f);
const string DIR="Assets/WajibyUI";
static Sprite _panel,_sheet;
static readonly string[] FRAME={"frame","body","content","bg","background","panel","outline","win","window","border","box","surface","shell","cal","fill","base","card","row","item","dialog","popup","modal","sheet","rect"};
static readonly string[] ICON={"btn","button","icon","switch","trash","edit","scroll","plus","add","menu","hamburger","caret","arrow","line","divider","sep","slider","handle","knob","thumb","check","close","ok","no","confirm","cancel","star","heart","bell","gear","settings","x_","_x"};
static readonly string[] SKIP={"textmesh pro","tmp","atlas","sdf","font","shader","default-","resources/sprite"};
static int _assets,_fields,_imgs;
[MenuItem("Tools/Wajiby/Redesign/Flatten Borders (one click)")]
static void Go()
{
_assets=_fields=_imgs=0;
try{ MakeTiles(); }catch(System.Exception e){ Debug.LogError("tiles: "+e.Message); return; }
Safe(AssetPass); Safe(FieldPass); Safe(ImagePass);
AssetDatabase.SaveAssets();
var sc=EditorSceneManager.GetActiveScene(); EditorSceneManager.MarkSceneDirty(sc); EditorSceneManager.SaveScene(sc);
Debug.Log("WAJIBY FLATTEN V6: "+_assets+" gold asset(s) pixel-flattened, "+_fields+" field(s) repointed, "+_imgs+" placed image(s) flattened. Scene saved. Originals kept as *.orig.png.");
EditorUtility.DisplayDialog("Wajiby Redesign",
"Changed the SOURCE pictures, so the launch can't bring the gold back.\nBuild and look: gold gone? white card dark? titles readable?",
"OK");
}
static void Safe(System.Action a){ try{ a(); }catch(System.Exception e){ Debug.LogWarning("pass skipped: "+e.Message); } }
static void MakeTiles(){
if(!AssetDatabase.IsValidFolder(DIR)) AssetDatabase.CreateFolder("Assets","WajibyUI");
_panel=Tile("tile_panel.png",Panel); _sheet=Tile("tile_sheet.png",Sheet);
AssetDatabase.SaveAssets(); AssetDatabase.Refresh();
_panel=AssetDatabase.LoadAssetAtPath<Sprite>(DIR+"/tile_panel.png");
_sheet=AssetDatabase.LoadAssetAtPath<Sprite>(DIR+"/tile_sheet.png");
}
static Sprite Tile(string n,Color col){
string p=DIR+"/"+n;
if(AssetDatabase.LoadAssetAtPath<Texture2D>(p)==null){
int s=4; var t=new Texture2D(s,s); var px=new Color32[s*s];
for(int k=0;k<px.Length;k++) px[k]=new Color32((byte)(col.r*255),(byte)(col.g*255),(byte)(col.b*255),255);
t.SetPixels32(px); t.Apply(); File.WriteAllBytes(p,t.EncodeToPNG()); Object.DestroyImmediate(t); AssetDatabase.ImportAsset(p);
}
var imp=AssetImporter.GetAtPath(p) as TextureImporter;
if(imp!=null && imp.textureType!=TextureImporterType.Sprite){ imp.textureType=TextureImporterType.Sprite; imp.SaveAndReimport(); }
var spr=AssetDatabase.LoadAssetAtPath<Sprite>(p);
if(spr==null) foreach(var o in AssetDatabase.LoadAllAssetsAtPath(p)) if(o is Sprite sp){ spr=sp; break; }
return spr;
}
// PASS 1 (the decisive one): overwrite the pixels of every frame-sized sprite ASSET.
// Whatever field or code fetches it now gets charcoal. Originals backed up once.
static void AssetPass(){
foreach(var guid in AssetDatabase.FindAssets("t:Sprite", new[]{"Assets"})){
string path=AssetDatabase.GUIDToAssetPath(guid);
if(SkipPath(path)) continue;
var tex=AssetDatabase.LoadAssetAtPath<Texture2D>(path); if(tex==null) continue;
string nm=Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
if(nm.StartsWith("tile_")) continue;
bool big=Mathf.Min(tex.width,tex.height)>=64;
if(!(big||NameFrame(nm))||IconName(nm)) continue;
string bk=path+".orig"; if(!File.Exists(bk)) File.Copy(path,bk,true);
int w=tex.width,h=tex.height; var t2=new Texture2D(w,h); var px=new Color32[w*h];
Color32 fc=new Color32((byte)(Panel.r*255),(byte)(Panel.g*255),(byte)(Panel.b*255),255);
for(int k=0;k<px.Length;k++) px[k]=fc;
t2.SetPixels32(px); t2.Apply(); File.WriteAllBytes(path,t2.EncodeToPNG()); Object.DestroyImmediate(t2);
AssetDatabase.ImportAsset(path);
var ii=AssetImporter.GetAtPath(path) as TextureImporter;
if(ii!=null && ii.textureType!=TextureImporterType.Sprite){ ii.textureType=TextureImporterType.Sprite; ii.SaveAndReimport(); }
_assets++;
}
}
// PASS 2: repoint any Sprite FIELD holding a frame-candidate to charcoal; also fill
// None-valued fields whose NAME says frame (a null frame is the white-card source).
static void FieldPass(){
var done=new HashSet<Object>();
var sc=UnityEngine.SceneManagement.SceneManager.GetActiveScene();
FieldsOn(sc.GetRootGameObjects(),done);
foreach(var g in AssetDatabase.FindAssets("t:Prefab",new[]{"Assets"})){
var root=AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(g));
if(root==null) continue;
int before=_fields; FieldsOn(new GameObject[]{root},done); if(_fields>before) EditorUtility.SetDirty(root);
}
}
static void FieldsOn(GameObject[] gos, HashSet<Object> done){
foreach(var go in gos) foreach(var c in go.GetComponentsInChildren<Component>(true)){
if(c==null||!done.Add(c)) continue;
var so=new SerializedObject(c); var it=so.GetIterator(); bool enter=true; bool ch=false;
while(it.NextVisible(enter)){ enter=false;
if(it.propertyType!=SerializedPropertyType.ObjectReference) continue;
var v=it.objectReferenceValue; string fn=it.name.ToLowerInvariant();
if(v is Sprite s){ if(s.name.StartsWith("tile_")) continue; if(!FrameCand(s)&&!NameFrame(fn)) continue; it.objectReferenceValue=Sheetish(fn)?_sheet:_panel; ch=true; _fields++; }
else if(v==null && NameFrame(fn)){ it.objectReferenceValue=_panel; ch=true; _fields++; }
}
if(ch) so.ApplyModifiedProperties();
}
}
// PASS 3: flatten genuinely editor-placed frames + the baked white card body.
static void ImagePass(){
var sc=UnityEngine.SceneManagement.SceneManager.GetActiveScene();
foreach(var go in sc.GetRootGameObjects()) if(ImgsOn(go.transform)) EditorUtility.SetDirty(go);
foreach(var g in AssetDatabase.FindAssets("t:Prefab",new[]{"Assets"})){
var root=AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(g));
if(root==null) continue;
if(ImgsOn(root.transform)) EditorUtility.SetDirty(root);
}
}
static bool ImgsOn(Transform root){
bool hit=false;
foreach(var tr in root.GetComponentsInChildren<Transform>(true)){
var im=tr.GetComponent<Image>();
if(im!=null){
string n=tr.name.ToLowerInvariant(); var r=tr as RectTransform;
bool icon=IconName(n);
bool sliced=im.type==Image.Type.Sliced;
bool blankWhite=im.sprite==null && im.color.a>0.5f && Lum(im.color)>0.85f && r!=null && r.rect.width>=200;
bool goldSprite=im.sprite!=null && FrameCand(im.sprite);
if(!icon && (sliced||blankWhite||goldSprite)){ im.sprite=null; im.type=Image.Type.Simple; im.color=Sheetish(n)?Sheet:Panel; hit=true; _imgs++; }
}
var tx=tr.GetComponent<TMP_Text>();
if(tx!=null && Lum(tx.color)<0.3f){ tx.color=Paper; hit=true; }
}
return hit;
}
static bool FrameCand(Sprite s){
if(s==null) return false;
if(s.name.StartsWith("tile_")) return false;
string p=AssetDatabase.GetAssetPath(s); if(SkipPath(p)) return false;
string nm=s.name.ToLowerInvariant(); if(IconName(nm)) return false;
var tex=s.texture; if(tex==null) return false;
return Mathf.Min(tex.width,tex.height)>=64 || NameFrame(nm);
}
static bool NameFrame(string s){ foreach(var k in FRAME) if(s.Contains(k)) return true; return false; }
static bool IconName(string s){ foreach(var k in ICON) if(s.Contains(k)) return true; return false; }
static bool Sheetish(string s){ return s.Contains("body")||s.Contains("content")||s.Contains("bg")||s.Contains("background")||s.Contains("fill")||s.Contains("base")||s.Contains("sheet"); }
static bool SkipPath(string p){ p=p.ToLowerInvariant(); foreach(var k in SKIP) if(p.Contains(k)) return true; return false; }
static float Lum(Color c){ return 0.299f*c.r+0.587f*c.g+0.114f*c.b; }
}
// END-WAJIBY-FLATTEN-V64textEditor/WajibyFlatten.cs.meta
fileFormatVersion: 2 guid: cedd7f3e80a067143944fcb37d1a7507
5textEditor/WajibyInventory.cs
// Editor-only, READ-ONLY. Tools > Wajiby > Redesign > Inventory sprite fields.
// Changes NOTHING. Walks every component in the open scene and every prefab under Assets,
// and prints each field whose type is Sprite, with its current value (or None).
// "None" on a frame field is exactly what makes a card render as a blank white slab;
// a gold sprite name on a frame field is what the runtime re-applies over any static edit.
// This list is the map of where the gold/white actually comes from, so the next fix targets
// the real source instead of my guesses. Reflection + GetAssetPath only -> cannot corrupt.
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Reflection;
public static class WajibyInventory
{
[MenuItem("Tools/Wajiby/Redesign/Inventory sprite fields (read-only)")]
static void Run()
{
var seen = new HashSet<string>();
var byType = new SortedDictionary<string, List<string>>();
// 1) components living in the open scene
var sc = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
foreach (var go in sc.GetRootGameObjects()) Collect(go.GetComponentsInChildren<Component>(true), seen, byType);
// 2) components living inside every prefab asset under Assets
foreach (var guid in AssetDatabase.FindAssets("t:Prefab", new[] { "Assets" }))
{
var root = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid));
if (root != null) Collect(root.GetComponentsInChildren<Component>(true), seen, byType);
}
var sb = new System.Text.StringBuilder();
sb.AppendLine("===== WAJIBY SPRITE-FIELD INVENTORY (read-only) =====");
foreach (var kv in byType)
{
sb.AppendLine("[" + kv.Key + "]");
foreach (var line in kv.Value) sb.AppendLine(" " + line);
}
sb.AppendLine("===== end inventory =====");
Debug.Log(sb.ToString());
EditorUtility.DisplayDialog("Wajiby Inventory",
"Printed to the Console (no files changed).\nExpand the [HomeManager] / [AlarmWindow] / [CardBuilder] blocks and screenshot them for me.",
"OK");
}
static void Collect(Component[] comps, HashSet<string> seen, SortedDictionary<string, List<string>> byType)
{
foreach (var c in comps)
{
if (c == null) continue; // destroyed / missing script
System.Type t;
try { t = c.GetType(); } catch { continue; }
foreach (var f in SpriteFields(t))
{
Sprite s = null;
try { s = f.GetValue(c) as Sprite; } catch { continue; }
string val = (s == null) ? "None" : (s.name + " @" + AssetDatabase.GetAssetPath(s));
string line = f.Name + " = " + val;
if (!seen.Add(t.Name + "." + line)) continue; // one line per unique Type.field=value
if (!byType.TryGetValue(t.Name, out var lst)) byType[t.Name] = lst = new List<string>();
lst.Add(line);
}
}
}
static IEnumerable<FieldInfo> SpriteFields(System.Type t)
{
while (t != null && t != typeof(object))
{
FieldInfo[] fs;
try { fs = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); }
catch { yield break; }
foreach (var f in fs) if (f.FieldType == typeof(Sprite)) yield return f;
t = t.BaseType;
}
}
}
// END-WAJIBY-INVENTORY-V16textEditor/WajibyInventory.cs.meta
fileFormatVersion: 2 guid: 588c2a2534c070c4cbd8865628dc3d2d
7textEditor/WajibyRedesign.cs
// Editor-only. Tools > Wajiby > Apply Sleek Palette / Restore Classic Palette.
// Surgical, reversible recolor of the runtime color CONSTANTS + a few unique inline
// background literals in the four builder files. Never reprints a file, never adds a
// class, never changes line counts. Backs up to .bak, verifies canary+length after each
// edit, auto-restores on mismatch. Phase 1 of the redesign (see chat for phase 2).
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
public static class WajibyRedesign
{
struct C { public int r,g,b,a; public C(int r,int g,int b,int a=255){this.r=r;this.g=g;this.b=b;this.a=a;}
public string Lit(){ return r+"f/255f,"+g+"f/255f,"+b+"f/255f,"+(a==255?"1f":a+"f/255f"); } }
// ---- the two palettes ----
static readonly Dictionary<string,C> CLASSIC = new Dictionary<string,C>{
{"Gold",new C(201,162,39)}, {"Ink",new C(13,13,13)}, {"Paper",new C(242,242,236)},
{"Sub",new C(154,165,156)}, {"GrayPill",new C(51,56,51)}, {"White",new C(242,242,236)},
{"InfoGray",new C(154,165,156)}, {"Dim",new C(89,89,89)}, {"DimText",new C(115,115,115)},
{"Black",new C(13,13,13)}, {"BodyGray",new C(56,61,56)},
};
static readonly Dictionary<string,C> SLEEK = new Dictionary<string,C>{
{"Gold",new C(244,183,64)}, {"Ink",new C(20,16,10)}, {"Paper",new C(242,243,240)},
{"Sub",new C(140,148,155)}, {"GrayPill",new C(35,40,43)}, {"White",new C(242,243,240)},
{"InfoGray",new C(162,171,163)}, {"Dim",new C(42,46,49)}, {"DimText",new C(90,97,102)},
{"Black",new C(20,16,10)}, {"BodyGray",new C(27,32,36)},
};
// inline background literals: file -> list of (exactOldSubstring, newRgba). Each must occur exactly once.
static readonly Dictionary<string,List<KeyValuePair<string,C>>> INLINE = new Dictionary<string,List<KeyValuePair<string,C>>>{
{"AlarmSystem.cs", new List<KeyValuePair<string,C>>{
new KeyValuePair<string,C>("new Color(0.15f, 0.16f, 0.15f, 1f)", new C(17,20,23)),
new KeyValuePair<string,C>("new Color(18f/255f,16f/255f,14f/255f,1f)", new C(14,17,19)),
}},
{"AlarmWindow.cs", new List<KeyValuePair<string,C>>{
new KeyValuePair<string,C>("new Color(10f/255f,14f/255f,11f/255f,235f/255f)", new C(14,17,19,235)),
new KeyValuePair<string,C>("new Color(10f/255f,14f/255f,11f/255f,220f/255f)", new C(14,17,19,220)),
new KeyValuePair<string,C>("new Color(0.30f,0.32f,0.30f,1f)", new C(30,35,39)),
}},
};
// which named constants each file actually defines (so we only edit lines that exist)
static readonly Dictionary<string,string[]> DEFINES = new Dictionary<string,string[]>{
{"AlarmSystem.cs", new[]{"Gold","Ink","Paper","Sub","GrayPill"}},
{"HomeManager.cs", new[]{"Gold","White","InfoGray","Dim","DimText"}},
{"AlarmWindow.cs", new[]{"White","Black","Gold","BodyGray","DimText"}},
{"DutyScheduler.cs",new string[0]},
};
static readonly Dictionary<string,string> CANARY = new Dictionary<string,string>{
{"AlarmSystem.cs","WAJIBY_ENGINE_V30_KEYED"},{"HomeManager.cs","WAJIBY_HOME_V30_FIT"},
{"AlarmWindow.cs","WAJIBY_WINDOW_V30_ROWS"},{"DutyScheduler.cs","WAJIBY_SCHED_V30_LABELS"},
};
[MenuItem("Tools/Wajiby/Apply Sleek Palette")]
static void Sleek(){ Run(SLEEK, CLASSIC); }
[MenuItem("Tools/Wajiby/Restore Classic Palette")]
static void Classic(){ Run(CLASSIC, SLEEK); }
static void Run(Dictionary<string,C> target, Dictionary<string,C> sourceIfRevert)
{
string dir = Path.Combine(Application.dataPath, "Scripts");
if (!Directory.Exists(dir)) { EditorUtility.DisplayDialog("Wajiby Redesign","Assets/Scripts not found.","OK"); return; }
var report = new List<string>(); int changedFiles = 0;
foreach (var fn in DEFINES.Keys)
{
string path = Path.Combine(dir, fn);
if (!File.Exists(path)) { report.Add("skip (missing): "+fn); continue; }
string orig = File.ReadAllText(path);
int origLines = Count(orig,"\n")+1;
string txt = orig;
// 1) named constant definitions
foreach (string name in DEFINES[fn])
{
if (!target.TryGetValue(name, out C nc)) continue;
var rx = new Regex(@"(?m)^([ \t]*static[ \t]+readonly[ \t]+Color[ \t]+"+Regex.Escape(name)+@"[ \t]*=[ \t]*new[ \t]+Color[ \t]*\()[^)]*(\);)[ \t]*$");
var ms = rx.Matches(txt);
if (ms.Count == 0) { report.Add("no-def "+name+" in "+fn); continue; }
if (ms.Count > 1) { report.Add("AMBIGUOUS "+name+" ("+ms.Count+" defs) in "+fn+" -> skipped"); continue; }
txt = rx.Replace(txt, m => m.Groups[1].Value + nc.Lit() + m.Groups[2].Value, 1);
}
// 2) unique inline backgrounds
if (INLINE.TryGetValue(fn, out var ins))
foreach (var kv in ins)
{
int occ = Count(txt, kv.Key);
if (occ == 0) { report.Add("no-inline ["+kv.Key+"] in "+fn); continue; }
if (occ != 1) { report.Add("AMBIGUOUS inline x"+occ+" ["+kv.Key+"] in "+fn+" -> skipped"); continue; }
txt = txt.Replace(kv.Key, "new Color("+kv.Value.Lit()+")");
}
if (txt == orig) { report.Add("unchanged: "+fn); continue; }
// safety: canary intact + same line count (we only swapped same-line RHS)
if (!CANARY.TryGetValue(fn,out string cy) || !txt.Contains(cy)) { report.Add("REFUSE "+fn+": canary lost, not written"); continue; }
if (Count(txt,"\n")+1 != origLines) { report.Add("REFUSE "+fn+": line count changed, not written"); continue; }
// backup once, then write
string bak = path+".bak"; if (!File.Exists(bak)) File.WriteAllText(bak, orig);
File.WriteAllText(path, txt); changedFiles++;
report.Add("recolored: "+fn);
}
AssetDatabase.Refresh();
string head = changedFiles==0 ? "Nothing changed (already at target palette, or all anchors ambiguous)."
: changedFiles+" file(s) recolored. Unity recompiled. Run the app to see the new mood.";
Debug.Log("===== WAJIBY REDESIGN =====\n"+head+"\n"+string.Join("\n",report));
EditorUtility.DisplayDialog("Wajiby Redesign", head, "OK");
}
static int Count(string hay, string needle){ int n=0,i=0; while((i=hay.IndexOf(needle,i))>=0){n++;i+=needle.Length;} return n; }
}8textEditor/WajibyRedesign.cs.meta
fileFormatVersion: 2 guid: cb6a0f6dfd8bda344887a314004d2c7e
9textEditor/WajibySettings.cs
using UnityEditor; using UnityEngine;
public static class WajibySettings
{
[MenuItem("Tools/Wajiby/Open Settings (test)")]
static void Open(){ if(!Application.isPlaying){ EditorUtility.DisplayDialog("Wajiby","شغّل Play أولاً ثم اضغط هذه القائمة.","OK"); return; } SettingsScreen.Show(); }
}
// END-WAJIBY-SETTINGS-EDITOR-V110textEditor/WajibySettings.cs.meta
fileFormatVersion: 2 guid: e052278cf39fa2940b91af5195f1266b
11textEditor/WajibyTools.cs
// Editor tool — NOT shipped in builds. Tools > Wajiby menu.
// V5: closes the two blind spots that let a setSilent KeepAliveService and a
// no-StopLive AlarmSystem pass as "CLEAN". Now positively asserts:
// KeepAliveService -> canary V2 AND must NOT contain "setSilent"
// AlarmSystem -> canary V30_KEYED AND must contain "StopLive"
// manifest -> package + <receiver + <service + FOREGROUND_SERVICE
// Global declaration counting still catches intra-file stacks. Corruption
// regex still requires a real space (&\s+&) so the legal && is never flagged.
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
public static class WajibyTools
{
class Req { public string canary; public string[] must; public string[] mustNot;
public Req(string c, string[] m = null, string[] mn = null){ canary=c; must=m; mustNot=mn; } }
static readonly Dictionary<string,Req> CS = new Dictionary<string,Req>
{
{ "AlarmSystem", new Req("WAJIBY_ENGINE_V30_KEYED", new[]{ "StopLive" }) },
{ "HomeManager", new Req("WAJIBY_HOME_V30_FIT") },
{ "DutyScheduler", new Req("WAJIBY_SCHED_V30_LABELS") },
{ "Profile", new Req("WAJIBY_PROFILE_V30") },
{ "AlarmWindow", new Req("WAJIBY_WINDOW_V30_ROWS") },
{ "ArabicShaper", new Req(null) },
{ "ArabicText", new Req(null) },
{ "AlarmController", new Req("__DELETE__") },
};
static readonly Dictionary<string,Req> JAVA = new Dictionary<string,Req>
{
{ "AlarmReceiver", new Req("WAJIBY_RECEIVER_V13_GEN") },
{ "WajibyActivity", new Req("WAJIBY_ACTIVITY_V9_GEN") },
{ "KeepAliveService",new Req("WAJIBY_KEEPALIVE_V2", null, new[]{ "setSilent" }) },
};
const string REAL_PKG = "package=\"com.knoxprojects.wajiby\"";
[MenuItem("Tools/Wajiby/Verify & Clean (one click)")]
static void Clean() { Run(true); }
[MenuItem("Tools/Wajiby/Report Only (no delete)")]
static void Report() { Run(false); }
static int CountOcc(string hay, string needle)
{ int n = 0, i = 0; while ((i = hay.IndexOf(needle, i)) >= 0) { n++; i += needle.Length; } return n; }
static void Run(bool doDelete)
{
int deleted = 0;
var issues = new List<string>();
var notes = new List<string>();
var csFiles = new List<string>();
foreach (var g in AssetDatabase.FindAssets("t:MonoScript", new[] { "Assets/Scripts" }))
csFiles.Add(AssetDatabase.GUIDToAssetPath(g));
CheckGroup(csFiles, CS, "C#", issues, notes, doDelete, ref deleted);
string androidDir = Path.Combine(Application.dataPath, "Plugins", "Android");
if (Directory.Exists(androidDir))
{
var java = new List<string>(Directory.GetFiles(androidDir, "*.java", SearchOption.AllDirectories));
CheckGroup(java, JAVA, "java", issues, notes, doDelete, ref deleted);
var mans = Directory.GetFiles(androidDir, "AndroidManifest.xml", SearchOption.AllDirectories);
var good = new List<string>();
foreach (var f in mans) { string t = File.ReadAllText(f); if (t.Contains(REAL_PKG) && t.Contains("<receiver")) good.Add(f); }
foreach (var f in mans) if (!good.Contains(f) && Kill(f, doDelete, issues)) deleted++;
if (good.Count == 0) issues.Add("ERROR: no valid AndroidManifest.xml (need " + REAL_PKG + " + <receiver>)");
else if (good.Count > 1) issues.Add("DUPLICATE manifests: " + string.Join(", ", good));
else
{
string mt = File.ReadAllText(good[0]);
bool svc = mt.Contains("<service") && mt.Contains("KeepAliveService");
bool perm = mt.Contains("FOREGROUND_SERVICE");
if (!svc || !perm) issues.Add("MANIFEST MISSING keepalive (" + (svc?"":"no <service> ") + (perm?"":"no FOREGROUND_SERVICE ") + ") -> paste AndroidManifest.xml");
else notes.Add("ok manifest " + good[0] + " (service+perm)");
}
var bad = new[] {
new KeyValuePair<string,Regex>("& & (broken preprocessor/AND)", new Regex(@"&\s+&")),
new KeyValuePair<string,Regex>("UnityPlayer<space>\" (trailing space in string)", new Regex(@"UnityPlayer\s+""")),
new KeyValuePair<string,Regex>("p c = (split identifier)", new Regex(@"\bp\s+c\s*=")),
new KeyValuePair<string,Regex>("enableWordWrapping (obsolete TMP3 setter)", new Regex(@"enableWordWrapping")),
};
var scan = new List<string>(csFiles); scan.AddRange(java); scan.AddRange(mans);
foreach (var f in scan)
{
string[] lines = File.ReadAllLines(f);
for (int i = 0; i < lines.Length; i++)
foreach (var r in bad)
if (r.Value.IsMatch(lines[i]))
issues.Add("CORRUPT [" + r.Key + "] @ " + f + ":" + (i + 1) + " -> " + lines[i].Trim());
}
}
if (doDelete && deleted > 0) AssetDatabase.Refresh();
string head = issues.Count == 0
? "CLEAN — all canonicals unique (no stacks, no stale singles), manifest has keepalive service+perm, no corruption markers."
: issues.Count + " real issue(s). Deleted " + deleted + " file(s).";
var body = new List<string>(issues);
if (notes.Count > 0) { body.Add("---- checked OK ----"); body.AddRange(notes); }
Debug.Log("===== WAJIBY VERIFY =====\n" + (body.Count == 0 ? head : string.Join("\n", body)));
EditorUtility.DisplayDialog("Wajiby Verify", head, "OK");
}
static void CheckGroup(List<string> files, Dictionary<string,Req> map, string kind,
List<string> issues, List<string> notes, bool doDelete, ref int deleted)
{
foreach (var kv in map)
{
string T = kv.Key; Req R = kv.Value;
var declRx = new Regex(@"\b(?:class|struct|interface)\s+" + T + @"\b");
int totalDecl = 0; var filesWith = new List<string>(); var perFileDecl = new Dictionary<string,int>();
int totalCanary = 0;
foreach (var f in files)
{
string txt = File.ReadAllText(f);
int d = declRx.Matches(txt).Count;
if (d > 0) { totalDecl += d; filesWith.Add(f); perFileDecl[f] = d; }
if (R.canary != null && R.canary != "__DELETE__") totalCanary += CountOcc(txt, R.canary);
}
if (R.canary == "__DELETE__")
{ foreach (var f in filesWith) if (Kill(f, doDelete, issues)) deleted++; continue; }
if (totalDecl == 0) { issues.Add("MISSING " + kind + " type: " + T); continue; }
foreach (var f in filesWith) if (perFileDecl[f] > 1)
issues.Add("STACKED " + kind + " " + T + " (" + perFileDecl[f] + " declarations in ONE file) @ " + f + " -> open externally, Ctrl+A, Delete, re-paste ONE copy.");
string keep = null;
if (R.canary != null) foreach (var f in filesWith) if (File.ReadAllText(f).Contains(R.canary)) { keep = f; break; }
if (keep == null) { keep = filesWith[0]; foreach (var f in filesWith) if (File.ReadAllText(f).Length > File.ReadAllText(keep).Length) keep = f; }
if (filesWith.Count > 1)
{
foreach (var f in filesWith) if (f != keep && Kill(f, doDelete, issues)) deleted++;
issues.Add("DUPLICATE " + kind + " " + T + " (" + filesWith.Count + " files) kept " + keep);
}
// positive content assertions on the surviving file
string kt = File.ReadAllText(keep);
if (R.canary != null && CountOcc(kt, R.canary) != 1)
issues.Add("STALE " + kind + " " + T + ": canary '" + R.canary + "' found " + CountOcc(kt, R.canary) + "x in " + keep);
if (R.must != null) foreach (string tok in R.must)
if (!kt.Contains(tok)) issues.Add("STALE " + kind + " " + T + ": missing required token '" + tok + "' in " + keep + " (wrong variant on disk)");
if (R.mustNot != null) foreach (string tok in R.mustNot)
if (kt.Contains(tok)) issues.Add("BAD " + kind + " " + T + ": contains forbidden '" + tok + "' in " + keep + " (will not compile / stale)");
if (issues.Count == 0 || !issues.Exists(s => s.Contains(T))) notes.Add("ok " + T);
}
}
static bool Kill(string assetPath, bool doDelete, List<string> log)
{
if (!doDelete) { log.Add("WOULD DELETE: " + assetPath); return false; }
bool ok = AssetDatabase.DeleteAsset(assetPath);
log.Add((ok ? "DELETED " : "FAILED ") + assetPath);
return ok;
}
}12textEditor/WajibyTools.cs.meta
fileFormatVersion: 2 guid: 8ab5a55bd200e1b40bfd1669a3923949
13textEditor/WajibyUIStudio.cs
// CANARY: WAJIBY_EDITOR_UI_STUDIO_V2
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public sealed class WajibyUIStudioWindow : EditorWindow
{
[MenuItem("Tools/Wajiby/UI Studio")]
public static void Open()
{
WajibyUIStudioWindow w = GetWindow<WajibyUIStudioWindow>("Wajiby UI Studio");
w.minSize = new Vector2(430f, 360f);
w.Show();
}
void OnGUI()
{
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("واجبي العسكري", EditorStyles.boldLabel);
EditorGUILayout.LabelField("UI Studio — safe editor utilities only.", EditorStyles.wordWrappedMiniLabel);
EditorGUILayout.Space(10);
DrawStatus();
EditorGUILayout.Space(10);
if (GUILayout.Button("CHECK UI SETUP", GUILayout.Height(38f)))
CheckSetup();
if (GUILayout.Button("CLEAN LEGACY UI", GUILayout.Height(38f)))
CleanLegacyUi();
if (GUILayout.Button("SELECT UI SCRIPTS", GUILayout.Height(38f)))
SelectUiScripts();
EditorGUILayout.Space(10);
EditorGUILayout.HelpBox(
"This tool no longer enters Play Mode and does not attach a runtime UI scanner. " +
"The visual design is built directly into the runtime UI builders.",
MessageType.Info);
}
void DrawStatus()
{
HomeManager hm = FindObjectOfType<HomeManager>(true);
Canvas cv = FindObjectOfType<Canvas>(true);
bool cards = hm != null && FindDeep(hm.transform, "CardsContainer") != null;
bool theme = AssetDatabase.FindAssets("WajibyUITheme t:MonoScript").Length > 0;
int duplicateCount = DuplicateUiScripts();
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Scene", SceneManager.GetActiveScene().name);
EditorGUILayout.LabelField("HomeManager", hm != null ? "FOUND" : "MISSING");
EditorGUILayout.LabelField("Canvas", cv != null ? "FOUND" : "MISSING");
EditorGUILayout.LabelField("CardsContainer", cards ? "FOUND" : "MISSING");
EditorGUILayout.LabelField("WajibyUITheme", theme ? "FOUND" : "MISSING");
EditorGUILayout.LabelField("Duplicate UI script names", duplicateCount.ToString());
EditorGUILayout.EndVertical();
}
static void CheckSetup()
{
HomeManager hm = FindObjectOfType<HomeManager>(true);
Canvas cv = FindObjectOfType<Canvas>(true);
if (hm == null || cv == null)
{
EditorUtility.DisplayDialog("Wajiby UI Studio", "HomeManager or Canvas is missing in the active scene.", "OK");
return;
}
bool cards = FindDeep(hm.transform, "CardsContainer") != null;
int duplicates = DuplicateUiScripts();
string msg = "UI setup looks valid.\n\n" +
"HomeManager: FOUND\n" +
"Canvas: FOUND\n" +
"CardsContainer: " + (cards ? "FOUND" : "MISSING") + "\n" +
"Duplicate UI script names: " + duplicates;
EditorUtility.DisplayDialog("Wajiby UI Studio", msg, "OK");
}
static void CleanLegacyUi()
{
Canvas cv = FindObjectOfType<Canvas>(true);
if (cv == null)
{
EditorUtility.DisplayDialog("Wajiby UI Studio", "No Canvas was found in the active scene.", "OK");
return;
}
int removed = 0;
MonoBehaviour[] mbs = cv.GetComponentsInChildren<MonoBehaviour>(true);
for (int i = 0; i < mbs.Length; i++)
{
MonoBehaviour mb = mbs[i];
if (mb == null) continue;
Type t = mb.GetType();
if (t.Name == "WajibyUIRuntimePolish" || t.Name == "ModalChromeFix")
{
Undo.DestroyObjectImmediate(mb);
removed++;
}
}
GameObject[] all = Resources.FindObjectsOfTypeAll<GameObject>();
for (int i = 0; i < all.Length; i++)
{
GameObject go = all[i];
if (go == null) continue;
if (!go.scene.IsValid() || go.scene != SceneManager.GetActiveScene()) continue;
if (go.GetComponent<Canvas>() != null || go.transform.IsChildOf(cv.transform))
GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
}
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
EditorUtility.DisplayDialog("Wajiby UI Studio", "Legacy runtime UI helpers cleaned: " + removed, "OK");
}
static void SelectUiScripts()
{
string[] names =
{
"HomeManager",
"CardRuntimeBuilder",
"WizardRuntimeBuilder",
"CreationWizard",
"AlarmWindow",
"SettingsScreen",
"RenamePanel",
"ConfirmDelete",
"Toast",
"MainMenuDrawer",
"TopGear",
"ModalChromeFix",
"WajibyUITheme"
};
List<UnityEngine.Object> objects = new List<UnityEngine.Object>();
for (int i = 0; i < names.Length; i++)
{
string[] guids = AssetDatabase.FindAssets(names[i] + " t:MonoScript");
if (guids.Length == 0) continue;
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(AssetDatabase.GUIDToAssetPath(guids[0]));
if (obj != null) objects.Add(obj);
}
Selection.objects = objects.ToArray();
EditorUtility.DisplayDialog("Wajiby UI Studio", "Selected " + objects.Count + " UI scripts.", "OK");
}
static int DuplicateUiScripts()
{
string[] names = {
"HomeManager", "CardRuntimeBuilder", "WizardRuntimeBuilder", "CreationWizard",
"AlarmWindow", "SettingsScreen", "RenamePanel", "ConfirmDelete", "Toast", "WajibyUITheme"
};
int duplicates = 0;
for (int i = 0; i < names.Length; i++)
{
string[] guids = AssetDatabase.FindAssets(names[i] + " t:MonoScript");
if (guids.Length > 1) duplicates++;
}
return duplicates;
}
static Transform FindDeep(Transform root, string name)
{
if (root == null) return null;
for (int i = 0; i < root.childCount; i++)
{
Transform c = root.GetChild(i);
if (c.name == name) return c;
Transform d = FindDeep(c, name);
if (d != null) return d;
}
return null;
}
}
#endif
// END-WAJIBY-EDITOR-UI_STUDIO-V2
14textEditor/WajibyUIStudio.cs.meta
fileFormatVersion: 2 guid: f52de75622fdc0a4dbb3f511d29c6799
15textAndroid/AlarmReceiver.java
// CANARY: WAJIBY_RECEIVER_V22_RINGBASE
package com.knoxprojects.wajiby.app;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.HashMap;
public class AlarmReceiver extends BroadcastReceiver {
public static final int GEN = 18;
public static volatile boolean ringing = false; // activity reads this to start the FGS from the foreground
// Was the phone asleep/locked at the instant this alarm FIRED? Set on the RECEIVER's own static (no new
// WajibyActivity field) so C# (AlarmSystem.FinishIfOutside) can decide post-answer navigation: true = the
// system dragged the ringer over the keyguard (answer -> finish, back to lock); false = the user was awake
// (in-app, or tapped the notif on an unlocked screen) (answer -> hide overlay only, stay in the app).
// Computed AFTER the ring is guaranteed, so it can never silence an alarm.
public static volatile boolean fireHappenedLocked = false;
static final String CH = "wajiby_alarms_v18";
static final String INFO_CH = "wajiby_info_v18";
static final long AUTO_GRACE_MS = 60 * 1000L;
static final long LIMIT_MS = 5 * 60 * 1000L;
static final int F = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
static final HashMap<Integer, MediaPlayer> players = new HashMap<Integer, MediaPlayer>();
static Context sCtx;
static PowerManager.WakeLock sWakeLock;
static synchronized void acquireWakeLock(Context c) {
try {
if (sWakeLock == null) {
PowerManager pm = (PowerManager) c.getSystemService(Context.POWER_SERVICE);
if (pm != null) {
sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "Wajiby:AlarmWakeLock");
sWakeLock.setReferenceCounted(false);
}
}
if (sWakeLock != null && !sWakeLock.isHeld()) {
sWakeLock.acquire(10 * 60 * 1000L);
}
} catch (Exception ignored) {}
}
static synchronized void releaseWakeLock() {
try {
if (sWakeLock != null && sWakeLock.isHeld()) {
sWakeLock.release();
}
} catch (Exception ignored) {}
}
static int key(long uid, int kind, long ticks) {
long h = (uid * 1000003L) ^ ((long) kind * 7919L) ^ ((ticks >> 4) * 2654435761L);
return (int) (h & 0x7FFFFFFFL);
}
// live.txt line = uid:kind:ticks:fireMs:snoozeMin (legacy 3-token lines still validate: identity = first 3 tokens)
static boolean isLive(Context c, long uid, int kind, long ticks) {
try { File f = new File(c.getFilesDir(), "live.txt"); if (!f.exists()) return false;
String want = uid + ":" + kind + ":" + ticks;
BufferedReader r = new BufferedReader(new FileReader(f)); String line;
while ((line = r.readLine()) != null) {
String[] p = line.trim().split(":");
if (p.length >= 3 && (p[0].trim() + ":" + p[1].trim() + ":" + p[2].trim()).equals(want)) { r.close(); return true; }
}
r.close();
} catch (Exception ignored) {}
return false;
}
// When JAVA arms a re-ring (snooze), C# may not have synced live.txt yet (app backgrounded/killed)
// -> isLive would silence the re-ring ("mute on snooze"). So Java records the entry itself, now, with fire+minutes.
static void appendLive(Context c, long uid, int kind, long ticks, long fireMs, int sm) {
try { File f = new File(c.getFilesDir(), "live.txt"); String want = uid + ":" + kind + ":" + ticks;
String cur = f.exists() ? readAll(f) : "";
if (cur.length() > 0) { BufferedReader r = new BufferedReader(new StringReader(cur)); String ln;
while ((ln = r.readLine()) != null) { String[] p = ln.trim().split(":");
if (p.length >= 3 && (p[0].trim() + ":" + p[1].trim() + ":" + p[2].trim()).equals(want)) { r.close(); return; } } r.close(); }
PrintWriter w = new PrintWriter(new FileWriter(f, true));
if (cur.length() > 0 && !cur.endsWith("\n")) w.println();
w.println(want + ":" + fireMs + ":" + sm); w.close();
} catch (Exception ignored) {}
}
// REBOOT RE-ARM: rebuild the OS registry from live.txt. Skips entries already past (missed while off);
// re-arms each future one with the SAME keys/intents the normal path uses, so a later C# schedule
// (FLAG_UPDATE_CURRENT) updates rather than doubles them. Title defaults to the app name (live.txt
// never carried a title, same as before). Requires >=5 tokens (needs fireMs); legacy lines are skipped.
public static void rearmFromLive(Context c) {
try { File f = new File(c.getFilesDir(), "live.txt"); if (!f.exists()) return;
AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
if (am == null) return;
long now = System.currentTimeMillis();
BufferedReader r = new BufferedReader(new FileReader(f)); String line;
while ((line = r.readLine()) != null) {
try {
String[] t = line.trim().split(":"); if (t.length < 5) continue;
long uid = Long.parseLong(t[0].trim()); int kind = Integer.parseInt(t[1].trim()); long ticks = Long.parseLong(t[2].trim());
long fireMs = Long.parseLong(t[3].trim()); int sm = Integer.parseInt(t[4].trim());
if (fireMs <= now) continue;
int k = key(uid, kind, ticks);
PendingIntent trig = PendingIntent.getBroadcast(c, k, alarmIntent(c, uid, ticks, kind, k, "واجبي", sm), F);
PendingIntent show = PendingIntent.getActivity(c, k + 1, launchIntent(c, uid, ticks, kind, k, "واجبي", null, sm), F);
am.setAlarmClock(new AlarmManager.AlarmClockInfo(fireMs, show), trig);
} catch (Exception ignored) {} // one bad line must not abort the rest
}
r.close();
} catch (Exception ignored) {}
}
static String readAll(File f) throws Exception {
StringBuilder sb = new StringBuilder(); BufferedReader r = new BufferedReader(new FileReader(f)); String ln;
while ((ln = r.readLine()) != null) { sb.append(ln).append('\n'); } r.close(); return sb.toString();
}
// V22: guarded against Throwable. It touches WajibyActivity.lastFire* (V10 fields); on the stop/snooze/auto
// paths a throw here would kill the RE-ring the same way an unguarded write killed the first ring in V19/V20.
static void clearFireIfMatches(long uid, long ticks) {
try { if (WajibyActivity.lastFireUid == uid && WajibyActivity.lastFireTicks == ticks) {
WajibyActivity.lastFireUid = 0; WajibyActivity.lastFireTicks = 0;
WajibyActivity.lastFireKind = 0; WajibyActivity.lastFireMs = 0;
} } catch (Throwable t) {}
}
static void hold(Context c) {
try { Intent i = new Intent(c, KeepAliveService.class);
if (Build.VERSION.SDK_INT >= 26) c.startForegroundService(i); else c.startService(i); } catch (Exception ignored) {}
}
static void releaseIfIdle(Context c) {
if (!players.isEmpty()) return;
try { c.stopService(new Intent(c, KeepAliveService.class)); } catch (Exception ignored) {}
releaseWakeLock();
}
@Override public void onReceive(Context c, Intent in) {
sCtx = c.getApplicationContext();
String cmd = in.getStringExtra("WAJIBY_CMD");
int kind = in.getIntExtra("KIND", 1);
long uid = in.getLongExtra("UID", 0);
long ticks = in.getLongExtra("TICKS", 0);
int k = in.getIntExtra("KEY", key(uid, kind, ticks));
int sm = in.getIntExtra("SNOOZE_MIN", 5);
String title = in.getStringExtra("TITLE"); if (title == null) title = "واجبي";
try {
if (cmd == null) { // FIRED -- pre-sound section is V18's, byte-for-byte
if (!isLive(c, uid, kind, ticks)) return; // orphan -> silence (local file read, no class risk)
acquireWakeLock(c);
WajibyActivity.lastFireUid = uid; WajibyActivity.lastFireTicks = ticks;
WajibyActivity.lastFireKind = kind; WajibyActivity.lastFireMs = System.currentTimeMillis();
writeMarker(c);
hold(c); // best-effort (8-11); 12-14 rely on the activity-started FGS
startSound(c, k); ringing = true; vibrate(c); notify(c, k, uid, ticks, kind, title, sm);
scheduleAuto(c, k, uid, ticks, kind, title, sm); scheduleLimit(c, k, uid, ticks, kind, title, sm);
// V22: the ONLY new thing on this path, and it runs AFTER the ring is already playing, Throwable-guarded,
// touching no WajibyActivity field -> it cannot silence an alarm. (firedWhileResumed line removed: dead since
// V40 reads fireHappenedLocked instead, so the activity's resumed/firedWhileResumed fields are now unused.)
try {
PowerManager pm = (PowerManager) c.getSystemService(Context.POWER_SERVICE);
boolean inter = (pm == null) || pm.isInteractive();
KeyguardManager km = (KeyguardManager) c.getSystemService(Context.KEYGUARD_SERVICE);
boolean kg = (km != null) && km.isKeyguardLocked();
fireHappenedLocked = (!inter) || kg;
} catch (Throwable t) { fireHappenedLocked = false; }
} else if (cmd.equals("stop")) {
stopSound(k); cancelNotif(k); cancelAuto(k); cancelLimit(k); clearFireIfMatches(uid, ticks);
ringing = false; releaseIfIdle(c); log(c, "stop:" + uid + ":" + kind + ":" + ticks);
} else if (cmd.equals("snooze")) {
doSnooze(c, k, uid, ticks, sm, title);
} else if (cmd.equals("dismiss")) {
doSnooze(c, k, uid, ticks, sm, title);
} else if (cmd.equals("auto")) {
if (players.containsKey(k)) doSnooze(c, k, uid, ticks, sm, title);
} else if (cmd.equals("limit")) { stopSound(k); cancelNotif(k); cancelAuto(k); ringing = false; releaseIfIdle(c); }
} catch (Exception ignored) {}
}
static void doSnooze(Context c, int k, long uid, long ticks, int sm, String title) {
stopSound(k); cancelNotif(k); cancelAuto(k); cancelLimit(k);
long t = System.currentTimeMillis() + (long) sm * 60 * 1000L;
scheduleBroadcast(c, uid, ticks, 3, t, title, sm);
clearFireIfMatches(uid, ticks);
postSnoozeNotice(c, uid, ticks, sm);
ringing = false; releaseIfIdle(c);
log(c, "snooze:" + uid + ":" + ticks + ":" + t);
}
public static void stopNow(int k) { stopSound(k); cancelNotif(k); cancelAuto(k); cancelLimit(k); ringing = false; if (sCtx != null) releaseIfIdle(sCtx); }
public static void snoozeNow(int k, long uid, long ticks, long minutes) {
stopSound(k); cancelNotif(k); cancelAuto(k); cancelLimit(k);
if (sCtx != null) {
long t = System.currentTimeMillis() + minutes * 60 * 1000L;
scheduleBroadcast(sCtx, uid, ticks, 3, t, "واجبي", (int) minutes);
postSnoozeNotice(sCtx, uid, ticks, (int) minutes);
ringing = false; releaseIfIdle(sCtx);
}
}
static void startSound(Context c, int k) {
stopSound(k);
acquireWakeLock(c);
try { Uri u = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
if (u == null) u = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
if (u == null) u = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
MediaPlayer mp = new MediaPlayer();
mp.setWakeMode(c, PowerManager.PARTIAL_WAKE_LOCK);
mp.setDataSource(c, u);
mp.setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build());
mp.setLooping(true); mp.prepare(); mp.start(); players.put(k, mp);
} catch (Exception ignored) {}
}
static void stopSound(int k) { try { MediaPlayer mp = players.remove(k); if (mp != null) { mp.stop(); mp.release(); } } catch (Exception ignored) {} }
static void vibrate(Context c) {
try { Vibrator v = (Vibrator) c.getSystemService(Context.VIBRATOR_SERVICE); long[] p = {0,600,300,600};
if (Build.VERSION.SDK_INT >= 26) v.vibrate(VibrationEffect.createWaveform(p, 0)); else v.vibrate(p, 0); } catch (Exception ignored) {}
}
static void notify(Context c, int k, long uid, long ticks, int kind, String title, int sm) {
NotificationManager nm = (NotificationManager) c.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel ch = new NotificationChannel(CH, "Wajiby Alarms", NotificationManager.IMPORTANCE_HIGH);
ch.setSound(null, null); ch.enableVibration(false); ch.setBypassDnd(true); ch.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
nm.createNotificationChannel(ch);
}
int icon = c.getApplicationInfo().icon; if (icon == 0) icon = android.R.drawable.ic_dialog_alert;
String msg = (kind == 2) ? "تذكير: الواجب بعد قليل" : "حان وقت الواجب";
PendingIntent stop = PendingIntent.getBroadcast(c, k + 500000, cmdIntent(c, "stop", kind, uid, ticks, k, title, sm), F);
PendingIntent sn = PendingIntent.getBroadcast(c, k + 600000, cmdIntent(c, "snooze", kind, uid, ticks, k, title, sm), F);
PendingIntent dm = PendingIntent.getBroadcast(c, k + 650000, cmdIntent(c, "dismiss", kind, uid, ticks, k, title, sm), F);
PendingIntent fs = PendingIntent.getActivity(c, k + 700000, launchIntent(c, uid, ticks, kind, k, title, null, sm), F);
Notification.Builder nb = (Build.VERSION.SDK_INT >= 26) ? new Notification.Builder(c, CH) : new Notification.Builder(c);
nb.setContentTitle(title).setContentText(msg).setSmallIcon(icon).setOngoing(true)
.setCategory(Notification.CATEGORY_ALARM).setVisibility(Notification.VISIBILITY_PUBLIC)
.setDeleteIntent(dm)
.addAction(icon, "إيقاف", stop).addAction(icon, "غفو", sn).setFullScreenIntent(fs, true);
Notification n = nb.build();
n.flags |= 32; // 32 == FLAG_NO_CLEAR: a swipe-up cannot dismiss the ringing alarm (no mute-on-swipe)
nm.notify(k, n);
}
static void postSnoozeNotice(Context c, long uid, long ticks, int sm) {
try {
NotificationManager nm = (NotificationManager) c.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel ch = new NotificationChannel(INFO_CH, "Wajiby", NotificationManager.IMPORTANCE_LOW);
ch.setSound(null, null); ch.enableVibration(false); ch.setShowBadge(false);
nm.createNotificationChannel(ch);
}
java.util.Calendar cal = java.util.Calendar.getInstance(); cal.setTimeInMillis(System.currentTimeMillis() + (long) sm * 60 * 1000L);
int h = cal.get(java.util.Calendar.HOUR_OF_DAY); int mi = cal.get(java.util.Calendar.MINUTE);
String ap = h < 12 ? "صباحاً" : "مساءً"; int h12 = h % 12; if (h12 == 0) h12 = 12;
String t = String.format("%02d:%02d %s", h12, mi, ap);
int icon = c.getApplicationInfo().icon; if (icon == 0) icon = android.R.drawable.ic_dialog_alert;
PendingIntent open = PendingIntent.getActivity(c, key(uid,1,ticks) + 950001, launchIntent(c, uid, ticks, 1, key(uid,1,ticks), "واجبي", null, 5), F);
Notification.Builder nb = (Build.VERSION.SDK_INT >= 26) ? new Notification.Builder(c, INFO_CH) : new Notification.Builder(c);
nb.setContentTitle("واجبي").setContentText("تم التأجيل إلى " + t).setSmallIcon(icon)
.setAutoCancel(true).setContentIntent(open).setOngoing(false);
nm.notify(key(uid,1,ticks) + 950000, nb.build());
} catch (Exception ignored) {}
}
static void scheduleAuto(Context c, int k, long uid, long ticks, int kind, String title, int sm) {
try { AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(c, k + 800000, cmdIntent(c, "auto", kind, uid, ticks, k, title, sm), F);
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + AUTO_GRACE_MS, pi); } catch (Exception ignored) {}
}
static void cancelAuto(int k) {
try { AlarmManager am = (AlarmManager) sCtx.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(sCtx, k + 800000, cmdIntent(sCtx, "auto", 1, 0, 0, k, "", 5), PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
if (pi != null) am.cancel(pi); } catch (Exception ignored) {}
}
static void scheduleLimit(Context c, int k, long uid, long ticks, int kind, String title, int sm) {
try { AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(c, k + 900000, cmdIntent(c, "limit", kind, uid, ticks, k, title, sm), F);
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + LIMIT_MS, pi); } catch (Exception ignored) {}
}
static void cancelLimit(int k) {
try { AlarmManager am = (AlarmManager) sCtx.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(sCtx, k + 900000, cmdIntent(sCtx, "limit", 1, 0, 0, k, "", 5), PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
if (pi != null) am.cancel(pi); } catch (Exception ignored) {}
}
static void scheduleBroadcast(Context c, long uid, long ticks, int kind, long atMs, String title, int sm) {
try { AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
int k2 = key(uid, kind, ticks);
appendLive(c, uid, kind, ticks, atMs, sm); // record the re-ring WITH its fire time + minutes
PendingIntent trig = PendingIntent.getBroadcast(c, k2, alarmIntent(c, uid, ticks, kind, k2, title, sm), F);
PendingIntent show = PendingIntent.getActivity(c, k2 + 1, launchIntent(c, uid, ticks, kind, k2, title, null, sm), F);
am.setAlarmClock(new AlarmManager.AlarmClockInfo(atMs, show), trig); } catch (Exception ignored) {}
}
static void cancelNotif(int k) { try { ((NotificationManager) sCtx.getSystemService(Context.NOTIFICATION_SERVICE)).cancel(k); } catch (Exception ignored) {} }
static Intent alarmIntent(Context c, long uid, long ticks, int kind, int k, String title, int sm) {
Intent i = new Intent(); i.setClassName(c, "com.knoxprojects.wajiby.app.AlarmReceiver");
i.putExtra("UID", uid); i.putExtra("TICKS", ticks); i.putExtra("KIND", kind); i.putExtra("KEY", k); i.putExtra("TITLE", title); i.putExtra("SNOOZE_MIN", sm); return i;
}
static Intent cmdIntent(Context c, String cmd, int kind, long uid, long ticks, int k, String title, int sm) { Intent i = alarmIntent(c, uid, ticks, kind, k, title, sm); i.putExtra("WAJIBY_CMD", cmd); return i; }
static Intent launchIntent(Context c, long uid, long ticks, int kind, int k, String title, String cmd, int sm) {
Intent i = new Intent(); i.setClassName(c, "com.knoxprojects.wajiby.app.WajibyActivity");
i.putExtra("WAJIBY_ALARM", true); i.putExtra("UID", uid); i.putExtra("TICKS", ticks); i.putExtra("KIND", kind); i.putExtra("KEY", k); i.putExtra("TITLE", title); i.putExtra("SNOOZE_MIN", sm);
if (cmd != null) i.putExtra("WAJIBY_CMD", cmd);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); return i;
}
static void log(Context c, String line) { try { PrintWriter w = new PrintWriter(new FileWriter(new File(c.getFilesDir(), "wjq_cmds.txt"), true)); w.println(line); w.close(); } catch (Exception ignored) {} }
static void writeMarker(Context c) { try { PrintWriter w = new PrintWriter(new FileWriter(new File(c.getFilesDir(), "wjq_bg.txt"), false)); w.println(System.currentTimeMillis()); w.close(); } catch (Exception ignored) {} }
}
// END-WAJIBY-RECEIVER-V22_RINGBASE16textAndroid/AlarmReceiver.java.meta
fileFormatVersion: 2 guid: 29dd6d8c2a9d6314fb7438d56beac7db
17textAndroid/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <!-- WAJIBY MANIFEST V6 : AD block mandatory, BOOT block optional but safe --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.knoxprojects.wajiby"> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <!-- AD: network perms, required or no ad can ever load --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- BOOT: re-arm alarms after reboot, optional but recommended --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <application android:allowBackup="true" android:icon="@mipmap/app_icon" android:label="@string/app_name" android:supportsRtl="true"> <activity android:name="com.knoxprojects.wajiby.app.WajibyActivity" android:theme="@style/Theme.AppCompat.NoActionBar" android:exported="true" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:hardwareAccelerated="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="unityplayer.UnityActivity" android:value="true" /> </activity> <!-- AD: test application id, swap to your own before publishing --> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713" /> <receiver android:name="com.knoxprojects.wajiby.app.AlarmReceiver" android:exported="true" /> <!-- BOOT: delete this receiver plus the RECEIVE_BOOT_COMPLETED perm above if your Plugins/Android has no BootReceiver class --> <receiver android:name="com.knoxprojects.wajiby.app.BootReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> <!-- BOOT: specialUse type keeps the foreground service alive on Android 14, harmless if your Java does not request it --> <service android:name="com.knoxprojects.wajiby.app.KeepAliveService" android:exported="false" android:foregroundServiceType="specialUse"> <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="alarm_and_reminder_keepalive" /> </service> </application> </manifest> <!-- END-WAJIBY-MANIFEST-V6 -->
18textAndroid/AndroidManifest.xml.meta
fileFormatVersion: 2
guid: bb83e8b0d327735489ab0f50a9a1fae1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
19textAndroid/BootReceiver.java
// CANARY: WAJIBY_BOOTRECEIVER_V1
package com.knoxprojects.wajiby.app;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class BootReceiver extends BroadcastReceiver {
public static final int GEN = 1;
@Override public void onReceive(Context c, Intent in) {
// Exact alarms are wiped by a power-cycle; rebuild them from the engine's live.txt ledger.
// No scheduler logic is duplicated here: AlarmReceiver.rearmFromLive() reads the recorded
// fire instants + snooze minutes and re-arms each still-future entry with setAlarmClock.
AlarmReceiver.rearmFromLive(c.getApplicationContext());
}
}
// END-WAJIBY-BOOTRECEIVER-V120textAndroid/BootReceiver.java.meta
fileFormatVersion: 2 guid: 4dacdf9f3a148394893db305861bcfcf
21textAndroid/googlemobileads-unity.aar.meta
fileFormatVersion: 2
guid: cae514165f5d4bbbbd9c4bbceb947a28
labels:
- gvh
- gvh_version-11.5.0
- gvhp_exportpath-Plugins/Android/googlemobileads-unity.aar
timeCreated: 1480838400
PluginImporter:
serializedVersion: 1
iconMap: {}
executionOrder: {}
isPreloaded: 0
platformData:
Android:
enabled: 1
settings:
CPU: AnyCPU
Any:
enabled: 0
settings: {}
Editor:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
Linux:
enabled: 1
settings:
CPU: x86
Linux64:
enabled: 1
settings:
CPU: x86_64
LinuxUniversal:
enabled: 1
settings:
CPU: AnyCPU
OSXIntel:
enabled: 1
settings:
CPU: x86
OSXIntel64:
enabled: 1
settings:
CPU: x86_64
OSXUniversal:
enabled: 1
settings:
CPU: AnyCPU
Web:
enabled: 0
settings: {}
WebStreamed:
enabled: 0
settings: {}
Win:
enabled: 1
settings:
CPU: x86
Win64:
enabled: 1
settings:
CPU: x86_64
WindowsStoreApps:
enabled: 0
settings:
CPU: AnyCPU
iOS:
enabled: 0
settings:
CompileFlags:
FrameworkDependencies:
tvOS:
enabled: 0
settings:
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
22textAndroid/GoogleMobileAdsPlugin.androidlib.meta
fileFormatVersion: 2 guid: 92700a82f1ce5814f9675dfdb213c5f7
23textAndroid/gradleTemplate.properties
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M org.gradle.parallel=true unityStreamingAssets=**STREAMING_ASSETS** # Android Resolver Properties Start android.useAndroidX=true android.enableJetifier=true # Android Resolver Properties End **ADDITIONAL_PROPERTIES**
24textAndroid/gradleTemplate.properties.meta
fileFormatVersion: 2
guid: 2def6e51d420789448a6e88b0671acb3
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
25textAndroid/KeepAliveService.java
// CANARY: WAJIBY_KEEPALIVE_V2
package com.knoxprojects.wajiby.app;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
public class KeepAliveService extends android.app.Service {
public static final int GEN = 2;
// Separate, SILENT channel. No setSilent()/setShowBadge(): those builder methods
// are not guaranteed in the android.jar Unity compiles against here, and they are
// redundant -- the channel is mute, so this can never add a second ringtone.
static final String CH = "wajiby_keepalive";
static final int NOTIF_ID = 4242;
@Override public int onStartCommand(Intent i, int flags, int startId) {
try {
int icon = getApplicationInfo().icon != 0 ? getApplicationInfo().icon : android.R.drawable.ic_dialog_alert;
Notification notif;
if (Build.VERSION.SDK_INT >= 26) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel ch = new NotificationChannel(CH, "Wajiby background", NotificationManager.IMPORTANCE_MIN);
ch.setSound(null, null);
ch.enableVibration(false);
nm.createNotificationChannel(ch);
notif = new Notification.Builder(this, CH)
.setSmallIcon(icon)
.setContentTitle("واجبي")
.setContentText("منبهات مؤجلة تعمل في الخلفية")
.setOngoing(true)
.setPriority(Notification.PRIORITY_MIN)
.build();
} else {
notif = new Notification.Builder(this)
.setSmallIcon(icon)
.setContentTitle("واجبي")
.setContentText("منبهات مؤجلة تعمل في الخلفية")
.setOngoing(true)
.setPriority(Notification.PRIORITY_MIN)
.build();
}
startForeground(NOTIF_ID, notif);
} catch (Exception ignored) {}
return START_STICKY;
}
@Override public android.os.IBinder onBind(Intent i) { return null; }
}26textAndroid/KeepAliveService.java.meta
fileFormatVersion: 2 guid: f47426dfa85011d4b90688a923913a37
27textAndroid/mainTemplate.gradle
apply plugin: 'com.android.library'
apply from: '../shared/keepUnitySymbols.gradle'
apply from: '../shared/common.gradle'
**APPLY_PLUGINS**
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Android Resolver Dependencies Start
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:12
implementation 'androidx.fragment:fragment:1.7.1' // Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:22
implementation 'androidx.lifecycle:lifecycle-process:2.6.2' // Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:17
implementation 'com.google.android.gms:play-services-ads:25.4.0' // Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:7
implementation 'com.google.android.ump:user-messaging-platform:4.0.0' // Assets/GoogleMobileAds/Editor/GoogleUmpDependencies.xml:7
// Android Resolver Dependencies End
**DEPS**}
// Android Resolver Exclusions Start
android {
packaging {
exclude ('/lib/armeabi/*' + '*')
exclude ('/lib/armeabi-v7a/*' + '*')
exclude ('/lib/mips/*' + '*')
exclude ('/lib/mips64/*' + '*')
exclude ('/lib/x86/*' + '*')
exclude ('/lib/x86_64/*' + '*')
}
}
// Android Resolver Exclusions End
android {
namespace "com.unity3d.player"
ndkPath "**NDKPATH**"
ndkVersion "**NDKVERSION**"
compileSdk **APIVERSION**
buildToolsVersion "**BUILDTOOLS**"
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
defaultConfig {
minSdk **MINSDK**
targetSdk **TARGETSDK**
ndk {
abiFilters **ABIFILTERS**
debugSymbolLevel **DEBUGSYMBOLLEVEL**
}
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
**DEFAULT_CONFIG_SETUP**
}
lint {
abortOnError false
}
androidResources {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**PACKAGING**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**
28textAndroid/mainTemplate.gradle.meta
fileFormatVersion: 2
guid: 4bcdfcf9ca0fe6c419f8737f3743cef3
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
29textAndroid/settingsTemplate.gradle
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher'
include ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
google()
mavenCentral()
// Android Resolver Repos Start
def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/")
maven {
url "https://maven.google.com/" // Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:7, Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:12, Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:17, Assets/GoogleMobileAds/Editor/GoogleMobileAdsDependencies.xml:22, Assets/GoogleMobileAds/Editor/GoogleUmpDependencies.xml:7
}
mavenLocal()
// Android Resolver Repos End
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
}
}
30textAndroid/settingsTemplate.gradle.meta
fileFormatVersion: 2
guid: a81604699765b3d408e967abee136449
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
31textAndroid/WajibyActivity.java
// CANARY: WAJIBY_ACTIVITY_V12_GATEDWAKE
package com.knoxprojects.wajiby.app;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import com.unity3d.player.UnityPlayerGameActivity;
public class WajibyActivity extends UnityPlayerGameActivity {
public static final int GEN = 10;
public static volatile Intent latestIntent = null;
public static volatile long lastFireUid = 0;
public static volatile long lastFireTicks = 0;
public static volatile int lastFireKind = 0;
public static volatile long lastFireMs = 0;
@Override protected void onCreate(Bundle b) {
super.onCreate(b);
Intent i = getIntent();
boolean alarm = i != null && i.getBooleanExtra("WAJIBY_ALARM", false);
applyWake(alarm); ensureHold(); latestIntent = i;
}
@Override protected void onNewIntent(Intent i) {
super.onNewIntent(i); setIntent(i); latestIntent = i;
boolean alarm = i != null && i.getBooleanExtra("WAJIBY_ALARM", false);
applyWake(alarm); ensureHold();
}
// V12 reconcile: hold the lock-defeating flags ONLY while an alarm is actually ringing. During a live ring
// AlarmReceiver.ringing is true (it is set in onReceive BEFORE the full-screen/notification intent brings the
// activity forward), so this never clears mid-ring; on a normal resume -- or after answering -- it clears, so the
// power-button lock sticks. (No engine change needed: ringing is the receiver's own static.)
@Override protected void onResume() {
super.onResume();
if (!AlarmReceiver.ringing) applyWake(false);
ensureHold();
}
void ensureHold() {
try { if (AlarmReceiver.ringing) {
Intent i = new Intent(this, KeepAliveService.class);
if (Build.VERSION.SDK_INT >= 26) startForegroundService(i); else startService(i);
} } catch (Exception ignored) {}
}
// V12: GATED. alarm=true -> show over the keyguard + turn the screen on (the ringer must be visible on a locked
// phone). alarm=false -> CLEAR those flags, because the old unconditional setShowWhenLocked(true) made EVERY
// launch paint over the keyguard, so locking never took (the reported bypass). KEEP_SCREEN_ON is harmless
// (auto-sleep only, not a lock bypass) and stays on in both branches. Body guarded against Throwable (OEM window
// quirks) so a failed flag call can never crash the activity lifecycle.
void applyWake(final boolean alarm) {
runOnUiThread(new Runnable() { public void run() {
try {
if (Build.VERSION.SDK_INT >= 27) { setShowWhenLocked(alarm); setTurnScreenOn(alarm); }
else {
int f = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
if (alarm) getWindow().addFlags(f); else getWindow().clearFlags(f);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} catch (Throwable ignored) {}
}});
}
}
// END-WAJIBY-ACTIVITY-V12_GATEDWAKE32textAndroid/WajibyActivity.java.meta
fileFormatVersion: 2 guid: 3b1d95bfd1fe98a42a09788e4d1a98ed
33textAndroid/GoogleMobileAdsPlugin.androidlib/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
This Google Mobile Ads plugin library manifest will get merged with your
application's manifest, adding the necessary metadata
required for displaying ads.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.unity.ads.plugin">
<uses-sdk android:minSdkVersion="23"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application>
<uses-library android:required="false" android:name="org.apache.http.legacy"/>
</application>
</manifest>
34textAndroid/GoogleMobileAdsPlugin.androidlib/next_gen_exclusions.gradle
configurations.configureEach {
exclude group: "com.google.android.gms", module: "play-services-ads"
exclude group: "com.google.android.gms", module: "play-services-ads-lite"
}35textAndroid/GoogleMobileAdsPlugin.androidlib/next_gen_resolution_strategy.gradle
configurations.configureEach {
resolutionStrategy {
force 'androidx.webkit:webkit:1.12.0'
force 'com.google.errorprone:error_prone_annotations:2.23.0'
}
}
36textAndroid/GoogleMobileAdsPlugin.androidlib/packaging_options.gradle
android {
packagingOptions {
pickFirst "META-INF/kotlinx_coroutines_core.version"
}
}
37textAndroid/GoogleMobileAdsPlugin.androidlib/project.properties
target=android-31 android.library=true
38textScripts/AboutText.cs
// CANARY: WAJIBY_ABOUTTEXT_V1
public static class AboutText
{
public const string Title = "عن التطبيق";
public const string VersionLabel = "اصدار التطبيق";
public const string Body =
"تطبيق واجبي العسكري برنامجٌ منبه يساعد الحرّاس والشرطة والجنود على واجباتهم اليومية في الدوام.\n\n" +
"ملاحظة: التطبيق الأصلي لا يتطلب أي معلومات شخصية. التطبيق فقط يتطلب اذونات مخصصة وإعلانات لكي يشتغل بشكل طبيعي. أيّ تطبيق مشابه لتطبيق واجبي العسكري يطلب معلومات شخصية فهو ليس أصلي. لا تسمح له بتدخل أي معلومات وامسح التطبيق فوراٌ لكي تتجنب لسرق البيانات.";
}
// END-WAJIBY-ABOUTTEXT-V139textScripts/AboutText.cs.meta
fileFormatVersion: 2 guid: 2b9c44f2999dc924ebcf3343ad198ed1
40textScripts/AdBridge.cs
// CANARY: WAJIBY_ADBRIDGE_V6_THREADSAFE
using System;
using System.Collections.Concurrent;
using UnityEngine;
using GoogleMobileAds.Api; // package installed + Android Resolver green (your tombstone proves the SDK is live)
public static class AdBridge
{
public const int REQUIRED = 2; // UNCHANGED: HomeManager's (n/2) + unlock-at-2 rely on this
// Google's OFFICIAL rewarded TEST unit -> a real ad UI, grants reward, zero policy risk.
const string REWARDED_UNIT = "ca-app-pub-3940256099942544/5224354917";
// The APPLICATION_ID (test app id ca-app-pub-3940256099942544~3347511713) lives in AndroidManifest, NOT here.
static Runner _r; static bool _warmed;
// Warm-up runs on the main thread at scene load; it only KICKS initialize/preload, the work itself is pumped in Update.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void WarmUp(){ if(_warmed) return; _warmed=true; Ensure(); _r.Preload(); }
static void Ensure(){ if(_r==null){ var go=new GameObject("AdRunner"); UnityEngine.Object.DontDestroyOnLoad(go); _r=go.AddComponent<Runner>(); } }
// SAME signature as before -> HomeManager needs NO edit.
public static void ShowRewarded(Action onEarned, Action onFail){ Ensure(); _r.Show(onEarned,onFail); }
class Runner:MonoBehaviour
{
// The ONLY place Unity APIs and user callbacks are ever invoked is Update(), i.e. the main thread.
// SDK callbacks (any thread) just enqueue; Update drains. ConcurrentQueue makes the cross-thread hand-off safe.
readonly ConcurrentQueue<Action> _q = new ConcurrentQueue<Action>();
RewardedAd _cached; // loaded and ready to show (the cache)
bool _loading,_showing,_earned; // in-flight load / on-screen now / reward fired for the current show
Action _pendE,_pendF; // cold-tap callbacks waiting for a load to finish
Action _curE,_curF; // callbacks bound to the ad currently on screen
float _retry; // main-thread retry timer (replaces the off-thread Invoke that crashed us)
void Update()
{
if(_retry>0f){ _retry-=Time.unscaledDeltaTime; if(_retry<=0f){ _retry=0f; LoadNext(); } }
int n=0; Action a; while(n<32 && _q.TryDequeue(out a)){ a(); n++; } // run queued work ON THE MAIN THREAD
}
public void Preload(){ MobileAds.Initialize(st => _q.Enqueue(()=>LoadNext())); } // init callback is off-thread -> enqueue
void LoadNext()
{
if(_loading || _cached!=null) return; // already staged or a load is running
_loading=true;
var req=new AdRequest(); // v11: no AdRequest.Builder (your CS0246 fix, kept)
RewardedAd.Load(REWARDED_UNIT, req, (ad, error) => _q.Enqueue(()=> // Load callback is off-thread -> enqueue the whole body
{
_loading=false;
if(error!=null || ad==null){ FlushPending(false); _retry=2f; return; } // cold fail -> tell waiter on main, retry on main
ad.OnAdFullScreenContentClosed += () => _q.Enqueue(()=> FinishClosed(ad)); // parameterless Action (your CS1593 fix)
ad.OnAdFullScreenContentFailed += (err) => _q.Enqueue(()=> FinishFailed(ad)); // Action<AdError> (your CS1593 fix)
_cached=ad;
if(_pendE!=null || _pendF!=null){ var pe=_pendE; var pf=_pendF; _pendE=null;_pendF=null; DoShow(pe,pf); } // a tap arrived while loading -> show on main
}));
}
public void Show(Action onEarned, Action onFail) // called from HomeManager on the main thread (button tap)
{
if(_showing) return; // ignore double taps mid-ad
if(_pendE!=null || _pendF!=null) return; // a cold tap is already queued
if(_cached!=null){ DoShow(onEarned,onFail); return; }// WARM: instant
_pendE=onEarned; _pendF=onFail; LoadNext(); // COLD: queue the request, show the moment it's ready (on main)
}
void DoShow(Action onEarned, Action onFail) // main thread only
{
var ad=_cached; _cached=null;
if(ad==null){ if(onFail!=null) onFail(); return; }
_showing=true; _earned=false; _curE=onEarned; _curF=onFail;
ad.Show(reward => _q.Enqueue(()=> _earned=true)); // reward callback is off-thread -> set the flag on main
}
void FinishClosed(RewardedAd ad) // main thread only
{
if(!_showing){ if(ad!=null) ad.Destroy(); return; }
_showing=false; var e=_curE; var f=_curF; _curE=null;_curF=null;
if(_earned){ if(e!=null) e(); } else { if(f!=null) f(); } // skipped -> no increment; earned -> HomeManager Rebuild/Toast NOW on main
_earned=false;
if(ad!=null) ad.Destroy();
LoadNext(); // stage the replacement -> next tap instant (on main)
}
void FinishFailed(RewardedAd ad) // main thread only
{
if(!_showing){ if(ad!=null) ad.Destroy(); return; }
_showing=false; var f=_curF; _curE=null;_curF=null;
if(f!=null) f();
_earned=false;
if(ad!=null) ad.Destroy();
LoadNext();
}
void FlushPending(bool earned){ var e=_pendE; var f=_pendF; _pendE=null;_pendF=null; if(earned){ if(e!=null)e(); } else { if(f!=null)f(); } }
}
}
// END-WAJIBY-ADBRIDGE-V6_THREADSAFE41textScripts/AdBridge.cs.meta
fileFormatVersion: 2 guid: a21a25c6b8028624488348d59c5c4e4b
42textScripts/AlarmSystem.cs
// CANARY: WAJIBY_ENGINE_V40_WALLCLOCK
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
using System.Collections.Generic;
using System.IO;
public static class AlarmSystem
{
public const int KIND_DUTY = 1, KIND_REMINDER = 2, KIND_SNOOZE = 3;
public const int AUTO_SECONDS = 60;
const int GEN = 38; // MUST stay 38: MigrateIfNeeded cancels EVERY armed alarm if this changes -> do NOT bump
public static int fireCount = 0;
public static int revision = 0;
public static bool permFromSettings;
static AndroidJavaObject _ctx, _am;
static readonly HashSet<int> _kept = new HashSet<int>();
static int _ringingKey = 0;
static GameObject _overlay; static ArabicText _overlayText; static TextMeshProUGUI _countText; static ArabicText _snoozeAr;
static long _fireUid, _fireTicks; static int _fireKind; static float _fireAt; static bool _counting;
static long _fireWallMs = 0; // V40: native fire epoch-ms, so the visible countdown + auto-trigger use WALL time (survives the lockscreen freeze of Time.time)
static long _polledFireMs = 0;
static float _tSched = -10f, _tCmd = -10f, _tPrune = -10f, _tPerm = -10f;
static GameObject _permPanel; static bool _permFromSettings;
static GameObject _contGo, _xGo; static Image _xImg;
static readonly Dictionary<Button, Func<bool>> _stateMap = new Dictionary<Button, Func<bool>>();
static readonly Dictionary<Button, GameObject> _labelMap = new Dictionary<Button, GameObject>();
static readonly Dictionary<Button, bool> _autoMap = new Dictionary<Button, bool>();
static readonly Color Gold = new Color(201f/255f,162f/255f,39f/255f,1f);
static readonly Color Ink = new Color(0.05f,0.05f,0.05f,1f);
static readonly Color Paper = new Color(242f/255f,242f/255f,236f/255f,1f);
static readonly Color Sub = new Color(154f/255f,165f/255f,156f/255f,1f);
static readonly Color GrayPill = new Color(0.20f,0.22f,0.20f,1f);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
if (UnityEngine.Object.FindAnyObjectByType<Drv>() == null) new GameObject("AlarmDrv").AddComponent<Drv>();
Init(); DiagToast();
EnsureUids(); PruneEmpty(); ConsumeLog();
MigrateIfNeeded();
ScheduleNow();
if (!AllGood()) ShowPermissionsPage();
}
class Drv : MonoBehaviour
{
void OnApplicationFocus(bool f) { if (f) { PollFire(); ConsumeLog(); ScheduleNow(); RefreshPills(); } }
void Update() { Tick(); }
}
public static void Init()
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (_ctx == null)
{
try { using (var up = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) _ctx = up.GetStatic<AndroidJavaObject>("currentActivity");
if (_ctx != null) _am = _ctx.Call<AndroidJavaObject>("getSystemService", "alarm"); }
catch (Exception e) { Debug.LogWarning("Init: " + e.Message); }
}
#endif
}
static void Bump() { revision++; }
static int SnoozeMinutes() { int v = PlayerPrefs.GetInt("wajiby_snooze_min", 5); switch (v) { case 3: case 5: case 10: case 15: return v; default: return 5; } }
static int Code(long uid, int kind, long ticks)
{ unchecked { long h = (uid * 1000003L) ^ ((long)kind * 7919L) ^ ((ticks >> 4) * 2654435761L); return (int)(h & 0x7FFFFFFFL); } }
static long ToMs(long ticks) { return (long)new DateTime(ticks).ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; }
static void MigrateIfNeeded()
{
int prev = PlayerPrefs.GetInt("wajiby_engine_gen", -1);
if (prev == GEN) return;
#if UNITY_ANDROID && !UNITY_EDITOR
Init();
if (_ctx != null && _am != null)
{
try { foreach (var p in ProfileStore.Load()) { if (p?.alarms == null) continue;
foreach (var a in p.alarms) { if (a == null) continue;
int cd = Code(0, KIND_DUTY, a.ticks), cr = Code(0, KIND_REMINDER, a.reminderTicks), cs = Code(0, KIND_SNOOZE, a.ticks);
Cancel(cd); Cancel(cr); Cancel(cs); StopLive(cd); StopLive(cr); StopLive(cs);
} } } catch { }
}
_kept.Clear();
WriteLive(new List<string>());
#endif
PlayerPrefs.SetInt("wajiby_engine_gen", GEN); PlayerPrefs.Save();
}
static void PruneExpiredReminders()
{
long now = DateTime.Now.Ticks;
var list = ProfileStore.Load(); bool ch = false;
foreach (var p in list) { if (p?.alarms == null) continue;
foreach (var a in p.alarms) if (a != null && a.reminderTicks != 0 && a.reminderTicks <= now) { a.reminderTicks = 0; a.reminderLocked = false; ch = true; } }
if (ch) { ProfileStore.Save(list); Bump(); }
}
static void DiagToast()
{
int r = -1, a = -1;
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) r = rc.GetStatic<int>("GEN"); } catch { }
try { using (var ac = new AndroidJavaClass("com.knoxprojects.wajiby.app.WajibyActivity")) a = ac.GetStatic<int>("GEN"); } catch { }
#endif
Toast(DutyScheduler.AppName() + " E" + GEN + " R" + r + " A" + a);
}
static void Toast(string s)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var t = new AndroidJavaClass("android.widget.Toast")) using (var to = t.CallStatic<AndroidJavaObject>("makeText", _ctx, s, 1)) to.Call("show"); } catch { }
#endif
}
static bool NotifEnabled()
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (_ctx == null) return false;
try { using (var nm = _ctx.Call<AndroidJavaObject>("getSystemService", "notification")) return nm.Call<bool>("areNotificationsEnabled"); } catch { }
return false;
#else
return true;
#endif
}
static bool BatteryIgnored()
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (_ctx == null) return false;
try { string pkg = _ctx.Call<string>("getPackageName");
using (var pm = _ctx.Call<AndroidJavaObject>("getSystemService", "power")) return pm.Call<bool>("isIgnoringBatteryOptimizations", pkg); } catch { }
return false;
#else
return true;
#endif
}
static bool MarkerExists()
{
string p = PathOf("wjq_bg.txt");
return p != null && File.Exists(p);
}
static bool AutostartProven() { return MarkerExists(); }
static bool AllGood() { return NotifEnabled() && BatteryIgnored() && (MarkerExists() || PlayerPrefs.GetInt("wajiby_autostart_ok", 0) == 1); }
public static void ConfirmAutostart() { PlayerPrefs.SetInt("wajiby_autostart_ok", 1); PlayerPrefs.Save(); }
public static void RequestPermissions()
{
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return;
try { int sdk; using (var v = new AndroidJavaClass("android.os.Build$VERSION")) sdk = v.GetStatic<int>("SDK_INT");
if (sdk >= 33 && _ctx.Call<int>("checkSelfPermission", "android.permission.POST_NOTIFICATIONS") != 0)
_ctx.Call("requestPermissions", new string[] { "android.permission.POST_NOTIFICATIONS" }, 101); } catch { }
#endif
}
static void EnsureUids()
{
var list = ProfileStore.Load(); int seed = PlayerPrefs.GetInt("wajiby_uid_seed", 1); bool ch = false;
foreach (var p in list) { if (p?.alarms == null) continue; foreach (var a in p.alarms) if (a != null && a.uid == 0) { a.uid = seed++; ch = true; } }
if (ch) { PlayerPrefs.SetInt("wajiby_uid_seed", seed); PlayerPrefs.Save(); ProfileStore.Save(list); Bump(); }
}
static void PruneEmpty()
{
var list = ProfileStore.Load(); int b = list.Count;
list.RemoveAll(p => p == null || p.alarms == null || p.alarms.Count == 0);
if (list.Count != b) { ProfileStore.Save(list); Bump(); }
}
static string PathOf(string name)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var f = _ctx.Call<AndroidJavaObject>("getFilesDir")) return f.Call<string>("getPath") + "/" + name; } catch { }
#endif
return null;
}
static void WriteLive(List<string> reg)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string p = PathOf("live.txt"); if (p == null) return;
File.WriteAllText(p, reg.Count == 0 ? "" : string.Join("\n", reg.ToArray())); } catch { }
#endif
}
static long MsToTicks(long ms) { return ms * TimeSpan.TicksPerMillisecond + 6213559680000000L; }
static void ConsumeLog()
{
string p = PathOf("wjq_cmds.txt"); if (p == null || !File.Exists(p)) return;
string[] lines; try { lines = File.ReadAllLines(p); } catch { return; }
if (lines.Length == 0) return;
var list = ProfileStore.Load(); bool ch = false;
foreach (var ln in lines)
{
var t = ln.Split(':'); if (t.Length < 2) continue;
long uid = 0, x1 = 0, x2 = 0; int k = 0;
long.TryParse(t.Length > 1 ? t[1] : "", out uid);
if (t[0] == "stop" && t.Length >= 4 && int.TryParse(t[2], out k) && long.TryParse(t[3], out x1))
{ bool found = ApplyStop(list, uid, k, x1); ch |= found; if (uid == _fireUid && x1 == _fireTicks) { HideOverlay(); _counting = false; } }
else if (t[0] == "snooze" && t.Length >= 4 && long.TryParse(t[2], out x1) && long.TryParse(t[3], out x2))
{ ch |= ApplySnooze(list, uid, x1, MsToTicks(x2)); if (uid == _fireUid && x1 == _fireTicks) { HideOverlay(); _counting = false; } }
}
try { File.WriteAllText(p, ""); } catch { }
if (ch) { ProfileStore.Save(list); Bump(); PruneEmpty(); }
}
static bool Match(DutyAlarm a, long uid, long ticks) { return (uid != 0 && a.uid == uid) || (uid == 0 && (a.ticks == ticks || a.reminderTicks == ticks || a.snoozedTicks == ticks)); }
static bool ApplyStop(List<Profile> list, long uid, int kind, long ticks)
{
foreach (var p in list) { if (p?.alarms == null) continue;
for (int i = p.alarms.Count - 1; i >= 0; i--) { var a = p.alarms[i]; if (a == null || !Match(a, uid, ticks)) continue;
p.alarms.RemoveAt(i); return true; } }
return false;
}
static bool ApplySnooze(List<Profile> list, long uid, long ticks, long tgt)
{
foreach (var p in list) { if (p?.alarms == null) continue;
foreach (var a in p.alarms) if (a != null && Match(a, uid, ticks)) { a.snoozedTicks = tgt; a.reminderLocked = false; return true; } }
return false;
}
public static void ScheduleAll(List<Profile> ignored)
{
EnsureUids(); PruneEmpty();
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null || _am == null) return;
try { long now = DateTime.Now.Ticks; int sm = SnoozeMinutes(); var keep = new HashSet<int>(); var reg = new List<string>();
foreach (var p in ProfileStore.Load()) { if (p == null || !p.alarmsActive || p.alarms == null) continue;
foreach (var a in p.alarms) { if (a == null || !a.active) continue;
if (a.snoozedTicks > now) { Arm(a.snoozedTicks, a.uid, KIND_SNOOZE, p.name, a.ticks, keep); reg.Add(a.uid + ":" + KIND_SNOOZE + ":" + a.ticks + ":" + ToMs(a.snoozedTicks) + ":" + sm); }
if (a.reminderTicks > now && !a.reminderLocked) { Arm(a.reminderTicks, a.uid, KIND_REMINDER, p.name, a.reminderTicks, keep); reg.Add(a.uid + ":" + KIND_REMINDER + ":" + a.reminderTicks + ":" + ToMs(a.reminderTicks) + ":" + sm); }
if (a.ticks > now) { Arm(a.ticks, a.uid, KIND_DUTY, p.name, a.ticks, keep); reg.Add(a.uid + ":" + KIND_DUTY + ":" + a.ticks + ":" + ToMs(a.ticks) + ":" + sm); } } }
foreach (int old in _kept) if (!keep.Contains(old)) { if (old == _ringingKey) continue; Cancel(old); StopLive(old); }
_kept.Clear(); foreach (int x in keep) _kept.Add(x);
WriteLive(reg);
} catch (Exception e) { Debug.LogWarning("ScheduleAll: " + e.Message); }
#endif
}
static void ScheduleNow() { _tSched = Time.time; ScheduleAll(null); }
static void Arm(long when, long uid, int kind, string title, long payloadTicks, HashSet<int> keep)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { long ms = ToMs(when);
int k = Code(uid, kind, payloadTicks);
using (var bi = new AndroidJavaObject("android.content.Intent"))
{
bi.Call<AndroidJavaObject>("setClassName", _ctx, "com.knoxprojects.wajiby.app.AlarmReceiver");
bi.Call<AndroidJavaObject>("putExtra", "UID", uid); bi.Call<AndroidJavaObject>("putExtra", "TICKS", payloadTicks);
bi.Call<AndroidJavaObject>("putExtra", "KIND", kind); bi.Call<AndroidJavaObject>("putExtra", "KEY", k); bi.Call<AndroidJavaObject>("putExtra", "TITLE", title ?? "");
bi.Call<AndroidJavaObject>("putExtra", "SNOOZE_MIN", SnoozeMinutes());
using (var pc = new AndroidJavaClass("android.app.PendingIntent"))
using (var trig = pc.CallStatic<AndroidJavaObject>("getBroadcast", _ctx, k, bi, 0x0C000000))
using (var li = new AndroidJavaObject("android.content.Intent"))
{
li.Call<AndroidJavaObject>("setClassName", _ctx, "com.knoxprojects.wajiby.app.WajibyActivity");
li.Call<AndroidJavaObject>("putExtra", "WAJIBY_ALARM", true); li.Call<AndroidJavaObject>("putExtra", "UID", uid);
li.Call<AndroidJavaObject>("putExtra", "TICKS", payloadTicks); li.Call<AndroidJavaObject>("putExtra", "KIND", kind); li.Call<AndroidJavaObject>("putExtra", "KEY", k);
using (var show = pc.CallStatic<AndroidJavaObject>("getActivity", _ctx, k + 1, li, 0x0C000000))
using (var info = new AndroidJavaObject("android.app.AlarmManager$AlarmClockInfo", ms, show))
_am.Call("setAlarmClock", info, trig);
}
}
keep.Add(k);
} catch (Exception e) { Debug.LogWarning("Arm: " + e.Message); }
#endif
}
static void Cancel(int k)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var bi = new AndroidJavaObject("android.content.Intent"))
{ bi.Call<AndroidJavaObject>("setClassName", _ctx, "com.knoxprojects.wajiby.app.AlarmReceiver");
using (var pc = new AndroidJavaClass("android.app.PendingIntent"))
using (var p = pc.CallStatic<AndroidJavaObject>("getBroadcast", _ctx, k, bi, 0x20000000))
if (p != null) _am.Call("cancel", p); } } catch { }
#endif
}
static void StopLive(int k)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) rc.CallStatic("stopNow", k); } catch { }
#endif
}
static void PollFire()
{
long fu = 0, ft = 0; int fk = 0; long fms = 0;
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var ac = new AndroidJavaClass("com.knoxprojects.wajiby.app.WajibyActivity")) { fu = ac.GetStatic<long>("lastFireUid"); ft = ac.GetStatic<long>("lastFireTicks"); fk = ac.GetStatic<int>("lastFireKind"); fms = ac.GetStatic<long>("lastFireMs"); } } catch { }
#endif
if (fms != 0 && fms != _polledFireMs && (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - 62135596800000L) - fms < 90000)
{ _polledFireMs = fms; fireCount++; OnFire(fu, ft, fk, fms); }
}
static void Tick()
{
PollFire();
if (Time.time - _tSched > 25f) ScheduleNow();
if (Time.time - _tCmd > 3f) { ConsumeLog(); _tCmd = Time.time; }
if (Time.time - _tPrune > 1.5f) { _tPrune = Time.time; PruneExpiredReminders(); }
if (_permPanel != null && _permPanel.activeSelf && Time.time - _tPerm > 0.8f) { _tPerm = Time.time; RefreshPills(); }
long cu, ct; int ck = ConsumeAlarmIntent(out cu, out ct);
if (ck == -1) InAppStop(cu, ct, _fireKind);
else if (ck == -2) InAppSnooze(cu, ct, _fireKind);
if (_counting && _overlay != null && _overlay.activeSelf)
{
// V40: countdown from WALL time (the native fire-ms), NOT Time.time, because Time.time is frozen while the
// ringer is shown over the keyguard -> that freeze is why the label sat at 1:00 until you unlocked. Wall time
// also keeps the C# auto-trigger in lockstep with the native 60s grace (scheduleAuto uses currentTimeMillis).
float rem;
if (_fireWallMs > 0)
{
long nowMs = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - 62135596800000L;
rem = AUTO_SECONDS - (nowMs - _fireWallMs) / 1000f;
}
else rem = AUTO_SECONDS - (Time.time - _fireAt); // editor / no-native fallback
if (_countText != null) { int s = Mathf.Max(0, Mathf.CeilToInt(rem));
_countText.text = ArabicShaper.Shape(DutyScheduler.CountdownPrefix() + (s / 60) + ":" + (s % 60).ToString("00")); }
if (rem <= 0f) InAppSnooze(_fireUid, _fireTicks, _fireKind);
}
}
public static int ConsumeAlarmIntent(out long uid, out long ticks)
{
uid = 0; ticks = 0;
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return 0;
try { using (var actClass = new AndroidJavaClass("com.knoxprojects.wajiby.app.WajibyActivity"))
using (var i = actClass.GetStatic<AndroidJavaObject>("latestIntent"))
{
if (i == null) return 0;
if (i.Call<bool>("getBooleanExtra", "WAJIBY_ALARM", false))
{
string cmd = i.Call<string>("getStringExtra", "WAJIBY_CMD");
uid = i.Call<long>("getLongExtra", "UID", 0);
ticks = i.Call<long>("getLongExtra", "TICKS", 0);
i.Call("removeExtra", "WAJIBY_ALARM"); i.Call("removeExtra", "WAJIBY_CMD"); i.Call("removeExtra", "UID"); i.Call("removeExtra", "TICKS"); i.Call("removeExtra", "KIND"); i.Call("removeExtra", "KEY");
actClass.SetStatic<AndroidJavaObject>("latestIntent", null);
if (cmd == "stop") return -1;
if (cmd == "snooze") return -2;
return 0;
}
}
} catch { }
#endif
return 0;
}
static void OnFire(long uid, long ticks, int kind, long fireMs)
{
_fireUid = uid; _fireTicks = ticks; _fireKind = kind; _fireWallMs = fireMs;
_ringingKey = Code(uid, kind, ticks); _kept.Remove(_ringingKey);
if (kind == KIND_SNOOZE) { var l = ProfileStore.Load(); bool ch = false; foreach (var p in l) if (p?.alarms != null) foreach (var a in p.alarms) if (a != null && Match(a, uid, ticks)) { a.snoozedTicks = 0; ch = true; } if (ch) { ProfileStore.Save(l); Bump(); } }
Screen.sleepTimeout = SleepTimeout.NeverSleep;
ShowOverlay((kind == KIND_REMINDER) ? DutyScheduler.MsgReminder() : DutyScheduler.MsgDuty(), NameFor(uid, ticks));
_fireAt = Time.time; _counting = true;
}
static string NameFor(long uid, long ticks) { foreach (var p in ProfileStore.Load()) if (p?.alarms != null) foreach (var a in p.alarms) if (a != null && Match(a, uid, ticks)) return p.name ?? ""; return ""; }
static void InAppStop(long uid, long ticks, int kind)
{
int k = Code(uid, kind, ticks);
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) rc.CallStatic("stopNow", k); } catch { }
#endif
var l = ProfileStore.Load(); bool found = ApplyStop(l, uid, kind, ticks);
if (found) { ProfileStore.Save(l); Bump(); PruneEmpty(); }
HideOverlay(); _counting = false; ScheduleNow();
FinishIfOutside();
}
static void InAppSnooze(long uid, long ticks, int kind)
{
int k = Code(uid, kind, ticks);
int mn = SnoozeMinutes();
long tgt = DateTime.Now.AddMinutes(mn).Ticks;
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) rc.CallStatic("snoozeNow", k, uid, ticks, (long)mn); } catch { }
#endif
var l = ProfileStore.Load(); bool found = ApplySnooze(l, uid, ticks, tgt);
if (found) { ProfileStore.Save(l); Bump(); }
HideOverlay(); _counting = false; ScheduleNow();
FinishIfOutside();
}
// V40: finish ONLY when the alarm rang while the phone was asleep/locked (the system dragged the ringer up over
// the keyguard against the user's will) -> answer drops back to the lockscreen. When it rang while awake (in-app,
// or the user tapped the notif on an unlocked screen) we only hid the overlay above, revealing the app they chose
// to open -> NO finish. The flag is the receiver's own static (fireHappenedLocked), set at fire time; reading a
// missing field (old receiver) throws -> caught -> false -> never finish, so a partial paste degrades, never breaks.
static void FinishIfOutside()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try
{
using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver"))
{
bool lockedAtFire = rc.GetStatic<bool>("fireHappenedLocked");
if (lockedAtFire)
{
using (var up = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (var act = up.GetStatic<AndroidJavaObject>("currentActivity"))
if (act != null) act.Call("finish");
}
}
} catch { }
#endif
}
static TMP_FontAsset _font;
static TMP_FontAsset Font()
{
if (_font != null) return _font;
foreach (var f in Resources.FindObjectsOfTypeAll<TMP_FontAsset>()) { if (f?.characterTable == null) continue; foreach (var c in f.characterTable) if (c != null && c.unicode == 0x062D) { _font = f; return f; } }
var t = UnityEngine.Object.FindAnyObjectByType<TextMeshProUGUI>(); if (t?.font != null) _font = t.font;
if (_font == null) _font = TMP_Settings.defaultFontAsset; return _font;
}
static void HideOverlay() { _ringingKey = 0; if (_overlay != null) _overlay.SetActive(false); Screen.sleepTimeout = SleepTimeout.SystemSetting; }
static void ShowOverlay(string msg, string pname)
{
if (_overlay == null)
{
_overlay = new GameObject("AlarmOverlay");
var cv = _overlay.AddComponent<Canvas>(); cv.renderMode = RenderMode.ScreenSpaceOverlay; cv.overrideSorting = true; cv.sortingOrder = 1000; _overlay.AddComponent<GraphicRaycaster>();
var rt = _overlay.GetComponent<RectTransform>(); rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
var bg = _overlay.AddComponent<Image>(); bg.color = new Color(17f/255f,20f/255f,23f/255f,1f); bg.raycastTarget = true;
var tg = new GameObject("Text"); tg.transform.SetParent(_overlay.transform, false); var tr = tg.AddComponent<RectTransform>(); tr.anchorMin = new Vector2(0f, 0.60f); tr.anchorMax = new Vector2(1f, 0.84f); tr.offsetMin = new Vector2(60, 0); tr.offsetMax = new Vector2(-60, 0);
var tmp = tg.AddComponent<TextMeshProUGUI>(); tmp.font = Font(); tmp.fontSize = 56; tmp.color = Paper; tmp.alignment = TextAlignmentOptions.Center; _overlayText = tg.AddComponent<ArabicText>();
var cg = new GameObject("Count"); cg.transform.SetParent(_overlay.transform, false); var cr = cg.AddComponent<RectTransform>(); cr.anchorMin = new Vector2(0f, 0.50f); cr.anchorMax = new Vector2(1f, 0.58f); cr.offsetMin = new Vector2(60, 0); cr.offsetMax = new Vector2(-60, 0);
_countText = cg.AddComponent<TextMeshProUGUI>(); _countText.font = Font(); _countText.fontSize = 40; _countText.color = Gold; _countText.alignment = TextAlignmentOptions.Center;
PillBtn(_overlay.transform, "Stop", DutyScheduler.BtnStop(), Gold, Ink, 0.38f, () => InAppStop(_fireUid, _fireTicks, _fireKind));
PillBtn(_overlay.transform, "Snooze", DutyScheduler.BtnSnoozeN(SnoozeMinutes()), GrayPill, Paper, 0.24f, () => InAppSnooze(_fireUid, _fireTicks, _fireKind));
}
if (_snoozeAr != null) _snoozeAr.SetText(DutyScheduler.BtnSnoozeN(SnoozeMinutes()));
_overlayText.SetText(msg + "\n" + pname); _overlay.SetActive(true);
}
static void PillBtn(Transform parent, string name, string label, Color bg, Color fg, float y, UnityEngine.Events.UnityAction on)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var rt = go.AddComponent<RectTransform>(); rt.anchorMin = new Vector2(0.5f, y); rt.anchorMax = new Vector2(0.5f, y); rt.pivot = new Vector2(0.5f, 0.5f); rt.sizeDelta = new Vector2(560, 130); rt.anchoredPosition = Vector2.zero;
var img = go.AddComponent<Image>(); img.color = bg;
var lg = new GameObject("Label"); lg.transform.SetParent(go.transform, false); var lr = lg.AddComponent<RectTransform>(); lr.anchorMin = Vector2.zero; lr.anchorMax = Vector2.one; lr.offsetMin = Vector2.zero; lr.offsetMax = Vector2.zero;
var lt = lg.AddComponent<TextMeshProUGUI>(); lt.font = Font(); lt.fontSize = 48; lt.color = fg; lt.alignment = TextAlignmentOptions.Center;
var ar = lg.AddComponent<ArabicText>(); ar.SetText(label); if (name == "Snooze") _snoozeAr = ar;
var b = go.AddComponent<Button>(); b.targetGraphic = img; var cb = b.colors; cb.pressedColor = new Color(0.55f,0.55f,0.55f,1f); b.colors = cb; b.transition = Selectable.Transition.ColorTint; b.onClick.AddListener(on);
}
public static void ShowPermissionsPage()
{
_permFromSettings = permFromSettings; permFromSettings = false;
if (_permPanel != null) { _permPanel.SetActive(true); ApplyPermMode(); RefreshPills(); return; }
_permPanel = new GameObject("PermPage");
var cv = _permPanel.AddComponent<Canvas>(); cv.renderMode = RenderMode.ScreenSpaceOverlay; cv.overrideSorting = true; cv.sortingOrder = 1100; _permPanel.AddComponent<GraphicRaycaster>();
var rt = _permPanel.GetComponent<RectTransform>(); rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
var bg = _permPanel.AddComponent<Image>(); bg.color = new Color(14f/255f,17f/255f,19f/255f,1f); bg.raycastTarget = true;
Head(_permPanel.transform, "Title", 0.90f, 0.99f, DutyScheduler.PermTitle(), 64, Paper);
Head(_permPanel.transform, "Sub", 0.84f, 0.90f, DutyScheduler.PermSub(), 28, Sub);
PermRow(_permPanel.transform, "R_Notif", 0.66f, DutyScheduler.PermNotifT(), DutyScheduler.PermNotifS(), () => NotifEnabled(), () => { RequestPermissions(); OpenNotifSettings(); }, false);
PermRow(_permPanel.transform, "R_Auto", 0.48f, DutyScheduler.PermAutoT(), DutyScheduler.PermAutoS(), () => AutostartProven(), () => OpenAutoStartSettings(), true);
PermRow(_permPanel.transform, "R_Batt", 0.30f, DutyScheduler.PermBattT(), DutyScheduler.PermBattS(), () => BatteryIgnored(), () => OpenBatteryExempt(), false);
var cont = new GameObject("Continue"); cont.transform.SetParent(_permPanel.transform, false);
var crt = cont.AddComponent<RectTransform>(); crt.anchorMin = new Vector2(0.5f, 0.06f); crt.anchorMax = new Vector2(0.5f, 0.06f); crt.pivot = new Vector2(0.5f,0.5f); crt.sizeDelta = new Vector2(560,150);
var cimg = cont.AddComponent<Image>(); cimg.color = Gold;
var clg = new GameObject("L"); clg.transform.SetParent(cont.transform, false); var clrt = clg.AddComponent<RectTransform>(); clrt.anchorMin = Vector2.zero; clrt.anchorMax = Vector2.one; clrt.offsetMin = Vector2.zero; clrt.offsetMax = Vector2.zero;
var ct = clg.AddComponent<TextMeshProUGUI>(); ct.font = Font(); ct.fontSize = 48; ct.color = Ink; ct.alignment = TextAlignmentOptions.Center; clg.AddComponent<ArabicText>().SetText(DutyScheduler.PermContinue());
var cb2 = cont.AddComponent<Button>(); cb2.targetGraphic = cimg; cb2.onClick.AddListener(() => { ConfirmAutostart(); _permPanel.SetActive(false); ScheduleNow(); });
_contGo = cont;
var hm = UnityEngine.Object.FindAnyObjectByType<HomeManager>();
_xGo = new GameObject("PermClose"); _xGo.transform.SetParent(_permPanel.transform, false);
var xrt = _xGo.AddComponent<RectTransform>(); xrt.anchorMin = new Vector2(0,1); xrt.anchorMax = new Vector2(0,1); xrt.pivot = new Vector2(.5f,.5f); xrt.sizeDelta = new Vector2(110,110); xrt.anchoredPosition = new Vector2(95,-95);
_xImg = _xGo.AddComponent<Image>(); _xImg.sprite = hm != null ? hm.wizardClose : null; _xImg.preserveAspect = true; _xImg.raycastTarget = true; _xImg.color = Color.white;
var xb = _xGo.AddComponent<Button>(); xb.targetGraphic = _xImg; xb.onClick.AddListener(() => { _permFromSettings = false; _permPanel.SetActive(false); SettingsScreen.Show(); });
_xGo.transform.SetAsLastSibling();
ApplyPermMode();
RefreshPills();
}
static void ApplyPermMode()
{
if (_permPanel == null) return;
var cv = _permPanel.GetComponent<Canvas>();
if (_permFromSettings) { if (cv != null) cv.sortingOrder = 1200; if (_contGo != null) _contGo.SetActive(false); if (_xGo != null) _xGo.SetActive(true); }
else { if (cv != null) cv.sortingOrder = 1100; if (_contGo != null) _contGo.SetActive(true); if (_xGo != null) _xGo.SetActive(false); }
}
static void Head(Transform parent, string name, float y0, float y1, string text, float size, Color col)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var rt = go.AddComponent<RectTransform>(); rt.anchorMin = new Vector2(0.07f, y0); rt.anchorMax = new Vector2(0.93f, y1); rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
var t = go.AddComponent<TextMeshProUGUI>(); t.font = Font(); t.fontSize = size; t.color = col; t.alignment = TextAlignmentOptions.MidlineRight; go.AddComponent<ArabicText>().SetText(text);
}
static void PermRow(Transform parent, string name, float y, string title, string sub, Func<bool> state, Action grant, bool isAuto)
{
var row = new GameObject(name); row.transform.SetParent(parent, false);
var rrt = row.AddComponent<RectTransform>(); rrt.anchorMin = new Vector2(0.07f, y); rrt.anchorMax = new Vector2(0.93f, y + 0.13f); rrt.offsetMin = Vector2.zero; rrt.offsetMax = Vector2.zero;
var tt = new GameObject("T"); tt.transform.SetParent(row.transform, false); var ttr = tt.AddComponent<RectTransform>(); ttr.anchorMin = new Vector2(0.34f,0.5f); ttr.anchorMax = new Vector2(1f,1f); ttr.offsetMin = Vector2.zero; ttr.offsetMax = Vector2.zero;
var ttmp = tt.AddComponent<TextMeshProUGUI>(); ttmp.font = Font(); ttmp.fontSize = 38; ttmp.color = Paper; ttmp.alignment = TextAlignmentOptions.MidlineRight; tt.AddComponent<ArabicText>().SetText(title);
var sg = new GameObject("S"); sg.transform.SetParent(row.transform, false); var srt = sg.AddComponent<RectTransform>(); srt.anchorMin = new Vector2(0.34f,0.0f); srt.anchorMax = new Vector2(1f,0.5f); srt.offsetMin = Vector2.zero; srt.offsetMax = Vector2.zero;
var stmp = sg.AddComponent<TextMeshProUGUI>(); stmp.font = Font(); stmp.fontSize = 24; stmp.color = Sub; stmp.alignment = TextAlignmentOptions.MidlineRight; sg.AddComponent<ArabicText>().SetText(sub);
var pill = new GameObject("Pill"); pill.transform.SetParent(row.transform, false); var prt = pill.AddComponent<RectTransform>(); prt.anchorMin = new Vector2(0f,0.2f); prt.anchorMax = new Vector2(0.30f,0.8f); prt.offsetMin = Vector2.zero; prt.offsetMax = Vector2.zero;
var pimg = pill.AddComponent<Image>(); pimg.color = Gold;
var plg = new GameObject("PL"); plg.transform.SetParent(pill.transform, false); var plrt = plg.AddComponent<RectTransform>(); plrt.anchorMin = Vector2.zero; plrt.anchorMax = Vector2.one; plrt.offsetMin = Vector2.zero; plrt.offsetMax = Vector2.zero;
var pt = plg.AddComponent<TextMeshProUGUI>(); pt.font = Font(); pt.fontSize = 30; pt.color = Ink; pt.alignment = TextAlignmentOptions.Center;
plg.AddComponent<ArabicText>().SetText(isAuto ? DutyScheduler.PermOpenSettings() : DutyScheduler.PermAllow());
var b = pill.AddComponent<Button>(); b.targetGraphic = pimg; b.onClick.AddListener(() => grant());
_stateMap[b] = state; _labelMap[b] = plg; _autoMap[b] = isAuto;
}
static void RefreshPills()
{
if (_permPanel == null || !_permPanel.activeSelf) return;
foreach (var kv in _stateMap)
{
bool ok = kv.Value(); bool isAuto = _autoMap[kv.Key];
var lbl = _labelMap[kv.Key].GetComponent<TextMeshProUGUI>();
var ar = _labelMap[kv.Key].GetComponent<ArabicText>();
var img = kv.Key.targetGraphic as Image;
string txt = ok ? DutyScheduler.PermAllowed() : (isAuto ? DutyScheduler.PermOpenSettings() : DutyScheduler.PermAllow());
if (ar != null) ar.SetText(txt); else if (lbl != null) lbl.text = txt;
if (lbl != null) lbl.color = ok ? Sub : Ink;
if (img != null) img.color = ok ? GrayPill : Gold;
kv.Key.interactable = !ok;
}
}
static void OpenNotifSettings()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var i = new AndroidJavaObject("android.content.Intent", "android.settings.APP_NOTIFICATION_SETTINGS"))
{ i.Call<AndroidJavaObject>("putExtra", "android.provider.extra.APP_PACKAGE", _ctx.Call<string>("getPackageName")); i.Call<AndroidJavaObject>("addFlags", 0x10000000); _ctx.Call("startActivity", i); } } catch { }
#endif
}
public static void OpenAutoStartSettings()
{
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return;
try { string m; using (var b = new AndroidJavaClass("android.os.Build")) m = (b.GetStatic<string>("MANUFACTURER") ?? "").ToLower();
AndroidJavaObject intent = null;
if (m.Contains("xiaomi") || m.Contains("redmi") || m.Contains("poco") || m.Contains("mi")) intent = Comp("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity");
else if (m.Contains("oppo") || m.Contains("realme") || m.Contains("oneplus") || m.Contains("oplus")) intent = Comp("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity") ?? Comp("com.oplus.safecenter", "com.oplus.safecenter.startupapp.StartupAppListActivity");
else if (m.Contains("vivo") || m.Contains("iqoo")) intent = Comp("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity");
else if (m.Contains("huawei") || m.Contains("honor")) intent = Comp("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
else if (m.Contains("samsung")) intent = Comp("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity");
if (intent != null) { try { _ctx.Call("startActivity", intent); return; } catch { } finally { try { intent.Dispose(); } catch { } } }
OpenAppDetails();
} catch (Exception e) { Debug.LogWarning("AutoStart: " + e.Message); }
#endif
}
static void OpenBatteryExempt()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string pkg = _ctx.Call<string>("getPackageName");
using (var uc = new AndroidJavaClass("android.net.Uri")) using (var u = uc.CallStatic<AndroidJavaObject>("parse", "package:" + pkg))
using (var rq = new AndroidJavaObject("android.content.Intent", "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", u))
{ rq.Call<AndroidJavaObject>("addFlags", 0x10000000); _ctx.Call("startActivity", rq); return; } } catch { }
OpenAppDetails();
#endif
}
static void OpenAppDetails()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string pkg = _ctx.Call<string>("getPackageName");
using (var uc = new AndroidJavaClass("android.net.Uri")) using (var u = uc.CallStatic<AndroidJavaObject>("parse", "package:" + pkg))
using (var i = new AndroidJavaObject("android.content.Intent", "android.settings.APPLICATION_DETAILS_SETTINGS", u))
{ i.Call<AndroidJavaObject>("addFlags", 0x10000000); _ctx.Call("startActivity", i); } } catch { }
#endif
}
static AndroidJavaObject Comp(string pkg, string cls)
{ try { using (var c = new AndroidJavaObject("android.content.ComponentName", pkg, cls)) { var i = new AndroidJavaObject("android.content.Intent"); i.Call<AndroidJavaObject>("setComponent", c); i.Call<AndroidJavaObject>("addFlags", 0x10000000); return i; } } catch { return null; } }
}
// END-WAJIBY-ENGINE-V40_WALLCLOCK43textScripts/AlarmSystem.cs.bak
// CANARY: WAJIBY_ENGINE_V30_KEYED
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
using System.Collections.Generic;
using System.IO;
public static class AlarmSystem
{
public const int KIND_DUTY = 1, KIND_REMINDER = 2, KIND_SNOOZE = 3;
public const int AUTO_SECONDS = 60;
const int SNOOZE_MINUTES = 5;
const int GEN = 30;
public static int fireCount = 0;
public static int revision = 0;
static AndroidJavaObject _ctx, _am;
static readonly HashSet<int> _kept = new HashSet<int>();
static GameObject _overlay; static ArabicText _overlayText; static TextMeshProUGUI _countText;
static long _fireUid, _fireTicks; static int _fireKind; static float _fireAt; static bool _counting;
static long _polledFireMs = 0;
static float _tSched = -10f, _tCmd = -10f;
static GameObject _permPanel;
static readonly Dictionary<Button, Func<bool>> _stateMap = new Dictionary<Button, Func<bool>>();
static readonly Dictionary<Button, GameObject> _labelMap = new Dictionary<Button, GameObject>();
static readonly Dictionary<Button, bool> _autoMap = new Dictionary<Button, bool>();
static readonly Color Gold = new Color(201f/255f,162f/255f,39f/255f,1f);
static readonly Color Ink = new Color(0.05f,0.05f,0.05f,1f);
static readonly Color Paper = new Color(242f/255f,242f/255f,236f/255f,1f);
static readonly Color Sub = new Color(154f/255f,165f/255f,156f/255f,1f);
static readonly Color GrayPill = new Color(0.20f,0.22f,0.20f,1f);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
if (UnityEngine.Object.FindAnyObjectByType<Drv>() == null) new GameObject("AlarmDrv").AddComponent<Drv>();
Init(); DiagToast();
EnsureUids(); PruneEmpty(); ConsumeLog(); ScheduleNow();
if (!AllGood()) ShowPermissionsPage();
}
class Drv : MonoBehaviour
{
void OnApplicationFocus(bool f) { if (f) { ConsumeLog(); ScheduleNow(); RefreshPills(); } }
void Update() { Tick(); }
}
public static void Init()
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (_ctx == null)
{
try { using (var up = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) _ctx = up.GetStatic<AndroidJavaObject>("currentActivity");
if (_ctx != null) _am = _ctx.Call<AndroidJavaObject>("getSystemService", "alarm"); }
catch (Exception e) { Debug.LogWarning("Init: " + e.Message); }
}
#endif
}
static void Bump() { revision++; }
static int Code(long uid, int kind, long ticks)
{ unchecked { long h = (uid * 1000003L) ^ ((long)kind * 7919L) ^ ((ticks >> 4) * 2654435761L); return (int)(h & 0x7FFFFFFFL); } }
static void DiagToast()
{
int r = -1, a = -1;
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) r = rc.GetStatic<int>("GEN"); } catch { }
try { using (var ac = new AndroidJavaClass("com.knoxprojects.wajiby.app.WajibyActivity")) a = ac.GetStatic<int>("GEN"); } catch { }
#endif
Toast(DutyScheduler.AppName() + " E" + GEN + " R" + r + " A" + a);
}
static void Toast(string s)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var t = new AndroidJavaClass("android.widget.Toast")) using (var to = t.CallStatic<AndroidJavaObject>("makeText", _ctx, s, 1)) to.Call("show"); } catch { }
#endif
}
static bool NotifEnabled()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { int sdk; using (var v = new AndroidJavaClass("android.os.Build$VERSION")) sdk = v.GetStatic<int>("SDK_INT");
if (sdk >= 33 && _ctx.Call<int>("checkSelfPermission", "android.permission.POST_NOTIFICATIONS") != 0) return false;
using (var nm = _ctx.Call<AndroidJavaObject>("getSystemService", "notification")) return nm.Call<bool>("areNotificationsEnabled"); } catch { }
#endif
return true;
}
static bool BatteryIgnored()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string pkg = _ctx.Call<string>("getPackageName");
using (var pm = _ctx.Call<AndroidJavaObject>("getSystemService", "power")) return pm.Call<bool>("isIgnoringBatteryOptimizations", pkg); } catch { }
#endif
return true;
}
static bool AutostartProven()
{
if (PlayerPrefs.GetInt("wajiby_autostart_ok", 0) == 1) return true;
string p = PathOf("wjq_bg.txt");
if (p != null && File.Exists(p)) { PlayerPrefs.SetInt("wajiby_autostart_ok", 1); PlayerPrefs.Save(); return true; }
return false;
}
static bool AllGood() { return NotifEnabled() && BatteryIgnored() && AutostartProven(); }
public static void ConfirmAutostart() { PlayerPrefs.SetInt("wajiby_autostart_ok", 1); PlayerPrefs.Save(); }
public static void RequestPermissions()
{
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return;
try { int sdk; using (var v = new AndroidJavaClass("android.os.Build$VERSION")) sdk = v.GetStatic<int>("SDK_INT");
if (sdk >= 33 && _ctx.Call<int>("checkSelfPermission", "android.permission.POST_NOTIFICATIONS") != 0)
_ctx.Call("requestPermissions", new string[] { "android.permission.POST_NOTIFICATIONS" }, 101); } catch { }
#endif
}
static void EnsureUids()
{
var list = ProfileStore.Load(); int seed = PlayerPrefs.GetInt("wajiby_uid_seed", 1); bool ch = false;
foreach (var p in list) { if (p?.alarms == null) continue; foreach (var a in p.alarms) if (a != null && a.uid == 0) { a.uid = seed++; ch = true; } }
if (ch) { PlayerPrefs.SetInt("wajiby_uid_seed", seed); PlayerPrefs.Save(); ProfileStore.Save(list); Bump(); }
}
static void PruneEmpty()
{
var list = ProfileStore.Load(); int b = list.Count;
list.RemoveAll(p => p == null || p.alarms == null || p.alarms.Count == 0);
if (list.Count != b) { ProfileStore.Save(list); Bump(); }
}
static string PathOf(string name)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var f = _ctx.Call<AndroidJavaObject>("getFilesDir")) return f.Call<string>("getPath") + "/" + name; } catch { }
#endif
return null;
}
// live-registry: exact triples currently armed. The receiver refuses any fire whose
// uid:kind:ticks is NOT here, so orphans left in the OS by a previous session/version
// (or by a deleted profile) can never ring. Rewritten on every schedule.
static void WriteLive(List<string> reg)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string p = PathOf("live.txt"); if (p == null) return;
File.WriteAllText(p, reg.Count == 0 ? "" : string.Join("\n", reg.ToArray())); } catch { }
#endif
}
static long MsToTicks(long ms) { return ms * TimeSpan.TicksPerMillisecond + 6213559680000000L; }
static void ConsumeLog()
{
string p = PathOf("wjq_cmds.txt"); if (p == null || !File.Exists(p)) return;
string[] lines; try { lines = File.ReadAllLines(p); } catch { return; }
if (lines.Length == 0) return;
var list = ProfileStore.Load(); bool ch = false;
foreach (var ln in lines)
{
var t = ln.Split(':'); if (t.Length < 2) continue;
long uid = 0, x1 = 0, x2 = 0; int k = 0;
long.TryParse(t.Length > 1 ? t[1] : "", out uid);
if (t[0] == "stop" && t.Length >= 4 && int.TryParse(t[2], out k) && long.TryParse(t[3], out x1))
{ bool found = ApplyStop(list, uid, k, x1); ch |= found; if (uid == _fireUid && x1 == _fireTicks) { HideOverlay(); _counting = false; } }
else if (t[0] == "snooze" && t.Length >= 4 && long.TryParse(t[2], out x1) && long.TryParse(t[3], out x2))
ch |= ApplySnooze(list, uid, x1, MsToTicks(x2));
}
try { File.WriteAllText(p, ""); } catch { }
if (ch) { ProfileStore.Save(list); Bump(); PruneEmpty(); }
}
static bool Match(DutyAlarm a, long uid, long ticks) { return (uid != 0 && a.uid == uid) || (uid == 0 && (a.ticks == ticks || a.reminderTicks == ticks || a.snoozedTicks == ticks)); }
// STOP = DELETE uniformly (duty AND reminder). The old kind-split is what made a
// pre-reminder stop look like "won't delete" while a duty-time stop deleted.
static bool ApplyStop(List<Profile> list, long uid, int kind, long ticks)
{
foreach (var p in list) { if (p?.alarms == null) continue;
for (int i = p.alarms.Count - 1; i >= 0; i--) { var a = p.alarms[i]; if (a == null || !Match(a, uid, ticks)) continue;
p.alarms.RemoveAt(i); return true; } }
return false;
}
static bool ApplySnooze(List<Profile> list, long uid, long ticks, long tgt)
{
foreach (var p in list) { if (p?.alarms == null) continue;
foreach (var a in p.alarms) if (a != null && Match(a, uid, ticks)) { a.reminderTicks = 0; a.snoozedTicks = tgt; a.reminderLocked = true; return true; } }
return false;
}
public static void ScheduleAll(List<Profile> ignored)
{
EnsureUids(); PruneEmpty();
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null || _am == null) return;
try { long now = DateTime.Now.Ticks; var keep = new HashSet<int>(); var reg = new List<string>();
foreach (var p in ProfileStore.Load()) { if (p == null || !p.alarmsActive || p.alarms == null) continue;
foreach (var a in p.alarms) { if (a == null || !a.active) continue;
if (a.snoozedTicks > now) { Arm(a.snoozedTicks, a.uid, KIND_SNOOZE, p.name, a.ticks, keep); reg.Add(a.uid + ":" + KIND_SNOOZE + ":" + a.ticks); }
if (a.reminderTicks > now && !a.reminderLocked) { Arm(a.reminderTicks, a.uid, KIND_REMINDER, p.name, a.reminderTicks, keep); reg.Add(a.uid + ":" + KIND_REMINDER + ":" + a.reminderTicks); }
if (a.ticks > now) { Arm(a.ticks, a.uid, KIND_DUTY, p.name, a.ticks, keep); reg.Add(a.uid + ":" + KIND_DUTY + ":" + a.ticks); } } }
foreach (int old in _kept) if (!keep.Contains(old)) { Cancel(old); StopLive(old); }
_kept.Clear(); foreach (int x in keep) _kept.Add(x);
WriteLive(reg);
} catch (Exception e) { Debug.LogWarning("ScheduleAll: " + e.Message); }
#endif
}
static void ScheduleNow() { _tSched = Time.time; ScheduleAll(null); }
static void Arm(long when, long uid, int kind, string title, long payloadTicks, HashSet<int> keep)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { long ms = (long)new DateTime(when).ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
int k = Code(uid, kind, payloadTicks);
using (var bi = new AndroidJavaObject("android.content.Intent"))
{
bi.Call<AndroidJavaObject>("setClassName", _ctx, "com.knoxprojects.wajiby.app.AlarmReceiver");
bi.Call<AndroidJavaObject>("putExtra", "UID", uid); bi.Call<AndroidJavaObject>("putExtra", "TICKS", payloadTicks);
bi.Call<AndroidJavaObject>("putExtra", "KIND", kind); bi.Call<AndroidJavaObject>("putExtra", "KEY", k); bi.Call<AndroidJavaObject>("putExtra", "TITLE", title ?? "");
using (var pc = new AndroidJavaClass("android.app.PendingIntent"))
using (var trig = pc.CallStatic<AndroidJavaObject>("getBroadcast", _ctx, k, bi, 0x0C000000))
using (var li = new AndroidJavaObject("android.content.Intent"))
{
li.Call<AndroidJavaObject>("setClassName", _ctx, "com.knoxprojects.wajiby.app.WajibyActivity");
li.Call<AndroidJavaObject>("putExtra", "WAJIBY_ALARM", true); li.Call<AndroidJavaObject>("putExtra", "UID", uid);
li.Call<AndroidJavaObject>("putExtra", "TICKS", payloadTicks); li.Call<AndroidJavaObject>("putExtra", "KIND", kind); li.Call<AndroidJavaObject>("putExtra", "KEY", k);
using (var show = pc.CallStatic<AndroidJavaObject>("getActivity", _ctx, k + 1, li, 0x0C000000))
using (var info = new AndroidJavaObject("android.app.AlarmManager$AlarmClockInfo", ms, show))
_am.Call("setAlarmClock", info, trig);
}
}
keep.Add(k);
} catch (Exception e) { Debug.LogWarning("Arm: " + e.Message); }
#endif
}
static void Cancel(int k)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var bi = new AndroidJavaObject("android.content.Intent"))
{ bi.Call<AndroidJavaObject>("setClassName", _ctx, "com.knoxprojects.wajiby.app.AlarmReceiver");
using (var pc = new AndroidJavaClass("android.app.PendingIntent"))
using (var p = pc.CallStatic<AndroidJavaObject>("getBroadcast", _ctx, k, bi, 0x20000000))
if (p != null) _am.Call("cancel", p); } } catch { }
#endif
}
static void StopLive(int k)
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) rc.CallStatic("stopNow", k); } catch { }
#endif
}
static void Tick()
{
if (Time.time - _tSched > 25f) ScheduleNow();
if (Time.time - _tCmd > 3f) { ConsumeLog(); _tCmd = Time.time; }
long fu = 0, ft = 0; int fk = 0; long fms = 0;
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var ac = new AndroidJavaClass("com.knoxprojects.wajiby.app.WajibyActivity")) { fu = ac.GetStatic<long>("lastFireUid"); ft = ac.GetStatic<long>("lastFireTicks"); fk = ac.GetStatic<int>("lastFireKind"); fms = ac.GetStatic<long>("lastFireMs"); } } catch { }
#endif
if (fms != 0 && fms != _polledFireMs && (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond - 62135596800000L) - fms < 90000)
{ _polledFireMs = fms; fireCount++; OnFire(fu, ft, fk); }
long cu, ct; int ck = ConsumeAlarmIntent(out cu, out ct);
if (ck == -1) InAppStop(cu, ct, _fireKind);
else if (ck == -2) InAppSnooze(cu, ct, _fireKind);
if (_counting && _overlay != null && _overlay.activeSelf)
{
float rem = AUTO_SECONDS - (Time.time - _fireAt);
if (_countText != null) { int s = Mathf.Max(0, Mathf.CeilToInt(rem));
_countText.text = ArabicShaper.Shape(DutyScheduler.CountdownPrefix() + (s / 60) + ":" + (s % 60).ToString("00")); }
if (rem <= 0f) InAppSnooze(_fireUid, _fireTicks, _fireKind);
}
}
public static int ConsumeAlarmIntent(out long uid, out long ticks)
{
uid = 0; ticks = 0;
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return 0;
try { using (var actClass = new AndroidJavaClass("com.knoxprojects.wajiby.app.WajibyActivity"))
using (var i = actClass.GetStatic<AndroidJavaObject>("latestIntent"))
{
if (i == null) return 0;
if (i.Call<bool>("getBooleanExtra", "WAJIBY_ALARM", false))
{
string cmd = i.Call<string>("getStringExtra", "WAJIBY_CMD");
uid = i.Call<long>("getLongExtra", "UID", 0);
ticks = i.Call<long>("getLongExtra", "TICKS", 0);
i.Call("removeExtra", "WAJIBY_ALARM"); i.Call("removeExtra", "WAJIBY_CMD"); i.Call("removeExtra", "UID"); i.Call("removeExtra", "TICKS"); i.Call("removeExtra", "KIND"); i.Call("removeExtra", "KEY");
actClass.SetStatic<AndroidJavaObject>("latestIntent", null);
if (cmd == "stop") return -1;
if (cmd == "snooze") return -2;
return 0;
}
}
} catch { }
#endif
return 0;
}
static void OnFire(long uid, long ticks, int kind)
{
_fireUid = uid; _fireTicks = ticks; _fireKind = kind;
if (kind == KIND_SNOOZE) { var l = ProfileStore.Load(); bool ch = false; foreach (var p in l) if (p?.alarms != null) foreach (var a in p.alarms) if (a != null && Match(a, uid, ticks)) { a.snoozedTicks = 0; ch = true; } if (ch) { ProfileStore.Save(l); Bump(); } }
Screen.sleepTimeout = SleepTimeout.NeverSleep;
ShowOverlay((kind == KIND_REMINDER) ? DutyScheduler.MsgReminder() : DutyScheduler.MsgDuty(), NameFor(uid, ticks));
_fireAt = Time.time; _counting = true;
}
static string NameFor(long uid, long ticks) { foreach (var p in ProfileStore.Load()) if (p?.alarms != null) foreach (var a in p.alarms) if (a != null && Match(a, uid, ticks)) return p.name ?? ""; return ""; }
static void InAppStop(long uid, long ticks, int kind)
{
int k = Code(uid, kind, ticks);
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) rc.CallStatic("stopNow", k); } catch { }
#endif
var l = ProfileStore.Load(); bool found = ApplyStop(l, uid, kind, ticks);
if (found) { ProfileStore.Save(l); Bump(); PruneEmpty(); }
HideOverlay(); _counting = false; ScheduleNow();
}
static void InAppSnooze(long uid, long ticks, int kind)
{
int k = Code(uid, kind, ticks);
long tgt = DateTime.Now.AddMinutes(SNOOZE_MINUTES).Ticks;
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var rc = new AndroidJavaClass("com.knoxprojects.wajiby.app.AlarmReceiver")) rc.CallStatic("snoozeNow", k, uid, ticks); } catch { }
#endif
var l = ProfileStore.Load(); bool found = ApplySnooze(l, uid, ticks, tgt);
if (found) { ProfileStore.Save(l); Bump(); }
HideOverlay(); _counting = false; ScheduleNow();
}
static TMP_FontAsset _font;
static TMP_FontAsset Font()
{
if (_font != null) return _font;
foreach (var f in Resources.FindObjectsOfTypeAll<TMP_FontAsset>()) { if (f?.characterTable == null) continue; foreach (var c in f.characterTable) if (c != null && c.unicode == 0x062D) { _font = f; return f; } }
var t = UnityEngine.Object.FindAnyObjectByType<TextMeshProUGUI>(); if (t?.font != null) _font = t.font;
if (_font == null) _font = TMP_Settings.defaultFontAsset; return _font;
}
static void HideOverlay() { if (_overlay != null) _overlay.SetActive(false); Screen.sleepTimeout = SleepTimeout.SystemSetting; }
static void ShowOverlay(string msg, string pname)
{
if (_overlay == null)
{
_overlay = new GameObject("AlarmOverlay");
var cv = _overlay.AddComponent<Canvas>(); cv.renderMode = RenderMode.ScreenSpaceOverlay; cv.overrideSorting = true; cv.sortingOrder = 1000; _overlay.AddComponent<GraphicRaycaster>();
var rt = _overlay.GetComponent<RectTransform>(); rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
var bg = _overlay.AddComponent<Image>(); bg.color = new Color(0.15f, 0.16f, 0.15f, 1f); bg.raycastTarget = true;
var tg = new GameObject("Text"); tg.transform.SetParent(_overlay.transform, false); var tr = tg.AddComponent<RectTransform>(); tr.anchorMin = new Vector2(0f, 0.60f); tr.anchorMax = new Vector2(1f, 0.84f); tr.offsetMin = new Vector2(60, 0); tr.offsetMax = new Vector2(-60, 0);
var tmp = tg.AddComponent<TextMeshProUGUI>(); tmp.font = Font(); tmp.fontSize = 56; tmp.color = Paper; tmp.alignment = TextAlignmentOptions.Center; _overlayText = tg.AddComponent<ArabicText>();
var cg = new GameObject("Count"); cg.transform.SetParent(_overlay.transform, false); var cr = cg.AddComponent<RectTransform>(); cr.anchorMin = new Vector2(0f, 0.50f); cr.anchorMax = new Vector2(1f, 0.58f); cr.offsetMin = new Vector2(60, 0); cr.offsetMax = new Vector2(-60, 0);
_countText = cg.AddComponent<TextMeshProUGUI>(); _countText.font = Font(); _countText.fontSize = 40; _countText.color = Gold; _countText.alignment = TextAlignmentOptions.Center;
PillBtn(_overlay.transform, "Stop", DutyScheduler.BtnStop(), Gold, Ink, 0.38f, () => InAppStop(_fireUid, _fireTicks, _fireKind));
PillBtn(_overlay.transform, "Snooze", DutyScheduler.BtnSnoozeN(SNOOZE_MINUTES), GrayPill, Paper, 0.24f, () => InAppSnooze(_fireUid, _fireTicks, _fireKind));
}
_overlayText.SetText(msg + "\n" + pname); _overlay.SetActive(true);
}
static void PillBtn(Transform parent, string name, string label, Color bg, Color fg, float y, UnityEngine.Events.UnityAction on)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var rt = go.AddComponent<RectTransform>(); rt.anchorMin = new Vector2(0.5f, y); rt.anchorMax = new Vector2(0.5f, y); rt.pivot = new Vector2(0.5f, 0.5f); rt.sizeDelta = new Vector2(560, 130); rt.anchoredPosition = Vector2.zero;
var img = go.AddComponent<Image>(); img.color = bg;
var lg = new GameObject("Label"); lg.transform.SetParent(go.transform, false); var lr = lg.AddComponent<RectTransform>(); lr.anchorMin = Vector2.zero; lr.anchorMax = Vector2.one; lr.offsetMin = Vector2.zero; lr.offsetMax = Vector2.zero;
var lt = lg.AddComponent<TextMeshProUGUI>(); lt.font = Font(); lt.fontSize = 48; lt.color = fg; lt.alignment = TextAlignmentOptions.Center; lg.AddComponent<ArabicText>().SetText(label);
var b = go.AddComponent<Button>(); b.targetGraphic = img; var cb = b.colors; cb.pressedColor = new Color(0.55f,0.55f,0.55f,1f); b.colors = cb; b.transition = Selectable.Transition.ColorTint; b.onClick.AddListener(on);
}
public static void ShowPermissionsPage()
{
if (_permPanel != null) { _permPanel.SetActive(true); RefreshPills(); return; }
_permPanel = new GameObject("PermPage");
var cv = _permPanel.AddComponent<Canvas>(); cv.renderMode = RenderMode.ScreenSpaceOverlay; cv.overrideSorting = true; cv.sortingOrder = 1100; _permPanel.AddComponent<GraphicRaycaster>();
var rt = _permPanel.GetComponent<RectTransform>(); rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
var bg = _permPanel.AddComponent<Image>(); bg.color = new Color(18f/255f,16f/255f,14f/255f,1f); bg.raycastTarget = true;
Head(_permPanel.transform, "Title", 0.90f, 0.99f, DutyScheduler.PermTitle(), 64, Paper);
Head(_permPanel.transform, "Sub", 0.84f, 0.90f, DutyScheduler.PermSub(), 28, Sub);
PermRow(_permPanel.transform, "R_Notif", 0.66f, DutyScheduler.PermNotifT(), DutyScheduler.PermNotifS(), () => NotifEnabled(), () => { RequestPermissions(); OpenNotifSettings(); }, false);
PermRow(_permPanel.transform, "R_Auto", 0.48f, DutyScheduler.PermAutoT(), DutyScheduler.PermAutoS(), () => AutostartProven(), () => OpenAutoStartSettings(), true);
PermRow(_permPanel.transform, "R_Batt", 0.30f, DutyScheduler.PermBattT(), DutyScheduler.PermBattS(), () => BatteryIgnored(), () => OpenBatteryExempt(), false);
var cont = new GameObject("Continue"); cont.transform.SetParent(_permPanel.transform, false);
var crt = cont.AddComponent<RectTransform>(); crt.anchorMin = new Vector2(0.5f, 0.06f); crt.anchorMax = new Vector2(0.5f, 0.06f); crt.pivot = new Vector2(0.5f,0.5f); crt.sizeDelta = new Vector2(560,150);
var cimg = cont.AddComponent<Image>(); cimg.color = Gold;
var clg = new GameObject("L"); clg.transform.SetParent(cont.transform, false); var clrt = clg.AddComponent<RectTransform>(); clrt.anchorMin = Vector2.zero; clrt.anchorMax = Vector2.one; clrt.offsetMin = Vector2.zero; clrt.offsetMax = Vector2.zero;
var ct = clg.AddComponent<TextMeshProUGUI>(); ct.font = Font(); ct.fontSize = 48; ct.color = Ink; ct.alignment = TextAlignmentOptions.Center; clg.AddComponent<ArabicText>().SetText(DutyScheduler.PermContinue());
var cb = cont.AddComponent<Button>(); cb.targetGraphic = cimg; cb.onClick.AddListener(() => { ConfirmAutostart(); _permPanel.SetActive(false); ScheduleNow(); });
RefreshPills();
}
static void Head(Transform parent, string name, float y0, float y1, string text, float size, Color col)
{
var go = new GameObject(name); go.transform.SetParent(parent, false);
var rt = go.AddComponent<RectTransform>(); rt.anchorMin = new Vector2(0.07f, y0); rt.anchorMax = new Vector2(0.93f, y1); rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
var t = go.AddComponent<TextMeshProUGUI>(); t.font = Font(); t.fontSize = size; t.color = col; t.alignment = TextAlignmentOptions.MidlineRight; go.AddComponent<ArabicText>().SetText(text);
}
static void PermRow(Transform parent, string name, float y, string title, string sub, Func<bool> state, Action grant, bool isAuto)
{
var row = new GameObject(name); row.transform.SetParent(parent, false);
var rrt = row.AddComponent<RectTransform>(); rrt.anchorMin = new Vector2(0.07f, y); rrt.anchorMax = new Vector2(0.93f, y + 0.13f); rrt.offsetMin = Vector2.zero; rrt.offsetMax = Vector2.zero;
var tt = new GameObject("T"); tt.transform.SetParent(row.transform, false); var ttr = tt.AddComponent<RectTransform>(); ttr.anchorMin = new Vector2(0.34f,0.5f); ttr.anchorMax = new Vector2(1f,1f); ttr.offsetMin = Vector2.zero; ttr.offsetMax = Vector2.zero;
var ttmp = tt.AddComponent<TextMeshProUGUI>(); ttmp.font = Font(); ttmp.fontSize = 38; ttmp.color = Paper; ttmp.alignment = TextAlignmentOptions.MidlineRight; tt.AddComponent<ArabicText>().SetText(title);
var sg = new GameObject("S"); sg.transform.SetParent(row.transform, false); var srt = sg.AddComponent<RectTransform>(); srt.anchorMin = new Vector2(0.34f,0.0f); srt.anchorMax = new Vector2(1f,0.5f); srt.offsetMin = Vector2.zero; srt.offsetMax = Vector2.zero;
var stmp = sg.AddComponent<TextMeshProUGUI>(); stmp.font = Font(); stmp.fontSize = 24; stmp.color = Sub; stmp.alignment = TextAlignmentOptions.MidlineRight; sg.AddComponent<ArabicText>().SetText(sub);
var pill = new GameObject("Pill"); pill.transform.SetParent(row.transform, false); var prt = pill.AddComponent<RectTransform>(); prt.anchorMin = new Vector2(0f,0.2f); prt.anchorMax = new Vector2(0.30f,0.8f); prt.offsetMin = Vector2.zero; prt.offsetMax = Vector2.zero;
var pimg = pill.AddComponent<Image>(); pimg.color = Gold;
var plg = new GameObject("PL"); plg.transform.SetParent(pill.transform, false); var plrt = plg.AddComponent<RectTransform>(); plrt.anchorMin = Vector2.zero; plrt.anchorMax = Vector2.one; plrt.offsetMin = Vector2.zero; plrt.offsetMax = Vector2.zero;
var pt = plg.AddComponent<TextMeshProUGUI>(); pt.font = Font(); pt.fontSize = 30; pt.color = Ink; pt.alignment = TextAlignmentOptions.Center;
plg.AddComponent<ArabicText>().SetText(isAuto ? DutyScheduler.PermOpenSettings() : DutyScheduler.PermAllow());
var b = pill.AddComponent<Button>(); b.targetGraphic = pimg; b.onClick.AddListener(() => grant());
_stateMap[b] = state; _labelMap[b] = plg; _autoMap[b] = isAuto;
}
static void RefreshPills()
{
if (_permPanel == null || !_permPanel.activeSelf) return;
foreach (var kv in _stateMap)
{
bool ok = kv.Value(); bool isAuto = _autoMap[kv.Key];
var lbl = _labelMap[kv.Key].GetComponent<TextMeshProUGUI>();
var ar = _labelMap[kv.Key].GetComponent<ArabicText>();
var img = kv.Key.targetGraphic as Image;
string txt = ok ? DutyScheduler.PermAllowed() : (isAuto ? DutyScheduler.PermOpenSettings() : DutyScheduler.PermAllow());
if (ar != null) ar.SetText(txt); else if (lbl != null) lbl.text = txt;
if (lbl != null) lbl.color = ok ? Sub : Ink;
if (img != null) img.color = ok ? GrayPill : Gold;
kv.Key.interactable = !ok;
}
}
static void OpenNotifSettings()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var i = new AndroidJavaObject("android.content.Intent", "android.settings.APP_NOTIFICATION_SETTINGS"))
{ i.Call<AndroidJavaObject>("putExtra", "android.provider.extra.APP_PACKAGE", _ctx.Call<string>("getPackageName")); i.Call<AndroidJavaObject>("addFlags", 0x10000000); _ctx.Call("startActivity", i); } } catch { }
#endif
}
public static void OpenAutoStartSettings()
{
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return;
try { string m; using (var b = new AndroidJavaClass("android.os.Build")) m = (b.GetStatic<string>("MANUFACTURER") ?? "").ToLower();
AndroidJavaObject intent = null;
if (m.Contains("xiaomi") || m.Contains("redmi") || m.Contains("poco") || m.Contains("mi")) intent = Comp("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity");
else if (m.Contains("oppo") || m.Contains("realme") || m.Contains("oneplus") || m.Contains("oplus")) intent = Comp("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity") ?? Comp("com.oplus.safecenter", "com.oplus.safecenter.startupapp.StartupAppListActivity");
else if (m.Contains("vivo") || m.Contains("iqoo")) intent = Comp("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity");
else if (m.Contains("huawei") || m.Contains("honor")) intent = Comp("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
else if (m.Contains("samsung")) intent = Comp("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity");
if (intent != null) { try { _ctx.Call("startActivity", intent); return; } catch { } finally { try { intent.Dispose(); } catch { } } }
OpenAppDetails();
} catch (Exception e) { Debug.LogWarning("AutoStart: " + e.Message); }
#endif
}
static void OpenBatteryExempt()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string pkg = _ctx.Call<string>("getPackageName");
using (var uc = new AndroidJavaClass("android.net.Uri")) using (var u = uc.CallStatic<AndroidJavaObject>("parse", "package:" + pkg))
using (var rq = new AndroidJavaObject("android.content.Intent", "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", u))
{ rq.Call<AndroidJavaObject>("addFlags", 0x10000000); _ctx.Call("startActivity", rq); return; } } catch { }
OpenAppDetails();
#endif
}
static void OpenAppDetails()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { string pkg = _ctx.Call<string>("getPackageName");
using (var uc = new AndroidJavaClass("android.net.Uri")) using (var u = uc.CallStatic<AndroidJavaObject>("parse", "package:" + pkg))
using (var i = new AndroidJavaObject("android.content.Intent", "android.settings.APPLICATION_DETAILS_SETTINGS", u))
{ i.Call<AndroidJavaObject>("addFlags", 0x10000000); _ctx.Call("startActivity", i); } } catch { }
#endif
}
static AndroidJavaObject Comp(string pkg, string cls)
{ try { using (var c = new AndroidJavaObject("android.content.ComponentName", pkg, cls)) { var i = new AndroidJavaObject("android.content.Intent"); i.Call<AndroidJavaObject>("setComponent", c); i.Call<AndroidJavaObject>("addFlags", 0x10000000); return i; } } catch { return null; } }
}44textScripts/AlarmSystem.cs.bak.meta
fileFormatVersion: 2
guid: 2c855aa37f810bd42acfbf335194574b
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
45textScripts/AlarmSystem.cs.meta
fileFormatVersion: 2 guid: 5c35ca772cffba8408a73bf780dea989
46textScripts/AlarmWindow.cs
// CANARY: WAJIBY_WINDOW_V38_COMMANDUI
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class AlarmWindow : MonoBehaviour
{
HomeManager home; int index = -1, pendingSlot = -1, remSlot = -1, lastFire = -1, lastRev = -1;
TMP_FontAsset font; Sprite win, cardFrame, swOn, swOff, trashSpr; Transform content, title;
GameObject confirmRoot, remRoot; TMP_InputField remInput; RenamePanel rename; const float CARD_H = 360f;
static readonly Color White = new Color(244f/255f,240f/255f,228f/255f,1f);
static readonly Color HeaderText = new Color(244f/255f,240f/255f,228f/255f,1f);
static readonly Color Gold = new Color(213f/255f,178f/255f,74f/255f,1f);
static readonly Color BodyGray = new Color(31f/255f,48f/255f,38f/255f,1f);
static readonly Color DimText = new Color(124f/255f,138f/255f,127f/255f,1f);
static readonly Color RootDark = new Color(5f/255f,10f/255f,7f,185f/255f);
static readonly Color BoxBody = new Color(25f/255f,40f/255f,31f/255f,1f);
static Sprite _brightFrame, _brightRow;
void Update()
{
if (lastFire != AlarmSystem.fireCount) { lastFire = AlarmSystem.fireCount; if (remRoot != null && remRoot.activeSelf) remRoot.SetActive(false); if (confirmRoot != null && confirmRoot.activeSelf) confirmRoot.SetActive(false); }
if (gameObject.activeInHierarchy && AlarmSystem.revision != lastRev) { lastRev = AlarmSystem.revision; Refresh(); }
}
public static AlarmWindow Build(Transform root, HomeManager hm, Sprite win, Sprite cardFrame, Sprite swOn, Sprite trash, TMP_FontAsset font, Sprite editSpr)
{
GameObject aroot = new GameObject("AlarmRoot"); aroot.transform.SetParent(root, false);
RectTransform art = aroot.AddComponent<RectTransform>(); art.anchorMin = Vector2.zero; art.anchorMax = Vector2.one; art.offsetMin = Vector2.zero; art.offsetMax = Vector2.zero;
var abg = aroot.AddComponent<Image>(); abg.color = RootDark; abg.raycastTarget = true;
GameObject frame = new GameObject("Alarm_Frame"); frame.transform.SetParent(aroot.transform, false);
RectTransform frt = frame.AddComponent<RectTransform>(); frt.anchorMin = Vector2.zero; frt.anchorMax = Vector2.one; frt.offsetMin = Vector2.zero; frt.offsetMax = Vector2.zero;
Image fim = frame.AddComponent<Image>(); fim.sprite = BrightFrame(); fim.type = Image.Type.Sliced; fim.color = Color.white;
GameObject header = new GameObject("Alarm_Header"); header.transform.SetParent(frame.transform,false);
RectTransform hrt=header.AddComponent<RectTransform>(); hrt.anchorMin=new Vector2(0,1); hrt.anchorMax=new Vector2(1,1); hrt.offsetMin=new Vector2(0,-150); hrt.offsetMax=Vector2.zero;
Image him=header.AddComponent<Image>(); him.color=new Color(37f/255f,63f/255f,45f/255f,1f); him.raycastTarget=false;
GameObject hline=new GameObject("Alarm_HeaderLine"); hline.transform.SetParent(header.transform,false); RectTransform hlrt=hline.AddComponent<RectTransform>(); hlrt.anchorMin=new Vector2(.06f,0); hlrt.anchorMax=new Vector2(.94f,0); hlrt.pivot=new Vector2(.5f,0); hlrt.sizeDelta=new Vector2(0,5); Image hlim=hline.AddComponent<Image>(); hlim.color=Gold; hlim.raycastTarget=false;
GameObject ttl = new GameObject("Alarm_Title"); ttl.transform.SetParent(frame.transform, false);
RectTransform trt = ttl.AddComponent<RectTransform>(); trt.anchorMin = new Vector2(0,1); trt.anchorMax = new Vector2(1,1); trt.offsetMin = new Vector2(150,-105); trt.offsetMax = new Vector2(-150,-22);
var ttmp = ttl.AddComponent<TextMeshProUGUI>(); ttmp.fontSize = 54; ttmp.color = HeaderText; ttmp.alignment = TextAlignmentOptions.Right; if (font != null) ttmp.font = font; ttl.AddComponent<ArabicText>();
GameObject cap = new GameObject("Alarm_Accent"); cap.transform.SetParent(frame.transform, false); var caprt = cap.AddComponent<RectTransform>(); caprt.anchorMin = new Vector2(0.05f,1f); caprt.anchorMax = new Vector2(0.95f,1f); caprt.offsetMin = new Vector2(0,-112); caprt.offsetMax = new Vector2(0,-104); var capim = cap.AddComponent<Image>(); capim.color = Gold; capim.raycastTarget = false;
GameObject pen = new GameObject("Btn_Edit"); pen.transform.SetParent(frame.transform, false);
RectTransform prt = pen.AddComponent<RectTransform>(); prt.anchorMin = new Vector2(1,1); prt.anchorMax = new Vector2(1,1); prt.pivot = new Vector2(.5f,.5f); prt.sizeDelta = new Vector2(60,60); prt.anchoredPosition = new Vector2(-90,-62);
Image pim = pen.AddComponent<Image>(); pim.sprite = FlatIcons.Edit(); pim.color = HeaderText; pim.preserveAspect = true; pim.raycastTarget = true;
GameObject close = new GameObject("Btn_Close"); close.transform.SetParent(frame.transform, false);
RectTransform crt2 = close.AddComponent<RectTransform>(); crt2.anchorMin = new Vector2(0,1); crt2.anchorMax = new Vector2(0,1); crt2.pivot = new Vector2(.5f,.5f); crt2.sizeDelta = new Vector2(80,80); crt2.anchoredPosition = new Vector2(85,-65);
Image cim = close.AddComponent<Image>(); cim.sprite = hm != null ? hm.wizardClose : null; cim.color = Color.white; cim.preserveAspect = true; cim.raycastTarget = true;
GameObject body = new GameObject("Alarm_Body"); body.transform.SetParent(frame.transform, false);
RectTransform brt = body.AddComponent<RectTransform>(); brt.anchorMin = Vector2.zero; brt.anchorMax = Vector2.one; brt.offsetMin = new Vector2(35,35); brt.offsetMax = new Vector2(-35,-154);
var bim = body.AddComponent<Image>(); bim.color = BodyGray; bim.raycastTarget = true;
var sr = body.AddComponent<ScrollRect>(); sr.horizontal = false; sr.vertical = true; sr.movementType = ScrollRect.MovementType.Elastic;
GameObject cnt = new GameObject("Alarm_Content"); cnt.transform.SetParent(body.transform, false);
RectTransform cnrt = cnt.AddComponent<RectTransform>(); cnrt.anchorMin = new Vector2(0,1); cnrt.anchorMax = new Vector2(1,1); cnrt.pivot = new Vector2(.5f,1); cnrt.anchoredPosition = Vector2.zero; sr.content = cnrt;
body.AddComponent<Mask>().showMaskGraphic = true;
GameObject cr = Box(aroot, "AlarmConfirm", DutyScheduler.AskDeleteTitle(), win, font, hm != null ? hm.wizardCheck : null, hm != null ? hm.wizardClose : null, false);
GameObject rr = Box(aroot, "RemEdit", DutyScheduler.RemEditTitle(), win, font, hm != null ? hm.wizardCheck : null, hm != null ? hm.wizardClose : null, true);
var aw = aroot.AddComponent<AlarmWindow>(); aw.home = hm; aw.font = font; aw.win = BrightFrame(); aw.cardFrame = BrightRow(); aw.swOn = CardRuntimeBuilder.BrightSwitchOn; aw.swOff = CardRuntimeBuilder.BrightSwitchOff; aw.trashSpr = trash; aw.content = cnt.transform; aw.title = ttl.transform; aw.confirmRoot = cr; aw.remRoot = rr; aw.remInput = rr.GetComponentInChildren<TMP_InputField>(); aw.lastFire = AlarmSystem.fireCount; aw.lastRev = AlarmSystem.revision;
Btn(close, aw.Hide); Btn(cr.transform.Find("Box_Frame/Btn_Confirm").gameObject, aw.DoRemove); Btn(cr.transform.Find("Box_Frame/Btn_Cancel").gameObject, aw.HideConfirm); Btn(rr.transform.Find("Box_Frame/Btn_Confirm").gameObject, aw.ApplyReminder); Btn(rr.transform.Find("Box_Frame/Btn_Cancel").gameObject, () => aw.remRoot.SetActive(false));
Btn(pen, () => { aw.EnsureRename(); if (aw.rename != null) { var list = ProfileStore.Load(); string cur = (aw.index >= 0 && aw.index < list.Count && list[aw.index] != null) ? list[aw.index].name : ""; aw.rename.Show(cur); } });
aroot.SetActive(false); return aw;
}
static Sprite BrightFrame()
{
if (_brightFrame != null) return _brightFrame;
_brightFrame = RoundedSprite(new Color(48f/255f,68f/255f,54f/255f,1f), Gold, 8f);
return _brightFrame;
}
static Sprite BrightRow()
{
if (_brightRow != null) return _brightRow;
_brightRow = RoundedSprite(new Color(255f/255f,255f/255f,252f/255f,1f), new Color(45f/255f,82f/255f,58f,.18f), 3f);
return _brightRow;
}
static Sprite RoundedSprite(Color fill, Color edge, float edgeWidth)
{
const int N = 96; var tex = new Texture2D(N,N,TextureFormat.RGBA32,false); Vector2 c = new Vector2(N/2f,N/2f), h = new Vector2(N/2f-1,N/2f-1);
for(int y=0;y<N;y++) for(int x=0;x<N;x++) { Vector2 p=new Vector2(x+.5f,y+.5f); float d=RD(p,c,h,22f); Color col=d<=0?fill:new Color(0,0,0,0); if(edgeWidth>0 && d<=0 && RD(p,c,new Vector2(h.x-edgeWidth,h.y-edgeWidth),Mathf.Max(3,22f-edgeWidth))>0) col=edge; tex.SetPixel(x,y,col); }
tex.Apply(); return Sprite.Create(tex,new Rect(0,0,N,N),new Vector2(.5f,.5f),100f,0,SpriteMeshType.FullRect,new Vector4(28,28,28,28));
}
static float RD(Vector2 p,Vector2 c,Vector2 h,float r){Vector2 q=new Vector2(Mathf.Abs(p.x-c.x),Mathf.Abs(p.y-c.y))-(h-new Vector2(r,r));float ox=Mathf.Max(q.x,0),oy=Mathf.Max(q.y,0);return Mathf.Sqrt(ox*ox+oy*oy)+Mathf.Min(Mathf.Max(q.x,q.y),0)-r;}
static void Btn(GameObject go, UnityEngine.Events.UnityAction a) { Button old = go.GetComponent<Button>(); if (old != null) UnityEngine.Object.Destroy(old); SafeTap st = go.GetComponent<SafeTap>(); if (st == null) st = go.AddComponent<SafeTap>(); st.onTap = a; }
static GameObject Box(GameObject parent, string name, string titleText, Sprite win, TMP_FontAsset font, Sprite check, Sprite close, bool withField)
{
GameObject cr = new GameObject(name); cr.transform.SetParent(parent.transform, false);
RectTransform crrt = cr.AddComponent<RectTransform>(); crrt.anchorMin = Vector2.zero; crrt.anchorMax = Vector2.one; crrt.offsetMin = Vector2.zero; crrt.offsetMax = Vector2.zero;
var crbg = cr.AddComponent<Image>(); crbg.color = new Color(14f/255f,17f/255f,19f/255f,220f/255f); crbg.raycastTarget = true;
GameObject cframe = new GameObject("Box_Frame"); cframe.transform.SetParent(cr.transform, false);
RectTransform cfrt = cframe.AddComponent<RectTransform>(); cfrt.anchorMin = new Vector2(.5f,1); cfrt.anchorMax = new Vector2(.5f,1); cfrt.pivot = new Vector2(.5f,1); cfrt.sizeDelta = new Vector2(700,440); cfrt.anchoredPosition = new Vector2(0,-220);
Image cfim = cframe.AddComponent<Image>(); cfim.sprite = BrightFrame(); cfim.type = Image.Type.Sliced; cfim.color = Color.white;
GameObject cttl = new GameObject("Box_Title"); cttl.transform.SetParent(cframe.transform, false);
RectTransform ctrt = cttl.AddComponent<RectTransform>(); ctrt.anchorMin = new Vector2(0,1); ctrt.anchorMax = new Vector2(1,1); ctrt.offsetMin = new Vector2(75,-100); ctrt.offsetMax = new Vector2(-75,-25);
var cttmp = cttl.AddComponent<TextMeshProUGUI>(); cttmp.fontSize = 44; cttmp.color = White; cttmp.alignment = TextAlignmentOptions.Right; if (font != null) cttmp.font = font; cttl.AddComponent<ArabicText>().SetText(titleText);
GameObject cbody = new GameObject("Box_Body"); cbody.transform.SetParent(cframe.transform, false);
RectTransform cbrt = cbody.AddComponent<RectTransform>(); cbrt.anchorMin = Vector2.zero; cbrt.anchorMax = Vector2.one; cbrt.offsetMin = new Vector2(30,30); cbrt.offsetMax = new Vector2(-30,-110);
var cbim = cbody.AddComponent<Image>(); cbim.color = BoxBody; cbim.raycastTarget = true;
if (withField)
{
GameObject rf = new GameObject("RemField"); rf.transform.SetParent(cbody.transform, false);
RectTransform rfrt = rf.AddComponent<RectTransform>(); rfrt.anchorMin = new Vector2(.5f,.5f); rfrt.anchorMax = new Vector2(.5f,.5f); rfrt.pivot = new Vector2(.5f,.5f); rfrt.sizeDelta = new Vector2(300,100); rfrt.anchoredPosition = new Vector2(0,60);
var rfim = rf.AddComponent<Image>(); rfim.color = new Color(0,0,0,0); rfim.raycastTarget = true;
GameObject rtxt = new GameObject("Text"); rtxt.transform.SetParent(rf.transform, false);
RectTransform rxrt = rtxt.AddComponent<RectTransform>(); rxrt.anchorMin = Vector2.zero; rxrt.anchorMax = Vector2.one; rxrt.offsetMin = new Vector2(4,4); rxrt.offsetMax = new Vector2(-4,-4);
var rxtmp = rtxt.AddComponent<TextMeshProUGUI>(); rxtmp.fontSize = 44; rxtmp.color = White; rxtmp.alignment = TextAlignmentOptions.Center; rxtmp.raycastTarget = true; if (font != null) rxtmp.font = font;
GameObject ul = new GameObject("Underline"); ul.transform.SetParent(rf.transform, false);
RectTransform urt = ul.AddComponent<RectTransform>(); urt.anchorMin = new Vector2(0.05f,0f); urt.anchorMax = new Vector2(0.95f,0f); urt.pivot = new Vector2(.5f,0f); urt.sizeDelta = new Vector2(0,4); urt.anchoredPosition = Vector2.zero;
ul.AddComponent<Image>().color = new Color(1f,1f,1f,0.85f);
var rfld = rf.AddComponent<TMP_InputField>(); rfld.textViewport = rxrt; rfld.textComponent = rxtmp; rfld.contentType = TMP_InputField.ContentType.IntegerNumber; rfld.characterLimit = 3; rf.AddComponent<FieldKeyboard>();
}
else
{
GameObject ctxt = new GameObject("Box_Text"); ctxt.transform.SetParent(cbody.transform, false);
RectTransform cxrt = ctxt.AddComponent<RectTransform>(); cxrt.anchorMin = Vector2.zero; cxrt.anchorMax = Vector2.one; cxrt.offsetMin = new Vector2(30,140); cxrt.offsetMax = new Vector2(-30,-120);
var cxtmp = ctxt.AddComponent<TextMeshProUGUI>(); cxtmp.fontSize = 40; cxtmp.color = White; cxtmp.alignment = TextAlignmentOptions.Center; if (font != null) cxtmp.font = font; ctxt.AddComponent<ArabicText>().SetText(DutyScheduler.NoUndo());
}
GameObject cok = new GameObject("Btn_Confirm"); cok.transform.SetParent(cframe.transform, false);
RectTransform cokrt = cok.AddComponent<RectTransform>(); cokrt.anchorMin = new Vector2(1,0); cokrt.anchorMax = new Vector2(1,0); cokrt.pivot = new Vector2(.5f,.5f); cokrt.sizeDelta = new Vector2(110,110); cokrt.anchoredPosition = new Vector2(-150,70);
Image cokim = cok.AddComponent<Image>(); cokim.sprite = check; cokim.preserveAspect = true; cokim.color = Color.white;
GameObject cno = new GameObject("Btn_Cancel"); cno.transform.SetParent(cframe.transform, false);
RectTransform cnort = cno.AddComponent<RectTransform>(); cnort.anchorMin = new Vector2(0,0); cnort.anchorMax = new Vector2(0,0); cnort.pivot = new Vector2(.5f,.5f); cnort.sizeDelta = new Vector2(110,110); cnort.anchoredPosition = new Vector2(150,70);
Image cnoim = cno.AddComponent<Image>(); cnoim.sprite = close; cnoim.preserveAspect = true; cnoim.color = Color.white;
cr.SetActive(false); return cr;
}
public void EnsureRename() { if (rename == null && home != null) rename = RenamePanel.Build(transform, home.transform, font, home.wizardWindow, home.wizardCheck, home.wizardClose, s => { if (s == null) return; var list = ProfileStore.Load(); if (index >= 0 && index < list.Count && list[index] != null) { list[index].name = s; ProfileStore.Save(list); home.Rebuild(); Refresh(); } }); }
public void Hide() => gameObject.SetActive(false);
public void Show(int i) { index = i; lastFire = AlarmSystem.fireCount; lastRev = AlarmSystem.revision; Refresh(); transform.SetAsLastSibling(); gameObject.SetActive(true); }
public void Refresh()
{
try
{
var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) { Hide(); return; }
Profile p = list[index]; if (p == null) { Hide(); return; }
if (p.alarms == null) p.alarms = new List<DutyAlarm>(); if (p.breakTicks == null) p.breakTicks = new List<long>();
if (title != null) { var at = title.GetComponent<ArabicText>(); if (at != null) at.SetText(p.name ?? ""); var ttmp = title.GetComponent<TextMeshProUGUI>(); if (ttmp != null) { ttmp.ForceMeshUpdate(); float tw = ttmp.GetPreferredValues(ttmp.text).x; Transform pen = transform.Find("Alarm_Frame/Btn_Edit"); if (pen != null) pen.GetComponent<RectTransform>().anchoredPosition = new Vector2(-(140 + tw + 45), -62); } }
foreach (Transform c in content) Destroy(c.gameObject);
int n = p.alarms.Count; content.GetComponent<RectTransform>().sizeDelta = new Vector2(0, n == 0 ? 200 : n * CARD_H + 40);
if (n == 0) { GameObject e = new GameObject("Empty"); e.transform.SetParent(content, false); RectTransform ert = e.AddComponent<RectTransform>(); ert.anchorMin = new Vector2(0,1); ert.anchorMax = new Vector2(1,1); ert.offsetMin = new Vector2(40,-120); ert.offsetMax = new Vector2(-40,-40); var et = e.AddComponent<TextMeshProUGUI>(); et.fontSize = 44; et.color = DimText; et.alignment = TextAlignmentOptions.Center; if (font != null) et.font = font; e.AddComponent<ArabicText>().SetText(DutyScheduler.EmptyAlarms()); return; }
long nowTicks = DateTime.Now.Ticks;
for (int j = 0; j < n; j++)
{
DutyAlarm a = p.alarms[j]; if (a == null) continue; int slot = j; bool effective = p.alarmsActive && a.active;
GameObject card = new GameObject("Alarm_" + j); card.transform.SetParent(content, false);
RectTransform crt = card.AddComponent<RectTransform>(); crt.anchorMin = new Vector2(0,1); crt.anchorMax = new Vector2(1,1); crt.pivot = new Vector2(.5f,1); crt.offsetMin = new Vector2(30,-(slot+1)*CARD_H+40); crt.offsetMax = new Vector2(-30,-slot*CARD_H-40);
Image cim = card.AddComponent<Image>(); cim.sprite = cardFrame != null ? cardFrame : win; cim.type = Image.Type.Sliced; cim.color = Color.white;
GameObject sw = new GameObject("Btn_Stop"); sw.transform.SetParent(card.transform, false); RectTransform srt = sw.AddComponent<RectTransform>(); srt.anchorMin = new Vector2(0,1); srt.anchorMax = new Vector2(0,1); srt.pivot = new Vector2(.5f,.5f); srt.sizeDelta = new Vector2(150,70); srt.anchoredPosition = new Vector2(112,-78);
Image sim = sw.AddComponent<Image>(); sim.sprite = a.active ? swOn : (swOff != null ? swOff : swOn); sim.color = Color.white; sim.preserveAspect = true; Btn(sw, () => ToggleStop(slot));
GameObject tr = new GameObject("Btn_Trash"); tr.transform.SetParent(card.transform, false); RectTransform trt2 = tr.AddComponent<RectTransform>(); trt2.anchorMin = new Vector2(0,0); trt2.anchorMax = new Vector2(0,0); trt2.pivot = new Vector2(.5f,.5f); trt2.sizeDelta = new Vector2(58,58); trt2.anchoredPosition = new Vector2(92,82);
Image tim = tr.AddComponent<Image>(); tim.sprite = trashSpr; tim.color = new Color(190f/255f,65f/255f,58f,1f); tim.preserveAspect = true; Btn(tr, () => AskRemove(slot));
DateTime d = new DateTime(a.ticks); string main = DutyScheduler.GetArabicDayName(d.DayOfWeek) + " • " + d.ToString("yyyy/MM/dd") + " • " + DutyScheduler.Time12(d) + (a.isLast ? " • " + DutyScheduler.LastTag() : "");
GameObject mt = new GameObject("Main"); mt.transform.SetParent(card.transform, false); RectTransform mrt = mt.AddComponent<RectTransform>(); mrt.anchorMin = Vector2.zero; mrt.anchorMax = Vector2.one; mrt.offsetMin = new Vector2(200,118); mrt.offsetMax = new Vector2(-40,-52);
var mtmp = mt.AddComponent<TextMeshProUGUI>(); mtmp.fontSize = 37; mtmp.color = effective ? White : DimText; mtmp.alignment = TextAlignmentOptions.Center; if (font != null) mtmp.font = font; mt.AddComponent<ArabicText>().SetText(main);
GameObject rt3 = new GameObject("Rem"); rt3.transform.SetParent(card.transform, false); RectTransform rrt3 = rt3.AddComponent<RectTransform>(); rrt3.anchorMin = new Vector2(.5f,0); rrt3.anchorMax = new Vector2(.5f,0); rrt3.pivot = new Vector2(.5f,.5f); rrt3.sizeDelta = new Vector2(520,55); rrt3.anchoredPosition = new Vector2(55,48);
var rtmp = rt3.AddComponent<TextMeshProUGUI>(); rtmp.fontSize = 31; rtmp.color = effective ? Gold : DimText; rtmp.alignment = TextAlignmentOptions.Center; if (font != null) rtmp.font = font;
if (a.snoozedTicks > nowTicks)
{
rtmp.raycastTarget = false;
rt3.AddComponent<ArabicText>().SetText(DutyScheduler.SnoozeInfoPrefix() + DutyScheduler.Time12(new DateTime(a.snoozedTicks)));
}
else
{
int mins = a.reminderTicks != 0 ? (int)Math.Round((d.Ticks - a.reminderTicks) / (double)TimeSpan.TicksPerMinute) : 0;
rtmp.raycastTarget = true;
rt3.AddComponent<ArabicText>().SetText(mins <= 0 ? DutyScheduler.RemText(0) + " !" : DutyScheduler.RemText(mins));
int mSlot = slot, mMins = mins;
Btn(rt3, () => { remSlot = mSlot; if (remInput != null) remInput.text = mMins > 0 ? mMins.ToString() : ""; if (remRoot != null) remRoot.SetActive(true); });
}
}
}
catch (Exception e) { Debug.LogError("AlarmWindow.Refresh: " + e); }
}
public void ApplyReminder()
{
if (remRoot != null) remRoot.SetActive(false); if (remInput == null) return; int mins; if (!int.TryParse(remInput.text, out mins) || mins < 0) return;
var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || p.alarms == null) return; if (remSlot < 0 || remSlot >= p.alarms.Count) return;
int cap = p.restHours * 60; if (mins > 0 && (cap <= 0 || mins >= cap)) { Toast.Show(home != null ? home.transform : transform, DutyScheduler.RemTooLong(), font); return; }
DateTime duty = new DateTime(p.alarms[remSlot].ticks);
long rt = mins > 0 ? duty.AddMinutes(-mins).Ticks : 0;
if (rt != 0 && rt <= DateTime.Now.Ticks) rt = 0; // RULE at edit: a past reminder becomes no reminder (visible flip to "بدون تذكير !")
p.alarms[remSlot].reminderLocked = false; p.alarms[remSlot].reminderTicks = rt; // snoozedTicks left untouched (orthogonal)
ProfileStore.Save(list); home.Rebuild(); Refresh();
}
public void ToggleStop(int slot) { var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || p.alarms == null) return; if (slot < 0 || slot >= p.alarms.Count) return; p.alarms[slot].active = !p.alarms[slot].active; ProfileStore.Save(list); home.Rebuild(); Refresh(); }
public void AskRemove(int slot) { pendingSlot = slot; if (confirmRoot != null) confirmRoot.SetActive(true); }
public void HideConfirm() { pendingSlot = -1; if (confirmRoot != null) confirmRoot.SetActive(false); }
public void DoRemove() { if (pendingSlot >= 0) Remove(pendingSlot); HideConfirm(); }
void Remove(int slot) { var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || p.alarms == null) return; if (slot < 0 || slot >= p.alarms.Count) return; p.alarms.RemoveAt(slot); ProfileStore.Save(list); home.Rebuild(); Refresh(); }
}
// END-WAJIBY-WINDOW-V38_COMMANDUI
47textScripts/AlarmWindow.cs.bak
// CANARY: WAJIBY_WINDOW_V30_ROWS
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class AlarmWindow : MonoBehaviour
{
HomeManager home; int index = -1, pendingSlot = -1, remSlot = -1, lastFire = -1, lastRev = -1;
TMP_FontAsset font; Sprite win, cardFrame, swOn, swOff, trashSpr; Transform content, title;
GameObject confirmRoot, remRoot; TMP_InputField remInput; RenamePanel rename; const float CARD_H = 420f;
static readonly Color White = new Color(242f/255f,242f/255f,236f/255f,1f);
static readonly Color Black = new Color(0.05f,0.05f,0.05f,1f);
static readonly Color Gold = new Color(201f/255f,162f/255f,39f/255f,1f);
static readonly Color BodyGray = new Color(0.22f,0.24f,0.22f,1f);
static readonly Color DimText = new Color(0.55f,0.55f,0.55f,1f);
void Update()
{
if (lastFire != AlarmSystem.fireCount) { lastFire = AlarmSystem.fireCount; if (remRoot != null && remRoot.activeSelf) remRoot.SetActive(false); if (confirmRoot != null && confirmRoot.activeSelf) confirmRoot.SetActive(false); }
// a stop/snooze tapped from the shade while this list is open must update it live
if (gameObject.activeInHierarchy && AlarmSystem.revision != lastRev) { lastRev = AlarmSystem.revision; Refresh(); }
}
public static AlarmWindow Build(Transform root, HomeManager hm, Sprite win, Sprite cardFrame, Sprite swOn, Sprite trash, TMP_FontAsset font, Sprite editSpr)
{
GameObject aroot = new GameObject("AlarmRoot"); aroot.transform.SetParent(root, false);
RectTransform art = aroot.AddComponent<RectTransform>(); art.anchorMin = Vector2.zero; art.anchorMax = Vector2.one; art.offsetMin = Vector2.zero; art.offsetMax = Vector2.zero;
var abg = aroot.AddComponent<Image>(); abg.color = new Color(10f/255f,14f/255f,11f/255f,235f/255f); abg.raycastTarget = true;
GameObject frame = new GameObject("Alarm_Frame"); frame.transform.SetParent(aroot.transform, false);
RectTransform frt = frame.AddComponent<RectTransform>(); frt.anchorMin = Vector2.zero; frt.anchorMax = Vector2.one; frt.offsetMin = Vector2.zero; frt.offsetMax = Vector2.zero;
Image fim = frame.AddComponent<Image>(); fim.sprite = win; fim.type = Image.Type.Sliced; fim.color = Color.white;
GameObject ttl = new GameObject("Alarm_Title"); ttl.transform.SetParent(frame.transform, false);
RectTransform trt = ttl.AddComponent<RectTransform>(); trt.anchorMin = new Vector2(0,1); trt.anchorMax = new Vector2(1,1); trt.offsetMin = new Vector2(140,-100); trt.offsetMax = new Vector2(-140,-25);
var ttmp = ttl.AddComponent<TextMeshProUGUI>(); ttmp.fontSize = 52; ttmp.color = Black; ttmp.alignment = TextAlignmentOptions.Right; if (font != null) ttmp.font = font; ttl.AddComponent<ArabicText>();
GameObject pen = new GameObject("Btn_Edit"); pen.transform.SetParent(frame.transform, false);
RectTransform prt = pen.AddComponent<RectTransform>(); prt.anchorMin = new Vector2(1,1); prt.anchorMax = new Vector2(1,1); prt.pivot = new Vector2(.5f,.5f); prt.sizeDelta = new Vector2(60,60); prt.anchoredPosition = new Vector2(-90,-62);
Image pim = pen.AddComponent<Image>(); pim.sprite = editSpr; pim.preserveAspect = true; pim.raycastTarget = true;
GameObject close = new GameObject("Btn_Close"); close.transform.SetParent(frame.transform, false);
RectTransform crt2 = close.AddComponent<RectTransform>(); crt2.anchorMin = new Vector2(0,1); crt2.anchorMax = new Vector2(0,1); crt2.pivot = new Vector2(.5f,.5f); crt2.sizeDelta = new Vector2(80,80); crt2.anchoredPosition = new Vector2(85,-65);
Image cim = close.AddComponent<Image>(); cim.sprite = hm != null ? hm.wizardClose : null; cim.color = Color.white; cim.preserveAspect = true; cim.raycastTarget = true;
GameObject body = new GameObject("Alarm_Body"); body.transform.SetParent(frame.transform, false);
RectTransform brt = body.AddComponent<RectTransform>(); brt.anchorMin = Vector2.zero; brt.anchorMax = Vector2.one; brt.offsetMin = new Vector2(35,35); brt.offsetMax = new Vector2(-35,-110);
var bim = body.AddComponent<Image>(); bim.color = BodyGray; bim.raycastTarget = true;
var sr = body.AddComponent<ScrollRect>(); sr.horizontal = false; sr.vertical = true; sr.movementType = ScrollRect.MovementType.Elastic;
GameObject cnt = new GameObject("Alarm_Content"); cnt.transform.SetParent(body.transform, false);
RectTransform cnrt = cnt.AddComponent<RectTransform>(); cnrt.anchorMin = new Vector2(0,1); cnrt.anchorMax = new Vector2(1,1); cnrt.pivot = new Vector2(.5f,1); cnrt.anchoredPosition = Vector2.zero; sr.content = cnrt;
body.AddComponent<Mask>().showMaskGraphic = true;
GameObject cr = Box(aroot, "AlarmConfirm", DutyScheduler.AskDeleteTitle(), win, font, hm != null ? hm.wizardCheck : null, hm != null ? hm.wizardClose : null, false);
GameObject rr = Box(aroot, "RemEdit", DutyScheduler.RemEditTitle(), win, font, hm != null ? hm.wizardCheck : null, hm != null ? hm.wizardClose : null, true);
var aw = aroot.AddComponent<AlarmWindow>(); aw.home = hm; aw.font = font; aw.win = win; aw.cardFrame = cardFrame != null ? cardFrame : win; aw.swOn = swOn; aw.swOff = hm != null ? hm.switchOff : null; aw.trashSpr = trash; aw.content = cnt.transform; aw.title = ttl.transform; aw.confirmRoot = cr; aw.remRoot = rr; aw.remInput = rr.GetComponentInChildren<TMP_InputField>(); aw.lastFire = AlarmSystem.fireCount; aw.lastRev = AlarmSystem.revision;
Btn(close, aw.Hide); Btn(cr.transform.Find("Box_Frame/Btn_Confirm").gameObject, aw.DoRemove); Btn(cr.transform.Find("Box_Frame/Btn_Cancel").gameObject, aw.HideConfirm); Btn(rr.transform.Find("Box_Frame/Btn_Confirm").gameObject, aw.ApplyReminder); Btn(rr.transform.Find("Box_Frame/Btn_Cancel").gameObject, () => aw.remRoot.SetActive(false));
Btn(pen, () => { aw.EnsureRename(); if (aw.rename != null) { var list = ProfileStore.Load(); string cur = (aw.index >= 0 && aw.index < list.Count && list[aw.index] != null) ? list[aw.index].name : ""; aw.rename.Show(cur); } });
aroot.SetActive(false); return aw;
}
static void Btn(GameObject go, UnityEngine.Events.UnityAction a) { Button old = go.GetComponent<Button>(); if (old != null) UnityEngine.Object.Destroy(old); SafeTap st = go.GetComponent<SafeTap>(); if (st == null) st = go.AddComponent<SafeTap>(); st.onTap = a; }
static GameObject Box(GameObject parent, string name, string titleText, Sprite win, TMP_FontAsset font, Sprite check, Sprite close, bool withField)
{
GameObject cr = new GameObject(name); cr.transform.SetParent(parent.transform, false);
RectTransform crrt = cr.AddComponent<RectTransform>(); crrt.anchorMin = Vector2.zero; crrt.anchorMax = Vector2.one; crrt.offsetMin = Vector2.zero; crrt.offsetMax = Vector2.zero;
var crbg = cr.AddComponent<Image>(); crbg.color = new Color(10f/255f,14f/255f,11f/255f,220f/255f); crbg.raycastTarget = true;
GameObject cframe = new GameObject("Box_Frame"); cframe.transform.SetParent(cr.transform, false);
RectTransform cfrt = cframe.AddComponent<RectTransform>(); cfrt.anchorMin = new Vector2(.5f,1); cfrt.anchorMax = new Vector2(.5f,1); cfrt.pivot = new Vector2(.5f,1); cfrt.sizeDelta = new Vector2(700,450); cfrt.anchoredPosition = new Vector2(0,-200);
Image cfim = cframe.AddComponent<Image>(); cfim.sprite = win; cfim.type = Image.Type.Sliced; cfim.color = Color.white;
GameObject cttl = new GameObject("Box_Title"); cttl.transform.SetParent(cframe.transform, false);
RectTransform ctrt = cttl.AddComponent<RectTransform>(); ctrt.anchorMin = new Vector2(0,1); ctrt.anchorMax = new Vector2(1,1); ctrt.offsetMin = new Vector2(75,-100); ctrt.offsetMax = new Vector2(-75,-25);
var cttmp = cttl.AddComponent<TextMeshProUGUI>(); cttmp.fontSize = 44; cttmp.color = Black; cttmp.alignment = TextAlignmentOptions.Right; if (font != null) cttmp.font = font; cttl.AddComponent<ArabicText>().SetText(titleText);
GameObject cbody = new GameObject("Box_Body"); cbody.transform.SetParent(cframe.transform, false);
RectTransform cbrt = cbody.AddComponent<RectTransform>(); cbrt.anchorMin = Vector2.zero; cbrt.anchorMax = Vector2.one; cbrt.offsetMin = new Vector2(30,30); cbrt.offsetMax = new Vector2(-30,-110);
var cbim = cbody.AddComponent<Image>(); cbim.color = new Color(0.30f,0.32f,0.30f,1f); cbim.raycastTarget = true;
if (withField)
{
GameObject rf = new GameObject("RemField"); rf.transform.SetParent(cbody.transform, false);
RectTransform rfrt = rf.AddComponent<RectTransform>(); rfrt.anchorMin = new Vector2(.5f,.5f); rfrt.anchorMax = new Vector2(.5f,.5f); rfrt.pivot = new Vector2(.5f,.5f); rfrt.sizeDelta = new Vector2(300,100); rfrt.anchoredPosition = new Vector2(0,60);
var rfim = rf.AddComponent<Image>(); rfim.color = new Color(0,0,0,0); rfim.raycastTarget = true;
GameObject rtxt = new GameObject("Text"); rtxt.transform.SetParent(rf.transform, false);
RectTransform rxrt = rtxt.AddComponent<RectTransform>(); rxrt.anchorMin = Vector2.zero; rxrt.anchorMax = Vector2.one; rxrt.offsetMin = new Vector2(4,4); rxrt.offsetMax = new Vector2(-4,-4);
var rxtmp = rtxt.AddComponent<TextMeshProUGUI>(); rxtmp.fontSize = 44; rxtmp.color = White; rxtmp.alignment = TextAlignmentOptions.Center; rxtmp.raycastTarget = true; if (font != null) rxtmp.font = font;
GameObject ul = new GameObject("Underline"); ul.transform.SetParent(rf.transform, false);
RectTransform urt = ul.AddComponent<RectTransform>(); urt.anchorMin = new Vector2(0.05f,0f); urt.anchorMax = new Vector2(0.95f,0f); urt.pivot = new Vector2(.5f,0f); urt.sizeDelta = new Vector2(0,4); urt.anchoredPosition = Vector2.zero;
ul.AddComponent<Image>().color = new Color(1f,1f,1f,0.85f);
var rfld = rf.AddComponent<TMP_InputField>(); rfld.textViewport = rxrt; rfld.textComponent = rxtmp; rfld.contentType = TMP_InputField.ContentType.IntegerNumber; rfld.characterLimit = 3; rf.AddComponent<FieldKeyboard>();
}
else
{
GameObject ctxt = new GameObject("Box_Text"); ctxt.transform.SetParent(cbody.transform, false);
RectTransform cxrt = ctxt.AddComponent<RectTransform>(); cxrt.anchorMin = Vector2.zero; cxrt.anchorMax = Vector2.one; cxrt.offsetMin = new Vector2(30,140); cxrt.offsetMax = new Vector2(-30,-120);
var cxtmp = ctxt.AddComponent<TextMeshProUGUI>(); cxtmp.fontSize = 40; cxtmp.color = White; cxtmp.alignment = TextAlignmentOptions.Center; if (font != null) cxtmp.font = font; ctxt.AddComponent<ArabicText>().SetText(DutyScheduler.NoUndo());
}
GameObject cok = new GameObject("Btn_Confirm"); cok.transform.SetParent(cframe.transform, false);
RectTransform cokrt = cok.AddComponent<RectTransform>(); cokrt.anchorMin = new Vector2(1,0); cokrt.anchorMax = new Vector2(1,0); cokrt.pivot = new Vector2(.5f,.5f); cokrt.sizeDelta = new Vector2(110,110); cokrt.anchoredPosition = new Vector2(-150,70);
Image cokim = cok.AddComponent<Image>(); cokim.sprite = check; cokim.preserveAspect = true; cokim.color = Color.white;
GameObject cno = new GameObject("Btn_Cancel"); cno.transform.SetParent(cframe.transform, false);
RectTransform cnort = cno.AddComponent<RectTransform>(); cnort.anchorMin = new Vector2(0,0); cnort.anchorMax = new Vector2(0,0); cnort.pivot = new Vector2(.5f,.5f); cnort.sizeDelta = new Vector2(110,110); cnort.anchoredPosition = new Vector2(150,70);
Image cnoim = cno.AddComponent<Image>(); cnoim.sprite = close; cnoim.preserveAspect = true; cnoim.color = Color.white;
cr.SetActive(false); return cr;
}
public void EnsureRename() { if (rename == null && home != null) rename = RenamePanel.Build(transform, home.transform, font, home.wizardWindow, home.wizardCheck, home.wizardClose, s => { if (s == null) return; var list = ProfileStore.Load(); if (index >= 0 && index < list.Count && list[index] != null) { list[index].name = s; ProfileStore.Save(list); home.Rebuild(); Refresh(); } }); }
public void Hide() => gameObject.SetActive(false);
public void Show(int i) { index = i; lastFire = AlarmSystem.fireCount; lastRev = AlarmSystem.revision; Refresh(); transform.SetAsLastSibling(); gameObject.SetActive(true); }
public void Refresh()
{
try
{
var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) { Hide(); return; }
Profile p = list[index]; if (p == null) { Hide(); return; }
if (p.alarms == null) p.alarms = new List<DutyAlarm>(); if (p.breakTicks == null) p.breakTicks = new List<long>();
if (title != null) { var at = title.GetComponent<ArabicText>(); if (at != null) at.SetText(p.name ?? ""); var ttmp = title.GetComponent<TextMeshProUGUI>(); if (ttmp != null) { ttmp.ForceMeshUpdate(); float tw = ttmp.GetPreferredValues(ttmp.text).x; Transform pen = transform.Find("Alarm_Frame/Btn_Edit"); if (pen != null) pen.GetComponent<RectTransform>().anchoredPosition = new Vector2(-(140 + tw + 45), -62); } }
foreach (Transform c in content) Destroy(c.gameObject);
int n = p.alarms.Count; content.GetComponent<RectTransform>().sizeDelta = new Vector2(0, n == 0 ? 200 : n * CARD_H + 40);
if (n == 0) { GameObject e = new GameObject("Empty"); e.transform.SetParent(content, false); RectTransform ert = e.AddComponent<RectTransform>(); ert.anchorMin = new Vector2(0,1); ert.anchorMax = new Vector2(1,1); ert.offsetMin = new Vector2(40,-120); ert.offsetMax = new Vector2(-40,-40); var et = e.AddComponent<TextMeshProUGUI>(); et.fontSize = 44; et.color = DimText; et.alignment = TextAlignmentOptions.Center; if (font != null) et.font = font; e.AddComponent<ArabicText>().SetText(DutyScheduler.EmptyAlarms()); return; }
long nowTicks = DateTime.Now.Ticks;
for (int j = 0; j < n; j++)
{
DutyAlarm a = p.alarms[j]; if (a == null) continue; int slot = j; bool effective = p.alarmsActive && a.active;
GameObject card = new GameObject("Alarm_" + j); card.transform.SetParent(content, false);
RectTransform crt = card.AddComponent<RectTransform>(); crt.anchorMin = new Vector2(0,1); crt.anchorMax = new Vector2(1,1); crt.pivot = new Vector2(.5f,1); crt.offsetMin = new Vector2(30,-(slot+1)*CARD_H+40); crt.offsetMax = new Vector2(-30,-slot*CARD_H-40);
Image cim = card.AddComponent<Image>(); cim.sprite = cardFrame != null ? cardFrame : win; cim.type = Image.Type.Sliced; cim.color = Color.white;
GameObject sw = new GameObject("Btn_Stop"); sw.transform.SetParent(card.transform, false); RectTransform srt = sw.AddComponent<RectTransform>(); srt.anchorMin = new Vector2(0,1); srt.anchorMax = new Vector2(0,1); srt.pivot = new Vector2(.5f,.5f); srt.sizeDelta = new Vector2(140,70); srt.anchoredPosition = new Vector2(110,-95);
Image sim = sw.AddComponent<Image>(); sim.sprite = a.active ? swOn : (swOff != null ? swOff : swOn); sim.preserveAspect = true; Btn(sw, () => ToggleStop(slot));
GameObject tr = new GameObject("Btn_Trash"); tr.transform.SetParent(card.transform, false); RectTransform trt2 = tr.AddComponent<RectTransform>(); trt2.anchorMin = new Vector2(0,0); trt2.anchorMax = new Vector2(0,0); trt2.pivot = new Vector2(.5f,.5f); trt2.sizeDelta = new Vector2(60,60); trt2.anchoredPosition = new Vector2(95,95);
Image tim = tr.AddComponent<Image>(); tim.sprite = trashSpr; tim.preserveAspect = true; Btn(tr, () => AskRemove(slot));
DateTime d = new DateTime(a.ticks); string main = DutyScheduler.GetArabicDayName(d.DayOfWeek) + " • " + d.ToString("yyyy/MM/dd") + " • " + DutyScheduler.Time12(d) + (a.isLast ? " • " + DutyScheduler.LastTag() : "");
GameObject mt = new GameObject("Main"); mt.transform.SetParent(card.transform, false); RectTransform mrt = mt.AddComponent<RectTransform>(); mrt.anchorMin = Vector2.zero; mrt.anchorMax = Vector2.one; mrt.offsetMin = new Vector2(200,150); mrt.offsetMax = new Vector2(-40,-60);
var mtmp = mt.AddComponent<TextMeshProUGUI>(); mtmp.fontSize = 40; mtmp.color = effective ? White : DimText; mtmp.alignment = TextAlignmentOptions.Center; if (font != null) mtmp.font = font; mt.AddComponent<ArabicText>().SetText(main);
if (!(a.reminderLocked && a.snoozedTicks <= 0))
{
GameObject rt3 = new GameObject("Rem"); rt3.transform.SetParent(card.transform, false); RectTransform rrt3 = rt3.AddComponent<RectTransform>(); rrt3.anchorMin = new Vector2(.5f,0); rrt3.anchorMax = new Vector2(.5f,0); rrt3.pivot = new Vector2(.5f,.5f); rrt3.sizeDelta = new Vector2(520,60); rrt3.anchoredPosition = new Vector2(60,70);
var rtmp = rt3.AddComponent<TextMeshProUGUI>(); rtmp.fontSize = 32; rtmp.color = effective ? Gold : DimText; rtmp.alignment = TextAlignmentOptions.Center; if (font != null) rtmp.font = font;
if (a.snoozedTicks > nowTicks) { rtmp.raycastTarget = false; rt3.AddComponent<ArabicText>().SetText(DutyScheduler.SnoozeInfoPrefix() + DutyScheduler.Time12(new DateTime(a.snoozedTicks))); }
else { int mins = a.reminderTicks != 0 ? (int)Math.Round((d.Ticks - a.reminderTicks) / (double)TimeSpan.TicksPerMinute) : 0; rtmp.raycastTarget = true; rt3.AddComponent<ArabicText>().SetText(DutyScheduler.RemText(mins)); Btn(rt3, () => { remSlot = slot; if (remInput != null) remInput.text = mins > 0 ? mins.ToString() : ""; if (remRoot != null) remRoot.SetActive(true); }); }
}
}
}
catch (Exception e) { Debug.LogError("AlarmWindow.Refresh: " + e); }
}
public void ApplyReminder()
{
if (remRoot != null) remRoot.SetActive(false); if (remInput == null) return; int mins; if (!int.TryParse(remInput.text, out mins) || mins < 0) return;
var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || p.alarms == null) return; if (remSlot < 0 || remSlot >= p.alarms.Count) return;
int cap = p.restHours * 60; if (mins > 0 && (cap <= 0 || mins >= cap)) { Toast.Show(home != null ? home.transform : transform, DutyScheduler.RemTooLong(), font); return; }
DateTime duty = new DateTime(p.alarms[remSlot].ticks);
p.alarms[remSlot].reminderLocked = false; p.alarms[remSlot].snoozedTicks = 0; p.alarms[remSlot].reminderTicks = mins > 0 ? duty.AddMinutes(-mins).Ticks : 0;
ProfileStore.Save(list); home.Rebuild(); Refresh();
}
public void ToggleStop(int slot) { var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || p.alarms == null) return; if (slot < 0 || slot >= p.alarms.Count) return; p.alarms[slot].active = !p.alarms[slot].active; ProfileStore.Save(list); home.Rebuild(); Refresh(); }
public void AskRemove(int slot) { pendingSlot = slot; if (confirmRoot != null) confirmRoot.SetActive(true); }
public void HideConfirm() { pendingSlot = -1; if (confirmRoot != null) confirmRoot.SetActive(false); }
public void DoRemove() { if (pendingSlot >= 0) Remove(pendingSlot); HideConfirm(); }
void Remove(int slot) { var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || p.alarms == null) return; if (slot < 0 || slot >= p.alarms.Count) return; p.alarms.RemoveAt(slot); ProfileStore.Save(list); home.Rebuild(); Refresh(); }
}48textScripts/AlarmWindow.cs.bak.meta
fileFormatVersion: 2
guid: 83e20fea1092da14aabe8eff4c24b639
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
49textScripts/AlarmWindow.cs.meta
fileFormatVersion: 2 guid: 78c6545edcf3ee541b1f2554ef417530
50textScripts/ArabicShaper.cs
using System.Collections.Generic;
public static class ArabicShaper
{
static readonly HashSet<char> noNext = new HashSet<char>
{ 'ا','آ','أ','إ','د','ذ','ر','ز','و','ؤ','ء','ة' };
static readonly HashSet<char> punct = new HashSet<char> { '؟','،','؛' };
static bool IsArabic(char c) { return c >= '\u0600' && c <= '\u06FF'; }
static bool IsDiacritic(char c) { return (c >= '\u064B' && c <= '\u065F') || c == '\u0670'; }
static bool IsAlif(char c) { return c == 'ا' || c == 'آ' || c == 'أ' || c == 'إ'; }
static bool IsLtr(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| c == ':' || c == '.' || c == '/' || c == '-';
}
static char[] Forms(char c)
{
switch (c)
{
case 'ء': return new char[] { '\uFE80', '\0', '\0', '\0' };
case 'آ': return new char[] { '\uFE81', '\uFE82', '\0', '\0' };
case 'أ': return new char[] { '\uFE83', '\uFE84', '\0', '\0' };
case 'ؤ': return new char[] { '\uFE85', '\uFE86', '\0', '\0' };
case 'إ': return new char[] { '\uFE87', '\uFE88', '\0', '\0' };
case 'ا': return new char[] { '\uFE8D', '\uFE8E', '\0', '\0' };
case 'ب': return new char[] { '\uFE8F', '\uFE90', '\uFE91', '\uFE92' };
case 'ت': return new char[] { '\uFE95', '\uFE96', '\uFE97', '\uFE98' };
case 'ث': return new char[] { '\uFE99', '\uFE9A', '\uFE9B', '\uFE9C' };
case 'ج': return new char[] { '\uFE9D', '\uFE9E', '\uFE9F', '\uFEA0' };
case 'ح': return new char[] { '\uFEA1', '\uFEA2', '\uFEA3', '\uFEA4' };
case 'خ': return new char[] { '\uFEA5', '\uFEA6', '\uFEA7', '\uFEA8' };
case 'د': return new char[] { '\uFEA9', '\uFEAA', '\0', '\0' };
case 'ذ': return new char[] { '\uFEAB', '\uFEAC', '\0', '\0' };
case 'ر': return new char[] { '\uFEAD', '\uFEAE', '\0', '\0' };
case 'ز': return new char[] { '\uFEAF', '\uFEB0', '\0', '\0' };
case 'س': return new char[] { '\uFEB1', '\uFEB2', '\uFEB3', '\uFEB4' };
case 'ش': return new char[] { '\uFEB5', '\uFEB6', '\uFEB7', '\uFEB8' };
case 'ص': return new char[] { '\uFEB9', '\uFEBA', '\uFEBB', '\uFEBC' };
case 'ض': return new char[] { '\uFEBD', '\uFEBE', '\uFEBF', '\uFEC0' };
case 'ط': return new char[] { '\uFEC1', '\uFEC2', '\uFEC3', '\uFEC4' };
case 'ظ': return new char[] { '\uFEC5', '\uFEC6', '\uFEC7', '\uFEC8' };
case 'ع': return new char[] { '\uFEC9', '\uFECA', '\uFECB', '\uFECC' };
case 'غ': return new char[] { '\uFECD', '\uFECE', '\uFECF', '\uFED0' };
case 'ف': return new char[] { '\uFED1', '\uFED2', '\uFED3', '\uFED4' };
case 'ق': return new char[] { '\uFED5', '\uFED6', '\uFED7', '\uFED8' };
case 'ك': return new char[] { '\uFED9', '\uFEDA', '\uFEDB', '\uFEDC' };
case 'ل': return new char[] { '\uFEDD', '\uFEDE', '\uFEDF', '\uFEE0' };
case 'م': return new char[] { '\uFEE1', '\uFEE2', '\uFEE3', '\uFEE4' };
case 'ن': return new char[] { '\uFEE5', '\uFEE6', '\uFEE7', '\uFEE8' };
case 'ه': return new char[] { '\uFEE9', '\uFEEA', '\uFEEB', '\uFEEC' };
case 'و': return new char[] { '\uFEED', '\uFEEE', '\0', '\0' };
case 'ى': return new char[] { '\uFEEF', '\uFEF0', '\0', '\0' };
case 'ي': return new char[] { '\uFEF1', '\uFEF2', '\uFEF3', '\uFEF4' };
case 'ة': return new char[] { 'ة', 'ة', '\0', '\0' };
case 'ئ': return new char[] { '\uFE89', '\uFE8A', '\uFE8B', '\uFE8C' };
default: return null;
}
}
public static string Shape(string input)
{
if (string.IsNullOrEmpty(input)) return input;
System.Text.StringBuilder sb = new System.Text.StringBuilder(input.Length);
foreach (char c in input)
if (!IsDiacritic(c)) sb.Append(c);
char[] src = sb.ToString().ToCharArray();
List<char> outp = new List<char>(src.Length);
int i = 0;
while (i < src.Length)
{
char c = src[i];
int p = i - 1, n = i + 1;
char prev = p >= 0 ? src[p] : ' ';
char next = n < src.Length ? src[n] : ' ';
bool connPrev = IsArabic(prev) && !noNext.Contains(prev) && !punct.Contains(prev);
if (c == 'ل' && IsAlif(next))
{
switch (next)
{
case 'آ': outp.Add(connPrev ? '\uFEF6' : '\uFEF5'); break;
case 'أ': outp.Add(connPrev ? '\uFEF8' : '\uFEF7'); break;
case 'إ': outp.Add(connPrev ? '\uFEFA' : '\uFEF9'); break;
default: outp.Add(connPrev ? '\uFEFC' : '\uFEFB'); break;
}
i = n + 1;
continue;
}
char[] f = Forms(c);
if (f == null) { outp.Add(c); i++; continue; }
// FIX: previous letter MUST be allowed to connect into 'ة' (the old
// "next != 'ة'" clause broke e.g. م+ة). 'ة' itself still never joins
// forward because it is in noNext (checked via !noNext.Contains(c)).
bool connNext = IsArabic(next) && !noNext.Contains(c) && !punct.Contains(next);
if (connPrev && connNext && f[3] != '\0') outp.Add(f[3]);
else if (connPrev && f[1] != '\0') outp.Add(f[1]);
else if (connNext && f[2] != '\0') outp.Add(f[2]);
else outp.Add(f[0]);
i++;
}
char[] result = outp.ToArray();
System.Array.Reverse(result);
int k = 0;
while (k < result.Length)
{
if (IsLtr(result[k]))
{
int s = k;
while (k < result.Length && IsLtr(result[k])) k++;
System.Array.Reverse(result, s, k - s);
}
else k++;
}
return new string(result);
}
}51textScripts/ArabicShaper.cs.meta
fileFormatVersion: 2 guid: 75f0844414798ab439e0a0b40ab6cb2e
52textScripts/ArabicText.cs
using UnityEngine;
using TMPro;
[ExecuteAlways]
[RequireComponent(typeof(TextMeshProUGUI))]
public class ArabicText : MonoBehaviour
{
[TextArea] public string text = "";
void Awake() => Apply();
void OnValidate() => Apply();
public void SetText(string value) { text = value; Apply(); }
void Apply()
{
var tmp = GetComponent<TextMeshProUGUI>();
if (tmp == null || string.IsNullOrEmpty(text)) return;
var parts = text.Split('\n');
var sb = new System.Text.StringBuilder();
for (int i = 0; i < parts.Length; i++)
{
if (i > 0) sb.Append('\n');
sb.Append(ArabicShaper.Shape(parts[i])); // shape per-line → line order kept
}
tmp.text = sb.ToString();
}
}53textScripts/ArabicText.cs.meta
fileFormatVersion: 2 guid: ed7850e6244b26744af1baba5d5c65f2
54textScripts/CardRuntimeBuilder.cs
// CANARY: WAJIBY_CARDRT_V12_COMMANDUI
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public static class CardRuntimeBuilder
{
const float H = 440f, SIDE = 28f;
const float LX = 0.16f;
public const float RX = 0.79f;
public const float MIDY = 0.46f;
static readonly Color Gold = new Color(213f/255f, 178f/255f, 74f/255f, 1f);
static readonly Color DeepGreen = new Color(45f/255f, 82f/255f, 56f/255f, 1f);
static readonly Color TextDark = new Color(244f/255f, 240f/255f, 228f/255f, 1f);
static readonly Color InfoGray = new Color(169f/255f, 182f/255f, 170f/255f, 1f);
static readonly Color Surface = new Color(31f/255f, 48f/255f, 38f/255f, 1f);
static readonly Color Surface2 = new Color(42f/255f, 61f/255f, 48f/255f, 1f);
static readonly Color OffGray = new Color(82f/255f, 94f/255f, 85f/255f, 1f);
static readonly Color Green = new Color(82f/255f, 145f/255f, 91f/255f, 1f);
static readonly Color Clear = new Color(0f,0f,0f,0f);
static Sprite _panelF, _borderF, _idGrey, _switchOnF, _switchOffF;
static int _pw, _ph;
public static Sprite Res(string n) { return Resources.Load<Sprite>("Icons/" + n); }
public static Sprite TrashGlyph
{
get { return Res("trash") ?? Glyph(DrawTrash); }
}
public static Sprite IdGrey
{
get { if (_idGrey == null) _idGrey = Glyph(DrawIdGrey); return _idGrey; }
}
public static Sprite BrightSwitchOn
{
get { if (_switchOnF == null) _switchOnF = ToggleSprite(true); return _switchOnF; }
}
public static Sprite BrightSwitchOff
{
get { if (_switchOffF == null) _switchOffF = ToggleSprite(false); return _switchOffF; }
}
static TMP_FontAsset Font()
{
foreach (var f in Resources.FindObjectsOfTypeAll<TMP_FontAsset>())
{
if (f == null || f.characterTable == null) continue;
foreach (var c in f.characterTable)
if (c != null && c.unicode == 0x062D) return f;
}
var t = Object.FindAnyObjectByType<TextMeshProUGUI>();
if (t != null && t.font != null) return t.font;
return TMP_Settings.defaultFontAsset;
}
public static GameObject Build(RectTransform parent, TMP_FontAsset fontIn, Sprite panel, Sprite id, Sprite pen,
Sprite scroll, Sprite trash, Sprite swOn, Sprite swOff)
{
TMP_FontAsset font = fontIn != null ? fontIn : Font();
float w = (parent != null && parent.rect.width > 200f) ? parent.rect.width - SIDE * 2f : 940f;
int tw = Mathf.Max(64, Mathf.RoundToInt(w));
int th = Mathf.Max(64, Mathf.RoundToInt(H));
if (_panelF == null || _pw != tw || _ph != th)
{
_panelF = RoundedSprite(Surface, new Color(Gold.r,Gold.g,Gold.b,.65f), 4f);
_borderF = RoundedSprite(Clear, new Color(Gold.r,Gold.g,Gold.b,.78f), 4f);
_pw = tw; _ph = th;
}
id = id ?? Res("id-card");
pen = pen ?? Res("edit") ?? Res("edit_white");
scroll = scroll ?? Res("scroll");
trash = trash ?? TrashGlyph;
swOn = BrightSwitchOn;
swOff = BrightSwitchOff;
GameObject card = new GameObject("Card");
card.transform.SetParent(parent, false);
RectTransform cr = card.AddComponent<RectTransform>();
cr.anchorMin = new Vector2(.5f,1f); cr.anchorMax = new Vector2(.5f,1f);
cr.pivot = new Vector2(.5f,1f); cr.sizeDelta = new Vector2(w,H);
Image hit = card.AddComponent<Image>();
hit.color = Clear; hit.raycastTarget = true;
RectTransform frt = AddRect(card.transform, "Card_Frame", 0,0,1,1,0,0,0,0);
Image fim = frt.gameObject.AddComponent<Image>();
fim.sprite = _panelF; fim.type = Image.Type.Sliced; fim.color = Color.white; fim.raycastTarget = false;
Shadow sh = frt.gameObject.AddComponent<Shadow>();
sh.effectColor = new Color(0f,0f,0f,.13f); sh.effectDistance = new Vector2(0f,-6f); sh.useGraphicAlpha = true;
RectTransform brt = AddRect(card.transform, "Card_Border", 0,0,1,1,0,0,0,0);
Image bim = brt.gameObject.AddComponent<Image>();
bim.sprite = _borderF; bim.type = Image.Type.Sliced; bim.color = Color.white; bim.raycastTarget = false;
RectTransform cap = AddRect(card.transform, "Card_TopAccent", 0,1,1,1,30,-8,-30,-2);
Image capIm = cap.gameObject.AddComponent<Image>(); capIm.color = Gold; capIm.raycastTarget = false;
RectTransform rail = AddRect(card.transform, "Card_GoldRail", 1,0,1,1,-7,24,0,-24);
Image railIm = rail.gameObject.AddComponent<Image>(); railIm.color = new Color(Gold.r,Gold.g,Gold.b,.26f); railIm.raycastTarget = false;
// Right information area: emblem, name, and schedule summary.
Icon(card.transform, "Icon_ID", .89f, .79f, 0,0,110,86, id, false, new Color(214f/255f,169f/255f,58f/255f,1f));
RectTransform nrt = AddRect(card.transform, "NameText", .30f, .58f, .80f, .91f, 0,-6,0,0);
Tmp(nrt, font, 44f, TextDark, TextAlignmentOptions.MidlineRight, false);
nrt.gameObject.AddComponent<ArabicText>();
RectTransform irt = AddRect(card.transform, "InfoText", .26f, .16f, .79f, .52f, 0,0,0,0);
Tmp(irt, font, 29f, InfoGray, TextAlignmentOptions.TopRight, false);
irt.gameObject.AddComponent<ArabicText>();
// Subtle divider between information and action strip.
RectTransform divider = AddRect(card.transform, "Card_Divider", .24f, .13f, .94f, .13f, 0,0,0,0);
divider.sizeDelta = new Vector2(0,2);
Image dim = divider.gameObject.AddComponent<Image>(); dim.color = new Color(Gold.r,Gold.g,Gold.b,.22f); dim.raycastTarget=false;
// Clean action rail: active state at the top, utility actions at the bottom.
IconButton(card.transform, "Btn_Switch", 0,1, 98,-70, 142,66, swOn, true);
GameObject scrollGo = IconButton(card.transform, "Btn_Scroll", 0,0, 92,78, 58,58, scroll, true);
if (scrollGo.GetComponent<Image>() != null) scrollGo.GetComponent<Image>().color = DeepGreen;
GameObject trashGo = IconButton(card.transform, "Btn_Trash", 0,0, 166,78, 58,58, trash, true);
if (trashGo.GetComponent<Image>() != null) trashGo.GetComponent<Image>().color = new Color(190f/255f,65f/255f,58f/255f,1f);
IconButton(card.transform, "Btn_Edit", .79f, .73f, -12,0, 56,56, pen, true, true);
return card;
}
static RectTransform AddRect(Transform p, string n, float ax0, float ay0, float ax1, float ay1,
float ox0, float oy0, float ox1, float oy1)
{
GameObject g = new GameObject(n); g.transform.SetParent(p,false);
RectTransform r = g.AddComponent<RectTransform>();
r.anchorMin = new Vector2(ax0,ay0); r.anchorMax = new Vector2(ax1,ay1);
r.pivot = new Vector2(.5f,.5f); r.offsetMin = new Vector2(ox0,oy0); r.offsetMax = new Vector2(ox1,oy1);
return r;
}
static void Icon(Transform p,string n,float ax,float ay,float px,float py,float w,float h,Sprite s,bool ray,Color tint)
{
GameObject g=new GameObject(n); g.transform.SetParent(p,false);
RectTransform r=g.AddComponent<RectTransform>(); r.anchorMin=new Vector2(ax,ay); r.anchorMax=new Vector2(ax,ay);
r.pivot=new Vector2(.5f,.5f); r.sizeDelta=new Vector2(w,h); r.anchoredPosition=new Vector2(px,py);
Image im=g.AddComponent<Image>(); im.sprite=s; im.color=tint; im.preserveAspect=true; im.raycastTarget=ray;
}
static GameObject IconButton(Transform p,string n,float ax,float ay,float px,float py,float w,float h,Sprite s,bool ray,bool darkIcon=false)
{
GameObject g=new GameObject(n); g.transform.SetParent(p,false);
RectTransform r=g.AddComponent<RectTransform>(); r.anchorMin=new Vector2(ax,ay); r.anchorMax=new Vector2(ax,ay);
r.pivot=new Vector2(.5f,.5f); r.sizeDelta=new Vector2(w,h); r.anchoredPosition=new Vector2(px,py);
Image im=g.AddComponent<Image>(); im.sprite=s; im.color=darkIcon ? DeepGreen : Color.white; im.preserveAspect=true; im.raycastTarget=ray;
if(ray){ Button b=g.AddComponent<Button>(); b.transition=Selectable.Transition.None; b.targetGraphic=im; }
return g;
}
static void Tmp(RectTransform r,TMP_FontAsset f,float size,Color c,TextAlignmentOptions a,bool ray)
{
TextMeshProUGUI t=r.gameObject.AddComponent<TextMeshProUGUI>();
t.font=f; t.fontSize=size; t.color=c; t.alignment=a; t.raycastTarget=ray; t.textWrappingMode=TextWrappingModes.NoWrap;
}
static Sprite ToggleSprite(bool on)
{
const int W=176,Hh=78; const float R=36f;
Texture2D tex=new Texture2D(W,Hh,TextureFormat.RGBA32,false); tex.filterMode=FilterMode.Bilinear;
Color bg=on?Green:OffGray; Color knob=Color.white; Vector2 c=new Vector2(W/2f,Hh/2f);
for(int y=0;y<Hh;y++) for(int x=0;x<W;x++)
{
Vector2 p=new Vector2(x+.5f,y+.5f);
if(!Rounded(p,c,new Vector2(W/2f-2f,Hh/2f-2f),R)){tex.SetPixel(x,y,Clear);continue;}
Vector2 kc=on?new Vector2(W-39,Hh/2f):new Vector2(39,Hh/2f);
tex.SetPixel(x,y,Vector2.Distance(p,kc)<=25f?knob:bg);
}
tex.Apply(); return Sprite.Create(tex,new Rect(0,0,W,Hh),new Vector2(.5f,.5f),100f);
}
static Sprite RoundedSprite(Color fill,Color edge,float edgeWidth)
{
const int N=128; Texture2D tex=new Texture2D(N,N,TextureFormat.RGBA32,false); tex.filterMode=FilterMode.Bilinear;
Vector2 c=new Vector2(N/2f,N/2f), h=new Vector2(N/2f-1,N/2f-1);
for(int y=0;y<N;y++) for(int x=0;x<N;x++)
{
Vector2 p=new Vector2(x+.5f,y+.5f); float d=RoundedDistance(p,c,h,26f);
Color col=d<=0?fill:Clear;
if(edgeWidth>0 && d<=0 && RoundedDistance(p,c,new Vector2(h.x-edgeWidth,h.y-edgeWidth),Mathf.Max(4f,26f-edgeWidth))>0) col=edge;
tex.SetPixel(x,y,col);
}
tex.Apply();
return Sprite.Create(tex,new Rect(0,0,N,N),new Vector2(.5f,.5f),100f,0,SpriteMeshType.FullRect,new Vector4(30,30,30,30));
}
static float RoundedDistance(Vector2 p,Vector2 c,Vector2 h,float r)
{
Vector2 q=new Vector2(Mathf.Abs(p.x-c.x),Mathf.Abs(p.y-c.y))-(h-new Vector2(r,r));
float ox=Mathf.Max(q.x,0), oy=Mathf.Max(q.y,0);
return Mathf.Sqrt(ox*ox+oy*oy)+Mathf.Min(Mathf.Max(q.x,q.y),0)-r;
}
static bool Rounded(Vector2 p,Vector2 c,Vector2 h,float r){ return RoundedDistance(p,c,h,r)<=0f; }
static Sprite Glyph(System.Func<int,int,bool> draw)
{
const int S=96; Texture2D tex=new Texture2D(S,S,TextureFormat.RGBA32,false); tex.filterMode=FilterMode.Bilinear;
for(int y=0;y<S;y++) for(int x=0;x<S;x++) tex.SetPixel(x,y,draw(x,y)?Color.white:Clear);
tex.Apply(); return Sprite.Create(tex,new Rect(0,0,S,S),new Vector2(.5f,.5f),100f);
}
static bool DrawTrash(int x,int y)
{
float dx=x-48, dy=y-49; return (Mathf.Abs(dx)<15&&dy>-24&&dy<24)||(Mathf.Abs(dx)<20&&dy<-28&&dy>-34);
}
static bool DrawIdGrey(int x,int y)
{
float dx=x-48, dy=y-46; return (Mathf.Abs(dx)<20&&Mathf.Abs(dy)<24) && (Mathf.Abs(dx)>14 || Mathf.Abs(dy)>18);
}
}
// END-WAJIBY-CARDRT-V12_COMMANDUI
55textScripts/CardRuntimeBuilder.cs.meta
fileFormatVersion: 2 guid: bbf2211a74cccf94989f3746bf50f8bc
56textScripts/ConfirmBuilder.cs
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using TMPro;
public static class ConfirmBuilder
{
static readonly Color White = new Color(242f / 255f, 242f / 255f, 236f / 255f, 1f);
static readonly Color Black = new Color(0.05f, 0.05f, 0.05f, 1f);
static readonly Color BodyGray = new Color(0.30f, 0.32f, 0.30f, 1f);
static readonly Color Dark = new Color(10f / 255f, 14f / 255f, 11f / 255f, 220f / 255f);
[MenuItem("Tools/Build Confirm Window")]
static void Build()
{
var hm = Object.FindAnyObjectByType<HomeManager>();
if (hm == null) { Debug.LogError("HomeManager not found"); return; }
Transform root = hm.transform;
var old = FindDeep(root, "ConfirmRoot");
if (old != null) Object.DestroyImmediate(old.gameObject);
var ti = AssetImporter.GetAtPath("Assets/Icons/window.png") as TextureImporter;
if (ti != null)
{
ti.textureType = TextureImporterType.Sprite;
ti.spriteImportMode = SpriteImportMode.Single;
ti.spriteBorder = new Vector4(40, 40, 40, 40);
ti.SaveAndReimport();
}
GameObject croot = new GameObject("ConfirmRoot");
croot.transform.SetParent(root, false);
RectTransform crt = croot.AddComponent<RectTransform>();
crt.anchorMin = Vector2.zero; crt.anchorMax = Vector2.one;
crt.offsetMin = Vector2.zero; crt.offsetMax = Vector2.zero;
var bg = croot.AddComponent<Image>();
bg.color = Dark;
bg.raycastTarget = true;
GameObject frame = new GameObject("Confirm_Frame");
frame.transform.SetParent(croot.transform, false);
RectTransform frt = frame.AddComponent<RectTransform>();
frt.anchorMin = new Vector2(.5f, .5f); frt.anchorMax = new Vector2(.5f, .5f);
frt.pivot = new Vector2(.5f, .5f);
frt.sizeDelta = new Vector2(800, 500);
frt.anchoredPosition = new Vector2(0, 100);
Image fim = frame.AddComponent<Image>();
fim.sprite = Load("window");
fim.type = Image.Type.Sliced;
fim.color = Color.white;
GameObject txt = new GameObject("Confirm_Text");
txt.transform.SetParent(frame.transform, false);
RectTransform trt = txt.AddComponent<RectTransform>();
trt.anchorMin = new Vector2(0, 1); trt.anchorMax = new Vector2(1, 1);
trt.offsetMin = new Vector2(50, -105);
trt.offsetMax = new Vector2(-50, -25);
var tmp = txt.AddComponent<TextMeshProUGUI>();
tmp.fontSize = 52;
tmp.color = Black;
tmp.alignment = TextAlignmentOptions.Right;
tmp.raycastTarget = false;
var font = Amiri();
if (font != null) tmp.font = font;
txt.AddComponent<ArabicText>().SetText("حذف الملف؟ لا يمكن التراجع!");
GameObject body = new GameObject("Confirm_Body");
body.transform.SetParent(frame.transform, false);
RectTransform brt = body.AddComponent<RectTransform>();
brt.anchorMin = Vector2.zero; brt.anchorMax = Vector2.one;
brt.offsetMin = new Vector2(35, 35);
brt.offsetMax = new Vector2(-35, -115);
var bim = body.AddComponent<Image>();
bim.color = BodyGray;
bim.raycastTarget = true;
// white warning pinned just under the ledge
GameObject btxt = new GameObject("Confirm_BodyText");
btxt.transform.SetParent(body.transform, false);
RectTransform btrt = btxt.AddComponent<RectTransform>();
btrt.anchorMin = new Vector2(0, 1); btrt.anchorMax = new Vector2(1, 1);
btrt.offsetMin = new Vector2(40, -95);
btrt.offsetMax = new Vector2(-40, -25);
var btmp = btxt.AddComponent<TextMeshProUGUI>();
btmp.fontSize = 44;
btmp.color = White;
btmp.alignment = TextAlignmentOptions.Center;
btmp.raycastTarget = false;
if (font != null) btmp.font = font;
btxt.AddComponent<ArabicText>().SetText("ستحذف جميع المنبهات بداخله.");
GameObject ok = new GameObject("Btn_Confirm");
ok.transform.SetParent(frame.transform, false);
RectTransform ort = ok.AddComponent<RectTransform>();
ort.anchorMin = new Vector2(1, 0); ort.anchorMax = new Vector2(1, 0);
ort.pivot = new Vector2(.5f, .5f);
ort.sizeDelta = new Vector2(140, 140);
ort.anchoredPosition = new Vector2(-170, 120);
Image oim = ok.AddComponent<Image>();
oim.sprite = Load("check");
oim.color = Color.white;
oim.preserveAspect = true;
GameObject no = new GameObject("Btn_Cancel");
no.transform.SetParent(frame.transform, false);
RectTransform nrt = no.AddComponent<RectTransform>();
nrt.anchorMin = new Vector2(0, 0); nrt.anchorMax = new Vector2(0, 0);
nrt.pivot = new Vector2(.5f, .5f);
nrt.sizeDelta = new Vector2(140, 140);
nrt.anchoredPosition = new Vector2(170, 120);
Image nim = no.AddComponent<Image>();
nim.sprite = Load("close");
nim.color = Color.white;
nim.preserveAspect = true;
croot.transform.SetAsLastSibling();
var cd = croot.AddComponent<ConfirmDelete>();
hm.confirm = cd;
croot.SetActive(false);
Debug.Log("Confirm window built.");
}
static Sprite Load(string name)
{
Sprite s = AssetDatabase.LoadAssetAtPath<Sprite>($"Assets/Icons/{name}.png");
if (s == null) Debug.LogWarning("Sprite not found: " + name);
return s;
}
static TMP_FontAsset Amiri()
{
string[] g = AssetDatabase.FindAssets("Amiri t:TMP_FontAsset");
if (g.Length > 0)
return AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(AssetDatabase.GUIDToAssetPath(g[0]));
return TMP_Settings.defaultFontAsset;
}
static Transform FindDeep(Transform t, string n)
{
foreach (Transform c in t)
{
if (c.name == n) return c;
Transform d = FindDeep(c, n);
if (d != null) return d;
}
return null;
}
}
#endif57textScripts/ConfirmBuilder.cs.meta
fileFormatVersion: 2 guid: d65ae9dc52ae67b49a5531ab8750d1df
58textScripts/ConfirmDelete.cs
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class ConfirmDelete : MonoBehaviour
{
HomeManager home;
int pending = -1;
void Awake()
{
home = Object.FindAnyObjectByType<HomeManager>();
Wire("Confirm_Frame/Btn_Confirm", () =>
{
if (pending >= 0) home.DeleteProfile(pending);
Hide();
});
Wire("Confirm_Frame/Btn_Cancel", Hide);
// INSIDE the window: absorb every click, never close
Transform f = transform.Find("Confirm_Frame");
if (f != null)
{
Image fim = f.GetComponent<Image>();
if (fim != null) WajibyUITheme.Panel(fim, WajibyUITheme.Surface2, true, WajibyUITheme.Border);
f.GetComponent<Image>().raycastTarget = true;
Button fb = f.GetComponent<Button>();
if (fb == null) fb = f.gameObject.AddComponent<Button>();
fb.transition = Selectable.Transition.None;
fb.onClick.RemoveAllListeners();
}
// OUTSIDE (dark backdrop): close
Button b = GetComponent<Button>();
if (b == null) b = gameObject.AddComponent<Button>();
b.transition = Selectable.Transition.None;
b.onClick.RemoveAllListeners();
b.onClick.AddListener(Hide);
}
void Update()
{
if (Esc.Take()) Hide();
}
void Wire(string path, UnityEngine.Events.UnityAction a)
{
Transform t = transform.Find(path);
if (t == null) { Debug.LogError("Missing: " + path); return; }
Button b = t.GetComponent<Button>();
if (b == null) b = t.gameObject.AddComponent<Button>();
b.transition = Selectable.Transition.ColorTint;
b.targetGraphic = t.GetComponent<Image>();
b.colors = new ColorBlock
{
normalColor = Color.white,
highlightedColor = new Color(0.7f, 0.7f, 0.7f, 1f),
pressedColor = new Color(0.5f, 0.5f, 0.5f, 1f),
selectedColor = Color.white,
disabledColor = new Color(0.5f, 0.5f, 0.5f, 0.5f),
colorMultiplier = 1f,
fadeDuration = 0.1f
};
b.onClick.RemoveAllListeners();
b.onClick.AddListener(a);
}
public void Ask(int index)
{
pending = index;
transform.SetAsLastSibling();
gameObject.SetActive(true);
}
public void Hide()
{
pending = -1;
gameObject.SetActive(false);
}
}
59textScripts/ConfirmDelete.cs.meta
fileFormatVersion: 2 guid: 16bb56d9390afad4fb695ab2562fdd7d
60textScripts/CreationWizard.cs
// CANARY: WAJIBY_WIZARD_V8_COMMANDUI
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class CreationWizard : MonoBehaviour
{
const string C = "Wizard_Frame/Wizard_Body/Content/";
const string CC = "WizardCancelRoot/WizardCancel_Frame/";
HomeManager home; TMP_FontAsset font; RenamePanel rename; string draftName;
int draftH = 9, draftM; bool draftUseLast; int draftLastH = 9, draftLastM; bool hasDate; DateTime draftDate, calMonth;
volatile bool qDate, qTime, qLastTime; int qY, qM, qD, qH, qMin, qLH, qLMin;
static readonly Color GoldLine = new Color(228f / 255f, 184f / 255f, 63f / 255f, 1f);
static readonly Color Red = new Color(226f / 255f, 80f / 255f, 70f / 255f, 1f);
static readonly string[] Days = { "الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت" };
static readonly string[] Months = { "كانون الثاني", "شباط", "آذار", "نيسان", "أيار", "حزيران", "تموز", "آب", "أيلول", "تشرين الأول", "تشرين الثاني", "كانون الأول" };
void Awake()
{
home = UnityEngine.Object.FindAnyObjectByType<HomeManager>();
Transform vd = transform.Find(C + "Value_Date"); if (vd != null) font = vd.GetComponent<TextMeshProUGUI>().font;
Wire(CC + "Btn_Confirm", () => { SetCancel(false); HideAll(); });
Wire(CC + "Btn_Cancel", () => SetCancel(false));
Wire("Wizard_Frame/Btn_Close", () => SetCancel(true));
Wire("Wizard_Frame/Btn_Edit", () => { EnsureRename(); if (rename != null) rename.Show(DraftName()); });
Wire(C + "Value_Date", OpenDate); Wire(C + "Value_Time", OpenTime);
Wire(C + "Btn_LastSwitch", ToggleLast); Wire(C + "Value_LastTime", OpenLastTime);
Wire("Wizard_Frame/Cal_Root/Btn_Prev", () => { calMonth = calMonth.AddMonths(-1); BuildCalendar(); });
Wire("Wizard_Frame/Cal_Root/Btn_Next", () => { calMonth = calMonth.AddMonths(1); BuildCalendar(); });
Wire("Wizard_Frame/Btn_Save", Save);
foreach (string f in new[] { "Field_Days", "Field_Duty", "Field_Break", "Field_Remind" })
{
Transform t = transform.Find(C + f);
if (t != null) { string path = C + f; t.GetComponent<TMP_InputField>().onValueChanged.AddListener(s => SetLine(path, GoldLine)); }
}
if (NativePickers.Supported)
foreach (string n in new[] { C + "Field_HH", C + "Field_MM", C + "Colon" }) { Transform t = transform.Find(n); if (t != null) t.gameObject.SetActive(false); }
Transform cc = transform.Find(CC);
if (cc != null)
{
cc.GetComponent<Image>().raycastTarget = true;
Button eb = cc.GetComponent<Button>();
if (eb == null) eb = cc.gameObject.AddComponent<Button>();
eb.transition = Selectable.Transition.None;
eb.onClick.RemoveAllListeners();
}
}
string DraftName() { if (draftName != null) return draftName; var l = ProfileStore.Load(); return "الملف " + (l.Count + 1); }
void EnsureRename()
{
if (rename == null && home != null)
rename = RenamePanel.Build(transform, home.transform, font, home.wizardWindow, home.wizardCheck, home.wizardClose,
s => { draftName = s; Transform tt = transform.Find("Wizard_Frame/Wizard_Title"); if (tt != null) tt.GetComponent<ArabicText>().SetText(s != null ? s : "ملف جديد"); });
}
void Update()
{
if (qDate) { qDate = false; SetDate(new DateTime(qY, qM, qD)); }
if (qTime) { qTime = false; draftH = qH; draftM = qMin; SetText(C + "Value_Time", DutyScheduler.Time12(new DateTime(2000, 1, 1, draftH, draftM, 0))); }
if (qLastTime) { qLastTime = false; draftLastH = qLH; draftLastM = qLMin; SetText(C + "Value_LastTime", DutyScheduler.Time12(new DateTime(2000, 1, 1, draftLastH, draftLastM, 0))); }
RectTransform fr = transform.Find("Wizard_Frame") as RectTransform;
if (fr != null) { float target = TouchScreenKeyboard.visible ? 250f : 0f; if (Mathf.Abs(fr.anchoredPosition.y - target) > 1f) fr.anchoredPosition = new Vector2(0, target); }
}
// V7: use a real Button for generated controls. The old SafeTap route was unnecessary here and could lose the tap
// when another Image intercepted the pointer. Existing buttons are reused instead of being destroyed.
void Wire(string path, UnityEngine.Events.UnityAction a)
{
Transform t = transform.Find(path);
if (t == null) { Debug.LogError("Missing: " + path); return; }
SafeTap st = t.GetComponent<SafeTap>(); if (st != null) UnityEngine.Object.Destroy(st);
Button b = t.GetComponent<Button>(); if (b == null) b = t.gameObject.AddComponent<Button>();
b.transition = Selectable.Transition.None; b.interactable = true; b.targetGraphic = t.GetComponent<Image>(); b.onClick.RemoveAllListeners(); b.onClick.AddListener(a);
}
public void Ask()
{
DateTime plus = DateTime.Now.AddHours(1);
DateTime def = new DateTime(plus.Year, plus.Month, plus.Day, plus.Hour, 0, 0);
draftH = def.Hour; draftM = 0; draftUseLast = false; draftLastH = 9; draftLastM = 0; draftName = null;
hasDate = true; draftDate = def.Date; qDate = false; qTime = false; qLastTime = false;
Transform tt = transform.Find("Wizard_Frame/Wizard_Title"); if (tt != null) tt.GetComponent<ArabicText>().SetText("ملف جديد");
SetInput(C + "Field_Days", ""); SetInput(C + "Field_Duty", ""); SetInput(C + "Field_Break", "");
SetInput(C + "Field_HH", def.Hour.ToString("00")); SetInput(C + "Field_MM", "00"); SetInput(C + "Field_Remind", "");
foreach (string f in new[] { "Field_Days", "Field_Duty", "Field_Break", "Field_Remind" }) SetLine(C + f, GoldLine);
SetText(C + "Value_Date", DateLine(draftDate));
SetText(C + "Value_Time", DutyScheduler.Time12(new DateTime(2000, 1, 1, draftH, draftM, 0)));
SetText(C + "Value_LastTime", DutyScheduler.Time12(new DateTime(2000, 1, 1, 9, 0, 0)));
UpdateLastSwitch(false);
Transform lvt = transform.Find(C + "Value_LastTime"); if (lvt != null) lvt.gameObject.SetActive(false);
Transform lcol = transform.Find(C + "Colon_Last"); if (lcol != null) lcol.gameObject.SetActive(false);
SetCancel(false); Transform cal = transform.Find("Wizard_Frame/Cal_Root"); if (cal != null) cal.gameObject.SetActive(false);
transform.SetAsLastSibling(); gameObject.SetActive(true);
}
void HideAll() { SetCancel(false); gameObject.SetActive(false); }
void SetCancel(bool on) { Transform c = transform.Find("WizardCancelRoot"); if (c != null) c.gameObject.SetActive(on); }
void SetLine(string fieldPath, Color c) { Transform t = transform.Find(fieldPath + "/Line"); if (t != null) t.GetComponent<Image>().color = c; }
string DateLine(DateTime d) { return Days[(int)d.DayOfWeek] + " : " + d.ToString("yyyy/MM/dd"); }
int ReadInt(string path) { int v; return int.TryParse(GetInput(path), out v) ? v : -1; }
void UpdateLastSwitch(bool on)
{
Transform sw = transform.Find(C + "Btn_LastSwitch");
if (sw == null) return;
Sprite sprite = on ? CardRuntimeBuilder.BrightSwitchOn : CardRuntimeBuilder.BrightSwitchOff;
Image rootImage = sw.GetComponent<Image>();
if (rootImage != null)
{
// The root is a transparent hit area in the new design. Keep it transparent so
// the visible switch cannot lose its hit target.
rootImage.sprite = null;
rootImage.color = new Color(0f,0f,0f,0f);
rootImage.raycastTarget = true;
}
Transform visual = sw.Find("SwitchVisual");
Image visualImage = visual != null ? visual.GetComponent<Image>() : null;
if (visualImage != null)
{
visualImage.sprite = sprite;
visualImage.color = Color.white;
visualImage.preserveAspect = true;
visualImage.raycastTarget = false;
}
else if (rootImage != null)
{
// Backward compatibility with the previous builder where the sprite lived on the root.
rootImage.sprite = sprite;
rootImage.color = Color.white;
}
Button b = sw.GetComponent<Button>();
if (b == null) b = sw.gameObject.AddComponent<Button>();
b.interactable = true;
b.transition = Selectable.Transition.ColorTint;
b.targetGraphic = rootImage;
ColorBlock cb = b.colors;
cb.normalColor = Color.white;
cb.highlightedColor = new Color(1f,1f,1f,.96f);
cb.pressedColor = new Color(1f,1f,1f,.86f);
cb.selectedColor = Color.white;
cb.disabledColor = new Color(1f,1f,1f,.45f);
cb.colorMultiplier = 1f;
cb.fadeDuration = .08f;
b.colors = cb;
sw.SetAsLastSibling();
}
void ToggleLast()
{
draftUseLast = !draftUseLast;
UpdateLastSwitch(draftUseLast);
Transform lvt = transform.Find(C + "Value_LastTime"); if (lvt != null) lvt.gameObject.SetActive(draftUseLast);
Transform lcol = transform.Find(C + "Colon_Last"); if (lcol != null) lcol.gameObject.SetActive(draftUseLast);
}
void OpenDate()
{
DateTime start = hasDate ? draftDate : DateTime.Today;
if (NativePickers.Supported) NativePickers.PickDate(start.Year, start.Month, start.Day, (y, m, d) => { qY = y; qM = m; qD = d; qDate = true; });
else { calMonth = new DateTime(start.Year, start.Month, 1); BuildCalendar(); transform.Find("Wizard_Frame/Cal_Root").gameObject.SetActive(true); }
}
void SetDate(DateTime d) { hasDate = true; draftDate = d; SetText(C + "Value_Date", DateLine(d)); }
void OpenTime() { if (NativePickers.Supported) NativePickers.PickTime(draftH, draftM, (h, m) => { qH = h; qMin = m; qTime = true; }); }
void OpenLastTime() { if (NativePickers.Supported) NativePickers.PickTime(draftLastH, draftLastM, (h, m) => { qLH = h; qLMin = m; qLastTime = true; }); }
void BuildCalendar()
{
Transform cal = transform.Find("Wizard_Frame/Cal_Root"); Transform title = cal.Find("Cal_Title");
if (title != null) title.GetComponent<ArabicText>().SetText(Months[calMonth.Month - 1] + " " + calMonth.Year);
Transform grid = cal.Find("Cal_Grid"); foreach (Transform c in grid) Destroy(c.gameObject);
int lead = (int)calMonth.DayOfWeek, count = DateTime.DaysInMonth(calMonth.Year, calMonth.Month);
for (int d = 0; d < count; d++)
{
int cell = lead + d; GameObject go = new GameObject("Day_" + (d + 1)); go.transform.SetParent(grid, false);
RectTransform rt = go.AddComponent<RectTransform>(); rt.anchorMin = new Vector2(0, 1); rt.anchorMax = new Vector2(0, 1); rt.pivot = new Vector2(.5f, .5f);
rt.sizeDelta = new Vector2(90, 70); rt.anchoredPosition = new Vector2(60 + (cell % 7) * 97, -45 - (cell / 7) * 78);
var tmp = go.AddComponent<TextMeshProUGUI>(); tmp.fontSize = 38; tmp.alignment = TextAlignmentOptions.Center; tmp.raycastTarget = true;
DateTime date = calMonth.AddDays(d); tmp.color = (hasDate && date.Date == draftDate.Date) ? GoldLine : new Color(255f / 255f, 253f / 255f, 245f / 255f, 1f); tmp.text = (d + 1).ToString();
var b = go.AddComponent<Button>(); b.transition = Selectable.Transition.None; int day = d + 1;
b.onClick.AddListener(() => { SetDate(new DateTime(calMonth.Year, calMonth.Month, day)); cal.gameObject.SetActive(false); });
}
}
void Save()
{
foreach (string f in new[] { "Field_Days", "Field_Duty", "Field_Break", "Field_Remind" }) SetLine(C + f, GoldLine);
bool anyEmpty = false;
int days = ReadInt(C + "Field_Days"); if (days < 1) { SetLine(C + "Field_Days", Red); anyEmpty = true; }
int duty = ReadInt(C + "Field_Duty"); if (duty < 1) { SetLine(C + "Field_Duty", Red); anyEmpty = true; }
string restStr = GetInput(C + "Field_Break"); int rest;
if (string.IsNullOrEmpty(restStr) || !int.TryParse(restStr, out rest) || rest < 0) { SetLine(C + "Field_Break", Red); anyEmpty = true; rest = 0; }
string remStr = GetInput(C + "Field_Remind"); int remind = 0;
if (!string.IsNullOrEmpty(remStr) && (!int.TryParse(remStr, out remind) || remind < 0)) { SetLine(C + "Field_Remind", Red); anyEmpty = true; remind = 0; }
if (anyEmpty) { Toast.Show(home != null ? home.transform : transform, "لا يمكن ترك الحقل فارغاً", font); return; }
if (duty + rest > days * 24) { SetLine(C + "Field_Duty", Red); SetLine(C + "Field_Break", Red); Toast.Show(home != null ? home.transform : transform, "مجموع الواجب والاستراحة أطول من مدة الدوام", font); return; }
if (remind > 0 && remind >= rest * 60) { SetLine(C + "Field_Remind", Red); Toast.Show(home != null ? home.transform : transform, "التذكير يساوي أو أطول من مدة الاستراحة", font); return; }
if (!hasDate) { OpenDate(); return; }
if (!NativePickers.Supported) { int hh, mm; int.TryParse(GetInput(C + "Field_HH"), out hh); int.TryParse(GetInput(C + "Field_MM"), out mm); draftH = Mathf.Clamp(hh, 0, 23); draftM = Mathf.Clamp(mm, 0, 59); }
DateTime start = draftDate.Date.AddMinutes(draftH * 60 + draftM);
if (start < DateTime.Now) { Toast.Show(home != null ? home.transform : transform, "لا يمكن إنشاء جدول يبدأ في الماضي", font); return; }
if (remind > 0 && start.AddMinutes(-remind) < DateTime.Now) { SetLine(C + "Field_Remind", Red); Toast.Show(home != null ? home.transform : transform, "التذكير في وقت مضى لأول واجب", font); return; }
var list = ProfileStore.Load(); if (list.Count >= ProfileStore.MaxProfiles) { HideAll(); return; }
Profile p = new Profile(); p.name = draftName != null ? draftName : "الملف " + (list.Count + 1);
p.shiftDays = days; p.dutyHours = duty; p.restHours = rest; p.firstShiftTicks = draftDate.Date.Ticks; p.firstDutyTime = draftH.ToString("00") + ":" + draftM.ToString("00");
p.useLastDuty = draftUseLast; p.lastDutyTime = draftLastH.ToString("00") + ":" + draftLastM.ToString("00"); p.reminderMinutes = remind;
p.alarmsActive = false; p.locked = true; p.adsWatched = 0;
List<DutyAlarm> duties; List<long> breaks;
DutyScheduler.GenerateAll(draftDate, days, duty, rest, draftH * 60 + draftM, draftUseLast, draftLastH * 60 + draftLastM, remind, out duties, out breaks);
p.alarms = duties; p.breakTicks = breaks; list.Add(p); ProfileStore.Save(list); home.Rebuild(); HideAll();
}
void SetInput(string path, string v) { Transform t = transform.Find(path); if (t != null) t.GetComponent<TMP_InputField>().text = v; }
string GetInput(string path) { Transform t = transform.Find(path); return t != null ? t.GetComponent<TMP_InputField>().text : ""; }
void SetText(string path, string v) { Transform t = transform.Find(path); if (t != null) t.GetComponent<ArabicText>().SetText(v); }
}
// END-WAJIBY-WIZARD-V8_COMMANDUI
61textScripts/CreationWizard.cs.meta
fileFormatVersion: 2 guid: 3a5d749bdf7a6e84bab0a1ee9c3ce613
62textScripts/DutyScheduler.cs
// CANARY: WAJIBY_SCHED_V30_LABELS
using System;
using System.Collections.Generic;
public static class DutyScheduler
{
public static void GenerateAll(DateTime firstShift, int shiftDays,
int dutyHours, int restHours, int firstMinutes,
bool useLast, int lastMinutes, int reminderMinutes,
out List<DutyAlarm> duties, out List<long> breaks)
{
duties = new List<DutyAlarm>();
breaks = new List<long>();
if (shiftDays < 1) shiftDays = 1;
int step = Math.Max(dutyHours + restHours, 1);
DateTime shiftStart = firstShift.Date;
// V31 LAST-DUTY FIX: the closing duty belongs on the SAME weekday the tour began,
// shiftDays days later (a 7-day tour from Saturday ends on Saturday), not on
// shiftDays-1 (the 7th-inclusive Friday) which produced an off-grid phantom.
DateTime lastDt = shiftStart.AddDays(shiftDays).Date.AddMinutes(lastMinutes);
// With a pinned closing duty the window runs up to that handover, so every real grid
// duty before it (incl. any early ones on the closing day) is kept, and the handover
// is appended once as the final duty -> nothing is ever scheduled after the last duty.
DateTime shiftEnd = useLast ? lastDt : shiftStart.AddDays(shiftDays);
DateTime t = shiftStart.AddMinutes(firstMinutes);
while (t < shiftEnd && duties.Count < 400)
{
if (!(useLast && t == lastDt)) Add(duties, t, reminderMinutes, false);
if (restHours > 0) breaks.Add(t.AddHours(dutyHours).Ticks);
t = t.AddHours(step);
}
if (useLast && lastDt >= shiftStart && duties.Count < 400)
Add(duties, lastDt, reminderMinutes, true);
duties.Sort((x, y) => x.ticks.CompareTo(y.ticks));
for (int i = duties.Count - 1; i > 0; i--)
if (duties[i].ticks == duties[i - 1].ticks) duties.RemoveAt(i);
breaks.Sort();
}
static void Add(List<DutyAlarm> list, DateTime t, int reminderMinutes, bool isLast)
{
list.Add(new DutyAlarm
{
ticks = t.Ticks,
reminderTicks = reminderMinutes > 0 ? t.AddMinutes(-reminderMinutes).Ticks : 0,
isLast = isLast,
active = true
});
}
public static long NextAlarm(Profile p)
{
long now = DateTime.Now.Ticks;
foreach (var a in p.alarms)
if (a.active && p.alarmsActive && a.ticks >= now) return a.ticks;
return 0;
}
public static string Time12(DateTime d, bool shortSuffix = false)
{
int h = d.Hour % 12; if (h == 0) h = 12;
string suffix = d.Hour < 12 ? (shortSuffix ? "ص" : "صباحا") : (shortSuffix ? "م" : "مساءا");
return h.ToString("00") + ":" + d.Minute.ToString("00") + " " + suffix;
}
public static string FormatDateWithDots(DateTime d)
{ return GetArabicDayName(d.DayOfWeek) + " • " + d.ToString("yyyy/MM/dd") + " • " + Time12(d); }
public static string GetArabicDayName(DayOfWeek day)
{
switch (day)
{
case DayOfWeek.Sunday: return "الأحد";
case DayOfWeek.Monday: return "الإثنين";
case DayOfWeek.Tuesday: return "الثلاثاء";
case DayOfWeek.Wednesday: return "الأربعاء";
case DayOfWeek.Thursday: return "الخميس";
case DayOfWeek.Friday: return "الجمعة";
case DayOfWeek.Saturday: return "السبت";
default: return "";
}
}
public static string RemText(int mins)
{
if (mins <= 0) return "بدون تذكير";
int hours = mins / 60; int minutes = mins % 60;
string ht = GetHourLabel(hours); string mt = GetMinuteLabel(minutes);
if (hours > 0 && minutes > 0) return "تذكير: " + hours + " " + ht + " و " + minutes + " " + mt;
if (hours > 0) return "تذكير: " + hours + " " + ht;
return "تذكير: " + minutes + " " + mt;
}
static string GetHourLabel(int h) { if (h == 1 || h == 2) return "ساعة"; if (h >= 3 && h <= 10) return "ساعات"; return "ساعة"; }
static string GetMinuteLabel(int m) { if (m == 1 || m == 2) return "دقيقة"; if (m >= 3 && m <= 10) return "دقائق"; return "دقيقة"; }
public static string DayPart(DateTime d)
{
if (d.Date == DateTime.Today) return "";
if (d.Date == DateTime.Today.AddDays(1)) return "غداً ";
return GetArabicDayName(d.DayOfWeek) + " ";
}
public static string NextHeading() { return "الواجب القادم: "; }
public static string NoneValue() { return "لا يوجد"; }
public static string LastHeading() { return "آخر واجب: "; }
public static string LastTag() { return "أخير"; }
public static string DutiesLine(int totD, int totDH, int remD, int remDH)
{ return "الواجبات " + totD + " • " + totDH + " ساعة : المتبقي " + remD + " • " + remDH + " ساعة"; }
public static string RestsLine(int totB, int totBH, int remB, int remBH)
{ return "الاستراحات " + totB + " • " + totBH + " ساعة : المتبقي " + remB + " • " + remBH + " ساعة"; }
public static string AppName() { return "واجبي"; }
public static string BtnStop() { return "إيقاف"; }
public static string BtnSnoozeN(int n) { return "غفو " + n + " دقائق"; }
public static string MsgReminder() { return "تذكير: الواجب بعد قليل"; }
public static string MsgDuty() { return "حان وقت الواجب"; }
public static string CountdownPrefix() { return "غفو تلقائي بعد "; }
public static string AskDeleteTitle() { return "حذف الواجب؟"; }
public static string NoUndo() { return "لا يمكن التراجع!"; }
public static string EmptyAlarms() { return "لا توجد واجبات متبقية"; }
public static string RemEditTitle() { return "تذكير قبل الواجب بالدقائق"; }
public static string SnoozeInfoPrefix() { return "تم التأجيل الى: "; }
public static string RemTooLong() { return "التذكير يساوي أو أطول من مدة الاستراحة"; }
public static string PermTitle() { return "الأذونات"; }
public static string PermSub() { return "يتطلب واجبي الأذونات التالية ليعمل المنبه بشكل صحيح"; }
public static string PermNotifT() { return "الإشعارات"; }
public static string PermNotifS() { return "إرسال إشعارات المنبه والتذكير"; }
public static string PermAutoT() { return "التشغيل التلقائي بالخلفية"; }
// parenthetical removed -> no brackets to mirror, no mystery text
public static string PermAutoS() { return "السماح للتطبيق بالعمل في الخلفية"; }
public static string PermBattT() { return "تحسين البطارية"; }
public static string PermBattS() { return "استثناء التطبيق من قيود البطارية"; }
public static string PermAllow() { return "سماح"; }
public static string PermAllowed() { return "مسموح"; }
public static string PermOpenSettings(){ return "فتح الإعدادات"; }
public static string PermContinue() { return "المتابعة"; }
}63textScripts/DutyScheduler.cs.meta
fileFormatVersion: 2 guid: 912d0cd806159384ab38dab120df5d65
64textScripts/FieldKeyboard.cs
using UnityEngine;
using UnityEngine.UI;
using TMPro;
[RequireComponent(typeof(TMP_InputField))]
public class FieldKeyboard : MonoBehaviour
{
TMP_InputField fld;
TouchScreenKeyboard kb;
void Awake()
{
fld = GetComponent<TMP_InputField>();
fld.shouldHideMobileInput = true; // TMP draws caret + selection
fld.caretColor = Color.white;
fld.selectionColor = new Color(1f, 1f, 1f, 0.25f);
fld.onSelect.AddListener(s => Open());
fld.onDeselect.AddListener(s => Close());
}
void Open()
{
if (kb != null) return;
var type = fld.contentType == TMP_InputField.ContentType.IntegerNumber
? TouchScreenKeyboardType.NumberPad
: TouchScreenKeyboardType.Default;
kb = TouchScreenKeyboard.Open(fld.text, type, false, false, false, false);
}
void Close()
{
if (kb != null) { kb.active = false; kb = null; }
}
void Update()
{
if (kb == null) return;
if (!fld.isFocused) { Close(); return; }
if (kb.text != fld.text) fld.text = kb.text;
if (kb.status == TouchScreenKeyboard.Status.Done ||
kb.status == TouchScreenKeyboard.Status.Canceled) Close();
}
void OnDisable() { Close(); }
}65textScripts/FieldKeyboard.cs.meta
fileFormatVersion: 2 guid: 3714c9fb5ec972e4bb0496e6a7795ece
66textScripts/FindDuplicateClasses.cs
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
public static class FindDuplicateClasses
{
[MenuItem("Tools/Find Duplicate Class Definitions")]
static void Scan()
{
var hits = new Dictionary<string, List<string>>();
foreach (string path in Directory.GetFiles(Application.dataPath, "*.cs", SearchOption.AllDirectories))
{
foreach (string line in File.ReadAllLines(path))
{
string t = line.Trim();
if (t.StartsWith("public class ") || t.StartsWith("class ") ||
t.StartsWith("public static class ") || t.StartsWith("static class "))
{
string name = t.Substring(t.LastIndexOf(' ') + 1).TrimEnd(':', '{', ' ');
if (!hits.ContainsKey(name)) hits[name] = new List<string>();
hits[name].Add(path); // one entry per occurrence
}
}
}
bool found = false;
foreach (var kv in hits)
{
if (kv.Value.Count > 1)
{
found = true;
Debug.LogError($"CLASS '{kv.Key}' APPEARS {kv.Value.Count} TIMES:\n" + string.Join("\n", kv.Value));
}
}
if (!found) Debug.Log("No duplicate class definitions found.");
}
}
#endif67textScripts/FindDuplicateClasses.cs.meta
fileFormatVersion: 2 guid: 4d9e45a85a7e830478ce7a6b22d6eb17
68textScripts/FlatIcons.cs
// CANARY: WAJIBY_FLATICONS_V2
using UnityEngine;
public static class FlatIcons
{
const int N = 128;
static Sprite _edit,_id,_scroll,_gear;
public static Sprite Edit(){ if(_edit==null)_edit=Build(Pencil); return _edit; }
public static Sprite Id(){ if(_id==null)_id=Build(IdCard); return _id; }
public static Sprite Scroll(){ if(_scroll==null)_scroll=Build(Chevrons); return _scroll; }
public static Sprite Gear(){ if(_gear==null)_gear=Build(GearDraw); return _gear; }
static Sprite Build(System.Action<Color32[]> draw)
{
var buf=new Color32[N*N];
for(int i=0;i<buf.Length;i++) buf[i]=new Color32(255,255,255,0);
draw(buf);
var t=new Texture2D(N,N,TextureFormat.RGBA32,false){ filterMode=FilterMode.Bilinear, wrapMode=TextureWrapMode.Clamp };
t.SetPixels32(buf); t.Apply();
return Sprite.Create(t,new Rect(0,0,N,N),new Vector2(.5f,.5f),100f);
}
static void Put(Color32[] b,int x,int y,float a){ if(x<0||y<0||x>=N||y>=N)return; int i=y*N+x; byte na=(byte)(Mathf.Clamp01(a)*255); if(na>b[i].a) b[i]=new Color32(255,255,255,na); }
static float DSeg(float px,float py,float x1,float y1,float x2,float y2){ float dx=x2-x1,dy=y2-y1,l2=dx*dx+dy*dy; float t=l2>0?Mathf.Clamp01(((px-x1)*dx+(py-y1)*dy)/l2):0; float cx=x1+t*dx,cy=y1+t*dy; return Mathf.Sqrt((px-cx)*(px-cx)+(py-cy)*(py-cy)); }
static void Seg(Color32[] b,float x1,float y1,float x2,float y2,float r){ int x0=(int)Mathf.Min(x1,x2)-2,x1e=(int)Mathf.Max(x1,x2)+2,y0=(int)Mathf.Min(y1,y2)-2,y1e=(int)Mathf.Max(y1,y2)+2; for(int y=y0;y<=y1e;y++)for(int x=x0;x<=x1e;x++) Put(b,x,y,r-DSeg(x+.5f,y+.5f,x1,y1,x2,y2)+.5f); }
static void Disc(Color32[] b,float cx,float cy,float r){ int x0=(int)(cx-r)-1,x1=(int)(cx+r)+1,y0=(int)(cy-r)-1,y1=(int)(cy+r)+1; for(int y=y0;y<=y1;y++)for(int x=x0;x<=x1;x++){ float d=Mathf.Sqrt((x+.5f-cx)*(x+.5f-cx)+(y+.5f-cy)*(y+.5f-cy)); Put(b,x,y,r-d+.5f);} }
static float RSDF(float px,float py,float cx,float cy,float hx,float hy,float rr){ float qx=Mathf.Abs(px-cx)-(hx-rr),qy=Mathf.Abs(py-cy)-(hy-rr); float ox=Mathf.Max(qx,0),oy=Mathf.Max(qy,0); return Mathf.Sqrt(ox*ox+oy*oy)+Mathf.Min(Mathf.Max(qx,qy),0)-rr; }
static void Pencil(Color32[] b){ float ax=40,ay=40,bx=92,by=92,dx=bx-ax,dy=by-ay,l2=dx*dx+dy*dy;
for(int y=30;y<=102;y++)for(int x=30;x<=102;x++){ float t=l2>0?Mathf.Clamp01(((x+.5f-ax)*dx+(y+.5f-ay)*dy)/l2):0; float cx=ax+t*dx,cy=ay+t*dy; float perp=Mathf.Sqrt((x+.5f-cx)*(x+.5f-cx)+(y+.5f-cy)*(y+.5f-cy)); float rad=Mathf.Lerp(2.5f,9f,Mathf.SmoothStep(0,1,Mathf.Clamp01(t*3.2f))); Put(b,x,y,rad-perp+.5f);} }
static void IdCard(Color32[] b){ float cx=64,cy=64;
for(int y=8;y<=120;y++)for(int x=8;x<=120;x++){ float po=RSDF(x+.5f,y+.5f,cx,cy,52,38,12), pi=RSDF(x+.5f,y+.5f,cx,cy,45,31,8); float band=Mathf.Max(po,-pi); Put(b,x,y,.5f-band);}
Disc(b,cx-20,cy+9,11); Seg(b,cx-34,cy-15,cx-6,cy-15,9); Seg(b,cx+8,cy+7,cx+40,cy+7,4); Seg(b,cx+8,cy-7,cx+30,cy-7,4); }
static void Chevrons(Color32[] b){ Seg(b,44,72,64,92,5); Seg(b,64,92,84,72,5); Seg(b,44,56,64,36,5); Seg(b,64,36,84,56,5); }
// cog: filled disc with 8 lobes and a centre hole
static void GearDraw(Color32[] b){ float cx=64,cy=64,hole=15;
for(int y=0;y<N;y++)for(int x=0;x<N;x++){ float dx=x+.5f-cx,dy=y+.5f-cy; float d=Mathf.Sqrt(dx*dx+dy*dy); float ang=Mathf.Atan2(dy,dx); float ro=34f+8f*(0.5f+0.5f*Mathf.Cos(8f*ang)); Put(b,x,y,Mathf.Clamp01(ro-d+0.5f)*Mathf.Clamp01(d-hole+0.5f)); } }
}
// END-WAJIBY-FLATICONS-V269textScripts/FlatIcons.cs.meta
fileFormatVersion: 2 guid: 10e7c33362db87b48a8a3c2f73cdf863
70textScripts/ForcePortrait.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public static class ForcePortrait
{
[MenuItem("Tools/Force Portrait Orientation")]
static void Set()
{
PlayerSettings.defaultInterfaceOrientation = UIOrientation.Portrait;
Debug.Log("Orientation locked to Portrait.");
}
}
#endif71textScripts/ForcePortrait.cs.meta
fileFormatVersion: 2 guid: 723397b483acf6d4686b636200fe30f5
72textScripts/HomeManager.cs
// CANARY: WAJIBY_HOME_V68_COMMANDUI
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class HomeManager : MonoBehaviour
{
public GameObject cardPrefab;
public Sprite switchOn, switchOff, wizardWindow, wizardClose, wizardCheck, editIcon, scrollIcon;
public Sprite cardPanel, cardId, cardTrash, cardPen, cardPlay;
public ConfirmDelete confirm;
public CreationWizard wizard;
public AlarmWindow alarmWindow;
public GameObject permissionsButton;
RenamePanel homeRename;
int renameIndex = -1;
int lastRev = -1;
TMP_FontAsset uiFont;
const float CARD_H = 440f;
const float CARD_SPACING = 450f;
const float CARD_TOP = 150f;
const float HINT_GAP = 30f;
const float GEAR_RIGHT = 36f;
const float GEAR_DOWN = 30f;
const float GEAR_SIZE = 72f;
const string LOCK_PROMPT = "شاهد إعلانين لفتح الملف";
// Bright, warm military palette. Data and scheduling logic remain unchanged.
static readonly Color Gold = new Color(213f/255f, 178f/255f, 74f/255f, 1f);
static readonly Color White = new Color(244f/255f, 240f/255f, 228f/255f, 1f);
static readonly Color Paper = new Color(244f/255f, 240f/255f, 228f/255f, 1f);
static readonly Color InfoGray = new Color(169f/255f, 182f/255f, 170f/255f, 1f);
static readonly Color Dim = new Color(63f/255f, 75f/255f, 66f/255f, 1f);
static readonly Color DimText = new Color(118f/255f, 132f/255f, 122f/255f, 1f);
static readonly Color PlayOrange = new Color(213f/255f, 178f/255f, 74f/255f, 1f);
static readonly Color Accent = new Color(213f/255f, 178f/255f, 74f/255f, 1f);
static readonly Color Bg = new Color(15f/255f, 23f/255f, 18f/255f, 1f);
static readonly Color Header = new Color(37f/255f, 63f/255f, 45f/255f, 1f);
static Sprite _playSprite;
RectTransform cardParent;
GameObject emptyHint;
GameObject fab;
GameObject maxHint;
GameObject emptyLogo;
GameObject settingsGear;
void Awake()
{
Transform cards = FindDeep(transform, "CardsContainer");
Transform empty = FindDeep(transform, "EmptyHint");
Transform add = FindDeep(transform, "FAB_Add");
Transform max = FindDeep(transform, "MaxHint");
cardParent = cards != null ? cards.GetComponent<RectTransform>() : null;
emptyHint = empty != null ? empty.gameObject : null;
fab = add != null ? add.gameObject : null;
maxHint = max != null ? max.gameObject : null;
if (maxHint != null && cardParent != null)
{
maxHint.transform.SetParent(cardParent, false);
RectTransform mr = maxHint.GetComponent<RectTransform>();
if (mr != null)
{
mr.anchorMin = new Vector2(.5f, 1f);
mr.anchorMax = new Vector2(.5f, 1f);
mr.pivot = new Vector2(.5f, 1f);
mr.sizeDelta = new Vector2(900f, 90f);
}
TextMeshProUGUI mt = maxHint.GetComponent<TextMeshProUGUI>();
if (mt == null) mt = maxHint.GetComponentInChildren<TextMeshProUGUI>(true);
if (mt != null)
{
mt.textWrappingMode = TextWrappingModes.NoWrap;
mt.alignment = TextAlignmentOptions.Center;
mt.color = InfoGray;
}
}
uiFont = ResolveFont();
SetupAddButton();
DisableLegacyDrawerButtons();
EnsureSettingsGear();
StyleHomeChrome();
BuildEmptyLogo();
}
void Start()
{
Rebuild();
}
void Update()
{
if (AlarmSystem.revision != lastRev) Rebuild();
}
static TMP_FontAsset ResolveFont()
{
foreach (TMP_FontAsset f in Resources.FindObjectsOfTypeAll<TMP_FontAsset>())
{
if (f == null || f.characterTable == null) continue;
foreach (TMP_Character c in f.characterTable)
if (c != null && c.unicode == 0x062D) return f;
}
TextMeshProUGUI t = UnityEngine.Object.FindAnyObjectByType<TextMeshProUGUI>();
if (t != null && t.font != null) return t.font;
return TMP_Settings.defaultFontAsset;
}
void SetupAddButton()
{
if (fab == null) return;
RectTransform rt = fab.GetComponent<RectTransform>();
if (rt == null) rt = fab.AddComponent<RectTransform>();
rt.anchorMin = new Vector2(.5f,0f); rt.anchorMax = new Vector2(.5f,0f); rt.pivot = new Vector2(.5f,0f);
rt.sizeDelta = new Vector2(430f,102f); rt.anchoredPosition = new Vector2(0f,74f);
Button b = fab.GetComponent<Button>();
if (b == null) b = fab.AddComponent<Button>();
b.transition = Selectable.Transition.ColorTint;
b.onClick.RemoveAllListeners(); b.onClick.AddListener(AddProfile);
Image im = fab.GetComponent<Image>();
if (im != null)
{
Sprite old = im.sprite;
im.sprite = RoundedUiSprite(Header, new Color(Gold.r,Gold.g,Gold.b,.35f));
im.type = Image.Type.Sliced; im.color = Color.white; im.raycastTarget = true;
Transform icon = fab.transform.Find("FabIcon");
if (icon == null)
{
GameObject go = new GameObject("FabIcon"); go.transform.SetParent(fab.transform,false);
RectTransform ir = go.AddComponent<RectTransform>(); ir.anchorMin=new Vector2(1,0.5f); ir.anchorMax=new Vector2(1,0.5f); ir.pivot=new Vector2(.5f,.5f); ir.sizeDelta=new Vector2(66,66); ir.anchoredPosition=new Vector2(-56,0);
Image ii=go.AddComponent<Image>(); ii.sprite=old != null ? old : CardRuntimeBuilder.Res("addperson"); ii.color=Color.white; ii.preserveAspect=true; ii.raycastTarget=false;
icon=go.transform;
}
Transform label = fab.transform.Find("FabLabel");
if (label == null)
{
GameObject go=new GameObject("FabLabel"); go.transform.SetParent(fab.transform,false);
RectTransform lr=go.AddComponent<RectTransform>(); lr.anchorMin=new Vector2(0.12f,0); lr.anchorMax=new Vector2(.82f,1); lr.offsetMin=Vector2.zero; lr.offsetMax=Vector2.zero;
TextMeshProUGUI tt=go.AddComponent<TextMeshProUGUI>(); tt.font=uiFont; tt.fontSize=36; tt.color=new Color(1f,1f,.95f,1f); tt.alignment=TextAlignmentOptions.Center; tt.textWrappingMode=TextWrappingModes.NoWrap; go.AddComponent<ArabicText>().SetText("إضافة ملف شخصي");
}
AddOutline(fab, new Color(Gold.r,Gold.g,Gold.b,.55f), 1.5f);
AddShadow(fab, new Color(0f,0f,0f,.18f), new Vector2(0f,-7f));
ColorBlock cb=b.colors; cb.normalColor=Color.white; cb.highlightedColor=new Color(1f,1f,1f,.96f); cb.pressedColor=new Color(1f,1f,1f,.88f); cb.selectedColor=Color.white; cb.fadeDuration=.08f; b.colors=cb;
}
}
Sprite RoundedUiSprite(Color fill, Color edge)
{
const int N=128; Texture2D tex=new Texture2D(N,N,TextureFormat.RGBA32,false); tex.filterMode=FilterMode.Bilinear;
Vector2 c=new Vector2(N/2f,N/2f), h=new Vector2(N/2f-1,N/2f-1);
for(int y=0;y<N;y++) for(int x=0;x<N;x++)
{
Vector2 q=new Vector2(Mathf.Abs(x+.5f-c.x),Mathf.Abs(y+.5f-c.y))-(h-new Vector2(26,26));
float d=Mathf.Sqrt(Mathf.Max(q.x,0)*Mathf.Max(q.x,0)+Mathf.Max(q.y,0)*Mathf.Max(q.y,0))+Mathf.Min(Mathf.Max(q.x,q.y),0)-26;
tex.SetPixel(x,y,d<=0?fill:new Color(0,0,0,0));
}
tex.Apply(); return Sprite.Create(tex,new Rect(0,0,N,N),new Vector2(.5f,.5f),100f,0,SpriteMeshType.FullRect,new Vector4(30,30,30,30));
}
void DisableLegacyDrawerButtons()
{
// The old drawer is retired. Hide only its well-known trigger objects.
string[] names = { "Btn_Menu", "Hamburger", "TopMenu", "Btn_Hamburger" };
for (int i = 0; i < names.Length; i++)
{
Transform t = FindDeep(transform, names[i]);
if (t != null && (settingsGear == null || t.gameObject != settingsGear))
t.gameObject.SetActive(false);
}
}
void EnsureSettingsGear()
{
Canvas canvas = GetComponentInParent<Canvas>();
Transform root = canvas != null ? canvas.transform : transform;
Transform existing = permissionsButton != null ? permissionsButton.transform : null;
if (existing == null)
{
string[] names = { "Btn_Settings", "Settings" };
for (int i = 0; i < names.Length && existing == null; i++)
existing = FindDeep(root, names[i]);
}
if (existing != null)
{
settingsGear = existing.gameObject;
}
else
{
settingsGear = new GameObject("WajibySettingsGear");
settingsGear.transform.SetParent(root, false);
settingsGear.AddComponent<RectTransform>();
settingsGear.AddComponent<Image>();
}
RectTransform rt = settingsGear.GetComponent<RectTransform>();
if (rt == null) rt = settingsGear.AddComponent<RectTransform>();
rt.anchorMin = new Vector2(1f, 1f);
rt.anchorMax = new Vector2(1f, 1f);
rt.pivot = new Vector2(1f, 1f);
rt.sizeDelta = new Vector2(GEAR_SIZE, GEAR_SIZE);
rt.anchoredPosition = new Vector2(-GEAR_RIGHT, -GEAR_DOWN);
Image im = settingsGear.GetComponent<Image>();
if (im == null) im = settingsGear.GetComponentInChildren<Image>(true);
if (im == null) im = settingsGear.AddComponent<Image>();
Sprite gear = Resources.Load<Sprite>("Icons/gear");
if (gear == null) gear = FlatIcons.Gear();
im.sprite = gear;
im.color = Gold;
im.preserveAspect = true;
im.raycastTarget = true;
SafeTap oldTap = settingsGear.GetComponent<SafeTap>();
if (oldTap != null) UnityEngine.Object.Destroy(oldTap);
Button b = settingsGear.GetComponent<Button>();
if (b == null) b = settingsGear.AddComponent<Button>();
b.transition = Selectable.Transition.None;
b.onClick.RemoveAllListeners();
b.onClick.AddListener(SettingsScreen.Show);
EnsureGearPlate(root, rt);
settingsGear.SetActive(true);
}
void EnsureGearPlate(Transform root, RectTransform gearRt)
{
Transform plate = root.Find("WajibyGearPlate");
if (plate == null)
{
GameObject go = new GameObject("WajibyGearPlate");
go.transform.SetParent(root, false);
go.transform.SetAsFirstSibling();
Image im = go.AddComponent<Image>();
im.sprite = MakeCircleSprite(new Color(31f/255f, 51f/255f, 37f/255f, 1f), new Color(Gold.r, Gold.g, Gold.b, .45f));
im.raycastTarget = false;
plate = go.transform;
}
RectTransform prt = plate.GetComponent<RectTransform>();
if (prt == null) prt = plate.gameObject.AddComponent<RectTransform>();
prt.anchorMin = gearRt.anchorMin;
prt.anchorMax = gearRt.anchorMax;
prt.pivot = gearRt.pivot;
prt.sizeDelta = gearRt.sizeDelta + new Vector2(22f,22f);
prt.anchoredPosition = gearRt.anchoredPosition;
}
Sprite MakeCircleSprite(Color fill, Color ring)
{
const int S = 96;
Texture2D tex = new Texture2D(S,S,TextureFormat.RGBA32,false);
Vector2 c = new Vector2(S/2f,S/2f);
float ro=44f, ri=40f;
for(int y=0;y<S;y++) for(int x=0;x<S;x++)
{
float d=Vector2.Distance(new Vector2(x+.5f,y+.5f),c);
tex.SetPixel(x,y,d<=ro ? (d>=ri?ring:fill) : new Color(0,0,0,0));
}
tex.Apply();
return Sprite.Create(tex,new Rect(0,0,S,S),new Vector2(.5f,.5f),100f);
}
void BuildEmptyLogo()
{
if (emptyLogo != null || emptyHint == null) return;
Sprite s = Resources.Load<Sprite>("Logo/WajibyLogo");
if (s == null) s = Resources.Load<Sprite>("Logo/WajibiLogo");
if (s == null) return;
GameObject go = new GameObject("EmptyLogo");
go.transform.SetParent(emptyHint.transform, false);
RectTransform rt = go.AddComponent<RectTransform>();
rt.anchorMin = new Vector2(.5f,1f); rt.anchorMax = new Vector2(.5f,1f); rt.pivot = new Vector2(.5f,0f);
rt.anchoredPosition = new Vector2(0f,34f); rt.sizeDelta = new Vector2(190f,190f);
Image im = go.AddComponent<Image>(); im.sprite = s; im.color = Color.white; im.preserveAspect = true; im.raycastTarget = false;
emptyLogo = go;
}
void StyleHomeChrome()
{
Canvas canvas = GetComponentInParent<Canvas>();
Transform root = canvas != null ? canvas.transform : transform;
Transform bg = FindDeep(root, "Background");
if (bg != null)
{
Image im = bg.GetComponent<Image>();
if (im != null) { im.color = Bg; im.raycastTarget = true; }
}
Transform top = FindDeep(root, "TopBar");
if (top != null)
{
Image im = top.GetComponent<Image>();
if (im != null) { im.color = Header; im.raycastTarget = false; }
EnsureHomeTitle(top);
EnsureHomeAccent(top);
}
if (emptyHint != null)
{
Transform txt = emptyHint.transform.Find("Text");
if (txt == null) txt = emptyHint.transform;
TextMeshProUGUI tmp = txt.GetComponent<TextMeshProUGUI>();
if (tmp != null)
{
tmp.font = uiFont != null ? uiFont : ResolveFont();
tmp.fontSize = 42f;
tmp.color = White;
tmp.alignment = TextAlignmentOptions.Center;
tmp.textWrappingMode = TextWrappingModes.NoWrap;
}
}
}
void EnsureHomeTitle(Transform top)
{
Transform t = top.Find("WajibyHomeTitle");
if (t == null)
{
GameObject go = new GameObject("WajibyHomeTitle");
go.transform.SetParent(top, false);
RectTransform rt = go.AddComponent<RectTransform>();
rt.anchorMin = new Vector2(.18f, 0f);
rt.anchorMax = new Vector2(.82f, 1f);
rt.offsetMin = new Vector2(0f, 14f);
rt.offsetMax = new Vector2(0f, -8f);
TextMeshProUGUI tmp = go.AddComponent<TextMeshProUGUI>();
tmp.font = uiFont != null ? uiFont : ResolveFont();
tmp.fontSize = 56f;
tmp.color = new Color(244f/255f, 240f/255f, 228f/255f, 1f);
tmp.alignment = TextAlignmentOptions.Center;
tmp.textWrappingMode = TextWrappingModes.NoWrap;
go.AddComponent<ArabicText>().SetText("واجبي العسكري");
Transform sub = top.Find("WajibyHomeSubtitle");
if (sub == null)
{
GameObject sg = new GameObject("WajibyHomeSubtitle"); sg.transform.SetParent(top,false);
RectTransform sr = sg.AddComponent<RectTransform>(); sr.anchorMin=new Vector2(.22f,0); sr.anchorMax=new Vector2(.78f,0.42f); sr.offsetMin=Vector2.zero; sr.offsetMax=Vector2.zero;
TextMeshProUGUI st = sg.AddComponent<TextMeshProUGUI>(); st.font=uiFont; st.fontSize=25; st.color=new Color(224f/255f, 232f/255f, 225f/255f, .78f); st.alignment=TextAlignmentOptions.Center; st.textWrappingMode=TextWrappingModes.NoWrap;
sg.AddComponent<ArabicText>().SetText("نظّم واجباتك • التزم بوقتك");
}
}
}
void EnsureHomeAccent(Transform top)
{
Transform t = top.Find("WajibyHomeAccent");
if (t == null)
{
GameObject go = new GameObject("WajibyHomeAccent");
go.transform.SetParent(top, false);
RectTransform rt = go.AddComponent<RectTransform>();
rt.anchorMin = new Vector2(.06f, 0f);
rt.anchorMax = new Vector2(.94f, 0f);
rt.pivot = new Vector2(.5f, 0f);
rt.sizeDelta = new Vector2(0f, 5f);
rt.anchoredPosition = Vector2.zero;
Image im = go.AddComponent<Image>();
im.color = Gold;
im.raycastTarget = false;
}
}
void AddOutline(GameObject go, Color color, float thickness)
{
if (go == null) return;
Outline o = go.GetComponent<Outline>();
if (o == null) o = go.AddComponent<Outline>();
o.effectColor = color;
o.effectDistance = new Vector2(thickness, thickness);
o.useGraphicAlpha = true;
}
void AddShadow(GameObject go, Color color, Vector2 distance)
{
if (go == null) return;
Shadow sh = go.GetComponent<Shadow>();
if (sh == null) sh = go.AddComponent<Shadow>();
sh.effectColor = color;
sh.effectDistance = distance;
sh.useGraphicAlpha = true;
}
void CleanupPassed()
{
long now = DateTime.Now.Ticks; var list = ProfileStore.Load(); bool changed = false;
foreach (var p in list) { if (p == null) continue;
if (p.alarms != null && p.alarms.RemoveAll(a => a == null || a.ticks < now) > 0) changed = true;
if (p.breakTicks != null && p.breakTicks.RemoveAll(b => b < now) > 0) changed = true; }
int before = list.Count; list.RemoveAll(p => p == null || p.alarms == null || p.alarms.Count == 0); if (list.Count != before) changed = true;
if (changed) ProfileStore.Save(list);
}
public void AddProfile()
{
if (wizard == null) wizard = UnityEngine.Object.FindAnyObjectByType<CreationWizard>(FindObjectsInactive.Include);
if (wizard == null) { wizard = WizardRuntimeBuilder.Build(transform, this, wizardWindow, wizardClose, wizardCheck, uiFont, switchOn, switchOff, editIcon); }
if (wizard != null) wizard.Ask(); // V65: no inline visibility call -- LateUpdate owns it
}
public void Rebuild()
{
if (cardParent == null) return;
CleanupPassed();
foreach (Transform t in cardParent) if (t.name == "Card") Destroy(t.gameObject);
if (uiFont == null) uiFont = ResolveFont();
Sprite panel = cardPanel != null ? cardPanel : wizardWindow;
List<Profile> list = ProfileStore.Load();
if (emptyHint != null) emptyHint.SetActive(list.Count == 0);
if (fab != null) fab.SetActive(list.Count < ProfileStore.MaxProfiles);
if (maxHint != null) maxHint.SetActive(list.Count >= ProfileStore.MaxProfiles);
float lastBottom = -CARD_TOP;
for (int i = 0; i < list.Count; i++)
{
GameObject go = CardRuntimeBuilder.Build(cardParent, uiFont, panel, cardId, cardPen, null, cardTrash, switchOn, switchOff);
RectTransform rt = go.GetComponent<RectTransform>(); float top = -CARD_TOP - i * CARD_SPACING; rt.anchoredPosition = new Vector2(0, top); lastBottom = top - CARD_H;
Fill(go, list[i], i); Wire(go, i, list[i]);
}
if (maxHint != null)
{
var mrt = maxHint.GetComponent<RectTransform>();
if (mrt != null)
{
float cw = cardParent != null && cardParent.rect.width > 200f ? cardParent.rect.width - 52f : 940f;
mrt.anchorMin = new Vector2(0.5f,1f); mrt.anchorMax = new Vector2(0.5f,1f); mrt.pivot = new Vector2(0.5f,1f);
mrt.sizeDelta = new Vector2(cw, 90f);
mrt.anchoredPosition = new Vector2(0f, lastBottom - HINT_GAP);
mrt.SetAsLastSibling();
}
}
AlarmSystem.ScheduleAll(list); lastRev = AlarmSystem.revision;
}
public void MoveProfile(int from, int to)
{
List<Profile> list = ProfileStore.Load(); if (from < 0 || from >= list.Count) { Rebuild(); return; }
to = Mathf.Clamp(to, 0, list.Count - 1); if (to != from) { Profile p = list[from]; list.RemoveAt(from); list.Insert(to, p); ProfileStore.Save(list); } Rebuild();
}
public void ToggleAlarm(int i) { List<Profile> list = ProfileStore.Load(); if (i < 0 || i >= list.Count) return; Profile p = list[i]; if (p == null || p.locked) return; p.alarmsActive = !p.alarmsActive; ProfileStore.Save(list); Rebuild(); }
public void DeleteProfile(int i) { List<Profile> list = ProfileStore.Load(); if (i < 0 || i >= list.Count) return; list.RemoveAt(i); ProfileStore.Save(list); Rebuild(); }
void TryUnlock(int index)
{
var list = ProfileStore.Load(); if (index < 0 || index >= list.Count) return; Profile p = list[index]; if (p == null || !p.locked) return;
AdBridge.ShowRewarded(
() => { p.adsWatched++; bool ju = false; if (p.adsWatched >= AdBridge.REQUIRED) { p.locked = false; p.alarmsActive = false; ju = true; } ProfileStore.Save(list); Rebuild(); Toast.Show(transform, ju ? "تم فتح الملف!" : ("تم مشاهدة إعلان " + p.adsWatched + "/" + AdBridge.REQUIRED), uiFont); },
() => { Toast.Show(transform, "تعذر عرض الإعلان، حاول لاحقا", uiFont); });
}
void Fill(GameObject card, Profile p, int index)
{
float cardW = card.GetComponent<RectTransform>().sizeDelta.x;
Transform nameTr = card.transform.Find("NameText");
if (nameTr != null)
{
var ntmp = nameTr.GetComponent<TextMeshProUGUI>(); if (ntmp != null && ntmp.font == null) ntmp.font = uiFont != null ? uiFont : ResolveFont();
var nat = nameTr.GetComponent<ArabicText>(); if (nat != null) nat.SetText(p.locked ? LOCK_PROMPT : (p.name ?? ""));
if (ntmp != null)
{
ntmp.ForceMeshUpdate();
float inner = cardW * 0.56f - 24f;
for (float f = 42f; f >= 26f; f -= 2f) { ntmp.fontSize = f; ntmp.ForceMeshUpdate(); if (ntmp.GetPreferredValues(ntmp.text).x <= inner) break; }
float nameW = ntmp.GetPreferredValues(ntmp.text).x;
var nrt = nameTr.GetComponent<RectTransform>();
if (nrt != null)
{
float rightAnchor = CardRuntimeBuilder.RX - 0.015f;
nrt.anchorMin = new Vector2(rightAnchor, nrt.anchorMin.y);
nrt.offsetMin = new Vector2(nrt.offsetMax.x - (nameW + 16f), nrt.offsetMin.y);
}
Transform pen = card.transform.Find("Btn_Edit");
if (pen != null) { pen.gameObject.SetActive(!p.locked); pen.GetComponent<RectTransform>().anchoredPosition = new Vector2(-(nameW + 50f), 0f); }
}
}
Transform infoTr = card.transform.Find("InfoText");
if (infoTr != null) { var iat = infoTr.GetComponent<ArabicText>(); if (iat != null) iat.SetText(DutyText(p)); FitInfo(card, infoTr); }
ApplyState(card, p);
BuildUnlock(card, p, index);
}
void FitInfo(GameObject card, Transform infoTr)
{
var itmp = infoTr.GetComponent<TextMeshProUGUI>(); if (itmp == null) return; if (itmp.font == null) itmp.font = uiFont != null ? uiFont : ResolveFont();
var irt = infoTr.GetComponent<RectTransform>(); if (irt == null) return;
float cardH = card.GetComponent<RectTransform>().sizeDelta.y, cardW = card.GetComponent<RectTransform>().sizeDelta.x;
irt.anchorMin = new Vector2(0.20f, 0.06f); irt.anchorMax = new Vector2(0.76f, 0.40f); irt.offsetMin = Vector2.zero; irt.offsetMax = Vector2.zero; irt.pivot = new Vector2(0.5f, 1f);
itmp.alignment = TextAlignmentOptions.TopRight; itmp.textWrappingMode = TextWrappingModes.Normal; itmp.lineSpacing = -2f;
float bandH = (0.40f - 0.06f) * cardH, bandW = (0.76f - 0.20f) * cardW;
for (float f = 28f; f >= 14f; f -= 2f) { itmp.fontSize = f; itmp.ForceMeshUpdate(); if (itmp.preferredHeight <= bandH && itmp.preferredWidth <= bandW) break; }
}
void ApplyState(GameObject card, Profile p)
{
bool on = p.alarmsActive && !p.locked;
card.transform.Find("Card_Frame").GetComponent<Image>().color = on ? Color.white : Dim;
var bd = card.transform.Find("Card_Border"); if (bd != null) bd.GetComponent<Image>().color = on ? Gold : Dim;
var idT = card.transform.Find("Icon_ID");
if (idT != null)
{
var iim = idT.GetComponent<Image>();
if (!on)
{
var g = CardRuntimeBuilder.IdGrey;
if (g != null) { iim.sprite = g; iim.color = Color.white; }
else iim.color = new Color(1f,1f,1f,0.40f);
}
}
foreach (string n in new[] { "Card_Divider", "Card_VDivider", "Card_VDividerLeft" }) { Transform d = card.transform.Find(n); if (d != null) d.GetComponent<Image>().color = on ? new Color(Gold.r,Gold.g,Gold.b,90f/255f) : new Color(Dim.r,Dim.g,Dim.b,90f/255f); }
var nt = card.transform.Find("NameText"); if (nt != null) nt.GetComponent<TextMeshProUGUI>().color = p.locked ? Paper : (on ? White : DimText);
card.transform.Find("InfoText").GetComponent<TextMeshProUGUI>().color = on ? InfoGray : DimText;
Transform pe = card.transform.Find("Btn_Edit"); if (pe != null) { Image im = pe.GetComponent<Image>(); if (im != null) im.color = new Color(40f/255f,83f/255f,57f/255f,1f); }
Transform sc = card.transform.Find("Btn_Scroll"); if (sc != null) { Image im = sc.GetComponent<Image>(); if (im != null) im.color = new Color(40f/255f,83f/255f,57f/255f,1f); }
Transform tr = card.transform.Find("Btn_Trash"); if (tr != null) { Image im = tr.GetComponent<Image>(); if (im != null) im.color = new Color(190f/255f,65f/255f,58f/255f,1f); }
}
void BuildUnlock(GameObject card, Profile p, int index)
{
Transform ex = card.transform.Find("Btn_Unlock");
if (!p.locked) { if (ex != null) ex.gameObject.SetActive(false); return; }
GameObject go;
if (ex != null) go = ex.gameObject;
else
{
go = new GameObject("Btn_Unlock"); go.transform.SetParent(card.transform, false);
var grt = go.AddComponent<RectTransform>(); grt.anchorMin = new Vector2(CardRuntimeBuilder.RX, 0f); grt.anchorMax = new Vector2(1f, CardRuntimeBuilder.MIDY); grt.pivot = new Vector2(.5f,.5f); grt.offsetMin = Vector2.zero; grt.offsetMax = Vector2.zero;
var hit = go.AddComponent<Image>(); hit.color = new Color(0, 0, 0, 0); hit.raycastTarget = true;
var sq = new GameObject("Play"); sq.transform.SetParent(go.transform, false);
var srt = sq.AddComponent<RectTransform>(); srt.anchorMin = new Vector2(.5f,.60f); srt.anchorMax = new Vector2(.5f,.60f); srt.pivot = new Vector2(.5f,.5f); srt.sizeDelta = new Vector2(84,84); srt.anchoredPosition = Vector2.zero;
var sim = sq.AddComponent<Image>(); Sprite ps = cardPlay != null ? cardPlay : CardRuntimeBuilder.Res("play"); sim.sprite = ps != null ? ps : PlaySprite(); sim.color = Color.white; sim.preserveAspect = true; sim.raycastTarget = false;
var ct = new GameObject("Count"); ct.transform.SetParent(go.transform, false);
var crt = ct.AddComponent<RectTransform>(); crt.anchorMin = new Vector2(.5f,.20f); crt.anchorMax = new Vector2(.5f,.20f); crt.pivot = new Vector2(.5f,.5f); crt.sizeDelta = new Vector2(200,46); crt.anchoredPosition = Vector2.zero;
var ctmp = ct.AddComponent<TextMeshProUGUI>(); ctmp.font = uiFont != null ? uiFont : ResolveFont(); ctmp.fontSize = 40; ctmp.color = Accent; ctmp.alignment = TextAlignmentOptions.Center; ctmp.raycastTarget = false; ctmp.textWrappingMode = TextWrappingModes.NoWrap;
}
var cnt = go.transform.Find("Count"); if (cnt != null) cnt.GetComponent<TextMeshProUGUI>().text = "(" + p.adsWatched + "/" + AdBridge.REQUIRED + ")";
go.SetActive(true); go.transform.SetAsLastSibling();
Button ob = go.GetComponent<Button>(); if (ob != null) UnityEngine.Object.Destroy(ob);
SafeTap st = go.GetComponent<SafeTap>(); if (st == null) st = go.AddComponent<SafeTap>();
int idx = index; st.onTap = () => TryUnlock(idx);
}
static Sprite PlaySprite()
{
if (_playSprite != null) return _playSprite;
int S = 128, rad = 30; var tex = new Texture2D(S, S, TextureFormat.RGBA32, false); Color clear = new Color(0,0,0,0);
Vector2 ctr = new Vector2(S/2f,S/2f), half = new Vector2(S/2f-4f,S/2f-4f), a = new Vector2(40,38), b = new Vector2(40,90), c = new Vector2(96,64);
for (int y=0;y<S;y++)for(int x=0;x<S;x++){ Vector2 pp=new Vector2(x+.5f,y+.5f); if(!InsideRound(pp,ctr,half,rad)){tex.SetPixel(x,y,clear);continue;} tex.SetPixel(x,y,InTri(pp,a,b,c)?Color.white:PlayOrange);} tex.Apply();
_playSprite = Sprite.Create(tex,new Rect(0,0,S,S),new Vector2(.5f,.5f),100f); return _playSprite;
}
static bool InsideRound(Vector2 p, Vector2 ctr, Vector2 half, float r){ Vector2 q=new Vector2(Mathf.Abs(p.x-ctr.x),Mathf.Abs(p.y-ctr.y))-(half-new Vector2(r,r)); float ox=Mathf.Max(q.x,0f),oy=Mathf.Max(q.y,0f); return Mathf.Sqrt(ox*ox+oy*oy)+Mathf.Min(Mathf.Max(q.x,q.y),0f)-r<=0f; }
static bool InTri(Vector2 p, Vector2 a, Vector2 b, Vector2 c){ float d1=Sign(p,a,b),d2=Sign(p,b,c),d3=Sign(p,c,a); bool neg=(d1<0)||(d2<0)||(d3<0),pos=(d1>0)||(d2>0)||(d3>0); return !(neg&&pos); }
static float Sign(Vector2 p1, Vector2 p2, Vector2 p3){ return (p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y); }
void Wire(GameObject card, int index, Profile p)
{
bool locked = p.locked;
UnityEngine.Events.UnityAction openRename = () => { renameIndex = index; EnsureHomeRename(); if (homeRename != null) homeRename.Show(p.name); };
UnityEngine.Events.UnityAction inert = () => { };
Transform sw = card.transform.Find("Btn_Switch"); Image swImg = sw.GetComponent<Image>();
Sprite son = switchOn != null ? switchOn : CardRuntimeBuilder.Res("switch-on");
Sprite soff = switchOff != null ? switchOff : CardRuntimeBuilder.Res("switch-off");
swImg.sprite = p.alarmsActive ? son : soff;
Button sb = sw.GetComponent<Button>(); if (sb == null) sb = sw.gameObject.AddComponent<Button>(); sb.transition = Selectable.Transition.None; sb.onClick.RemoveAllListeners();
int swIdx = index; sb.onClick.AddListener(() => { if (!ProfileStore.Load()[swIdx].locked) ToggleAlarm(swIdx); });
Transform sc = card.transform.Find("Btn_Scroll"); if (sc != null) { Button ob = sc.GetComponent<Button>(); if (ob != null) UnityEngine.Object.Destroy(ob); var pd = sc.gameObject.AddComponent<ProfileDrag>(); pd.card = card.GetComponent<RectTransform>(); pd.spacing = CARD_SPACING; pd.index = index; Canvas cv = card.GetComponentInParent<Canvas>(); pd.scale = cv != null ? cv.scaleFactor : 1f; pd.onMove = (f, t) => MoveProfile(f, t); }
Transform tr = card.transform.Find("Btn_Trash"); Button tb = tr.GetComponent<Button>(); if (tb == null) tb = tr.gameObject.AddComponent<Button>(); tb.transition = Selectable.Transition.None; tb.onClick.RemoveAllListeners(); tb.onClick.AddListener(() => { if (confirm != null) confirm.Ask(index); else DeleteProfile(index); });
Transform pe = card.transform.Find("Btn_Edit"); if (pe != null) { Button pb = pe.GetComponent<Button>(); if (pb != null) UnityEngine.Object.Destroy(pb); var st = pe.gameObject.AddComponent<SafeTap>(); st.onTap = locked ? inert : openRename; }
Transform nt = card.transform.Find("NameText"); if (nt != null) { var st2 = nt.gameObject.AddComponent<SafeTap>(); st2.onTap = locked ? inert : openRename; }
Button cb = card.GetComponent<Button>(); if (cb == null) cb = card.AddComponent<Button>(); cb.transition = Selectable.Transition.None; cb.onClick.RemoveAllListeners();
int cbIdx = index; cb.onClick.AddListener(() => { if (locked) return; EnsureAlarmWindow(); if (alarmWindow != null) alarmWindow.Show(cbIdx); }); // V65: no inline visibility call
}
void EnsureHomeRename()
{
if (homeRename == null) { homeRename = RenamePanel.Build(transform, transform, uiFont, wizardWindow, wizardCheck, wizardClose, s => { if (s == null) return; var list = ProfileStore.Load(); if (renameIndex >= 0 && renameIndex < list.Count && list[renameIndex] != null) { list[renameIndex].name = s; ProfileStore.Save(list); Rebuild(); } }); }
}
void EnsureAlarmWindow()
{
if (alarmWindow == null) {
Sprite trashSpr = cardTrash != null ? cardTrash : CardRuntimeBuilder.TrashGlyph;
Sprite penSpr = cardPen != null ? cardPen : CardRuntimeBuilder.Res("edit_white");
alarmWindow = AlarmWindow.Build(transform, this, wizardWindow, null, switchOn, trashSpr, uiFont, penSpr);
}
}
string DutyText(Profile p)
{
if (p.alarms == null) p.alarms = new List<DutyAlarm>(); if (p.breakTicks == null) p.breakTicks = new List<long>();
long now = DateTime.Now.Ticks; int totD = p.alarms.Count, remD = 0; long next = 0;
foreach (var a in p.alarms) if (a != null && a.active && a.ticks >= now) { remD++; if (next == 0) next = a.ticks; }
int totDH = totD * p.dutyHours, remDH = remD * p.dutyHours;
int totB = p.breakTicks.Count, remB = 0; foreach (long b in p.breakTicks) if (b >= now) remB++;
int totBH = totB * p.restHours, remBH = remB * p.restHours;
string nextLine = (next == 0) ? DutyScheduler.NextHeading() + DutyScheduler.NoneValue() : DutyScheduler.NextHeading() + DutyScheduler.DayPart(new DateTime(next)) + DutyScheduler.Time12(new DateTime(next), true);
string lastLine = "";
if (p.useLastDuty) { long lt = 0; foreach (var a in p.alarms) if (a != null && a.isLast) lt = a.ticks; if (lt > 0) lastLine = "\n" + DutyScheduler.LastHeading() + DutyScheduler.DayPart(new DateTime(lt)) + DutyScheduler.Time12(new DateTime(lt), true); }
return nextLine + lastLine + "\n" + DutyScheduler.DutiesLine(totD, totDH, remD, remDH) + "\n" + DutyScheduler.RestsLine(totB, totBH, remB, remBH);
}
static Transform FindDeep(Transform root, string name) { foreach (Transform child in root) { if (child.name == name) return child; Transform deeper = FindDeep(child, name); if (deeper != null) return deeper; } return null; }
}
// END-WAJIBY-HOME-V68_COMMANDUI
73textScripts/HomeManager.cs.meta
fileFormatVersion: 2
guid: e2cd8866fe0d09d43bf482fd939a738e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- cardPrefab: {instanceID: 0}
- switchOn: {fileID: 21300000, guid: 848b98a92f9c49741bb082b95d68ef88, type: 3}
- switchOff: {fileID: 21300000, guid: dc7a89b33a451844db11e5008d4d91b1, type: 3}
- wizardWindow: {fileID: 21300000, guid: 4c50a3ca8aa402245b3b6a3162308c33, type: 3}
- wizardClose: {fileID: 21300000, guid: 92edd783ca4672d469f4826be5f66b83, type: 3}
- wizardCheck: {fileID: 21300000, guid: d576e4d0afc95134489dfabf4f5a1985, type: 3}
- editIcon: {fileID: 21300000, guid: f15d4c528aa01014092afd21e66e5fa6, type: 3}
- scrollIcon: {fileID: 21300000, guid: bc9544f0dc4baaf418dd43cfad092140, type: 3}
- cardPanel: {instanceID: 0}
- cardId: {fileID: 21300000, guid: c1ff43de4e5795747a4d3cdbf7781546, type: 3}
- cardTrash: {fileID: 21300000, guid: 90b1fa1dd3c5ba94d95f8109e8f165be, type: 3}
- cardPen: {fileID: 21300000, guid: f15d4c528aa01014092afd21e66e5fa6, type: 3}
- cardPlay: {fileID: 21300000, guid: 1ff724e61142abd44b29e28adf0b1f96, type: 3}
- confirm: {instanceID: 0}
- wizard: {instanceID: 0}
- alarmWindow: {instanceID: 0}
- permissionsButton: {instanceID: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
74textScripts/KeepAlive.cs
// CANARY: WAJIBY_KEEPALIVE_DRIVER_V1
using UnityEngine;
using System;
using System.Collections.Generic;
public static class KeepAlive
{
const string SVC = "com.knoxprojects.wajiby.app.KeepAliveService";
static AndroidJavaObject _ctx;
static bool _on;
static float _t = -10f;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
if (UnityEngine.Object.FindAnyObjectByType<Drv>() == null) new GameObject("KeepAliveDrv").AddComponent<Drv>();
Init();
Evaluate();
}
class Drv : MonoBehaviour { void Update() { if (Time.time - _t > 15f) { _t = Time.time; Evaluate(); } } void OnApplicationFocus(bool f) { if (f) Evaluate(); } }
static void Init()
{
#if UNITY_ANDROID && !UNITY_EDITOR
if (_ctx == null)
{
try { using (var up = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) _ctx = up.GetStatic<AndroidJavaObject>("currentActivity"); }
catch { }
}
#endif
}
// Decoupled from AlarmSystem: decides purely from the stored profiles whether a future alarm exists.
static bool NeedAlive()
{
long now = DateTime.Now.Ticks;
List<Profile> list;
try { list = ProfileStore.Load(); } catch { return _on; }
foreach (var p in list)
{
if (p == null || !p.alarmsActive || p.alarms == null) continue;
foreach (var a in p.alarms)
{
if (a == null || !a.active) continue;
if (a.ticks > now) return true;
if (a.reminderTicks > now && !a.reminderLocked) return true;
if (a.snoozedTicks > now) return true;
}
}
return false;
}
static void Evaluate()
{
#if UNITY_ANDROID && !UNITY_EDITOR
Init(); if (_ctx == null) return;
bool need = NeedAlive();
if (need && !_on) { Start(); _on = true; }
else if (!need && _on) { Stop(); _on = false; }
#endif
}
static void Start()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try
{
int sdk; using (var v = new AndroidJavaClass("android.os.Build$VERSION")) sdk = v.GetStatic<int>("SDK_INT");
using (var cls = new AndroidJavaClass(SVC))
using (var intent = new AndroidJavaObject("android.content.Intent", _ctx, cls))
{
if (sdk >= 26) _ctx.Call("startForegroundService", intent);
else _ctx.Call("startService", intent);
}
}
catch (Exception e) { Debug.LogWarning("KeepAlive start: " + e.Message); }
#endif
}
static void Stop()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try { using (var cls = new AndroidJavaClass(SVC)) using (var intent = new AndroidJavaObject("android.content.Intent", _ctx, cls)) _ctx.Call("stopService", intent); }
catch { }
#endif
}
}75textScripts/KeepAlive.cs.meta
fileFormatVersion: 2 guid: 6d34ed68c463c5f4ea37cfab91e0e2c4
76textScripts/LayoutFixer.cs
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
public static class LayoutFixer
{
[MenuItem("Tools/Fix Detail Layout")]
static void Fix()
{
var hm = Object.FindAnyObjectByType<HomeManager>();
if (hm == null) { Debug.LogError("HomeManager not found"); return; }
Transform root = hm.transform;
Apply(root, "DetailRoot", 0, 0, 1, 1, 0, 0, 0, 0);
Apply(root, "Detail_Frame", .5f, .5f, .5f, .5f, 0, 100, 700, 700);
Apply(root, "Detail_ID", .5f, .5f, .5f, .5f, 0, 380, 200, 140);
Apply(root, "Detail_Name", .5f, .5f, .5f, .5f, 0, 240, 600, 80);
Apply(root, "Detail_Divider", .5f, .5f, .5f, .5f, 0, 180, 560, 2);
Apply(root, "Detail_Info", .5f, .5f, .5f, .5f, 0, 20, 620, 200);
Apply(root, "Btn_Switch", 0, 1, 0, 1, 70, -70, 140, 70);
Apply(root, "Btn_Trash", 0, 0, 0, 0, 70, 70, 90, 90);
Transform dr = FindDeep(root, "DetailRoot");
if (dr != null)
{
var img = dr.GetComponent<UnityEngine.UI.Image>();
if (img != null) img.color = new Color(20f / 255f, 26f / 255f, 22f / 255f, 1f);
dr.gameObject.SetActive(false);
}
Debug.Log("Detail layout fixed.");
}
static void Apply(Transform root, string name,
float ax0, float ay0, float ax1, float ay1,
float px, float py, float w, float h)
{
Transform t = FindDeep(root, name);
if (t == null) { Debug.LogWarning("Missing: " + name); return; }
RectTransform rt = t.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(ax0, ay0);
rt.anchorMax = new Vector2(ax1, ay1);
rt.pivot = new Vector2(0.5f, 0.5f);
rt.sizeDelta = new Vector2(w, h);
rt.anchoredPosition = new Vector2(px, py);
}
static Transform FindDeep(Transform t, string n)
{
foreach (Transform c in t)
{
if (c.name == n) return c;
Transform d = FindDeep(c, n);
if (d != null) return d;
}
return null;
}
}
#endif77textScripts/LayoutFixer.cs.meta
fileFormatVersion: 2 guid: bc8576783ef5c6e4e946730535ecf657
78textScripts/MainMenuDrawer.cs
// CANARY: WAJIBY_DRAWER_V7_FINAL
using UnityEngine;
using UnityEngine.UI;
public class MainMenuDrawer : MonoBehaviour
{
public RectTransform drawerPanel;
public CanvasGroup backdrop;
public Button menuButton;
void Awake()
{
if(drawerPanel==null){var t=FindDeep(transform,"DrawerPanel"); if(t)drawerPanel=t.GetComponent<RectTransform>();}
if(backdrop==null){var t=FindDeep(transform,"Backdrop"); if(t)backdrop=t.GetComponent<CanvasGroup>();}
if(menuButton==null){var t=FindDeep(transform,"MenuButton"); if(t)menuButton=t.GetComponent<Button>();}
// SIDEBAR REMOVED: hide the panel, the scrim and the hamburger; bind nothing. The gear
// (a separate home-screen button) is wired to Settings inside HomeManager.WireMenu.
if(menuButton!=null){ menuButton.onClick.RemoveAllListeners(); menuButton.gameObject.SetActive(false); }
if(drawerPanel!=null) drawerPanel.gameObject.SetActive(false);
if(backdrop!=null) backdrop.gameObject.SetActive(false);
}
public void SetOpen(bool open,bool instant=false){} // no-op stub: nothing should drive the drawer any more
static Transform FindDeep(Transform t,string n){ foreach(Transform c in t){ if(c.name==n)return c; var d=FindDeep(c,n); if(d!=null)return d; } return null; }
}
// END-WAJIBY-DRAWER-V7_FINAL79textScripts/MainMenuDrawer.cs.meta
fileFormatVersion: 2 guid: ac0e008b7c6dbdc43acb32e9288dd9a8
80textScripts/ModalChromeFix.cs
// CANARY: WAJIBY_MODALCHROME_V2_RETIRED
// Modal styling is applied by the actual builders; no runtime scanner is used.
public static class ModalChromeFix { }
// END-WAJIBY-MODALCHROME-V2
81textScripts/ModalChromeFix.cs.meta
fileFormatVersion: 2 guid: 7601a037ae69e3f40b49fd15805e38d6
82textScripts/NativePickers.cs
using System;
using UnityEngine;
using UnityEngine.Scripting;
public static class NativePickers
{
public static bool Supported => Application.platform == RuntimePlatform.Android;
[Preserve]
class DateListener : AndroidJavaProxy
{
Action<int, int, int> cb;
public DateListener(Action<int, int, int> cb)
: base("android.app.DatePickerDialog$OnDateSetListener") { this.cb = cb; }
[Preserve]
void onDateSet(AndroidJavaObject view, int year, int monthOfYear, int dayOfMonth)
{ cb(year, monthOfYear + 1, dayOfMonth); }
}
[Preserve]
class TimeListener : AndroidJavaProxy
{
Action<int, int> cb;
public TimeListener(Action<int, int> cb)
: base("android.app.TimePickerDialog$OnTimeSetListener") { this.cb = cb; }
[Preserve]
void onTimeSet(AndroidJavaObject view, int hourOfDay, int minute)
{ cb(hourOfDay, minute); }
}
public static void PickDate(int y, int m, int d, Action<int, int, int> onPicked)
{
try
{
var unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unity.GetStatic<AndroidJavaObject>("currentActivity");
activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
var dlg = new AndroidJavaObject("android.app.DatePickerDialog",
activity, new DateListener(onPicked), y, m - 1, d);
dlg.Call("show");
}));
}
catch (Exception e) { Debug.LogError("Date picker failed: " + e.Message); }
}
public static void PickTime(int h, int min, Action<int, int> onPicked)
{
try
{
var unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unity.GetStatic<AndroidJavaObject>("currentActivity");
activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
var dlg = new AndroidJavaObject("android.app.TimePickerDialog",
activity, new TimeListener(onPicked), h, min, true);
dlg.Call("show");
}));
}
catch (Exception e) { Debug.LogError("Time picker failed: " + e.Message); }
}
}83textScripts/NativePickers.cs.meta
fileFormatVersion: 2 guid: 59113de90b3bacb43bd8f673847558c2
84textScripts/Profile.cs
// CANARY: WAJIBY_PROFILE_V31_LOCK
using System;
using System.Collections.Generic;
[Serializable]
public class DutyAlarm
{
public long uid;
public long ticks;
public long reminderTicks;
public long snoozedTicks;
public bool reminderLocked;
public bool isLast;
public bool active = true;
}
[Serializable]
public class Profile
{
public string name = "الملف الشخصي";
public int shiftDays = 1;
public int dutyHours = 24;
public int restHours = 0;
public long firstShiftTicks = 0;
public string firstDutyTime = "09:00";
public bool useLastDuty = false;
public string lastDutyTime = "09:00";
public int reminderMinutes = 0;
public bool alarmsActive = true;
// AD LOCK: a freshly created profile is locked + disabled until REQUIRED ads are watched.
// Defaults are unlocked/0 so any profile saved by an older version loads UNLOCKED (no roster lost on update).
public bool locked = false;
public int adsWatched = 0;
public List<DutyAlarm> alarms = new List<DutyAlarm>();
public List<long> breakTicks = new List<long>();
}
// END-WAJIBY-PROFILE-V31_LOCK85textScripts/Profile.cs.meta
fileFormatVersion: 2 guid: ee50a71d1a597d64fa2a1297abf9eda4
86textScripts/ProfileDrag.cs
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class ProfileDrag : MonoBehaviour,
IPointerDownHandler, IDragHandler, IPointerUpHandler, IPointerClickHandler
{
public RectTransform card;
public float spacing = 480f;
public int index;
public float scale = 1f;
public UnityEngine.Events.UnityAction<int, int> onMove;
bool drag;
float startY, baseY;
ScrollRect parentScroll;
public void OnPointerDown(PointerEventData e)
{
drag = true;
startY = e.position.y;
baseY = card.anchoredPosition.y;
card.SetAsLastSibling();
parentScroll = card.GetComponentInParent<ScrollRect>();
if (parentScroll != null) parentScroll.enabled = false;
}
public void OnDrag(PointerEventData e)
{
if (!drag) return;
float dy = (e.position.y - startY) / Mathf.Max(scale, 0.001f);
card.anchoredPosition = new Vector2(card.anchoredPosition.x, baseY + dy);
}
public void OnPointerUp(PointerEventData e)
{
if (!drag) return;
drag = false;
if (parentScroll != null) parentScroll.enabled = true;
// dragging UP (dy > 0) means moving to an EARLIER index
float dy = card.anchoredPosition.y - baseY;
int di = Mathf.RoundToInt(-dy / spacing);
if (onMove != null) onMove.Invoke(index, index + di);
}
public void OnPointerClick(PointerEventData e) { }
}87textScripts/ProfileDrag.cs.meta
fileFormatVersion: 2 guid: a2639c8ebe92b1048ab73cda610c2645
88textScripts/ProfileStore.cs
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public static class ProfileStore
{
public const int MaxProfiles = 3;
static string FilePath => Path.Combine(Application.persistentDataPath, "profiles.json");
public static List<Profile> Load()
{
List<Profile> list;
if (!File.Exists(FilePath)) list = new List<Profile>();
else
{
var w = JsonUtility.FromJson<Wrapper>(File.ReadAllText(FilePath));
list = (w != null && w.items != null) ? w.items : new List<Profile>();
}
foreach (var p in list)
{
if (p == null) continue;
if (p.alarms == null) p.alarms = new List<DutyAlarm>();
if (p.breakTicks == null) p.breakTicks = new List<long>();
if (string.IsNullOrEmpty(p.name)) p.name = "الملف الشخصي";
}
list.RemoveAll(p => p == null);
return list;
}
public static void Save(List<Profile> profiles)
{
File.WriteAllText(FilePath, JsonUtility.ToJson(new Wrapper { items = profiles }));
}
public static void ClearAll()
{
if (File.Exists(FilePath)) File.Delete(FilePath);
}
[Serializable]
class Wrapper { public List<Profile> items; }
#if UNITY_EDITOR
[UnityEditor.MenuItem("Tools/Clear Profiles Save")]
static void EditorClear() => ClearAll();
#endif
}89textScripts/ProfileStore.cs.meta
fileFormatVersion: 2 guid: 15d3ff1807eb4dc4eb4acd9734481564
90textScripts/RenamePanel.cs
// CANARY: WAJIBY_RENAME_V4_COMMANDUI
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class RenamePanel : MonoBehaviour
{
TMP_InputField input; TextMeshProUGUI view; Image caretImg; TMP_FontAsset font; Transform toastRoot;
System.Action<string> onApply; bool suppress; TouchScreenKeyboard kb;
static readonly Color White = new Color(244f/255f,240f/255f,228f/255f,1f);
static readonly Color Paper = new Color(244f/255f,240f/255f,228f/255f,1f);
static readonly Color Gold = new Color(213f/255f,178f/255f,74f/255f,1f);
static readonly Color Green = new Color(82f/255f,145f/255f,91f/255f,1f);
static readonly Color Body = new Color(30f/255f,46f/255f,36f/255f,1f);
static Sprite _panel;
public static RenamePanel Build(Transform parent, Transform toastRoot, TMP_FontAsset font, Sprite win, Sprite check, Sprite close, System.Action<string> onApply)
{
GameObject root = new GameObject("RenameRoot"); root.transform.SetParent(parent,false);
RectTransform rt = root.AddComponent<RectTransform>(); rt.anchorMin=Vector2.zero; rt.anchorMax=Vector2.one; rt.offsetMin=Vector2.zero; rt.offsetMax=Vector2.zero;
var bg = root.AddComponent<Image>(); bg.color = new Color(5f/255f,10f/255f,7f/255f,185f/255f); bg.raycastTarget = true;
GameObject frame = new GameObject("Rename_Frame"); frame.transform.SetParent(root.transform,false);
RectTransform frt = frame.AddComponent<RectTransform>(); frt.anchorMin=new Vector2(.5f,.5f); frt.anchorMax=new Vector2(.5f,.5f); frt.pivot=new Vector2(.5f,.5f); frt.sizeDelta=new Vector2(720,430); frt.anchoredPosition=Vector2.zero;
Image fim = frame.AddComponent<Image>(); fim.sprite=PanelSprite(); fim.type=Image.Type.Sliced; fim.color=Color.white;
GameObject ttl = new GameObject("Title"); ttl.transform.SetParent(frame.transform,false);
RectTransform trt = ttl.AddComponent<RectTransform>(); trt.anchorMin=new Vector2(0,1); trt.anchorMax=new Vector2(1,1); trt.offsetMin=new Vector2(50,-90); trt.offsetMax=new Vector2(-50,-20);
var ttmp = ttl.AddComponent<TextMeshProUGUI>(); ttmp.fontSize=52; ttmp.color=Green; ttmp.alignment=TextAlignmentOptions.Right; if(font!=null) ttmp.font=font;
ttl.AddComponent<ArabicText>().SetText("إعادة التسمية");
GameObject body = new GameObject("Rename_Body"); body.transform.SetParent(frame.transform,false);
RectTransform brt = body.AddComponent<RectTransform>(); brt.anchorMin=Vector2.zero; brt.anchorMax=Vector2.one; brt.offsetMin=new Vector2(40,30); brt.offsetMax=new Vector2(-40,-100);
var bim = body.AddComponent<Image>(); bim.color=Body; bim.raycastTarget=true;
GameObject f = new GameObject("NameField"); f.transform.SetParent(body.transform,false);
RectTransform frt2 = f.AddComponent<RectTransform>(); frt2.anchorMin=Vector2.zero; frt2.anchorMax=Vector2.one; frt2.offsetMin=new Vector2(20,120); frt2.offsetMax=new Vector2(-20,-60);
var fimg = f.AddComponent<Image>(); fimg.color=new Color(0,0,0,0); fimg.raycastTarget=true;
GameObject raw = new GameObject("Raw"); raw.transform.SetParent(f.transform,false);
RectTransform rrt2 = raw.AddComponent<RectTransform>(); rrt2.anchorMin=Vector2.zero; rrt2.anchorMax=Vector2.one; rrt2.offsetMin=new Vector2(10,5); rrt2.offsetMax=new Vector2(-10,-5);
var rtmp = raw.AddComponent<TextMeshProUGUI>(); rtmp.fontSize=38; rtmp.color=new Color(0,0,0,0); rtmp.alignment=TextAlignmentOptions.Center; rtmp.raycastTarget=false; if(font!=null) rtmp.font=font;
GameObject vw = new GameObject("View"); vw.transform.SetParent(f.transform,false);
RectTransform vrt = vw.AddComponent<RectTransform>(); vrt.anchorMin=Vector2.zero; vrt.anchorMax=Vector2.one; vrt.offsetMin=new Vector2(10,5); vrt.offsetMax=new Vector2(-10,-5);
var vtmp = vw.AddComponent<TextMeshProUGUI>(); vtmp.fontSize=40; vtmp.color=Paper; vtmp.alignment=TextAlignmentOptions.Center; vtmp.raycastTarget=false; if(font!=null) vtmp.font=font;
GameObject caret = new GameObject("Caret"); caret.transform.SetParent(f.transform,false);
RectTransform cart = caret.AddComponent<RectTransform>(); cart.anchorMin=new Vector2(.5f,.5f); cart.anchorMax=new Vector2(.5f,.5f); cart.pivot=new Vector2(.5f,.5f); cart.sizeDelta=new Vector2(3,44); cart.anchoredPosition=Vector2.zero;
var cim = caret.AddComponent<Image>(); cim.color=White; cim.raycastTarget=false;
var rp = root.AddComponent<RenamePanel>();
var fld = f.AddComponent<TMP_InputField>(); fld.textViewport=rrt2; fld.textComponent=rtmp; fld.characterLimit=9; fld.shouldHideMobileInput=true; fld.contentType=TMP_InputField.ContentType.Standard;
rp.input=fld; rp.view=vtmp; rp.caretImg=cim; rp.font=font; rp.toastRoot=toastRoot; rp.onApply=onApply;
fld.onValueChanged.AddListener(rp.Filter); fld.onSelect.AddListener(s=>rp.OpenKeyboard(fld.text)); fld.onDeselect.AddListener(s=>rp.CloseKeyboard());
GameObject line = new GameObject("Line"); line.transform.SetParent(body.transform,false);
RectTransform lrt = line.AddComponent<RectTransform>(); lrt.anchorMin=new Vector2(.5f,0); lrt.anchorMax=new Vector2(.5f,0); lrt.pivot=new Vector2(.5f,.5f); lrt.sizeDelta=new Vector2(340,3); lrt.anchoredPosition=new Vector2(0,110);
var lim = line.AddComponent<Image>(); lim.color=Gold; lim.raycastTarget=false;
GameObject ok = new GameObject("Btn_Ok"); ok.transform.SetParent(body.transform,false);
RectTransform ort = ok.AddComponent<RectTransform>(); ort.anchorMin=new Vector2(1,0); ort.anchorMax=new Vector2(1,0); ort.pivot=new Vector2(.5f,.5f); ort.sizeDelta=new Vector2(90,90); ort.anchoredPosition=new Vector2(-110,55);
Image oim = ok.AddComponent<Image>(); oim.sprite=check; oim.preserveAspect=true;
GameObject no = new GameObject("Btn_No"); no.transform.SetParent(body.transform,false);
RectTransform nrt = no.AddComponent<RectTransform>(); nrt.anchorMin=new Vector2(0,0); nrt.anchorMax=new Vector2(0,0); nrt.pivot=new Vector2(.5f,.5f); nrt.sizeDelta=new Vector2(90,90); nrt.anchoredPosition=new Vector2(110,55);
Image nim = no.AddComponent<Image>(); nim.sprite=close; nim.preserveAspect=true;
var b1=ok.AddComponent<Button>(); b1.transition=Selectable.Transition.None; b1.onClick.AddListener(rp.Apply);
var b2=no.AddComponent<Button>(); b2.transition=Selectable.Transition.None; b2.onClick.AddListener(rp.Hide);
root.SetActive(false); return rp;
}
static Sprite PanelSprite()
{
if (_panel != null) return _panel;
const int S=128; Texture2D tex=new Texture2D(S,S,TextureFormat.RGBA32,false); tex.filterMode=FilterMode.Bilinear;
Vector2 c=new Vector2(S/2f,S/2f), h=new Vector2(S/2f-1,S/2f-1);
for(int y=0;y<S;y++) for(int x=0;x<S;x++)
{
Vector2 q=new Vector2(Mathf.Abs(x+.5f-c.x),Mathf.Abs(y+.5f-c.y))-(h-new Vector2(26,26));
float ox=Mathf.Max(q.x,0), oy=Mathf.Max(q.y,0); float d=Mathf.Sqrt(ox*ox+oy*oy)+Mathf.Min(Mathf.Max(q.x,q.y),0)-26;
Color col=d<=0?White:new Color(0,0,0,0); tex.SetPixel(x,y,col);
}
tex.Apply(); _panel=Sprite.Create(tex,new Rect(0,0,S,S),new Vector2(.5f,.5f),100f,0,SpriteMeshType.FullRect,new Vector4(30,30,30,30)); return _panel;
}
public void OpenKeyboard(string text){ if(kb==null) kb=TouchScreenKeyboard.Open(text,TouchScreenKeyboardType.Default,false,false,false,false); }
public void CloseKeyboard(){ if(kb!=null){ kb.active=false; kb=null; } }
void Update()
{
if(Input.GetKeyDown(KeyCode.Escape)){ Hide(); return; }
RectTransform fr = transform.Find("Rename_Frame") as RectTransform;
if(fr!=null){ float target=(kb!=null||TouchScreenKeyboard.visible)?300f:0f; if(Mathf.Abs(fr.anchoredPosition.y-target)>1f) fr.anchoredPosition=new Vector2(0,target); }
if(kb!=null && input!=null){ if(!input.isFocused) CloseKeyboard(); else { if(kb.text!=input.text){ suppress=true; input.text=kb.text; suppress=false; Filter(input.text);} if(kb.status==TouchScreenKeyboard.Status.Done||kb.status==TouchScreenKeyboard.Status.Canceled) CloseKeyboard(); } }
if(caretImg!=null && view!=null){ bool focused=input!=null&&input.isFocused; float w=view.GetPreferredValues(view.text).x; caretImg.transform.localPosition=new Vector3(-(w/2f)-4,caretImg.transform.localPosition.y,0f); Color c=caretImg.color; c.a=focused&&(Time.time%1f<0.5f)?1f:0f; caretImg.color=c; }
}
public void Show(string current){ suppress=true; input.text=current??""; suppress=false; view.text=ArabicShaper.Shape(input.text); transform.SetAsLastSibling(); gameObject.SetActive(true); }
public void Hide(){ CloseKeyboard(); gameObject.SetActive(false); }
void Filter(string s)
{
if(suppress) return;
var sb=new System.Text.StringBuilder();
foreach(char c in s){ bool letter=c>='\u0621'&&c<='\u064A'; bool digit=(c>='0'&&c<='9')||(c>='\u0660'&&c<='\u0669'); bool az=(c>='a'&&c<='z')||(c>='A'&&c<='Z'); if(letter||digit||az||c==' ') sb.Append(c); }
string t=sb.ToString(); if(t.Length>9) t=t.Substring(0,9); if(t!=s){ suppress=true; input.text=t; suppress=false; } view.text=ArabicShaper.Shape(t);
}
void Apply()
{
string v=input.text??""; if(v.Length>0&&v.Trim().Length==0){ Toast.Show(toastRoot,"الاسم غير صالح",font); return; }
string trimmed=v.Trim(); onApply?.Invoke(trimmed.Length==0?null:trimmed); Hide();
}
}
// END-WAJIBY-RENAME-V4_COMMANDUI
91textScripts/RenamePanel.cs.meta
fileFormatVersion: 2 guid: 749af364bb8ea6d4d85157cca4e9c58a
92textScripts/SafeAreaFitter.cs
using UnityEngine;
public class SafeAreaFitter : MonoBehaviour
{
void Awake()
{
Rect safe = Screen.safeArea;
if (safe.width <= 0 || safe.height <= 0 || Screen.width <= 0 || Screen.height <= 0)
return;
float x0 = safe.x / Screen.width;
float y0 = safe.y / Screen.height;
float x1 = (safe.x + safe.width) / Screen.width;
float y1 = (safe.y + safe.height) / Screen.height;
if (float.IsNaN(x0) || float.IsNaN(y0) || float.IsNaN(x1) || float.IsNaN(y1))
return;
RectTransform rt = GetComponent<RectTransform>();
rt.anchorMin = new Vector2(x0, y0);
rt.anchorMax = new Vector2(x1, y1);
rt.anchoredPosition = Vector2.zero;
rt.sizeDelta = Vector2.zero;
}
}93textScripts/SafeAreaFitter.cs.meta
fileFormatVersion: 2 guid: 704310e438415ad419a546005ac3254d
94textScripts/SafeTap.cs
using UnityEngine;
using UnityEngine.EventSystems;
public static class Esc
{
static int frame = -1;
public static bool Take()
{
if (Input.GetKeyDown(KeyCode.Escape) && frame != Time.frameCount)
{ frame = Time.frameCount; return true; }
return false;
}
}
public class SafeTap : MonoBehaviour,
IPointerDownHandler, IPointerUpHandler, IPointerExitHandler, IPointerClickHandler
{
public UnityEngine.Events.UnityAction onTap;
public RectTransform[] ignore;
bool pressed;
bool InIgnore(Vector2 pos, Camera cam)
{
if (ignore == null) return false;
foreach (var r in ignore)
if (r != null && RectTransformUtility.RectangleContainsScreenPoint(r, pos, cam))
return true;
return false;
}
public void OnPointerDown(PointerEventData e)
{ pressed = !InIgnore(e.position, e.pressEventCamera); }
public void OnPointerExit(PointerEventData e) { pressed = false; }
public void OnPointerUp(PointerEventData e)
{
if (!pressed) return;
pressed = false;
if (InIgnore(e.position, e.pressEventCamera)) return;
RectTransform rt = GetComponent<RectTransform>();
if (RectTransformUtility.RectangleContainsScreenPoint(rt, e.position, e.pressEventCamera))
onTap?.Invoke();
}
public void OnPointerClick(PointerEventData e) { }
}95textScripts/SafeTap.cs.meta
fileFormatVersion: 2 guid: e53a64c727f42dc44a03fc84f2262c5b
96textScripts/SettingsScreen.cs
// CANARY: WAJIBY_SETTINGS_V24_COMMANDUI
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
public static class SettingsScreen
{
static Color Bg=new Color(247/255f,243/255f,232/255f,1f), Card=new Color(255/255f,253/255f,246/255f,1f);
static Color Panel=new Color(40/255f,83/255f,57/255f,1f), Paper=new Color(37/255f,55/255f,43/255f,1f);
static Color Muted=new Color(105/255f,119/255f,108/255f,1f), Accent=new Color(214/255f,169/255f,58/255f,1f);
static Color Hair=new Color(194/255f,181/255f,133/255f,1f);
static GameObject _root; static RectTransform _rootRT; static TMP_FontAsset _font;
static TextMeshProUGUI _snoozeSub; static RectTransform _snoozeRowRT;
const float HEAD=184f, ROW=228f, GAP=16f, OPT=116f, PAD=12f;
const float ABOUT_FONT=46f, LINE_H=68f, SPACER_H=24f, INSET=24f; // fixed big font; no shrink
static readonly int[] SNOOZE={3,5,10,15};
const string ABOUT_BODY =
"\u062A\u0637\u0628\u064A\u0642 \u0648\u0627\u062C\u0628\u064A \u0627\u0644\u0639\u0633\u0643\u0631\u064A \u0628\u0631\u0646\u0627\u0645\u062C\u064C \u0645\u0646\u0628\u0647 \u064A\u0633\u0627\u0639\u062F \u0627\u0644\u062D\u0631\u0651\u0627\u0633 \u0648\u0627\u0644\u0634\u0631\u0637\u0629 \u0648\u0627\u0644\u062C\u0646\u0648\u062F \u0639\u0644\u0649 \u0648\u0627\u062C\u0628\u0627\u062A\u0647\u0645 \u0627\u0644\u064A\u0648\u0645\u064A\u0629 \u0641\u064A \u0627\u0644\u062F\u0648\u0627\u0645.\n" +
"\u0645\u0644\u0627\u062D\u0638\u0629: \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0627\u0644\u0623\u0635\u0644\u064A \u0644\u0627 \u064A\u062A\u0637\u0644\u0628 \u0623\u064A \u0645\u0639\u0644\u0648\u0645\u0627\u062A \u0634\u062E\u0635\u064A\u0629. \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0641\u0642\u0637 \u064A\u062A\u0637\u0644\u0628 \u0627\u0630\u0648\u0646\u0627\u062A \u0645\u062E\u0635\u0635\u0629 \u0648\u0625\u0639\u0644\u0627\u0646\u0627\u062A \u0644\u0643\u064A \u064A\u0634\u062A\u063A\u0644 \u0628\u0634\u0643\u0644 \u0637\u0628\u064A\u0639\u064A. \u0623\u064A\u0651 \u062A\u0637\u0628\u064A\u0642 \u0645\u0634\u0627\u0628\u0647 \u0644\u062A\u0637\u0628\u064A\u0642 \u0627\u0644\u0639\u0633\u0643\u0631\u064A \u064A\u0637\u0644\u0628 \u0645\u0639\u0644\u0648\u0645\u0627\u062A \u0634\u062E\u0635\u064A\u0629 \u0641\u0647\u0648 \u0644\u064A\u0633 \u0623\u0635\u0644\u064A. \u0644\u0627 \u062A\u0633\u0645\u062D \u0644\u0647 \u0628\u062A\u062F\u062E\u0644 \u0623\u064A \u0645\u0639\u0644\u0648\u0645\u0627\u062A \u0648\u0627\u0645\u0633\u062D \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0641\u0648\u0631\u0627\u064B \u0644\u0643\u064A \u062A\u062A\u062C\u0646\u0628 \u0644\u0633\u0631\u0642 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A.\n" +
"\u062A\u0637\u0628\u064A\u0642 \u0648\u0627\u062C\u0628\u064A \u0627\u0644\u0639\u0633\u0643\u0631\u064A \u0644\u064A\u0633 \u0644\u0647 \u062A\u062F\u062E\u0644 \u062D\u0643\u0648\u0645\u064A \u0623\u0648 \u0633\u064A\u0627\u0633\u064A \u0648\u0644\u0627 \u0644\u0647 \u0639\u0644\u0627\u0642\u0629 \u0645\u0639 \u0623\u064A \u0645\u0646\u0647\u0645\u0627.";
static GameObject _pick; static RectTransform _panelRT;
class Opt{ public string label; public bool sel; public System.Action go; public Opt(string l,bool s,System.Action g){label=l;sel=s;go=g;} }
static readonly List<Opt> _opts=new List<Opt>();
static GameObject _about;
public static void Show(){ if(_root==null)Build(); ClosePicker(); _root.SetActive(true); _root.transform.SetAsLastSibling(); RefreshLabels(); }
public static void Hide(){ ClosePicker(); if(_root!=null)_root.SetActive(false); }
static void Build()
{
_font=Font();
_root=new GameObject("SettingsRoot");
var cv=_root.AddComponent<Canvas>(); cv.renderMode=RenderMode.ScreenSpaceOverlay; cv.overrideSorting=true; cv.sortingOrder=1150;
_root.AddComponent<GraphicRaycaster>();
var rt=_root.GetComponent<RectTransform>(); rt.anchorMin=Vector2.zero; rt.anchorMax=Vector2.one; rt.offsetMin=Vector2.zero; rt.offsetMax=Vector2.zero; _rootRT=rt;
var bg=_root.AddComponent<Image>(); bg.color=Bg; bg.raycastTarget=true;
var bar=new GameObject("_bar"); bar.transform.SetParent(_root.transform,false);
var brt=bar.AddComponent<RectTransform>(); brt.anchorMin=new Vector2(0,1); brt.anchorMax=new Vector2(1,1); brt.pivot=new Vector2(.5f,1); brt.offsetMin=new Vector2(0,-HEAD); brt.offsetMax=Vector2.zero;
var bi=bar.AddComponent<Image>(); bi.sprite=null; bi.color=Panel; bi.raycastTarget=true;
var accentBar=new GameObject("Accent"); accentBar.transform.SetParent(bar.transform,false); var abrt=accentBar.AddComponent<RectTransform>(); abrt.anchorMin=new Vector2(.06f,0); abrt.anchorMax=new Vector2(.94f,0); abrt.pivot=new Vector2(.5f,0); abrt.sizeDelta=new Vector2(0,4); abrt.anchoredPosition=Vector2.zero; var abim=accentBar.AddComponent<Image>(); abim.color=Accent; abim.raycastTarget=false;
var ttl=new GameObject("T"); ttl.transform.SetParent(bar.transform,false);
var ttr=ttl.AddComponent<RectTransform>(); ttr.anchorMin=new Vector2(.14f,0); ttr.anchorMax=new Vector2(.96f,1); ttr.offsetMin=Vector2.zero; ttr.offsetMax=Vector2.zero;
var tt=ttr.gameObject.AddComponent<TextMeshProUGUI>(); tt.font=_font; tt.fontSize=58; tt.color=new Color(244f/255f,240f/255f,228f/255f,1f); tt.alignment=TextAlignmentOptions.MidlineRight; ttr.gameObject.AddComponent<ArabicText>().SetText("الإعدادات");
var hm=UnityEngine.Object.FindAnyObjectByType<HomeManager>();
var cls=new GameObject("X"); cls.transform.SetParent(bar.transform,false);
var crt=cls.AddComponent<RectTransform>(); crt.anchorMin=new Vector2(0,1); crt.anchorMax=new Vector2(0,1); crt.pivot=new Vector2(.5f,.5f); crt.sizeDelta=new Vector2(130,130); crt.anchoredPosition=new Vector2(110,-HEAD/2f);
var cim=crt.gameObject.AddComponent<Image>(); cim.sprite=hm!=null?hm.wizardClose:null; cim.preserveAspect=true; cim.raycastTarget=true;
crt.gameObject.AddComponent<SafeTap>().onTap=Hide;
var vp=new GameObject("Viewport"); vp.transform.SetParent(_root.transform,false);
var vprt=vp.AddComponent<RectTransform>(); vprt.anchorMin=Vector2.zero; vprt.anchorMax=Vector2.one; vprt.offsetMin=Vector2.zero; vprt.offsetMax=new Vector2(0,-HEAD);
var vpi=vp.AddComponent<Image>(); vpi.sprite=null; vpi.color=new Color(0,0,0,0); vpi.raycastTarget=true; vp.AddComponent<RectMask2D>();
var sr=vp.AddComponent<ScrollRect>(); sr.horizontal=false; sr.vertical=true; sr.movementType=ScrollRect.MovementType.Clamped; sr.inertia=true; sr.scrollSensitivity=30f;
var ct=new GameObject("Content"); ct.transform.SetParent(vp.transform,false);
var ctrt=ct.AddComponent<RectTransform>(); ctrt.anchorMin=new Vector2(0,1); ctrt.anchorMax=new Vector2(1,1); ctrt.pivot=new Vector2(.5f,1); ctrt.sizeDelta=Vector2.zero; ctrt.anchoredPosition=Vector2.zero;
var vlg=ct.AddComponent<VerticalLayoutGroup>(); vlg.padding=new RectOffset(0,0,26,26); vlg.spacing=GAP; vlg.childControlWidth=true; vlg.childControlHeight=true; vlg.childForceExpandWidth=true; vlg.childForceExpandHeight=false; vlg.childAlignment=TextAnchor.UpperRight;
ct.AddComponent<ContentSizeFitter>().verticalFit=ContentSizeFitter.FitMode.PreferredSize;
sr.viewport=vprt; sr.content=ctrt;
int i=0;
Row(ct.transform,i++,"الصلاحيات","الإشعارات • البطارية • التشغيل التلقائي",OpenPermsFromSettings,out _,out _);
Row(ct.transform,i++,"مدة الغفو",SnoozeMin()+" دقائق",OpenSnoozePicker,out _,out _snoozeSub);
Row(ct.transform,i++,"عن التطبيق","",OpenAbout,out _,out _);
_snoozeRowRT=ct.transform.Find("R1") as RectTransform;
BuildPickerShell(); BuildAbout();
}
static void Row(Transform content,int idx,string title,string sub,UnityEngine.Events.UnityAction act,out TextMeshProUGUI titleTmp,out TextMeshProUGUI subTmp)
{
titleTmp=null; subTmp=null;
var go=new GameObject("R"+idx); go.transform.SetParent(content,false);
var r=go.AddComponent<RectTransform>(); r.anchorMin=new Vector2(0,1); r.anchorMax=new Vector2(1,1); r.pivot=new Vector2(.5f,1);
var le=go.AddComponent<LayoutElement>(); le.preferredHeight=ROW; le.minHeight=ROW;
var im=go.AddComponent<Image>(); im.sprite=null; im.color=Card; im.raycastTarget=true;
var tg=new GameObject("TT"); tg.transform.SetParent(go.transform,false);
var tgr=tg.AddComponent<RectTransform>(); tgr.anchorMin=new Vector2(.05f,.5f); tgr.anchorMax=new Vector2(.95f,1f); tgr.offsetMin=Vector2.zero; tgr.offsetMax=Vector2.zero;
titleTmp=tgr.gameObject.AddComponent<TextMeshProUGUI>(); titleTmp.font=_font; titleTmp.fontSize=60; titleTmp.color=Paper; titleTmp.alignment=TextAlignmentOptions.MidlineRight; tgr.gameObject.AddComponent<ArabicText>().SetText(title);
var sg=new GameObject("SG"); sg.transform.SetParent(go.transform,false);
var sgr=sg.AddComponent<RectTransform>(); sgr.anchorMin=new Vector2(.05f,.05f); sgr.anchorMax=new Vector2(.95f,.5f); sgr.offsetMin=Vector2.zero; sgr.offsetMax=Vector2.zero;
subTmp=sgr.gameObject.AddComponent<TextMeshProUGUI>(); subTmp.font=_font; subTmp.fontSize=34; subTmp.color=Muted; subTmp.alignment=TextAlignmentOptions.MidlineRight; sgr.gameObject.AddComponent<ArabicText>().SetText(sub);
go.AddComponent<SafeTap>().onTap=act??(()=>{});
}
static void BuildPickerShell()
{
_pick=new GameObject("Pick"); _pick.transform.SetParent(_root.transform,false);
var catcher=new GameObject("Catch"); catcher.transform.SetParent(_pick.transform,false);
var cr=catcher.AddComponent<RectTransform>(); cr.anchorMin=Vector2.zero; cr.anchorMax=Vector2.one; cr.offsetMin=Vector2.zero; cr.offsetMax=Vector2.zero;
var cri=catcher.AddComponent<Image>(); cri.sprite=null; cri.color=new Color(0,0,0,0); cri.raycastTarget=true;
catcher.AddComponent<SafeTap>().onTap=ClosePicker;
var pan=new GameObject("Panel"); pan.transform.SetParent(_pick.transform,false);
_panelRT=pan.AddComponent<RectTransform>(); _panelRT.anchorMin=new Vector2(.5f,.5f); _panelRT.anchorMax=new Vector2(.5f,.5f); _panelRT.pivot=new Vector2(.5f,1f);
var pim=pan.AddComponent<Image>(); pim.sprite=null; pim.color=Card; pim.raycastTarget=true;
_pick.SetActive(false);
}
static void OpenSnoozePicker(){ _opts.Clear(); foreach(int m in SNOOZE){ int mm=m; _opts.Add(new Opt(mm+" دقائق",SnoozeMin()==mm,()=>SetSnooze(mm))); } OpenPicker(_snoozeRowRT); }
static void ClosePicker(){ if(_pick!=null)_pick.SetActive(false); }
static void OpenPicker(RectTransform anchorRow)
{
if(_pick==null||anchorRow==null)return;
for(int k=_panelRT.childCount-1;k>=0;k--) Object.Destroy(_panelRT.GetChild(k).gameObject);
float pw=Mathf.Min(anchorRow.rect.width,_rootRT.rect.width-40f);
_panelRT.sizeDelta=new Vector2(pw,PAD*2f+_opts.Count*OPT);
for(int k=0;k<_opts.Count;k++){ var o=_opts[k]; int kk=k;
var op=new GameObject("O"+kk); op.transform.SetParent(_panelRT,false);
var or=op.AddComponent<RectTransform>(); or.anchorMin=new Vector2(0,1); or.anchorMax=new Vector2(1,1); or.pivot=new Vector2(.5f,.5f);
or.sizeDelta=new Vector2(0,OPT); or.anchoredPosition=new Vector2(0,-(PAD+kk*OPT+OPT/2f));
var oi=op.AddComponent<Image>(); oi.sprite=null; oi.color=new Color(0,0,0,0); oi.raycastTarget=true;
var lb=new GameObject("L"); lb.transform.SetParent(op.transform,false);
var lr=lb.AddComponent<RectTransform>(); lr.anchorMin=Vector2.zero; lr.anchorMax=Vector2.one; lr.offsetMin=new Vector2(30,0); lr.offsetMax=new Vector2(-30,0);
var lt=lr.gameObject.AddComponent<TextMeshProUGUI>(); lt.font=_font; lt.fontSize=46; lt.alignment=TextAlignmentOptions.MidlineRight; lt.raycastTarget=false; lt.color=o.sel?Accent:Paper;
lr.gameObject.AddComponent<ArabicText>().SetText(o.label);
op.AddComponent<SafeTap>().onTap=()=>{ o.go(); ClosePicker(); RefreshLabels(); };
}
Vector3 wp=anchorRow.TransformPoint(new Vector3(0f,anchorRow.rect.yMin-8f,0f));
Vector3 lp=_rootRT.InverseTransformPoint(wp);
_panelRT.anchoredPosition=new Vector2(lp.x,lp.y);
_pick.SetActive(true); _pick.transform.SetAsLastSibling();
}
static int SnoozeMin(){ int v=PlayerPrefs.GetInt("wajiby_snooze_min",5); foreach(int s in SNOOZE) if(s==v)return v; return 5; }
static void SetSnooze(int m){ PlayerPrefs.SetInt("wajiby_snooze_min",m); PlayerPrefs.Save(); }
static void RefreshLabels(){ if(_snoozeSub!=null){ var at=_snoozeSub.GetComponent<ArabicText>(); string s=SnoozeMin()+" دقائق"; if(at!=null)at.SetText(s); else _snoozeSub.text=s; } }
static void OpenPermsFromSettings(){ AlarmSystem.permFromSettings=true; AlarmSystem.ShowPermissionsPage(); }
// Greedy wrap MEASURED at ABOUT_FONT against the real inner width -> each returned string is ONE
// visual line that fills the column. Caller renders each as its own NoWrap TMP (auto-wrap is the
// reverser; one-line-per-TMP can never reverse). "" entries = paragraph gaps.
static List<string> MeasureLines(float innerW)
{
var res=new List<string>();
if(innerW<300f) innerW=900f;
var meas=new GameObject("AWM"); var mt=meas.AddComponent<TextMeshProUGUI>();
mt.font=_font; mt.fontSize=ABOUT_FONT; mt.textWrappingMode=TextWrappingModes.NoWrap;
foreach(var para in ABOUT_BODY.Split('\n'))
{
if(para.Trim().Length==0){ res.Add(""); continue; }
string cur="";
foreach(var w in para.Split(' '))
{
if(w.Length==0) continue;
if(cur.Length==0){ cur=w; continue; }
string cand=cur+" "+w;
if(mt.GetPreferredValues(ArabicShaper.Shape(cand)).x<=innerW) cur=cand;
else { res.Add(ArabicShaper.Shape(cur)); cur=w; }
}
if(cur.Length>0) res.Add(ArabicShaper.Shape(cur));
res.Add("");
}
Object.Destroy(meas);
while(res.Count>0 && res[res.Count-1]=="") res.RemoveAt(res.Count-1);
return res;
}
static void BuildAbout()
{
_about=new GameObject("SettingsAboutRoot");
var cv=_about.AddComponent<Canvas>(); cv.renderMode=RenderMode.ScreenSpaceOverlay; cv.overrideSorting=true; cv.sortingOrder=5000;
_about.AddComponent<GraphicRaycaster>();
var rt=_about.GetComponent<RectTransform>(); rt.anchorMin=Vector2.zero; rt.anchorMax=Vector2.one; rt.offsetMin=Vector2.zero; rt.offsetMax=Vector2.zero;
var bg=_about.AddComponent<Image>(); bg.color=Bg; bg.raycastTarget=true;
var bar=new GameObject("_bar"); bar.transform.SetParent(_about.transform,false);
var brt=bar.AddComponent<RectTransform>(); brt.anchorMin=new Vector2(0,1); brt.anchorMax=new Vector2(1,1); brt.pivot=new Vector2(.5f,1); brt.offsetMin=new Vector2(0,-HEAD); brt.offsetMax=Vector2.zero;
var bi=bar.AddComponent<Image>(); bi.sprite=null; bi.color=Panel; bi.raycastTarget=true;
var ttl=new GameObject("T"); ttl.transform.SetParent(bar.transform,false);
var ttr=ttl.AddComponent<RectTransform>(); ttr.anchorMin=new Vector2(.14f,0); ttr.anchorMax=new Vector2(.96f,1); ttr.offsetMin=Vector2.zero; ttr.offsetMax=Vector2.zero;
var tt=ttr.gameObject.AddComponent<TextMeshProUGUI>(); tt.font=_font; tt.fontSize=58; tt.color=new Color(244f/255f,240f/255f,228f/255f,1f); tt.alignment=TextAlignmentOptions.MidlineRight; ttr.gameObject.AddComponent<ArabicText>().SetText("عن التطبيق");
var hm=UnityEngine.Object.FindAnyObjectByType<HomeManager>();
var cls=new GameObject("X"); cls.transform.SetParent(bar.transform,false);
var crt=cls.AddComponent<RectTransform>(); crt.anchorMin=new Vector2(0,1); crt.anchorMax=new Vector2(0,1); crt.pivot=new Vector2(.5f,.5f); crt.sizeDelta=new Vector2(130,130); crt.anchoredPosition=new Vector2(110,-HEAD/2f);
var cim=crt.gameObject.AddComponent<Image>(); cim.sprite=hm!=null?hm.wizardClose:null; cim.preserveAspect=true; cim.raycastTarget=true;
crt.gameObject.AddComponent<SafeTap>().onTap=()=>_about.SetActive(false);
// scroll viewport between header and the bottom divider/version band
var vp=new GameObject("AVP"); vp.transform.SetParent(_about.transform,false);
var vprt=vp.AddComponent<RectTransform>(); vprt.anchorMin=Vector2.zero; vprt.anchorMax=Vector2.one; vprt.offsetMin=new Vector2(40,150); vprt.offsetMax=new Vector2(-40,-HEAD-20);
vp.AddComponent<RectMask2D>(); var vpi=vp.AddComponent<Image>(); vpi.sprite=null; vpi.color=new Color(0,0,0,0); vpi.raycastTarget=true;
var sr=vp.AddComponent<ScrollRect>(); sr.horizontal=false; sr.vertical=true; sr.movementType=ScrollRect.MovementType.Clamped; sr.inertia=true; sr.scrollSensitivity=30f;
var ct=new GameObject("AC"); ct.transform.SetParent(vp.transform,false);
var ctrt=ct.AddComponent<RectTransform>(); ctrt.anchorMin=new Vector2(0,1); ctrt.anchorMax=new Vector2(1,1); ctrt.pivot=new Vector2(.5f,1); ctrt.sizeDelta=Vector2.zero; ctrt.anchoredPosition=Vector2.zero;
var vlg=ct.AddComponent<VerticalLayoutGroup>(); vlg.padding=new RectOffset(0,0,14,14); vlg.spacing=4; vlg.childControlWidth=true; vlg.childControlHeight=true; vlg.childForceExpandWidth=true; vlg.childForceExpandHeight=false; vlg.childAlignment=TextAnchor.UpperRight;
ct.AddComponent<ContentSizeFitter>().verticalFit=ContentSizeFitter.FitMode.PreferredSize;
sr.viewport=vprt; sr.content=ctrt;
float vw=vprt.rect.width; if(vw<300f){ float sf=(cv!=null&&cv.scaleFactor>0f)?cv.scaleFactor:1f; vw=Screen.width/sf; }
float innerW=vw-2f*INSET-20f;
foreach(var line in MeasureLines(innerW))
{
bool empty=(line=="");
var lg=new GameObject(empty?"Gap":"L"); lg.transform.SetParent(ct.transform,false);
var lrt=lg.AddComponent<RectTransform>(); lrt.anchorMin=new Vector2(0,1); lrt.anchorMax=new Vector2(1,1); lrt.pivot=new Vector2(.5f,1); lrt.sizeDelta=Vector2.zero;
var le=lg.AddComponent<LayoutElement>(); le.preferredHeight=empty?SPACER_H:LINE_H; le.minHeight=le.preferredHeight;
if(!empty)
{
var t=lg.AddComponent<TextMeshProUGUI>(); t.font=_font; t.fontSize=ABOUT_FONT; t.color=Paper; t.alignment=TextAlignmentOptions.MidlineRight; t.textWrappingMode=TextWrappingModes.NoWrap; t.overflowMode=TextOverflowModes.Overflow; t.raycastTarget=false;
var tr=t.GetComponent<RectTransform>(); tr.offsetMin=new Vector2(INSET,0); tr.offsetMax=new Vector2(-INSET,0);
t.text=line; // already shaped, single line => reverser-safe, measured => fills width
}
}
var dgo=new GameObject("ADiv"); dgo.transform.SetParent(_about.transform,false);
var drt=dgo.AddComponent<RectTransform>(); drt.anchorMin=new Vector2(.05f,0); drt.anchorMax=new Vector2(.95f,0); drt.pivot=new Vector2(.5f,0); drt.sizeDelta=new Vector2(0,2); drt.anchoredPosition=new Vector2(0,120);
var dim=dgo.AddComponent<Image>(); dim.sprite=null; dim.color=Hair; dim.raycastTarget=false;
var vgo=new GameObject("AVer"); vgo.transform.SetParent(_about.transform,false);
var vgrt=vgo.AddComponent<RectTransform>(); vgrt.anchorMin=new Vector2(.05f,0); vgrt.anchorMax=new Vector2(.95f,0); vgrt.pivot=new Vector2(.5f,0); vgrt.sizeDelta=new Vector2(0,80); vgrt.anchoredPosition=new Vector2(0,30);
var vt=vgo.AddComponent<TextMeshProUGUI>(); vt.font=_font; vt.fontSize=34; vt.color=Muted; vt.alignment=TextAlignmentOptions.MidlineRight; vt.raycastTarget=false;
vgo.AddComponent<ArabicText>().SetText("اصدار التطبيق "+Application.version);
_about.SetActive(false);
}
static void OpenAbout(){ if(_about!=null){ _about.SetActive(true); _about.transform.SetAsLastSibling(); } }
static TMP_FontAsset Font()
{
if(_font!=null)return _font;
foreach(var f in Resources.FindObjectsOfTypeAll<TMP_FontAsset>()){ if(f?.characterTable==null)continue; foreach(var c in f.characterTable) if(c!=null&&c.unicode==0x062D){_font=f;return f;} }
var t=UnityEngine.Object.FindAnyObjectByType<TextMeshProUGUI>(); if(t?.font!=null)_font=t.font;
if(_font==null)_font=TMP_Settings.defaultFontAsset; return _font;
}
}
// END-WAJIBY-SETTINGS-V24_COMMANDUI
97textScripts/SettingsScreen.cs.meta
fileFormatVersion: 2 guid: 3ac25f70e5f61034aaddf6daa3f40988
98textScripts/Toast.cs
// CANARY: WAJIBY_TOAST_V2_TOP
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections;
public class Toast : MonoBehaviour
{
public static void Show(Transform root, string text, TMP_FontAsset font)
{
if (root == null) return;
GameObject go = new GameObject("WajibyToast");
go.transform.SetParent(root, false);
RectTransform rt = go.AddComponent<RectTransform>();
rt.anchorMin = new Vector2(0.5f, 1f);
rt.anchorMax = new Vector2(0.5f, 1f);
rt.pivot = new Vector2(0.5f, 1f);
rt.sizeDelta = new Vector2(900f, 112f);
rt.anchoredPosition = new Vector2(0f, -145f);
Image im = go.AddComponent<Image>();
WajibyUITheme.Panel(im, WajibyUITheme.Surface2, true, WajibyUITheme.Border);
im.raycastTarget = false;
GameObject t = new GameObject("Text");
t.transform.SetParent(go.transform, false);
RectTransform trt = t.AddComponent<RectTransform>();
trt.anchorMin = Vector2.zero;
trt.anchorMax = Vector2.one;
trt.offsetMin = new Vector2(28f, 10f);
trt.offsetMax = new Vector2(-28f, -10f);
TextMeshProUGUI tmp = t.AddComponent<TextMeshProUGUI>();
tmp.fontSize = 38f;
tmp.color = WajibyUITheme.Text;
tmp.alignment = TextAlignmentOptions.Center;
tmp.textWrappingMode = TextWrappingModes.NoWrap;
tmp.raycastTarget = false;
if (font != null) tmp.font = font;
ArabicText at = t.AddComponent<ArabicText>();
at.SetText(text ?? "");
go.transform.SetAsLastSibling();
go.AddComponent<Toast>().Die_();
}
void Die_() { StartCoroutine(Die()); }
IEnumerator Die()
{
yield return new WaitForSeconds(3.5f);
Destroy(gameObject);
}
}
// END-WAJIBY-TOAST-V2_TOP
99textScripts/Toast.cs.meta
fileFormatVersion: 2 guid: f02cb6ac1cbbbc84fb6844958a719b8d
100textScripts/TopGear.cs
// CANARY: WAJIBY_TOPGEAR_V2_FINAL
using UnityEngine;
using UnityEngine.UI;
public static class TopGear
{
static Drv _d;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
if (_d == null) _d = new GameObject("TopGearDrv").AddComponent<Drv>();
}
class Drv : MonoBehaviour
{
float _next;
GameObject _gear;
void Update()
{
if (Time.unscaledTime < _next) return;
_next = Time.unscaledTime + 0.25f;
try { Ensure(); } catch (System.Exception e) { Debug.LogWarning("TopGear disabled: " + e.Message); enabled = false; }
}
void Ensure()
{
var hm = Object.FindAnyObjectByType<HomeManager>();
if (hm == null) return;
var cv = hm.GetComponentInParent<Canvas>();
if (cv == null) return;
if (_gear != null) return;
Transform existing = cv.transform.Find("TopGear");
if (existing != null)
{
_gear = existing.gameObject;
Wire(_gear);
return;
}
var g = new GameObject("TopGear");
g.transform.SetParent(cv.transform, false);
var r = g.AddComponent<RectTransform>();
r.anchorMin = new Vector2(1, 1);
r.anchorMax = new Vector2(1, 1);
r.pivot = new Vector2(.5f, .5f);
r.sizeDelta = new Vector2(92, 92);
r.anchoredPosition = new Vector2(-58, -58);
var im = g.AddComponent<Image>();
im.sprite = FlatIcons.Gear();
im.preserveAspect = true;
im.color = new Color(201f/255f,162f/255f,39f/255f,1f);
im.raycastTarget = true;
Wire(g);
g.transform.SetAsLastSibling();
_gear = g;
}
void Wire(GameObject g)
{
var st = g.GetComponent<SafeTap>();
if (st == null) st = g.AddComponent<SafeTap>();
st.onTap = SettingsScreen.Show;
}
}
}
// END-WAJIBY-TOPGEAR-V2_FINAL
101textScripts/TopGear.cs.meta
fileFormatVersion: 2 guid: b840bfa050a6abb41bf6dc1b8e0109e7
102textScripts/WajibyBrightGuard.cs
// CANARY: WAJIBY_BRIGHTGUARD_V1
using UnityEngine;
// Disables the old runtime scanner, if it is still attached to a canvas from an earlier UI experiment.
// It does not create UI and does not alter alarms, profiles, ads, or scheduling.
[DefaultExecutionOrder(-30000)]
public class WajibyBrightGuard : MonoBehaviour
{
void Awake()
{
Canvas[] canvases = Resources.FindObjectsOfTypeAll<Canvas>();
for (int i = 0; i < canvases.Length; i++)
{
Canvas c = canvases[i];
if (c == null) continue;
MonoBehaviour[] all = c.GetComponentsInChildren<MonoBehaviour>(true);
for (int k = 0; k < all.Length; k++)
{
MonoBehaviour mb = all[k];
if (mb == null || mb == this) continue;
string n = mb.GetType().Name;
if (n == "WajibyUIRuntimePolish") mb.enabled = false;
}
}
}
}
// END-WAJIBY-BRIGHTGUARD-V1
103textScripts/WajibyBrightGuard.cs.meta
fileFormatVersion: 2 guid: 3d6b5a3e30c98514a872be08382b23e9
104textScripts/WajibyUIStudio.cs
// CANARY: WAJIBY_UISTUDIO_LEGACY_REMOVED
// Runtime UI Studio was removed. The real Editor window is Assets/Editor/WajibyUIStudio.cs.
public static class WajibyUIStudioLegacyPlaceholder { }
// END-WAJIBY-UISTUDIO-LEGACY
105textScripts/WajibyUIStudio.cs.meta
fileFormatVersion: 2 guid: a9f74d71fa380ab4793f7bbab79837f1
106textScripts/WajibyUITheme.cs
// CANARY: WAJIBY_UI_THEME_V2_CLEAN
using UnityEngine;
using UnityEngine.UI;
using TMPro;
/// <summary>
/// Wajiby visual language for the runtime-generated UI.
/// This file is visual-only: it does not change profiles, schedules, alarms, or ads.
/// </summary>
public static class WajibyUITheme
{
public static readonly Color Background = Hex("141A16");
public static readonly Color Surface = Hex("202823");
public static readonly Color Surface2 = Hex("27312B");
public static readonly Color Surface3 = Hex("2D3931");
public static readonly Color Border = Hex("3A473C");
public static readonly Color BorderSoft = Hex("303B33");
public static readonly Color Gold = Hex("C9A227");
public static readonly Color GoldSoft = Hex("8C7420");
public static readonly Color Text = Hex("F2F2EC");
public static readonly Color Secondary = Hex("9AA59C");
public static readonly Color Muted = Hex("748078");
public static readonly Color Danger = Hex("B3402F");
static Sprite _round;
static Sprite _ring;
static Color Hex(string value)
{
Color c;
return ColorUtility.TryParseHtmlString("#" + value, out c) ? c : Color.white;
}
public static Sprite RoundSprite
{
get { return _round ?? (_round = BuildRoundSprite(128, 22)); }
}
public static Sprite RingSprite
{
get { return _ring ?? (_ring = BuildRingSprite(128, 22, 4)); }
}
public static void Panel(Image image, Color fill, bool shadow = true, Color? outline = null)
{
if (image == null) return;
image.sprite = RoundSprite;
image.type = Image.Type.Sliced;
image.color = fill;
image.raycastTarget = true;
AddOutline(image.gameObject, outline ?? BorderSoft, 1.25f);
if (shadow) AddShadow(image.gameObject);
}
public static void Field(Image image)
{
Panel(image, Surface3, false, Border);
}
public static void ButtonIcon(Image image, Color tint)
{
if (image == null) return;
image.color = tint;
image.preserveAspect = true;
image.raycastTarget = true;
}
public static void ApplyText(TMP_Text text, TMP_FontAsset font, float size,
Color color, TextAlignmentOptions alignment, bool wrap = false)
{
if (text == null) return;
if (font != null) text.font = font;
text.fontSize = size;
text.color = color;
text.alignment = alignment;
text.textWrappingMode = wrap ? TextWrappingModes.Normal : TextWrappingModes.NoWrap;
text.raycastTarget = false;
}
public static void AddOutline(GameObject go, Color color, float width)
{
if (go == null) return;
Outline outline = go.GetComponent<Outline>();
if (outline == null) outline = go.AddComponent<Outline>();
outline.effectColor = color;
outline.effectDistance = new Vector2(width, width);
outline.useGraphicAlpha = true;
}
public static void AddShadow(GameObject go)
{
if (go == null) return;
Shadow shadow = go.GetComponent<Shadow>();
if (shadow == null) shadow = go.AddComponent<Shadow>();
shadow.effectColor = new Color(0f, 0f, 0f, 0.28f);
shadow.effectDistance = new Vector2(0f, -5f);
shadow.useGraphicAlpha = true;
}
static Sprite BuildRoundSprite(int size, float radius)
{
Texture2D tex = new Texture2D(size, size, TextureFormat.RGBA32, false);
tex.name = "WajibyThemeRound";
tex.filterMode = FilterMode.Bilinear;
tex.wrapMode = TextureWrapMode.Clamp;
Vector2 c = new Vector2(size * 0.5f, size * 0.5f);
Vector2 h = new Vector2(size * 0.5f - 2f, size * 0.5f - 2f);
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
Vector2 p = new Vector2(x + 0.5f, y + 0.5f);
tex.SetPixel(x, y, Sdf(p, c, h, radius) <= 0f
? Color.white
: new Color(1f, 1f, 1f, 0f));
}
tex.Apply(false, false);
return Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100f,
0, SpriteMeshType.FullRect, new Vector4(28, 28, 28, 28), false);
}
static Sprite BuildRingSprite(int size, float radius, float stroke)
{
Texture2D tex = new Texture2D(size, size, TextureFormat.RGBA32, false);
tex.name = "WajibyThemeRing";
tex.filterMode = FilterMode.Bilinear;
tex.wrapMode = TextureWrapMode.Clamp;
Vector2 c = new Vector2(size * 0.5f, size * 0.5f);
Vector2 outer = new Vector2(size * 0.5f - 2f, size * 0.5f - 2f);
Vector2 inner = outer - Vector2.one * stroke;
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
Vector2 p = new Vector2(x + 0.5f, y + 0.5f);
bool ring = Sdf(p, c, outer, radius) <= 0f
&& Sdf(p, c, inner, Mathf.Max(2f, radius - stroke)) > 0f;
tex.SetPixel(x, y, ring ? Color.white : new Color(1f, 1f, 1f, 0f));
}
tex.Apply(false, false);
return Sprite.Create(tex, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100f,
0, SpriteMeshType.FullRect, new Vector4(28, 28, 28, 28), false);
}
static float Sdf(Vector2 p, Vector2 c, Vector2 half, float radius)
{
Vector2 q = new Vector2(Mathf.Abs(p.x - c.x), Mathf.Abs(p.y - c.y))
- (half - Vector2.one * radius);
float ox = Mathf.Max(q.x, 0f);
float oy = Mathf.Max(q.y, 0f);
return Mathf.Sqrt(ox * ox + oy * oy)
+ Mathf.Min(Mathf.Max(q.x, q.y), 0f)
- radius;
}
}
// END-WAJIBY-UI_THEME-V2
107textScripts/WajibyUITheme.cs.meta
fileFormatVersion: 2 guid: 5482cd24605a1514d91df53ae9c80342
108textScripts/WizardRuntimeBuilder.cs
// CANARY: WAJIBY_WIZARDBUILDER_V5_COMMANDUI
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public static class WizardRuntimeBuilder
{
static readonly Color TextDark = new Color(244f/255f,240f/255f,228f/255f,1f);
static readonly Color White = new Color(244f/255f,240f/255f,228f/255f,1f);
static readonly Color Gold = new Color(213f/255f,178f/255f,74f/255f,1f);
static readonly Color Green = new Color(82f/255f,145f/255f,91f/255f,1f);
static readonly Color GreenDark = new Color(58f/255f,94f/255f,66f/255f,1f);
static readonly Color Overlay = new Color(7f/255f,12f/255f,9f/255f,.68f);
static readonly Color FrameBg = new Color(23f/255f,36f/255f,28f/255f,1f);
static readonly Color BodyBg = new Color(30f/255f,46f/255f,36f/255f,1f);
static readonly Color FieldBg = new Color(38f/255f,57f/255f,44f/255f,1f);
static readonly Color Muted = new Color(164f/255f,179f/255f,166f/255f,1f);
static readonly Color Line = new Color(123f/255f,112f/255f,76f,.75f);
static Sprite _panel, _fieldPanel;
public static CreationWizard Build(Transform root, HomeManager hm, Sprite win, Sprite closeSpr, Sprite checkSpr,
TMP_FontAsset font, Sprite swOn, Sprite swOff, Sprite editSpr)
{
_panel = _panel ?? RoundedSprite(FrameBg, new Color(Gold.r,Gold.g,Gold.b,.82f), 4f);
_fieldPanel = _fieldPanel ?? RoundedSprite(FieldBg, new Color(GreenDark.r,GreenDark.g,GreenDark.b,.30f), 2f);
GameObject wroot = new GameObject("WizardRoot");
wroot.transform.SetParent(root,false);
Stretch(wroot.AddComponent<RectTransform>());
Image bg = wroot.AddComponent<Image>(); bg.color = Overlay; bg.raycastTarget = true;
// Almost full-screen sheet: no giant black gaps around the form.
GameObject frame = FrameObject(wroot,"Wizard_Frame",1020,2260,_panel);
Shadow sh = frame.AddComponent<Shadow>(); sh.effectColor = new Color(0f,0f,0f,.18f); sh.effectDistance = new Vector2(0f,-10f);
// Header is visual only; the interactive controls remain direct children of Wizard_Frame
// so CreationWizard's existing paths continue to work unchanged.
GameObject header = new GameObject("Wizard_Header"); header.transform.SetParent(frame.transform,false);
RectTransform hrt = header.AddComponent<RectTransform>(); hrt.anchorMin = new Vector2(0,1); hrt.anchorMax = new Vector2(1,1);
hrt.offsetMin = new Vector2(18,-150); hrt.offsetMax = new Vector2(-18,-18);
Image him = header.AddComponent<Image>(); him.color = GreenDark; him.raycastTarget = false;
Label(frame,"Wizard_Title","ملف جديد",56,White,TextAlignmentOptions.Right,new Vector2(180,-128),new Vector2(-120,-32),font);
IconButton(frame.transform,"Btn_Close",0,1,62,-78,78,78,closeSpr);
IconButton(frame.transform,"Btn_Save",1,1,-62,-78,86,86,checkSpr);
IconButton(frame.transform,"Btn_Edit",1,1,-166,-78,60,60,editSpr != null ? editSpr : FlatIcons.Edit());
AddBar(header,0,0,1,0,new Vector2(40,0),new Vector2(-40,5),Gold);
GameObject body = new GameObject("Wizard_Body"); body.transform.SetParent(frame.transform,false);
RectTransform brt = body.AddComponent<RectTransform>(); brt.anchorMin=Vector2.zero; brt.anchorMax=Vector2.one;
brt.offsetMin = new Vector2(34,34); brt.offsetMax = new Vector2(-34,-174);
Image bim = body.AddComponent<Image>(); bim.color = BodyBg; bim.raycastTarget = true;
Mask mask = body.AddComponent<Mask>(); mask.showMaskGraphic = true;
ScrollRect sr = body.AddComponent<ScrollRect>(); sr.horizontal=false; sr.vertical=true; sr.movementType=ScrollRect.MovementType.Elastic; sr.scrollSensitivity=45f;
GameObject content = new GameObject("Content"); content.transform.SetParent(body.transform,false);
RectTransform crt = content.AddComponent<RectTransform>(); crt.anchorMin=new Vector2(0,1); crt.anchorMax=new Vector2(1,1); crt.pivot=new Vector2(.5f,1); crt.sizeDelta=new Vector2(0,1660); crt.anchoredPosition=Vector2.zero;
sr.content=crt;
// Name / first section is intentionally spacious and easy to scan.
SectionTitle(content,"Section_Basic","بيانات الدوام",60,120,font);
RowLabel(content,"Label_Days","مدة الدوام بالأيام",125,185,font);
Field(content,"Field_Days",55,195,320,90,font);
RowLabel(content,"Label_FirstDay","أول يوم الدوام",270,330,font);
ValueBtn(content,"Value_Date","غير محدد",55,340,590,435,font,35);
SectionTitle(content,"Section_Duty","الواجب والاستراحة",535,220+60,font);
RowLabel(content,"Label_Duty","مدة الواجب بالساعات",625,685,font);
Field(content,"Field_Duty",55,695,320,90,font);
RowLabel(content,"Label_Break","مدة الاستراحة بالساعات",775,835,font);
Field(content,"Field_Break",55,845,320,90,font);
RowLabel(content,"Label_FirstDuty","أول واجب",955,1015,font);
ValueBtn(content,"Value_Time","09:00",55,1025,590,1120,font,36);
GameObject hh = Field(content,"Field_HH",55,1025,100,78,font);
GameObject colon = new GameObject("Colon"); colon.transform.SetParent(crt,false);
Place(colon.AddComponent<RectTransform>(),0,1,0,1,180,-1064,32,78);
TextMeshProUGUI ctmp=colon.AddComponent<TextMeshProUGUI>(); ctmp.font=font; ctmp.fontSize=40; ctmp.color=TextDark; ctmp.alignment=TextAlignmentOptions.Center; ctmp.text=":"; ctmp.raycastTarget=false;
GameObject mm = Field(content,"Field_MM",205,1025,100,78,font);
if(NativePickers.Supported){hh.SetActive(false);mm.SetActive(false);colon.SetActive(false);}
RowLabel(content,"Label_LastDuty","أخير واجب",1165,1225,font);
GameObject lsw = BuildSwitch(content.transform,"Btn_LastSwitch",55,-1250,CardRuntimeBuilder.BrightSwitchOff);
GameObject lcol = new GameObject("Colon_Last"); lcol.transform.SetParent(crt,false);
Place(lcol.AddComponent<RectTransform>(),0,1,0,1,510,-1298,30,70);
TextMeshProUGUI lct=lcol.AddComponent<TextMeshProUGUI>(); lct.font=font; lct.fontSize=38; lct.color=TextDark; lct.alignment=TextAlignmentOptions.Center; lct.text=":"; lct.raycastTarget=false; lcol.SetActive(false);
GameObject lvt=ValueBtn(content,"Value_LastTime","09:00",365,1250,490,1325,font,31); lvt.SetActive(false);
SectionTitle(content,"Section_Reminder","التذكير",1370,220+60,font);
RowLabel(content,"Label_Remind","تذكير قبل الواجب بالدقائق",1460,1520,font);
Field(content,"Field_Remind",55,1530,360,90,font);
Label(content,"FormHint","يمكنك تعديل الاسم والوقت قبل الحفظ",25,Muted,TextAlignmentOptions.Right,new Vector2(55,-1620),new Vector2(-55,-1570),font);
// Cancel confirmation
GameObject croot=new GameObject("WizardCancelRoot"); croot.transform.SetParent(wroot.transform,false); Stretch(croot.AddComponent<RectTransform>());
Image cbg=croot.AddComponent<Image>(); cbg.color=Overlay; cbg.raycastTarget=true;
GameObject cframe=FrameObject(croot,"WizardCancel_Frame",760,470,_panel);
Label(cframe,"WizardCancel_Text","إلغاء الملف؟",50,TextDark,TextAlignmentOptions.Center,new Vector2(60,-110),new Vector2(-60,-30),font);
GameObject cbody=new GameObject("WizardCancel_Body"); cbody.transform.SetParent(cframe.transform,false);
RectTransform cbrt=cbody.AddComponent<RectTransform>(); cbrt.anchorMin=Vector2.zero;cbrt.anchorMax=Vector2.one;cbrt.offsetMin=new Vector2(30,30);cbrt.offsetMax=new Vector2(-30,-125);
Image cbim=cbody.AddComponent<Image>(); cbim.color=BodyBg; cbim.raycastTarget=true;
Label(cbody,"WizardCancel_BodyText","لن تحفظ التغييرات!",42,TextDark,TextAlignmentOptions.Center,new Vector2(30,-110),new Vector2(-30,-30),font);
IconButton(cframe.transform,"Btn_Confirm",1,0,-165,112,110,110,checkSpr);
IconButton(cframe.transform,"Btn_Cancel",0,0,165,112,110,110,closeSpr);
croot.SetActive(false);
GameObject cal=BuildCalendar(frame,font,closeSpr); cal.SetActive(false);
wroot.transform.SetAsLastSibling();
CreationWizard wz=wroot.AddComponent<CreationWizard>(); hm.wizard=wz; wroot.SetActive(false); return wz;
}
static GameObject BuildSwitch(Transform p,string name,float x,float y,Sprite visual)
{
GameObject root=new GameObject(name); root.transform.SetParent(p,false);
RectTransform r=root.AddComponent<RectTransform>(); r.anchorMin=new Vector2(0,1); r.anchorMax=new Vector2(0,1); r.pivot=new Vector2(.5f,.5f);
r.sizeDelta=new Vector2(260,96); r.anchoredPosition=new Vector2(x+130,y-48);
Image hit=root.AddComponent<Image>(); hit.color=new Color(0,0,0,0); hit.raycastTarget=true;
Button b=root.AddComponent<Button>(); b.transition=Selectable.Transition.None; b.targetGraphic=hit;
GameObject sw=new GameObject("SwitchVisual"); sw.transform.SetParent(root.transform,false);
RectTransform sr=sw.AddComponent<RectTransform>(); sr.anchorMin=new Vector2(.5f,.5f); sr.anchorMax=new Vector2(.5f,.5f); sr.sizeDelta=new Vector2(176,78); sr.anchoredPosition=Vector2.zero;
Image sim=sw.AddComponent<Image>(); sim.sprite=visual; sim.color=Color.white; sim.preserveAspect=true; sim.raycastTarget=false;
return root;
}
static GameObject BuildCalendar(GameObject frame,TMP_FontAsset font,Sprite closeSpr)
{
GameObject cal=new GameObject("Cal_Root"); cal.transform.SetParent(frame.transform,false);
RectTransform rt=cal.AddComponent<RectTransform>(); rt.anchorMin=Vector2.zero;rt.anchorMax=Vector2.one;rt.offsetMin=new Vector2(36,36);rt.offsetMax=new Vector2(-36,-160);
Image im=cal.AddComponent<Image>(); im.color=FrameBg; im.raycastTarget=true;
Label(cal,"Cal_Title","آب 2026",46,TextDark,TextAlignmentOptions.Center,new Vector2(160,-78),new Vector2(-160,-18),font);
CalendarButton(cal,"Btn_Prev","‹",80,-82,font); CalendarButton(cal,"Btn_Next","›",-80,-82,font);
string[] wd={"أ","ن","ث","ر","خ","ج","س"};
for(int i=0;i<7;i++) LabelAt(cal,"WD_"+i,wd[i],28,GreenDark,TextAlignmentOptions.Center,new Vector2(70+i*125,-170),new Vector2(95,45),font);
GameObject grid=new GameObject("Cal_Grid"); grid.transform.SetParent(cal.transform,false);
RectTransform grt=grid.AddComponent<RectTransform>();grt.anchorMin=Vector2.zero;grt.anchorMax=Vector2.one;grt.offsetMin=new Vector2(26,30);grt.offsetMax=new Vector2(-26,-202);
return cal;
}
static GameObject CalendarButton(GameObject p,string n,string s,float x,float y,TMP_FontAsset f){GameObject g=new GameObject(n);g.transform.SetParent(p.transform,false);RectTransform r=g.AddComponent<RectTransform>();r.anchorMin=new Vector2(.5f,1);r.anchorMax=new Vector2(.5f,1);r.pivot=new Vector2(.5f,.5f);r.sizeDelta=new Vector2(86,70);r.anchoredPosition=new Vector2(x,y);TextMeshProUGUI t=g.AddComponent<TextMeshProUGUI>();t.font=f;t.fontSize=54;t.color=Gold;t.alignment=TextAlignmentOptions.Center;t.text=s;t.raycastTarget=true;Button b=g.AddComponent<Button>();b.transition=Selectable.Transition.None;return g;}
static void LabelAt(GameObject p,string n,string s,int size,Color c,TextAlignmentOptions a,Vector2 pos,Vector2 sizeDelta,TMP_FontAsset f){GameObject g=new GameObject(n);g.transform.SetParent(p.transform,false);RectTransform r=g.AddComponent<RectTransform>();r.anchorMin=new Vector2(0,1);r.anchorMax=new Vector2(0,1);r.pivot=new Vector2(.5f,.5f);r.sizeDelta=sizeDelta;r.anchoredPosition=pos;TextMeshProUGUI t=g.AddComponent<TextMeshProUGUI>();t.font=f;t.fontSize=size;t.color=c;t.alignment=a;t.raycastTarget=false;t.text=s;}
static GameObject FrameObject(GameObject parent,string name,float w,float h,Sprite sprite){GameObject go=new GameObject(name);go.transform.SetParent(parent.transform,false);RectTransform rt=go.AddComponent<RectTransform>();rt.anchorMin=new Vector2(.5f,.5f);rt.anchorMax=new Vector2(.5f,.5f);rt.pivot=new Vector2(.5f,.5f);rt.sizeDelta=new Vector2(w,h);Image im=go.AddComponent<Image>();im.sprite=sprite;im.type=Image.Type.Sliced;im.color=Color.white;return go;}
static void AddBar(GameObject parent,float ax0,float ay0,float ax1,float ay1,Vector2 min,Vector2 max,Color color){GameObject g=new GameObject("AccentBar");g.transform.SetParent(parent.transform,false);RectTransform r=g.AddComponent<RectTransform>();r.anchorMin=new Vector2(ax0,ay0);r.anchorMax=new Vector2(ax1,ay1);r.offsetMin=min;r.offsetMax=max;Image im=g.AddComponent<Image>();im.color=color;im.raycastTarget=false;}
static void SectionTitle(GameObject parent,string name,string text,float top,float bottom,TMP_FontAsset f){Label(parent,name,text,30,GreenDark,TextAlignmentOptions.Right,new Vector2(55,-top),new Vector2(-55,-bottom),f);}
static void RowLabel(GameObject parent,string name,string text,float top,float bottom,TMP_FontAsset f){Label(parent,name,text,39,TextDark,TextAlignmentOptions.Right,new Vector2(55,-top),new Vector2(-55,-bottom),f);}
static GameObject ValueBtn(GameObject parent,string name,string text,float left,float top,float right,float bottom,TMP_FontAsset f,int size){GameObject go=new GameObject(name);go.transform.SetParent(parent.transform,false);RectTransform r=go.AddComponent<RectTransform>();r.anchorMin=new Vector2(0,1);r.anchorMax=new Vector2(0,1);r.pivot=new Vector2(.5f,.5f);r.sizeDelta=new Vector2(right-left,top-bottom);r.anchoredPosition=new Vector2((left+right)/2f,-(top+bottom)/2f);Image im=go.AddComponent<Image>();im.sprite=_fieldPanel;im.type=Image.Type.Sliced;im.color=Color.white;im.raycastTarget=true;TextMeshProUGUI t=go.AddComponent<TextMeshProUGUI>();t.font=f;t.fontSize=size;t.color=TextDark;t.alignment=TextAlignmentOptions.Center;t.raycastTarget=false;go.AddComponent<ArabicText>().SetText(text);Button b=go.AddComponent<Button>();b.transition=Selectable.Transition.None;b.targetGraphic=im;return go;}
static GameObject Field(GameObject parent,string name,float left,float top,float width,float height,TMP_FontAsset f){GameObject go=new GameObject(name);go.transform.SetParent(parent.transform,false);RectTransform r=go.AddComponent<RectTransform>();r.anchorMin=new Vector2(0,1);r.anchorMax=new Vector2(0,1);r.pivot=new Vector2(.5f,.5f);r.sizeDelta=new Vector2(width,height);r.anchoredPosition=new Vector2(left+width/2f,-(top+height/2f));Image im=go.AddComponent<Image>();im.sprite=_fieldPanel;im.type=Image.Type.Sliced;im.color=Color.white;im.raycastTarget=true;GameObject txt=new GameObject("Text");txt.transform.SetParent(go.transform,false);RectTransform tr=txt.AddComponent<RectTransform>();tr.anchorMin=Vector2.zero;tr.anchorMax=Vector2.one;tr.offsetMin=new Vector2(14,5);tr.offsetMax=new Vector2(-14,-5);TextMeshProUGUI tmp=txt.AddComponent<TextMeshProUGUI>();tmp.font=f;tmp.fontSize=38;tmp.color=TextDark;tmp.alignment=TextAlignmentOptions.Center;tmp.raycastTarget=true;TMP_InputField fld=go.AddComponent<TMP_InputField>();fld.textViewport=tr;fld.textComponent=tmp;fld.contentType=TMP_InputField.ContentType.IntegerNumber;fld.characterLimit=3;go.AddComponent<FieldKeyboard>();return go;}
static void Label(GameObject parent,string name,string text,int size,Color color,TextAlignmentOptions align,Vector2 oMin,Vector2 oMax,TMP_FontAsset f){GameObject go=new GameObject(name);go.transform.SetParent(parent.transform,false);RectTransform rt=go.AddComponent<RectTransform>();rt.anchorMin=new Vector2(0,1);rt.anchorMax=Vector2.one;rt.offsetMin=oMin;rt.offsetMax=oMax;TextMeshProUGUI t=go.AddComponent<TextMeshProUGUI>();t.font=f;t.fontSize=size;t.color=color;t.alignment=align;t.raycastTarget=false;go.AddComponent<ArabicText>().SetText(text);}
static void Place(RectTransform rt,float ax0,float ay0,float ax1,float ay1,float px,float py,float w,float h){rt.anchorMin=new Vector2(ax0,ay0);rt.anchorMax=new Vector2(ax1,ay1);rt.pivot=new Vector2(.5f,.5f);rt.sizeDelta=new Vector2(w,h);rt.anchoredPosition=new Vector2(px,py);}
static void Stretch(RectTransform rt){rt.anchorMin=Vector2.zero;rt.anchorMax=Vector2.one;rt.offsetMin=Vector2.zero;rt.offsetMax=Vector2.zero;}
static GameObject IconButton(Transform p,string n,float ax,float ay,float px,float py,float w,float h,Sprite s){GameObject g=new GameObject(n);g.transform.SetParent(p,false);RectTransform r=g.AddComponent<RectTransform>();r.anchorMin=new Vector2(ax,ay);r.anchorMax=new Vector2(ax,ay);r.pivot=new Vector2(.5f,.5f);r.sizeDelta=new Vector2(w,h);r.anchoredPosition=new Vector2(px,py);Image im=g.AddComponent<Image>();im.sprite=s;im.color=Color.white;im.preserveAspect=true;im.raycastTarget=true;Button b=g.AddComponent<Button>();b.transition=Selectable.Transition.None;b.targetGraphic=im;return g;}
static Sprite RoundedSprite(Color fill,Color edge,float edgeWidth){const int N=128;Texture2D tex=new Texture2D(N,N,TextureFormat.RGBA32,false);tex.filterMode=FilterMode.Bilinear;Vector2 c=new Vector2(N/2f,N/2f),h=new Vector2(N/2f-1,N/2f-1);for(int y=0;y<N;y++)for(int x=0;x<N;x++){Vector2 p=new Vector2(x+.5f,y+.5f);float d=RoundedDistance(p,c,h,28f);Color col=d<=0?fill:new Color(0,0,0,0);if(edgeWidth>0&&d<=0&&RoundedDistance(p,c,new Vector2(h.x-edgeWidth,h.y-edgeWidth),Mathf.Max(5f,28f-edgeWidth))>0)col=edge;tex.SetPixel(x,y,col);}tex.Apply();return Sprite.Create(tex,new Rect(0,0,N,N),new Vector2(.5f,.5f),100f,0,SpriteMeshType.FullRect,new Vector4(30,30,30,30));}
static float RoundedDistance(Vector2 p,Vector2 c,Vector2 h,float r){Vector2 q=new Vector2(Mathf.Abs(p.x-c.x),Mathf.Abs(p.y-c.y))-(h-new Vector2(r,r));float ox=Mathf.Max(q.x,0),oy=Mathf.Max(q.y,0);return Mathf.Sqrt(ox*ox+oy*oy)+Mathf.Min(Mathf.Max(q.x,q.y),0)-r;}
}
// END-WAJIBY-WIZARDBUILDER-V5_COMMANDUI
109textScripts/WizardRuntimeBuilder.cs.meta
fileFormatVersion: 2 guid: c437d4d7eabaee94a80d676391f21328