using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Linq; using Xamarin.Forms; using InABox.Core; using InABox.Clients; using System.IO; using InABox.Mobile; using Comal.Classes; using PRSClasses; namespace PRS.Mobile { public class QAFormViewer : Grid, IDFRenderer { #region Fields // This connects the DFLayout elements to the respective UI elements public readonly Dictionary Bindings = new(); // These get saved into FormData public DFLoadStorage Storage { get; private set; } // These are used to pre-populate the next instance of this form public DFLoadStorage RetainedData { get; private set; } //public readonly Dictionary> dfLayoutLookupFieldLookupOptions = new(); private DFLayout _layout = new(); private IDigitalFormDataModel? _model; const string nullOrInvalidType = "Null or Invalid Type"; private readonly List useSavedSignatures = new(); public readonly List errors = new(); public bool isRequiredEmpty { get; set; } public string isRequiredMessage { get; set; } = ""; private readonly Location location = new(); private readonly Color isRequiredColor = Color.DarkOrange; private readonly Color isRequiredFrame = Color.Firebrick; private DateTime timeStarted = DateTime.MinValue; readonly List headersToCollapse = new(); #endregion #region Constructor public QAFormViewer() { Storage = new DFLoadStorage(); RetainedData = new DFLoadStorage(); } public QAFormViewer(IDigitalFormDataModel model, DFLayout layout, Guid job = default) : this() { Load(model, layout, job); } public void Load(IDigitalFormDataModel model, DFLayout layout, Guid job = default) { GetLocation(); timeStarted = DateTime.Now; _model = model; _layout = layout; _layout.Renderer = this; isRequiredEmpty = false; isRequiredMessage = ""; LoadFormLayout(); Storage = DigitalForm.DeserializeFormData(_model.Instance) ?? new DFLoadStorage(); LoadData(); } #endregion #region Location private async void GetLocation() { await Task.Run(() => { LocationServices locationServices = new LocationServices(); locationServices.OnLocationFound += LocationFound; locationServices.OnLocationError += LocationError; locationServices.GetLocation(); }); } private void LocationFound(LocationServices sender) { location.Latitude = sender.Latitude; location.Longitude = sender.Longitude; } private void LocationError(LocationServices sender, Exception error) { errors.Add("Location error: " + error.Message); } #endregion Location #region Load and Save Data public void LoadData() { foreach (DFLayoutField field in Bindings.Keys) { try { if (RetainedData.HasValue(field.Name)) Bindings[field].Deserialize(RetainedData.GetEntry(field.Name)); else if (Storage.HasValue(field.Name)) Bindings[field].Deserialize(Storage.GetEntry(field.Name)); _layout.ChangeField(field.Name); } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex,$"LoadData({field.Name})"); } } } public void SaveData(bool saveForLater) { try { var save = new DFSaveStorage(); var retained = new DFSaveStorage(); foreach (DFLayoutField field in Bindings.Keys) { var entry = save.GetEntry(field.Name); Bindings[field].Serialize(entry); if (field.GetProperties().Retain) { var retentry = retained.GetEntry(field.Name); Bindings[field].Serialize(retentry); } } UpdateModel(save, saveForLater); Storage.Clear(); RetainedData.Load(retained.FormData, retained.BlobData); } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex,"SaveData"); } } #endregion #region Load Form Layout private void LoadFormLayout() { RowDefinitions.Clear(); foreach (var row in _layout.RowHeights) { RowDefinition def = new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }; SetRowHeight(row, def); RowDefinitions.Add(def); } ColumnDefinitions.Clear(); foreach (var col in _layout.ColumnWidths) { ColumnDefinition def = new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }; def = SetColumnWidth(col, def); ColumnDefinitions.Add(def); } Children.Clear(); foreach (DFLayoutControl element in _layout.Elements) LoadViewAccordingToElement(element); foreach (var header in headersToCollapse) AdjustHeaderSection(header, false); } private static void SetRowHeight(string row, RowDefinition def) { if (int.TryParse(row, out int rowHeight)) def.Height = new GridLength(Math.Max(60, rowHeight), GridUnitType.Absolute); else if (row.Contains("*")) { def.Height = int.TryParse(row.Substring(0, row.IndexOf("*")), out int result) ? new GridLength(result, GridUnitType.Star) : new GridLength(1, GridUnitType.Star); } else def.Height = new GridLength(1, GridUnitType.Auto); } private static ColumnDefinition SetColumnWidth(string col, ColumnDefinition def) { if (int.TryParse(col, out int colWidth)) def = new ColumnDefinition { Width = new GridLength(colWidth, GridUnitType.Absolute) }; else if (col.Contains("*")) { def = int.TryParse(col.Substring(0, col.IndexOf("*")), out int result) ? new ColumnDefinition { Width = new GridLength(result, GridUnitType.Star) } : new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }; } return def; } private void LoadViewAccordingToElement(DFLayoutControl element) { // Not sure why this is here - perhaps to ry and handle malformed element definitions? if (string.IsNullOrEmpty(element.Description)) return; if (element is DFLayoutLabel l) { var result = new DigitalFormLabel() { Definition = l }; AddViewToGrid(result, element); } else if (element is DFLayoutImage img ) { var result = new DigitalFormImage() { Definition = img }; AddViewToGrid(result, element); } else if (element is DFLayoutHeader hdr) { var result = new DigitalFormHeader() { Definition = hdr }; result.CollapsedChanged += (sender, args) => AdjustHeaderSection(result, result.Collapsed); if (hdr.Collapsed) headersToCollapse.Add(result); AddViewToGrid(result, element); } else if (element is DFLayoutBooleanField b) CreateView(b); else if (element is DFLayoutStringField s) { if (s.Properties.PopupEditor) CreateView(s); else if (s.Properties.TextWrapping) CreateView(s); else CreateView(s); } else if (element is DFLayoutIntegerField i) CreateView(i); else if (element is DFLayoutDoubleField d) CreateView(d); else if (element is DFLayoutDateField df) CreateView(df); else if (element is DFLayoutTimeField tf) CreateView(tf); else if (element is DFLayoutOptionField of) { if (of.Properties.OptionType == DFLayoutOptionType.Radio) CreateView(of); else if (of.Properties.OptionType == DFLayoutOptionType.Buttons) CreateView(of); else CreateView(of); } else if (element is DFLayoutLookupField lf) { if (lf.Properties.DisplayType == DFLayoutLookupDisplayType.Combo) CreateView(lf); else CreateView(lf); } else if (element is DFLayoutSignaturePad sp) CreateView(sp); else if (element is DFLayoutEmbeddedImage ei) CreateView(ei); else if (element is DFLayoutVideoField ev) CreateView(ev); else if (element is DFLayoutMultiImage mi) CreateView, DFLayoutEmbeddedMediaValues>(mi); else if (element is DFLayoutMultiSignaturePad ms) { CreateView, Dictionary?>(ms); //var tuple = LoadMultiSignaturePad(element); //AddViewToGrid(tuple.Item1, element); } else if (element is DFLayoutAddTaskField at) { CreateView(at); //var tuple = LoadAddTaskField(element); //AddViewToGrid(tuple.Item1, element); } } private TField CreateView(TDefinition definition) where TField : View, IDigitalFormField, new() where TDefinition : DFLayoutField where TProperties : DFLayoutFieldProperties, new() { TField item = new TField() { Definition = definition, IsEnabled = !definition.GetProperties().Secure && _model.Instance.FormCompleted.IsEmpty() && string.IsNullOrWhiteSpace(definition.GetPropertyValue("Expression")), }; item.ValueChanged += (sender, args) => { // Update Object? var property = args.Definition.GetProperties().Property; if (!String.IsNullOrWhiteSpace(property)) CoreUtils.SetPropertyValue(_model.Entity,property,args.Value); foreach (var other in Bindings.Keys.Where(x=>x.Name != args.Definition.Name)) { var linked = other.GetProperties().Property; if (!String.IsNullOrWhiteSpace(linked)) { var linkedvalue = CoreUtils.GetPropertyValue(_model.Entity, linked); // Problem - linkedvalue might not be the same as the //CoreUtils.SetPropertyValue(pairs[other],"Value",linkedvalue); } } // Update ChangedLinkedProperties? // Run Expressions and Scripts? // Update Dictionary? }; Bindings[definition] = item; AddViewToGrid(item, definition); return item; } private void AddViewToGrid(View view, DFLayoutControl element) { //rows and columns coming in from the desktop designer start from 1, not 0 (most of the time??) SetRow(view, Math.Max(0,element.Row - 1)); SetRowSpan(view, Math.Max(1,element.RowSpan)); SetColumn(view, Math.Max(0,element.Column - 1)); SetColumnSpan(view, Math.Max(1,element.ColumnSpan)); Children.Add(view); } private void AdjustHeaderSection(DigitalFormHeader header, bool collapsed) { try { var thisHeaderRow = GetRow(header); var headerRows = Children.OfType().Select(x => GetRow(x)).OrderBy(x=>x).ToArray(); if (headerRows.Any()) AdjustHeightsToNextHeader(headerRows, thisHeaderRow, collapsed); else AdjustHeightsBelowHeader(headerRows, thisHeaderRow, collapsed); } catch (Exception ex) { MobileLogging.Log(ex, "AdjustHeaderSection()"); } } private void AdjustHeightsToNextHeader(int[] headerRows, int thisHeaderRow, bool collapsed) { int nextHeaderRow = headerRows[0]; for (int i = thisHeaderRow + 1; i < nextHeaderRow; i++) AdjustHeight(i, collapsed); } private void AdjustHeightsBelowHeader(int[] headerRows, int thisHeaderRow, bool collapsed) { for (int i = thisHeaderRow + 1; i < RowDefinitions.Count; i++) AdjustHeight(i, collapsed); } private void AdjustHeight(int i, bool collapsed) { var rowdef = RowDefinitions[i]; if (collapsed) SetRowHeight(_layout.RowHeights[i], rowdef); else rowdef.Height = 0; } // private Tuple LoadMultiSignaturePad(DFLayoutControl element) // { // string value = ""; // DFLayoutMultiSignaturePad dfLayoutMultiSignaturePad = element as DFLayoutMultiSignaturePad; // DataButtonControl button = new DataButtonControl // { // Text = "Add Signatures", // }; // if (Storage.TryGetValue(dfLayoutMultiSignaturePad.Name, out value)) // { // button.Data = value; // } // MultiSignaturePad multiSignaturePad = new MultiSignaturePad(button.Data); // multiSignaturePad.OnMultiSignatureSaved += (result) => // { // button.Data = result; // }; // button.Clicked += (object sender, MobileButtonClickEventArgs e) => // { // Navigation.PushAsync(multiSignaturePad); // }; // // if (dfLayoutMultiSignaturePad.Properties.Required) // { // button.BackgroundColor = isRequiredColor; // button.BorderColor = isRequiredFrame; // } // else if (!_model.Instance.FormCompleted.IsEmpty()) // { // button.BackgroundColor = Color.Silver; // button.BorderColor = Color.Gray; // } // // return new Tuple(button, dfLayoutMultiSignaturePad.Properties.Required); // } // private Tuple LoadAddTaskField(DFLayoutControl element) // { // string value = ""; // DFLayoutAddTaskField field = element as DFLayoutAddTaskField; // DFCreateTaskView taskView = new DFCreateTaskView(); // taskView.OnAddTaskButtonClicked += () => // { // AddEditTask addEditTask = new AddEditTask(Guid.Empty, "New task from Digital Form"); // if (field.Properties.TaskType != null) // { // addEditTask.kanban.Type.ID = field.Properties.TaskType.ID; // try // { // addEditTask.kanban.Type.Description = new Client().Query( // new Filter(x => x.ID).IsEqualTo(field.Properties.TaskType.ID), // new Columns(x => x.Description) // ).Rows.FirstOrDefault()?.Get(c=>c.Description) ?? ""; // } // catch (Exception ex) // { // MobileLogging.Log(ex,"LoadAddTaskField"); // } // addEditTask.UpdateScreen(true); // } // addEditTask.OnTaskSaved += (taskNumber) => // { // taskView.DisableButton(); // taskView.TaskNumber = taskNumber.ToString(); // }; // Navigation.PushAsync(addEditTask); // }; // if (Storage.TryGetValue(field.Name, out value)) // { // taskView.TaskNumber = value; // taskView.DisableButton(); // } // // return new Tuple(taskView, field.Properties.Required); // } #endregion #region Checks / Custom methods private void CheckRequired(DFLayoutField field) { if (field is DFLayoutStringField) { if ((field as DFLayoutStringField).Properties.Required) { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutStringField).Description; } } else if (field is DFLayoutIntegerField) { if ((field as DFLayoutIntegerField).Properties.Required) { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutIntegerField).Description; } } else if (field is DFLayoutDoubleField) { if ((field as DFLayoutDoubleField).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutIntegerField).Description; } } } else if (field is DFLayoutBooleanField) { if ((field as DFLayoutBooleanField).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutBooleanField).Description; } } } else if (field is DFLayoutDateField) { if ((field as DFLayoutDateField).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutDateField).Description; } } } else if (field is DFLayoutTimeField) { if ((field as DFLayoutTimeField).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutTimeField).Description; } } } else if (field is DFLayoutOptionField) { if ((field as DFLayoutOptionField).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutOptionField).Description; } } } else if (field is DFLayoutLookupField) { if ((field as DFLayoutLookupField).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutLookupField).Description; } } } else if (field is DFLayoutSignaturePad) { if ((field as DFLayoutSignaturePad).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutSignaturePad).Description; } } } else if (field is DFLayoutEmbeddedImage) { if ((field as DFLayoutEmbeddedImage).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutEmbeddedImage).Description; } } } else if (field is DFLayoutMultiImage) { if ((field as DFLayoutMultiImage).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutMultiImage).Description; } } } else if (field is DFLayoutMultiSignaturePad) { if ((field as DFLayoutMultiSignaturePad).Properties.Required) { { isRequiredEmpty = true; isRequiredMessage = (field as DFLayoutMultiSignaturePad).Description; } } } } // // { // StreamImageSource streamImageSource = (StreamImageSource)source; // System.Threading.CancellationToken cancellationToken = System.Threading.CancellationToken.None; // Task task = streamImageSource.Stream(cancellationToken); // Stream stream = task.Result; // byte[] bytes = new byte[stream.Length]; // stream.Read(bytes, 0, bytes.Length); // string s = Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks); // return s; // } // // private string FindIDForDesignLookupField(DataButtonControl button) // { // try // { // string userInputValue = button.Data; // string guidString = ""; // if (userInputValue != nullOrInvalidType) // { // foreach (KeyValuePair> pair in dfLayoutLookupFieldLookupOptions) // { // foreach (KeyValuePair innerPair in pair.Value) // { // if (innerPair.Value == userInputValue) // { // guidString = innerPair.Key.ToString(); // } // } // } // } // // return guidString; // } // catch (Exception e) // { // MobileLogging.Log(e,"FindIDForDesignLookupField"); // return ""; // } // } #endregion #region Save Completion public void DeleteForm() { _model.Instance.FormCancelled = DateTime.Now; _model.Update(null); } //point of determination for whether or not QA form gets completed or saved for later private void UpdateModel(DFSaveStorage save, bool saveForLater) { if (save.Count() > 0 && _model != null) { // Blobs are uploaded in the background by DigitalFormDocumentHandler class, // so the blob breakup doesn't have to happen anymore :-) DigitalForm.SerializeFormData(_model.Instance, _model.Variables, save); if (_model.Instance.FormStarted == DateTime.MinValue) _model.Instance.FormStarted = timeStarted; _model.Instance.FormOpen += (DateTime.Now - timeStarted); if (!saveForLater) { _model.Instance.FormCompleted = DateTime.Now; _model.Instance.FormCompletedBy.ID = App.Data.Me.UserID; _model.Instance.Location.Longitude = location.Longitude; _model.Instance.Location.Latitude = location.Latitude; _model.Instance.Location.Timestamp = _model.Instance.FormCompleted; } _model.Update(null); } } #endregion #region Expressions public object GetFieldValue(string name) { try { var definition = Bindings.Keys.FirstOrDefault(x => String.Equals(x.Name, name)); if (definition != null) return CoreUtils.GetPropertyValue(Bindings[definition],"Value"); } catch (Exception e) { MobileLogging.Log(e,"SetFieldValue"); } return null; } public void SetFieldValue(string name, object value) { try { var definition = Bindings.Keys.FirstOrDefault(x => String.Equals(x.Name, name)); if (definition != null) CoreUtils.SetPropertyValue(Bindings[definition],"Value",value); } catch (Exception e) { MobileLogging.Log(e,"SetFieldValue"); } } public object GetFieldData(string fieldName, string dataField) { var definition = Bindings.Keys.FirstOrDefault(x => String.Equals(x.Name, fieldName)); if ((definition != null) && (Bindings[definition] is DigitalFormLookupView lookup)) return lookup.OtherValue(dataField); return null; } public void SetFieldColour(string field, System.Drawing.Color? colour = null) { if (colour != null) { try { System.Drawing.Color color = (System.Drawing.Color)colour; var definition = Bindings.Keys.FirstOrDefault(x => String.Equals(x.Name, field)); if (definition != null) { Bindings[definition].BackgroundColor = Color.FromRgba( Convert.ToDouble(color.R), Convert.ToDouble(color.G), Convert.ToDouble(color.B), Convert.ToDouble(color.A) ); } } catch (Exception e) { MobileLogging.Log(e,"SetFieldColor"); } } } #endregion } }