diff --git a/src/App/App.csproj b/src/App/App.csproj index 43411eb3f..639e73df4 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -67,6 +67,7 @@ + diff --git a/src/App/Controls/AddCipherToolbarItem.cs b/src/App/Controls/AddCipherToolbarItem.cs new file mode 100644 index 000000000..e70137a30 --- /dev/null +++ b/src/App/Controls/AddCipherToolbarItem.cs @@ -0,0 +1,16 @@ +using Bit.App.Resources; +using Bit.App.Utilities; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class AddCipherToolbarItem : ExtendedToolbarItem + { + public AddCipherToolbarItem(Page page, string folderId) + : base(() => Helpers.AddCipher(page, folderId)) + { + Text = AppResources.Add; + Icon = "plus.png"; + } + } +} diff --git a/src/App/Pages/Vault/VaultAddCipherPage.cs b/src/App/Pages/Vault/VaultAddCipherPage.cs index 5c0be8e23..d72ceb44f 100644 --- a/src/App/Pages/Vault/VaultAddCipherPage.cs +++ b/src/App/Pages/Vault/VaultAddCipherPage.cs @@ -30,6 +30,7 @@ namespace Bit.App.Pages private readonly IAppInfoService _appInfoService; private readonly IDeviceInfoService _deviceInfo; private readonly IDeviceActionService _deviceActionService; + private readonly string _defaultFolderId; private readonly string _defaultUri; private readonly string _defaultName; private readonly string _defaultUsername; @@ -60,9 +61,10 @@ namespace Bit.App.Pages Init(); } - public VaultAddCipherPage(CipherType type, string defaultUri = null, - string defaultName = null, bool fromAutofill = false, bool doInit = true) + public VaultAddCipherPage(CipherType type, string defaultUri = null, string defaultName = null, + bool fromAutofill = false, bool doInit = true, string defaultFolderId = null) { + _defaultFolderId = defaultFolderId; _type = type; _defaultUri = defaultUri; _defaultName = defaultName; @@ -146,11 +148,19 @@ namespace Bit.App.Pages var folderOptions = new List { AppResources.FolderNone }; Folders = _folderService.GetAllAsync().GetAwaiter().GetResult() .OrderBy(f => f.Name?.Decrypt()).ToList(); + var selectedIndex = 0; + var i = 1; foreach(var folder in Folders) { + if(folder.Id == _defaultFolderId) + { + selectedIndex = i; + } folderOptions.Add(folder.Name.Decrypt()); + i++; } FolderCell = new FormPickerCell(AppResources.Folder, folderOptions.ToArray()); + FolderCell.Picker.SelectedIndex = selectedIndex; // Favorite FavoriteCell = new ExtendedSwitchCell { Text = AppResources.Favorite }; diff --git a/src/App/Pages/Vault/VaultListGroupingsPage.cs b/src/App/Pages/Vault/VaultListGroupingsPage.cs index e6d7837e2..5af457308 100644 --- a/src/App/Pages/Vault/VaultListGroupingsPage.cs +++ b/src/App/Pages/Vault/VaultListGroupingsPage.cs @@ -12,7 +12,6 @@ using Plugin.Settings.Abstractions; using Plugin.Connectivity.Abstractions; using System.Collections.Generic; using System.Threading; -using Bit.App.Enums; using static Bit.App.Models.Page.VaultListPageModel; namespace Bit.App.Pages @@ -57,13 +56,13 @@ namespace Bit.App.Pages public ListView ListView { get; set; } public StackLayout NoDataStackLayout { get; set; } public ActivityIndicator LoadingIndicator { get; set; } - private AddCipherToolBarItem AddCipherItem { get; set; } + private AddCipherToolbarItem AddCipherItem { get; set; } private SearchToolBarItem SearchItem { get; set; } private void Init() { SearchItem = new SearchToolBarItem(this); - AddCipherItem = new AddCipherToolBarItem(this); + AddCipherItem = new AddCipherToolbarItem(this, null); ToolbarItems.Add(SearchItem); ToolbarItems.Add(AddCipherItem); @@ -102,7 +101,7 @@ namespace Bit.App.Pages var addCipherButton = new ExtendedButton { Text = AppResources.AddAnItem, - Command = new Command(() => AddCipher()), + Command = new Command(() => Helpers.AddCipher(this, null)), Style = (Style)Application.Current.Resources["btn-primaryAccent"] }; @@ -135,6 +134,37 @@ namespace Bit.App.Pages SearchItem?.InitEvents(); _filterResultsCancellationTokenSource = FetchAndLoadVault(); + + if(_connectivity.IsConnected && Device.RuntimePlatform == Device.iOS) + { + var pushPromptShow = _settings.GetValueOrDefault(Constants.PushInitialPromptShown, false); + Action registerAction = () => + { + var lastPushRegistration = + _settings.GetValueOrDefault(Constants.PushLastRegistrationDate, DateTime.MinValue); + if(!pushPromptShow || DateTime.UtcNow - lastPushRegistration > TimeSpan.FromDays(1)) + { + _pushNotification.Register(); + } + }; + + if(!pushPromptShow) + { + _settings.AddOrUpdateValue(Constants.PushInitialPromptShown, true); + _userDialogs.Alert(new AlertConfig + { + Message = AppResources.PushNotificationAlert, + Title = AppResources.EnableAutomaticSyncing, + OnAction = registerAction, + OkText = AppResources.OkGotIt + }); + } + else + { + // Check push registration once per day + registerAction(); + } + } } protected override void OnDisappearing() @@ -236,49 +266,12 @@ namespace Bit.App.Pages ((ListView)sender).SelectedItem = null; } - private async void AddCipher() - { - var type = await _userDialogs.ActionSheetAsync(AppResources.SelectTypeAdd, AppResources.Cancel, null, null, - AppResources.TypeLogin, AppResources.TypeCard, AppResources.TypeIdentity, AppResources.TypeSecureNote); - - var selectedType = CipherType.SecureNote; - if(type == AppResources.Cancel) - { - return; - } - else if(type == AppResources.TypeLogin) - { - selectedType = CipherType.Login; - } - else if(type == AppResources.TypeCard) - { - selectedType = CipherType.Card; - } - else if(type == AppResources.TypeIdentity) - { - selectedType = CipherType.Identity; - } - - var page = new VaultAddCipherPage(selectedType); - await Navigation.PushForDeviceAsync(page); - } - private async void Search() { var page = new ExtendedNavigationPage(new VaultSearchCiphersPage()); await Navigation.PushModalAsync(page); } - private class AddCipherToolBarItem : ExtendedToolbarItem - { - public AddCipherToolBarItem(VaultListGroupingsPage page) - : base(() => page.AddCipher()) - { - Text = AppResources.Add; - Icon = "plus.png"; - } - } - private class SearchToolBarItem : ExtendedToolbarItem { public SearchToolBarItem(VaultListGroupingsPage page) diff --git a/src/App/Pages/Vault/VaultSearchCiphersPage.cs b/src/App/Pages/Vault/VaultSearchCiphersPage.cs index 457655f6d..83022843a 100644 --- a/src/App/Pages/Vault/VaultSearchCiphersPage.cs +++ b/src/App/Pages/Vault/VaultSearchCiphersPage.cs @@ -24,15 +24,17 @@ namespace Bit.App.Pages private readonly ISettings _settings; private readonly IAppSettingsService _appSettingsService; private readonly IGoogleAnalyticsService _googleAnalyticsService; + private readonly IDeviceActionService _deviceActionService; private CancellationTokenSource _filterResultsCancellationTokenSource; private readonly bool _favorites = false; private readonly bool _folder = false; private readonly string _folderId = null; private readonly string _collectionId = null; private readonly string _groupingName = null; + private readonly string _uri = null; public VaultSearchCiphersPage(bool folder = false, string folderId = null, - string collectionId = null, string groupingName = null, bool favorites = false) + string collectionId = null, string groupingName = null, bool favorites = false, string uri = null) : base(true) { _folder = folder; @@ -40,6 +42,7 @@ namespace Bit.App.Pages _collectionId = collectionId; _favorites = favorites; _groupingName = groupingName; + _uri = uri; _cipherService = Resolver.Resolve(); _connectivity = Resolver.Resolve(); @@ -48,6 +51,7 @@ namespace Bit.App.Pages _settings = Resolver.Resolve(); _appSettingsService = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); Init(); } @@ -58,9 +62,16 @@ namespace Bit.App.Pages public ListView ListView { get; set; } public SearchBar Search { get; set; } public StackLayout ResultsStackLayout { get; set; } + private AddCipherToolbarItem AddCipherItem { get; set; } private void Init() { + if(!string.IsNullOrWhiteSpace(_uri) || _folder || !string.IsNullOrWhiteSpace(_folderId)) + { + AddCipherItem = new AddCipherToolbarItem(this, _folderId); + ToolbarItems.Add(AddCipherItem); + } + ListView = new ListView(ListViewCachingStrategy.RecycleElement) { IsGroupingEnabled = true, @@ -70,7 +81,7 @@ namespace Bit.App.Pages nameof(Section.Count))), GroupShortNameBinding = new Binding(nameof(Section.Name)), ItemTemplate = new DataTemplate(() => new VaultListViewCell( - (Cipher c) => Helpers.CipherMoreClickedAsync(this, c, false))) + (Cipher c) => Helpers.CipherMoreClickedAsync(this, c, !string.IsNullOrWhiteSpace(_uri)))) }; if(Device.RuntimePlatform == Device.iOS) @@ -187,6 +198,18 @@ namespace Bit.App.Pages } } + protected override bool OnBackButtonPressed() + { + if(string.IsNullOrWhiteSpace(_uri)) + { + return false; + } + + _googleAnalyticsService.TrackExtensionEvent("BackClosed", _uri.StartsWith("http") ? "Website" : "App"); + _deviceActionService.CloseAutofill(); + return true; + } + protected override void OnAppearing() { base.OnAppearing(); @@ -198,6 +221,7 @@ namespace Bit.App.Pages } }); + AddCipherItem?.InitEvents(); ListView.ItemSelected += CipherSelected; Search.TextChanged += SearchBar_TextChanged; Search.SearchButtonPressed += SearchBar_SearchButtonPressed; @@ -214,6 +238,7 @@ namespace Bit.App.Pages base.OnDisappearing(); MessagingCenter.Unsubscribe(_syncService, "SyncCompleted"); + AddCipherItem?.Dispose(); ListView.ItemSelected -= CipherSelected; Search.TextChanged -= SearchBar_TextChanged; Search.SearchButtonPressed -= SearchBar_SearchButtonPressed; @@ -291,8 +316,32 @@ namespace Bit.App.Pages return; } - var page = new VaultViewCipherPage(cipher.Type, cipher.Id); - await Navigation.PushForDeviceAsync(page); + string selection = null; + if(!string.IsNullOrWhiteSpace(_uri)) + { + selection = await DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null, + AppResources.Autofill, AppResources.View); + } + + if(selection == AppResources.View || string.IsNullOrWhiteSpace(_uri)) + { + var page = new VaultViewCipherPage(cipher.Type, cipher.Id); + await Navigation.PushForDeviceAsync(page); + } + else if(selection == AppResources.Autofill) + { + if(_deviceInfoService.Version < 21) + { + Helpers.CipherMoreClickedAsync(this, cipher, !string.IsNullOrWhiteSpace(_uri)); + } + else + { + _googleAnalyticsService.TrackExtensionEvent("AutoFilled", + _uri.StartsWith("http") ? "Website" : "App"); + _deviceActionService.Autofill(cipher); + } + } + ((ListView)sender).SelectedItem = null; } } diff --git a/src/App/Utilities/Helpers.cs b/src/App/Utilities/Helpers.cs index 97bbaa916..6a82d520a 100644 --- a/src/App/Utilities/Helpers.cs +++ b/src/App/Utilities/Helpers.cs @@ -144,5 +144,33 @@ namespace Bit.App.Utilities Resolver.Resolve().CopyToClipboard(copyText); Resolver.Resolve().Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); } + + public static async void AddCipher(Page page, string folderId) + { + var type = await Resolver.Resolve().ActionSheetAsync( + AppResources.SelectTypeAdd, AppResources.Cancel, null, null, AppResources.TypeLogin, + AppResources.TypeCard, AppResources.TypeIdentity, AppResources.TypeSecureNote); + + var selectedType = CipherType.SecureNote; + if(type == AppResources.Cancel) + { + return; + } + else if(type == AppResources.TypeLogin) + { + selectedType = CipherType.Login; + } + else if(type == AppResources.TypeCard) + { + selectedType = CipherType.Card; + } + else if(type == AppResources.TypeIdentity) + { + selectedType = CipherType.Identity; + } + + var addPage = new VaultAddCipherPage(selectedType, defaultFolderId: folderId); + await page.Navigation.PushForDeviceAsync(addPage); + } } }