Major changes in MainPage: now is layout dependant (with a base, to prepare for Settings)

Main sub pages also changed to better work with Main (but needs improvement)
Added a Settings Page stub
This commit is contained in:
2017-10-26 18:57:39 +02:00
committed by BONNEVILLE Geoffroy
parent 1b439a4960
commit d10f617910
26 changed files with 833 additions and 67 deletions

View File

@@ -17,7 +17,7 @@ namespace ModernKeePass
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
sealed partial class App
{
public DatabaseHelper Database { get; set; } = new DatabaseHelper();
public Dictionary<string, IPwEntity> PendingDeleteEntities = new Dictionary<string, IPwEntity>();
@@ -87,7 +87,7 @@ namespace ModernKeePass
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), lauchActivatedEventArgs.Arguments);
rootFrame.Navigate(typeof(Pages.MainPage), lauchActivatedEventArgs.Arguments);
}
else
{
@@ -140,7 +140,7 @@ namespace ModernKeePass
base.OnFileActivated(args);
var rootFrame = new Frame();
Database.DatabaseFile = args.Files[0] as StorageFile;
rootFrame.Navigate(typeof(MainPage), args);
rootFrame.Navigate(typeof(Pages.MainPage), args);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.Interfaces
{
public interface IHasSelectableObject
{
ISelectableModel SelectedItem { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.Interfaces
{
public interface ISelectableModel
{
bool IsSelected { get; set; }
}
}

View File

@@ -119,7 +119,10 @@
<Compile Include="Common\RelayCommand.cs" />
<Compile Include="Common\SuspensionManager.cs" />
<Compile Include="Common\ToastNotificationHelper.cs" />
<Compile Include="Controls\FirstItemDataTemplateSelector.cs" />
<Compile Include="Interfaces\IHasSelectableObject.cs" />
<Compile Include="Interfaces\ISelectableModel.cs" />
<Compile Include="Pages\BasePages\LayoutAwarePageBase.cs" />
<Compile Include="TemplateSelectors\FirstItemDataTemplateSelector.cs" />
<Compile Include="Controls\ListViewWithDisable.cs" />
<Compile Include="Controls\OpenDatabaseUserControl.xaml.cs">
<DependentUpon>OpenDatabaseUserControl.xaml</DependentUpon>
@@ -140,13 +143,16 @@
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Mappings\PwIconToSegoeMapping.cs" />
<Compile Include="Pages\AboutPage.xaml.cs">
<Compile Include="Pages\MainPageFrames\AboutPage.xaml.cs">
<DependentUpon>AboutPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\NewDatabasePage.xaml.cs">
<Compile Include="Pages\MainPageFrames\NewDatabasePage.xaml.cs">
<DependentUpon>NewDatabasePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\WelcomePage.xaml.cs">
<Compile Include="Pages\SettingsPage.xaml.cs">
<DependentUpon>SettingsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\MainPageFrames\WelcomePage.xaml.cs">
<DependentUpon>WelcomePage.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\AboutVm.cs" />
@@ -158,13 +164,13 @@
<Compile Include="Pages\GroupDetailPage.xaml.cs">
<DependentUpon>GroupDetailPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\OpenDatabasePage.xaml.cs">
<Compile Include="Pages\MainPageFrames\OpenDatabasePage.xaml.cs">
<DependentUpon>OpenDatabasePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\RecentDatabasesPage.xaml.cs">
<Compile Include="Pages\MainPageFrames\RecentDatabasesPage.xaml.cs">
<DependentUpon>RecentDatabasesPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SaveDatabasePage.xaml.cs">
<Compile Include="Pages\MainPageFrames\SaveDatabasePage.xaml.cs">
<DependentUpon>SaveDatabasePage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -202,7 +208,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Pages\AboutPage.xaml">
<Page Include="Pages\MainPageFrames\AboutPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -214,23 +220,27 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\NewDatabasePage.xaml">
<Page Include="Pages\MainPageFrames\NewDatabasePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\OpenDatabasePage.xaml">
<Page Include="Pages\MainPageFrames\OpenDatabasePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\RecentDatabasesPage.xaml">
<Page Include="Pages\MainPageFrames\RecentDatabasesPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\SaveDatabasePage.xaml">
<Page Include="Pages\MainPageFrames\SaveDatabasePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\WelcomePage.xaml">
<Page Include="Pages\SettingsPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\MainPageFrames\WelcomePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -320,6 +330,9 @@
<Name>ModernKeePassLib</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Pages\SettingsPageFrames\" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '12.0' ">
<VisualStudioVersion>12.0</VisualStudioVersion>
</PropertyGroup>

View File

@@ -0,0 +1,184 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using ModernKeePass.Common;
using ModernKeePass.Interfaces;
namespace ModernKeePass.Pages.BasePages
{
public class LayoutAwarePageBase: Page
{
/// <summary>
/// NavigationHelper is used on each page to aid in navigation and
/// process lifetime management
/// </summary>
public NavigationHelper NavigationHelper { get; }
public virtual ListView ListView { get; set; }
public virtual CollectionViewSource ListViewSource { get; set; }
public virtual IHasSelectableObject Model { get; set; }
public LayoutAwarePageBase()
{
// Setup the navigation helper
NavigationHelper = new NavigationHelper(this);
NavigationHelper.LoadState += navigationHelper_LoadState;
NavigationHelper.SaveState += navigationHelper_SaveState;
// Setup the logical page navigation components that allow
// the page to only show one pane at a time.
NavigationHelper.GoBackCommand = new RelayCommand(() => GoBack(), () => CanGoBack());
// Start listening for Window size changes
// to change from showing two panes to showing a single pane
Window.Current.SizeChanged += Window_SizeChanged;
InvalidateVisualState();
}
protected void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Invalidate the view state when logical page navigation is in effect, as a change
// in selection may cause a corresponding change in the current logical page. When
// an item is selected this has the effect of changing from displaying the item list
// to showing the selected item's details. When the selection is cleared this has the
// opposite effect.
if (!UsingLogicalPageNavigation()) return;
NavigationHelper.GoBackCommand.RaiseCanExecuteChanged();
InvalidateVisualState();
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="sender">
/// The source of the event; typically <see cref="Common.NavigationHelper"/>
/// </param>
/// <param name="e">Event data that provides both the navigation parameter passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
/// a dictionary of state preserved by this page during an earlier
/// session. The state will be null the first time a page is visited.</param>
protected void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
// TODO: Assign a bindable group to Me.DefaultViewModel("Group")
// TODO: Assign a collection of bindable items to Me.DefaultViewModel("Items")
if (e.PageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!UsingLogicalPageNavigation() && ListViewSource.View != null)
{
ListViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (e.PageState.ContainsKey("SelectedItem") && ListViewSource.View != null)
{
ListViewSource.View.MoveCurrentTo(e.PageState["SelectedItem"]);
}
}
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="Common.SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="sender">The source of the event; typically <see cref="Common.NavigationHelper"/></param>
/// <param name="e">Event data that provides an empty dictionary to be populated with
/// serializable state.</param>
protected void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
if (ListViewSource.View != null)
{
e.PageState["SelectedItem"] = Model?.SelectedItem;
}
}
#region Logical page navigation
// The split page is designed so that when the Window does have enough space to show
// both the list and the details, only one pane will be shown at at time.
//
// This is all implemented with a single physical page that can represent two logical
// pages. The code below achieves this goal without making the user aware of the
// distinction.
protected const int MinimumWidthForSupportingTwoPanes = 768;
/// <summary>
/// Invoked to determine whether the page should act as one logical page or two.
/// </summary>
/// <returns>True if the window should show act as one logical page, false
/// otherwise.</returns>
protected bool UsingLogicalPageNavigation()
{
return Window.Current.Bounds.Width < MinimumWidthForSupportingTwoPanes;
}
/// <summary>
/// Invoked with the Window changes size
/// </summary>
/// <param name="sender">The current Window</param>
/// <param name="e">Event data that describes the new size of the Window</param>
protected void Window_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
InvalidateVisualState();
}
protected bool CanGoBack()
{
if (UsingLogicalPageNavigation() && ListView.SelectedItem != null)
{
return true;
}
return NavigationHelper.CanGoBack();
}
protected void GoBack()
{
if (UsingLogicalPageNavigation() && ListView.SelectedItem != null)
{
// When logical page navigation is in effect and there's a selected item that
// item's details are currently displayed. Clearing the selection will return to
// the item list. From the user's point of view this is a logical backward
// navigation.
ListView.SelectedItem = null;
}
else
{
NavigationHelper.GoBack();
}
}
protected void InvalidateVisualState()
{
var visualState = DetermineVisualState();
VisualStateManager.GoToState(this, visualState, false);
NavigationHelper.GoBackCommand.RaiseCanExecuteChanged();
}
/// <summary>
/// Invoked to determine the name of the visual state that corresponds to an application
/// view state.
/// </summary>
/// <returns>The name of the desired visual state. This is the same as the name of the
/// view state except when there is a selected item in portrait and snapped views where
/// this additional logical page is represented by adding a suffix of _Detail.</returns>
protected string DetermineVisualState()
{
if (!UsingLogicalPageNavigation())
return "PrimaryView";
// Update the back button's enabled state when the view state changes
var logicalPageBack = UsingLogicalPageNavigation() && ListView?.SelectedItem != null;
return logicalPageBack ? "SinglePane_Detail" : "SinglePane";
}
#endregion
}
}

View File

@@ -379,7 +379,9 @@
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</AppBarButton>
<AppBarButton Icon="Setting" Label="Settings" />
<AppBarButton Icon="Setting" Label="Settings">
<core:NavigateToPageAction TargetPage="ModernKeePass.Pages.SettingsPage" />
</AppBarButton>
</CommandBar.SecondaryCommands>
<AppBarToggleButton Icon="Edit" Label="Edit" IsChecked="{Binding IsEditMode, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>

View File

@@ -5,10 +5,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:ModernKeePass.ViewModels"
xmlns:converters="using:ModernKeePass.Converters"
xmlns:local="using:ModernKeePass.Controls"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:actions="using:ModernKeePass.Actions"
xmlns:templateSelectors="using:ModernKeePass.TemplateSelectors"
x:Name="PageRoot"
x:Class="ModernKeePass.Pages.GroupDetailPage"
mc:Ignorable="d" >
@@ -32,7 +32,9 @@
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</AppBarButton>
<AppBarButton Icon="Setting" Label="Settings" />
<AppBarButton Icon="Setting" Label="Settings">
<core:NavigateToPageAction TargetPage="ModernKeePass.Pages.SettingsPage" />
</AppBarButton>
</CommandBar.SecondaryCommands>
<AppBarToggleButton Icon="Edit" Label="Edit" IsChecked="{Binding IsEditMode, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
@@ -139,7 +141,7 @@
<viewModels:EntryVm/>
</GridView.DataContext>
<GridView.ItemTemplateSelector>
<local:FirstItemDataTemplateSelector
<templateSelectors:FirstItemDataTemplateSelector
FirstItem="{StaticResource GroupFirstItem}"
OtherItem="{StaticResource GroupOtherItem}" />
</GridView.ItemTemplateSelector>

View File

@@ -1,16 +1,14 @@
<Page
<basePages:LayoutAwarePageBase
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:ModernKeePass.ViewModels"
xmlns:controls="using:ModernKeePass.Controls"
x:Class="ModernKeePass.MainPage"
mc:Ignorable="d"
Background="{StaticResource ApplicationPageBackgroundThemeBrush}" >
<Page.DataContext>
<viewModels:MainVm />
</Page.DataContext>
xmlns:basePages="using:ModernKeePass.Pages.BasePages"
x:Class="ModernKeePass.Pages.MainPage"
x:Name="PageRoot"
mc:Ignorable="d">
<Page.Resources>
<CollectionViewSource
x:Name="MenuItemsSource"
@@ -18,49 +16,136 @@
IsSourceGrouped="True" />
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}" >
<Page.Background>
<StaticResource ResourceKey="ApplicationPageBackgroundThemeBrush"/>
</Page.Background>
<Page.DataContext>
<viewModels:MainVm />
</Page.DataContext>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition/>
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition x:Name="PrimaryColumn" Width="350" />
<ColumnDefinition x:Name="SecondaryColumn" Width="*" />
</Grid.ColumnDefinitions>
<controls:ListViewWithDisable Grid.Column="0"
<!-- Back button and page title -->
<Grid x:Name="TitlePanel" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="BackButton" Margin="39,20,0,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}"
Style="{StaticResource NavigationBackButtonNormalStyle}"
Visibility="Collapsed"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button"/>
<TextBlock x:Name="TitleTextBox" Text="{Binding Name}" Grid.Column="1" FontSize="24" Margin="20" />
</Grid>
<controls:ListViewWithDisable
Grid.Column="0"
Grid.Row="1"
x:Name="MenuListView"
RequestedTheme="Dark"
SelectionChanged="ListView_SelectionChanged"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
SelectionChanged="MenuListView_SelectionChanged"
Background="{ThemeResource AppBarBackgroundThemeBrush}"
ItemsSource="{Binding Source={StaticResource MenuItemsSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False">
<ListView.Header>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="36" Margin="20" />
</ListView.Header>
<ListView.ItemTemplate>
<controls:ListViewWithDisable.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding SymbolIcon}" />
<TextBlock Text="{Binding Title}" Margin="10,5,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
</controls:ListViewWithDisable.ItemTemplate>
<controls:ListViewWithDisable.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="20,5,0,0" />
<Setter Property="Margin" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.GroupStyle>
</controls:ListViewWithDisable.ItemContainerStyle>
<controls:ListViewWithDisable.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="DarkGray" Margin="20,0,0,0">
<Border Height="1" Width="240" HorizontalAlignment="Stretch"/>
<Border Height="1" Width="300" HorizontalAlignment="Stretch"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</controls:ListViewWithDisable.GroupStyle>
</controls:ListViewWithDisable>
<Frame Grid.Column="2" Name="MenuFrame" Width="auto" Margin="0,60,0,0" />
<Frame x:Name="MenuFrame" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" Width="auto" Margin="30,60,0,0" />
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ViewStates">
<VisualState x:Name="PrimaryView" />
<VisualState x:Name="SinglePane">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PrimaryColumn" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SecondaryColumn" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MenuFrame" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MenuListView" Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0" Value="120,0,90,60"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--
When an item is selected and only one pane is shown the details display requires more extensive changes:
* Hide the master list and the column it was in
* Move item details down a row to make room for the title
* Move the title directly above the details
* Adjust padding for details
-->
<VisualState x:Name="SinglePane_Detail">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PrimaryColumn" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MenuListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MenuFrame" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MenuFrame" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitlePanel" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleTextBox" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MenuFrame" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="10,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>
</basePages:LayoutAwarePageBase>

View File

@@ -1,34 +1,56 @@
using Windows.UI.Xaml.Controls;
using System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using ModernKeePass.Pages;
using ModernKeePass.ViewModels;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass
namespace ModernKeePass.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage
{
public MainVm Model => (MainVm)DataContext;
public new MainVm Model => (MainVm)DataContext;
public MainPage()
{
InitializeComponent();
ListView = MenuListView;
ListViewSource = MenuItemsSource;
}
private void MenuListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView_SelectionChanged(sender, e);
var selectedItem = Model.SelectedItem as MainMenuItemVm;
selectedItem?.Destination.Navigate(selectedItem.PageType, selectedItem.Parameter);
}
#region NavigationHelper registration
/// The methods provided in this section are simply used to allow
/// NavigationHelper to respond to the page's navigation methods.
///
/// Page specific logic should be placed in event handlers for the
/// <see cref="Common.NavigationHelper.LoadState"/>
/// and <see cref="Common.NavigationHelper.SaveState"/>.
/// The navigation parameter is available in the LoadState method
/// in addition to page state preserved during an earlier session.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
NavigationHelper.OnNavigatedTo(e);
DataContext = new MainVm(Frame, MenuFrame);
if (Model.SelectedItem == null) MenuFrame.Navigate(typeof(WelcomePage));
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
Model.SelectedItem?.Destination.Navigate(Model.SelectedItem.PageType, Model.SelectedItem.Parameter);
NavigationHelper.OnNavigatedFrom(e);
}
#endregion
}
}

View File

@@ -12,7 +12,7 @@
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="0,-20,0,20">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="80,-60,0,20" FontSize="36">
<Run Text="About"/>
</TextBlock>
<TextBlock Style="{StaticResource BodyTextBlockStyle}">

View File

@@ -19,7 +19,9 @@
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="0,-20,0,20">New</TextBlock>
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="80,-60,0,20" FontSize="36">
<Run Text="New"></Run>
</TextBlock>
<HyperlinkButton Content="Create new..." Click="ButtonBase_OnClick" />
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30">Create a new password database to the location of your chosing.</TextBlock>
<Border HorizontalAlignment="Left" BorderThickness="1" BorderBrush="AliceBlue" Width="550" Visibility="{Binding ShowPasswordBox, Converter={StaticResource BooleanToVisibilityConverter}}">

View File

@@ -17,7 +17,9 @@
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="0,-20,0,20">Open</TextBlock>
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="80,-60,0,20" FontSize="36">
<Run Text="Open"></Run>
</TextBlock>
<HyperlinkButton Content="Browse files..." Click="ButtonBase_OnClick" />
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30">Open an existing password database from your PC.</TextBlock>
<HyperlinkButton Content="From Url..." IsEnabled="False" />

View File

@@ -17,7 +17,7 @@
<viewModels:RecentVm/>
</Page.DataContext>
<StackPanel Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="0,-20,0,20">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="80,-60,0,20" FontSize="36">
<Run Text="Recent"/>
</TextBlock>
<ListView

View File

@@ -11,7 +11,9 @@
</Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="0,-20,0,20">Save</TextBlock>
<TextBlock Style="{StaticResource HeaderTextBlockStyle}" Margin="80,-60,0,20" FontSize="36">
<Run Text="Save"></Run>
</TextBlock>
<HyperlinkButton Content="Save and close" Click="SaveButton_OnClick" />
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30">This will save and close the currently opened database.</TextBlock>
<HyperlinkButton Content="Save as..." Click="SaveAsButton_OnClick" />

View File

@@ -0,0 +1,181 @@
<Page
x:Name="pageRoot"
x:Class="ModernKeePass.Pages.SettingsPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ModernKeePass.Pages"
xmlns:common="using:ModernKeePass.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<!-- Collection of items displayed by this page -->
<CollectionViewSource
x:Name="itemsViewSource"
Source="{Binding Items}"/>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition/>
</TransitionCollection>
</Grid.ChildrenTransitions>
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="primaryColumn" Width="420"/>
<ColumnDefinition x:Name="secondaryColumn" Width="*"/>
</Grid.ColumnDefinitions>
<!-- Back button and page title -->
<Grid x:Name="titlePanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Margin="39,59,39,0" Command="{Binding NavigationHelper.GoBackCommand, ElementName=pageRoot}"
Style="{StaticResource NavigationBackButtonNormalStyle}"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button"/>
<TextBlock x:Name="pageTitle" Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}" Grid.Column="1"
IsHitTestVisible="false" TextWrapping="NoWrap" VerticalAlignment="Bottom" Margin="0,0,0,40"/>
</Grid>
<!-- Vertical scrolling item list -->
<ListView
x:Name="itemListView"
AutomationProperties.AutomationId="ItemsListView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.Row="1"
Margin="-10,-10,0,0"
Padding="120,0,0,60"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
IsSwipeEnabled="False"
SelectionChanged="ItemListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Width="60" Height="60">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
</Border>
<StackPanel Grid.Column="1" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" MaxHeight="40"/>
<TextBlock Text="{Binding Subtitle}" Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="Margin" Value="0,0,0,10"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<!-- Details for selected item -->
<ScrollViewer
x:Name="itemDetail"
AutomationProperties.AutomationId="ItemDetailScrollViewer"
Grid.Column="1"
Grid.RowSpan="2"
Padding="60,0,66,0"
DataContext="{Binding SelectedItem, ElementName=itemListView}"
HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.ZoomMode="Disabled">
<Grid x:Name="itemDetailGrid" Margin="0,60,0,50">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Row="1" Margin="0,0,20,0" Width="180" Height="180" Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
<StackPanel x:Name="itemDetailTitlePanel" Grid.Row="1" Grid.Column="1">
<TextBlock x:Name="itemTitle" Margin="0,-10,0,0" Text="{Binding Title}" Style="{StaticResource SubheaderTextBlockStyle}"/>
<TextBlock x:Name="itemSubtitle" Margin="0,0,0,20" Text="{Binding Subtitle}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
<TextBlock Grid.Row="2" Grid.ColumnSpan="2" Margin="0,20,0,0" Text="{Binding Content}" Style="{StaticResource BodyTextBlockStyle}"/>
</Grid>
</ScrollViewer>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ViewStates">
<VisualState x:Name="PrimaryView" />
<VisualState x:Name="SinglePane">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="primaryColumn" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="*"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="secondaryColumn" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetail" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0" Value="120,0,90,60"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--
When an item is selected and only one pane is shown the details display requires more extensive changes:
* Hide the master list and the column it was in
* Move item details down a row to make room for the title
* Move the title directly above the details
* Adjust padding for details
-->
<VisualState x:Name="SinglePane_Detail">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="primaryColumn" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetail" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetail" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="titlePanel" Storyboard.TargetProperty="(Grid.Column)">
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetailGrid" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,60"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemDetail" Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0" Value="120,0,90,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>

View File

@@ -0,0 +1,255 @@
using ModernKeePass.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Windows.Input;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.ViewManagement;
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;
// The Split Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234234
namespace ModernKeePass.Pages
{
/// <summary>
/// A page that displays a group title, a list of items within the group, and details for
/// the currently selected item.
/// </summary>
public sealed partial class SettingsPage : Page
{
private NavigationHelper navigationHelper;
private ObservableDictionary defaultViewModel = new ObservableDictionary();
/// <summary>
/// This can be changed to a strongly typed view model.
/// </summary>
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
/// <summary>
/// NavigationHelper is used on each page to aid in navigation and
/// process lifetime management
/// </summary>
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
public SettingsPage()
{
this.InitializeComponent();
// Setup the navigation helper
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
this.navigationHelper.SaveState += navigationHelper_SaveState;
// Setup the logical page navigation components that allow
// the page to only show one pane at a time.
this.navigationHelper.GoBackCommand = new ModernKeePass.Common.RelayCommand(() => this.GoBack(), () => this.CanGoBack());
this.itemListView.SelectionChanged += itemListView_SelectionChanged;
// Start listening for Window size changes
// to change from showing two panes to showing a single pane
Window.Current.SizeChanged += Window_SizeChanged;
this.InvalidateVisualState();
}
void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.UsingLogicalPageNavigation())
{
this.navigationHelper.GoBackCommand.RaiseCanExecuteChanged();
}
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="sender">
/// The source of the event; typically <see cref="Common.NavigationHelper"/>
/// </param>
/// <param name="e">Event data that provides both the navigation parameter passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
/// a dictionary of state preserved by this page during an earlier
/// session. The state will be null the first time a page is visited.</param>
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
// TODO: Assign a bindable group to Me.DefaultViewModel("Group")
// TODO: Assign a collection of bindable items to Me.DefaultViewModel("Items")
if (e.PageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (e.PageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
{
// TODO: Invoke Me.itemsViewSource.View.MoveCurrentTo() with the selected
// item as specified by the value of pageState("SelectedItem")
}
}
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="Common.SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="sender">The source of the event; typically <see cref="Common.NavigationHelper"/></param>
/// <param name="e">Event data that provides an empty dictionary to be populated with
/// serializable state.</param>
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
if (this.itemsViewSource.View != null)
{
// TODO: Derive a serializable navigation parameter and assign it to
// pageState("SelectedItem")
}
}
#region Logical page navigation
// The split page is designed so that when the Window does have enough space to show
// both the list and the details, only one pane will be shown at at time.
//
// This is all implemented with a single physical page that can represent two logical
// pages. The code below achieves this goal without making the user aware of the
// distinction.
private const int MinimumWidthForSupportingTwoPanes = 768;
/// <summary>
/// Invoked to determine whether the page should act as one logical page or two.
/// </summary>
/// <returns>True if the window should show act as one logical page, false
/// otherwise.</returns>
private bool UsingLogicalPageNavigation()
{
return Window.Current.Bounds.Width < MinimumWidthForSupportingTwoPanes;
}
/// <summary>
/// Invoked with the Window changes size
/// </summary>
/// <param name="sender">The current Window</param>
/// <param name="e">Event data that describes the new size of the Window</param>
private void Window_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
this.InvalidateVisualState();
}
/// <summary>
/// Invoked when an item within the list is selected.
/// </summary>
/// <param name="sender">The GridView displaying the selected item.</param>
/// <param name="e">Event data that describes how the selection was changed.</param>
private void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Invalidate the view state when logical page navigation is in effect, as a change
// in selection may cause a corresponding change in the current logical page. When
// an item is selected this has the effect of changing from displaying the item list
// to showing the selected item's details. When the selection is cleared this has the
// opposite effect.
if (this.UsingLogicalPageNavigation()) this.InvalidateVisualState();
}
private bool CanGoBack()
{
if (this.UsingLogicalPageNavigation() && this.itemListView.SelectedItem != null)
{
return true;
}
else
{
return this.navigationHelper.CanGoBack();
}
}
private void GoBack()
{
if (this.UsingLogicalPageNavigation() && this.itemListView.SelectedItem != null)
{
// When logical page navigation is in effect and there's a selected item that
// item's details are currently displayed. Clearing the selection will return to
// the item list. From the user's point of view this is a logical backward
// navigation.
this.itemListView.SelectedItem = null;
}
else
{
this.navigationHelper.GoBack();
}
}
private void InvalidateVisualState()
{
var visualState = DetermineVisualState();
VisualStateManager.GoToState(this, visualState, false);
this.navigationHelper.GoBackCommand.RaiseCanExecuteChanged();
}
/// <summary>
/// Invoked to determine the name of the visual state that corresponds to an application
/// view state.
/// </summary>
/// <returns>The name of the desired visual state. This is the same as the name of the
/// view state except when there is a selected item in portrait and snapped views where
/// this additional logical page is represented by adding a suffix of _Detail.</returns>
private string DetermineVisualState()
{
if (!UsingLogicalPageNavigation())
return "PrimaryView";
// Update the back button's enabled state when the view state changes
var logicalPageBack = this.UsingLogicalPageNavigation() && this.itemListView.SelectedItem != null;
return logicalPageBack ? "SinglePane_Detail" : "SinglePane";
}
#endregion
#region NavigationHelper registration
/// The methods provided in this section are simply used to allow
/// NavigationHelper to respond to the page's navigation methods.
///
/// Page specific logic should be placed in event handlers for the
/// <see cref="Common.NavigationHelper.LoadState"/>
/// and <see cref="Common.NavigationHelper.SaveState"/>.
/// The navigation parameter is available in the LoadState method
/// in addition to page state preserved during an earlier session.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
navigationHelper.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
navigationHelper.OnNavigatedFrom(e);
}
#endregion
}
}

View File

@@ -1,7 +1,7 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ModernKeePass.Controls
namespace ModernKeePass.TemplateSelectors
{
public class FirstItemDataTemplateSelector: DataTemplateSelector
{

View File

@@ -5,7 +5,7 @@ using ModernKeePass.Interfaces;
namespace ModernKeePass.ViewModels
{
public class MainMenuItemVm: NotifyPropertyChangedBase, IIsEnabled
public class MainMenuItemVm: NotifyPropertyChangedBase, IIsEnabled, ISelectableModel
{
private bool _isSelected;

View File

@@ -5,11 +5,13 @@ using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using ModernKeePass.Common;
using ModernKeePass.Interfaces;
using ModernKeePass.Pages;
using ModernKeePass.Pages.BasePages;
namespace ModernKeePass.ViewModels
{
public class MainVm : NotifyPropertyChangedBase
public class MainVm : NotifyPropertyChangedBase, IHasSelectableObject
{
private IOrderedEnumerable<IGrouping<int, MainMenuItemVm>> _mainMenuItems;
private MainMenuItemVm _selectedItem;
@@ -22,7 +24,7 @@ namespace ModernKeePass.ViewModels
set { SetProperty(ref _mainMenuItems, value); }
}
public MainMenuItemVm SelectedItem
public ISelectableModel SelectedItem
{
get { return _selectedItem; }
set
@@ -33,7 +35,7 @@ namespace ModernKeePass.ViewModels
_selectedItem.IsSelected = false;
}
SetProperty(ref _selectedItem, value);
SetProperty(ref _selectedItem, (MainMenuItemVm)value);
if (_selectedItem != null)
{