MainPage.xaml.cs 37 KB

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