diff --git a/examples/QuickJournal/Models/JournalEntry.cs b/examples/QuickJournal/Models/JournalEntry.cs index b8ae244605..ca36991c19 100644 --- a/examples/QuickJournal/Models/JournalEntry.cs +++ b/examples/QuickJournal/Models/JournalEntry.cs @@ -9,5 +9,10 @@ public partial class JournalEntry : IRealmObject public string? Body { get; set; } public EntryMetadata? Metadata { get; set; } + + public override string ToString() + { + return $"{Title} - {Body}"; + } } } diff --git a/examples/QuickJournal/ViewModels/EntriesViewModel.cs b/examples/QuickJournal/ViewModels/EntriesViewModel.cs index a57f41e90f..d84b1b4539 100644 --- a/examples/QuickJournal/ViewModels/EntriesViewModel.cs +++ b/examples/QuickJournal/ViewModels/EntriesViewModel.cs @@ -1,8 +1,8 @@ -using CommunityToolkit.Maui.Alerts; +using System.Collections; +using System.Collections.Specialized; +using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using CommunityToolkit.Mvvm.Messaging; -using QuickJournal.Messages; using QuickJournal.Models; using Realms; @@ -13,18 +13,30 @@ public partial class EntriesViewModel : ObservableObject private readonly Realm realm; [ObservableProperty] - private IQueryable? entries; + private IEnumerable? entries; public EntriesViewModel() { realm = Realm.GetInstance(); - Entries = realm.All(); - // We are using a WeakReferenceManager here to get notified when JournalEntriesDetailPage is closed. - // This could have been implemeted hooking up on the back button behaviour - // (with Shell.BackButtonBehaviour), but there is a current bug in MAUI - // that would make the application crash (https://github.com/dotnet/maui/pull/11438) - WeakReferenceMessenger.Default.Register(this, EntryModifiedHandler); + realm.Write(() => + { + realm.RemoveAll(); + for (int i = 0; i < 5; i++) + { + realm.Add(new JournalEntry + { + Title = $"Title-{i}", + Body = $"Body-{i}", + Metadata = new EntryMetadata + { + CreatedDate = DateTimeOffset.Now, + } + }); + } + }); + + Entries = new WrapperCollection(realm.All(), i => new JournalEntryViewModel(i)); } [RelayCommand] @@ -41,40 +53,172 @@ public async Task AddEntry() }); }); - await GoToEntry(entry); + await GoToEntry(new JournalEntryViewModel(entry)); } [RelayCommand] - public async Task EditEntry(JournalEntry entry) + public async Task EditEntry(JournalEntryViewModel entry) { await GoToEntry(entry); } [RelayCommand] - public async Task DeleteEntry(JournalEntry entry) + public async Task DeleteEntry(JournalEntryViewModel entry) { await realm.WriteAsync(() => { - realm.Remove(entry); + realm.Remove(entry.Entry); }); } - private async Task GoToEntry(JournalEntry entry) + private async Task GoToEntry(JournalEntryViewModel entry) { var navigationParameter = new Dictionary { - { "Entry", entry }, + { "Entry", entry.Entry }, }; await Shell.Current.GoToAsync($"entryDetail", navigationParameter); } + } + + public class WrapperCollection : INotifyCollectionChanged, IReadOnlyList + where T : IRealmObject + where TViewModel : class + { + private IRealmCollection _results; + private Func _viewModelFactory; + + private NotifyCollectionChangedEventHandler _collectionChanged; + + public event NotifyCollectionChangedEventHandler CollectionChanged + { + add + { + if (_collectionChanged == null) + { + SubscribeToChanges(); + } + _collectionChanged += value; + } + + remove + { + _collectionChanged -= value; + if (_collectionChanged == null) + { + UnsubscribeFromChanges(); + } + } + } + + void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + _collectionChanged?.Invoke(this, e); + } + + public void SubscribeToChanges() + { + _results.CollectionChanged += SubscribeInternal; + } + + public void UnsubscribeFromChanges() + { + _results.CollectionChanged -= SubscribeInternal; + } + + private void SubscribeInternal(object? sender, NotifyCollectionChangedEventArgs args) + { + if (args.Action == NotifyCollectionChangedAction.Reset) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + return; + } + + var newItems = args.NewItems?.Cast().Select(t => _viewModelFactory(t)).ToList(); + + List oldItems = null; + + if (args.Action != NotifyCollectionChangedAction.Remove) + { + oldItems = args.OldItems?.Cast().Select(t => _viewModelFactory(t)).ToList(); + } + else + { + oldItems = Enumerable.Repeat(default(TViewModel), args.OldItems.Count).ToList(); + } + + if (args.Action == NotifyCollectionChangedAction.Add) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, args.NewStartingIndex)); + } + else if (args.Action == NotifyCollectionChangedAction.Remove) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, args.OldStartingIndex)); + } + else if (args.Action == NotifyCollectionChangedAction.Replace) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, args.OldStartingIndex)); + } + else if (args.Action == NotifyCollectionChangedAction.Move) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, newItems, args.NewStartingIndex, args.OldStartingIndex)); + } + } + + public int Count => _results.Count; + + public TViewModel this[int index] + { + get + { + var item = _viewModelFactory(_results[index]); + Console.WriteLine($"Indexer: {item}"); + return item; + } + } + + public WrapperCollection(IQueryable query, Func viewModelFactory) + { + _results = query.AsRealmCollection(); + _viewModelFactory = viewModelFactory; + } + + public IEnumerator GetEnumerator() + { + foreach (var item in _results) + { + Console.WriteLine($"Enumerator: {item}"); + yield return _viewModelFactory(item); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public class JournalEntryViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler? PropertyChanged; + + public JournalEntry Entry { get; private set; } + + public string Summary => Entry.Title + Entry.Body; + + public JournalEntryViewModel(JournalEntry entry) + { + Entry = entry; + Entry.PropertyChanged += Inner_PropertyChanged; + } + + public override string ToString() + { + return Entry.ToString(); + } - private async void EntryModifiedHandler(object recipient, EntryModifiedMessage message) + private void Inner_PropertyChanged(object? sender, PropertyChangedEventArgs e) { - var newEntry = message.Value; - if (string.IsNullOrEmpty(newEntry.Body + newEntry.Title)) + if (e.PropertyName == nameof(JournalEntry.Title) || e.PropertyName == nameof(JournalEntry.Body)) { - await DeleteEntry(newEntry); - await Toast.Make("Empty note discarded").Show(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Summary))); } } } diff --git a/examples/QuickJournal/Views/EntriesPage.xaml b/examples/QuickJournal/Views/EntriesPage.xaml index 784aa9de23..4c6b5f716b 100644 --- a/examples/QuickJournal/Views/EntriesPage.xaml +++ b/examples/QuickJournal/Views/EntriesPage.xaml @@ -28,8 +28,8 @@ - +