diff --git a/ModernKeePass/Common/DatabaseHelper.cs b/ModernKeePass/Common/DatabaseHelper.cs index 7166d48..a759daa 100644 --- a/ModernKeePass/Common/DatabaseHelper.cs +++ b/ModernKeePass/Common/DatabaseHelper.cs @@ -16,13 +16,31 @@ namespace ModernKeePass.Common Opening = 1, Opened = 2 } - private PwDatabase _pwDatabase = new PwDatabase(); + private readonly PwDatabase _pwDatabase = new PwDatabase(); private StorageFile _databaseFile; + private GroupVm _recycleBin; public GroupVm RootGroup { get; set; } + + public GroupVm RecycleBin + { + get { return _recycleBin; } + set + { + _recycleBin = value; + _pwDatabase.RecycleBinUuid = _recycleBin.IdUuid; + } + } + public DatabaseStatus Status { get; private set; } = DatabaseStatus.Closed; public string Name => DatabaseFile?.Name; - + + public bool RecycleBinEnabled + { + get { return _pwDatabase.RecycleBinEnabled; } + set { _pwDatabase.RecycleBinEnabled = value; } + } + public StorageFile DatabaseFile { get { return _databaseFile; } @@ -52,7 +70,7 @@ namespace ModernKeePass.Common if (_pwDatabase.IsOpen) { Status = DatabaseStatus.Opened; - RootGroup = new GroupVm(_pwDatabase.RootGroup, null); + RootGroup = new GroupVm(_pwDatabase.RootGroup, null, RecycleBinEnabled ? _pwDatabase.RecycleBinUuid : null); } } catch (ArgumentNullException) diff --git a/ModernKeePass/Converters/TextToFontStyleConverter.cs b/ModernKeePass/Converters/BooleanToFontStyleConverter.cs similarity index 52% rename from ModernKeePass/Converters/TextToFontStyleConverter.cs rename to ModernKeePass/Converters/BooleanToFontStyleConverter.cs index 6cb62fb..e767a33 100644 --- a/ModernKeePass/Converters/TextToFontStyleConverter.cs +++ b/ModernKeePass/Converters/BooleanToFontStyleConverter.cs @@ -1,19 +1,15 @@ using System; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.UI.Text; using Windows.UI.Xaml.Data; namespace ModernKeePass.Converters { - public class TextToFontStyleConverter : IValueConverter + public class BooleanToFontStyleConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { - var compareValue = parameter as string; - var text = value as string; - return string.Compare(text, compareValue, StringComparison.OrdinalIgnoreCase) == 0 - ? FontStyle.Italic - : FontStyle.Normal; + var boolean = value is bool ? (bool)value : false; + return boolean ? FontStyle.Italic : FontStyle.Normal; } public object ConvertBack(object value, Type targetType, object parameter, string language) diff --git a/ModernKeePass/Interfaces/IPwEntity.cs b/ModernKeePass/Interfaces/IPwEntity.cs index 3142f80..d595d47 100644 --- a/ModernKeePass/Interfaces/IPwEntity.cs +++ b/ModernKeePass/Interfaces/IPwEntity.cs @@ -11,9 +11,21 @@ namespace ModernKeePass.Interfaces string Name { get; set; } bool IsEditMode { get; } + /// + /// Delete from Model + /// void CommitDelete(); + /// + /// Restore ViewModel + /// void UndoDelete(); + /// + /// Save changes to Model + /// void Save(); + /// + /// Delete from ViewModel + /// void MarkForDelete(); } } \ No newline at end of file diff --git a/ModernKeePass/ModernKeePass.csproj b/ModernKeePass/ModernKeePass.csproj index ab87ed5..d6b5387 100644 --- a/ModernKeePass/ModernKeePass.csproj +++ b/ModernKeePass/ModernKeePass.csproj @@ -123,6 +123,9 @@ + + SettingsDatabasePage.xaml + @@ -133,7 +136,7 @@ - + @@ -157,6 +160,7 @@ WelcomePage.xaml + @@ -177,11 +181,13 @@ + + @@ -245,6 +251,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -341,9 +351,7 @@ ModernKeePassLib - - - + 12.0 diff --git a/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs b/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs index 68c2d0f..5e15942 100644 --- a/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs +++ b/ModernKeePass/Pages/BasePages/LayoutAwarePageBase.cs @@ -5,7 +5,6 @@ using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Navigation; using ModernKeePass.Common; using ModernKeePass.Interfaces; -using ModernKeePass.ViewModels; namespace ModernKeePass.Pages.BasePages { diff --git a/ModernKeePass/Pages/EntryDetailPage.xaml b/ModernKeePass/Pages/EntryDetailPage.xaml index 5802291..5a93cbc 100644 --- a/ModernKeePass/Pages/EntryDetailPage.xaml +++ b/ModernKeePass/Pages/EntryDetailPage.xaml @@ -379,13 +379,13 @@ - + diff --git a/ModernKeePass/Pages/EntryDetailPage.xaml.cs b/ModernKeePass/Pages/EntryDetailPage.xaml.cs index ea701e7..9e1fbff 100644 --- a/ModernKeePass/Pages/EntryDetailPage.xaml.cs +++ b/ModernKeePass/Pages/EntryDetailPage.xaml.cs @@ -75,7 +75,11 @@ namespace ModernKeePass.Pages private void DeleteButton_Click(object sender, RoutedEventArgs e) { - MessageDialogHelper.ShowDeleteConfirmationDialog("Are you sure you want to delete this entry?", Model, Frame); + var app = (App)Application.Current; + var message = app.Database.RecycleBinEnabled + ? "Are you sure you want to send this entry to the recycle bin?" + : "Are you sure you want to delete this entry?"; + MessageDialogHelper.ShowDeleteConfirmationDialog(message, Model, Frame); } private async void UrlButton_Click(object sender, RoutedEventArgs e) diff --git a/ModernKeePass/Pages/GroupDetailPage.xaml b/ModernKeePass/Pages/GroupDetailPage.xaml index f180b7f..c1f3882 100644 --- a/ModernKeePass/Pages/GroupDetailPage.xaml +++ b/ModernKeePass/Pages/GroupDetailPage.xaml @@ -16,7 +16,7 @@ - + @@ -32,13 +32,13 @@ - + @@ -101,8 +101,7 @@ - - + @@ -161,12 +160,12 @@ - + - + @@ -189,10 +188,7 @@ - - diff --git a/ModernKeePass/Pages/GroupDetailPage.xaml.cs b/ModernKeePass/Pages/GroupDetailPage.xaml.cs index 582dbdd..3db3970 100644 --- a/ModernKeePass/Pages/GroupDetailPage.xaml.cs +++ b/ModernKeePass/Pages/GroupDetailPage.xaml.cs @@ -1,9 +1,6 @@ using System; using System.Linq; -using System.Threading.Tasks; using Windows.Storage.Streams; -using Windows.UI.Core; -using Windows.UI.Popups; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; @@ -115,7 +112,11 @@ namespace ModernKeePass.Pages private void DeleteButton_Click(object sender, RoutedEventArgs e) { - MessageDialogHelper.ShowDeleteConfirmationDialog("Are you sure you want to delete the whole group and all its entries?", Model, Frame); + var app = (App) Application.Current; + var message = app.Database.RecycleBinEnabled + ? "Are you sure you want to send the whole group and all its entries to the recycle bin?" + : "Are you sure you want to delete the whole group and all its entries?"; + MessageDialogHelper.ShowDeleteConfirmationDialog(message, Model, Frame); } private void SemanticZoom_ViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e) diff --git a/ModernKeePass/Pages/MainPage.xaml b/ModernKeePass/Pages/MainPage.xaml index eab0dc9..9f01e41 100644 --- a/ModernKeePass/Pages/MainPage.xaml +++ b/ModernKeePass/Pages/MainPage.xaml @@ -130,12 +130,6 @@ - @@ -148,9 +142,6 @@ - diff --git a/ModernKeePass/Pages/SettingsPage.xaml b/ModernKeePass/Pages/SettingsPage.xaml index e4785f9..c217926 100644 --- a/ModernKeePass/Pages/SettingsPage.xaml +++ b/ModernKeePass/Pages/SettingsPage.xaml @@ -1,144 +1,111 @@ - - - - + + + + + - - + + + + - + - - + + - + - + - + - - - - + x:Name="MenuListView" + SelectionChanged="MenuListView_SelectionChanged" + Background="{ThemeResource AppBarBackgroundThemeBrush}" + ItemsSource="{Binding Source={StaticResource MenuItemsSource}}" + SelectedItem="{Binding SelectedItem, Mode=TwoWay}" + IsSynchronizedWithCurrentItem="False" + ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}"> + - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - + - + - + + + + @@ -152,30 +119,27 @@ --> - + - + - + - - + + - - + + - - - - - + + - + diff --git a/ModernKeePass/Pages/SettingsPage.xaml.cs b/ModernKeePass/Pages/SettingsPage.xaml.cs index 8763d08..20a2fac 100644 --- a/ModernKeePass/Pages/SettingsPage.xaml.cs +++ b/ModernKeePass/Pages/SettingsPage.xaml.cs @@ -1,20 +1,5 @@ -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; +using Windows.UI.Xaml.Controls; +using ModernKeePass.ViewModels; // The Split Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234234 @@ -24,232 +9,23 @@ namespace ModernKeePass.Pages /// A page that displays a group title, a list of items within the group, and details for /// the currently selected item. /// - public sealed partial class SettingsPage : Page + public sealed partial class SettingsPage { - private NavigationHelper navigationHelper; - private ObservableDictionary defaultViewModel = new ObservableDictionary(); - - /// - /// This can be changed to a strongly typed view model. - /// - public ObservableDictionary DefaultViewModel - { - get { return this.defaultViewModel; } - } - - /// - /// NavigationHelper is used on each page to aid in navigation and - /// process lifetime management - /// - public NavigationHelper NavigationHelper - { - get { return this.navigationHelper; } - } + public new SettingsVM Model => (SettingsVM)DataContext; 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(); + InitializeComponent(); + ListView = MenuListView; + ListViewSource = MenuItemsSource; } - - void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + + private void MenuListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (this.UsingLogicalPageNavigation()) - { - this.navigationHelper.GoBackCommand.RaiseCanExecuteChanged(); - } + ListView_SelectionChanged(sender, e); + var selectedItem = Model.SelectedItem as ListMenuItemVm; + if (selectedItem == null) return; + MenuFrame?.Navigate(selectedItem.PageType); } - - /// - /// 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. - 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") - - } - } - } - - /// - /// 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. - 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; - - /// - /// 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. - private 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 - private void Window_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e) - { - this.InvalidateVisualState(); - } - - /// - /// Invoked when an item within the list is selected. - /// - /// The GridView displaying the selected item. - /// Event data that describes how the selection was changed. - 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(); - } - - /// - /// 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. - 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 - /// - /// and . - /// 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 } } diff --git a/ModernKeePass/Pages/SettingsPageFrames/SettingsDatabasePage.xaml b/ModernKeePass/Pages/SettingsPageFrames/SettingsDatabasePage.xaml new file mode 100644 index 0000000..f08249f --- /dev/null +++ b/ModernKeePass/Pages/SettingsPageFrames/SettingsDatabasePage.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/ModernKeePass/Pages/SettingsPageFrames/SettingsDatabasePage.xaml.cs b/ModernKeePass/Pages/SettingsPageFrames/SettingsDatabasePage.xaml.cs new file mode 100644 index 0000000..ea905f2 --- /dev/null +++ b/ModernKeePass/Pages/SettingsPageFrames/SettingsDatabasePage.xaml.cs @@ -0,0 +1,15 @@ +// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238 + +namespace ModernKeePass.Pages +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class SettingsDatabasePage + { + public SettingsDatabasePage() + { + InitializeComponent(); + } + } +} diff --git a/ModernKeePass/ViewModels/EntryVm.cs b/ModernKeePass/ViewModels/EntryVm.cs index 86e7996..1716830 100644 --- a/ModernKeePass/ViewModels/EntryVm.cs +++ b/ModernKeePass/ViewModels/EntryVm.cs @@ -14,13 +14,13 @@ namespace ModernKeePass.ViewModels public class EntryVm : INotifyPropertyChanged, IPwEntity { public GroupVm ParentGroup { get; } - public PwEntry Entry { get; } - public System.Drawing.Color? BackgroundColor => Entry?.BackgroundColor; - public System.Drawing.Color? ForegroundColor => Entry?.ForegroundColor; + public System.Drawing.Color? BackgroundColor => _pwEntry?.BackgroundColor; + public System.Drawing.Color? ForegroundColor => _pwEntry?.ForegroundColor; public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password); - public bool HasExpired => HasExpirationDate && Entry.ExpiryTime < DateTime.Now; + public bool HasExpired => HasExpirationDate && _pwEntry.ExpiryTime < DateTime.Now; public double PasswordComplexityIndicator => QualityEstimation.EstimatePasswordBits(Password.ToCharArray()); + public bool IsFirstItem => _pwEntry == null; public double PasswordLength { get; set; } = 25; public bool UpperCasePatternSelected { get; set; } = true; @@ -38,12 +38,12 @@ namespace ModernKeePass.ViewModels get { var title = GetEntryValue(PwDefs.TitleField); - return title == null ? "New entry" : title; + return title == null ? "< New entry >" : title; } set { SetEntryValue(PwDefs.TitleField, value); } } - public string Id => Entry.Uuid.ToHexString(); + public string Id => _pwEntry?.Uuid.ToHexString(); public string UserName { @@ -75,22 +75,22 @@ namespace ModernKeePass.ViewModels { get { - if (Entry == null) return Symbol.Add; + if (_pwEntry == null) return Symbol.Add; if (HasExpired) return Symbol.Priority; - var result = PwIconToSegoeMapping.GetSymbolFromIcon(Entry.IconId); + var result = PwIconToSegoeMapping.GetSymbolFromIcon(_pwEntry.IconId); return result == Symbol.More ? Symbol.Permissions : result; } } public DateTimeOffset ExpiryDate { - get { return new DateTimeOffset(Entry.ExpiryTime.Date); } - set { if (HasExpirationDate) Entry.ExpiryTime = value.DateTime; } + get { return new DateTimeOffset(_pwEntry.ExpiryTime.Date); } + set { if (HasExpirationDate) _pwEntry.ExpiryTime = value.DateTime; } } public TimeSpan ExpiryTime { - get { return Entry.ExpiryTime.TimeOfDay; } - set { if (HasExpirationDate) Entry.ExpiryTime = Entry.ExpiryTime.Date.Add(value); } + get { return _pwEntry.ExpiryTime.TimeOfDay; } + set { if (HasExpirationDate) _pwEntry.ExpiryTime = _pwEntry.ExpiryTime.Date.Add(value); } } public bool IsEditMode @@ -114,16 +114,17 @@ namespace ModernKeePass.ViewModels } public bool HasExpirationDate { - get { return Entry.Expires; } + get { return _pwEntry.Expires; } set { - Entry.Expires = value; + _pwEntry.Expires = value; NotifyPropertyChanged("HasExpirationDate"); } } public event PropertyChangedEventHandler PropertyChanged; + private readonly PwEntry _pwEntry; private bool _isEditMode; private bool _isRevealPassword; @@ -135,7 +136,7 @@ namespace ModernKeePass.ViewModels public EntryVm() { } public EntryVm(PwEntry entry, GroupVm parent) { - Entry = entry; + _pwEntry = entry; ParentGroup = parent; } @@ -163,7 +164,7 @@ namespace ModernKeePass.ViewModels ProtectedString password; PwGenerator.Generate(out password, pwProfile, null, new CustomPwGeneratorPool()); - Entry?.Strings.Set(PwDefs.PasswordField, password); + _pwEntry?.Strings.Set(PwDefs.PasswordField, password); NotifyPropertyChanged("Password"); NotifyPropertyChanged("IsRevealPasswordEnabled"); NotifyPropertyChanged("PasswordComplexityIndicator"); @@ -171,12 +172,12 @@ namespace ModernKeePass.ViewModels private string GetEntryValue(string key) { - return Entry?.Strings.GetSafe(key).ReadString(); + return _pwEntry?.Strings.GetSafe(key).ReadString(); } private void SetEntryValue(string key, string newValue) { - Entry?.Strings.Set(key, new ProtectedString(true, newValue)); + _pwEntry?.Strings.Set(key, new ProtectedString(true, newValue)); } public void MarkForDelete() @@ -187,7 +188,7 @@ namespace ModernKeePass.ViewModels } public void CommitDelete() { - Entry.ParentGroup.Entries.Remove(Entry); + _pwEntry.ParentGroup.Entries.Remove(_pwEntry); } public void UndoDelete() diff --git a/ModernKeePass/ViewModels/GroupVm.cs b/ModernKeePass/ViewModels/GroupVm.cs index a2f3a7e..5b15e13 100644 --- a/ModernKeePass/ViewModels/GroupVm.cs +++ b/ModernKeePass/ViewModels/GroupVm.cs @@ -7,11 +7,10 @@ using ModernKeePass.Common; using ModernKeePass.Interfaces; using ModernKeePass.Mappings; using ModernKeePassLib; -using System; namespace ModernKeePass.ViewModels { - public class GroupVm : NotifyPropertyChangedBase, IPwEntity + public class GroupVm : NotifyPropertyChangedBase, IPwEntity, ISelectableModel { public GroupVm ParentGroup { get; } public ObservableCollection Entries { get; set; } = new ObservableCollection(); @@ -19,26 +18,33 @@ namespace ModernKeePass.ViewModels public ObservableCollection Groups { get; set; } = new ObservableCollection(); public int EntryCount => Entries.Count() - 1; - public int GroupCount => Groups.Count - 1; - public bool IsNotRoot => ParentGroup != null; public FontWeight FontWeight => _pwGroup == null ? FontWeights.Bold : FontWeights.Normal; - public string Id => _pwGroup.Uuid.ToHexString(); - - public IOrderedEnumerable> EntriesZoomedOut + public int GroupCount => Groups.Count - 1; + public PwUuid IdUuid => _pwGroup?.Uuid; + public string Id => IdUuid?.ToHexString(); + public bool IsNotRoot => ParentGroup != null; + /// + /// Is the Group the database Recycle Bin? + /// + public bool IsSelected { - get + get { return _app.Database.RecycleBinEnabled && _app.Database.RecycleBin.Id == Id; } + set { - return from e in Entries - where e.Entry != null - group e by e.Name.FirstOrDefault() into grp - orderby grp.Key - select grp; + // TODO: if _pwGroup is null, create a new group + if (value && _pwGroup != null) _app.Database.RecycleBin = this; } } + public IOrderedEnumerable> EntriesZoomedOut => from e in Entries + where !e.IsFirstItem + group e by e.Name.FirstOrDefault() into grp + orderby grp.Key + select grp; + public string Name { - get { return _pwGroup == null ? "New group" : _pwGroup.Name; } + get { return _pwGroup == null ? "< New group >" : _pwGroup.Name; } set { _pwGroup.Name = value; } } @@ -59,18 +65,20 @@ namespace ModernKeePass.ViewModels } private readonly PwGroup _pwGroup; - private bool _isLeftPaneOpen; + private readonly App _app = (App)Application.Current; private bool _isEditMode; public GroupVm() {} - public GroupVm(PwGroup pwGroup, GroupVm parent) + public GroupVm(PwGroup pwGroup, GroupVm parent, PwUuid recycleBinId = null) { _pwGroup = pwGroup; ParentGroup = parent; + + if (recycleBinId != null && _pwGroup.Uuid.Equals(recycleBinId)) _app.Database.RecycleBin = this; Entries = new ObservableCollection(pwGroup.Entries.Select(e => new EntryVm(e, this)).OrderBy(e => e.Name)); Entries.Insert(0, new EntryVm ()); - Groups = new ObservableCollection(pwGroup.Groups.Select(g => new GroupVm(g, this)).OrderBy(g => g.Name)); + Groups = new ObservableCollection(pwGroup.Groups.Select(g => new GroupVm(g, this, recycleBinId)).OrderBy(g => g.Name)); Groups.Insert(0, new GroupVm ()); } @@ -94,24 +102,31 @@ namespace ModernKeePass.ViewModels public void MarkForDelete() { - var app = (App)Application.Current; - app.PendingDeleteEntities.Add(Id, this); + _app.PendingDeleteEntities.Add(Id, this); ParentGroup.Groups.Remove(this); + if (_app.Database.RecycleBinEnabled && !IsSelected) _app.Database.RecycleBin.Groups.Add(this); } public void CommitDelete() { _pwGroup.ParentGroup.Groups.Remove(_pwGroup); + if (_app.Database.RecycleBinEnabled && !IsSelected) _app.Database.RecycleBin._pwGroup.AddGroup(_pwGroup, true); } + public void UndoDelete() { ParentGroup.Groups.Add(this); + if (_app.Database.RecycleBinEnabled && !IsSelected) _app.Database.RecycleBin.Groups.Remove(this); } public void Save() { - var app = (App)Application.Current; - app.Database.Save(); + _app.Database.Save(); + } + + public override string ToString() + { + return Name; } } } diff --git a/ModernKeePass/ViewModels/Items/ListMenuItemVm.cs b/ModernKeePass/ViewModels/Items/ListMenuItemVm.cs new file mode 100644 index 0000000..abb49c1 --- /dev/null +++ b/ModernKeePass/ViewModels/Items/ListMenuItemVm.cs @@ -0,0 +1,30 @@ +using System; +using Windows.UI.Xaml.Controls; +using ModernKeePass.Common; +using ModernKeePass.Interfaces; + +namespace ModernKeePass.ViewModels +{ + public class ListMenuItemVm : NotifyPropertyChangedBase, IIsEnabled, ISelectableModel + { + private bool _isSelected; + + public string Title { get; set; } + + public int Group { get; set; } = 0; + public Type PageType { get; set; } + public Symbol SymbolIcon { get; set; } + public bool IsEnabled { get; set; } = true; + + public bool IsSelected + { + get { return _isSelected; } + set { SetProperty(ref _isSelected, value); } + } + + public override string ToString() + { + return Title; + } + } +} diff --git a/ModernKeePass/ViewModels/Items/MainMenuItemVm.cs b/ModernKeePass/ViewModels/Items/MainMenuItemVm.cs index f871336..f5b5ba2 100644 --- a/ModernKeePass/ViewModels/Items/MainMenuItemVm.cs +++ b/ModernKeePass/ViewModels/Items/MainMenuItemVm.cs @@ -1,32 +1,10 @@ -using System; -using Windows.UI.Xaml.Controls; -using ModernKeePass.Common; -using ModernKeePass.Interfaces; +using Windows.UI.Xaml.Controls; namespace ModernKeePass.ViewModels { - public class MainMenuItemVm: NotifyPropertyChangedBase, IIsEnabled, ISelectableModel + public class MainMenuItemVm: ListMenuItemVm { - private bool _isSelected; - - public string Title { get; set; } - - public Type PageType { get; set; } public object Parameter { get; set; } public Frame Destination { get; set; } - public int Group { get; set; } = 0; - public Symbol SymbolIcon { get; set; } - public bool IsEnabled { get; set; } = true; - - public bool IsSelected - { - get { return _isSelected; } - set { SetProperty(ref _isSelected, value); } - } - - public override string ToString() - { - return Title; - } } } diff --git a/ModernKeePass/ViewModels/Items/RecentItemVm.cs b/ModernKeePass/ViewModels/Items/RecentItemVm.cs index 8091283..a970a36 100644 --- a/ModernKeePass/ViewModels/Items/RecentItemVm.cs +++ b/ModernKeePass/ViewModels/Items/RecentItemVm.cs @@ -1,5 +1,4 @@ -using System; -using Windows.Storage; +using Windows.Storage; using ModernKeePass.Common; using Windows.Storage.AccessCache; diff --git a/ModernKeePass/ViewModels/Items/SettingsVM.cs b/ModernKeePass/ViewModels/Items/SettingsVM.cs new file mode 100644 index 0000000..ef29685 --- /dev/null +++ b/ModernKeePass/ViewModels/Items/SettingsVM.cs @@ -0,0 +1,45 @@ +using System.Collections.ObjectModel; +using System.Linq; +using Windows.UI.Xaml.Controls; +using ModernKeePass.Common; +using ModernKeePass.Interfaces; +using ModernKeePass.Pages; + +namespace ModernKeePass.ViewModels +{ + public class SettingsVM : NotifyPropertyChangedBase, IHasSelectableObject + { + private ListMenuItemVm _selectedItem; + + public ObservableCollection MenuItems { get; set; } + public ISelectableModel SelectedItem + { + get { return _selectedItem; } + set + { + if (_selectedItem == value) return; + if (_selectedItem != null) + { + _selectedItem.IsSelected = false; + } + + SetProperty(ref _selectedItem, (ListMenuItemVm)value); + + if (_selectedItem != null) + { + _selectedItem.IsSelected = true; + } + } + } + + public SettingsVM() + { + MenuItems = new ObservableCollection + { + new ListMenuItemVm { Title = "Database", SymbolIcon = Symbol.Setting, PageType = typeof(SettingsDatabasePage), IsSelected = true }, + //new ListMenuItemVm { Title = "General", SymbolIcon = Symbol.Edit, PageType = typeof(SettingsGeneralPage) } + }; + SelectedItem = MenuItems.FirstOrDefault(m => m.IsSelected); + } + } +} diff --git a/ModernKeePass/ViewModels/MainVm.cs b/ModernKeePass/ViewModels/MainVm.cs index 4daef6d..f83890f 100644 --- a/ModernKeePass/ViewModels/MainVm.cs +++ b/ModernKeePass/ViewModels/MainVm.cs @@ -7,7 +7,6 @@ using Windows.UI.Xaml.Controls; using ModernKeePass.Common; using ModernKeePass.Interfaces; using ModernKeePass.Pages; -using ModernKeePass.Pages.BasePages; namespace ModernKeePass.ViewModels { diff --git a/ModernKeePass/ViewModels/SettingsDatabaseVm.cs b/ModernKeePass/ViewModels/SettingsDatabaseVm.cs new file mode 100644 index 0000000..9069fb4 --- /dev/null +++ b/ModernKeePass/ViewModels/SettingsDatabaseVm.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using Windows.Storage; +using Windows.UI.Xaml; +using ModernKeePass.Common; +using ModernKeePass.Interfaces; + +namespace ModernKeePass.ViewModels +{ + public class SettingsDatabaseVm: NotifyPropertyChangedBase, IHasSelectableObject + { + private readonly App _app = (App)Application.Current; + private readonly ApplicationDataContainer _localSettings = ApplicationData.Current.LocalSettings; + private GroupVm _selectedItem; + + public bool HasRecycleBin + { + get { return _app.Database.RecycleBinEnabled; } + set + { + _app.Database.RecycleBinEnabled = value; + OnPropertyChanged(); + } + } + + public ObservableCollection Groups { get; set; } + + public ISelectableModel SelectedItem + { + get { return Groups.FirstOrDefault(g => g.IsSelected); } + set + { + if (_selectedItem == value) return; + if (_selectedItem != null) + { + _selectedItem.IsSelected = false; + } + + SetProperty(ref _selectedItem, (GroupVm)value); + + if (_selectedItem != null) + { + _selectedItem.IsSelected = true; + } + } + } + + public SettingsDatabaseVm() + { + Groups = _app.Database.RootGroup.Groups; + } + + // TODO: Move to another setting class (or a static class) + private T GetSetting(string property) + { + try + { + return (T) Convert.ChangeType(_localSettings.Values[property], typeof(T)); + } + catch (InvalidCastException) + { + return default(T); + } + } + } +}