From d10f6179104103ca8252e0490fc11d55cf4216f4 Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville Date: Thu, 26 Oct 2017 18:57:39 +0200 Subject: [PATCH] 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 --- ModernKeePass/App.xaml.cs | 6 +- .../Interfaces/IHasSelectableObject.cs | 7 + ModernKeePass/Interfaces/ISelectableModel.cs | 7 + ModernKeePass/ModernKeePass.csproj | 39 ++- .../Pages/BasePages/LayoutAwarePageBase.cs | 184 +++++++++++++ ModernKeePass/Pages/EntryDetailPage.xaml | 4 +- ModernKeePass/Pages/GroupDetailPage.xaml | 10 +- ModernKeePass/Pages/MainPage.xaml | 141 ++++++++-- ModernKeePass/Pages/MainPage.xaml.cs | 38 ++- .../Pages/{ => MainPageFrames}/AboutPage.xaml | 2 +- .../{ => MainPageFrames}/AboutPage.xaml.cs | 0 .../{ => MainPageFrames}/NewDatabasePage.xaml | 4 +- .../NewDatabasePage.xaml.cs | 0 .../OpenDatabasePage.xaml | 4 +- .../OpenDatabasePage.xaml.cs | 0 .../RecentDatabasesPage.xaml | 2 +- .../RecentDatabasesPage.xaml.cs | 0 .../SaveDatabasePage.xaml | 4 +- .../SaveDatabasePage.xaml.cs | 0 .../{ => MainPageFrames}/WelcomePage.xaml | 0 .../{ => MainPageFrames}/WelcomePage.xaml.cs | 0 ModernKeePass/Pages/SettingsPage.xaml | 181 +++++++++++++ ModernKeePass/Pages/SettingsPage.xaml.cs | 255 ++++++++++++++++++ .../FirstItemDataTemplateSelector.cs | 2 +- .../ViewModels/Items/MainMenuItemVm.cs | 2 +- ModernKeePass/ViewModels/MainVm.cs | 8 +- 26 files changed, 833 insertions(+), 67 deletions(-) create mode 100644 ModernKeePass/Interfaces/IHasSelectableObject.cs create mode 100644 ModernKeePass/Interfaces/ISelectableModel.cs create mode 100644 ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs rename ModernKeePass/Pages/{ => MainPageFrames}/AboutPage.xaml (98%) rename ModernKeePass/Pages/{ => MainPageFrames}/AboutPage.xaml.cs (100%) rename ModernKeePass/Pages/{ => MainPageFrames}/NewDatabasePage.xaml (96%) rename ModernKeePass/Pages/{ => MainPageFrames}/NewDatabasePage.xaml.cs (100%) rename ModernKeePass/Pages/{ => MainPageFrames}/OpenDatabasePage.xaml (96%) rename ModernKeePass/Pages/{ => MainPageFrames}/OpenDatabasePage.xaml.cs (100%) rename ModernKeePass/Pages/{ => MainPageFrames}/RecentDatabasesPage.xaml (98%) rename ModernKeePass/Pages/{ => MainPageFrames}/RecentDatabasesPage.xaml.cs (100%) rename ModernKeePass/Pages/{ => MainPageFrames}/SaveDatabasePage.xaml (92%) rename ModernKeePass/Pages/{ => MainPageFrames}/SaveDatabasePage.xaml.cs (100%) rename ModernKeePass/Pages/{ => MainPageFrames}/WelcomePage.xaml (100%) rename ModernKeePass/Pages/{ => MainPageFrames}/WelcomePage.xaml.cs (100%) create mode 100644 ModernKeePass/Pages/SettingsPage.xaml create mode 100644 ModernKeePass/Pages/SettingsPage.xaml.cs rename ModernKeePass/{Controls => TemplateSelectors}/FirstItemDataTemplateSelector.cs (93%) diff --git a/ModernKeePass/App.xaml.cs b/ModernKeePass/App.xaml.cs index ff09eb1..7938eef 100644 --- a/ModernKeePass/App.xaml.cs +++ b/ModernKeePass/App.xaml.cs @@ -17,7 +17,7 @@ namespace ModernKeePass /// /// Provides application-specific behavior to supplement the default Application class. /// - sealed partial class App : Application + sealed partial class App { public DatabaseHelper Database { get; set; } = new DatabaseHelper(); public Dictionary PendingDeleteEntities = new Dictionary(); @@ -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(); } diff --git a/ModernKeePass/Interfaces/IHasSelectableObject.cs b/ModernKeePass/Interfaces/IHasSelectableObject.cs new file mode 100644 index 0000000..038cb05 --- /dev/null +++ b/ModernKeePass/Interfaces/IHasSelectableObject.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Interfaces +{ + public interface IHasSelectableObject + { + ISelectableModel SelectedItem { get; set; } + } +} diff --git a/ModernKeePass/Interfaces/ISelectableModel.cs b/ModernKeePass/Interfaces/ISelectableModel.cs new file mode 100644 index 0000000..b1382cd --- /dev/null +++ b/ModernKeePass/Interfaces/ISelectableModel.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Interfaces +{ + public interface ISelectableModel + { + bool IsSelected { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass/ModernKeePass.csproj b/ModernKeePass/ModernKeePass.csproj index 256831f..ed3cc0a 100644 --- a/ModernKeePass/ModernKeePass.csproj +++ b/ModernKeePass/ModernKeePass.csproj @@ -119,7 +119,10 @@ - + + + + OpenDatabaseUserControl.xaml @@ -140,13 +143,16 @@ MainPage.xaml - + AboutPage.xaml - + NewDatabasePage.xaml - + + SettingsPage.xaml + + WelcomePage.xaml @@ -158,13 +164,13 @@ GroupDetailPage.xaml - + OpenDatabasePage.xaml - + RecentDatabasesPage.xaml - + SaveDatabasePage.xaml @@ -202,7 +208,7 @@ MSBuild:Compile Designer - + Designer MSBuild:Compile @@ -214,23 +220,27 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + Designer MSBuild:Compile @@ -320,6 +330,9 @@ ModernKeePassLib + + + 12.0 diff --git a/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs b/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs new file mode 100644 index 0000000..be8f8da --- /dev/null +++ b/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs @@ -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 + { + /// + /// NavigationHelper is used on each page to aid in navigation and + /// process lifetime management + /// + 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(); + } + + /// + /// Populates the page with content passed during navigation. Any saved state is also + /// provided when recreating a page from a prior session. + /// + /// + /// The source of the event; typically + /// + /// Event data that provides both the navigation parameter passed to + /// 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. + 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"]); + } + } + } + + /// + /// 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 . + /// + /// The source of the event; typically + /// Event data that provides an empty dictionary to be populated with + /// serializable state. + 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; + + /// + /// Invoked to determine whether the page should act as one logical page or two. + /// + /// True if the window should show act as one logical page, false + /// otherwise. + protected bool UsingLogicalPageNavigation() + { + return Window.Current.Bounds.Width < MinimumWidthForSupportingTwoPanes; + } + + /// + /// Invoked with the Window changes size + /// + /// The current Window + /// Event data that describes the new size of the Window + 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(); + } + + /// + /// Invoked to determine the name of the visual state that corresponds to an application + /// view state. + /// + /// 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. + 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 + } +} diff --git a/ModernKeePass/Pages/EntryDetailPage.xaml b/ModernKeePass/Pages/EntryDetailPage.xaml index 45f4fd2..48c3567 100644 --- a/ModernKeePass/Pages/EntryDetailPage.xaml +++ b/ModernKeePass/Pages/EntryDetailPage.xaml @@ -379,7 +379,9 @@ - + + + diff --git a/ModernKeePass/Pages/GroupDetailPage.xaml b/ModernKeePass/Pages/GroupDetailPage.xaml index ba74310..0bb0269 100644 --- a/ModernKeePass/Pages/GroupDetailPage.xaml +++ b/ModernKeePass/Pages/GroupDetailPage.xaml @@ -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:actions="using:ModernKeePass.Actions" + xmlns:templateSelectors="using:ModernKeePass.TemplateSelectors" x:Name="PageRoot" x:Class="ModernKeePass.Pages.GroupDetailPage" mc:Ignorable="d" > @@ -32,7 +32,9 @@ - + + + @@ -139,7 +141,7 @@ - diff --git a/ModernKeePass/Pages/MainPage.xaml b/ModernKeePass/Pages/MainPage.xaml index ded151f..974a5f2 100644 --- a/ModernKeePass/Pages/MainPage.xaml +++ b/ModernKeePass/Pages/MainPage.xaml @@ -1,16 +1,14 @@ - - - - + xmlns:controls="using:ModernKeePass.Controls" + xmlns:basePages="using:ModernKeePass.Pages.BasePages" + x:Class="ModernKeePass.Pages.MainPage" + x:Name="PageRoot" + mc:Ignorable="d"> - + + + + + + + + + + + + + + + + + - - - + + - + + + + + +