MainPage.xaml.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using System.Windows.Input;
  7. using Xamarin.Forms;
  8. using InABox.Core;
  9. using InABox.Clients;
  10. using InABox.Mobile;
  11. using Comal.Classes;
  12. using InABox.Configuration;
  13. using Xamarin.Forms.Xaml;
  14. using XF.Material.Forms.UI.Dialogs;
  15. using Comal.Classes.SecurityDescriptors;
  16. namespace PRS.Mobile
  17. {
  18. public class MainPageSettings : ILocalConfigurationSettings
  19. {
  20. public bool ClockOnDisabled
  21. {
  22. get;
  23. set;
  24. }
  25. public MainPageSettings()
  26. {
  27. ClockOnDisabled = true;
  28. }
  29. }
  30. public partial class MainPage
  31. {
  32. #region Fields
  33. private MainPageSettings _settings;
  34. private TimeSheet _currenttimesheet;
  35. #endregion
  36. #region Constructor
  37. private readonly Task[] _setuptasks;
  38. public MainPage()
  39. {
  40. _settings = new LocalConfiguration<MainPageSettings>().Load();
  41. _setuptasks = new Task[]
  42. {
  43. //Task.Run(() => App.Data.Activities.Refresh(false)),
  44. //Task.Run(() => App.Data.Employees.Refresh(false)),
  45. //Task.Run(() => App.Data.TimeSheets.Refresh(false)),
  46. Task.Run(() => App.Data.GPSTrackers.Refresh(false)),
  47. Task.Run(() => App.Data.BluetoothGates.Refresh(false)),
  48. //Task.Run(() => App.Data.Notifications.Refresh(false)),
  49. //Task.Run(() => App.Data.Kanbans.Refresh(false))
  50. };
  51. MobileLogging.Log("MainPage.Create");
  52. InitializeComponent();
  53. SetupClockOnButton(_settings.ClockOnDisabled);
  54. MobileLogging.Log("MainPage.InitNotificationCentre");
  55. InitNotificationCentre();
  56. MobileLogging.Log("MainPage.Subscribe");
  57. MessagingCenter.Subscribe<App>(this, App.MessageOnResume,
  58. (o) =>
  59. {
  60. if (!App.GPS.RecentlyLocated)
  61. App.GPS.GetLocation();
  62. RefreshScreen();
  63. }
  64. );
  65. // MobileLogging.Log("MainPage.NotifyChanges");
  66. // NotifyChanges();
  67. MobileLogging.Log("MainPage.Create Done");
  68. }
  69. // private void NotifyChanges()
  70. // {
  71. // Task.Run(() =>
  72. // {
  73. // string changes = NotifyMobileChanges.Notifiy();
  74. // if (!string.IsNullOrWhiteSpace(changes))
  75. // Device.BeginInvokeOnMainThread(() =>
  76. // {
  77. // DisplayAlert("Latest changes", changes, "OK");
  78. // });
  79. // });
  80. // }
  81. #endregion
  82. #region OnAppearing and Display
  83. private SelectionPage _selectionpage;
  84. protected override void OnAppearing()
  85. {
  86. //ProgressVisible = false;
  87. MobileLogging.Log("MainPage.Appearing");
  88. if (_selectionpage == null)
  89. {
  90. _pollingToken = new CancellationTokenSource();
  91. BackButtonEnabled = App.IsSharedDevice;
  92. EnableModules();
  93. StartMonitoringNotifications();
  94. StartMonitoringTasks();
  95. StartMonitoringVersion();
  96. StartMonitoringEmpoyeeDetails();
  97. //StartPromptingForUpdates();
  98. App.Data.GPSLocationUpdated += OnGPSLocationUpdated;
  99. App.Data.BluetoothScanFinished += OnBluetothScanFinished;
  100. //LoadHRToDos(); to be uncommented when ready for roll out
  101. CheckTimeSheet();
  102. }
  103. _selectionpage = null;
  104. Task.WaitAll(_setuptasks);
  105. RefreshScreen();
  106. base.OnAppearing();
  107. }
  108. protected override void OnDisappearing()
  109. {
  110. base.OnDisappearing();
  111. //App.Data.GPSLocationUpdated -= OnGPSLocationUpdated;
  112. //App.Data.BluetoothScanFinished -= OnBluetothScanFinished;
  113. _pollingToken.Cancel();
  114. }
  115. private void OnBluetothScanFinished(BluetoothEventArgs args)
  116. {
  117. Dispatcher.BeginInvokeOnMainThread(RefreshScreen);
  118. }
  119. private void OnGPSLocationUpdated(GPSEventArgs args)
  120. {
  121. Dispatcher.BeginInvokeOnMainThread(RefreshScreen);
  122. }
  123. void SetupClockOnButton(bool disable)
  124. {
  125. if (disable != _settings.ClockOnDisabled)
  126. {
  127. _settings.ClockOnDisabled = disable;
  128. new LocalConfiguration<MainPageSettings>().Save(_settings);
  129. }
  130. SplashCard.IsVisible = disable;
  131. ClockOnButton.IsVisible = !disable;
  132. jobBtn.IsVisible = !disable;
  133. addNoteBtn.IsVisible = !disable;
  134. taskBtn.IsVisible = !disable;
  135. }
  136. private void EnableModules()
  137. {
  138. SetupClockOnButton(Security.IsAllowed<IsJobOnlyEmployee>());
  139. Assignments.IsVisible = Security.IsAllowed<ViewMobileAssignmentsModule>();
  140. Deliveries.IsVisible = Security.IsAllowed<ViewMobileDeliveriesModule>();
  141. Forms.IsVisible = Security.IsAllowed<ViewMobileFormsModule>();
  142. Equipment.IsVisible = Security.IsAllowed<ViewMobileEquipmentModule>();
  143. InOut.IsVisible = Security.IsAllowed<ViewMobileInOutModule>();
  144. Manufacturing.IsVisible = Security.IsAllowed<ViewMobileManufacturingModule>();
  145. Meetings.IsVisible = Security.IsAllowed<ViewMobileMeetingsModule>();
  146. Products.IsVisible = Security.IsAllowed<ViewMobileProductsModule>();
  147. PurchaseOrders.IsVisible = Security.IsAllowed<ViewMobilePurchaseOrdersModule>();
  148. StoreRequis.IsVisible = Security.IsAllowed<ViewMobileStoreRequisModule>();
  149. MyTasks.IsVisible = Security.IsAllowed<ViewMobileMyTasksModule>();
  150. Warehousing.IsVisible = Security.IsAllowed<ViewMobileWarehousingModule>();
  151. }
  152. protected override void UpdateTransportStatus()
  153. {
  154. base.UpdateTransportStatus();
  155. RefreshScreen();
  156. }
  157. private CancellationTokenSource _pollingToken = new CancellationTokenSource();
  158. private void StartMonitoringNotifications()
  159. {
  160. //notifications are allowed to upload once every 30 seconds
  161. // This will eventually be replaced with websocket pushes
  162. // but we're not ready for that yet :-(
  163. var token = _pollingToken.Token;
  164. Task.Run(
  165. () =>
  166. {
  167. while (!_pollingToken.Token.IsCancellationRequested)
  168. {
  169. App.Data.Notifications.Refresh(true);
  170. int _notificationcount = App.Data.Notifications.Items.Where(x=>x.Closed.IsEmpty()).ToList().Count;
  171. Dispatcher.BeginInvokeOnMainThread(() =>
  172. {
  173. Notifications.Alert = _notificationcount > 0
  174. ? _notificationcount.ToString()
  175. : "";
  176. });
  177. Task.Delay(TimeSpan.FromSeconds(30), token)
  178. .Wait(token);
  179. }
  180. },
  181. token
  182. );
  183. }
  184. private void StartMonitoringTasks()
  185. {
  186. var token = _pollingToken.Token;
  187. Task.Run(
  188. () =>
  189. {
  190. while (!_pollingToken.Token.IsCancellationRequested)
  191. {
  192. App.Data.Kanbans.Refresh(true);
  193. int _taskcount = App.Data.Kanbans.Items.Where(x=>String.Equals(x.Category, Kanban.OPEN)).ToList().Count;
  194. Dispatcher.BeginInvokeOnMainThread(() =>
  195. {
  196. MyTasks.Alert = _taskcount > 0
  197. ? _taskcount.ToString()
  198. : "";
  199. });
  200. Task.Delay(TimeSpan.FromSeconds(30), token)
  201. .Wait(token);
  202. }
  203. },
  204. token
  205. );
  206. }
  207. private void StartMonitoringVersion()
  208. {
  209. var token = _pollingToken.Token;
  210. Task.Run(
  211. async () =>
  212. {
  213. while (!_pollingToken.Token.IsCancellationRequested)
  214. {
  215. bool isLatest = true;
  216. try
  217. {
  218. isLatest = await MobileUtils.AppVersion.IsUsingLatestVersion();
  219. }
  220. catch (Exception e)
  221. {
  222. InABox.Mobile.MobileLogging.Log(e);
  223. }
  224. //bool isLatest = MobileUtils.AppVersion.IsUsingLatestVersion().Result;
  225. Dispatcher.BeginInvokeOnMainThread(() =>
  226. {
  227. if (Update.IsVisible == isLatest)
  228. {
  229. Update.IsVisible = !isLatest;
  230. Tools.Refresh();
  231. Update.Alert = isLatest
  232. ? ""
  233. : "!";
  234. }
  235. });
  236. Task.Delay(TimeSpan.FromSeconds(30), token)
  237. .Wait(token);
  238. }
  239. },
  240. token
  241. );
  242. }
  243. private void StartMonitoringEmpoyeeDetails()
  244. {
  245. var token = _pollingToken.Token;
  246. Task.Run(() =>
  247. {
  248. while (!_pollingToken.Token.IsCancellationRequested)
  249. {
  250. App.Data.EmployeeQualifications.Refresh(true);
  251. bool bQualificationIssues = App.Data.EmployeeQualifications.NeedsAttention > 0;
  252. App.Data.EmployeeForms.Refresh(true);
  253. bool bFormIssues = App.Data.EmployeeForms.NeedsAttention > 0;
  254. Dispatcher.BeginInvokeOnMainThread(() =>
  255. {
  256. MyHR.Alert = (bQualificationIssues || bFormIssues)
  257. ? "!"
  258. : "";
  259. });
  260. Task.Delay(TimeSpan.FromSeconds(30), token)
  261. .Wait(token);
  262. }
  263. },
  264. token
  265. );
  266. }
  267. // private void StartPromptingForUpdates()
  268. // {
  269. // var token = _pollingToken.Token;
  270. // Task.Run(
  271. // () =>
  272. // {
  273. // while (!token.IsCancellationRequested)
  274. // {
  275. // bool isLatest = MobileUtils.AppVersion.IsUsingLatestVersion().Result;
  276. // if (!isLatest)
  277. // {
  278. // string latestVersionNumber = MobileUtils.AppVersion.GetLatestVersionNumber().Result;
  279. // string chosenOption = DisplayActionSheet(
  280. // $"Version {latestVersionNumber} Available. Update now?",
  281. // "You will be reminded again in 10 minutes.", null, "Yes", "No").Result;
  282. // if (String.Equals(chosenOption,"Yes"))
  283. // Dispatcher.BeginInvokeOnMainThread(() => { MobileUtils.AppVersion.OpenAppInStore(); });
  284. // }
  285. //
  286. // Task.Delay(TimeSpan.FromMinutes(10), token)
  287. // .Wait(token);
  288. // }
  289. //
  290. // }
  291. // , token
  292. // );
  293. // }
  294. private void RefreshScreen()
  295. {
  296. bool GateReady = CheckLocation();
  297. var location = GetAddress();
  298. Title = App.Data.Me != null
  299. ? App.Data.Me.Name
  300. : "(Not Logged In)";
  301. ClockOnButton.IsEnabled = App.Data.IsConnected() && GateReady;
  302. ClockOnOffLabel.Text = GateReady ? _currenttimesheet == null ? "CLOCK ON" : "CLOCK OFF" : "PLEASE WAIT";
  303. CurrentLocation.Text = location.ToUpper().Contains("ERROR")
  304. ? "Unknown Address"
  305. : location;
  306. ClockOnButton.BackgroundColor = App.Data.IsConnected() && GateReady
  307. ? _currenttimesheet == null
  308. ? Color.FromHex("#e6e6fa")
  309. : Color.FromHex("#15C7C1")
  310. : Color.Silver;
  311. ClockOnButton.BorderColor = App.Data.IsConnected() && GateReady
  312. ? _currenttimesheet == null
  313. ? Color.FromHex("#96969a")
  314. : Color.FromHex("#059791")
  315. : Color.Gray;
  316. addNoteBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null;
  317. jobBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null;
  318. taskBtn.IsEnabled = App.Data.IsConnected() && _currenttimesheet != null;
  319. jobBtn.Text = ((_currenttimesheet?.JobID ?? Guid.Empty) != Guid.Empty)
  320. ? $"{_currenttimesheet?.JobLink.JobNumber}: {_currenttimesheet?.JobLink.Name}"
  321. : "Select Job";
  322. Assignments.IsEnabled = App.Data.IsConnected();
  323. Deliveries.IsEnabled = App.Data.IsConnected();
  324. Equipment.IsEnabled = App.Data.IsConnected();
  325. Manufacturing.IsEnabled = App.Data.IsConnected();
  326. Forms.IsEnabled = App.Data.IsConnected();
  327. InOut.IsEnabled = App.Data.IsConnected();
  328. MyHR.IsEnabled = App.Data.IsConnected();
  329. Notifications.IsEnabled = App.Data.IsConnected();
  330. Products.IsEnabled = App.Data.IsConnected();
  331. PurchaseOrders.IsEnabled = App.Data.IsConnected();
  332. Site.IsEnabled = App.Data.IsConnected();
  333. StoreRequis.IsEnabled = App.Data.IsConnected();
  334. MyTasks.IsEnabled = App.Data.IsConnected();
  335. Warehousing.IsEnabled = App.Data.IsConnected();
  336. }
  337. #endregion
  338. #region Clock on/off
  339. private void CheckTimeSheet()
  340. {
  341. if (App.Data.IsConnected())
  342. {
  343. var client = new Client<TimeSheet>();
  344. var filter = new Filter<TimeSheet>(x => x.EmployeeLink.ID).IsEqualTo(App.Data.Me.ID)
  345. .And(x => x.Date).IsEqualTo(DateTime.Today)
  346. .And(x => x.Finish).IsEqualTo(TimeSpan.Zero);
  347. var table = client.Query(filter);
  348. _currenttimesheet = table.Rows.FirstOrDefault()?.ToObject<TimeSheet>();
  349. }
  350. var token = _pollingToken.Token;
  351. Task.Run(
  352. () =>
  353. {
  354. while (!token.IsCancellationRequested)
  355. {
  356. if (_currenttimesheet != null)
  357. {
  358. if (_currenttimesheet.Date != DateTime.Today)
  359. {
  360. var here = GPSLocation();
  361. _currenttimesheet.FinishLocation = here;
  362. _currenttimesheet.Finish = TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1));
  363. new Client<TimeSheet>().Save(_currenttimesheet,
  364. "Closing Timesheet at Modnight on Mobile Device");
  365. CreateTimeSheet(_currenttimesheet.JobLink.ID, _currenttimesheet.JobLink.JobNumber, _currenttimesheet.JobLink.Name, here, here.Address, "Creating Timeheet at Midnight on Mobile Device");
  366. }
  367. }
  368. Task.Delay(TimeSpan.FromMinutes(1), token).Wait(token);
  369. }
  370. },
  371. token
  372. );
  373. }
  374. private DateTime _debounce = DateTime.MinValue;
  375. async void ClockOnOff_Clicked(object sender, EventArgs e)
  376. {
  377. if (_debounce > DateTime.Now.Subtract(TimeSpan.FromMilliseconds(500)))
  378. return;
  379. _debounce = DateTime.MaxValue;
  380. string chosenOption = _currenttimesheet != null
  381. ? await DisplayActionSheet("Clock off?", "Cancel", null, "Continue", "Cancel")
  382. : "Continue";
  383. if (!String.Equals(chosenOption, "Continue"))
  384. {
  385. _debounce = DateTime.Now;
  386. return;
  387. }
  388. try
  389. {
  390. using (await MaterialDialog.Instance.LoadingDialogAsync(message: $"Clocking {(_currenttimesheet == null ? "On" : "Off")}"))
  391. {
  392. var here = GPSLocation();
  393. if (_currenttimesheet != null)
  394. {
  395. FinishTimeSheet(here);
  396. }
  397. else
  398. {
  399. if (!Security.IsAllowed<CanBypassBluetoothGates>())
  400. {
  401. var tracker = App.Data.BluetoothGates.FirstOrDefault(x => App.Bluetooth.Devices.Contains(x.DeviceID));
  402. if (tracker != null)
  403. CreateTimeSheet(tracker.JobID, tracker.JobNumber, tracker.JobName, here, here.Address, "Clocking On");
  404. }
  405. else
  406. {
  407. if ((!here.Latitude.Equals(0.0F) && !here.Longitude.Equals(0.0F))
  408. || Security.IsAllowed<CanBypassGPSClockIn>())
  409. {
  410. var job = await ChooseNearbyJob(here);
  411. CreateTimeSheet(job?.ID ?? Guid.Empty, job?.JobNumber ?? "", job?.Name ?? "" , here, here.Address, "Clocking On");
  412. }
  413. }
  414. }
  415. Dispatcher.BeginInvokeOnMainThread(RefreshScreen);
  416. }
  417. }
  418. catch (Exception ex)
  419. {
  420. InABox.Mobile.MobileLogging.Log(ex);
  421. }
  422. _debounce = DateTime.Now;
  423. }
  424. private void FinishTimeSheet(Location here)
  425. {
  426. if ((_currenttimesheet == null) || (_currenttimesheet.ID == Guid.Empty))
  427. {
  428. _currenttimesheet = null;
  429. return;
  430. }
  431. try
  432. {
  433. if (ZeroLengthTimesheet())
  434. new Client<TimeSheet>().Delete(_currenttimesheet, "Deleted due to zero duration timesheet");
  435. else
  436. {
  437. _currenttimesheet.Finish =
  438. new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes, 0);
  439. _currenttimesheet.FinishLocation = here;
  440. //bUpdatingTimesheet = true;
  441. new Client<TimeSheet>().Save(_currenttimesheet, "Clocking Off");
  442. }
  443. _currenttimesheet = null;
  444. }
  445. catch (Exception ex)
  446. {
  447. InABox.Mobile.MobileLogging.Log(ex);
  448. }
  449. }
  450. #endregion
  451. #region Utilities
  452. private void InitNotificationCentre()
  453. {
  454. // LocalNotificationCenter.Current.NotificationActionTapped += (Plugin.LocalNotification.EventArgs.NotificationActionEventArgs e) =>
  455. // {
  456. // if (MainPageUtils.DetermineCorrectPage(e) != null)
  457. // {
  458. // Device.BeginInvokeOnMainThread(() =>
  459. // {
  460. // Navigation.PushAsync(MainPageUtils.DetermineCorrectPage(e));
  461. // });
  462. // }
  463. // };
  464. //
  465. // MainPageUtils.OnMainPageNotificationsChanged += RefreshOnNotificationsChange;
  466. }
  467. //private void CheckNotificationsPushed(CoreTable table)
  468. //{
  469. // try
  470. // {
  471. // if (!Application.Current.Properties.ContainsKey("LastPushedNotifications"))
  472. // {
  473. // Application.Current.Properties.Add("LastPushedNotifications", DateTime.Now);
  474. // }
  475. // DateTime lastPushed = DateTime.Parse(Application.Current.Properties["LastPushedNotifications"].ToString());
  476. // List<NotificationShell> toNotify = new List<NotificationShell>();
  477. // foreach (CoreRow row in table.Rows)
  478. // {
  479. // List<object> list = row.Values;
  480. // DateTime created = DateTime.Parse(list[3].ToString());
  481. // if (created > new DateTime(2022, 8, 22)) // prevent spam from buildup of old notifications before this is released
  482. // {
  483. // if (created > lastPushed)
  484. // {
  485. // if (list[1] == null) list[1] = "";
  486. // if (list[2] == null) list[2] = "";
  487. // if (list[3] == null) list[3] = DateTime.MinValue;
  488. // if (list[4] == null) list[4] = "";
  489. // if (list[5] == null) list[5] = "";
  490. // if (list[6] == null) list[6] = Guid.Empty;
  491. //
  492. // NotificationShell shell = new NotificationShell
  493. // {
  494. // ID = Guid.Parse(list[0].ToString()),
  495. // Sender = list[1].ToString(),
  496. // Title = list[2].ToString(),
  497. // Created = DateTime.Parse(list[3].ToString()),
  498. // EntityType = list[5].ToString(),
  499. // EntityID = Guid.Parse(list[6].ToString())
  500. // };
  501. // toNotify.Add(shell); //add notification to be pushed
  502. // }
  503. // }
  504. // }
  505. // if (toNotify.Count > 0)
  506. // PushNotificationsAsync(toNotify);
  507. // }
  508. // catch { }
  509. //}
  510. //private async Task PushNotificationsAsync(List<NotificationShell> shells)
  511. //{
  512. // try
  513. // {
  514. // int count = 1;
  515. //
  516. // foreach (NotificationShell shell in shells)
  517. // {
  518. // var notification = new NotificationRequest
  519. // {
  520. // BadgeNumber = 1,
  521. // Description = shell.Title,
  522. // Title = "New PRS Notification: ",
  523. // ReturningData = shell.EntityID.ToString() + "$" + shell.EntityType,
  524. // NotificationId = count,
  525. // };
  526. // count++;
  527. // NotificationImage img = new NotificationImage();
  528. // img.ResourceName = "icon16.png";
  529. // notification.Image = img;
  530. //
  531. // await LocalNotificationCenter.Current.Show(notification);
  532. // }
  533. // Application.Current.Properties["LastPushedNotifications"] = DateTime.Now;
  534. // }
  535. // catch { }
  536. //}
  537. private async Task<JobShell> ChooseNearbyJob(Location here)
  538. {
  539. var nearbyjobs = App.Data.Jobs.Where(x => here.DistanceTo(x.Location, UnitOfLength.Kilometers) < 1.0F)
  540. .ToArray();
  541. if (nearbyjobs.Length > 1)
  542. {
  543. Dictionary<String, JobShell> dict = new Dictionary<string, JobShell>();
  544. foreach (var job in nearbyjobs)
  545. dict[job.DisplayName] = job;
  546. string chosenOption = await DisplayActionSheet("Choose job site", "Cancel", null, dict.Keys.ToArray());
  547. if (string.IsNullOrEmpty(chosenOption) || chosenOption.Equals("Cancel"))
  548. return null;
  549. return dict[chosenOption];
  550. }
  551. return nearbyjobs.FirstOrDefault();
  552. }
  553. void AddNote_Tapped(object sender, EventArgs e)
  554. {
  555. if (_currenttimesheet == null)
  556. return;
  557. var notepage = new TimeSheetNotePage(_currenttimesheet);
  558. Navigation.PushAsync(notepage);
  559. }
  560. private void TaskBtn_Tapped(object sender, EventArgs e)
  561. {
  562. _selectionpage = new TaskSelectionPage( (task) =>
  563. {
  564. if (_currenttimesheet != null)
  565. {
  566. // Not sure hwat to do here...
  567. }
  568. }
  569. );
  570. Navigation.PushAsync(_selectionpage);
  571. }
  572. // private async void RequestUserInput(Guid taskID)
  573. // {
  574. // const string addtask = "Change current assignment task";
  575. // const string newassignment = "Start a new assignment with this task";
  576. // string chosenOption = await DisplayActionSheet("Choose an option", "Cancel", null, addtask, newassignment);
  577. // switch (chosenOption)
  578. // {
  579. // case addtask:
  580. // MainPageUtils.ChangeAssignmentTask(taskID);
  581. // break;
  582. // case newassignment:
  583. // MainPageUtils.SaveCurrentAssignment("PRS Mobile main screen - saving assignment on task change", true);
  584. // MainPageUtils.CreateNewAssignment(Guid.Empty, taskID);
  585. // break;
  586. // default:
  587. // break;
  588. // }
  589. // }
  590. private void SaveSiteModuleJobID()
  591. {
  592. using (var config = new LocalConfiguration<SiteModuleSettings>())
  593. {
  594. var _sitesettings = config.Load();
  595. _sitesettings.JobID = _currenttimesheet?.JobID ?? Guid.Empty;
  596. config.Save(_sitesettings);
  597. }
  598. }
  599. private void JobBtn_Tapped(object sender, EventArgs e)
  600. {
  601. _selectionpage = new JobSelectionPage(
  602. (job) =>
  603. {
  604. if (_currenttimesheet != null)
  605. {
  606. if (ZeroLengthTimesheet())
  607. {
  608. _currenttimesheet.JobLink.ID = job?.ID ?? Guid.Empty;
  609. _currenttimesheet.JobLink.JobNumber = job?.JobNumber ?? "";
  610. _currenttimesheet.JobLink.Name = job?.Name ?? "";
  611. new Client<TimeSheet>().Save(_currenttimesheet,"Updated Job Number via mobile device");
  612. SaveSiteModuleJobID();
  613. }
  614. else
  615. {
  616. var here = GPSLocation();
  617. FinishTimeSheet(here);
  618. CreateTimeSheet(job?.ID ?? Guid.Empty, job?.JobNumber ?? "", job?.Name ?? "", here, here.Address,
  619. "Switched Jobs via Mobile Device");
  620. }
  621. }
  622. }
  623. );
  624. Navigation.PushAsync(_selectionpage);
  625. }
  626. // private bool CheckTimeSheetAgainstGates(TimeSheet timesheet)
  627. // {
  628. // DateTime now = DateTime.Now;
  629. //
  630. // //var timesheet = CurrentTimeSheet();
  631. //
  632. // //Can't confirm if there is no timesheet
  633. // if (timesheet == null)
  634. // return false;
  635. //
  636. // // Can't confirm if there are no devices
  637. // if (App.Bluetooth.Devices.Length == 0)
  638. // return false;
  639. //
  640. // if (App.Data.Gates == null)
  641. // return false;
  642. //
  643. // long tsTicks = timesheet.Date.Add(timesheet.Start).Ticks;
  644. // long btTicks = App.Bluetooth.TimeStamp.Ticks;
  645. //
  646. // if (Math.Abs(tsTicks - btTicks) > new TimeSpan(0, 2, 0).Ticks)
  647. // return false;
  648. //
  649. // CoreRow firstgate = null;
  650. // List<String> gates = new List<string>();
  651. // // Scan every located d
  652. // foreach (var device in App.Bluetooth.Devices)
  653. // {
  654. // CoreRow gate = App.Data.Gates?.Rows.FirstOrDefault(r => r.Get<JobTracker, String>(c => c.TrackerLink.DeviceID) == device);
  655. // if (gate != null)
  656. // {
  657. //
  658. // if ((gate.Get<JobTracker, bool>(x => x.IsJobSite) == true) && (firstgate == null))
  659. // firstgate = gate;
  660. //
  661. // gates.Add(gate.Get<JobTracker, String>(x => x.Gate));
  662. // }
  663. // }
  664. // if (gates.Any())
  665. // {
  666. // timesheet.Gate = String.Join(", ", gates.OrderBy(x => x));
  667. // if (firstgate != null)
  668. // {
  669. // timesheet.JobLink.ID = firstgate.Get<JobTracker, Guid>(x => x.JobLink.ID);
  670. // timesheet.JobLink.JobNumber = firstgate.Get<JobTracker, String>(x => x.JobLink.JobNumber);
  671. // timesheet.JobLink.Name = firstgate.Get<JobTracker, String>(x => x.JobLink.Name);
  672. // }
  673. // return true;
  674. // //new Client<TimeSheet>().Save(timesheet, "Confirmed Gate Entry by Bluetooth Tracker", (o, e) => { });
  675. // }
  676. //
  677. // return false;
  678. //
  679. // }
  680. private bool ZeroLengthTimesheet()
  681. {
  682. if (_currenttimesheet == null)
  683. return true;
  684. if (!String.IsNullOrWhiteSpace(_currenttimesheet.Notes))
  685. return false;
  686. if (_currenttimesheet.Date.Equals(DateTime.Today))
  687. {
  688. var diff = (DateTime.Now.TimeOfDay - _currenttimesheet.Start).TotalSeconds;
  689. if (Math.Abs(diff) < 120.0F)
  690. return true;
  691. }
  692. return false;
  693. }
  694. private void CreateTimeSheet(Guid jobid, string jobnumber, String jobname, Location location, String address, String auditmessage)
  695. {
  696. try
  697. {
  698. _currenttimesheet = new TimeSheet
  699. {
  700. EmployeeLink =
  701. {
  702. ID = App.Data.Me.ID
  703. },
  704. Date = DateTime.Today,
  705. StartLocation = location,
  706. JobLink =
  707. {
  708. ID = jobid,
  709. JobNumber = jobnumber,
  710. Name = jobname
  711. },
  712. Address = address,
  713. SoftwareVersion = MobileUtils.AppVersion.InstalledVersionNumber + MobileUtils.GetDeviceID(),
  714. Start = (DateTime.Now - DateTime.Today).Floor(TimeSpan.FromMinutes(1))
  715. };
  716. new Client<TimeSheet>().Save(_currenttimesheet, auditmessage);
  717. }
  718. catch (Exception ex)
  719. {
  720. InABox.Mobile.MobileLogging.Log(ex);
  721. }
  722. }
  723. private bool CheckLocation()
  724. {
  725. // a 15 minute timeout is awfully long for this process.
  726. // The App times tick over every 30 seconds, so surely we can
  727. // drop this to max 1 or 2 minutes..
  728. // Also, we would expect the GPS / Bluetooth subsystems to take care
  729. // of purging stale data
  730. if (Security.IsAllowed<CanBypassGPSClockIn>())
  731. return true;
  732. if (Security.IsAllowed<CanBypassBluetoothGates>())
  733. return GPSTimeStamp() > DateTime.Now.Subtract(new TimeSpan(0, 15, 0));
  734. if (App.Bluetooth.TimeStamp < DateTime.Now.Subtract(new TimeSpan(0, 15, 0)))
  735. return false;
  736. return App.Data.BluetoothGates.Any(x =>
  737. App.Bluetooth.DetectedBlueToothMACAddresses.Contains(x.DeviceID));
  738. }
  739. private DateTime GPSTimeStamp()
  740. {
  741. return App.GPS.TimeStamp;
  742. }
  743. private static Location GPSLocation()
  744. {
  745. Location here = new InABox.Core.Location()
  746. {
  747. Latitude = App.GPS.Latitude,
  748. Longitude = App.GPS.Longitude,
  749. Address = App.GPS.Address,
  750. Timestamp = App.GPS.TimeStamp
  751. };
  752. return here;
  753. }
  754. private String GPSAddress()
  755. {
  756. return App.GPS.Address;
  757. }
  758. private String GetAddress()
  759. {
  760. if (Security.IsAllowed<CanBypassBluetoothGates>())
  761. {
  762. if (GPSTimeStamp() < DateTime.Now.Subtract(new TimeSpan(0, 5, 0)))
  763. {
  764. App.GPS.GetLocation(true);
  765. return InABox.Core.Security.IsAllowed<CanBypassGPSClockIn>()? "" : "Searching for GPS";
  766. }
  767. return GPSAddress();
  768. }
  769. else
  770. {
  771. var gate = App.Data.BluetoothGates.FirstOrDefault(x =>
  772. App.Bluetooth.DetectedBlueToothMACAddresses.Contains(x.DeviceID));
  773. return gate?.Gate ?? "Looking for Gate";
  774. }
  775. }
  776. #endregion
  777. public void SettingsTapped(object sender, EventArgs args) => Navigation.PushAsync(new SettingsPage());
  778. public void AssignmentListTapped(object sender, EventArgs args) => Navigation.PushAsync(new AssignmentList());
  779. public void DeliveryModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new DeliveryModule());
  780. public void EquipmentModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new EquipmentModule());
  781. public void KanbanFormsTapped(object sender, EventArgs args) => Navigation.PushAsync(new KanbanForms());
  782. public void StaffStatusPageTapped(object sender, EventArgs args) => Navigation.PushAsync(new StaffStatusPage());
  783. public void ManufacturingListTapped(object sender, EventArgs args) => Navigation.PushAsync(new ManufacturingList());
  784. private void MeetingsModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new MeetingList());
  785. public void HumanResourcesModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new HumanResourcesModule());
  786. public void NotificationListTapped(object sender, EventArgs args) => Navigation.PushAsync(new NotificationList());
  787. public void ProductListTapped(object sender, EventArgs args) => Navigation.PushAsync(new ProductList());
  788. public void PurchaseOrderListTapped(object sender, EventArgs args) => Navigation.PushAsync(new PurchaseOrderList());
  789. public void SiteModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new SiteModule());
  790. public void StoreRequiListTapped(object sender, EventArgs args) => Navigation.PushAsync(new StoreRequiList());
  791. public void KanbanListTapped(object sender, EventArgs args) => Navigation.PushAsync(new KanbanList()
  792. {
  793. Model = App.Data.Kanbans,
  794. Title = "My Tasks"
  795. });
  796. public void WarehouseModuleTapped(object sender, EventArgs args) => Navigation.PushAsync(new WarehouseModule());
  797. public void UpdatePageTapped(object sender, EventArgs args) => Navigation.PushAsync(new UpdatePage());
  798. }
  799. }