diff --git a/Diary/Diary/AppShell.xaml b/Diary/Diary/AppShell.xaml index d0f7ee4..613d979 100644 --- a/Diary/Diary/AppShell.xaml +++ b/Diary/Diary/AppShell.xaml @@ -62,7 +62,7 @@ - + - + - + - + @@ -135,7 +135,7 @@ Route="mood" /> - + - + diff --git a/Diary/Diary/Clients/EntryClient.cs b/Diary/Diary/Clients/EntryClient.cs index a4a74ef..a936de6 100644 --- a/Diary/Diary/Clients/EntryClient.cs +++ b/Diary/Diary/Clients/EntryClient.cs @@ -48,12 +48,12 @@ public async Task> GetAllAsync(EntryFilterModel? ent if (entryFilter?.DateFrom != null) { - entities = entities.Where(e => e.CreatedAt >= entryFilter.DateFrom).ToList(); + entities = entities.Where(e => e.DateTime >= entryFilter.DateFrom).ToList(); } if (entryFilter?.DateTo != null) { - entities = entities.Where(e => e.CreatedAt < entryFilter.DateTo.Value.AddDays(1)).ToList(); + entities = entities.Where(e => e.DateTime < entryFilter.DateTo.Value.AddDays(1)).ToList(); } if (entryFilter?.OrderByProperty != null) @@ -76,7 +76,7 @@ public async Task> GetAllAsync(EntryFilterModel? ent public async Task> GetByDayFromPreviousYearsAsync(DateTime date) { var entities = await _entryRepository.GetByDayFromPreviousYearsAsync(date); - entities = entities.OrderByDescending(e => e.CreatedAt).ToList(); + entities = entities.OrderByDescending(e => e.DateTime).ToList(); return entities.MapToListModels(); } @@ -94,13 +94,20 @@ public async Task> GetByDayFromPreviousYearsAsync(Da /// public async Task SetAsync(EntryDetailModel model) { + DateTime? oldDateTime = null; + if (model.Id != Guid.Empty) + { + var oldEntity = await _entryRepository.GetByIdAsync(model.Id); + oldDateTime = oldEntity?.DateTime; + } + var entity = model.MapToEntity(); var savedEntity = await _entryRepository.SetAsync(entity); await _mediaRepository.DeleteIfUnusedAsync(entity.Media); await MediaFileService.DeleteUnusedFilesAsync(_mediaRepository); #if ANDROID - await ScheduleTimeMachineNotificationAsync(savedEntity); + await ScheduleTimeMachineNotificationAsync(savedEntity, oldDateTime); #endif return savedEntity.MapToDetailModel(); } @@ -144,46 +151,73 @@ public async Task> GetAllLocationPinsAsync() return entitiesWithLocation.MapToPinModels(); } - private async Task ScheduleTimeMachineNotificationAsync(EntryEntity entity) + private async Task ScheduleTimeMachineNotificationAsync(EntryEntity entity, DateTime? oldDateTime) { if (await LocalNotificationCenter.Current.AreNotificationsEnabled() == false) { await LocalNotificationCenter.Current.RequestNotificationPermission(); } + if (oldDateTime != null) // If the entry is being updated, the old notification must be updated or removed + { + var oldNotificationId = Helpers.NotificationHelper.GetNotificationIdFromCreationDate(oldDateTime.Value); + var entriesWithOldNotificationId = await _entryRepository.GetByNotificationIdAsync(oldNotificationId); + + LocalNotificationCenter.Current.Cancel(oldNotificationId); + + if (entriesWithOldNotificationId.Count > 0) + { + var previousNotification = CreateNotification(oldNotificationId, oldDateTime.Value, entriesWithOldNotificationId.Count); + await LocalNotificationCenter.Current.Show(previousNotification); + } + } + var entriesWithTheSameNotificationId = await _entryRepository.GetByNotificationIdAsync(entity.NotificationId); - TimeSpan repeatInterval; - DateTime notificationDate = entity.CreatedAt.AddYears(1); + LocalNotificationCenter.Current.Cancel(entity.NotificationId); - if (entity.CreatedAt.Month == 2 && entity.CreatedAt.Day == 29) + var request = CreateNotification(entity.NotificationId, entity.DateTime, entriesWithTheSameNotificationId.Count); + + await LocalNotificationCenter.Current.Show(request); + } + + private NotificationRequest CreateNotification(int notificationId, DateTime date, int numOfEntries) + { + TimeSpan interval; + + if (date.Month == 2 && date.Day == 29) { - notificationDate = entity.CreatedAt.AddYears(4); - repeatInterval = TimeSpan.FromDays(365 * 3 + 366); + while (date < DateTime.Now) // Notification date must be in the future + { + date = date.AddYears(4); + } + interval = TimeSpan.FromDays(365 * 3 + 366); } else { - repeatInterval = notificationDate - entity.CreatedAt; + while (date < DateTime.Now) // Notification date must be in the future + { + date = date.AddYears(1); + } + interval = TimeSpan.FromDays(365); } - var request = new NotificationRequest + + var entryText = numOfEntries > 1 ? "entries" : "entry"; + + return new NotificationRequest { - NotificationId = entity.NotificationId, + NotificationId = notificationId, Title = "Diary Time Machine", - Description = $"You have already written {entriesWithTheSameNotificationId.Count} entries on this day in the past.", + Description = $"You have already written {numOfEntries} {entryText} on this day in the past.", Schedule = new NotificationRequestSchedule { - NotifyTime = notificationDate, - NotifyRepeatInterval = repeatInterval, + NotifyTime = date, + NotifyRepeatInterval = interval, RepeatType = NotificationRepeat.TimeInterval }, Android = { Priority = AndroidPriority.High } }; - - // Remove scheduled notification with out-of-date number of diary entries written on the same day - LocalNotificationCenter.Current.Cancel([entity.NotificationId]); - - await LocalNotificationCenter.Current.Show(request); } } diff --git a/Diary/Diary/Converters/IntToMoodEmojiConverter.cs b/Diary/Diary/Converters/IntToMoodEmojiConverter.cs index 5ade9f0..9988e12 100644 --- a/Diary/Diary/Converters/IntToMoodEmojiConverter.cs +++ b/Diary/Diary/Converters/IntToMoodEmojiConverter.cs @@ -5,7 +5,7 @@ namespace Diary.Converters { public class IntToMoodEmojiConverter : BaseConverterOneWay { - private static readonly string _defaultMoodEmoji = "😢"; + private static readonly string _defaultMoodEmoji = ":("; public override string ConvertFrom(int value, CultureInfo? culture = null) { diff --git a/Diary/Diary/Converters/IntToMoodIconConverter.cs b/Diary/Diary/Converters/IntToMoodIconConverter.cs index 28c75b4..08e3270 100644 --- a/Diary/Diary/Converters/IntToMoodIconConverter.cs +++ b/Diary/Diary/Converters/IntToMoodIconConverter.cs @@ -8,7 +8,7 @@ public class IntToMoodIconConverter : BaseConverterOneWay { private static readonly string _defaultMoodIconName = ""; - public override string ConvertFrom(int value, CultureInfo? culture) + public override string ConvertFrom(int value, CultureInfo? culture = null) { return value switch { diff --git a/Diary/Diary/Entities/EntryEntity.cs b/Diary/Diary/Entities/EntryEntity.cs index 780cec8..206fc51 100644 --- a/Diary/Diary/Entities/EntryEntity.cs +++ b/Diary/Diary/Entities/EntryEntity.cs @@ -8,9 +8,7 @@ public class EntryEntity : EntityBase public string Content { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } - - public DateTime EditedAt { get; set; } + public DateTime DateTime { get; set; } public bool IsFavorite { get; set; } diff --git a/Diary/Diary/Enums/EntryFilterEnums.cs b/Diary/Diary/Enums/EntryFilterEnums.cs index 87642bf..bb2ddf3 100644 --- a/Diary/Diary/Enums/EntryFilterEnums.cs +++ b/Diary/Diary/Enums/EntryFilterEnums.cs @@ -10,8 +10,8 @@ public enum OrderByProperty [Display(Name = nameof(EntryEntity.Title))] Title, - [Display(Name = nameof(EntryEntity.CreatedAt))] - CreatedAt, + [Display(Name = nameof(EntryEntity.DateTime))] + DateTime, [Display(Name = nameof(EntryEntity.Mood))] Mood, diff --git a/Diary/Diary/Mappers/EntryMapper.cs b/Diary/Diary/Mappers/EntryMapper.cs index e4e6fb8..2ba192d 100644 --- a/Diary/Diary/Mappers/EntryMapper.cs +++ b/Diary/Diary/Mappers/EntryMapper.cs @@ -20,7 +20,7 @@ public static partial class EntryMapper public static partial ICollection MapToListModels(this ICollection entities); - [MapProperty(nameof(EntryEntity.CreatedAt), nameof(MoodListModel.DateTime))] + [MapProperty(nameof(EntryEntity.DateTime), nameof(MoodListModel.DateTime))] public static partial MoodListModel MapToMoodListModel(this EntryEntity entities); public static partial ICollection MapToMoodListModels(this ICollection entities); @@ -31,7 +31,7 @@ public static PinModel MapToPinModel(this EntryEntity entity) { EntryId = entity.Id, Title = entity.Title, - Description = $"Created: {entity.CreatedAt.ToString(Constants.MapDateTimeFormat)}", + Description = $"Created: {entity.DateTime.ToString(Constants.MapDateTimeFormat)}", Location = new Location() { Latitude = entity.Latitude ?? 0, diff --git a/Diary/Diary/MauiProgram.cs b/Diary/Diary/MauiProgram.cs index 6c33ba2..1d0cc40 100644 --- a/Diary/Diary/MauiProgram.cs +++ b/Diary/Diary/MauiProgram.cs @@ -149,6 +149,9 @@ private static void RegisterRoutes() Routing.RegisterRoute("//templates/create", typeof(TemplateCreateView)); Routing.RegisterRoute("//importexport", typeof(ImportExportView)); + + Routing.RegisterRoute("//map/detail", typeof(EntryDetailView)); + Routing.RegisterRoute("//map/edit", typeof(EntryEditView)); } private static void SetupDirectories() diff --git a/Diary/Diary/Models/Entry/EntryDetailModel.cs b/Diary/Diary/Models/Entry/EntryDetailModel.cs index f1f649a..c3a7dc6 100644 --- a/Diary/Diary/Models/Entry/EntryDetailModel.cs +++ b/Diary/Diary/Models/Entry/EntryDetailModel.cs @@ -9,8 +9,7 @@ public record EntryDetailModel : ModelBase public required Guid Id { get; set; } public string Title { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } - public DateTime EditedAt { get; set; } + public DateTime DateTime { get; set; } public bool IsFavorite { get; set; } [Range(1, 5)] diff --git a/Diary/Diary/Models/Entry/EntryFilterModel.cs b/Diary/Diary/Models/Entry/EntryFilterModel.cs index d849ccd..1ad0510 100644 --- a/Diary/Diary/Models/Entry/EntryFilterModel.cs +++ b/Diary/Diary/Models/Entry/EntryFilterModel.cs @@ -8,7 +8,7 @@ namespace Diary.Models.Entry { public record EntryFilterModel : ModelBase { - public OrderByProperty? OrderByProperty { get; set; } = EntryFilterEnums.OrderByProperty.CreatedAt; + public OrderByProperty? OrderByProperty { get; set; } = EntryFilterEnums.OrderByProperty.DateTime; public OrderByDirection? OrderByDirection { get; set; } = EntryFilterEnums.OrderByDirection.Descending; diff --git a/Diary/Diary/Models/Entry/EntryListModel.cs b/Diary/Diary/Models/Entry/EntryListModel.cs index 989a7ea..f85c447 100644 --- a/Diary/Diary/Models/Entry/EntryListModel.cs +++ b/Diary/Diary/Models/Entry/EntryListModel.cs @@ -4,8 +4,7 @@ public record EntryListModel : ModelBase public required Guid Id { get; set; } public string Title { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } - public DateTime EditedAt { get; set; } + public DateTime DateTime { get; set; } public bool IsFavorite { get; set; } public int MediaCount { get; init; } } diff --git a/Diary/Diary/Repositories/EntryRepository.cs b/Diary/Diary/Repositories/EntryRepository.cs index f50d37d..3b6e575 100644 --- a/Diary/Diary/Repositories/EntryRepository.cs +++ b/Diary/Diary/Repositories/EntryRepository.cs @@ -25,7 +25,7 @@ public async Task> GetByDayFromPreviousYearsAsync(DateT var entities = await GetAllAsync(); entities = entities - .Where(e => e.CreatedAt.Month == date.Month && e.CreatedAt.Day == date.Day && e.CreatedAt.Year < date.Year) + .Where(e => e.DateTime.Month == date.Month && e.DateTime.Day == date.Day && e.DateTime.Year < date.Year) .ToList(); return entities; @@ -61,10 +61,9 @@ public override async Task SetAsync(EntryEntity entity) if (entity.Id == Guid.Empty) { entity.Id = Guid.NewGuid(); - entity.CreatedAt = DateTime.Now; - entity.NotificationId = NotificationHelper.GetNotificationIdFromCreationDate(entity.CreatedAt); } - entity.EditedAt = DateTime.Now; + + entity.NotificationId = NotificationHelper.GetNotificationIdFromCreationDate(entity.DateTime); // Get existing labels and media that are connected to the entry var existingLabels = await GetLabelEntriesByEntryIdAsync(entity.Id); @@ -133,7 +132,7 @@ public async Task> GetEntriesByDateRangeAsync(DateTime dateTo = new DateTime(dateTo.Year, dateTo.Month, dateTo.Day, 23, 59, 59); return await connection.Table() - .Where(e => e.CreatedAt >= dateFrom && e.CreatedAt <= dateTo) + .Where(e => e.DateTime >= dateFrom && e.DateTime <= dateTo) .ToListAsync(); } diff --git a/Diary/Diary/Resources/Styles/Styles.xaml b/Diary/Diary/Resources/Styles/Styles.xaml index 277d8d1..407aa25 100644 --- a/Diary/Diary/Resources/Styles/Styles.xaml +++ b/Diary/Diary/Resources/Styles/Styles.xaml @@ -219,7 +219,16 @@ - + + + +