Add chapter 6 samples
@@ -0,0 +1,43 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33530.505
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyMediaCollection", "MyMediaCollection\MyMediaCollection.csproj", "{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Build.0 = Debug|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Build.0 = Debug|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.ActiveCfg = Release|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Build.0 = Release|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Deploy.0 = Release|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.ActiveCfg = Release|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Build.0 = Release|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Deploy.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C9277197-C949-4948-80CD-0A685EE6DCBB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<Application
|
||||
x:Class="MyMediaCollection.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,72 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Services;
|
||||
using MyMediaCollection.ViewModels;
|
||||
using MyMediaCollection.Views;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyMediaCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static IHost HostContainer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
m_window = new MainWindow();
|
||||
var rootFrame = new Frame();
|
||||
await RegisterComponentsAsync(rootFrame);
|
||||
rootFrame.NavigationFailed += RootFrame_NavigationFailed;
|
||||
rootFrame.Navigate(typeof(MainPage), args);
|
||||
m_window.Content = rootFrame;
|
||||
m_window.Activate();
|
||||
}
|
||||
|
||||
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception($"Error loading page {e.SourcePageType.FullName}");
|
||||
}
|
||||
|
||||
private Window m_window;
|
||||
|
||||
private async Task RegisterComponentsAsync(Frame rootFrame)
|
||||
{
|
||||
var navigationService = new NavigationService(rootFrame);
|
||||
navigationService.Configure(nameof(MainPage), typeof(MainPage));
|
||||
navigationService.Configure(nameof(ItemDetailsPage), typeof(ItemDetailsPage));
|
||||
var dataService = new SqliteDataService();
|
||||
await dataService.InitializeDataAsync();
|
||||
|
||||
HostContainer = Host.CreateDefaultBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<INavigationService>(navigationService);
|
||||
services.AddSingleton<IDataService>(dataService);
|
||||
services.AddTransient<MainViewModel>();
|
||||
services.AddTransient<ItemDetailsViewModel>();
|
||||
}).Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,9 @@
|
||||
namespace MyMediaCollection.Enums
|
||||
{
|
||||
public enum ItemType
|
||||
{
|
||||
Music,
|
||||
Video,
|
||||
Book
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MyMediaCollection.Enums
|
||||
{
|
||||
public enum LocationType
|
||||
{
|
||||
InCollection,
|
||||
Loaned
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Model;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyMediaCollection.Interfaces
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
Task<IList<MediaItem>> GetItemsAsync();
|
||||
Task<MediaItem> GetItemAsync(int id);
|
||||
Task<int> AddItemAsync(MediaItem item);
|
||||
Task UpdateItemAsync(MediaItem item);
|
||||
Task DeleteItemAsync(MediaItem item);
|
||||
IList<ItemType> GetItemTypes();
|
||||
Medium GetMedium(string name);
|
||||
IList<Medium> GetMediums();
|
||||
IList<Medium> GetMediums(ItemType itemType);
|
||||
IList<LocationType> GetLocationTypes();
|
||||
Task InitializeDataAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace MyMediaCollection.Interfaces
|
||||
{
|
||||
public interface INavigationService
|
||||
{
|
||||
string CurrentPage { get; }
|
||||
void NavigateTo(string page);
|
||||
void NavigateTo(string page, object parameter);
|
||||
void GoBack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MyMediaCollection.Interfaces
|
||||
{
|
||||
public interface IValidatable
|
||||
{
|
||||
void Validate(string memberName, object value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Window
|
||||
x:Class="MyMediaCollection.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:MyMediaCollection.Model"
|
||||
mc:Ignorable="d">
|
||||
|
||||
</Window>
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace MyMediaCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Dapper.Contrib.Extensions;
|
||||
using MyMediaCollection.Enums;
|
||||
|
||||
namespace MyMediaCollection.Model
|
||||
{
|
||||
public class MediaItem
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ItemType MediaType { get; set; }
|
||||
[Computed]
|
||||
public Medium MediumInfo { get; set; }
|
||||
public LocationType Location { get; set; }
|
||||
public int MediumId => MediumInfo.Id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Dapper.Contrib.Extensions;
|
||||
using MyMediaCollection.Enums;
|
||||
|
||||
namespace MyMediaCollection.Model
|
||||
{
|
||||
public class Medium
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ItemType MediaType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>MyMediaCollection</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<SupportedOSPlatformVersion>10.0.18362.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Views\ItemDetailsPage.xaml" />
|
||||
<None Remove="Views\MainPage.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
|
||||
<PackageReference Include="Dapper" Version="2.0.143" />
|
||||
<PackageReference Include="Dapper.Contrib" Version="2.0.78" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221109.1" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\ItemDetailsPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="9457558b-77d0-43f9-b1e4-e6aa9aba05d6"
|
||||
Publisher="CN=alash"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>MyMediaCollection</DisplayName>
|
||||
<PublisherDisplayName>alash</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="MyMediaCollection"
|
||||
Description="MyMediaCollection"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"MyMediaCollection (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"MyMediaCollection (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyMediaCollection.Services
|
||||
{
|
||||
public class DataService : IDataService
|
||||
{
|
||||
private IList<MediaItem> _items;
|
||||
private IList<ItemType> _itemTypes;
|
||||
private IList<Medium> _mediums;
|
||||
private IList<LocationType> _locationTypes;
|
||||
|
||||
public async Task InitializeDataAsync()
|
||||
{
|
||||
PopulateItemTypes();
|
||||
PopulateMediums();
|
||||
PopulateLocationTypes();
|
||||
PopulateItems();
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
private void PopulateItems()
|
||||
{
|
||||
var cd = new MediaItem
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Classical Favorites",
|
||||
MediaType = ItemType.Music,
|
||||
MediumInfo = _mediums.FirstOrDefault(m => m.Name == "CD"),
|
||||
Location = LocationType.InCollection
|
||||
};
|
||||
|
||||
var book = new MediaItem
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Classic Fairy Tales",
|
||||
MediaType = ItemType.Book,
|
||||
MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Hardcover"),
|
||||
Location = LocationType.InCollection
|
||||
};
|
||||
|
||||
var bluRay = new MediaItem
|
||||
{
|
||||
Id = 3,
|
||||
Name = "The Mummy",
|
||||
MediaType = ItemType.Video,
|
||||
MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Blu Ray"),
|
||||
Location = LocationType.InCollection
|
||||
};
|
||||
|
||||
_items = new List<MediaItem>
|
||||
{
|
||||
cd,
|
||||
book,
|
||||
bluRay
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateMediums()
|
||||
{
|
||||
var cd = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" };
|
||||
var vinyl = new Medium { Id = 2, MediaType = ItemType.Music, Name = "Vinyl" };
|
||||
var hardcover = new Medium { Id = 3, MediaType = ItemType.Book, Name = "Hardcover" };
|
||||
var paperback = new Medium { Id = 4, MediaType = ItemType.Book, Name = "Paperback" };
|
||||
var dvd = new Medium { Id = 5, MediaType = ItemType.Video, Name = "DVD" };
|
||||
var bluRay = new Medium { Id = 6, MediaType = ItemType.Video, Name = "Blu Ray" };
|
||||
|
||||
_mediums = new List<Medium>
|
||||
{
|
||||
cd,
|
||||
vinyl,
|
||||
hardcover,
|
||||
paperback,
|
||||
dvd,
|
||||
bluRay
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateItemTypes()
|
||||
{
|
||||
_itemTypes = new List<ItemType>
|
||||
{
|
||||
ItemType.Book,
|
||||
ItemType.Music,
|
||||
ItemType.Video
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateLocationTypes()
|
||||
{
|
||||
_locationTypes = new List<LocationType>
|
||||
{
|
||||
LocationType.InCollection,
|
||||
LocationType.Loaned
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<int> AddItemAsync(MediaItem item)
|
||||
{
|
||||
item.Id = _items.Max(i => i.Id) + 1;
|
||||
_items.Add(item);
|
||||
await Task.Delay(1);
|
||||
|
||||
return item.Id;
|
||||
}
|
||||
|
||||
public async Task<MediaItem> GetItemAsync(int id)
|
||||
{
|
||||
await Task.Delay(1);
|
||||
return _items.FirstOrDefault(i => i.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IList<MediaItem>> GetItemsAsync()
|
||||
{
|
||||
await Task.Delay(1);
|
||||
return _items;
|
||||
}
|
||||
|
||||
public IList<ItemType> GetItemTypes()
|
||||
{
|
||||
return _itemTypes;
|
||||
}
|
||||
|
||||
public IList<Medium> GetMediums()
|
||||
{
|
||||
return _mediums;
|
||||
}
|
||||
|
||||
public IList<Medium> GetMediums(ItemType itemType)
|
||||
{
|
||||
return _mediums
|
||||
.Where(m => m.MediaType == itemType)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IList<LocationType> GetLocationTypes()
|
||||
{
|
||||
return _locationTypes;
|
||||
}
|
||||
|
||||
public async Task UpdateItemAsync(MediaItem item)
|
||||
{
|
||||
var idx = -1;
|
||||
var matchedItem =
|
||||
(from x in _items
|
||||
let ind = idx++
|
||||
where x.Id == item.Id
|
||||
select ind).FirstOrDefault();
|
||||
|
||||
if (idx == -1)
|
||||
{
|
||||
throw new Exception("Unable to update item. Item not found in collection.");
|
||||
}
|
||||
|
||||
_items[idx] = item;
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
public async Task DeleteItemAsync(MediaItem item)
|
||||
{
|
||||
await Task.Delay(1);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Medium GetMedium(string name)
|
||||
{
|
||||
return _mediums.FirstOrDefault(m => m.Name == name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MyMediaCollection.Services
|
||||
{
|
||||
public class NavigationService : INavigationService
|
||||
{
|
||||
public NavigationService(Frame rootFrame)
|
||||
{
|
||||
AppFrame = rootFrame;
|
||||
}
|
||||
|
||||
private readonly IDictionary<string, Type> _pages = new ConcurrentDictionary<string, Type>();
|
||||
|
||||
public const string RootPage = "(Root)";
|
||||
|
||||
public const string UnknownPage = "(Unknown)";
|
||||
|
||||
private static Frame AppFrame;
|
||||
|
||||
public void Configure(string page, Type type)
|
||||
{
|
||||
if (_pages.Values.Any(v => v == type))
|
||||
{
|
||||
throw new ArgumentException($"The {type.Name} view has already been registered under another name.");
|
||||
}
|
||||
|
||||
_pages[page] = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the currently displayed page.
|
||||
/// </summary>
|
||||
public string CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
var frame = AppFrame;
|
||||
|
||||
if (frame.BackStackDepth == 0)
|
||||
return RootPage;
|
||||
|
||||
if (frame.Content == null)
|
||||
return UnknownPage;
|
||||
|
||||
var type = frame.Content.GetType();
|
||||
|
||||
if (_pages.Values.All(v => v != type))
|
||||
return UnknownPage;
|
||||
|
||||
var item = _pages.Single(i => i.Value == type);
|
||||
|
||||
return item.Key;
|
||||
}
|
||||
}
|
||||
|
||||
public void NavigateTo(string page)
|
||||
{
|
||||
NavigateTo(page, null);
|
||||
}
|
||||
|
||||
public void NavigateTo(string page, object parameter)
|
||||
{
|
||||
if (!_pages.ContainsKey(page))
|
||||
{
|
||||
throw new ArgumentException($"Unable to find a page registered with the name {page}.");
|
||||
}
|
||||
|
||||
AppFrame.Navigate(_pages[page], parameter);
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
if (AppFrame?.CanGoBack == true)
|
||||
{
|
||||
AppFrame.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
using Dapper;
|
||||
using Dapper.Contrib.Extensions;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace MyMediaCollection.Services
|
||||
{
|
||||
public class SqliteDataService : IDataService
|
||||
{
|
||||
private IList<ItemType> _itemTypes;
|
||||
private IList<Medium> _mediums;
|
||||
private IList<LocationType> _locationTypes;
|
||||
private const string DbName = "mediaCollectionData.db";
|
||||
|
||||
private async Task<SqliteConnection> GetOpenConnectionAsync()
|
||||
{
|
||||
await ApplicationData.Current.LocalFolder.CreateFileAsync(DbName, CreationCollisionOption.OpenIfExists).AsTask();
|
||||
|
||||
string dbPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, DbName);
|
||||
var cn = new SqliteConnection($"Filename={dbPath}");
|
||||
cn.Open();
|
||||
|
||||
return cn;
|
||||
}
|
||||
|
||||
private async Task CreateMediumTableAsync(SqliteConnection db)
|
||||
{
|
||||
string tableCommand = @"CREATE TABLE IF NOT
|
||||
EXISTS Mediums (Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Name NVARCHAR(30) NOT NULL,
|
||||
MediumType INTEGER NOT NULL)";
|
||||
|
||||
using var createTable = new SqliteCommand(tableCommand, db);
|
||||
|
||||
await createTable.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
private async Task CreateMediaItemTableAsync(SqliteConnection db)
|
||||
{
|
||||
string tableCommand = @"CREATE TABLE IF NOT
|
||||
EXISTS MediaItems (Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
Name NVARCHAR(1000) NOT NULL,
|
||||
ItemType INTEGER NOT NULL,
|
||||
MediumId INTEGER NOT NULL,
|
||||
LocationType INTEGER,
|
||||
CONSTRAINT fk_mediums
|
||||
FOREIGN KEY(MediumId)
|
||||
REFERENCES Mediums(Id))";
|
||||
|
||||
using var createTable = new SqliteCommand(tableCommand, db);
|
||||
|
||||
await createTable.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
private async Task InsertMediumAsync(SqliteConnection db, Medium medium)
|
||||
{
|
||||
//// IMPLEMENTATION WITHOUT DAPPER
|
||||
//using var insertCommand = new SqliteCommand
|
||||
//{
|
||||
// Connection = db,
|
||||
// CommandText = "INSERT INTO Mediums VALUES (NULL, @Name, @MediumType);"
|
||||
//};
|
||||
//insertCommand.Parameters.AddWithValue("@Name", medium.Name);
|
||||
//insertCommand.Parameters.AddWithValue("@MediumType", (int)medium.MediaType);
|
||||
//await insertCommand.ExecuteNonQueryAsync();
|
||||
|
||||
var newIds = await db.QueryAsync<long>(
|
||||
$@"INSERT INTO Mediums
|
||||
({nameof(medium.Name)}, MediumType)
|
||||
VALUES
|
||||
(@{nameof(medium.Name)}, @{nameof(medium.MediaType)});
|
||||
SELECT last_insert_rowid()", medium);
|
||||
|
||||
medium.Id = (int)newIds.First();
|
||||
}
|
||||
|
||||
private async Task<IList<Medium>> GetAllMediumsAsync(SqliteConnection db)
|
||||
{
|
||||
//// IMPLEMENTATION WITHOUT DAPPER
|
||||
//IList<Medium> mediums = new List<Medium>();
|
||||
//using var selectCommand = new SqliteCommand("SELECT Id, Name, MediumType FROM Mediums", db);
|
||||
//using SqliteDataReader query = await selectCommand.ExecuteReaderAsync();
|
||||
|
||||
//while (query.Read())
|
||||
//{
|
||||
// var medium = new Medium
|
||||
// {
|
||||
// Id = query.GetInt32(0),
|
||||
// Name = query.GetString(1),
|
||||
// MediaType = (ItemType)query.GetInt32(2)
|
||||
// };
|
||||
// mediums.Add(medium);
|
||||
//}
|
||||
|
||||
//return mediums;
|
||||
|
||||
var mediums =
|
||||
await db.QueryAsync<Medium>(@"SELECT Id,
|
||||
Name,
|
||||
MediumType AS MediaType
|
||||
FROM Mediums");
|
||||
|
||||
return mediums.ToList();
|
||||
}
|
||||
|
||||
private async Task<List<MediaItem>> GetAllMediaItemsAsync(SqliteConnection db)
|
||||
{
|
||||
var itemsResult = await db.QueryAsync<MediaItem, Medium, MediaItem>
|
||||
(
|
||||
@"SELECT
|
||||
[MediaItems].[Id],
|
||||
[MediaItems].[Name],
|
||||
[MediaItems].[ItemType] AS MediaType,
|
||||
[MediaItems].[LocationType] AS Location,
|
||||
[Mediums].[Id],
|
||||
[Mediums].[Name],
|
||||
[Mediums].[MediumType] AS MediaType
|
||||
FROM
|
||||
[MediaItems]
|
||||
JOIN
|
||||
[Mediums]
|
||||
ON
|
||||
[Mediums].[Id] = [MediaItems].[MediumId]",
|
||||
(item, medium) =>
|
||||
{
|
||||
item.MediumInfo = medium;
|
||||
return item;
|
||||
}
|
||||
);
|
||||
|
||||
return itemsResult.ToList();
|
||||
}
|
||||
|
||||
private async Task<int> InsertMediaItemAsync(SqliteConnection db, MediaItem item)
|
||||
{
|
||||
var newIds = await db.QueryAsync<long>(
|
||||
@"INSERT INTO MediaItems
|
||||
(Name, ItemType, MediumId, LocationType)
|
||||
VALUES
|
||||
(@Name, @MediaType, @MediumId, @Location);
|
||||
SELECT last_insert_rowid()", item);
|
||||
|
||||
return (int)newIds.First();
|
||||
}
|
||||
|
||||
private async Task UpdateMediaItemAsync(SqliteConnection db, MediaItem item)
|
||||
{
|
||||
await db.QueryAsync(
|
||||
@"UPDATE MediaItems
|
||||
SET Name = @Name,
|
||||
ItemType = @MediaType,
|
||||
MediumId = @MediumId,
|
||||
LocationType = @Location
|
||||
WHERE Id = @Id;", item);
|
||||
}
|
||||
|
||||
private async Task DeleteMediaItemAsync(SqliteConnection db, int id)
|
||||
{
|
||||
await db.DeleteAsync<MediaItem>(new MediaItem { Id = id });
|
||||
}
|
||||
|
||||
private async Task PopulateMediumsAsync(SqliteConnection db)
|
||||
{
|
||||
_mediums = await GetAllMediumsAsync(db);
|
||||
|
||||
if (_mediums.Count == 0)
|
||||
{
|
||||
var cd = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" };
|
||||
var vinyl = new Medium { Id = 2, MediaType = ItemType.Music, Name = "Vinyl" };
|
||||
var hardcover = new Medium { Id = 3, MediaType = ItemType.Book, Name = "Hardcover" };
|
||||
var paperback = new Medium { Id = 4, MediaType = ItemType.Book, Name = "Paperback" };
|
||||
var dvd = new Medium { Id = 5, MediaType = ItemType.Video, Name = "DVD" };
|
||||
var bluRay = new Medium { Id = 6, MediaType = ItemType.Video, Name = "Blu Ray" };
|
||||
|
||||
var mediums = new List<Medium>
|
||||
{
|
||||
cd,
|
||||
vinyl,
|
||||
hardcover,
|
||||
paperback,
|
||||
dvd,
|
||||
bluRay
|
||||
};
|
||||
|
||||
foreach (var medium in mediums)
|
||||
{
|
||||
await InsertMediumAsync(db, medium);
|
||||
}
|
||||
|
||||
_mediums = await GetAllMediumsAsync(db);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateItemTypes()
|
||||
{
|
||||
_itemTypes = new List<ItemType>
|
||||
{
|
||||
ItemType.Book,
|
||||
ItemType.Music,
|
||||
ItemType.Video
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateLocationTypes()
|
||||
{
|
||||
_locationTypes = new List<LocationType>
|
||||
{
|
||||
LocationType.InCollection,
|
||||
LocationType.Loaned
|
||||
};
|
||||
}
|
||||
|
||||
public async Task InitializeDataAsync()
|
||||
{
|
||||
using (var db = await GetOpenConnectionAsync())
|
||||
{
|
||||
await CreateMediumTableAsync(db);
|
||||
await CreateMediaItemTableAsync(db);
|
||||
|
||||
PopulateItemTypes();
|
||||
await PopulateMediumsAsync(db);
|
||||
PopulateLocationTypes();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> AddItemAsync(MediaItem item)
|
||||
{
|
||||
using var db = await GetOpenConnectionAsync();
|
||||
return await InsertMediaItemAsync(db, item);
|
||||
}
|
||||
|
||||
public async Task<MediaItem> GetItemAsync(int id)
|
||||
{
|
||||
IList<MediaItem> mediaItems;
|
||||
using var db = await GetOpenConnectionAsync();
|
||||
mediaItems = await GetAllMediaItemsAsync(db);
|
||||
|
||||
//Filter the list to get the item for our Id
|
||||
return mediaItems.FirstOrDefault(i => i.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IList<MediaItem>> GetItemsAsync()
|
||||
{
|
||||
using var db = await GetOpenConnectionAsync();
|
||||
return await GetAllMediaItemsAsync(db);
|
||||
}
|
||||
|
||||
public IList<ItemType> GetItemTypes()
|
||||
{
|
||||
return _itemTypes;
|
||||
}
|
||||
|
||||
public IList<LocationType> GetLocationTypes()
|
||||
{
|
||||
return _locationTypes;
|
||||
}
|
||||
|
||||
public Medium GetMedium(string name)
|
||||
{
|
||||
return _mediums.FirstOrDefault(m => m.Name == name);
|
||||
}
|
||||
|
||||
public Medium GetMedium(int id)
|
||||
{
|
||||
return _mediums.FirstOrDefault(m => m.Id == id);
|
||||
}
|
||||
|
||||
public IList<Medium> GetMediums()
|
||||
{
|
||||
return _mediums;
|
||||
}
|
||||
|
||||
public IList<Medium> GetMediums(ItemType itemType)
|
||||
{
|
||||
return _mediums
|
||||
.Where(m => m.MediaType == itemType)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task UpdateItemAsync(MediaItem item)
|
||||
{
|
||||
using var db = await GetOpenConnectionAsync();
|
||||
await UpdateMediaItemAsync(db, item);
|
||||
}
|
||||
|
||||
public async Task DeleteItemAsync(MediaItem item)
|
||||
{
|
||||
using var db = await GetOpenConnectionAsync();
|
||||
await DeleteMediaItemAsync(db, item.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyMediaCollection.ViewModels
|
||||
{
|
||||
public partial class ItemDetailsViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> locationTypes = new();
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> mediums = new();
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> itemTypes = new();
|
||||
private int _itemId;
|
||||
[ObservableProperty]
|
||||
private string itemName;
|
||||
[ObservableProperty]
|
||||
private string selectedMedium;
|
||||
[ObservableProperty]
|
||||
private string selectedItemType;
|
||||
[ObservableProperty]
|
||||
private string selectedLocation;
|
||||
[ObservableProperty]
|
||||
private bool isDirty;
|
||||
private int _selectedItemId = -1;
|
||||
protected INavigationService _navigationService;
|
||||
protected IDataService _dataService;
|
||||
|
||||
public ItemDetailsViewModel(INavigationService navigationService, IDataService dataService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
_dataService = dataService;
|
||||
|
||||
PopulateLists();
|
||||
}
|
||||
|
||||
public void InitializeItemDetailData(int itemId)
|
||||
{
|
||||
_selectedItemId = itemId;
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
private void PopulateLists()
|
||||
{
|
||||
ItemTypes.Clear();
|
||||
foreach (string iType in Enum.GetNames(typeof(ItemType)))
|
||||
ItemTypes.Add(iType);
|
||||
|
||||
LocationTypes.Clear();
|
||||
foreach (string lType in Enum.GetNames(typeof(LocationType)))
|
||||
LocationTypes.Add(lType);
|
||||
|
||||
Mediums = new ObservableCollection<string>();
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
MediaItem item;
|
||||
|
||||
if (_itemId > 0)
|
||||
{
|
||||
item = await _dataService.GetItemAsync(_itemId);
|
||||
|
||||
item.Name = ItemName;
|
||||
item.Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation);
|
||||
item.MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType);
|
||||
item.MediumInfo = _dataService.GetMedium(SelectedMedium);
|
||||
|
||||
await _dataService.UpdateItemAsync(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = new MediaItem
|
||||
{
|
||||
Name = ItemName,
|
||||
Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation),
|
||||
MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType),
|
||||
MediumInfo = _dataService.GetMedium(SelectedMedium)
|
||||
};
|
||||
|
||||
await _dataService.AddItemAsync(item);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveItemAndContinueAsync()
|
||||
{
|
||||
await SaveAsync();
|
||||
_itemId = 0;
|
||||
ItemName = string.Empty;
|
||||
SelectedMedium = null;
|
||||
SelectedLocation = null;
|
||||
SelectedItemType = null;
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
public async Task SaveItemAndReturnAsync()
|
||||
{
|
||||
await SaveAsync();
|
||||
_navigationService.GoBack();
|
||||
}
|
||||
|
||||
partial void OnItemNameChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
partial void OnSelectedMediumChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
partial void OnSelectedItemTypeChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
Mediums.Clear();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
foreach (string med in _dataService.GetMediums((ItemType)Enum.Parse(typeof(ItemType), SelectedItemType)).Select(m => m.Name))
|
||||
Mediums.Add(med);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedLocationChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel()
|
||||
{
|
||||
_navigationService.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyMediaCollection.ViewModels
|
||||
{
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string selectedMedium;
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MediaItem> items = new ObservableCollection<MediaItem>();
|
||||
private ObservableCollection<MediaItem> allItems;
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> mediums;
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
|
||||
private MediaItem selectedMediaItem;
|
||||
private INavigationService _navigationService;
|
||||
private IDataService _dataService;
|
||||
private const string AllMediums = "All";
|
||||
|
||||
public MainViewModel(INavigationService navigationService, IDataService dataService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
_dataService = dataService;
|
||||
|
||||
PopulateDataAsync();
|
||||
}
|
||||
|
||||
public async Task PopulateDataAsync()
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
foreach (var item in await _dataService.GetItemsAsync())
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
|
||||
allItems = new ObservableCollection<MediaItem>(Items);
|
||||
|
||||
Mediums = new ObservableCollection<string>
|
||||
{
|
||||
AllMediums
|
||||
};
|
||||
|
||||
foreach (var itemType in _dataService.GetItemTypes())
|
||||
{
|
||||
Mediums.Add(itemType.ToString());
|
||||
}
|
||||
|
||||
SelectedMedium = Mediums[0];
|
||||
}
|
||||
|
||||
partial void OnSelectedMediumChanged(string value)
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)
|
||||
|| value == "All"
|
||||
|| value == item.MediaType.ToString())
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddEdit()
|
||||
{
|
||||
var selectedItemId = -1;
|
||||
|
||||
if (SelectedMediaItem != null)
|
||||
{
|
||||
selectedItemId = SelectedMediaItem.Id;
|
||||
}
|
||||
|
||||
_navigationService.NavigateTo("ItemDetailsPage", selectedItemId);
|
||||
}
|
||||
|
||||
public void ListViewDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
AddEdit();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanDeleteItem))]
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
await _dataService.DeleteItemAsync(SelectedMediaItem);
|
||||
allItems.Remove(SelectedMediaItem);
|
||||
Items.Remove(SelectedMediaItem);
|
||||
}
|
||||
|
||||
private bool CanDeleteItem() => SelectedMediaItem != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="MyMediaCollection.Views.ItemDetailsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
<Style x:Key="AttributeTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style x:Key="AttributeValueStyle" TargetType="TextBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
</Style>
|
||||
<Style x:Key="AttributeComboxValueStyle" TargetType="ComboBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Item Details" FontSize="18" Margin="8"/>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Name:" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<TextBox Grid.Column="1" Style="{StaticResource AttributeValueStyle}" Text="{x:Bind ViewModel.ItemName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<TextBlock Text="Media Type:" Grid.Row="1" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.ItemTypes}" SelectedValue="{x:Bind ViewModel.SelectedItemType, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Text="Medium:" Grid.Row="2" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.Mediums}" SelectedValue="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Text="Location:" Grid.Row="3" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<ComboBox Grid.Row="3" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.LocationTypes}" SelectedValue="{x:Bind ViewModel.SelectedLocation, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
|
||||
<SplitButton x:Name="SaveButton"
|
||||
Content="Save and Return"
|
||||
Margin="8,8,0,8"
|
||||
Click="{x:Bind ViewModel.SaveItemAndReturnAsync}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}">
|
||||
<SplitButton.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<Button Content="Save and Create New"
|
||||
Click="{x:Bind ViewModel.SaveItemAndContinueAsync}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}"
|
||||
Background="Transparent"/>
|
||||
<Button Content="Save and Return"
|
||||
Click="{x:Bind ViewModel.SaveItemAndReturnAsync}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}"
|
||||
Background="Transparent"/>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</SplitButton.Flyout>
|
||||
<SplitButton.Resources>
|
||||
<TeachingTip x:Name="SavingTip"
|
||||
Target="{x:Bind SaveButton}"
|
||||
Title="Save and create new"
|
||||
Subtitle="Use the dropdown button option to save your item and create another.">
|
||||
</TeachingTip>
|
||||
</SplitButton.Resources>
|
||||
</SplitButton>
|
||||
<Button Content="Cancel" Margin="8" Command="{x:Bind ViewModel.CancelCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MyMediaCollection.ViewModels;
|
||||
|
||||
namespace MyMediaCollection.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class ItemDetailsPage : Page
|
||||
{
|
||||
public ItemDetailsPage()
|
||||
{
|
||||
ViewModel = App.HostContainer.Services.GetService<ItemDetailsViewModel>();
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
|
||||
|
||||
// Load the user setting
|
||||
string haveExplainedSaveSetting = localSettings.Values[nameof(SavingTip)] as string;
|
||||
|
||||
// If the user has not seen the save tip, display it
|
||||
|
||||
if (!bool.TryParse(haveExplainedSaveSetting, out bool result) || !result)
|
||||
{
|
||||
SavingTip.IsOpen = true;
|
||||
|
||||
// Save the teaching tip setting
|
||||
localSettings.Values[nameof(SavingTip)] = "true";
|
||||
}
|
||||
}
|
||||
|
||||
public ItemDetailsViewModel ViewModel;
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
var itemId = (int)e.Parameter;
|
||||
|
||||
if (itemId > 0)
|
||||
{
|
||||
ViewModel.InitializeItemDetailData(itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="MyMediaCollection.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:MyMediaCollection.Model"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Media Collection" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Media Type:" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<ComboBox ItemsSource="{x:Bind ViewModel.Mediums, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}" MinWidth="120" Margin="0,2,6,4"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<ListView Grid.Row="1" ItemsSource="{x:Bind ViewModel.Items}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedMediaItem, Mode=TwoWay}"
|
||||
DoubleTapped="{x:Bind ViewModel.ListViewDoubleTapped}">
|
||||
<ListView.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Medium" Margin="5,0,0,0" FontWeight="Bold"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Title" Margin="5,0,0,0" FontWeight="Bold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.HeaderTemplate>
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:MediaItem">
|
||||
<Grid IsHitTestVisible="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{x:Bind Path=MediumInfo.Name}"/>
|
||||
<TextBlock Grid.Column="1" Text="{x:Bind Path=Name}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button Command="{x:Bind ViewModel.AddEditCommand}"
|
||||
Content="Add/Edit Item"
|
||||
Margin="8,8,0,8"/>
|
||||
<Button Command="{x:Bind ViewModel.DeleteCommand}"
|
||||
Content="Delete Item"
|
||||
Margin="8"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using MyMediaCollection.ViewModels;
|
||||
|
||||
namespace MyMediaCollection.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
ViewModel = App.HostContainer.Services.GetService<MainViewModel>();
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
public MainViewModel ViewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyMediaCollection.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
|
||||
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
|
||||
It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33530.505
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyMediaCollection", "MyMediaCollection\MyMediaCollection.csproj", "{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Build.0 = Debug|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Build.0 = Debug|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.ActiveCfg = Release|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Build.0 = Release|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x64.Deploy.0 = Release|x64
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.ActiveCfg = Release|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Build.0 = Release|x86
|
||||
{972D5C0D-86E6-4A2F-A6FD-8D4FE3380707}.Release|x86.Deploy.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C9277197-C949-4948-80CD-0A685EE6DCBB}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<Application
|
||||
x:Class="MyMediaCollection.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,70 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Services;
|
||||
using MyMediaCollection.ViewModels;
|
||||
using MyMediaCollection.Views;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyMediaCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static IHost HostContainer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
m_window = new MainWindow();
|
||||
var rootFrame = new Frame();
|
||||
RegisterComponents(rootFrame);
|
||||
rootFrame.NavigationFailed += RootFrame_NavigationFailed;
|
||||
rootFrame.Navigate(typeof(MainPage), args);
|
||||
m_window.Content = rootFrame;
|
||||
m_window.Activate();
|
||||
}
|
||||
|
||||
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception($"Error loading page {e.SourcePageType.FullName}");
|
||||
}
|
||||
|
||||
private Window m_window;
|
||||
|
||||
private void RegisterComponents(Frame rootFrame)
|
||||
{
|
||||
var navigationService = new NavigationService(rootFrame);
|
||||
navigationService.Configure(nameof(MainPage), typeof(MainPage));
|
||||
navigationService.Configure(nameof(ItemDetailsPage), typeof(ItemDetailsPage));
|
||||
|
||||
HostContainer = Host.CreateDefaultBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<INavigationService>(navigationService);
|
||||
services.AddSingleton<IDataService, DataService>();
|
||||
services.AddTransient<MainViewModel>();
|
||||
services.AddTransient<ItemDetailsViewModel>();
|
||||
}).Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,9 @@
|
||||
namespace MyMediaCollection.Enums
|
||||
{
|
||||
public enum ItemType
|
||||
{
|
||||
Music,
|
||||
Video,
|
||||
Book
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MyMediaCollection.Enums
|
||||
{
|
||||
public enum LocationType
|
||||
{
|
||||
InCollection,
|
||||
Loaned
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Model;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MyMediaCollection.Interfaces
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
IList<MediaItem> GetItems();
|
||||
MediaItem GetItem(int id);
|
||||
int AddItem(MediaItem item);
|
||||
void UpdateItem(MediaItem item);
|
||||
IList<ItemType> GetItemTypes();
|
||||
Medium GetMedium(string name);
|
||||
IList<Medium> GetMediums();
|
||||
IList<Medium> GetMediums(ItemType itemType);
|
||||
IList<LocationType> GetLocationTypes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace MyMediaCollection.Interfaces
|
||||
{
|
||||
public interface INavigationService
|
||||
{
|
||||
string CurrentPage { get; }
|
||||
void NavigateTo(string page);
|
||||
void NavigateTo(string page, object parameter);
|
||||
void GoBack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Window
|
||||
x:Class="MyMediaCollection.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:MyMediaCollection.Model"
|
||||
mc:Ignorable="d">
|
||||
|
||||
</Window>
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace MyMediaCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using MyMediaCollection.Enums;
|
||||
|
||||
namespace MyMediaCollection.Model
|
||||
{
|
||||
public class MediaItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ItemType MediaType { get; set; }
|
||||
public Medium MediumInfo { get; set; }
|
||||
public LocationType Location { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MyMediaCollection.Enums;
|
||||
|
||||
namespace MyMediaCollection.Model
|
||||
{
|
||||
public class Medium
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public ItemType MediaType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>MyMediaCollection</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<SupportedOSPlatformVersion>10.0.18362.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Views\ItemDetailsPage.xaml" />
|
||||
<None Remove="Views\MainPage.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221109.1" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\ItemDetailsPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="9457558b-77d0-43f9-b1e4-e6aa9aba05d6"
|
||||
Publisher="CN=alash"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>MyMediaCollection</DisplayName>
|
||||
<PublisherDisplayName>alash</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="MyMediaCollection"
|
||||
Description="MyMediaCollection"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"MyMediaCollection (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"MyMediaCollection (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MyMediaCollection.Services
|
||||
{
|
||||
public class DataService : IDataService
|
||||
{
|
||||
private IList<MediaItem> _items;
|
||||
private IList<ItemType> _itemTypes;
|
||||
private IList<Medium> _mediums;
|
||||
private IList<LocationType> _locationTypes;
|
||||
|
||||
public DataService()
|
||||
{
|
||||
PopulateItemTypes();
|
||||
PopulateMediums();
|
||||
PopulateLocationTypes();
|
||||
PopulateItems();
|
||||
}
|
||||
|
||||
private void PopulateItems()
|
||||
{
|
||||
var cd = new MediaItem
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Classical Favorites",
|
||||
MediaType = ItemType.Music,
|
||||
MediumInfo = _mediums.FirstOrDefault(m => m.Name == "CD"),
|
||||
Location = LocationType.InCollection
|
||||
};
|
||||
|
||||
var book = new MediaItem
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Classic Fairy Tales",
|
||||
MediaType = ItemType.Book,
|
||||
MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Hardcover"),
|
||||
Location = LocationType.InCollection
|
||||
};
|
||||
|
||||
var bluRay = new MediaItem
|
||||
{
|
||||
Id = 3,
|
||||
Name = "The Mummy",
|
||||
MediaType = ItemType.Video,
|
||||
MediumInfo = _mediums.FirstOrDefault(m => m.Name == "Blu Ray"),
|
||||
Location = LocationType.InCollection
|
||||
};
|
||||
|
||||
_items = new List<MediaItem>
|
||||
{
|
||||
cd,
|
||||
book,
|
||||
bluRay
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateMediums()
|
||||
{
|
||||
var cd = new Medium { Id = 1, MediaType = ItemType.Music, Name = "CD" };
|
||||
var vinyl = new Medium { Id = 2, MediaType = ItemType.Music, Name = "Vinyl" };
|
||||
var hardcover = new Medium { Id = 3, MediaType = ItemType.Book, Name = "Hardcover" };
|
||||
var paperback = new Medium { Id = 4, MediaType = ItemType.Book, Name = "Paperback" };
|
||||
var dvd = new Medium { Id = 5, MediaType = ItemType.Video, Name = "DVD" };
|
||||
var bluRay = new Medium { Id = 6, MediaType = ItemType.Video, Name = "Blu Ray" };
|
||||
|
||||
_mediums = new List<Medium>
|
||||
{
|
||||
cd,
|
||||
vinyl,
|
||||
hardcover,
|
||||
paperback,
|
||||
dvd,
|
||||
bluRay
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateItemTypes()
|
||||
{
|
||||
_itemTypes = new List<ItemType>
|
||||
{
|
||||
ItemType.Book,
|
||||
ItemType.Music,
|
||||
ItemType.Video
|
||||
};
|
||||
}
|
||||
|
||||
private void PopulateLocationTypes()
|
||||
{
|
||||
_locationTypes = new List<LocationType>
|
||||
{
|
||||
LocationType.InCollection,
|
||||
LocationType.Loaned
|
||||
};
|
||||
}
|
||||
|
||||
public int AddItem(MediaItem item)
|
||||
{
|
||||
item.Id = _items.Max(i => i.Id) + 1;
|
||||
_items.Add(item);
|
||||
|
||||
return item.Id;
|
||||
}
|
||||
|
||||
public MediaItem GetItem(int id)
|
||||
{
|
||||
return _items.FirstOrDefault(i => i.Id == id);
|
||||
}
|
||||
|
||||
public IList<MediaItem> GetItems()
|
||||
{
|
||||
return _items;
|
||||
}
|
||||
|
||||
public IList<ItemType> GetItemTypes()
|
||||
{
|
||||
return _itemTypes;
|
||||
}
|
||||
|
||||
public IList<Medium> GetMediums()
|
||||
{
|
||||
return _mediums;
|
||||
}
|
||||
|
||||
public IList<Medium> GetMediums(ItemType itemType)
|
||||
{
|
||||
return _mediums
|
||||
.Where(m => m.MediaType == itemType)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IList<LocationType> GetLocationTypes()
|
||||
{
|
||||
return _locationTypes;
|
||||
}
|
||||
|
||||
public void UpdateItem(MediaItem item)
|
||||
{
|
||||
var idx = -1;
|
||||
var matchedItem =
|
||||
(from x in _items
|
||||
let ind = idx++
|
||||
where x.Id == item.Id
|
||||
select ind).FirstOrDefault();
|
||||
|
||||
if (idx == -1)
|
||||
{
|
||||
throw new Exception("Unable to update item. Item not found in collection.");
|
||||
}
|
||||
|
||||
_items[idx] = item;
|
||||
}
|
||||
|
||||
public Medium GetMedium(string name)
|
||||
{
|
||||
return _mediums.FirstOrDefault(m => m.Name == name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MyMediaCollection.Services
|
||||
{
|
||||
public class NavigationService : INavigationService
|
||||
{
|
||||
public NavigationService(Frame rootFrame)
|
||||
{
|
||||
AppFrame = rootFrame;
|
||||
}
|
||||
|
||||
private readonly IDictionary<string, Type> _pages = new ConcurrentDictionary<string, Type>();
|
||||
|
||||
public const string RootPage = "(Root)";
|
||||
|
||||
public const string UnknownPage = "(Unknown)";
|
||||
|
||||
private static Frame AppFrame;
|
||||
|
||||
public void Configure(string page, Type type)
|
||||
{
|
||||
if (_pages.Values.Any(v => v == type))
|
||||
{
|
||||
throw new ArgumentException($"The {type.Name} view has already been registered under another name.");
|
||||
}
|
||||
|
||||
_pages[page] = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the currently displayed page.
|
||||
/// </summary>
|
||||
public string CurrentPage
|
||||
{
|
||||
get
|
||||
{
|
||||
var frame = AppFrame;
|
||||
|
||||
if (frame.BackStackDepth == 0)
|
||||
return RootPage;
|
||||
|
||||
if (frame.Content == null)
|
||||
return UnknownPage;
|
||||
|
||||
var type = frame.Content.GetType();
|
||||
|
||||
if (_pages.Values.All(v => v != type))
|
||||
return UnknownPage;
|
||||
|
||||
var item = _pages.Single(i => i.Value == type);
|
||||
|
||||
return item.Key;
|
||||
}
|
||||
}
|
||||
|
||||
public void NavigateTo(string page)
|
||||
{
|
||||
NavigateTo(page, null);
|
||||
}
|
||||
|
||||
public void NavigateTo(string page, object parameter)
|
||||
{
|
||||
if (!_pages.ContainsKey(page))
|
||||
{
|
||||
throw new ArgumentException($"Unable to find a page registered with the name {page}.");
|
||||
}
|
||||
|
||||
AppFrame.Navigate(_pages[page], parameter);
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
if (AppFrame?.CanGoBack == true)
|
||||
{
|
||||
AppFrame.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MyMediaCollection.Enums;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace MyMediaCollection.ViewModels
|
||||
{
|
||||
public partial class ItemDetailsViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> locationTypes = new();
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> mediums = new();
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> itemTypes = new();
|
||||
private int _itemId;
|
||||
[ObservableProperty]
|
||||
private string itemName;
|
||||
[ObservableProperty]
|
||||
private string selectedMedium;
|
||||
[ObservableProperty]
|
||||
private string selectedItemType;
|
||||
[ObservableProperty]
|
||||
private string selectedLocation;
|
||||
[ObservableProperty]
|
||||
private bool isDirty;
|
||||
private int _selectedItemId = -1;
|
||||
protected INavigationService _navigationService;
|
||||
protected IDataService _dataService;
|
||||
|
||||
public ItemDetailsViewModel(INavigationService navigationService, IDataService dataService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
_dataService = dataService;
|
||||
|
||||
PopulateLists();
|
||||
}
|
||||
|
||||
public void InitializeItemDetailData(int itemId)
|
||||
{
|
||||
_selectedItemId = itemId;
|
||||
|
||||
PopulateExistingItem(_dataService);
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
private void PopulateExistingItem(IDataService dataService)
|
||||
{
|
||||
if (_selectedItemId > 0)
|
||||
{
|
||||
var item = _dataService.GetItem(_selectedItemId);
|
||||
Mediums.Clear();
|
||||
|
||||
foreach (string medium in dataService.GetMediums(item.MediaType).Select(m => m.Name))
|
||||
Mediums.Add(medium);
|
||||
|
||||
_itemId = item.Id;
|
||||
ItemName = item.Name;
|
||||
SelectedMedium = item.MediumInfo.Name;
|
||||
SelectedLocation = item.Location.ToString();
|
||||
SelectedItemType = item.MediaType.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateLists()
|
||||
{
|
||||
ItemTypes.Clear();
|
||||
foreach (string iType in Enum.GetNames(typeof(ItemType)))
|
||||
ItemTypes.Add(iType);
|
||||
|
||||
LocationTypes.Clear();
|
||||
foreach (string lType in Enum.GetNames(typeof(LocationType)))
|
||||
LocationTypes.Add(lType);
|
||||
|
||||
Mediums = new ObservableCollection<string>();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
MediaItem item;
|
||||
|
||||
if (_itemId > 0)
|
||||
{
|
||||
item = _dataService.GetItem(_itemId);
|
||||
|
||||
item.Name = ItemName;
|
||||
item.Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation);
|
||||
item.MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType);
|
||||
item.MediumInfo = _dataService.GetMedium(SelectedMedium);
|
||||
|
||||
_dataService.UpdateItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
item = new MediaItem
|
||||
{
|
||||
Name = ItemName,
|
||||
Location = (LocationType)Enum.Parse(typeof(LocationType), SelectedLocation),
|
||||
MediaType = (ItemType)Enum.Parse(typeof(ItemType), SelectedItemType),
|
||||
MediumInfo = _dataService.GetMedium(SelectedMedium)
|
||||
};
|
||||
|
||||
_dataService.AddItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveItemAndContinue()
|
||||
{
|
||||
Save();
|
||||
_itemId = 0;
|
||||
ItemName = string.Empty;
|
||||
SelectedMedium = null;
|
||||
SelectedLocation = null;
|
||||
SelectedItemType = null;
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
public void SaveItemAndReturn()
|
||||
{
|
||||
Save();
|
||||
_navigationService.GoBack();
|
||||
}
|
||||
|
||||
partial void OnItemNameChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
partial void OnSelectedMediumChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
partial void OnSelectedItemTypeChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
Mediums.Clear();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
foreach (string med in _dataService.GetMediums((ItemType)Enum.Parse(typeof(ItemType), SelectedItemType)).Select(m => m.Name))
|
||||
Mediums.Add(med);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedLocationChanged(string value)
|
||||
{
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel()
|
||||
{
|
||||
_navigationService.GoBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using MyMediaCollection.Interfaces;
|
||||
using MyMediaCollection.Model;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace MyMediaCollection.ViewModels
|
||||
{
|
||||
public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string selectedMedium;
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MediaItem> items = new ObservableCollection<MediaItem>();
|
||||
private ObservableCollection<MediaItem> allItems;
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> mediums;
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
|
||||
private MediaItem selectedMediaItem;
|
||||
private INavigationService _navigationService;
|
||||
private IDataService _dataService;
|
||||
private const string AllMediums = "All";
|
||||
|
||||
public MainViewModel(INavigationService navigationService, IDataService dataService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
_dataService = dataService;
|
||||
|
||||
PopulateData();
|
||||
}
|
||||
|
||||
public void PopulateData()
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
foreach (var item in _dataService.GetItems())
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
|
||||
allItems = new ObservableCollection<MediaItem>(Items);
|
||||
|
||||
Mediums = new ObservableCollection<string>
|
||||
{
|
||||
AllMediums
|
||||
};
|
||||
|
||||
foreach (var itemType in _dataService.GetItemTypes())
|
||||
{
|
||||
Mediums.Add(itemType.ToString());
|
||||
}
|
||||
|
||||
SelectedMedium = Mediums[0];
|
||||
}
|
||||
|
||||
partial void OnSelectedMediumChanged(string value)
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)
|
||||
|| value == "All"
|
||||
|| value == item.MediaType.ToString())
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddEdit()
|
||||
{
|
||||
var selectedItemId = -1;
|
||||
|
||||
if (SelectedMediaItem != null)
|
||||
{
|
||||
selectedItemId = SelectedMediaItem.Id;
|
||||
}
|
||||
|
||||
_navigationService.NavigateTo("ItemDetailsPage", selectedItemId);
|
||||
}
|
||||
|
||||
public void ListViewDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
AddEdit();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanDeleteItem))]
|
||||
private void Delete()
|
||||
{
|
||||
allItems.Remove(SelectedMediaItem);
|
||||
Items.Remove(SelectedMediaItem);
|
||||
}
|
||||
|
||||
private bool CanDeleteItem() => SelectedMediaItem != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="MyMediaCollection.Views.ItemDetailsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
<Style x:Key="AttributeTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style x:Key="AttributeValueStyle" TargetType="TextBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
</Style>
|
||||
<Style x:Key="AttributeComboxValueStyle" TargetType="ComboBox">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Item Details" FontSize="18" Margin="8"/>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Name:" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<TextBox Grid.Column="1" Style="{StaticResource AttributeValueStyle}" Text="{x:Bind ViewModel.ItemName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<TextBlock Text="Media Type:" Grid.Row="1" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.ItemTypes}" SelectedValue="{x:Bind ViewModel.SelectedItemType, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Text="Medium:" Grid.Row="2" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.Mediums}" SelectedValue="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Text="Location:" Grid.Row="3" Style="{StaticResource AttributeTitleStyle}"/>
|
||||
<ComboBox Grid.Row="3" Grid.Column="1" Style="{StaticResource AttributeComboxValueStyle}" ItemsSource="{x:Bind ViewModel.LocationTypes}" SelectedValue="{x:Bind ViewModel.SelectedLocation, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Right">
|
||||
<SplitButton x:Name="SaveButton"
|
||||
Content="Save and Return"
|
||||
Margin="8,8,0,8"
|
||||
Click="{x:Bind ViewModel.SaveItemAndReturn}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}">
|
||||
<SplitButton.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<Button Content="Save and Create New"
|
||||
Click="{x:Bind ViewModel.SaveItemAndContinue}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}"
|
||||
Background="Transparent"/>
|
||||
<Button Content="Save and Return"
|
||||
Click="{x:Bind ViewModel.SaveItemAndReturn}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDirty, Mode=OneWay}"
|
||||
Background="Transparent"/>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</SplitButton.Flyout>
|
||||
<SplitButton.Resources>
|
||||
<TeachingTip x:Name="SavingTip"
|
||||
Target="{x:Bind SaveButton}"
|
||||
Title="Save and create new"
|
||||
Subtitle="Use the dropdown button option to save your item and create another.">
|
||||
</TeachingTip>
|
||||
</SplitButton.Resources>
|
||||
</SplitButton>
|
||||
<Button Content="Cancel" Margin="8" Command="{x:Bind ViewModel.CancelCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using MyMediaCollection.ViewModels;
|
||||
|
||||
namespace MyMediaCollection.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class ItemDetailsPage : Page
|
||||
{
|
||||
public ItemDetailsPage()
|
||||
{
|
||||
ViewModel = App.HostContainer.Services.GetService<ItemDetailsViewModel>();
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
|
||||
|
||||
// Load the user setting
|
||||
string haveExplainedSaveSetting = localSettings.Values[nameof(SavingTip)] as string;
|
||||
|
||||
// If the user has not seen the save tip, display it
|
||||
|
||||
if (!bool.TryParse(haveExplainedSaveSetting, out bool result) || !result)
|
||||
{
|
||||
SavingTip.IsOpen = true;
|
||||
|
||||
// Save the teaching tip setting
|
||||
localSettings.Values[nameof(SavingTip)] = "true";
|
||||
}
|
||||
}
|
||||
|
||||
public ItemDetailsViewModel ViewModel;
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
|
||||
var itemId = (int)e.Parameter;
|
||||
|
||||
if (itemId > 0)
|
||||
{
|
||||
ViewModel.InitializeItemDetailData(itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Page
|
||||
x:Class="MyMediaCollection.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyMediaCollection.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:MyMediaCollection.Model"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="Media Collection" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Media Type:" Margin="4" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
<ComboBox ItemsSource="{x:Bind ViewModel.Mediums}" SelectedItem="{x:Bind ViewModel.SelectedMedium, Mode=TwoWay}" MinWidth="120" Margin="0,2,6,4"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<ListView Grid.Row="1" ItemsSource="{x:Bind ViewModel.Items}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedMediaItem, Mode=TwoWay}"
|
||||
DoubleTapped="{x:Bind ViewModel.ListViewDoubleTapped}">
|
||||
<ListView.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Medium" Margin="5,0,0,0" FontWeight="Bold"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Title" Margin="5,0,0,0" FontWeight="Bold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.HeaderTemplate>
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:MediaItem">
|
||||
<Grid IsHitTestVisible="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{x:Bind Path=MediumInfo.Name}"/>
|
||||
<TextBlock Grid.Column="1" Text="{x:Bind Path=Name}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button Command="{x:Bind ViewModel.AddEditCommand}"
|
||||
Content="Add/Edit Item"
|
||||
Margin="8,8,0,8"/>
|
||||
<Button Command="{x:Bind ViewModel.DeleteCommand}"
|
||||
Content="Delete Item"
|
||||
Margin="8"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using MyMediaCollection.ViewModels;
|
||||
|
||||
namespace MyMediaCollection.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
ViewModel = App.HostContainer.Services.GetService<MainViewModel>();
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
public MainViewModel ViewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyMediaCollection.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
|
||||
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
|
||||
It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||