JobDocViewer.xaml.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. using comal.timesheets.CustomControls;
  2. using comal.timesheets.Data_Classes;
  3. using Comal.Classes;
  4. using InABox.Clients;
  5. using InABox.Core;
  6. using Syncfusion.TreeView.Engine;
  7. using Syncfusion.XForms.PopupLayout;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Collections.ObjectModel;
  11. using System.ComponentModel;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Text;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. using Xamarin.Essentials;
  18. using Xamarin.Forms;
  19. using Xamarin.Forms.Xaml;
  20. using XF.Material.Forms.UI.Dialogs;
  21. using PRSSecurity = InABox.Core.Security;
  22. namespace comal.timesheets
  23. {
  24. public delegate void OnFolderListChanged();
  25. [XamlCompilation(XamlCompilationOptions.Compile)]
  26. public partial class JobDocViewer : ContentPage
  27. {
  28. #region Fields and Constructor / Navigation
  29. ObservableCollection<FolderViewItem> folderList = new ObservableCollection<FolderViewItem>();
  30. ObservableCollection<FolderViewItem> displayList = new ObservableCollection<FolderViewItem>();
  31. DeviceIdiom idiom;
  32. bool foldersLoaded = false;
  33. bool filesLoaded = false;
  34. bool specificQueryLoading = false;
  35. Guid jobID = Guid.Empty;
  36. List<Guid> loadedFolders = new List<Guid>();
  37. List<MileStoneShell> mileStoneShells = new List<MileStoneShell>();
  38. List<JobDocSetFileShell> shells = new List<JobDocSetFileShell>();
  39. List<JobDocSetFileShell> searchList = new List<JobDocSetFileShell>();
  40. List<MileStoneShell> specificQueryMileStones = new List<MileStoneShell>();
  41. List<JobDocSetFileShell> specificQueryFileShells = new List<JobDocSetFileShell>();
  42. List<JobDocSetFileShell> currentQueryFileShells = new List<JobDocSetFileShell>();
  43. public JobDocViewer(Job job)
  44. {
  45. InitializeComponent();
  46. NavigationPage.SetHasBackButton(this, false);
  47. titleLbl.Text = "Job " + job.JobNumber;
  48. treeView.QueryNodeSize += TreeView_QueryNodeSize;
  49. idiom = DeviceInfo.Idiom;
  50. if (Device.RuntimePlatform.Equals(Device.iOS))
  51. {
  52. expandImg.HeightRequest = 50;
  53. expandImg.WidthRequest = 50;
  54. collapseImg.HeightRequest = 50;
  55. collapseImg.WidthRequest = 50;
  56. }
  57. jobID = job.ID;
  58. LoadFiles(new Filter<JobDocumentSetMileStone>(x => x.DocumentSet.Job.ID).IsEqualTo(jobID));
  59. LoadFolders(jobID);
  60. }
  61. void ExitBtn_Clicked(object sender, EventArgs e)
  62. {
  63. Navigation.PopAsync();
  64. }
  65. #endregion
  66. #region TreeView / Folder Interaction
  67. void Folder_Tapped(object sender, EventArgs e)
  68. {
  69. var folder = treeView.SelectedItem as FolderViewItem;
  70. if (folder.Documents == 0)
  71. return;
  72. if (specificQueryLoading)
  73. return;
  74. if (filesLoaded)
  75. {
  76. if (folder.ItemName == "All")
  77. {
  78. AddSearchItems(shells);
  79. }
  80. else
  81. {
  82. var list = shells.Where(x => x.FolderID == folder.ID);
  83. AddSearchItems(list);
  84. }
  85. }
  86. else if (loadedFolders.Contains(folder.ID))
  87. {
  88. var list = specificQueryFileShells.Where(x => x.FolderID == folder.ID);
  89. AddSearchItems(list);
  90. }
  91. else
  92. LoadFilesForFolder(folder.ID);
  93. }
  94. private async void LoadFilesForFolder(Guid folderID)
  95. {
  96. currentQueryFileShells.Clear();
  97. specificQueryLoading = true;
  98. ShowLoading();
  99. LoadFiles(new Filter<JobDocumentSetMileStone>(x => x.DocumentSet.Folder.ID).IsEqualTo(folderID), true, folderID);
  100. }
  101. void Expand_Tapped(object sender, EventArgs e)
  102. {
  103. if (!foldersLoaded)
  104. return;
  105. treeViewFrame.HeightRequest = 28 * folderList.Count;
  106. expandImg.IsVisible = false;
  107. collapseImg.IsVisible = true;
  108. }
  109. void Collapse_Tapped(object sender, EventArgs e)
  110. {
  111. treeViewFrame.HeightRequest = 120;
  112. expandImg.IsVisible = true;
  113. collapseImg.IsVisible = false;
  114. }
  115. #endregion
  116. #region Searching
  117. private void SearchEnt_Changed(object sender, EventArgs e)
  118. {
  119. if (specificQueryLoading)
  120. return;
  121. RunSearch();
  122. }
  123. private void AddSearchItems(IEnumerable<JobDocSetFileShell> list)
  124. {
  125. searchList.Clear();
  126. foreach (var v in list)
  127. {
  128. searchList.Add(v);
  129. }
  130. RunSearch();
  131. }
  132. private void RunSearch()
  133. {
  134. Device.BeginInvokeOnMainThread(() =>
  135. {
  136. listView.ItemsSource = null;
  137. if (string.IsNullOrWhiteSpace(searchEnt.Text))
  138. {
  139. listView.ItemsSource = searchList;
  140. fileCountLbl.Text = "Files (" + searchList.Count() + ")";
  141. }
  142. else
  143. {
  144. var list = searchList.Where
  145. (
  146. x =>
  147. x.FileName.Contains(searchEnt.Text) || x.FileName.Contains(searchEnt.Text.ToLower()) ||
  148. x.FileName.Contains(searchEnt.Text.ToUpper()) || x.FileName.Contains(SearchUtils.UpperCaseFirst(searchEnt.Text)) ||
  149. x.DocSetDescription.Contains(searchEnt.Text) || x.DocSetDescription.Contains(searchEnt.Text.ToLower()) ||
  150. x.DocSetDescription.Contains(searchEnt.Text.ToUpper()) || x.DocSetDescription.Contains(SearchUtils.UpperCaseFirst(searchEnt.Text)) ||
  151. x.Issued.Contains(searchEnt.Text) || x.Issued.Contains(searchEnt.Text.ToLower()) ||
  152. x.Issued.Contains(searchEnt.Text.ToUpper()) || x.Issued.Contains(SearchUtils.UpperCaseFirst(searchEnt.Text)) ||
  153. x.TrimmedIssued.Contains(searchEnt.Text) || x.TrimmedIssued.Contains(searchEnt.Text.ToLower()) ||
  154. x.TrimmedIssued.Contains(searchEnt.Text.ToUpper()) || x.TrimmedIssued.Contains(SearchUtils.UpperCaseFirst(searchEnt.Text)) ||
  155. x.TrimmedIssued.Contains(SearchUtils.UpperCaseSecond(searchEnt.Text)) || x.TrimmedIssued.Contains(SearchUtils.UpperCaseThird(searchEnt.Text)) || x.TrimmedIssued.Contains(SearchUtils.UpperCaseFourth(searchEnt.Text))
  156. );
  157. listView.ItemsSource = list;
  158. fileCountLbl.Text = "Files (" + list.Count() + ")";
  159. }
  160. });
  161. }
  162. #endregion
  163. #region Loading
  164. private void TreeView_QueryNodeSize(object sender, Syncfusion.XForms.TreeView.QueryNodeSizeEventArgs e)
  165. {
  166. e.Height = e.GetActualNodeHeight();
  167. e.Handled = true;
  168. }
  169. private void LoadFolders(Guid jobid)
  170. {
  171. CoreTable table = new Client<JobDocumentSetFolder>().Query(new Filter<JobDocumentSetFolder>(x => x.Job.ID).IsEqualTo(jobid),
  172. new Columns<JobDocumentSetFolder>(x => x.ID, x => x.Parent.ID, x => x.Name, x => x.Documents));
  173. foreach (CoreRow row in table.Rows)
  174. {
  175. FolderViewItem folderItem = new FolderViewItem
  176. {
  177. ID = row.Get<Guid>("ID"),
  178. ParentID = row.Get<Guid>("Parent.ID"),
  179. ItemName = row.Get<string>("Name"),
  180. Documents = row.Get<int>("Documents")
  181. };
  182. folderList.Add(folderItem);
  183. }
  184. foreach (var folder in folderList)
  185. {
  186. folder.List = folderList;
  187. if (folder.ParentID == Guid.Empty || folder.ParentID == CoreUtils.FullGuid)
  188. displayList.Add(folder);
  189. }
  190. displayList = new ObservableCollection<FolderViewItem>(displayList.OrderBy(x => x.ItemName));
  191. treeView.ItemsSource = displayList;
  192. foldersLoaded = true;
  193. }
  194. private async void LoadFiles(Filter<JobDocumentSetMileStone> filter, bool specificQuery = false, Guid loadFolder = new Guid())
  195. {
  196. await Task.Run(() =>
  197. {
  198. try
  199. {
  200. filter = filter.And(x => x.Status).IsEqualTo(JobDocumentSetMileStoneStatus.Approved)
  201. .And(x => x.Type.SiteVisible).IsEqualTo(true);
  202. CoreTable milestones = new Client<JobDocumentSetMileStone>().Query(filter,
  203. new Columns<JobDocumentSetMileStone>(
  204. x => x.ID,
  205. x => x.DocumentSet.Folder.Name,
  206. x => x.DocumentSet.Folder.ID,
  207. x => x.Submitted,
  208. x => x.Employee.Name,
  209. x => x.Type.Description,
  210. x => x.DocumentSet.Description
  211. )
  212. );
  213. if (milestones.Rows.Any())
  214. {
  215. var table = QueryFiles(milestones, specificQuery);
  216. if (table.Rows.Any())
  217. {
  218. foreach (CoreRow filerow in table.Rows)
  219. {
  220. AddFileShell(filerow, specificQuery);
  221. }
  222. Device.BeginInvokeOnMainThread(() =>
  223. {
  224. if (specificQuery)
  225. {
  226. ShowFiles();
  227. specificQueryLoading = false;
  228. loadedFolders.Add(loadFolder);
  229. AddSearchItems(currentQueryFileShells);
  230. }
  231. else
  232. {
  233. filesLoaded = true;
  234. displayList.Insert(0, new FolderViewItem { ItemName = "All", Documents = shells.Count });
  235. folderList.Insert(0, new FolderViewItem { ItemName = "All", Documents = shells.Count });
  236. treeView.ItemsSource = null;
  237. treeView.ItemsSource = displayList;
  238. }
  239. });
  240. }
  241. }
  242. else
  243. {
  244. specificQueryLoading = false;
  245. ShowFiles();
  246. }
  247. }
  248. catch
  249. {
  250. specificQueryLoading = false;
  251. ShowFiles();
  252. }
  253. });
  254. }
  255. private CoreTable QueryFiles(CoreTable milestones, bool specificQuery = false)
  256. {
  257. Filter<JobDocumentSetMileStoneFile> filter = new Filter<JobDocumentSetMileStoneFile>(x => x.EntityLink.ID).IsEqualTo(milestones.Rows.FirstOrDefault().Get<Guid>("ID"));
  258. foreach (CoreRow milestonerow in milestones.Rows)
  259. {
  260. Guid id = AddMileStoneShell(milestonerow, specificQuery);
  261. filter = filter.Or(x => x.EntityLink.ID).IsEqualTo(milestonerow.Get<Guid>("ID"));
  262. }
  263. CoreTable table = new Client<JobDocumentSetMileStoneFile>().Query(
  264. filter,
  265. new Columns<JobDocumentSetMileStoneFile>(
  266. x => x.DocumentLink.ID,
  267. x => x.Thumbnail,
  268. x => x.DocumentLink.FileName,
  269. x => x.EntityLink.ID
  270. ));
  271. return table;
  272. }
  273. private Guid AddMileStoneShell(CoreRow milestonerow, bool specificQuery = false)
  274. {
  275. Guid id = milestonerow.Get<Guid>("ID");
  276. MileStoneShell shell = new MileStoneShell
  277. {
  278. ID = milestonerow.Get<Guid>("ID"),
  279. FolderName = milestonerow.Get<string>("DocumentSet.Folder.Name"),
  280. FolderID = milestonerow.Get<Guid>("DocumentSet.Folder.ID"),
  281. EmployeeName = milestonerow.Get<string>("Employee.Name"),
  282. Type = milestonerow.Get<string>("Type.Description"),
  283. DocSetDescription = milestonerow.Get<string>("DocumentSet.Description"),
  284. Issued = "Issued: " + milestonerow.Get<DateTime>("Issued").ToString("dd MMM yy")
  285. };
  286. if (specificQuery)
  287. specificQueryMileStones.Add(shell);
  288. else
  289. mileStoneShells.Add(shell);
  290. return id;
  291. }
  292. private void AddFileShell(CoreRow filerow, bool specificQuery = false)
  293. {
  294. JobDocSetFileShell file = new JobDocSetFileShell
  295. {
  296. DocLinkID = filerow.Get<Guid>("DocumentLink.ID"),
  297. FileName = filerow.Get<string>("DocumentLink.FileName"),
  298. MileStoneID = filerow.Get<Guid>("EntityLink.ID")
  299. };
  300. MileStoneShell mileStone = mileStoneShells.Find(x => x.ID == file.MileStoneID);
  301. file.FolderName = mileStone.FolderName;
  302. file.FolderID = mileStone.FolderID;
  303. file.EmployeeName = mileStone.EmployeeName;
  304. file.Type = mileStone.Type;
  305. file.DocSetDescription = mileStone.DocSetDescription;
  306. file.Issued = mileStone.Issued;
  307. file.TrimmedIssued = TrimWhiteSpace(file.Issued);
  308. byte[] data = filerow.Get<byte[]>("Thumbnail");
  309. if (data != null)
  310. {
  311. file.ImageSource = ImageSource.FromStream(() => new MemoryStream(data));
  312. if (idiom == DeviceIdiom.Tablet)
  313. {
  314. file.HeightRequest = 400;
  315. file.WidthRequest = 500;
  316. }
  317. file.Thumbnail = data;
  318. }
  319. if (specificQuery)
  320. {
  321. specificQueryFileShells.Add(file);
  322. currentQueryFileShells.Add(file);
  323. }
  324. else
  325. shells.Add(file);
  326. }
  327. private void ShowFiles()
  328. {
  329. Device.BeginInvokeOnMainThread(() =>
  330. {
  331. treeView.IsEnabled = true;
  332. loadingLayout.IsVisible = false;
  333. loadingColumn.Width = 0;
  334. filesLayout.IsVisible = true;
  335. filesColumn.Width = new GridLength(1, GridUnitType.Star);
  336. });
  337. }
  338. private void ShowLoading()
  339. {
  340. Device.BeginInvokeOnMainThread(async () =>
  341. {
  342. treeView.IsEnabled = false;
  343. filesLayout.IsVisible = false;
  344. filesColumn.Width = 0;
  345. loadingLayout.IsVisible = true;
  346. loadingColumn.Width = new GridLength(1, GridUnitType.Star);
  347. Random random = new Random();
  348. uint number = (uint)random.Next(500, 3000);
  349. await loadingLbl.TranslateTo(0, 15, 500);
  350. await loadingLbl.TranslateTo(0, 0, 500);
  351. loadingLbl.RotateTo(360, number);
  352. await loadingLbl.TranslateTo(0, 15, 500);
  353. await loadingLbl.TranslateTo(0, 0, 500);
  354. await loadingLbl.TranslateTo(0, 15, 500);
  355. await loadingLbl.TranslateTo(0, 0, 500);
  356. await loadingLbl.TranslateTo(0, 15, 500);
  357. number = (uint)random.Next(500, 3000);
  358. await loadingLbl.TranslateTo(0, 0, 500);
  359. loadingLbl.RotateTo(360, number);
  360. });
  361. }
  362. static string TrimWhiteSpace(string s)
  363. {
  364. s = String.Concat(s.Where(c => !Char.IsWhiteSpace(c)));
  365. return s;
  366. }
  367. #endregion
  368. #region ListView Interaction
  369. void List_Tapped(object sender, EventArgs e)
  370. {
  371. var shell = listView.SelectedItem as JobDocSetFileShell;
  372. if (Device.RuntimePlatform.Equals(Device.Android) && PRSSecurity.IsAllowed<CanOpenMobileNativePDFViewer>())
  373. OpenNativeViewer(shell.DocLinkID);
  374. else
  375. {
  376. PDFViewer viewer = new PDFViewer(shell.DocLinkID);
  377. Navigation.PushAsync(viewer);
  378. }
  379. }
  380. private async void OpenNativeViewer(Guid docID)
  381. {
  382. CoreTable table = new Client<Document>().Query(new Filter<Document>(x => x.ID).IsEqualTo(docID),
  383. new Columns<Document>(x => x.Data, x => x.FileName));
  384. Document doc = table.Rows.First().ToObject<Document>();
  385. var filePath = Path.Combine(FileSystem.AppDataDirectory, doc.FileName);
  386. File.WriteAllBytes(filePath, doc.Data);
  387. await Launcher.OpenAsync(new OpenFileRequest
  388. {
  389. File = new ReadOnlyFile(filePath)
  390. });
  391. }
  392. void Image_Tapped(object sender, EventArgs e)
  393. {
  394. var shell = ((TappedEventArgs)e).Parameter as JobDocSetFileShell;
  395. if (shell == null)
  396. return;
  397. if (shell.Thumbnail != null)
  398. {
  399. Image popupContent = new Image();
  400. popupContent.HeightRequest = 500;
  401. popupContent.WidthRequest = 600;
  402. popupContent.HorizontalOptions = LayoutOptions.FillAndExpand;
  403. popupContent.VerticalOptions = LayoutOptions.FillAndExpand;
  404. popupContent.Aspect = Aspect.AspectFit;
  405. popupContent.Source = ImageSource.FromStream(() => new MemoryStream(shell.Thumbnail));
  406. popupLayout.PopupView.ShowHeader = false;
  407. popupLayout.PopupView.ShowFooter = false;
  408. popupLayout.PopupView.ContentTemplate = new DataTemplate(() =>
  409. {
  410. return popupContent;
  411. });
  412. popupLayout.Show();
  413. }
  414. }
  415. #endregion
  416. }
  417. #region Classes
  418. public class FolderViewItem : INotifyPropertyChanged
  419. {
  420. public event OnFolderListChanged OnFolderListChanged;
  421. public event PropertyChangedEventHandler PropertyChanged;
  422. public Guid ID { get; set; }
  423. public Guid ParentID { get; set; }
  424. public ImageSource ImageIcon { get; set; }
  425. private ObservableCollection<FolderViewItem> list;
  426. public ObservableCollection<FolderViewItem> List
  427. {
  428. get
  429. {
  430. return list;
  431. }
  432. set
  433. {
  434. list = value;
  435. OnFolderListChanged?.Invoke();
  436. }
  437. }
  438. private string itemName;
  439. public string ItemName
  440. {
  441. get { return itemName; }
  442. set
  443. {
  444. itemName = value;
  445. RaisedOnPropertyChanged("ItemName");
  446. }
  447. }
  448. public int Documents { get; set; }
  449. public ObservableCollection<FolderViewItem> SubFiles { get; set; }
  450. public Guid DocID { get; set; }
  451. public FolderViewItem()
  452. {
  453. ID = Guid.Empty;
  454. ItemName = "";
  455. ImageIcon = "folder.png";
  456. DocID = Guid.Empty;
  457. ParentID = Guid.Empty;
  458. OnFolderListChanged += FolderViewItem_OnFolderListChanged;
  459. Documents = 0;
  460. }
  461. private void FolderViewItem_OnFolderListChanged()
  462. {
  463. GetChildren();
  464. }
  465. public void RaisedOnPropertyChanged(string _PropertyName)
  466. {
  467. if (PropertyChanged != null)
  468. {
  469. PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
  470. }
  471. }
  472. private void GetChildren()
  473. {
  474. SubFiles = new ObservableCollection<FolderViewItem>(list.Where(x => x.ParentID.Equals(ID)));
  475. }
  476. }
  477. public class MileStoneShell
  478. {
  479. public Guid ID { get; set; }
  480. public string FolderName { get; set; }
  481. public Guid FolderID { get; set; }
  482. public string EmployeeName { get; set; }
  483. public string Type { get; set; }
  484. public string DocSetDescription { get; set; }
  485. public string Issued { get; set; }
  486. public MileStoneShell()
  487. {
  488. ID = Guid.Empty;
  489. FolderID = Guid.Empty;
  490. FolderName = "";
  491. EmployeeName = "";
  492. Type = "";
  493. DocSetDescription = "";
  494. Issued = "";
  495. }
  496. }
  497. public class JobDocSetFileShell
  498. {
  499. public Guid DocLinkID { get; set; }
  500. public string FileName { get; set; }
  501. public string FolderName { get; set; }
  502. public Guid FolderID { get; set; }
  503. public string EmployeeName { get; set; }
  504. public string Type { get; set; }
  505. public string DocSetDescription { get; set; }
  506. public string Issued { get; set; }
  507. public string TrimmedIssued { get; set; }
  508. public ImageSource ImageSource { get; set; }
  509. public double HeightRequest { get; set; }
  510. public double WidthRequest { get; set; }
  511. public byte[] Thumbnail { get; set; }
  512. public Guid MileStoneID { get; set; }
  513. public JobDocSetFileShell()
  514. {
  515. DocLinkID = Guid.Empty;
  516. FileName = "";
  517. FolderName = "";
  518. EmployeeName = "";
  519. Type = "";
  520. DocSetDescription = "";
  521. ImageSource = null;
  522. Issued = "";
  523. TrimmedIssued = "";
  524. HeightRequest = 150;
  525. WidthRequest = 200;
  526. Thumbnail = null;
  527. MileStoneID = Guid.Empty;
  528. FolderID = Guid.Empty;
  529. }
  530. }
  531. #endregion
  532. }