EditTimeEntryViewModel (TimeEntryData data, List<TagData> tagList) { this.data = data; // Save previous state. initialState = new TimeEntryData (data); initialTagList = new List<TagData> (tagList); durationTimer = new Timer (); durationTimer.Elapsed += DurationTimerCallback; TagList = tagList; IsManual = data.Id == Guid.Empty; if (IsManual) { data.StartTime = Time.UtcNow.AddMinutes (-5); data.StopTime = Time.UtcNow; data.State = TimeEntryState.Finished; initialTagList = new List<TagData> (); } UpdateView (); UpdateRelationships (data.ProjectId); ServiceContainer.Resolve<ITracker> ().CurrentScreen = "Edit Time Entry"; }
public void Dispose () { timeEntryDataList.Clear (); timeEntryData = new TimeEntryData (); projectData = new ProjectData (); clientData = new ClientData (); taskData = new TaskData (); }
public TimeEntryHolder (IEnumerable<TimeEntryData> timeEntryGroup) { if (timeEntryGroup == null || !timeEntryGroup.Any ()) { throw new ArgumentException ("Must be specified", "timeEntryGroup"); } timeEntryDataList.AddRange (timeEntryGroup); timeEntryData = new TimeEntryData (timeEntryGroup.Last ()); projectData = new ProjectData (); clientData = new ClientData (); taskData = new TaskData (); }
public TimeEntryGroup (TimeEntryData data, ITimeEntryHolder previous = null) { var previous2 = previous as TimeEntryGroup; if (previous2 != null) { Group = previous2.Group.ReplaceOrAppend (data, x => x.Id == data.Id) .OrderByDescending (x => x.StartTime).ToList (); // Recycle entry info if possible Info = previous2.Data.Id == Data.Id ? previous2.Info : null; } else { Group = new List<TimeEntryData> { data }; } }
public TimeEntryData (TimeEntryData other) : base (other) { State = other.State; Description = other.Description; StartTime = other.StartTime; StopTime = other.StopTime; DurationOnly = other.DurationOnly; IsBillable = other.IsBillable; UserId = other.UserId; WorkspaceId = other.WorkspaceId; ProjectId = other.ProjectId; TaskId = other.TaskId; }
private async Task<ListEntryData> ConvertToListEntryData (TimeEntryData entry) { var project = await FetchProjectData (entry.ProjectId ?? Guid.Empty); var entryData = new ListEntryData(); entryData.Id = entry.Id; entryData.Description = entry.Description; entryData.Duration = GetDuration (entry.StartTime, entry.StopTime ?? DateTime.Now); entryData.Project = project.Name; entryData.HasProject = !String.IsNullOrEmpty (project.Name); entryData.ProjectColor = project.Color; entryData.State = entry.State; return entryData; }
public static async Task<EditTimeEntryViewModel> Init (Guid timeEntryId) { TimeEntryData data; List<TagData> tagList; if (timeEntryId == Guid.Empty) { data = TimeEntryModel.GetDraft (); tagList = await GetDefaultTagList (data.WorkspaceId); } else { data = await TimeEntryModel.GetTimeEntryDataAsync (timeEntryId); tagList = await ServiceContainer.Resolve<IDataStore> ().GetTimeEntryTags (timeEntryId);; } return new EditTimeEntryViewModel (data, tagList); }
private static async Task<ProjectData> UpdateProject (TimeEntryData newTimeEntry, ProjectData oldProjectData) { if (!newTimeEntry.ProjectId.HasValue) { return new ProjectData (); } if (oldProjectData.Id == Guid.Empty && newTimeEntry.ProjectId.HasValue) { return await GetProjectDataAsync (newTimeEntry.ProjectId.Value); } if (newTimeEntry.ProjectId.Value != oldProjectData.Id) { return await GetProjectDataAsync (newTimeEntry.ProjectId.Value); } return oldProjectData; }
public ITimeEntryHolder UpdateOrDelete (TimeEntryData data, out bool isAffectedByDelete) { isAffectedByDelete = Group.Any (x => x.Id == data.Id); if (isAffectedByDelete) { if (Group.Count == 1) { return null; // Delete } else { var updated = new TimeEntryGroup (); updated.Group = Group.Where (x => x.Id != data.Id).ToList (); updated.Info = updated.Data.Id == Data.Id ? Info : null; // Recycle entry info if possible return updated; } } else { return null; } }
public static async Task<TimeEntryInfo> LoadAsync (TimeEntryData timeEntryData) { var info = new TimeEntryInfo (); info.ProjectData = timeEntryData.ProjectId.HasValue ? await GetProjectDataAsync (timeEntryData.ProjectId.Value) : new ProjectData (); info.ClientData = info.ProjectData.ClientId.HasValue ? await GetClientDataAsync (info.ProjectData.ClientId.Value) : new ClientData (); info.TaskData = timeEntryData.TaskId.HasValue ? await GetTaskDataAsync (timeEntryData.TaskId.Value) : new TaskData (); info.Description = timeEntryData.Description; info.Color = (info.ProjectData.Id != Guid.Empty) ? info.ProjectData.Color : -1; info.IsBillable = timeEntryData.IsBillable; info.NumberOfTags = await GetNumberOfTagsAsync (timeEntryData.Id); return info; }
private static long EncodeDuration (TimeEntryData data) { var now = Time.UtcNow; // Calculate time entry duration TimeSpan duration; if (data.StartTime == DateTime.MinValue) { duration = TimeSpan.Zero; } else { duration = (data.StopTime ?? now) - data.StartTime; if (duration < TimeSpan.Zero) { duration = TimeSpan.Zero; } } // Encode the duration var encoded = (long)duration.TotalSeconds; if (data.State == TimeEntryState.Running) { encoded = (long) (encoded - now.ToUnix ().TotalSeconds); } return encoded; }
private static void DecodeDuration (TimeEntryData data, TimeEntryJson json) { var now = Time.UtcNow.Truncate (TimeSpan.TicksPerSecond); // Decode duration: TimeSpan duration; if (json.Duration < 0) { data.State = TimeEntryState.Running; duration = now.ToUnix () + TimeSpan.FromSeconds (json.Duration); } else { data.State = TimeEntryState.Finished; duration = TimeSpan.FromSeconds (json.Duration); } // Set start and stop times based on the duration: if (data.State == TimeEntryState.Finished) { data.StartTime = json.StartTime.ToUtc (); data.StopTime = json.StartTime.ToUtc () + duration; } else { data.StartTime = now - duration; data.StopTime = null; } }
public TimeEntryJson Export (IDataStoreContext ctx, TimeEntryData data) { var userId = GetRemoteId<UserData> (ctx, data.UserId); var workspaceId = GetRemoteId<WorkspaceData> (ctx, data.WorkspaceId); var projectId = GetRemoteId<ProjectData> (ctx, data.ProjectId); var taskId = GetRemoteId<TaskData> (ctx, data.TaskId); var tags = GetTimeEntryTags (ctx, data.Id); return new TimeEntryJson () { Id = data.RemoteId, ModifiedAt = data.ModifiedAt.ToUtc (), Description = data.Description, IsBillable = data.IsBillable, StartTime = data.StartTime.ToUtc (), StopTime = data.StopTime.ToUtc (), DurationOnly = data.DurationOnly, Duration = EncodeDuration (data), Tags = tags, UserId = userId, WorkspaceId = workspaceId, ProjectId = projectId, TaskId = taskId, }; }
private static void ResetTags (IDataStoreContext ctx, TimeEntryData timeEntryData, TimeEntryJson json) { // Don't touch the tags when the field is null if (json.Tags == null) { return; } var con = ctx.Connection; // Resolve tags to IDs: var tagIds = new List<Guid> (); foreach (var tagName in json.Tags) { // Prevent importing empty (invalid) tags: if (String.IsNullOrWhiteSpace (tagName)) { continue; } var id = ctx.GetTagIdFromName (timeEntryData.WorkspaceId, tagName); if (id == Guid.Empty) { // Need to create a new tag: var tagData = new TagData () { Name = tagName, WorkspaceId = timeEntryData.WorkspaceId, }; con.Insert (tagData); id = timeEntryData.Id; } tagIds.Add (id); } // Iterate over TimeEntryTags and determine which to keep and which to discard: var inters = con.Table<TimeEntryTagData> ().Where (m => m.TimeEntryId == timeEntryData.Id); var toDelete = new List<TimeEntryTagData> (); foreach (var inter in inters) { if (tagIds.Contains (inter.TagId)) { tagIds.Remove (inter.TagId); } else { toDelete.Add (inter); } } // Delete unused tags intermediate rows: foreach (var inter in toDelete) { ctx.Delete (inter); } // Create new intermediate rows: foreach (var tagId in tagIds) { ctx.Put (new TimeEntryTagData () { TagId = tagId, TimeEntryId = timeEntryData.Id, }); } }
private static void ImportJson (IDataStoreContext ctx, TimeEntryData data, TimeEntryJson json) { var userId = GetUserLocalId (ctx, json.UserId); var workspaceId = GetLocalId<WorkspaceData> (ctx, json.WorkspaceId); var projectId = GetLocalId<ProjectData> (ctx, json.ProjectId); var taskId = GetLocalId<TaskData> (ctx, json.TaskId); data.Description = json.Description; data.IsBillable = json.IsBillable; data.DurationOnly = json.DurationOnly; data.UserId = userId; data.WorkspaceId = workspaceId; data.ProjectId = projectId; data.TaskId = taskId; DecodeDuration (data, json); ImportCommonJson (data, json); }
private void UpdateView (TimeEntryData data) { ServiceContainer.Resolve<IPlatformUtils> ().DispatchOnUIThread (async () => { if (data.State == TimeEntryState.Running) { Description = string.IsNullOrEmpty (data.Description) ? string.Empty : data.Description; if (data.ProjectId != null) { try { var prj = await TimeEntryModel.GetProjectDataAsync (data.ProjectId.Value); ProjectName = prj.Name; } catch (Exception ex) { var logger = ServiceContainer.Resolve<ILogger>(); logger.Warning ("LogTimeEntriesViewModel", ex, "Error reading project."); } } else { ProjectName = string.Empty; } IsTimeEntryRunning = true; durationTimer.Start (); } else { IsTimeEntryRunning = false; Description = string.Empty; ProjectName = string.Empty; durationTimer.Stop (); Duration = TimeSpan.FromSeconds (0).ToString ().Substring (0, 8); } }); }
public async Task<TimeEntryData> ContinueTimeEntryAsync (int index) { var newTimeEntry = new TimeEntryData (); var timeEntryHolder = Collection.ElementAt (index) as ITimeEntryHolder; if (timeEntryHolder == null) { return newTimeEntry; } if (timeEntryHolder.Data.State == TimeEntryState.Running) { newTimeEntry = await TimeEntryModel.StopAsync (timeEntryHolder.Data); ServiceContainer.Resolve<ITracker>().SendTimerStopEvent (TimerStopSource.App); } else { newTimeEntry = await TimeEntryModel.ContinueAsync (timeEntryHolder.Data); ServiceContainer.Resolve<ITracker>().SendTimerStartEvent (TimerStartSource.AppContinue); } return newTimeEntry; }
public TimeEntryData Import (IDataStoreContext ctx, TimeEntryJson json, Guid? localIdHint = null, TimeEntryData mergeBase = null) { var data = GetByRemoteId<TimeEntryData> (ctx, json.Id.Value, localIdHint); var merger = mergeBase != null ? new TimeEntryMerger (mergeBase) : null; if (merger != null && data != null) merger.Add (new TimeEntryData (data)); if (json.DeletedAt.HasValue) { if (data != null) { // TODO: Delete TimeEntryTag intermediate data ctx.Delete (data); data = null; } } else if (merger != null || ShouldOverwrite (data, json)) { data = data ?? new TimeEntryData (); ImportJson (ctx, data, json); if (merger != null) { merger.Add (data); data = merger.Result; } data = ctx.Put (data); // Also update tags from the JSON we are merging: if (mergeBase == null || (mergeBase != null && mergeBase.ModifiedAt != data.ModifiedAt)) { ResetTags (ctx, data, json); } } return data; }
private bool FindExistingEntry (TimeEntryData dataObject, out DateGroup dateGroup, out TimeEntryData existingDataObject) { foreach (var grp in dateGroups) { foreach (var obj in grp.TimeEntryList) { if (dataObject.Matches (obj)) { dateGroup = grp; existingDataObject = obj; return true; } } } dateGroup = null; existingDataObject = null; return false; }
public EditTimeEntryViewController (TimeEntryData data, List<TagData> tagList) { this.tagList = tagList; this.data = data; ViewModel = EditTimeEntryViewModel.Init (data, tagList); }
protected async override Task AddOrUpdateEntryAsync (TimeEntryData entry) { int groupIndex; int newIndex; NotifyCollectionChangedAction groupAction; TimeEntryData existingEntry; DateGroup grp; bool isNewGroup; if (FindExistingEntry (entry, out grp, out existingEntry)) { if (entry.StartTime != existingEntry.StartTime) { var date = entry.StartTime.ToLocalTime ().Date; var oldIndex = GetTimeEntryIndex (existingEntry); // Move TimeEntry to another DateGroup if (grp.Date != date) { // Remove entry from previous DateGroup: //TODO: remove dateGroup too? grp.Remove (existingEntry); groupIndex = GetDateGroupIndex (grp); await UpdateCollectionAsync (grp, NotifyCollectionChangedAction.Replace, groupIndex); // Move entry to new DateGroup grp = GetGroupFor (entry, out isNewGroup); grp.Add (entry); Sort (); newIndex = GetTimeEntryIndex (entry); await UpdateCollectionAsync (entry, NotifyCollectionChangedAction.Move, newIndex, oldIndex); // Update new container DateGroup groupIndex = GetDateGroupIndex (grp); groupAction = isNewGroup ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace; await UpdateCollectionAsync (grp, groupAction, groupIndex); return; } // Move TimeEntry inside DateGroup grp.TimeEntryList.UpdateData (entry); Sort (); // Update group groupIndex = GetDateGroupIndex (grp); await UpdateCollectionAsync (grp, NotifyCollectionChangedAction.Replace, groupIndex); newIndex = GetTimeEntryIndex (entry); if (newIndex != oldIndex) { // Move if index is differente. await UpdateCollectionAsync (entry, NotifyCollectionChangedAction.Move, newIndex, oldIndex); } // Update in any condition await UpdateCollectionAsync (entry, NotifyCollectionChangedAction.Replace, newIndex); } else { // Update TimeEntry only grp.TimeEntryList.UpdateData (entry); // Update entry newIndex = GetTimeEntryIndex (entry); await UpdateCollectionAsync (entry, NotifyCollectionChangedAction.Replace, newIndex); } } else { // Add new TimeEntry grp = GetGroupFor (entry, out isNewGroup); grp.Add (entry); Sort (); // Update group groupIndex = GetDateGroupIndex (grp); groupAction = isNewGroup ? NotifyCollectionChangedAction.Add : NotifyCollectionChangedAction.Replace; await UpdateCollectionAsync (grp, groupAction, groupIndex); // Add new TimeEntry newIndex = GetTimeEntryIndex (entry); await UpdateCollectionAsync (entry, NotifyCollectionChangedAction.Add, newIndex); } }
private DateGroup GetGroupFor (TimeEntryData dataObject, out bool isNewGroup) { isNewGroup = false; var date = dataObject.StartTime.ToLocalTime ().Date; var grp = dateGroups.FirstOrDefault (g => g.Date == date); if (grp == null) { grp = new DateGroup (date); dateGroups.Add (grp); isNewGroup = true; } return grp; }
private int GetTimeEntryIndex (TimeEntryData dataObject) { int count = 0; foreach (var grp in dateGroups) { count++; // Iterate by entry list. foreach (var obj in grp.DataObjects) { if (dataObject.Matches (obj)) { return count; } count++; } } return -1; }
public TimeEntryData Import (IDataStoreContext ctx, TimeEntryJson json, Guid? localIdHint = null, TimeEntryData mergeBase = null) { var log = ServiceContainer.Resolve<ILogger> (); var data = GetByRemoteId<TimeEntryData> (ctx, json.Id.Value, localIdHint); var merger = mergeBase != null ? new TimeEntryMerger (mergeBase) : null; if (merger != null && data != null) { merger.Add (new TimeEntryData (data)); } if (json.DeletedAt.HasValue) { if (data != null) { // TODO: Delete TimeEntryTag intermediate data log.Info (Tag, "Deleting local data for {0}.", data.ToIdString ()); ctx.Delete (data); data = null; } } else if (merger != null || ShouldOverwrite (data, json)) { data = data ?? new TimeEntryData (); ImportJson (ctx, data, json); if (merger != null) { merger.Add (data); data = merger.Result; } if (merger != null) { log.Info (Tag, "Importing {0}, merging with local data.", data.ToIdString ()); } else { log.Info (Tag, "Importing {0}, replacing local data.", data.ToIdString ()); } data = ctx.Put (data); // Also update tags from the JSON we are merging: if (mergeBase == null || (mergeBase != null && mergeBase.ModifiedAt != data.ModifiedAt)) { log.Info (Tag, "Resetting tags for {0}.", data.ToIdString ()); ResetTags (ctx, data, json); } } else { log.Info (Tag, "Skipping import of {0}.", json.ToIdString ()); } return data; }
private void HandleTimeEntryClick (TimeEntryData timeEntry) { var intent = new Intent (Activity, typeof (EditTimeEntryActivity)); intent.PutStringArrayListExtra (EditTimeEntryActivity.ExtraGroupedTimeEntriesGuids, new List<string> {timeEntry.Id.ToString()}); StartActivity (intent); }
protected async override Task RemoveEntryAsync (TimeEntryData entry) { int groupIndex; int entryIndex; NotifyCollectionChangedAction groupAction = NotifyCollectionChangedAction.Replace; DateGroup grp; TimeEntryData oldEntry; if (FindExistingEntry (entry, out grp, out oldEntry)) { entryIndex = GetTimeEntryIndex (oldEntry); groupIndex = GetDateGroupIndex (grp); grp.Remove (oldEntry); if (grp.TimeEntryList.Count == 0) { dateGroups.Remove (grp); groupAction = NotifyCollectionChangedAction.Remove; } // The order affects how the collection is updated. await UpdateCollectionAsync (entry, NotifyCollectionChangedAction.Remove, entryIndex); await UpdateCollectionAsync (grp, groupAction, groupIndex); } }
public void Add (TimeEntryData dataObject) { dataObjects.Add (dataObject); OnUpdated (); }
public static TimeEntryData Import (this TimeEntryJson json, IDataStoreContext ctx, Guid? localIdHint = null, TimeEntryData mergeBase = null) { var converter = ServiceContainer.Resolve<TimeEntryJsonConverter> (); return converter.Import (ctx, json, localIdHint, mergeBase); }
private async Task CreateTestData () { var workspace = await DataStore.PutAsync (new WorkspaceData () { RemoteId = 1, Name = "Unit Testing", }); var user = await DataStore.PutAsync (new UserData () { RemoteId = 1, Name = "Tester", DefaultWorkspaceId = workspace.Id, }); tag1 = await DataStore.PutAsync (new TagData () { RemoteId = 1, Name = DefaultTag, WorkspaceId = workspace.Id, }); tag2 = await DataStore.PutAsync (new TagData () { RemoteId = 2, Name = "Tag #2", WorkspaceId = workspace.Id, }); tag3 = await DataStore.PutAsync (new TagData () { RemoteId = 3, Name = "Tag #3", WorkspaceId = workspace.Id, }); timeEntry = await DataStore.PutAsync (new TimeEntryData () { RemoteId = 1, Description = "Initial concept", State = TimeEntryState.Finished, StartTime = MakeTime (09, 12), StopTime = MakeTime (10, 1), WorkspaceId = workspace.Id, UserId = user.Id, }); await DataStore.PutAsync (new TimeEntryTagData () { RemoteId = 1, TimeEntryId = timeEntry.Id, TagId = tag1.Id, }); await DataStore.PutAsync (new TimeEntryTagData () { RemoteId = 2, TimeEntryId = timeEntry.Id, TagId = tag2.Id, }); }
public void Remove (TimeEntryData dataObject) { dataObjects.Remove (dataObject); OnUpdated (); }