mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
* Initial commit of new TOTP page * Revert config files from previous commit This reverts commitb02c58e362. * clear extra code and fix build * add tab page * add authentication view cell * add toolbar icons * got the countdown working * enable context loading and vm init * PS-70 Added toggle to quickly filter TOTP cypher items and show their details, Added new text resource * PS-70 removed old authentication tab * removed unnecessary code on vm * fixed formatting * PS-70 Added circular progress to the OTP count down * PS-70 Fixed grid cell width. Added red progress at 20 percent. Refactored circular progress view. * PS-70 Added new props to custom control. * PS-70 show toggle only if it's premium * PS-70 removed unnecessary code * PS-70 add copy to clipboard. * PS-70 show upgrade to premium text on details to free user. * PS-70 added text labels to resource files * PS-70 Renamed TOTP to Totp to have consistency in naming. Removed a11y text of switch because android was overlapping text. * PS-70 added new UI to enter code manually in the QR Code scanner screen. Changed existing labels on scanner screen. * PS-70 Splited totp code to adjust spacing. * PS-70 Added scanner square corner overlay. Added scanning animation. Added scan success animation. * PS-70 let zxing scanner camera feed on until screen is closed. * PS-70 fixed scanner animation for android devices * PS-70 added vibrate permission to manifest. refactored scanpage code. added manual authentication key feature in scanner. * PS-70 fixed totp cell title label font * PS-70 added copy button to totp edit cipher. Added row button when totp is null. * PS-70 changed labels on manual scanner screen * PS-70 Added label on top of button to solve UI bug. * PS-70 Fixed android button overlapping bug by adding button styling to a Frame view and placing a label inside. Fixed Color on scanner page. * PS-70 Added frame styling for iOS, since frame view has different base configuration for android and iOS. * PS-70 fixed font clipping bug on iOS * PS-70 removed shadow for newer versions of android * PS-70 code format * PS-70 removed update to premium uri launch * PS-70 PR fix for AppResource vs code behind generation. * PS-70 changed premium required label. fixed bug when to show premium required label. * [SSG-416] Removed the dashes from free user and just left the Premium subscription required. * [SSG-416] removed unnecessary changes to the TabsPage file * [SSG-416] removed unnecessary using. * [SSG-416] Updated ViewPageViewModel and code refactoring. * [SG-416] Updated scanner mode toggle text color to be inline with figma designs * [SSG-416] Mobile PR Fixes * [SSG-416] Add to remove a11y text from totp toggle because on android it places an helper text next to the switch making it invisible. Also removed from the label because it already reads the text from the label * [SSG-416] run dotnet tool run dotnet-format * [SSG-416] PR fixes * Revert "[SSG-416] PR fixes" This reverts commit2f2b90acee. * [PS-416] Fixed a bug where the item details page was not updating after saving. * [SG-416] Authenticator toggle remake (#2027) * [SG-416] Removed toggle to TOTP. Added on MainPage new entry to go to screen with TOTP codes. Added filter for TOTP codes to be used when searching. * [SG-416] Removed unnecessary code. Added nav back if there is only 1 cipher with totp code and the user removes it. * [SG-416] Run dotnet format tool * [SG-416] PR fixes * [SG-416] PR Fixes. Manifest formatting. Add try catch. Extracted method and added null protection. * [SG-416] Make TOTP codes appear above favourites. * [SG-416] PR fixes. Show error dialog. Co-authored-by: Carlos J. Muentes <42616259+cmuentes@users.noreply.github.com> Co-authored-by: Jacob Fink <jfink@bitwarden.com>
336 lines
11 KiB
C#
336 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Bit.App.Resources;
|
|
using Bit.Core.Abstractions;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Utilities;
|
|
using Xamarin.Forms;
|
|
|
|
namespace Bit.App.Pages
|
|
{
|
|
public partial class CipherDetailsPage : BaseContentPage
|
|
{
|
|
private readonly IBroadcasterService _broadcasterService;
|
|
private readonly ISyncService _syncService;
|
|
private CipherDetailsPageViewModel _vm;
|
|
|
|
public CipherDetailsPage(string cipherId)
|
|
{
|
|
InitializeComponent();
|
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
|
_vm = BindingContext as CipherDetailsPageViewModel;
|
|
_vm.Page = this;
|
|
_vm.CipherId = cipherId;
|
|
SetActivityIndicator(_mainContent);
|
|
|
|
if (Device.RuntimePlatform == Device.iOS)
|
|
{
|
|
_absLayout.Children.Remove(_fab);
|
|
ToolbarItems.Add(_closeItem);
|
|
ToolbarItems.Add(_editItem);
|
|
ToolbarItems.Add(_moreItem);
|
|
}
|
|
else
|
|
{
|
|
_mainLayout.Padding = new Thickness(0, 0, 0, 75);
|
|
ToolbarItems.Add(_attachmentsItem);
|
|
ToolbarItems.Add(_deleteItem);
|
|
}
|
|
}
|
|
|
|
public CipherDetailsPageViewModel ViewModel => _vm;
|
|
|
|
public void UpdateCipherId(string cipherId)
|
|
{
|
|
_vm.CipherId = cipherId;
|
|
}
|
|
|
|
protected override async void OnAppearing()
|
|
{
|
|
base.OnAppearing();
|
|
if (_syncService.SyncInProgress)
|
|
{
|
|
IsBusy = true;
|
|
}
|
|
|
|
_broadcasterService.Subscribe(nameof(CipherDetailsPage), async (message) =>
|
|
{
|
|
try
|
|
{
|
|
if (message.Command == "syncStarted")
|
|
{
|
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
|
}
|
|
else if (message.Command == "syncCompleted")
|
|
{
|
|
await Task.Delay(500);
|
|
Device.BeginInvokeOnMainThread(() =>
|
|
{
|
|
IsBusy = false;
|
|
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
|
{
|
|
var success = data["successfully"] as bool?;
|
|
if (success.GetValueOrDefault())
|
|
{
|
|
var task = _vm.LoadAsync(() => AdjustToolbar());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
else if (message.Command == "selectSaveFileResult")
|
|
{
|
|
Device.BeginInvokeOnMainThread(() =>
|
|
{
|
|
var data = message.Data as Tuple<string, string>;
|
|
if (data == null)
|
|
{
|
|
return;
|
|
}
|
|
_vm.SaveFileSelected(data.Item1, data.Item2);
|
|
});
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
}
|
|
});
|
|
await LoadOnAppearedAsync(_scrollView, true, async () =>
|
|
{
|
|
var success = await _vm.LoadAsync(() => AdjustToolbar());
|
|
if (!success)
|
|
{
|
|
await Navigation.PopModalAsync();
|
|
}
|
|
}, _mainContent);
|
|
}
|
|
|
|
protected override void OnDisappearing()
|
|
{
|
|
base.OnDisappearing();
|
|
IsBusy = false;
|
|
_vm.StopCiphersTotpTick().FireAndForget();
|
|
_broadcasterService.Unsubscribe(nameof(CipherDetailsPage));
|
|
}
|
|
|
|
private async void PasswordHistory_Tapped(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
await Navigation.PushModalAsync(new NavigationPage(new PasswordHistoryPage(_vm.CipherId)));
|
|
}
|
|
}
|
|
|
|
private async void EditToolbarItem_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
if (_vm.IsDeleted)
|
|
{
|
|
if (await _vm.RestoreAsync())
|
|
{
|
|
await Navigation.PopModalAsync();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
await Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(_vm.CipherId)));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EditButton_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
EditToolbarItem_Clicked(sender, e);
|
|
}
|
|
|
|
private async void Attachments_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
var page = new AttachmentsPage(_vm.CipherId);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
}
|
|
|
|
private async void Share_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
var page = new SharePage(_vm.CipherId);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
}
|
|
|
|
private async void Delete_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
if (await _vm.DeleteAsync())
|
|
{
|
|
await Navigation.PopModalAsync();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void Collections_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
var page = new CollectionsPage(_vm.CipherId);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
}
|
|
|
|
private async void Clone_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
}
|
|
|
|
private async void More_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (!DoOnce())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var options = new List<string> { AppResources.Attachments };
|
|
if (_vm.Cipher.OrganizationId == null)
|
|
{
|
|
options.Add(AppResources.Clone);
|
|
options.Add(AppResources.MoveToOrganization);
|
|
}
|
|
else
|
|
{
|
|
options.Add(AppResources.Collections);
|
|
}
|
|
|
|
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
|
AppResources.Delete, options.ToArray());
|
|
|
|
if (!await _vm.PromptPasswordAsync())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (selection == AppResources.Delete)
|
|
{
|
|
if (await _vm.DeleteAsync())
|
|
{
|
|
await Navigation.PopModalAsync();
|
|
}
|
|
}
|
|
else if (selection == AppResources.Attachments)
|
|
{
|
|
var page = new AttachmentsPage(_vm.CipherId);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
else if (selection == AppResources.Collections)
|
|
{
|
|
var page = new CollectionsPage(_vm.CipherId);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
else if (selection == AppResources.MoveToOrganization)
|
|
{
|
|
var page = new SharePage(_vm.CipherId);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
else if (selection == AppResources.Clone)
|
|
{
|
|
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
|
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
}
|
|
}
|
|
|
|
private async void Close_Clicked(object sender, System.EventArgs e)
|
|
{
|
|
if (DoOnce())
|
|
{
|
|
await Navigation.PopModalAsync();
|
|
}
|
|
}
|
|
|
|
private void AdjustToolbar()
|
|
{
|
|
if (_vm.Cipher == null)
|
|
{
|
|
return;
|
|
}
|
|
_editItem.Text = _vm.Cipher.IsDeleted ? AppResources.Restore :
|
|
AppResources.Edit;
|
|
if (_vm.Cipher.IsDeleted)
|
|
{
|
|
_absLayout.Children.Remove(_fab);
|
|
}
|
|
if (Device.RuntimePlatform != Device.Android)
|
|
{
|
|
return;
|
|
}
|
|
if (_vm.Cipher.OrganizationId == null)
|
|
{
|
|
if (ToolbarItems.Contains(_collectionsItem))
|
|
{
|
|
ToolbarItems.Remove(_collectionsItem);
|
|
}
|
|
if (!ToolbarItems.Contains(_cloneItem))
|
|
{
|
|
ToolbarItems.Insert(1, _cloneItem);
|
|
}
|
|
if (!ToolbarItems.Contains(_shareItem))
|
|
{
|
|
ToolbarItems.Insert(2, _shareItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ToolbarItems.Contains(_cloneItem))
|
|
{
|
|
ToolbarItems.Remove(_cloneItem);
|
|
}
|
|
if (ToolbarItems.Contains(_shareItem))
|
|
{
|
|
ToolbarItems.Remove(_shareItem);
|
|
}
|
|
if (!ToolbarItems.Contains(_collectionsItem))
|
|
{
|
|
ToolbarItems.Insert(1, _collectionsItem);
|
|
}
|
|
}
|
|
if (_vm.Cipher.IsDeleted && !ToolbarItems.Contains(_editItem))
|
|
{
|
|
ToolbarItems.Insert(1, _editItem);
|
|
}
|
|
}
|
|
}
|
|
}
|