From ecb53b711c97da12b81d7efbe119fdec36551d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Augusto=20de=20Souza=20Santos?= <a61468@alunos.ipb.pt> Date: Wed, 11 Dec 2024 18:42:16 +0000 Subject: [PATCH] Muita coisa, incluindo product e user --- ShoppingList.Domain/IUnitOfWork.cs | 2 + ShoppingList.Domain/Models/User.cs | 22 ++++++ .../Repository/IUserRepository.cs | 10 +++ .../Repository/ProductRepository.cs | 7 ++ .../Repository/Repository.cs | 2 +- .../Repository/UserRepository.cs | 51 +++++++++++++ .../ShoppingListDbContext.cs | 24 ++++++ ShoppingList.Infrastructure/UnitOfWork.cs | 2 + ShoppingList.UWP/App.xaml | 15 +++- ShoppingList.UWP/App.xaml.cs | 45 ++++-------- .../Converters/BytesToImageConverter.cs | 32 ++++++++ ShoppingList.UWP/MainPage.xaml | 23 ++++-- ShoppingList.UWP/MainPage.xaml.cs | 33 ++++++++- ShoppingList.UWP/ShoppingList.UWP.csproj | 16 ++++ .../ViewModels/ProductViewModel.cs | 73 +++++++++++++++---- ShoppingList.UWP/ViewModels/UserViewModel.cs | 59 +++++++++++++++ .../Categories/ManageCategoriesPage.xaml | 4 +- .../Views/Products/ManageProductsPage.xaml | 13 +++- .../Views/Products/ManageProductsPage.xaml.cs | 4 +- .../Views/Products/ProductFormPage.xaml | 27 +++++-- .../Views/Products/ProductFormPage.xaml.cs | 40 +++++++++- ShoppingList.UWP/Views/Users/LoginDialog.xaml | 17 +++++ .../Views/Users/LoginDialog.xaml.cs | 30 ++++++++ .../Views/Users/RegisterDialog.xaml | 33 +++++++++ .../Views/Users/RegisterDialog.xaml.cs | 23 ++++++ 25 files changed, 535 insertions(+), 72 deletions(-) create mode 100644 ShoppingList.Domain/Models/User.cs create mode 100644 ShoppingList.Domain/Repository/IUserRepository.cs create mode 100644 ShoppingList.Infrastructure/Repository/UserRepository.cs create mode 100644 ShoppingList.UWP/Converters/BytesToImageConverter.cs create mode 100644 ShoppingList.UWP/ViewModels/UserViewModel.cs create mode 100644 ShoppingList.UWP/Views/Users/LoginDialog.xaml create mode 100644 ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs create mode 100644 ShoppingList.UWP/Views/Users/RegisterDialog.xaml create mode 100644 ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs diff --git a/ShoppingList.Domain/IUnitOfWork.cs b/ShoppingList.Domain/IUnitOfWork.cs index f390bb9..e10e3a3 100644 --- a/ShoppingList.Domain/IUnitOfWork.cs +++ b/ShoppingList.Domain/IUnitOfWork.cs @@ -1,4 +1,5 @@ using ShoppingList.Domain.Interfaces; +using ShoppingList.Domain.Repository; using System; using System.Threading.Tasks; @@ -6,6 +7,7 @@ namespace ShoppingList.Domain { public interface IUnitOfWork : IDisposable { ICategoryRepository CategoryRepository { get; } IProductRepository ProductRepository { get; } + IUserRepository UserRepository { get; } Task SaveAsync(); } } diff --git a/ShoppingList.Domain/Models/User.cs b/ShoppingList.Domain/Models/User.cs new file mode 100644 index 0000000..be217d0 --- /dev/null +++ b/ShoppingList.Domain/Models/User.cs @@ -0,0 +1,22 @@ +using ShoppingList.Domain.SeedWork; +using System; +using System.Security.Cryptography; +using System.Text; + +namespace ShoppingList.Domain.Models { + public class User : Entity { + public string Username { get; set; } + private string _password; + public string Password { + get { return _password; } + set { + var data = Encoding.UTF8.GetBytes(value); + var hasData = new SHA1Managed().ComputeHash(data); + _password = BitConverter.ToString(hasData).Replace("-", "").ToUpper(); + } + } + + public bool IsAdmin { get; set; } + + } +} diff --git a/ShoppingList.Domain/Repository/IUserRepository.cs b/ShoppingList.Domain/Repository/IUserRepository.cs new file mode 100644 index 0000000..1d089af --- /dev/null +++ b/ShoppingList.Domain/Repository/IUserRepository.cs @@ -0,0 +1,10 @@ +using ShoppingList.Domain.Models; +using ShoppingList.Domain.SeedWork; +using System.Threading.Tasks; + +namespace ShoppingList.Domain.Repository { + public interface IUserRepository : IRepository<User> { + Task<User> FindByUsernameAsync(string username); + Task<User> FindByUsernameAndPasswordAsync(string username, string password); + } +} diff --git a/ShoppingList.Infrastructure/Repository/ProductRepository.cs b/ShoppingList.Infrastructure/Repository/ProductRepository.cs index af44352..08caf5c 100644 --- a/ShoppingList.Infrastructure/Repository/ProductRepository.cs +++ b/ShoppingList.Infrastructure/Repository/ProductRepository.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using ShoppingList.Domain.Interfaces; using ShoppingList.Domain.Models; +using System.Collections.Generic; using System.Threading.Tasks; namespace ShoppingList.Infrastructure.Repository { @@ -44,5 +45,11 @@ namespace ShoppingList.Infrastructure.Repository { return p; } + + public override Task<List<Product>> FindAllAsync() { + return _dbContext.Products + .Include(x => x.Category) + .ToListAsync(); + } } } diff --git a/ShoppingList.Infrastructure/Repository/Repository.cs b/ShoppingList.Infrastructure/Repository/Repository.cs index 5ca1642..fed9317 100644 --- a/ShoppingList.Infrastructure/Repository/Repository.cs +++ b/ShoppingList.Infrastructure/Repository/Repository.cs @@ -30,7 +30,7 @@ namespace ShoppingList.Infrastructure.Repository { return _dbContext.Set<T>().SingleAsync(x => x.Id == id); } - public Task<List<T>> FindAllAsync() { + public virtual Task<List<T>> FindAllAsync() { return _dbContext.Set<T>().ToListAsync(); } diff --git a/ShoppingList.Infrastructure/Repository/UserRepository.cs b/ShoppingList.Infrastructure/Repository/UserRepository.cs new file mode 100644 index 0000000..f3b5a7c --- /dev/null +++ b/ShoppingList.Infrastructure/Repository/UserRepository.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using ShoppingList.Domain.Models; +using ShoppingList.Domain.Repository; +using System.Threading.Tasks; + +namespace ShoppingList.Infrastructure.Repository { + public class UserRepository : Repository<User>, IUserRepository { + public UserRepository(ShoppingListDbContext dbContext) : base(dbContext) { + } + public async Task<User> FindByUsernameAsync(string username) { + return await _dbContext.Users + .SingleOrDefaultAsync(x => x.Username == username); + } + + public async Task<User> FindByUsernameAndPasswordAsync(string username, string password) { + return await _dbContext.Users + .SingleOrDefaultAsync(x => x.Username == username && x.Password == password); + } + + public override async Task<User> FindOrCreateAsync(User entity) { + var f = await FindByUsernameAsync(entity.Username); + if (f == null) { + Create(entity); + f = entity; + } + + return f; + } + + public override async Task<User> UpsertAsync(User entity) { + User u = null; + User existing = await FindByUsernameAsync(entity.Username); + + if (existing == null) { + if (entity.Id == 0) + Create(entity); + else + Update(entity); + u = entity; + } else if (existing.Id == entity.Id) { + if (existing.Username != entity.Username) + Update(entity); + u = entity; + } else { + _dbContext.Entry(entity).State = EntityState.Detached; + } + + return u; + } + } +} diff --git a/ShoppingList.Infrastructure/ShoppingListDbContext.cs b/ShoppingList.Infrastructure/ShoppingListDbContext.cs index 1c11021..513d772 100644 --- a/ShoppingList.Infrastructure/ShoppingListDbContext.cs +++ b/ShoppingList.Infrastructure/ShoppingListDbContext.cs @@ -6,6 +6,7 @@ namespace ShoppingList.Infrastructure { public class ShoppingListDbContext : DbContext { public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } + public DbSet<User> Users { get; set; } public string DbPath { get; } public ShoppingListDbContext() { @@ -35,6 +36,29 @@ namespace ShoppingList.Infrastructure { .Property(x => x.Name) .IsRequired() .HasMaxLength(50); + + // User + modelBuilder.Entity<User>() + .HasIndex(x => x.Username) + .IsUnique(); + modelBuilder.Entity<User>() + .Property(x => x.Username) + .IsRequired() + .HasMaxLength(50); + modelBuilder.Entity<User>() + .Property(x => x.Password) + .IsRequired() + .HasMaxLength(250); + modelBuilder.Entity<User>() + .Property(x => x.IsAdmin) + .IsRequired(); + modelBuilder.Entity<User>() + .HasData(new User { + Id = 1, + Username = "admin", + Password = "admin", + IsAdmin = true + }); } } } diff --git a/ShoppingList.Infrastructure/UnitOfWork.cs b/ShoppingList.Infrastructure/UnitOfWork.cs index d2bc61a..9964365 100644 --- a/ShoppingList.Infrastructure/UnitOfWork.cs +++ b/ShoppingList.Infrastructure/UnitOfWork.cs @@ -1,5 +1,6 @@ using ShoppingList.Domain; using ShoppingList.Domain.Interfaces; +using ShoppingList.Domain.Repository; using ShoppingList.Infrastructure.Repository; using System.Threading.Tasks; @@ -20,6 +21,7 @@ namespace ShoppingList.Infrastructure { public ICategoryRepository CategoryRepository => new CategoryRepository(_dbContext); public IProductRepository ProductRepository => new ProductRepository(_dbContext); + public IUserRepository UserRepository => new UserRepository(_dbContext); public void Dispose() { _dbContext.Dispose(); diff --git a/ShoppingList.UWP/App.xaml b/ShoppingList.UWP/App.xaml index 6763bed..3d5d397 100644 --- a/ShoppingList.UWP/App.xaml +++ b/ShoppingList.UWP/App.xaml @@ -2,6 +2,17 @@ x:Class="ShoppingList.UWP.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:ShoppingList.UWP"> + xmlns:local="using:ShoppingList.UWP" xmlns:converters="using:ShoppingList.UWP.Converters" + RequestedTheme="Light"> + <Application.Resources> + <ResourceDictionary> + <ResourceDictionary.MergedDictionaries> + <ResourceDictionary> + <converters:BytesToImageConverter x:Key="bytesToImageConverter"/> + </ResourceDictionary> -</Application> + + </ResourceDictionary.MergedDictionaries> + </ResourceDictionary> + </Application.Resources> +</Application> \ No newline at end of file diff --git a/ShoppingList.UWP/App.xaml.cs b/ShoppingList.UWP/App.xaml.cs index 92131a3..2d4aa3c 100644 --- a/ShoppingList.UWP/App.xaml.cs +++ b/ShoppingList.UWP/App.xaml.cs @@ -1,35 +1,25 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; +using ShoppingList.UWP.ViewModels; +using System; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; -using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; -namespace ShoppingList.UWP -{ +namespace ShoppingList.UWP { /// <summary> ///Fornece o comportamento especÃfico do aplicativo para complementar a classe Application padrão. /// </summary> - sealed partial class App : Application - { + sealed partial class App : Application { + public static UserViewModel UserViewModel { get; set; } /// <summary> /// Inicializa o objeto singleton do aplicativo. Essa é a primeira linha do código criado /// executado e, por isso, é o equivalente lógico de main() ou WinMain(). /// </summary> - public App() - { + public App() { this.InitializeComponent(); this.Suspending += OnSuspending; + UserViewModel = new UserViewModel(); } /// <summary> @@ -37,21 +27,18 @@ namespace ShoppingList.UWP /// serão usados, por exemplo, quando o aplicativo for iniciado para abrir um arquivo especÃfico. /// </summary> /// <param name="e">Detalhes sobre a solicitação e o processo de inicialização.</param> - protected override void OnLaunched(LaunchActivatedEventArgs e) - { + protected override void OnLaunched(LaunchActivatedEventArgs e) { Frame rootFrame = Window.Current.Content as Frame; // Não repita a inicialização do aplicativo quando a Janela já tiver conteúdo, // apenas verifique se a janela está ativa - if (rootFrame == null) - { + if (rootFrame == null) { // Crie um Quadro para atuar como o contexto de navegação e navegue para a primeira página rootFrame = new Frame(); rootFrame.NavigationFailed += OnNavigationFailed; - if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) - { + if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { // TODO: Carregue o estado do aplicativo suspenso anteriormente } @@ -59,10 +46,8 @@ namespace ShoppingList.UWP Window.Current.Content = rootFrame; } - if (e.PrelaunchActivated == false) - { - if (rootFrame.Content == null) - { + if (e.PrelaunchActivated == false) { + if (rootFrame.Content == null) { // Quando a pilha de navegação não for restaurada, navegar para a primeira página, // configurando a nova página passando as informações necessárias como um parâmetro // de navegação @@ -78,8 +63,7 @@ namespace ShoppingList.UWP /// </summary> /// <param name="sender">O Quadro com navegação com falha</param> /// <param name="e">Detalhes sobre a falha na navegação</param> - void OnNavigationFailed(object sender, NavigationFailedEventArgs e) - { + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) { throw new Exception("Failed to load Page " + e.SourcePageType.FullName); } @@ -90,8 +74,7 @@ namespace ShoppingList.UWP /// </summary> /// <param name="sender">A origem da solicitação de suspensão.</param> /// <param name="e">Detalhes sobre a solicitação de suspensão.</param> - private void OnSuspending(object sender, SuspendingEventArgs e) - { + private void OnSuspending(object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); //TODO: Salvar o estado do aplicativo e parar qualquer atividade em segundo plano deferral.Complete(); diff --git a/ShoppingList.UWP/Converters/BytesToImageConverter.cs b/ShoppingList.UWP/Converters/BytesToImageConverter.cs new file mode 100644 index 0000000..f9e02e5 --- /dev/null +++ b/ShoppingList.UWP/Converters/BytesToImageConverter.cs @@ -0,0 +1,32 @@ +using System; +using Windows.Storage.Streams; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Media.Imaging; + +namespace ShoppingList.UWP.Converters { + public class BytesToImageConverter : IValueConverter { + + public object Convert(object value, Type targetType, object parameter, string language) { + if (value == null || !(value is byte[])) + return null; + + using (InMemoryRandomAccessStream ms = new InMemoryRandomAccessStream()) { + using (DataWriter writer = new DataWriter(ms.GetOutputStreamAt(0))) { + writer.WriteBytes((byte[])value); + writer.StoreAsync().GetResults(); + } + + var image = new BitmapImage(); + image.SetSource(ms); + return image; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) { + if (value == null || !(value is BitmapImage)) + return (null); + return (null); + } + } +} + diff --git a/ShoppingList.UWP/MainPage.xaml b/ShoppingList.UWP/MainPage.xaml index 7718618..ce104ba 100644 --- a/ShoppingList.UWP/MainPage.xaml +++ b/ShoppingList.UWP/MainPage.xaml @@ -10,18 +10,29 @@ RequestedTheme="Light"> <Grid> - <NavigationView IsBackButtonVisible="Collapsed" IsSettingsVisible="False" x:Name="nvMain" ItemInvoked="NvMain_ItemInvoked"> + <NavigationView IsBackButtonVisible="Collapsed" IsSettingsVisible="False" + x:Name="nvMain" ItemInvoked="NvMain_ItemInvoked" + PaneTitle="{x:Bind UserViewModel.LoggedUser.Username, Mode=OneWay}"> <NavigationView.MenuItems> - <NavigationViewItem x:Name="nvShoppingList" Content="Shopping List" Icon="List"/> + <NavigationViewItem x:Name="nvShoppingList" Content="Shopping List" Icon="List" Tag="shoppingList"/> <NavigationViewItem x:Name="nvProducts" Content="Products" Icon="Shop" Tag="products"/> <NavigationViewItem x:Name="nvCategories" Content="Categories" Icon="Library" Tag="categories"/> - <NavigationViewItem x:Name="nvUsers" Content="Users" Icon="OtherUser"/> + <NavigationViewItem x:Name="nvUsers" Content="Users" Icon="OtherUser" Tag="users"/> </NavigationView.MenuItems> <NavigationView.PaneFooter> <StackPanel> - <NavigationViewItem x:Name="nvLogin" Content="Login" Icon="Permissions"/> - <NavigationViewItem x:Name="nvLogout" Content="Logout" Icon="NewWindow"/> - <NavigationViewItem x:Name="nvRegister" Content="Register" Icon="ContactInfo"/> + <NavigationViewItem x:Name="nvLogin" Content="Login" + Icon="Permissions" + Tapped="nvLogin_Tapped" + Visibility="{x:Bind UserViewModel.IsNotLogged, Mode=OneWay}"/> + <NavigationViewItem x:Name="nvLogout" Content="Logout" + Icon="NewWindow" + Tapped="nvLogout_Tapped" + Visibility="{x:Bind UserViewModel.IsLogged, Mode=OneWay}"/> + <NavigationViewItem x:Name="nvRegister" Content="Register" + Icon="ContactInfo" + Tapped="nvRegister_Tapped" + Visibility="{x:Bind UserViewModel.IsNotLogged, Mode=OneWay}"/> </StackPanel> </NavigationView.PaneFooter> <Frame x:Name="frmMain" Padding="10,0"> diff --git a/ShoppingList.UWP/MainPage.xaml.cs b/ShoppingList.UWP/MainPage.xaml.cs index 35c6667..6b18171 100644 --- a/ShoppingList.UWP/MainPage.xaml.cs +++ b/ShoppingList.UWP/MainPage.xaml.cs @@ -1,5 +1,8 @@ -using ShoppingList.UWP.Views.Categories; +using ShoppingList.UWP.ViewModels; +using ShoppingList.UWP.Views.Categories; using ShoppingList.UWP.Views.Products; +using ShoppingList.UWP.Views.Users; +using System; using Windows.UI.Xaml.Controls; // O modelo de item de Página em Branco está documentado em https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x416 @@ -9,8 +12,10 @@ namespace ShoppingList.UWP { /// Uma página vazia que pode ser usada isoladamente ou navegada dentro de um Quadro. /// </summary> public sealed partial class MainPage : Page { + public UserViewModel UserViewModel { get; set; } public MainPage() { this.InitializeComponent(); + UserViewModel = App.UserViewModel; } private void NvMain_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) { @@ -22,10 +27,36 @@ namespace ShoppingList.UWP { case "products": frmMain.Navigate(typeof(ManageProductsPage)); break; + case "shoppingList": + frmMain.Navigate(typeof(ManageCategoriesPage)); + break; + case "users": + frmMain.Navigate(typeof(ManageCategoriesPage)); + break; default: break; } } } + + private void nvLogin_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { + + } + + private void nvLogout_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { + UserViewModel.DoLogout(); + frmMain.BackStack.Clear(); + frmMain.Content = null; + } + + private async void nvRegister_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { + RegisterDialog dlg = new RegisterDialog(); + var res = await dlg.ShowAsync(); + if (res == ContentDialogResult.Primary) { + if (App.UserViewModel.IsLogged) { + frmMain.Navigate(typeof(ManageCategoriesPage)); + } + } + } } } diff --git a/ShoppingList.UWP/ShoppingList.UWP.csproj b/ShoppingList.UWP/ShoppingList.UWP.csproj index a3ec477..b361ba1 100644 --- a/ShoppingList.UWP/ShoppingList.UWP.csproj +++ b/ShoppingList.UWP/ShoppingList.UWP.csproj @@ -119,6 +119,7 @@ <Compile Include="App.xaml.cs"> <DependentUpon>App.xaml</DependentUpon> </Compile> + <Compile Include="Converters\BytesToImageConverter.cs" /> <Compile Include="MainPage.xaml.cs"> <DependentUpon>MainPage.xaml</DependentUpon> </Compile> @@ -126,6 +127,7 @@ <Compile Include="ViewModels\BindableBase.cs" /> <Compile Include="ViewModels\CategoryViewModel.cs" /> <Compile Include="ViewModels\ProductViewModel.cs" /> + <Compile Include="ViewModels\UserViewModel.cs" /> <Compile Include="Views\Categories\CategoryFormPage.xaml.cs"> <DependentUpon>CategoryFormPage.xaml</DependentUpon> </Compile> @@ -138,6 +140,12 @@ <Compile Include="Views\Products\ProductFormPage.xaml.cs"> <DependentUpon>ProductFormPage.xaml</DependentUpon> </Compile> + <Compile Include="Views\Users\LoginDialog.xaml.cs"> + <DependentUpon>LoginDialog.xaml</DependentUpon> + </Compile> + <Compile Include="Views\Users\RegisterDialog.xaml.cs"> + <DependentUpon>RegisterDialog.xaml</DependentUpon> + </Compile> </ItemGroup> <ItemGroup> <AppxManifest Include="Package.appxmanifest"> @@ -179,6 +187,14 @@ <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> + <Page Include="Views\Users\LoginDialog.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Views\Users\RegisterDialog.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> diff --git a/ShoppingList.UWP/ViewModels/ProductViewModel.cs b/ShoppingList.UWP/ViewModels/ProductViewModel.cs index b120911..f0d2c63 100644 --- a/ShoppingList.UWP/ViewModels/ProductViewModel.cs +++ b/ShoppingList.UWP/ViewModels/ProductViewModel.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; namespace ShoppingList.UWP.ViewModels { public class ProductViewModel : BindableBase { public ObservableCollection<Product> Products { get; set; } + private string _productName; public string ProductName { get { return _productName; } @@ -15,17 +16,47 @@ namespace ShoppingList.UWP.ViewModels { OnPropertyChanged(nameof(Invalid)); } } + + private string _categoryName; + public string CategoryName { + get { return _categoryName; } + set { + Set(ref _categoryName, value); + OnPropertyChanged(nameof(Valid)); + OnPropertyChanged(nameof(Invalid)); + } + } + private Product _product; public Product Product { get { return _product; } set { _product = value; + CategoryName = _product?.Category?.Name; ProductName = _product?.Name; + Thumb = _product?.Thumb; } } + private byte[] _thumb; + public byte[] Thumb { + get { return _thumb; } + set { + Set(ref _thumb, value); + } + } + + private string _title; + public string Title { + get { return _title; } + set { _title = value; } + } + public bool Valid { - get { return !string.IsNullOrWhiteSpace(ProductName); } + get { + return !string.IsNullOrWhiteSpace(CategoryName) + && !string.IsNullOrWhiteSpace(ProductName); + } } public bool Invalid { @@ -33,13 +64,20 @@ namespace ShoppingList.UWP.ViewModels { } public ProductViewModel() { - Product = new Product(); Products = new ObservableCollection<Product>(); + Product = new Product(); + Title = "Products"; + } + public async void DeleteAsync(Product p) { + using (UnitOfWork uow = new UnitOfWork()) { + uow.ProductRepository.Delete(p); + Products.Remove(p); + await uow.SaveAsync(); + } } public async void LoadAllAsync() { using (var uow = new UnitOfWork()) { - System.Console.WriteLine(uow.GetDbPath()); var list = await uow.ProductRepository.FindAllAsync(); Products.Clear(); foreach (var product in list) { @@ -48,24 +86,29 @@ namespace ShoppingList.UWP.ViewModels { } } - internal async Task<Product> UpsertAsync() { - Product res = null; - + internal async Task<ObservableCollection<Category>> LoadCategoriesByNameStartedWithAsync(string categoryName) { + ObservableCollection<Category> res = null; using (var uow = new UnitOfWork()) { - Product.Name = ProductName; - Product.CategoryId = 5; - res = await uow.ProductRepository.UpsertAsync(Product); - await uow.SaveAsync(); + var list = await uow.CategoryRepository.FindAllByNameStartedWithAsync(categoryName); + res = new ObservableCollection<Category>(list); } - return res; } - internal async void DeleteAsync(Product p) { - using (UnitOfWork uow = new UnitOfWork()) { - uow.ProductRepository.Delete(p); - Products.Remove(p); + internal async Task<Product> UpsertAsync() { + using (var uow = new UnitOfWork()) { + Category c = new Category() { Name = CategoryName }; + Category cUpdated = await uow.CategoryRepository.FindOrCreateAsync(c); await uow.SaveAsync(); + + Product.CategoryId = cUpdated.Id; + Product.Category = null; + Product.Name = ProductName; + Product.Thumb = Thumb; + Product pUpdated = await uow.ProductRepository.UpsertAsync(Product); + await uow.SaveAsync(); + + return pUpdated; } } } diff --git a/ShoppingList.UWP/ViewModels/UserViewModel.cs b/ShoppingList.UWP/ViewModels/UserViewModel.cs new file mode 100644 index 0000000..481abdc --- /dev/null +++ b/ShoppingList.UWP/ViewModels/UserViewModel.cs @@ -0,0 +1,59 @@ +using ShoppingList.Domain.Models; +using ShoppingList.Infrastructure; +using System.Threading.Tasks; + +namespace ShoppingList.UWP.ViewModels { + public class UserViewModel : BindableBase { + public User User { get; set; } + private User _loggedUser; + public User LoggedUser { + get { return _loggedUser; } + set { + _loggedUser = value; + OnPropertyChanged(); + } + } + public bool IsLogged { get => _loggedUser != null; } + public bool IsNotLogged { get => !IsLogged; } + private bool _showError; + public bool ShowError { + get { return _showError; } + set { + _showError = value; + OnPropertyChanged(); + } + } + public bool IsAdmin => LoggedUser != null && LoggedUser.IsAdmin; + + public UserViewModel() { + User = new User(); + } + + internal async Task<bool> DoLoginAsync() { + using (UnitOfWork uow = new UnitOfWork()) { + var user = await uow.UserRepository + .FindByUsernameAndPasswordAsync(User.Username, User.Password); + LoggedUser = user; + ShowError = user == null; + return !ShowError; + } + } + + internal async Task<bool> DoRegisterAsync() { + using (UnitOfWork uow = new UnitOfWork()) { + var user = await uow.UserRepository.FindByUsernameAsync(User.Username); + if (user == null) { + uow.UserRepository.Create(User); + await uow.SaveAsync(); + LoggedUser = User; + ShowError = User == null; + } + } + return !ShowError; + } + + internal void DoLogout() { + LoggedUser = null; + } + } +} diff --git a/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml b/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml index 63a3bd4..402edae 100644 --- a/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml +++ b/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml @@ -47,13 +47,13 @@ </Flyout> </FlyoutBase.AttachedFlyout> <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Name}" /> - <Button x:Name="btnEdit" Grid.Column="1" Margin="5, 0" Click="btnEdit_Click"> + <Button x:Name="btnEdit" Grid.Column="1" Margin="5, 0" Click="btnEdit_Click" Background="Transparent"> <StackPanel> <SymbolIcon Symbol="Edit" /> <TextBlock Text="Edit" /> </StackPanel> </Button> - <Button x:Name="btnDelete" Grid.Column="2" Margin="5, 0" Click="btnDelete_Click"> + <Button x:Name="btnDelete" Grid.Column="2" Margin="5, 0" Click="btnDelete_Click" Background="Transparent"> <StackPanel> <SymbolIcon Symbol="Delete" /> <TextBlock Text="Delete" /> diff --git a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml index 160c1e2..a50e55b 100644 --- a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml +++ b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml @@ -18,7 +18,7 @@ <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> - <TextBlock Text="Products" Style="{StaticResource HeaderTextBlockStyle}"/> + <TextBlock Text="{x:Bind ProductViewModel.Title}" Style="{StaticResource HeaderTextBlockStyle}"/> <CommandBar Grid.Column="1" VerticalAlignment="Center" x:Name="mainCommandBar" Background="Transparent" OverflowButtonVisibility="Collapsed" DefaultLabelPosition="Right" > <AppBarButton x:Name="btnNew" Icon="Add" Label="New" ToolTipService.ToolTip="New Product" Click="btnNew_Click" /> </CommandBar> @@ -34,6 +34,7 @@ <DataTemplate x:DataType="models:Product"> <Grid Margin="0, 5" Tapped="Grid_Tapped"> <Grid.ColumnDefinitions> + <ColumnDefinition Width="100"/> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> @@ -46,14 +47,18 @@ </StackPanel> </Flyout> </FlyoutBase.AttachedFlyout> - <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Name}" /> - <Button x:Name="btnEdit" Grid.Column="1" Margin="5, 0" Click="btnEdit_Click"> + <Image Margin="10" x:Name="thumb" Source="{x:Bind Thumb, Converter={StaticResource bytesToImageConverter}}"/> + <StackPanel Grid.Column="1"> + <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Name}"/> + <TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{x:Bind Category.Name}" Foreground="Gray"/> + </StackPanel> + <Button x:Name="btnEdit" Grid.Column="2" Margin="5, 0" Click="btnEdit_Click" Background="Transparent"> <StackPanel> <SymbolIcon Symbol="Edit" /> <TextBlock Text="Edit" /> </StackPanel> </Button> - <Button x:Name="btnDelete" Grid.Column="2" Margin="5, 0" Click="btnDelete_Click"> + <Button x:Name="btnDelete" Grid.Column="3" Margin="5, 0" Click="btnDelete_Click" Background="Transparent"> <StackPanel> <SymbolIcon Symbol="Delete" /> <TextBlock Text="Delete" /> diff --git a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs index 4fc8d8c..d105b8c 100644 --- a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs +++ b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs @@ -17,8 +17,8 @@ namespace ShoppingList.UWP.Views.Products { public ProductViewModel ProductViewModel { get; set; } public ManageProductsPage() { - this.InitializeComponent(); ProductViewModel = new ProductViewModel(); + this.InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { @@ -27,7 +27,7 @@ namespace ShoppingList.UWP.Views.Products { } private void btnNew_Click(object sender, RoutedEventArgs e) { - this.Frame.Navigate(typeof(ProductFormPage)); + this.Frame.Navigate(typeof(ProductFormPage), ProductViewModel); } private void Grid_Tapped(object sender, TappedRoutedEventArgs e) { diff --git a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml index e69d6da..3743995 100644 --- a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml +++ b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml @@ -32,12 +32,25 @@ <AppBarButton x:Name="btnCancel" Icon="Cancel" Label="Cancel" Click="btnCancel_Click" /> </CommandBar> </Grid> - <StackPanel Grid.Row="1" Width="300" Padding="0, 30"> - <TextBox x:Name="txtName" Header="Name" Text="{x:Bind ProductViewModel.ProductName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> - <Border Background="red" Padding="5" Visibility="{x:Bind ProductViewModel.Invalid, Mode=OneWay}"> - <TextBlock Text="Invalid product name" Foreground="White"/> - </Border> - <AutoSuggestBox x:Name="txtSuggestCategory" Header="Category" PlaceholderText="Type to search..." Margin="0,10"/> - </StackPanel> + <ScrollViewer Grid.Row="1" Padding="0, 30"> + <StackPanel> + <AutoSuggestBox x:Name="txtSuggestCategory" + Header="Category name" + QueryIcon="Find" + DisplayMemberPath="Name" + Text="{x:Bind ProductViewModel.CategoryName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" + TextChanged="txtSuggestCategory_TextChanged" + QuerySubmitted="txtSuggestCategory_QuerySubmitted" + SuggestionChosen="txtSuggestCategory_SuggestionChosen"/> + <TextBox x:Name="txtName" Header="Product name" Width="300" + Text="{x:Bind ProductViewModel.ProductName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> + <Image Margin="0, 10" MaxHeight="350" x:Name="thumb" + Source="{x:Bind ProductViewModel.Thumb, Converter={StaticResource bytesToImageConverter}, Mode=OneWay}"/> + <Button Margin="0, 10" x:Name="btnUploadImage" Tapped="btnUploadImage_Tapped" Content="Choose a file..."/> + <Border Background="red" Padding="5" Visibility="{x:Bind ProductViewModel.Invalid, Mode=OneWay}"> + <TextBlock Text="Invalid product name" Foreground="White"/> + </Border> + </StackPanel> + </ScrollViewer> </Grid> </Page> diff --git a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs index 6bbd538..5d9209c 100644 --- a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs +++ b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs @@ -1,4 +1,8 @@ using ShoppingList.UWP.ViewModels; +using System; +using System.IO; +using Windows.Storage; +using Windows.Storage.Pickers; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; @@ -12,6 +16,7 @@ namespace ShoppingList.UWP.Views.Products { /// </summary> public sealed partial class ProductFormPage : Page { public ProductViewModel ProductViewModel { get; set; } + public CategoryViewModel CategoryViewModel { get; set; } public string Title { get; set; } public ProductFormPage() { @@ -20,6 +25,9 @@ namespace ShoppingList.UWP.Views.Products { } protected override void OnNavigatedTo(NavigationEventArgs e) { + CategoryViewModel.LoadAllAsync(); + if (e.Parameter != null) + ProductViewModel = e.Parameter as ProductViewModel; Title = "Add Product"; if (e.Parameter != null) { ProductViewModel = e.Parameter as ProductViewModel; @@ -40,8 +48,38 @@ namespace ShoppingList.UWP.Views.Products { Frame.GoBack(); } - private void txtSuggestCategory_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { + private async void txtSuggestCategory_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { + var list = await ProductViewModel.LoadCategoriesByNameStartedWithAsync(sender.Text); + sender.ItemsSource = list; + } + private void txtSuggestCategory_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { + if (args.ChosenSuggestion != null) + sender.Text = args.ChosenSuggestion.ToString(); + } + + private void txtSuggestCategory_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { + sender.Text = args.SelectedItem.ToString(); + } + + private async void btnUploadImage_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { + FileOpenPicker openPicker = new FileOpenPicker(); + openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; + openPicker.ViewMode = PickerViewMode.Thumbnail; + + openPicker.FileTypeFilter.Clear(); + openPicker.FileTypeFilter.Add(".jpg"); + openPicker.FileTypeFilter.Add(".jpeg"); + openPicker.FileTypeFilter.Add(".png"); + + StorageFile file = await openPicker.PickSingleFileAsync(); + if (file != null) { + using (Stream stream = await file.OpenStreamForReadAsync()) { + byte[] bytes = new byte[stream.Length]; + await stream.ReadAsync(bytes, 0, bytes.Length); + ProductViewModel viewModel = new ProductViewModel(); + } + } } } } diff --git a/ShoppingList.UWP/Views/Users/LoginDialog.xaml b/ShoppingList.UWP/Views/Users/LoginDialog.xaml new file mode 100644 index 0000000..7048de5 --- /dev/null +++ b/ShoppingList.UWP/Views/Users/LoginDialog.xaml @@ -0,0 +1,17 @@ +<ContentDialog + x:Class="ShoppingList.UWP.Views.Users.LoginDialog" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="using:ShoppingList.UWP.Views.Users" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" + Title="TITLE" + PrimaryButtonText="Button1" + SecondaryButtonText="Button2" + PrimaryButtonClick="ContentDialog_PrimaryButtonClick" + SecondaryButtonClick="ContentDialog_SecondaryButtonClick"> + + <Grid> + </Grid> +</ContentDialog> diff --git a/ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs b/ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs new file mode 100644 index 0000000..76a3aff --- /dev/null +++ b/ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// O modelo de item de Caixa de Diálogo de Conteúdo está documentado em https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace ShoppingList.UWP.Views.Users { + public sealed partial class LoginDialog : ContentDialog { + public LoginDialog() { + this.InitializeComponent(); + } + + private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { + } + + private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { + } + } +} diff --git a/ShoppingList.UWP/Views/Users/RegisterDialog.xaml b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml new file mode 100644 index 0000000..bae23b8 --- /dev/null +++ b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml @@ -0,0 +1,33 @@ +<ContentDialog + x:Class="ShoppingList.UWP.Views.Users.RegisterDialog" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="using:ShoppingList.UWP.Views.Users" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" + Title="Register a user" + PrimaryButtonText="Register" + SecondaryButtonText="Cancel" + PrimaryButtonClick="ContentDialog_PrimaryButtonClick" + SecondaryButtonClick="ContentDialog_SecondaryButtonClick"> + + <StackPanel> + <TextBox x:Name="txtUsername" Header="Username" + Text="{x:Bind UserViewModel.User.Username, Mode=TwoWay}" /> + <PasswordBox x:Name="txtPassword" Header="Password" + Password="{x:Bind UserViewModel.User.Password, Mode=TwoWay}" /> + <Border Background="Red" BorderBrush="Black" BorderThickness="2" + Margin="0,10" Padding="5" + Visibility="{x:Bind UserViewModel.ShowError, Mode=OneWay}"> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="48"/> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="" Foreground="White" /> + <TextBlock Grid.Column="1" Text="Register failed" Foreground="White"/> + </Grid> + </Border> + </StackPanel> +</ContentDialog> diff --git a/ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs new file mode 100644 index 0000000..e42b608 --- /dev/null +++ b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs @@ -0,0 +1,23 @@ +using ShoppingList.UWP.ViewModels; +using Windows.UI.Xaml.Controls; + +// O modelo de item de Caixa de Diálogo de Conteúdo está documentado em https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace ShoppingList.UWP.Views.Users { + public sealed partial class RegisterDialog : ContentDialog { + public UserViewModel UserViewModel { get; set; } + public RegisterDialog() { + this.InitializeComponent(); + UserViewModel = new UserViewModel(); + } + + private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { + var deferral = args.GetDeferral(); + args.Cancel = !await UserViewModel.DoRegisterAsync(); + deferral.Complete(); + } + + private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { + } + } +} -- GitLab