using Comal.Classes; using InABox.Clients; using InABox.Core; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Threading.Tasks; using Xamarin.Essentials; using Xamarin.Forms; using Xamarin.Forms.Xaml; using PRSSecurity = InABox.Core.Security; namespace comal.timesheets { public delegate void OnFolderListChanged(); [XamlCompilation(XamlCompilationOptions.Compile)] public partial class JobDocuments : SitePage { #region Fields and Constructor / Navigation ObservableCollection folderList = new ObservableCollection(); ObservableCollection displayList = new ObservableCollection(); DeviceIdiom idiom; bool foldersLoaded = false; bool filesLoaded = false; bool specificQueryLoading = false; Dictionary filters = new Dictionary(); Guid jobID = Guid.Empty; List loadedFolders = new List(); List mileStoneShells = new List(); List fullListShells = new List(); List filteredShells = new List(); List searchList = new List(); List specificQueryMileStones = new List(); List specificQueryFileShells = new List(); List currentQueryFileShells = new List(); public JobDocuments() { InitializeComponent(); } protected override void JobLoaded() { //Title = Job.Item.DisplayName; treeView.QueryNodeSize += TreeView_QueryNodeSize; idiom = DeviceInfo.Idiom; if (Device.RuntimePlatform.Equals(Device.iOS)) { expandImg.HeightRequest = 50; expandImg.WidthRequest = 50; collapseImg.HeightRequest = 50; collapseImg.WidthRequest = 50; } jobID = Job.Item.ID; LoadFiles(new Filter(x => x.DocumentSet.Job.ID).IsEqualTo(jobID)); LoadFolders(jobID); } void ExitBtn_Clicked(object sender, EventArgs e) { Navigation.PopAsync(); } private void FilterButton_Tapped(object sender, EventArgs e) { var page = new JobDocsFilterPage(jobID, filters); page.OnJobDocFiltersPicked += Page_OnJobDocFiltersPicked; Navigation.PushAsync(page); } private void Page_OnJobDocFiltersPicked(Dictionary newfilters) { filterLayout.Children.Clear(); filteredShells.Clear(); filters.Clear(); filters = new Dictionary(newfilters); AddFilters(); DisplayFolderContents(); } private void AddFilters() { foreach (var filter in filters) { JobDocFilterItem item = new JobDocFilterItem(); item.Init(filter.Key, filter.Value); item.OnJobDocFilterClosed += ((filter, type) => { filters.Remove(type); Page_OnJobDocFiltersPicked(new Dictionary(filters)); }); filterLayout.Children.Add(item); FilterList(filter.Key, filter.Value); } } private void FilterList(JobDocFilterType type, string text) { if (type == JobDocFilterType.Discipline) { var list = fullListShells.Where(x => x.DocSetDiscipline.Equals(text)); foreach (var v in list) CheckDuplicateAndAdd(v); } else if (type == JobDocFilterType.Type) { var list = fullListShells.Where(x => x.DocSetType.Equals(text)); foreach (var v in list) CheckDuplicateAndAdd(v); } else if (type == JobDocFilterType.Category) { var list = fullListShells.Where(x => x.DocSetCategory.Equals(text)); foreach (var v in list) CheckDuplicateAndAdd(v); } else if (type == JobDocFilterType.Area) { var list = fullListShells.Where(x => x.DocSetArea.Equals(text)); foreach (var v in list) CheckDuplicateAndAdd(v); } } private void CheckDuplicateAndAdd(JobDocSetFileShell v) { var duplicate = filteredShells.FirstOrDefault(x => x.ID == v.ID); if (duplicate == null) filteredShells.Add(v); } #endregion #region TreeView / Folder Interaction void Folder_Tapped(object sender, EventArgs e) { DisplayFolderContents(); } private void DisplayFolderContents() { if (treeView.SelectedItem == null) { DisplayAlert("Info", "Please select a folder or All folders", "OK"); return; } var folder = treeView.SelectedItem as FolderViewItem; if (folder.Documents == 0) return; if (specificQueryLoading) return; if (filesLoaded) { if (folder.ItemName == "All") { if (filters.Count != 0) AddSearchItems(filteredShells); else AddSearchItems(fullListShells); } else { if (filters.Count != 0) { var list = filteredShells.Where(x => x.FolderID == folder.ID); AddSearchItems(list); } else { var list = fullListShells.Where(x => x.FolderID == folder.ID); AddSearchItems(list); } } } else if (loadedFolders.Contains(folder.ID)) { var list = specificQueryFileShells.Where(x => x.FolderID == folder.ID); AddSearchItems(list); } else LoadFilesForFolder(folder.ID); } private async void LoadFilesForFolder(Guid folderID) { currentQueryFileShells.Clear(); specificQueryLoading = true; ShowLoading(); LoadFiles(new Filter(x => x.DocumentSet.Folder.ID).IsEqualTo(folderID), true, folderID); } void Expand_Tapped(object sender, EventArgs e) { if (!foldersLoaded) return; treeViewFrame.HeightRequest = 28 * folderList.Count; expandImg.IsVisible = false; collapseImg.IsVisible = true; } void Collapse_Tapped(object sender, EventArgs e) { treeViewFrame.HeightRequest = 120; expandImg.IsVisible = true; collapseImg.IsVisible = false; } #endregion #region Searching private void SearchEnt_Changed(object sender, EventArgs e) { if (specificQueryLoading) return; RunSearch(); } private void AddSearchItems(IEnumerable list) { searchList.Clear(); foreach (var v in list) { searchList.Add(v); } RunSearch(); } private void RunSearch() { Device.BeginInvokeOnMainThread(() => { listView.ItemsSource = null; if (string.IsNullOrWhiteSpace(searchEnt.Text)) { listView.ItemsSource = searchList; fileCountLbl.Text = "Files (" + searchList.Count() + ")"; } else { var list = searchList.Where(x => x.FileName.ToUpper().Contains(searchEnt.Text.ToUpper()) || x.DocSetDescription.ToUpper().Contains(searchEnt.Text.ToUpper()) || x.DocSetDescription.ToUpper().Contains(searchEnt.Text.ToUpper()) || x.Issued.ToUpper().Contains(searchEnt.Text.ToUpper()) || x.TrimmedIssued.ToUpper().Contains(searchEnt.Text.ToUpper()) ); listView.ItemsSource = list; fileCountLbl.Text = "Files (" + list.Count() + ")"; } }); } #endregion #region Loading private void TreeView_QueryNodeSize(object sender, Syncfusion.XForms.TreeView.QueryNodeSizeEventArgs e) { e.Height = e.GetActualNodeHeight(); e.Handled = true; } private void LoadFolders(Guid jobid) { Task.Run(() => { CoreTable table = QueryJobDocumentSetFolders(jobid); while (table == null) table = QueryJobDocumentSetFolders(jobid); Device.BeginInvokeOnMainThread(() => { foreach (CoreRow row in table.Rows) { FolderViewItem folderItem = new FolderViewItem { ID = row.Get("ID"), ParentID = row.Get("Parent.ID"), ItemName = row.Get("Name"), Documents = row.Get("Documents") }; folderList.Add(folderItem); } foreach (var folder in folderList) { folder.List = folderList; if (folder.ParentID == Guid.Empty || folder.ParentID == CoreUtils.FullGuid) displayList.Add(folder); } displayList = new ObservableCollection(displayList.OrderBy(x => x.ItemName)); treeView.ItemsSource = displayList; foldersLoaded = true; filterBtn.IsEnabled = true; }); }); } private CoreTable QueryJobDocumentSetFolders(Guid jobid) { try { return new Client().Query(new Filter(x => x.Job.ID).IsEqualTo(jobid), new Columns(x => x.ID, x => x.Parent.ID, x => x.Name, x => x.Documents)); } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex); return null; } } private void LoadFiles(Filter filter, bool specificQuery = false, Guid loadFolder = new Guid()) { Task.Run(() => { try { filter = filter.And(x => x.Status).IsEqualTo(JobDocumentSetMileStoneStatus.Approved) .And(x => x.Type.SiteVisible).IsEqualTo(true); CoreTable milestones = QueryJobDocumentSetMileStone(filter); while (milestones == null) milestones = QueryJobDocumentSetMileStone(filter); if (milestones.Rows.Any()) { var table = QueryFiles(milestones, specificQuery); if (table.Rows.Any()) { foreach (CoreRow filerow in table.Rows) { AddFileShell(filerow, specificQuery); } Device.BeginInvokeOnMainThread(() => { if (specificQuery) { ShowFiles(); specificQueryLoading = false; loadedFolders.Add(loadFolder); AddSearchItems(currentQueryFileShells); } else { filesLoaded = true; displayList.Insert(0, new FolderViewItem { ItemName = "All", Documents = fullListShells.Count }); folderList.Insert(0, new FolderViewItem { ItemName = "All", Documents = fullListShells.Count }); treeView.ItemsSource = null; treeView.ItemsSource = displayList; } }); } } else { specificQueryLoading = false; ShowFiles(); } } catch { specificQueryLoading = false; ShowFiles(); } }); } private CoreTable QueryJobDocumentSetMileStone(Filter filter) { try { return new Client().Query(filter, new Columns( x => x.ID, x => x.DocumentSet.Folder.Name, x => x.DocumentSet.Folder.ID, x => x.DocumentSet.Type.Description, x => x.DocumentSet.Category.Description, x => x.DocumentSet.Discipline.Description, x => x.DocumentSet.Area.Description, x => x.Submitted, x => x.Employee.Name, x => x.Type.Description, x => x.DocumentSet.Description ) ); } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex); return null; } } private CoreTable QueryFiles(CoreTable milestones, bool specificQuery = false) { Filter filter = new Filter(x => x.EntityLink.ID).IsEqualTo(milestones.Rows.FirstOrDefault().Get("ID")); foreach (CoreRow milestonerow in milestones.Rows) { Guid id = AddMileStoneShell(milestonerow, specificQuery); filter = filter.Or(x => x.EntityLink.ID).IsEqualTo(milestonerow.Get("ID")); } CoreTable table = DoQueryFiles(filter); while (table == null) table = DoQueryFiles(filter); return table; } private CoreTable DoQueryFiles(Filter filter) { try { return new Client().Query( filter, new Columns( x => x.ID, x => x.DocumentLink.ID, x => x.Thumbnail, x => x.DocumentLink.FileName, x => x.EntityLink.ID )); } catch (Exception ex) { InABox.Mobile.MobileLogging.Log(ex); return null; } } private Guid AddMileStoneShell(CoreRow milestonerow, bool specificQuery = false) { Guid id = milestonerow.Get("ID"); MileStoneShell shell = new MileStoneShell { ID = milestonerow.Get("ID"), FolderName = milestonerow.Get(x => x.DocumentSet.Folder.Name), FolderID = milestonerow.Get(x => x.DocumentSet.Folder.ID), EmployeeName = milestonerow.Get(x => x.Employee.Name), Type = milestonerow.Get(x => x.Type.Description), DocSetDescription = milestonerow.Get(x => x.DocumentSet.Description), Issued = "Submitted: " + milestonerow.Get(x => x.Submitted).ToString("dd MMM yy"), DocSetType = milestonerow.Get(x => x.DocumentSet.Type.Description), DocSetCategory = milestonerow.Get(x => x.DocumentSet.Category.Description), DocSetDiscipline = milestonerow.Get(x => x.DocumentSet.Discipline.Description), DocSetArea = milestonerow.Get(x => x.DocumentSet.Area.Description), }; if (specificQuery) specificQueryMileStones.Add(shell); else mileStoneShells.Add(shell); return id; } private void AddFileShell(CoreRow filerow, bool specificQuery = false) { JobDocSetFileShell file = new JobDocSetFileShell { ID = filerow.Get(x => x.ID), DocLinkID = filerow.Get(x => x.DocumentLink.ID), FileName = filerow.Get(x => x.DocumentLink.FileName), MileStoneID = filerow.Get(x => x.EntityLink.ID) }; MileStoneShell mileStone = mileStoneShells.Find(x => x.ID == file.MileStoneID); file.FolderName = mileStone.FolderName; file.FolderID = mileStone.FolderID; file.EmployeeName = mileStone.EmployeeName; file.Type = mileStone.Type; file.DocSetDescription = mileStone.DocSetDescription; file.Issued = mileStone.Issued; file.DocSetCategory = mileStone.DocSetCategory; file.DocSetType = mileStone.DocSetType; file.DocSetDiscipline = mileStone.DocSetDiscipline; file.DocSetArea = mileStone.DocSetArea; file.TrimmedIssued = TrimWhiteSpace(file.Issued); byte[] data = filerow.Get("Thumbnail"); if (data != null) { file.ImageSource = ImageSource.FromStream(() => new MemoryStream(data)); if (idiom == DeviceIdiom.Tablet) { file.HeightRequest = 400; file.WidthRequest = 500; } file.Thumbnail = data; } if (specificQuery) { specificQueryFileShells.Add(file); currentQueryFileShells.Add(file); } else fullListShells.Add(file); } private void ShowFiles() { Device.BeginInvokeOnMainThread(() => { treeView.IsEnabled = true; loadingLayout.IsVisible = false; loadingColumn.Width = 0; filesLayout.IsVisible = true; filesColumn.Width = new GridLength(1, GridUnitType.Star); }); } private void ShowLoading() { Device.BeginInvokeOnMainThread(async () => { treeView.IsEnabled = false; filesLayout.IsVisible = false; filesColumn.Width = 0; loadingLayout.IsVisible = true; loadingColumn.Width = new GridLength(1, GridUnitType.Star); Random random = new Random(); uint number = (uint)random.Next(500, 3000); await loadingLbl.TranslateTo(0, 15, 500); await loadingLbl.TranslateTo(0, 0, 500); loadingLbl.RotateTo(360, number); await loadingLbl.TranslateTo(0, 15, 500); await loadingLbl.TranslateTo(0, 0, 500); await loadingLbl.TranslateTo(0, 15, 500); await loadingLbl.TranslateTo(0, 0, 500); await loadingLbl.TranslateTo(0, 15, 500); number = (uint)random.Next(500, 3000); await loadingLbl.TranslateTo(0, 0, 500); loadingLbl.RotateTo(360, number); }); } static string TrimWhiteSpace(string s) { s = String.Concat(s.Where(c => !Char.IsWhiteSpace(c))); return s; } #endregion #region ListView Interaction void List_Tapped(object sender, EventArgs e) { var shell = listView.SelectedItem as JobDocSetFileShell; if (Device.RuntimePlatform.Equals(Device.Android) && PRSSecurity.IsAllowed()) OpenNativeViewer(shell.DocLinkID); else { PDFViewer viewer = new PDFViewer(shell.DocLinkID); Navigation.PushAsync(viewer); } } private async void OpenNativeViewer(Guid docID) { CoreTable table = new Client().Query(new Filter(x => x.ID).IsEqualTo(docID), new Columns(x => x.Data, x => x.FileName)); Document doc = table.Rows.First().ToObject(); var filePath = Path.Combine(FileSystem.AppDataDirectory, doc.FileName); File.WriteAllBytes(filePath, doc.Data); await Launcher.OpenAsync(new OpenFileRequest { File = new ReadOnlyFile(filePath) }); } void Image_Tapped(object sender, EventArgs e) { var shell = ((TappedEventArgs)e).Parameter as JobDocSetFileShell; if (shell == null) return; if (shell.Thumbnail != null) { Image popupContent = new Image(); popupContent.HeightRequest = 500; popupContent.WidthRequest = 600; popupContent.HorizontalOptions = LayoutOptions.FillAndExpand; popupContent.VerticalOptions = LayoutOptions.FillAndExpand; popupContent.Aspect = Aspect.AspectFit; popupContent.Source = ImageSource.FromStream(() => new MemoryStream(shell.Thumbnail)); popupLayout.PopupView.ShowHeader = false; popupLayout.PopupView.ShowFooter = false; popupLayout.PopupView.ContentTemplate = new DataTemplate(() => { return popupContent; }); popupLayout.Show(); } } #endregion } #region Classes public class FolderViewItem : INotifyPropertyChanged { public event OnFolderListChanged OnFolderListChanged; public event PropertyChangedEventHandler PropertyChanged; public Guid ID { get; set; } public Guid ParentID { get; set; } public ImageSource ImageIcon { get; set; } private ObservableCollection list; public ObservableCollection List { get { return list; } set { list = value; OnFolderListChanged?.Invoke(); } } private string itemName; public string ItemName { get { return itemName; } set { itemName = value; RaisedOnPropertyChanged("ItemName"); } } public int Documents { get; set; } public ObservableCollection SubFiles { get; set; } public Guid DocID { get; set; } public FolderViewItem() { ID = Guid.Empty; ItemName = ""; ImageIcon = "folder.png"; DocID = Guid.Empty; ParentID = Guid.Empty; OnFolderListChanged += FolderViewItem_OnFolderListChanged; Documents = 0; } private void FolderViewItem_OnFolderListChanged() { GetChildren(); } public void RaisedOnPropertyChanged(string _PropertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName)); } } private void GetChildren() { SubFiles = new ObservableCollection(list.Where(x => x.ParentID.Equals(ID))); } } public class MileStoneShell { public Guid ID { get; set; } public string FolderName { get; set; } public Guid FolderID { get; set; } public string EmployeeName { get; set; } public string Type { get; set; } public string DocSetDescription { get; set; } public string Issued { get; set; } public string DocSetType { get; set; } public string DocSetDiscipline { get; set; } public string DocSetCategory { get; set; } public string DocSetArea { get; set; } public MileStoneShell() { ID = Guid.Empty; FolderID = Guid.Empty; FolderName = ""; EmployeeName = ""; Type = ""; DocSetDescription = ""; Issued = ""; DocSetCategory = ""; DocSetDiscipline = ""; DocSetType = ""; DocSetArea = ""; } } public class JobDocSetFileShell { public Guid ID { get; set; } public Guid DocLinkID { get; set; } public string FileName { get; set; } public string FolderName { get; set; } public Guid FolderID { get; set; } public string EmployeeName { get; set; } public string Type { get; set; } public string DocSetDescription { get; set; } public string Issued { get; set; } public string TrimmedIssued { get; set; } public ImageSource ImageSource { get; set; } public double HeightRequest { get; set; } public double WidthRequest { get; set; } public byte[] Thumbnail { get; set; } public Guid MileStoneID { get; set; } public string DocSetType { get; set; } public string DocSetDiscipline { get; set; } public string DocSetCategory { get; set; } public string DocSetArea { get; set; } public JobDocSetFileShell() { ID = Guid.Empty; DocLinkID = Guid.Empty; FileName = ""; FolderName = ""; EmployeeName = ""; Type = ""; DocSetDescription = ""; ImageSource = null; Issued = ""; TrimmedIssued = ""; HeightRequest = 150; WidthRequest = 200; Thumbnail = null; MileStoneID = Guid.Empty; FolderID = Guid.Empty; DocSetCategory = ""; DocSetDiscipline = ""; DocSetType = ""; DocSetArea = ""; } } public enum JobDocFilterType { Discipline, Type, Category, Area } #endregion }