Commit 9146ffaa authored by HankG's avatar HankG

Merge branch 'profile-editor' into 'develop'

Profile editor

See merge request !7
parents c9e8004b 7851fb3b
Pipeline #103184943 canceled with stage
in 7 seconds
using System;
using System.Collections.Generic;
using MySocialPortalDesktop.ViewModels;
using MySocialPortalLib.Model;
namespace MySocialPortalDesktop.Converter
{
public class LabelValueToViewModelConverter<T>
{
public LabelValueViewModel LabelViewToViewModel(LabeledValue<T> labeledValue)
{
return new LabelValueViewModel(labeledValue?.Label ?? "", labeledValue?.Value.ToString() ?? "", false);
}
public LabeledValue<string> StringLabeledValueFromViewModel(LabelValueViewModel viewModel)
{
return new LabeledValue<string>(viewModel?.Label ?? "", viewModel.Value ?? "");
}
public IEnumerable<LabelValueViewModel> StringDictionaryToLabelValueViewModels(IDictionary<string, string> dictionary)
{
var vms = new List<LabelValueViewModel>();
foreach (var (key, value) in dictionary)
{
vms.Add(new LabelValueViewModel(key, value, false));
}
return vms;
}
public IEnumerable<LabelValueViewModel> SocialMediaDictionaryToLabelValueViewModels(IDictionary<string, SocialMediaAccountData> dictionary)
{
var vms = new List<LabelValueViewModel>();
foreach (var (key, value) in dictionary)
{
var id = value.ProfileId;
if (value.SocialMediaSystemName == StandardSocialNetworkNames.Twitter && value.AdditionalProperties.TryGetValue("ScreenName", out var twitterName))
{
id = twitterName;
}
vms.Add(new LabelValueViewModel(key, id, false));
}
return vms;
}
}
}
\ No newline at end of file
......@@ -11,8 +11,6 @@
<NoWarn>1701;1702;1303</NoWarn>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models" />
<Folder Include="Models\" />
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
......@@ -22,9 +20,9 @@
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.9.0-preview8" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.0-preview8" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.0-preview8" />
<PackageReference Include="Avalonia" Version="0.9.0" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.0" />
<PackageReference Include="AvaloniaRibbon" Version="1.1.0-build181119-01" />
<PackageReference Include="MySocialPortalLib" Version="1.0.0-alpha3" />
</ItemGroup>
......
namespace MySocialPortalDesktop.ViewModels
{
public interface IMultiViewUpdater
{
void ActivateDefaultView();
void ActivatePostsView();
void ActivateProfileView();
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MySocialPortalLib.Model;
using ReactiveUI;
namespace MySocialPortalDesktop.ViewModels
{
public class LabelValueListControlViewModel : ViewModelBase
{
private bool _isEditing;
private LabelValueViewModel _selectedItem;
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing != value)
{
Items.ToList().ForEach(i => i.IsEditing = value);
}
this.RaiseAndSetIfChanged(ref _isEditing, value);
}
}
public ObservableCollection<LabelValueViewModel> Items { get; }
public LabelValueViewModel SelectedItem
{
get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
public LabelValueListControlViewModel() : this(false)
{
}
public LabelValueListControlViewModel(bool isEditing) : this(new List<LabelValueViewModel>(), isEditing)
{
}
public LabelValueListControlViewModel(IEnumerable<LabelValueViewModel> initialItems, bool isEditing)
{
Items = new ObservableCollection<LabelValueViewModel>(initialItems);
IsEditing = isEditing;
}
public void AddItem()
{
Items.Add(new LabelValueViewModel("<new label>", "<new value>", IsEditing));
}
public void RemoveItem()
{
if (SelectedItem == null)
{
return;
}
Items.Remove(SelectedItem);
}
}
}
\ No newline at end of file
using System;
using MySocialPortalLib.Model;
using ReactiveUI;
namespace MySocialPortalDesktop.ViewModels
{
public class LabelValueViewModel : ViewModelBase
{
private const int BorderThicknessDefault = 3;
private const int BorderThicknessEditing = 4;
private int _borderThickness;
private bool _isEditing;
private string _label;
private string _value;
public int BorderThicknessValue
{
get => _borderThickness;
set => this.RaiseAndSetIfChanged(ref _borderThickness, value);
}
public bool IsEditing
{
get => _isEditing;
set
{
this.RaiseAndSetIfChanged(ref _isEditing, value);
BorderThicknessValue = value ? BorderThicknessEditing : BorderThicknessDefault;
}
}
public string Label
{
get => _label;
set => this.RaiseAndSetIfChanged(ref _label, value);
}
public string Value
{
get => _value;
set => this.RaiseAndSetIfChanged(ref _value, value);
}
public LabelValueViewModel() : this("", "", false)
{
}
public LabelValueViewModel(string label, string value, bool isEditing)
{
Label = label;
Value = value;
IsEditing = isEditing;
}
}
}
\ No newline at end of file
......@@ -24,10 +24,20 @@ namespace MySocialPortalDesktop.ViewModels
[SuppressMessage("ReSharper", "CA1822")]
[SuppressMessage("ReSharper", "CA1303")]
[SuppressMessage("ReSharper", "CA1031")]
public class MainWindowViewModel : ViewModelBase
public class MainWindowViewModel : ViewModelBase, IMultiViewUpdater
{
private const int DefaultMaxPosts = 50;
private const bool InitialProfileViewEnabled = false;
private const bool InitialPostViewEnable = !InitialProfileViewEnabled;
private ProfileViewModel _currentProfileViewModel;
public ProfileViewModel CurrentProfileViewModel
{
get => _currentProfileViewModel;
set => this.RaiseAndSetIfChanged(ref _currentProfileViewModel, value);
}
public PeopleListViewModel PeopleListViewModel { get; }
public PostTimelineViewModel PostTimelineViewModel { get; }
......@@ -37,13 +47,37 @@ namespace MySocialPortalDesktop.ViewModels
public MainWindowViewModel()
{
ImportedPosts = new List<Post>();
var person = new Person
{
Name = "Real name"
};
CurrentProfileViewModel = new ProfileViewModel(person);
CurrentProfileViewModel.IsProfileViewEnabled = InitialProfileViewEnabled;
PostTimelineViewModel = DefaultValueService.GetDefaultHomePostTimelineViewModel(DefaultMaxPosts);
PeopleListViewModel = new PeopleListViewModel(PostTimelineViewModel);
PostTimelineViewModel.IsPostTimelineEnabled = InitialPostViewEnable;
PeopleListViewModel = new PeopleListViewModel(PostTimelineViewModel, CurrentProfileViewModel, this);
UserLists = new ObservableCollection<string>();
var userLists = RepositoryFactory.Instance.ListsRepository.GetAllLists();
UserLists.AddRange(userLists);
}
public void ActivateDefaultView()
{
ActivatePostsView();
}
public void ActivatePostsView()
{
CurrentProfileViewModel.IsProfileViewEnabled = false;
PostTimelineViewModel.IsPostTimelineEnabled = true;
}
public void ActivateProfileView()
{
PostTimelineViewModel.IsPostTimelineEnabled = false;
CurrentProfileViewModel.IsProfileViewEnabled = true;
}
public void DoExit()
{
var app = Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
......@@ -52,16 +86,19 @@ namespace MySocialPortalDesktop.ViewModels
public void GoHome()
{
ActivatePostsView();
PostTimelineViewModel.GoHome();
}
public void LoadNewer()
{
ActivatePostsView();
PostTimelineViewModel.LoadNewer();
}
public void LoadOlder()
{
ActivatePostsView();
PostTimelineViewModel.LoadOlder();
}
......@@ -161,6 +198,5 @@ namespace MySocialPortalDesktop.ViewModels
return true;
}
}
}
......@@ -25,9 +25,13 @@ namespace MySocialPortalDesktop.ViewModels
public ObservableCollection<PersonViewModel> People { get; }
public PeopleListViewModel(PostTimelineViewModel relatedTimelineViewModel)
public PeopleListViewModel(PostTimelineViewModel relatedTimelineViewModel,
ProfileViewModel relatedProfileViewModel,
IMultiViewUpdater relatedViewUpdater)
{
RelatedTimelineViewModel = relatedTimelineViewModel;
RelatedMultiViewUpdater = relatedViewUpdater;
RelatedProfileViewModel = relatedProfileViewModel;
var peopleVms = GetAllPersonsFromDatabase();
People = new ObservableCollection<PersonViewModel>(peopleVms);
FullPeopleList = new List<PersonViewModel>(peopleVms);
......@@ -88,6 +92,10 @@ namespace MySocialPortalDesktop.ViewModels
private List<PersonViewModel> FullPeopleList { get; }
private IMultiViewUpdater RelatedMultiViewUpdater { get; }
private ProfileViewModel RelatedProfileViewModel { get; }
private PostTimelineViewModel RelatedTimelineViewModel { get; }
private void UpdateSortedView()
......@@ -131,7 +139,12 @@ namespace MySocialPortalDesktop.ViewModels
var peopleVms = allPeople?.Select(p => new PersonViewModel(p)).ToList() ?? new List<PersonViewModel>();
if (RelatedTimelineViewModel != null)
{
peopleVms.ForEach(p => p.TimelineToDrive = RelatedTimelineViewModel);
peopleVms.ForEach(p =>
{
p.TimelineToDrive = RelatedTimelineViewModel;
p.ViewToUpdate = RelatedMultiViewUpdater;
p.ProfileViewToDrive = RelatedProfileViewModel;
});
}
return peopleVms;
......
......@@ -16,6 +16,8 @@ namespace MySocialPortalDesktop.ViewModels
private Bitmap _displayIcon;
private Person _person;
private PostTimelineViewModel _timelineToDrive;
private ProfileViewModel _profileViewToDrive;
private IMultiViewUpdater _viewToUpdate;
private bool _isFavorite;
public PostTimelineViewModel TimelineToDrive
......@@ -48,6 +50,12 @@ namespace MySocialPortalDesktop.ViewModels
set => this.RaiseAndSetIfChanged(ref _isFavorite, value);
}
public IMultiViewUpdater ViewToUpdate
{
get => _viewToUpdate;
set => this.RaiseAndSetIfChanged(ref _viewToUpdate, value);
}
public Person Person
{
get => _person;
......@@ -57,7 +65,13 @@ namespace MySocialPortalDesktop.ViewModels
UpdatePersonData();
}
}
public ProfileViewModel ProfileViewToDrive
{
get => _profileViewToDrive;
set => this.RaiseAndSetIfChanged(ref _profileViewToDrive, value);
}
public PersonViewModel(Person person)
{
Person = person;
......@@ -67,6 +81,14 @@ namespace MySocialPortalDesktop.ViewModels
{
Console.WriteLine($"Change timeline to be for user {Person.Name}");
TimelineToDrive?.ConfigureSources(Person);
ViewToUpdate.ActivatePostsView();
}
public void ViewProfile()
{
Console.WriteLine($"Setting profile view for {Person.Name}");
ProfileViewToDrive.Person = Person;
ViewToUpdate.ActivateProfileView();
}
public void AddToFavorites()
......
......@@ -8,6 +8,7 @@ using MySocialPortalDesktop.Factory;
using MySocialPortalLib.Model;
using MySocialPortalLib.Service.SocialMediaConnectors;
using MySocialPortalLib.Util;
using ReactiveUI;
namespace MySocialPortalDesktop.ViewModels
{
......@@ -18,12 +19,21 @@ namespace MySocialPortalDesktop.ViewModels
private const int MaxPostsQuery = 20;
private const int MaxPostHistory = 50;
private bool _isPostTimelineEnabled;
public bool IsPostTimelineEnabled
{
get => _isPostTimelineEnabled;
set => this.RaiseAndSetIfChanged(ref _isPostTimelineEnabled, value);
}
public ObservableCollection<PostViewModel> PostViewModels { get; }
public PostTimelineViewModel(IEnumerable<Post> initialPosts)
{
PostViewModels = new ObservableCollection<PostViewModel>();
TwitterConnector = SocialMediaConnectorFactory.GetNewTwitterConnector();
IsPostTimelineEnabled = true;
LoadPosts(initialPosts);
}
......
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:MySocialPortalDesktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MySocialPortalDesktop.Views.LabelValueListControl">
<Design.DataContext>
<viewModels:LabelValueListControlViewModel/>
</Design.DataContext>
<Grid RowDefinitions="*,Auto" ColumnDefinitions="*">
<ListBox Grid.Row="0" Grid.Column="0" Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" BorderThickness="0">
<ListBox.DataTemplates>
<DataTemplate DataType="viewModels:LabelValueViewModel">
<Grid RowDefinitions="Auto" ColumnDefinitions="Auto, *">
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Label}" IsReadOnly="{Binding !IsEditing}" Margin="3" VerticalAlignment="Top" BorderThickness="{Binding BorderThicknessValue}"></TextBox>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Value}" IsReadOnly="{Binding !IsEditing}" TextWrapping="Wrap" Margin="3" BorderThickness="{Binding BorderThicknessValue}"></TextBox>
</Grid>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" IsVisible="{Binding IsEditing}" HorizontalAlignment="Center">
<Button MaxWidth="100" Content="Add Item" Command="{Binding AddItem}" Margin="5,3,5,3" Width="100"/>
<Button MaxWidth="100" Content="Remove Item" Command="{Binding RemoveItem}" Margin="5,3,5,3" Width="100"/>
</StackPanel>
</Grid>
</UserControl>
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace MySocialPortalDesktop.Views
{
public class LabelValueListControl : UserControl
{
public LabelValueListControl()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
\ No newline at end of file
......@@ -81,7 +81,8 @@
</Grid.ColumnDefinitions>
<views:PeopleListView Grid.Row="0" Grid.Column="0" Margin="2" DataContext="{Binding PeopleListViewModel}"/>
<GridSplitter Grid.Row="0" Grid.Column="1"/>
<views:PostTimelineView Grid.Row="0" Grid.Column="2" Margin="5" DataContext="{Binding PostTimelineViewModel}"/>
<views:PostTimelineView Grid.Row="0" Grid.Column="2" Margin="5" IsVisible="{Binding IsPostTimelineEnabled}" DataContext="{Binding PostTimelineViewModel}" />
<views:ProfileView Grid.Row="0" Grid.Column="2" Margin="5" IsVisible="{Binding IsProfileViewEnabled}" DataContext="{Binding CurrentProfileViewModel}" />
</Grid>
</DockPanel>
</Window>
......@@ -43,6 +43,7 @@
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="View Timeline" Command="{Binding ViewTimeline}"/>
<MenuItem Header="View Profile" Command="{Binding ViewProfile}"/>
<MenuItem Header="-"></MenuItem>
<MenuItem Header="Add To Favorites" Command="{Binding AddToFavorites}" IsEnabled="{Binding !IsFavorite}"/>
<MenuItem Header="Remove From Favorites" Command="{Binding RemoveFromFavorites}" IsEnabled="{Binding IsFavorite}"/>
......
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:MySocialPortalDesktop.Views"
xmlns:viewModels="clr-namespace:MySocialPortalDesktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MySocialPortalDesktop.Views.ProfileView">
<Design.DataContext>
<viewModels:ProfileViewModel/>
</Design.DataContext>
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*">
<Grid Grid.Row="0" Grid.Column="0" RowDefinitions="*" ColumnDefinitions="Auto,*">
<Image Grid.Row="0" Grid.Column="0" Source="{Binding ProfilePhoto}" VerticalAlignment="Top" Width="120" Height="120" Margin="10" Stretch="UniformToFill"/>
<Grid Grid.Row="0" Grid.Column="1" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, *">
<Grid.Styles>
<Style Selector="TextBox">
<Setter Property="Margin" Value="0,5"/>
<Setter Property="BorderBrush" Value="LightGray"/>
<Setter Property="MinWidth" Value="125" />
</Style>
<Style Selector="TextBlock">
<Setter Property="Margin" Value="0,5"/>
</Style>
<Style Selector="TextBlock.MainLabel">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Grid.Styles>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Real Name:" Classes="MainLabel"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding RealName}" IsReadOnly="{Binding !IsEditable}" HorizontalAlignment="Left"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Addresses:" VerticalAlignment="Top" Classes="MainLabel" IsVisible="{Binding IsAddressesFieldVisible}"/>
<views:LabelValueListControl Grid.Row="1" Grid.Column="1" DataContext="{Binding AddressesViewModel}" IsVisible="{Binding IsAddressesFieldVisible}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Social Media:" VerticalAlignment="Top" Classes="MainLabel" IsVisible="{Binding IsSocialMediaFieldVisible}"/>
<views:LabelValueListControl Grid.Row="2" Grid.Column="1" DataContext="{Binding SocialMediaAccountsViewModel}" IsVisible="{Binding IsSocialMediaFieldVisible}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="E-mails:" VerticalAlignment="Top" Classes="MainLabel" IsVisible="{Binding IsEmailFieldVisible}"/>
<views:LabelValueListControl Grid.Row="3" Grid.Column="1" DataContext="{Binding EmailsViewModel}" IsVisible="{Binding IsEmailFieldVisible}"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Phone Numbers:" VerticalAlignment="Top" Classes="MainLabel" IsVisible="{Binding IsPhoneNumbersFieldVisible}"/>
<views:LabelValueListControl Grid.Row="4" Grid.Column="1" DataContext="{Binding PhoneNumbersViewModel}" IsVisible="{Binding IsPhoneNumbersFieldVisible}"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Websites:" VerticalAlignment="Top" Classes="MainLabel" IsVisible="{Binding IsWebsitesFieldVisible}"/>
<views:LabelValueListControl Grid.Row="5" Grid.Column="1" DataContext="{Binding WebsitesViewModel}" IsVisible="{Binding IsWebsitesFieldVisible}"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Properties:" VerticalAlignment="Top" Classes="MainLabel" IsVisible="{Binding IsPropertyFieldVisible}"/>
<views:LabelValueListControl Grid.Row="6" Grid.Column="1" DataContext="{Binding AdditionalPropertiesViewModel}" IsVisible="{Binding IsPropertyFieldVisible}"/>
<StackPanel Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="{Binding EditingButtonLabel}" Command="{Binding ToggleEditing}" Width="100" Margin="3"/>
<Button Content="Save Changes" Command="{Binding CommitChanges}" Width="100" Margin="3" IsVisible="{Binding IsEditable}"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</UserControl>
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace MySocialPortalDesktop.Views
{
public class ProfileView : UserControl
{
public ProfileView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
\ No newline at end of file
using MySocialPortalDesktop.ViewModels;
using Xunit;
namespace MySocialPortalDesktopTest
{
public class MainWindowViewModelTest
{
}
}
\ No newline at end of file
using System.Data;
using System.IO;
using MySocialPortalDesktop.ViewModels;
using MySocialPortalLib.Model;
using MySocialPortalLib.Repository;
using Xunit;
namespace MySocialPortalDesktopTest.ViewModels
{
public class ProfileViewModelTest
{
[Fact]
public void TestConstruction()
{
Assert.NotNull(new ProfileViewModel());
}
[Fact]
public void TestStartStopEditing()
{
var vm = GetTempViewModel(new Person());
Assert.False(vm.IsEditable);
vm.StartEditing();
Assert.True(vm.IsEditable);
vm.StartEditing();
Assert.True(vm.IsEditable);
vm.StopEditing();
Assert.False(vm.IsEditable);
vm.StopEditing();
Assert.False(vm.IsEditable);
}
[Fact]
public void TestFailOnReadOnlyUpdate()
{
var person = new Person();
var vm = GetTempViewModel(person);
Assert.Throws<ReadOnlyException>(() => vm.RealName = "New Name");
vm.StartEditing();
var expectedRealName = "New Name";
vm.RealName = expectedRealName;
Assert.Equal(expectedRealName, vm.RealName);
}
[Fact]
public void TestCommitChanges()
{
var person = new Person
{
Name = "Original Name"
};
var vm = GetTempViewModel(person);
vm.PersonsRepository.AddPerson(person);
vm.StartEditing();
var expectedNewName = "New Name";
vm.RealName = expectedNewName;
var personFromDb = vm.PersonsRepository.FindById(person.Id);
Assert.NotEqual(expectedNewName, personFromDb.Name);
Assert.NotEqual(expectedNewName, person.Name);
vm.CommitChanges();
personFromDb = vm.PersonsRepository.FindById(person.Id);
Assert.Equal(expectedNewName, personFromDb.Name);
Assert.Equal(expectedNewName, person.Name);
}
[Fact]
public void TestRevertChanges()
{
}
private ProfileViewModel GetTempViewModel(Person person)
{
return new ProfileViewModel(person, GetTempPersonDb());
}
private PersonsLiteDbRepository GetTempPersonDb()
{
return new PersonsLiteDbRepository(new FileStream(Path.GetTempFileName() + "persons.db",
FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None, 4096,
FileOptions.RandomAccess | FileOptions.DeleteOnClose));
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment