using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Networking; using TMPro; // Page SUPPLIERS : recherche de fournisseurs par code postal, resultats affiches en // boutons dans le Scroll View. Le clic sur une ligne la selectionne (Selected). public static class Suppliers { // Mettre a false quand suppliers.php sera en ligne : la liste viendra alors du serveur. public const bool DemoMode = true; const string SearchUrl = "https://ukitchenit.com/go/suppliers.php?zip="; const float RowHeight = 60f; const float RowSpacing = 10f; const float SideInset = 30f;// marge autour de la liste, sur les quatre cotes const float RightInset = 50f;// marge droite du texte a l'interieur de la ligne const int DemoCount = 10; const float DemoMaxKm = 50f; static readonly Color RowColor = new(1f, 1f, 1f, 1f); static readonly Color RowColorSelected = new(0.85f, 0.93f, 1f, 1f); // Fournisseurs retenus, lus par le bouton "Send to supplier". La selection est // multiple : un clic sur une ligne l'ajoute, un second la retire. static readonly List SelectedList = new(); public static IReadOnlyList Selected => SelectedList; public static string SelectedNames => string.Join(", ", SelectedList); struct Supplier { public string Name; public float Km; public string Email; } // Courriels des fournisseurs retenus, prets pour un "mailto:" (separes par des virgules). static readonly Dictionary EmailByName = new(); public static string SelectedEmails { get { List Mails = new(); foreach (string Name in SelectedList) if (EmailByName.TryGetValue(Name, out string Mail) && Mail != "") Mails.Add(Mail); return string.Join(",", Mails); } } // Recherche par code postal. public static void Search() { StaticCoroutine.Start(SearchRoutine(PostalCode())); } // Bouton "Button Geolocalisation" : en demo on affiche la liste tout de suite, sans code // postal. En reel il faudra passer les coordonnees GPS a la place de ce marqueur. public static void SearchHere() { StaticCoroutine.Start(SearchRoutine("HERE")); } //--------------------------CODE POSTAL---------------------------------------- const int PostalLength = 6;// A1A1A1, espace non compte static bool Formatting; static string LastPrefix = ""; // A brancher sur le OnValueChanged (string) de SUPPLIERS/Input Postal Code. // Met le texte au masque A1A 1A1 et lance la recherche des les trois premiers caracteres. public static void PostalCodeChanged(string Raw) { if (Formatting) return;// c'est nous qui venons de reecrire le champ TMP_InputField Field = PostalField(); if (Field == null) return; string Formatted = FormatPostal(Raw); if (Formatted != Raw) { Formatting = true; Field.text = Formatted; Field.caretPosition = Formatted.Length; Formatting = false; } // Recherche seulement sur un code postal complet : A1A 1A1, soit 6 caracteres. string Clean = Formatted.Replace(" ", ""); if (Clean.Length < PostalLength) { LastPrefix = ""; return; } // Evite de relancer la meme recherche si le champ est reecrit a l'identique. if (Clean == LastPrefix) return; LastPrefix = Clean; Search(); } // Masque canadien : lettre, chiffre, lettre, espace, chiffre, lettre, chiffre. // Tout caractere qui ne correspond pas a la position attendue est ignore. public static string FormatPostal(string Raw) { string Out = ""; int n = 0; foreach (char C in (Raw ?? "").ToUpperInvariant()) { if (!char.IsLetterOrDigit(C)) continue; bool WantLetter = n % 2 == 0;// positions 0,2,4 = lettres ; 1,3,5 = chiffres if (WantLetter != char.IsLetter(C)) continue; if (n == 3) Out += " "; Out += C; if (++n == 6) break; } return Out; } static TMP_InputField PostalField() { GameObject P = Panel(); Transform Field = P != null ? P.transform.Find("Input Postal Code") : null; return Field != null && Field.TryGetComponent(out TMP_InputField Tmp) ? Tmp : null; } // Vide la liste : appele a l'ouverture de la page. public static void Clear() { SelectedList.Clear(); EmailByName.Clear(); Transform Content = ContentTransform(); if (Content == null) return; for (int i = Content.childCount - 1; i >= 0; i--) UnityEngine.Object.Destroy(Content.GetChild(i).gameObject); SetMessage(""); ShowSendButton(false);// rien a envoyer tant qu'aucune liste n'est affichee ShowListHint(true);// la consigne reprend sa place au-dessus du Scroll View vide } // "T_SupplierList" : consigne affichee tant qu'aucune liste n'est presentee. static void ShowListHint(bool On) { GameObject P = Panel(); if (P == null) return; Transform Hint = P.transform.Find("T_SupplierList"); if (Hint != null) Hint.gameObject.SetActive(On); } // "Button Send to supplier" : cache a l'ouverture, montre des que la liste est la. static void ShowSendButton(bool On) { GameObject P = Panel(); if (P == null) return; Transform Btn = P.transform.Find("Button Send to supplier"); if (Btn != null) Btn.gameObject.SetActive(On); } static IEnumerator SearchRoutine(string Zip) { Clear(); if (Zip == "") { SetMessage(TRANS.This("T_EnterPostalCode")); yield break; } List Found; if (DemoMode) { Found = DemoList(); } else { Found = null; yield return Fetch(Zip, List => Found = List); if (Found == null) { SetMessage(TRANS.This("T_SearchUnavailable")); yield break; } } if (Found.Count == 0) { SetMessage(TRANS.This("T_NoSupplier")); yield break; } Fill(Found); SetMessage(Found.Count + " " + TRANS.This("T_SuppliersFound")); ShowSendButton(true); ShowListHint(false);// la liste prend la place de la consigne } // Liste de demonstration : "Supplier 1" a "Supplier 10", distances tirees sous 50 km // et triees du plus proche au plus eloigne. static List DemoList() { List List = new(); for (int i = 1; i <= DemoCount; i++) List.Add(new Supplier { Name = "Supplier " + i, Km = UnityEngine.Random.Range(1f, DemoMaxKm), Email = "supplier" + i + "@example.com" }); List.Sort((a, b) => a.Km.CompareTo(b.Km)); return List; } // Reponse serveur attendue : une ligne par fournisseur, "nom;distance;courriel". static IEnumerator Fetch(string Zip, System.Action> OnDone) { using UnityWebRequest Req = UnityWebRequest.Get(SearchUrl + UnityWebRequest.EscapeURL(Zip)); yield return Req.SendWebRequest(); if (Req.result != UnityWebRequest.Result.Success) { Debug.LogError("[Suppliers] " + Req.responseCode + " " + Req.error); OnDone(null); yield break; } List List = new(); foreach (string Line in Req.downloadHandler.text.Split('\n')) { string[] Cell = Line.Trim().Split(';'); if (Cell.Length < 2 || Cell[0] == "") continue; float.TryParse(Cell[1], out float Km); List.Add(new Supplier { Name = Cell[0], Km = Km, Email = Cell.Length > 2 ? Cell[2].Trim() : "" }); } OnDone(List); } //--------------------------AFFICHAGE---------------------------------------- static void Fill(List List) { Transform Content = ContentTransform(); if (Content == null) { Debug.LogError("[Suppliers] SUPPLIERS/Scroll View/Viewport/Content introuvable."); return; } Font RowFont = PanelFont(); for (int i = 0; i < List.Count; i++) AddRow(Content, List[i], i, RowFont); // Hauteur du contenu pour que le Scroll View sache defiler, tant qu'aucun // ContentSizeFitter ne s'en charge. // Hauteur exacte : degagement haut + lignes + espacements + degagement bas. if (Content is RectTransform Rt) Rt.sizeDelta = new Vector2(Rt.sizeDelta.x, 2f * SideInset + List.Count * RowHeight + (List.Count - 1) * RowSpacing); // Retour en haut de liste : le ScrollRect garde sinon le decalage de la recherche // precedente et la premiere ligne apparait coupee. Transform View = Content.parent != null ? Content.parent.parent : null; if (View != null && View.TryGetComponent(out ScrollRect Scroll)) { Canvas.ForceUpdateCanvases();// la nouvelle hauteur doit etre prise en compte avant Scroll.verticalNormalizedPosition = 1f; } } static void AddRow(Transform Content, Supplier S, int Index, Font RowFont) { GameObject Row = new(S.Name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button)); Row.transform.SetParent(Content, false); // Hauteur imposee : si Content porte un LayoutGroup, c'est lui qui place les lignes et // il ignore le sizeDelta pose plus bas. Sans LayoutElement, elles seraient hautes de 0. LayoutElement Layout = Row.AddComponent(); Layout.minHeight = RowHeight; Layout.preferredHeight = RowHeight; Layout.flexibleHeight = 0f; RectTransform Rt = (RectTransform)Row.transform; Rt.anchorMin = new Vector2(0f, 1f); Rt.anchorMax = new Vector2(1f, 1f); Rt.pivot = new Vector2(0.5f, 1f); // Largeur = celle de Content moins SideInset de chaque cote (pivot centre en x). Rt.sizeDelta = new Vector2(-2f * SideInset, RowHeight); // SideInset sert aussi de degagement en haut, comme sur les cotes. Rt.anchoredPosition = new Vector2(0f, -SideInset - Index * (RowHeight + RowSpacing)); EmailByName[S.Name] = S.Email ?? ""; Image Bg = Row.GetComponent(); Bg.color = RowColor; AddText(Row.transform, "Name", S.Name, TextAnchor.MiddleLeft, RowFont, new Vector2(0f, 0f), new Vector2(0.65f, 1f)); AddText(Row.transform, "Km", S.Km.ToString("0.0") + " km", TextAnchor.MiddleRight, RowFont, new Vector2(0.65f, 0f), new Vector2(1f, 1f)); string Name = S.Name; Row.GetComponent