Skip to content

Commit

Permalink
Merge pull request #20 from theunrepentantgeek/feature/viewmodel-subs…
Browse files Browse the repository at this point in the history
…criptions

Add subscriptions to our view models
  • Loading branch information
theunrepentantgeek committed Sep 7, 2019
2 parents 2fa77fd + 664d794 commit d019632
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 43 deletions.
33 changes: 32 additions & 1 deletion src/WordTutor.Core/AddVocabularyWordScreen.cs
Expand Up @@ -4,7 +4,7 @@

namespace WordTutor.Core
{
public class AddVocabularyWordScreen : Screen
public class AddVocabularyWordScreen : Screen, IEquatable<AddVocabularyWordScreen>
{
/// <summary>
/// Gets the spelling currently shown on screen
Expand Down Expand Up @@ -64,5 +64,36 @@ public AddVocabularyWordScreen WithPronunciation(string pronunciation)
Pronunciation = pronunciation ?? original.Pronunciation;
Phrase = phrase ?? original.Phrase;
}

public override bool Equals(object other) => Equals(other as AddVocabularyWordScreen);

public override bool Equals(Screen other) => Equals(other as VocabularyBrowserScreen);

public override int GetHashCode()
{
unchecked
{
return Spelling.GetHashCode() * 23
+ Pronunciation.GetHashCode() * 41
+ Phrase.GetHashCode() * 71;
}
}

public bool Equals(AddVocabularyWordScreen other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return string.Equals(Spelling, other.Spelling, StringComparison.Ordinal)
&& string.Equals(Pronunciation, other.Pronunciation, StringComparison.Ordinal)
&& string.Equals(Phrase, other.Phrase, StringComparison.Ordinal);
}
}
}
6 changes: 3 additions & 3 deletions src/WordTutor.Core/Redux/IReduxStore.cs
Expand Up @@ -23,8 +23,8 @@ public interface IReduxStore<T>
/// <param name="whenChanged">Action to invoke when the value changes.</param>
/// <returns>Subscription object; disposal release the subscription.</returns>
IDisposable Subscribe<V>(
Func<T, V> reader,
Action<V> whenChanged)
where V : System.IEquatable<V>;
Func<T, V> reader,
Action<V> whenChanged)
where V : IEquatable<V>;
}
}
7 changes: 4 additions & 3 deletions src/WordTutor.Core/Redux/ReduxStore.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace WordTutor.Core.Redux
{
Expand Down Expand Up @@ -28,7 +29,7 @@ public class ReduxStore<T> : IReduxStore<T>
/// <param name="reducer">Reducer to use for state transformations.</param>
/// <param name="initialStateFactory">Factory used to create the initial state of the application.</param>
public ReduxStore(
IReduxReducer<T> reducer,
IReduxReducer<T> reducer,
IReduxStateFactory<T> initialStateFactory)
{
_reducer = reducer ?? throw new ArgumentNullException(nameof(reducer));
Expand Down Expand Up @@ -61,7 +62,7 @@ public void Dispatch(IReduxMessage message)
_dispatching = false;
}

foreach (var subscription in _subscriptions)
foreach (var subscription in _subscriptions.ToList())
{
subscription.Publish(State);
}
Expand All @@ -77,7 +78,7 @@ public void Dispatch(IReduxMessage message)
public IDisposable Subscribe<V>(
Func<T, V> reader,
Action<V> whenChanged)
where V: IEquatable<V>
where V : IEquatable<V>
{
var subscription = new ReduxSubscription<T, V>(
reader ?? throw new ArgumentNullException(nameof(reader)),
Expand Down
8 changes: 5 additions & 3 deletions src/WordTutor.Core/Screen.cs
@@ -1,8 +1,10 @@
namespace WordTutor.Core
using System;

namespace WordTutor.Core
{
public abstract class Screen
public abstract class Screen : IEquatable<Screen>
{

public abstract bool Equals(Screen other);
}

}
31 changes: 30 additions & 1 deletion src/WordTutor.Core/VocabularyBrowserScreen.cs
Expand Up @@ -2,7 +2,7 @@

namespace WordTutor.Core
{
public class VocabularyBrowserScreen : Screen
public class VocabularyBrowserScreen : Screen, IEquatable<VocabularyBrowserScreen>
{
/// <summary>
/// Gets the currently selected word
Expand Down Expand Up @@ -54,6 +54,35 @@ public VocabularyBrowserScreen MarkAsUnmodified()
modified: false);
}

public override bool Equals(object obj) => Equals(obj as VocabularyBrowserScreen);

public override bool Equals(Screen other) => Equals(other as VocabularyBrowserScreen);

public bool Equals(VocabularyBrowserScreen other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return Selection.Equals(other.Selection)
&& Modified == other.Modified;
}

public override int GetHashCode()
{
unchecked
{
return Selection.GetHashCode() * 23
+ Modified.GetHashCode();
}
}

private VocabularyBrowserScreen(
VocabularyBrowserScreen original,
VocabularyWord selection = null,
Expand Down
28 changes: 19 additions & 9 deletions src/WordTutor.Desktop/AddVocabularyWordViewModel.cs
@@ -1,4 +1,4 @@
using System;
using System;
using WordTutor.Core;
using WordTutor.Core.Actions;
using WordTutor.Core.Redux;
Expand All @@ -8,6 +8,7 @@ namespace WordTutor.Desktop
public class AddVocabularyWordViewModel : ViewModelBase
{
private readonly IReduxStore<WordTutorApplication> _store;
private readonly IDisposable _screenSubscription;

private string _spelling;
private string _phrase;
Expand All @@ -16,7 +17,15 @@ public class AddVocabularyWordViewModel : ViewModelBase
public AddVocabularyWordViewModel(IReduxStore<WordTutorApplication> store)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
UpdateFromStore();

var screen = _store.State.CurrentScreen as AddVocabularyWordScreen;
_spelling = screen?.Spelling ?? string.Empty;
_phrase = screen?.Phrase ?? string.Empty;
_pronunciation = screen?.Pronunciation ?? string.Empty;

_screenSubscription = _store.Subscribe(
app => app.CurrentScreen as AddVocabularyWordScreen,
RefreshFromScreen);
}

public string Spelling
Expand Down Expand Up @@ -46,16 +55,17 @@ public string Pronunciation
pr => _store.Dispatch(new ModifyPronunciationMessage(pr)));
}

private void UpdateFromStore()
private void RefreshFromScreen(AddVocabularyWordScreen screen)
{
var model = _store.State.CurrentScreen as AddVocabularyWordScreen;

if (!(model is null))
if (screen is null)
{
Spelling = model?.Spelling ?? string.Empty;
Phrase = model?.Phrase ?? string.Empty;
Pronunciation = model?.Pronunciation ?? string.Empty;
_screenSubscription.Dispose();
return;
}

Spelling = screen.Spelling ?? string.Empty;
Phrase = screen.Phrase ?? string.Empty;
Pronunciation = screen.Pronunciation ?? string.Empty;
}
}
}
47 changes: 33 additions & 14 deletions src/WordTutor.Desktop/VocabularyBrowserViewModel.cs
Expand Up @@ -11,15 +11,31 @@ public class VocabularyBrowserViewModel : ViewModelBase
{
private readonly IReduxStore<WordTutorApplication> _store;
private readonly ObservableCollection<VocabularyWord> _words;
private readonly IDisposable _screenSubscription;
private readonly IDisposable _vocabularySubscription;

private VocabularyWord _selection;
private bool _modified;

public VocabularyBrowserViewModel(IReduxStore<WordTutorApplication> store)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
_words = new ObservableCollection<VocabularyWord>();
RefreshFromApplication(_store.State);

var screen = (VocabularyBrowserScreen)_store.State.CurrentScreen;
var vocab = _store.State.VocabularySet.Words;

_selection = screen.Selection;
_modified = screen.Modified;
_words = new ObservableCollection<VocabularyWord>(
vocab.OrderBy(w => w.Spelling));

_screenSubscription = _store.Subscribe(
app => app.CurrentScreen as VocabularyBrowserScreen,
RefreshFromScreen);

_vocabularySubscription = _store.Subscribe(
app => app.VocabularySet,
RefreshFromVocabularySet);
}

public VocabularyWord Selection
Expand All @@ -42,22 +58,25 @@ public ObservableCollection<VocabularyWord> Words
get => _words;
}

private void RefreshFromApplication(WordTutorApplication application)
private void RefreshFromScreen(VocabularyBrowserScreen screen)
{
var screen = application.CurrentScreen as VocabularyBrowserScreen;
if (!(screen is null))
if (screen == null)
{
Selection = screen.Selection;
Modified = screen.Modified;
_screenSubscription.Dispose();
_vocabularySubscription.Dispose();
return;
}

if (!(application.VocabularySet is null))
{
var words = application.VocabularySet.Words
.OrderBy(w => w.Spelling)
.ToList();
UpdateCollection(_words, words);
}
Selection = screen.Selection;
Modified = screen.Modified;
}

private void RefreshFromVocabularySet(VocabularySet vocabularySet)
{
var words = vocabularySet.Words
.OrderBy(w => w.Spelling)
.ToList();
UpdateCollection(_words, words);
}
}
}
21 changes: 17 additions & 4 deletions src/WordTutor.Desktop/WordTutorViewModel.cs
@@ -1,4 +1,5 @@
using WordTutor.Core;
using System;
using WordTutor.Core;
using WordTutor.Core.Redux;

namespace WordTutor.Desktop
Expand All @@ -7,6 +8,7 @@ public class WordTutorViewModel : ViewModelBase
{
private readonly IReduxStore<WordTutorApplication> _store;
private readonly ViewModelFactory _factory;
private readonly IDisposable _screenSubscription;

private ViewModelBase _currentScreen;

Expand All @@ -16,8 +18,11 @@ public class WordTutorViewModel : ViewModelBase
{
_store = store;
_factory = factory;
_currentScreen = _factory.Create(_store.State.CurrentScreen);

UpdateFromStore();
_screenSubscription = _store.Subscribe(
app => app.CurrentScreen,
RefreshFromScreen);
}

public ViewModelBase CurrentScreen
Expand All @@ -33,9 +38,17 @@ public ViewModelBase CurrentScreen
}
}

private void UpdateFromStore()
private void RefreshFromScreen(Screen screen)
{
CurrentScreen = _factory.Create(_store.State.CurrentScreen);
// Only need a new instance if the screen type changes
// As long as the type of screen is unchanged,
// the exiting ViewModel will update

var neededType = ViewModelFactory.FindViewModelType(screen.GetType());
if (CurrentScreen?.GetType() != neededType)
{
CurrentScreen = _factory.Create(screen);
}
}
}
}
7 changes: 2 additions & 5 deletions tests/WordTutor.Core.Tests/Fakes/FakeScreen.cs
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace WordTutor.Core.Tests.Fakes
namespace WordTutor.Core.Tests.Fakes
{
/// <summary>
/// A fake screen for use when testing
/// </summary>
public class FakeScreen : Screen
{
public override bool Equals(Screen other) => other is FakeScreen;
}
}

0 comments on commit d019632

Please sign in to comment.