using System; using System.Linq; using Acr.UserDialogs; using Bit.App.Abstractions; using Bit.App.Controls; using Bit.App.Models.Page; using Bit.App.Resources; using Xamarin.Forms; using XLabs.Ioc; using Bit.App.Utilities; using Plugin.Connectivity.Abstractions; using System.Collections.Generic; using Bit.App.Models; using System.Threading.Tasks; namespace Bit.App.Pages { public class VaultAttachmentsPage : ExtendedContentPage { private readonly ICipherService _cipherService; private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; private readonly IDeviceActionService _deviceActiveService; private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly ITokenService _tokenService; private readonly ICryptoService _cryptoService; private readonly string _loginId; private Cipher _login; private byte[] _fileBytes; private DateTime? _lastAction; private bool _canUseAttachments = true; public VaultAttachmentsPage(string loginId) : base(true) { _loginId = loginId; _cipherService = Resolver.Resolve(); _connectivity = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _deviceActiveService = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); _tokenService = Resolver.Resolve(); _cryptoService = Resolver.Resolve(); Init(); } public ExtendedObservableCollection PresentationAttchments { get; private set; } = new ExtendedObservableCollection(); public ListView ListView { get; set; } public StackLayout NoDataStackLayout { get; set; } public StackLayout AddNewStackLayout { get; set; } public Label FileLabel { get; set; } public ExtendedTableView NewTable { get; set; } public Label NoDataLabel { get; set; } private void Init() { _canUseAttachments = _cryptoService.EncKey != null; SubscribeFileResult(true); var selectButton = new ExtendedButton { Text = AppResources.ChooseFile, Command = new Command(async () => await _deviceActiveService.SelectFileAsync()), Style = (Style)Application.Current.Resources["btn-primaryAccent"], FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button)) }; FileLabel = new Label { Text = AppResources.NoFileChosen, Style = (Style)Application.Current.Resources["text-muted"], FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)), HorizontalTextAlignment = TextAlignment.Center }; AddNewStackLayout = new StackLayout { Children = { selectButton, FileLabel }, Orientation = StackOrientation.Vertical, Padding = new Thickness(20, Helpers.OnPlatform(iOS: 10, Android: 20), 20, 20), VerticalOptions = LayoutOptions.Start }; NewTable = new ExtendedTableView { Intent = TableIntent.Settings, HasUnevenRows = true, NoFooter = true, EnableScrolling = false, EnableSelection = false, VerticalOptions = LayoutOptions.Start, Margin = new Thickness(0, Helpers.OnPlatform(iOS: 10, Android: 30), 0, 0), Root = new TableRoot { new TableSection(AppResources.AddNewAttachment) { new ExtendedViewCell { View = AddNewStackLayout, BackgroundColor = Color.White } } } }; ListView = new ListView(ListViewCachingStrategy.RecycleElement) { ItemsSource = PresentationAttchments, HasUnevenRows = true, ItemTemplate = new DataTemplate(() => new VaultAttachmentsViewCell()), VerticalOptions = LayoutOptions.FillAndExpand }; if(_tokenService.TokenPremium) { ListView.Footer = NewTable; } NoDataLabel = new Label { Text = AppResources.NoAttachments, HorizontalTextAlignment = TextAlignment.Center, FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)), Style = (Style)Application.Current.Resources["text-muted"] }; NoDataStackLayout = new StackLayout { VerticalOptions = LayoutOptions.Start, Spacing = 0, Margin = new Thickness(0, 40, 0, 0) }; var saveToolBarItem = new ToolbarItem(AppResources.Save, null, async () => { if(_lastAction.LastActionWasRecent() || _login == null) { return; } _lastAction = DateTime.UtcNow; if(!_canUseAttachments) { await ShowUpdateKeyAsync(); return; } if(!_connectivity.IsConnected) { AlertNoConnection(); return; } if(_fileBytes == null) { await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.File), AppResources.Ok); return; } _userDialogs.ShowLoading(AppResources.Saving, MaskType.Black); var saveTask = await _cipherService.EncryptAndSaveAttachmentAsync(_login, _fileBytes, FileLabel.Text); _userDialogs.HideLoading(); if(saveTask.Succeeded) { _fileBytes = null; FileLabel.Text = AppResources.NoFileChosen; _userDialogs.Toast(AppResources.AttachementAdded); _googleAnalyticsService.TrackAppEvent("AddedAttachment"); await LoadAttachmentsAsync(); } else if(saveTask.Errors.Count() > 0) { await _userDialogs.AlertAsync(saveTask.Errors.First().Message, AppResources.AnErrorHasOccurred); } else { await _userDialogs.AlertAsync(AppResources.AnErrorHasOccurred); } }, ToolbarItemOrder.Default, 0); Title = AppResources.Attachments; Content = ListView; if(_tokenService.TokenPremium) { ToolbarItems.Add(saveToolBarItem); } if(Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Windows) { ListView.RowHeight = -1; NewTable.RowHeight = -1; NewTable.EstimatedRowHeight = 44; NewTable.HeightRequest = 180; ListView.BackgroundColor = Color.Transparent; ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close)); } } protected async override void OnAppearing() { base.OnAppearing(); ListView.ItemSelected += AttachmentSelected; await LoadAttachmentsAsync(); if(_tokenService.TokenPremium && !_canUseAttachments) { await ShowUpdateKeyAsync(); } } protected override void OnDisappearing() { base.OnDisappearing(); ListView.ItemSelected -= AttachmentSelected; } private async Task LoadAttachmentsAsync() { _login = await _cipherService.GetByIdAsync(_loginId); if(_login == null) { await Navigation.PopForDeviceAsync(); return; } var attachmentsToAdd = _login.Attachments .Select(a => new VaultAttachmentsPageModel.Attachment(a)) .OrderBy(s => s.Name); PresentationAttchments.ResetWithRange(attachmentsToAdd); AdjustContent(); } private void AdjustContent() { if(PresentationAttchments.Count == 0) { NoDataStackLayout.Children.Clear(); NoDataStackLayout.Children.Add(NoDataLabel); NoDataStackLayout.Children.Add(NewTable); Content = NoDataStackLayout; } else { Content = ListView; } } private async void AttachmentSelected(object sender, SelectedItemChangedEventArgs e) { var attachment = e.SelectedItem as VaultAttachmentsPageModel.Attachment; if(attachment == null) { return; } ((ListView)sender).SelectedItem = null; if(!await _userDialogs.ConfirmAsync(AppResources.DoYouReallyWantToDelete, null, AppResources.Yes, AppResources.No)) { return; } _userDialogs.ShowLoading(AppResources.Deleting, MaskType.Black); var saveTask = await _cipherService.DeleteAttachmentAsync(_login, attachment.Id); _userDialogs.HideLoading(); if(saveTask.Succeeded) { _userDialogs.Toast(AppResources.AttachmentDeleted); _googleAnalyticsService.TrackAppEvent("DeletedAttachment"); await LoadAttachmentsAsync(); } else if(saveTask.Errors.Count() > 0) { await _userDialogs.AlertAsync(saveTask.Errors.First().Message, AppResources.AnErrorHasOccurred); } else { await _userDialogs.AlertAsync(AppResources.AnErrorHasOccurred); } } private void AlertNoConnection() { DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok); } private void SubscribeFileResult(bool subscribe) { MessagingCenter.Unsubscribe>(Application.Current, "SelectFileResult"); if(!subscribe) { return; } MessagingCenter.Subscribe>( Application.Current, "SelectFileResult", (sender, result) => { FileLabel.Text = result.Item2; _fileBytes = result.Item1; SubscribeFileResult(true); }); } private async Task ShowUpdateKeyAsync() { var confirmed = await _userDialogs.ConfirmAsync(AppResources.UpdateKey, AppResources.FeatureUnavailable, AppResources.LearnMore, AppResources.Cancel); if(confirmed) { Device.OpenUri(new Uri("https://help.bitwarden.com/article/update-encryption-key/")); } } } }