Refactor SelectedTab to DeclarativeProperty

This commit is contained in:
2023-08-02 09:43:38 +02:00
parent a97432e9c7
commit c2fbc32159
7 changed files with 44 additions and 32 deletions

View File

@@ -8,16 +8,15 @@ namespace FileTime.App.Core.ViewModels;
public interface IAppState public interface IAppState
{ {
ReadOnlyObservableCollection<ITabViewModel> Tabs { get; } ReadOnlyObservableCollection<ITabViewModel> Tabs { get; }
IObservable<ITabViewModel?> SelectedTab { get; } IDeclarativeProperty<ITabViewModel?> SelectedTab { get; }
IObservable<string?> SearchText { get; } IObservable<string?> SearchText { get; }
IDeclarativeProperty<ViewMode> ViewMode { get; } IDeclarativeProperty<ViewMode> ViewMode { get; }
DeclarativeProperty<string?> RapidTravelText { get; } DeclarativeProperty<string?> RapidTravelText { get; }
ITabViewModel? CurrentSelectedTab { get; }
ITimelineViewModel TimelineViewModel { get; } ITimelineViewModel TimelineViewModel { get; }
void AddTab(ITabViewModel tabViewModel); void AddTab(ITabViewModel tabViewModel);
void RemoveTab(ITabViewModel tabViewModel); void RemoveTab(ITabViewModel tabViewModel);
void SetSearchText(string? searchText); void SetSearchText(string? searchText);
Task SwitchViewModeAsync(ViewMode newViewMode); Task SwitchViewModeAsync(ViewMode newViewMode);
void SetSelectedTab(ITabViewModel tabToSelect); Task SetSelectedTabAsync(ITabViewModel tabToSelect);
} }

View File

@@ -177,7 +177,7 @@ public class TabPersistenceService : ITabPersistenceService
.SkipWhile(t => t.TabNumber <= tabStates.ActiveTabNumber); .SkipWhile(t => t.TabNumber <= tabStates.ActiveTabNumber);
var tabToActivate = optimalTabs.Concat(suboptimalTabs).FirstOrDefault(); var tabToActivate = optimalTabs.Concat(suboptimalTabs).FirstOrDefault();
if (tabToActivate is not null) _appState.SetSelectedTab(tabToActivate); if (tabToActivate is not null) await _appState.SetSelectedTabAsync(tabToActivate);
return true; return true;
} }
@@ -205,7 +205,7 @@ public class TabPersistenceService : ITabPersistenceService
return new TabStates( return new TabStates(
tabStates, tabStates,
_appState.CurrentSelectedTab?.TabNumber _appState.SelectedTab.Value?.TabNumber
); );
} }

View File

@@ -345,7 +345,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
await _userCommandHandlerService.HandleCommandAsync(ExitRapidTravelCommand.Instance); await _userCommandHandlerService.HandleCommandAsync(ExitRapidTravelCommand.Instance);
} }
_appState.SetSelectedTab(tabViewModel!); await _appState.SetSelectedTabAsync(tabViewModel!);
} }
private Task CloseTab() private Task CloseTab()

View File

@@ -15,17 +15,16 @@ namespace FileTime.App.Core.ViewModels;
public abstract partial class AppStateBase : IAppState public abstract partial class AppStateBase : IAppState
{ {
private readonly BehaviorSubject<string?> _searchText = new(null); private readonly BehaviorSubject<string?> _searchText = new(null);
private readonly BehaviorSubject<ITabViewModel?> _selectedTab = new(null); private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new();
private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default); private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default);
private readonly SourceList<ITabViewModel> _tabs = new(); private readonly ObservableCollection<ITabViewModel> _tabs = new();
public IDeclarativeProperty<ViewMode> ViewMode { get; private set; } public IDeclarativeProperty<ViewMode> ViewMode { get; private set; }
public ReadOnlyObservableCollection<ITabViewModel> Tabs { get; private set; } public ReadOnlyObservableCollection<ITabViewModel> Tabs { get; private set; }
public IObservable<string?> SearchText { get; private set; } public IObservable<string?> SearchText { get; private set; }
public IObservable<ITabViewModel?> SelectedTab { get; private set; } public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; private set; }
public ITabViewModel? CurrentSelectedTab { get; private set; }
public DeclarativeProperty<string?> RapidTravelText { get; private set; } public DeclarativeProperty<string?> RapidTravelText { get; private set; }
partial void OnInitialize() partial void OnInitialize()
@@ -33,37 +32,28 @@ public abstract partial class AppStateBase : IAppState
RapidTravelText = new(""); RapidTravelText = new("");
ViewMode = _viewMode; ViewMode = _viewMode;
var tabsObservable = _tabs.Connect();
SearchText = _searchText.AsObservable(); SearchText = _searchText.AsObservable();
SelectedTab = Observable.CombineLatest(tabsObservable.ToCollection(), _selectedTab.DistinctUntilChanged(), GetSelectedTab); SelectedTab = DeclarativePropertyHelpers.CombineLatest<ObservableCollection<ITabViewModel>, ITabViewModel, ITabViewModel>(
_tabs.Watch(),
_selectedTab,
(tabs, selectedTab) => Task.FromResult(GetSelectedTab(tabs, selectedTab))
);
SelectedTab.Subscribe(t => Tabs = new ReadOnlyObservableCollection<ITabViewModel>(_tabs);
{
_selectedTab.OnNext(t);
CurrentSelectedTab = t;
});
tabsObservable
.Bind(out var collection)
.DisposeMany()
.Subscribe();
Tabs = collection;
} }
public void AddTab(ITabViewModel tabViewModel) public void AddTab(ITabViewModel tabViewModel)
{ {
if (_tabs.Items.Any(t => t.TabNumber == tabViewModel.TabNumber)) if (_tabs.Any(t => t.TabNumber == tabViewModel.TabNumber))
throw new ArgumentException($"There is a tab with the same tab number {tabViewModel.TabNumber}.", nameof(tabViewModel)); throw new ArgumentException($"There is a tab with the same tab number {tabViewModel.TabNumber}.", nameof(tabViewModel));
var index = _tabs.Items.Count(t => t.TabNumber < tabViewModel.TabNumber); var index = _tabs.Count(t => t.TabNumber < tabViewModel.TabNumber);
_tabs.Insert(index, tabViewModel); _tabs.Insert(index, tabViewModel);
} }
public void RemoveTab(ITabViewModel tabViewModel) public void RemoveTab(ITabViewModel tabViewModel)
{ {
if (!_tabs.Items.Contains(tabViewModel)) return; if (!_tabs.Contains(tabViewModel)) return;
_tabs.Remove(tabViewModel); _tabs.Remove(tabViewModel);
} }
@@ -72,7 +62,7 @@ public abstract partial class AppStateBase : IAppState
public async Task SwitchViewModeAsync(ViewMode newViewMode) => await _viewMode.SetValue(newViewMode); public async Task SwitchViewModeAsync(ViewMode newViewMode) => await _viewMode.SetValue(newViewMode);
public void SetSelectedTab(ITabViewModel tabToSelect) => _selectedTab.OnNext(tabToSelect); public async Task SetSelectedTabAsync(ITabViewModel tabToSelect) => await _selectedTab.SetValue(tabToSelect);
private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab) private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab)
{ {

View File

@@ -56,7 +56,7 @@ public abstract partial class ItemViewModel : IItemViewModel
{ {
ItemViewModelType.Main => _appState.RapidTravelText.Map(async (s, _) => ItemViewModelType.Main => _appState.RapidTravelText.Map(async (s, _) =>
_appState.ViewMode.Value != Models.Enums.ViewMode.RapidTravel _appState.ViewMode.Value != Models.Enums.ViewMode.RapidTravel
&& _appState.CurrentSelectedTab?.CurrentLocation.Value?.Provider is IItemNameConverterProvider nameConverterProvider && _appState.SelectedTab.Value?.CurrentLocation.Value?.Provider is IItemNameConverterProvider nameConverterProvider
? (IReadOnlyList<ItemNamePart>) await nameConverterProvider.GetItemNamePartsAsync(item) ? (IReadOnlyList<ItemNamePart>) await nameConverterProvider.GetItemNamePartsAsync(item)
: _itemNameConverterService.GetDisplayName(item.DisplayName, s) : _itemNameConverterService.GetDisplayName(item.DisplayName, s)
), ),

View File

@@ -7,16 +7,25 @@ public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropert
{ {
private TCollection? _currentCollection; private TCollection? _currentCollection;
public CollectionRepeaterProperty(IDeclarativeProperty<TCollection?> from) public CollectionRepeaterProperty(IDeclarativeProperty<TCollection?> from) : base(from.Value)
{ {
_currentCollection = from.Value; _currentCollection = from.Value;
if (from.Value is { } value) if (from.Value is { } value)
{ {
value.CollectionChanged += HandleCollectionChanged; value.CollectionChanged += HandleCollectionChanged;
} }
AddDisposable(from.Subscribe(Handle)); AddDisposable(from.Subscribe(Handle));
} }
public CollectionRepeaterProperty(TCollection? collection) : base(collection)
{
ArgumentNullException.ThrowIfNull(collection);
_currentCollection = collection;
collection.CollectionChanged += HandleCollectionChanged;
}
private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) private void HandleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
var t = Task.Run(async () => await NotifySubscribersAsync(Value)); var t = Task.Run(async () => await NotifySubscribersAsync(Value));
@@ -29,7 +38,8 @@ public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropert
{ {
currentCollection.CollectionChanged -= HandleCollectionChanged; currentCollection.CollectionChanged -= HandleCollectionChanged;
} }
if (collection is {} newCollection)
if (collection is { } newCollection)
{ {
newCollection.CollectionChanged -= HandleCollectionChanged; newCollection.CollectionChanged -= HandleCollectionChanged;
newCollection.CollectionChanged += HandleCollectionChanged; newCollection.CollectionChanged += HandleCollectionChanged;

View File

@@ -69,6 +69,19 @@ public static class DeclarativePropertyExtensions
this IDeclarativeProperty<TCollection?> collection) this IDeclarativeProperty<TCollection?> collection)
where TCollection : IList<TItem>, INotifyCollectionChanged where TCollection : IList<TItem>, INotifyCollectionChanged
=> new CollectionRepeaterProperty<TCollection?, TItem>(collection); => new CollectionRepeaterProperty<TCollection?, TItem>(collection);
public static IDeclarativeProperty<TCollection?> Watch<TCollection, TItem>(
this TCollection collection)
where TCollection : IList<TItem>, INotifyCollectionChanged
=> new CollectionRepeaterProperty<TCollection?, TItem>(collection);
public static IDeclarativeProperty<ObservableCollection<TItem>?> Watch<TItem>(
this ObservableCollection<TItem> collection)
=> new CollectionRepeaterProperty<ObservableCollection<TItem>?, TItem>(collection);
public static IDeclarativeProperty<ReadOnlyObservableCollection<TItem>?> Watch<TItem>(
this ReadOnlyObservableCollection<TItem> collection)
=> new CollectionRepeaterProperty<ReadOnlyObservableCollection<TItem>?, TItem>(collection);
public static IDeclarativeProperty<TResult?> CombineLatest<T1, T2, TResult>( public static IDeclarativeProperty<TResult?> CombineLatest<T1, T2, TResult>(