MainPage.xaml.cs 38 KB

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