コード例 #1
0
        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";
        }
コード例 #2
0
 public void Dispose ()
 {
     timeEntryDataList.Clear ();
     timeEntryData = new TimeEntryData ();
     projectData = new ProjectData ();
     clientData = new ClientData ();
     taskData = new TaskData ();
 }
コード例 #3
0
        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 ();
        }
コード例 #4
0
        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 };
            }
        }
コード例 #5
0
 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;
 }
コード例 #6
0
        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;
        }
コード例 #7
0
        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);
        }
コード例 #8
0
        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;
        }
コード例 #9
0
        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;
            }
        }
コード例 #10
0
 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;
 }
コード例 #11
0
        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;
        }
コード例 #12
0
        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;
            }
        }
コード例 #13
0
        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,
            };
        }
コード例 #14
0
        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,
                });
            }
        }
コード例 #15
0
        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);
        }
コード例 #16
0
        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);
                }
            });
        }
コード例 #17
0
        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;
        }
コード例 #18
0
        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;
        }
コード例 #19
0
        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;
        }
コード例 #20
0
 public EditTimeEntryViewController (TimeEntryData data, List<TagData> tagList)
 {
     this.tagList = tagList;
     this.data = data;
     ViewModel = EditTimeEntryViewModel.Init (data, tagList);
 }
コード例 #21
0
        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);
            }
        }
コード例 #22
0
 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;
 }
コード例 #23
0
 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;
 }
コード例 #24
0
        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;
        }
コード例 #25
0
 private void HandleTimeEntryClick (TimeEntryData timeEntry)
 {
     var intent = new Intent (Activity, typeof (EditTimeEntryActivity));
     intent.PutStringArrayListExtra (EditTimeEntryActivity.ExtraGroupedTimeEntriesGuids, new List<string> {timeEntry.Id.ToString()});
     StartActivity (intent);
 }
コード例 #26
0
        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);
            }
        }
コード例 #27
0
 public void Add (TimeEntryData dataObject)
 {
     dataObjects.Add (dataObject);
     OnUpdated ();
 }
コード例 #28
0
 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);
 }
コード例 #29
0
        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,
            });
        }
コード例 #30
0
 public void Remove (TimeEntryData dataObject)
 {
     dataObjects.Remove (dataObject);
     OnUpdated ();
 }