mirror of
https://github.com/bitwarden/mobile
synced 2026-01-20 09:23:50 +00:00
[PM-3349] [PM-3350] MAUI Migration Initial (#2806)
* PM-3349 PM-3350 MAUI Migration Initial * PM-3349 PM-3350 MAUI Migration fix nullable exception bindings and AsyncCommand canExecute null exception * PM-3349 PM-3350 MAUI Migration fix nullable bindings and fallbacks * PM-3349: Android Added CustomTabbedPageHandler for Android to handle the tab "reselection" for PopToRoot. Commented support for Windows in App.csproj Disabled Interpreter on Android to avoid very slow app in Debug (during Login for example) Added some null checks that were causing crashes (on GeneratorPageVM and PickerVM) Minor TabsPage cleanup * TabBarEffect removed and it's behavior is now taken care of by CustomTabbedPageHandler * PM-3349 PM-3350 Add null checks on CipherDetailsPageVM to avoid crash opening Secure Notes. * PM-3349 PM-3350 MAUI Migration Start iOS extensions * Changes to solution to hopefully fix Config Mappings * PM-3349 Removed Deploy from iOS.Autofill to allow running Android Changed MainApplication SpecialFolder.Personal to SpecialFolder.LocalApplicationData * PM-3350 MAUI Migration Fix iOS Autofill extension * PM-3349 Changed UseMauiApp init so that Android Handlers still get added * PM-3349 Implemented HybridWebViewHandler for Android which enables 2nd factor auth flows Ensured CustomTabbedPageHandler had it's DisconnectHandler called Some minor code upgrades of older obsolete Xamarin Forms code. * PM-3349 Implemented HybridWebViewHandler for iOS * hardcoded AccountViewCell Avatar image to 40x40 to avoid current iOS/Android bugs where they fill much larger space. * PM-3349 PM-3350 Added (migrated) CustomNavigationHandler (which should partially fix the AvatarIcon in the NavBar in iOS) Added (migrated) CustomContentPageHandler (which should mostly place the AvatarIcon in the navBar in the correct place for iOS) Added Task.Delay (workaround) to allow the Avatar to load in iOS on the LoginPage Added workaround for iOS bug with the toolbar size (more info in comment in AvatarImageSource.cs) Went through the AccountViewCell MAUI-Migration comments. (and deleted/added more comments as needed) Migrated some Device calls to DeviceInfo and MainThread Added (migrated) CustomTabbedHandler (for managing the iOS TabBar) * PM-3349 Replaced the FabShadowEffect with the new MAUI Shadow to fix the buggy shadows on the Android Fab Button. * PM-3349 ToolbarHandler created for setting text on Android go back buttons. * PM-3350 Migrated the CustomViewCellRenderer for iOS * PM-3350 Removed ButtonHandlerMappings and some other code related with fonts as MAUI is taking care of Accessibility and no custom code should be needed Migrated SelectableLabelRenderer to Handler Cleaned LabelHandlerMappings and added logic to migrate the CustomLabelRenderer * Enabled argon2Id for iOS * PM-3349 Added Argon libraries for Android minor change to gitignore so that the Argon x86 lib is not ignored on the Android platform * PM-3350 Migrated some Device to DeviceInfo and added temporary workaround with some comments to be able to see the Generated Password on iOS * PM-3350 Added some missing images in iOS * PM-3349 PRM-3350 Replaced XZing with Camera.MAUI for QRCodes * Checked some [MAUI-Migration] and deleted when it's working as intended. SearchBarHandlerMapping: IME options working as intended SliderHandlerMappings: The MAUI "replacement" for Color.Default seems to be White so the old use case doesn't seem to be needed anymore. * PM-3350 Checked some [MAUI-Migration] and changed as needed. TimePickerHandlerMappings: Remove old code for forcing the Wheel. After testing without it wheel picker is still used so this code shouldn't be needed anymore. AppDelegate.ContinueUserActivity: Uncommented and changed the iOS ContinueUserActivity. It needs to call Platform.ContinueUserActivity according with Xamarin Essentials migration docs. * PM-3349 Fixed white tint color not appearing on images added as MAU IImage SVG PM-3349 PM-3350 Fix for Avatar text not adjusting to white/black color correctly * PM-3350 Removed MAUI Splash Screen. Fixed iOS Privacy Screen logo (hardcoded image to avoid it getting cropped) * PM-3350 Quick workaround to allow 2nd factor auth to not get stuck in iOS in modals. Updated some older "Device" code to the newer MAUI code. * PM-3350 Removed duplicate reference to LaunchScreen.storyboard * PM-3349 PM-3350 Minor change to HomePage to set fixed Image height otherwise it takes more space than it did in the old Xamarin Forms app. Added HIdeSoftInputOnTapped on several pages (the ones with Entry controls) to allow hiding the keyboard when tapping "outside" of it. (just like we did in Xamarin Forms app) * PM-3350 Added Scrollview on HomePage so that the "Create account" button can be accessed in smaller devices like iPhone SE. * PM-3349 Added Handler that enables the ExtendedDatePicker to get IsFocused events in Android. This is a workaround for fixing the current bug where it's not possible to select the "current day" in the expiration date of a Send. Fix for TimePicker not displaying default Time Value Updated some "Device" code to the new MAUI "DeviceInfo" * PM-3349 PM-3350 Migrated IconLabelButton Frames to Borders to fix issue with TapGestureRecognizer in Android Also fixed some minor "styles" for normal Button and IconLabelButton (both Android and iOS) * PM-3349 Fix for TabGestureRecognizer not working inside the StackLayout area of IconLabelButton * PM-3349 Fix for Android buttons having all letters in Caps * PM-3349 PM-3350 Started using OnNavigatedTo/From instead of On(Dis)Appearing for LoginPage and LoginSSOPage to avoid the "Modal loading" issues in iOS Also had to add IsInitialized logic to these pages because OnNavigatedTo can be called twice in some scenario. Some minor migrations of Device to DeviceInfo was also done * PM-3350 Fixed iOS extensions (iOS.Extension and iOS.ShareExtension) to load and commented argon2id from debug configuration until we have the .a compiled again with the new platform/arch * PM-3350 Added configurations for Release mode (no FDroid yet) * PM-3349 PM-3350 Migrated remaining AutomationProperties to SemanticProperties. All 'IsInAccessibleTree="True"' were deleted. 'IsInAccessibleTree="False"' were kept and stayed in code. * PM-3349 PM-3350 Changed binding set for CipherViewCell so it updates accordingly * PM-3349 PM-3350 Changed AccountViewCell and its binding to be directly against the ViewModel * PM-3349 Fix for HTML Label on Android. Color hex doesn't need to be cropped anymore. * PM-3350 Fix for colored html text on iOS * PM-3349 PM-3350 Added the partial MAUI Community Toolkit implementation for TouchEffect. This is a temporary solution until they finalize this and add it to their nuget package. This allows implementing the LongPressCommand in AccountSwitchingOverlay and also have the "Ripple effect" animation when touching an item in Android * PM-3349 PM-3350 Changed SendViewCell and its binding to be directly against the ViewModel * PM-3350 Fixed iOS Share extension lazy views loading and an issue with the avatar loading. Also discovered issue with TapGestureRecognizer not working on MAUI Embedding * PM-3350 Fixed iOS Extensions navigation to several pages and improved avoiding duplicate calls to OnNavigatedTo * PM-3350 Updated PCL Crypto to latest alpha version to fix "Dll not found NCrypt" issue * PM-3350 Removed workaround for iOS issue with Avatar icon as it's now fixed in latest .Net8 release. * PM-3349 PM-3350 Removed AsyncCommand "wrapper" and added AsyncRelayCommand directly in all ViewModels that were using the other one. * PM-3350 Added watchOS app to main project and fixed some csproj conditions for runtime identifiers on iOS. * PM-3350 Fixed/Updated all MAUI-Migration TODOs * PM-3350 Fixed account toolbar item and TitleView on SendAddOnlyPage, also removed comments on AvatarImageSource given the workaround is not needed anymore to draw the image successfully. * PM-3350 Updated AppCenter package to latest version 5.0.3 and updated some things into MAUI style * PM-3350 Added workaround for iOS Avatar icon again. * PM-3349 Added workaround for Android to avoid issues with setting MainPage when app is in background. They are now kept on a Queue to be executed after the app has resumed. Updated some things on App.xaml.cs to the new MAUI style * PM-3349 PM-3350 Fixed issue where creating an account with weak/exposed password would get stuck after the Captcha (if a captcha is shown) Changed App.xaml.cs NavigateImpl to be private * PM-3349 Started to configure build.yml for MAUI Android * PM-3349 build.yml update paths for MAUI Android * PM-3349 build.yml commented verify format and just set qa as variant on MAUI Android for faster checks on CI * PM-3349 PM-3350 build.cake updated paths * PM-3349 build.yml updated env helpers variables and set specific csproj to build on Android so not to build iOS extensions * PM-3349 build.yml add Android "prod" variant * PM-3350 build.yml updated iOS build and ignore Android build to try the CI faster * PM-3350 build.yml changed nuget restore for dotnet restore on iOS build to fix issue on restoring due to msbuild * PM-3350 build.yml Upgraded iOS build to run on macos-13 image which has XCode 15, and set the XCode 15 version as currently the default one is 14.x * PM-3350 build.yml try to fix ILLINK warnings and changed image to be macos-13-arm64 to see if the build is faster * PM-3350 build.yml changed image back to be macos-13 to see if the build is faster * PM-3350 Added Document.Build.props to disable trimming on publish * PM-3350 build.yml disable trimming on publish so it's faster * PM-3350 added linkskip for iOS csprojs * PM-3350 iOS projs disable linking and set Newstandkit as weak framework * Update build.yml disabling iOS job to avoid long running process of publish until we can fix that * PM-3349 PM-3350 Workaround to fix issues with text getting cropped/truncated when a Label has both Multiline and LinebreakMode set * PM-3349 build.yml enabled android build workflow * PM-3349 build.yml configured FDROID job for MAUI * PM-3350 iOS extensions TapGestureRecognizer try Window workaround * PM-3350 iOS applied workaround on the iOS Autofill and Share extension to maui embed the navigation page with its content page in the Window * PM-3349 PM-3350 Added workaround for More Options to work on Search and Groupings Page Updated some code to MAUI Style also * PM-3349 PM-3350 Added the ability for users to press "Continue" button as a fallback when using the Yubikey if the "SubmitCommand" doesn't trigger automatically. * PM-3349 PM-3350 Fix for text getting cut/truncated in both account switcher and ciphers/search lists Issue is due to MAUI but can be avoided by using slightly different layout * PM-3350 iOS updated CFBundlerShortVersionString to latest one 2023.10.1 * PM-3350 fix build.yml Bitwarden.ipa AppStore exported file * PM-3350 build.yml added step to validate app for submitting into App Store and have better logs of it * PM-3350 build.yml Added several fixes like not using MtouchUseLLVM on the iOS builds to fix they taking forever to build and some changes on the automation CI to do a debug build for the moment * PM-3350 Improved MTouch linking and extra args on iOS related csprojs * PM-3349 PM-3350 Added MAUI label on self-host settings and on about settings to differentiate from XF app * PM-3349 PM-3350 build.yml uncommented jobs so we have a more complete workflow * PM-3349 PM-3350 Minor change: removed unneeded HorizontalTextAlignment from Label. * PM-3349 Replaced CrossCurrentActivity plugin with MAUI internal CurrentActivity * PM-3350 Fix iOS extensions navigation and Window/RootViewController handling for TapGestureRecognizer to work * PM-3350 Cleared left ClipLogger from the iOS extensions debug logging. * PM-3349 PM-3350 Refactored cipher bindings to have a simpler approach reusing a new CipherItemViewModel to avoid unwanted issues in the app * PM-3349 Added base structure for avoiding Android Autofill crash. This workaround works but it's not complete as it can't handle the entire workflow when showing CipherSelectionPAge (like checking if it should show LockPage) * PM-3350 Bumped iOS version * PM-3350 Changed linker to use default mode given that "Full" is presenting some problems as the linker is stripping things it shouldn't and we're trying to solve it. So for now we will use the mode "Link SDK assemblies only" so QA can test. * PM-3349 Fix for app crashing on Android when Dark mode is enabled Removed unused button style for android * Proof of concept for having multiple window in Android for autofill support and navigating with the help of an Extended splash page. * PM-3350 Fix crash on Release by adding Interpreter on iOS and also adding System.Security.Cryptography to be ignored by the linker * PM-3350 Apply Cryptography TrimmerRootAssembly only to iOS * PM-3350 Updated Plugin.Fingerprint so biometrics work * Update .github/workflows/build.yml setup-xcode commit hash Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> * PM-3349 PM-3350 Enabled argon2id and fixed one issue with the Uris when getting the icon image * PM-3349 Upgraded Android targetSdkVersion to 34 * minor change (public to private fields) * minor improvemments on autofill-redirect * PM-3349 Commented the Deploy step for Android job given that we're using the hotfix-rc branch for testing iOS on TestFlight * PM-3349 Uncommented the Deploy step for Android job * PM-3349 Ensure "_isResumed=true" is set on App.xaml.cs:Bootstrap * Reusing App.xaml.cs Navigation for the Android RedirectPage Some other cleanup and changes * Improved autofill workaround to better handle switching between windows. * PM-3349 minor fix to add space in HomePage between the region picker labels. * Added some comments and improvemments. * PM-3349 Added Window events unsubscription of events. Also changed code to avoid potentially having multiple autofillwindow * PM-3349 Minor ui fix (space between buttons in delete account page) * initial commit of android credential provider service (wip) * Revert "initial commit of android credential provider service (wip)" This reverts commit6011b63958. * PM-3350 Fix for Delete Account buttons on iOS * PM-3349 PM-3350 Changed search icon used in app to avoid issue with icon size on iOS * PM-3349 Added custom window so that we can always get the current Active Window. This is used to support the Android Autofil and multi-window scenarios. * PM-3349 Fix for icon and text spacing in some list items * PM-3349 Minor aligment improvemment for region selection in HomePage * PM-3349 Changed the "track color" for the Android switch so that the color is different from the "thumb" * PM-3350 Updated version to 2024.1.0 on iOS * PM-3349 Fix Picker selection style by doing a custom PickerHandler for Android which uses SetSingleChoiceItems(...) to provide with the appropriate UI * PM-3350 Updated MauiVersion to 8.0.4-nightly.* to have the TapGestureRecognizer fix applied. This is done on the Directory.Build.props so we don't have to change it on every csproj. Also removed the workaround of TapGestureHack and fix the Show environment picker to work on the extensions as well. * PM-3350 Added nuget.config so we add the nuget package source for MAUI Nightly builds * Bump main iOS version * PM-3350 Removed "iOS" old folder project that has been moved into the MAUI Single app project. * PM-3350 Improved code safety adding a lot of try...catch and logging throughout the app. Also made the invoking on main thread safer on several places of the app. Additionally, on the GroupingsPageViewModel changed the code removing the old Xamarin hack and just using Replace directly instead of Clearing first to see if that fixes the crash we're having sometimes on the app. * PM-3350 PM-3349 Updated Unit Test projects to NET 8.0 and fixed it to work with Core project reference. Also fixed a test that was breaking due to CIpherKey creation being wrong. Added "UT" as a constant to add when building/running Core.Test project so we have something on the context that tells us that is for a UT. With this I had to remove FFImageLoading on UT context because it doesn't support NET 8.0 * PM-3350 PM-3349 Updated Readme with MAUI and main branch * PM-3350 PM-3349 Enable running Core tests * PM-3350 Fix build.yml format * PM-3349 Fix navigation when coming from autofill with Accessibility Services enabled. The user was getting into Home page instead of where they were, with this workaround the app navigates as if the account has been switched, leaving the user as closely as possible to where they were, basically on the first screen for the current state of the user. * PM-3350 PM-3349 Added property to Directory.Build.props to enable Unit Testing globally so Test runners work * Improve TOTP scan performance on Android * Move Android camera/scan changes to xaml * PM-3350 Testing UseInterpreter false on CI build * PM-3350 Enabled back UseInterpreter on iOS Release given that it crashes on startup without it. * PM-3349 PM-3350 Improved code safety with try...catch, better invoke on main thread and better null handling. * PM-3349 PM-3350 Updated XCode version on build.yml to 15.1 * PM-3350 Removed TapGesture Window MAUI hack from iOS.Extension and iOS.ShareExtension * PM-3350 Fixed CancellationTokenSource proper disposal * PM-3350 Fix Avatar toolbar icon on extensions to load properly and to take advantage of using directly SkiaSharp to do the native conversion to UIImage. Also improved the toolbar item so that size is set appropriately. * PM-3349 PM-3350 Fix external link icon * PM-3350 Added new style to prevent spell check and text prediction * Fix merge from main * PM-3350 Commented event collection upload on the timer and when sending the app to background to see if that prevents the app from crashing on release mode. * PM-3350 Added check for state migration version before trying to migrate LiteDB values into Prefs when there's no need to and that may be inducing crashes on backgrounded iOS apps. * PM-3350 Try to disable Interpreter to have better crash knowledge. This time testing if avoiding loading the argon2id lib we're able to not use the interpreter. * PM-5928 Fix circle animation to be shown on verification codes list on each item * PM-3350 Go back to use Interpreter and added some Directory.Build.props to easily change Codesign properties and also include/exclude iOS extensions / WatchOS from the build. * PM-3350 Enabled iOS extensions and WatchOS app to be included based on the Directory.Build.props * PM-3350 Go back to include argon2id and interpreter * Removing error/loading placeholders of icons on the cells to see if that is causing the background crash on iOS; so we can test this in TestFlight * [PM-5910] Workaround for for sliding elements in Duo 2FA flow (#2967) * workaround for sliding elements in duo 2fa flow * restrict workaround to Android * restrict workaround to Android * Revert "restrict workaround to Android" This reverts commitc2753d5dc4. * Revert "restrict workaround to Android" This reverts commit69688cfb98. * PM-5902 fix for account switcher not dismissing when tapping outside (#2974) * PM-3350 Fix iossimulator-x64 argon2id load so we can test on simulators and also made easier to maintain loading the argon2id library on the iOS projects by setting a general Directory.Build.props that is shared. * PM-5903 Changed App.xaml.cs SetOption to only update the needed properties instead of replacing the existing Options object which would cause the AccountSwitcher button bug (#2973) * [PM-5896] Fix MAUI iOS Background crash due to lock files on suspension (#2969) * PM-5896 Fix background crash on iOS due to lock files when app gets suspended. Changed loading and error placeholders of the CachedImage to not be used and use default icon of IconLabel instead changing visibility. * PM-5896 Changed methods to be protected so that they don't get removed by the linker. * PM-5896 Added stub class and references to it so to have stronger references to Icon_Success and Icon_Error so the linker doesn't remove them. * PM-3349 Removed commented code from build.yml regarding FDroid that is not needed anymore. * PM-6077 Separated Android and iOS HybridWebViewHandler so that it can be used on iOS.Core (#2983) * [PM-5907] Fix for incorrect TOTP white text color on label when using light theme on iOS (#2982) * PM-5907 workaround for incorrect textcolor when programmatically changing text on Entry * Update src/Core/Pages/Vault/CipherAddEditPage.xaml.cs Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> --------- Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> * [PM-5906] Fix for incorrect Send MaxAccess white text color on label when using light theme on iOS (#2981) * PM-5906 workaround for incorrect textcolor when programmatically changing text on Entry * Update src/Core/Pages/Send/SendAddEditPage.xaml.cs Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> --------- Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> * PM-3349 PM-3350 Fixed Unit tests because of referencing FFImageLoading when it's not possible --------- Co-authored-by: Dinis Vieira <dinisvieira@outlook.com> Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: mpbw2 <59324545+mpbw2@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
f30158adf5
commit
39a34bd8c4
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IAccountsManager
|
||||
{
|
||||
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
||||
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
||||
Task StartDefaultNavigationFlowAsync(Action<AppOptions> appOptionsAction);
|
||||
Task LogOutAsync(string userId, bool userInitiated, bool expired);
|
||||
Task PromptToSwitchToExistingAccountAsync(string userId);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface INavigationParams { }
|
||||
|
||||
public interface IAccountsManagerHost
|
||||
{
|
||||
Task SetPreviousPageInfoAsync();
|
||||
void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
|
||||
Task UpdateThemeAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IDeepLinkContext
|
||||
{
|
||||
bool OnNewUri(Uri uri);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Utilities.Prompts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IDeviceActionService
|
||||
{
|
||||
string DeviceUserAgent { get; }
|
||||
DeviceType DeviceType { get; }
|
||||
int SystemMajorVersion();
|
||||
string SystemModel();
|
||||
string GetBuildNumber();
|
||||
|
||||
void Toast(string text, bool longDuration = false);
|
||||
Task ShowLoadingAsync(string text);
|
||||
Task HideLoadingAsync();
|
||||
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
||||
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
||||
bool autofocus = true, bool password = false);
|
||||
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config);
|
||||
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
||||
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
||||
|
||||
bool SupportsFaceBiometric();
|
||||
Task<bool> SupportsFaceBiometricAsync();
|
||||
bool SupportsNfc();
|
||||
bool SupportsCamera();
|
||||
bool SupportsFido2();
|
||||
bool SupportsAutofillServices();
|
||||
bool SupportsInlineAutofill();
|
||||
bool SupportsDrawOver();
|
||||
|
||||
bool LaunchApp(string appName);
|
||||
void RateApp();
|
||||
void OpenAccessibilitySettings();
|
||||
void OpenAccessibilityOverlayPermissionSettings();
|
||||
void OpenAutofillSettings();
|
||||
long GetActiveTime();
|
||||
void CloseMainApp();
|
||||
float GetSystemFontSizeScale();
|
||||
Task OnAccountSwitchCompleteAsync();
|
||||
Task SetScreenCaptureAllowedAsync();
|
||||
void OpenAppSettings();
|
||||
void CloseExtensionPopUp();
|
||||
string GetAutofillAccessibilityDescription();
|
||||
string GetAutofillDrawOverDescription();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface ILocalizeService
|
||||
{
|
||||
CultureInfo GetCurrentCultureInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Format date using device locale.
|
||||
/// Needed for iOS as it provides locales unsupported in .Net
|
||||
/// </summary>
|
||||
string GetLocaleShortDate(DateTime? date);
|
||||
|
||||
/// <summary>
|
||||
/// Format time using device locale.
|
||||
/// Needed for iOS as it provides locales unsupported in .Net
|
||||
/// </summary>
|
||||
string GetLocaleShortTime(DateTime? time);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IPasswordRepromptService
|
||||
{
|
||||
string[] ProtectedFields { get; }
|
||||
|
||||
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
|
||||
|
||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IPushNotificationListenerService
|
||||
{
|
||||
Task OnMessageAsync(JObject values, string device);
|
||||
Task OnRegisteredAsync(string token, string device);
|
||||
void OnUnregistered(string device);
|
||||
void OnError(string message, string device);
|
||||
Task OnNotificationTapped(BaseNotificationData data);
|
||||
Task OnNotificationDismissed(BaseNotificationData data);
|
||||
bool ShouldShowNotification();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface IPushNotificationService
|
||||
{
|
||||
bool IsRegisteredForPush { get; }
|
||||
Task<bool> AreNotificationsSettingsEnabledAsync();
|
||||
Task<string> GetTokenAsync();
|
||||
Task RegisterAsync();
|
||||
Task UnregisterAsync();
|
||||
void SendLocalNotification(string title, string message, BaseNotificationData data);
|
||||
void DismissLocalNotification(string notificationId);
|
||||
}
|
||||
}
|
||||
@@ -1,441 +1,260 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<RootNamespace>Bit.App</RootNamespace>
|
||||
<AssemblyName>BitwardenApp</AssemblyName>
|
||||
<Configurations>Debug;Release;FDroid</Configurations>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
|
||||
|
||||
<!-- Uncomment to also build for Windows platform.-->
|
||||
<!--<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>-->
|
||||
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Bit.App</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<SingleProject>true</SingleProject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<!-- Display name -->
|
||||
<ApplicationTitle>Bitwarden</ApplicationTitle>
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">com.8bit.bitwarden</ApplicationId>
|
||||
<ApplicationId Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">com.x8bit.bitwarden</ApplicationId>
|
||||
<ApplicationIdGuid>ccf4766c-a36c-4647-900c-0ea7d323ccc6</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
|
||||
|
||||
<ForceSimulatorX64ArchitectureInIDE>true</ForceSimulatorX64ArchitectureInIDE>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants Condition=" '$(CustomConstants)' != '' ">$(DefineConstants);$(CustomConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android|AnyCPU'">
|
||||
<AndroidEnableMultiDex>True</AndroidEnableMultiDex>
|
||||
<UseInterpreter>False</UseInterpreter>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.3" />
|
||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.6" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
|
||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2612" />
|
||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||
<PackageReference Include="MessagePack" Version="2.4.59" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Pages\Accounts\EnvironmentPage.xaml.cs">
|
||||
<DependentUpon>EnvironmentPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\HintPage.xaml.cs">
|
||||
<DependentUpon>HintPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\LockPage.xaml.cs">
|
||||
<DependentUpon>LockPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\TwoFactorPage.xaml.cs">
|
||||
<DependentUpon>TwoFactorPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\RegisterPage.xaml.cs">
|
||||
<DependentUpon>RegisterPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\LoginPage.xaml.cs">
|
||||
<DependentUpon>LoginPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Generator\GeneratorPage.xaml.cs">
|
||||
<DependentUpon>GeneratorPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Generator\GeneratorHistoryPage.xaml.cs">
|
||||
<DependentUpon>GeneratorHistoryPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Settings\AutofillPage.xaml.cs">
|
||||
<DependentUpon>AutofillPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Settings\ExtensionPage.xaml.cs">
|
||||
<DependentUpon>ExtensionPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Settings\FolderAddEditPage.xaml.cs">
|
||||
<DependentUpon>FolderAddEditPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Settings\FoldersPage.xaml.cs">
|
||||
<DependentUpon>FoldersPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Settings\ExportVaultPage.xaml.cs">
|
||||
<DependentUpon>ExportVaultPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\AttachmentsPage.xaml.cs">
|
||||
<DependentUpon>AttachmentsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\AutofillCiphersPage.xaml.cs">
|
||||
<DependentUpon>AutofillCiphersPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\CollectionsPage.xaml.cs">
|
||||
<DependentUpon>CollectionsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\ScanPage.xaml.cs">
|
||||
<DependentUpon>ScanPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\SharePage.xaml.cs">
|
||||
<DependentUpon>SharePage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\CiphersPage.xaml.cs">
|
||||
<DependentUpon>CiphersPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\PasswordHistoryPage.xaml.cs">
|
||||
<DependentUpon>PasswordHistoryPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\CipherDetailsPage.xaml.cs">
|
||||
<DependentUpon>CipherDetailsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\CipherAddEditPage.xaml.cs">
|
||||
<DependentUpon>CipherAddEditPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Settings\SettingsPage\SettingsPage.xaml.cs">
|
||||
<DependentUpon>SettingsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Vault\GroupingsPage\GroupingsPage.xaml.cs">
|
||||
<DependentUpon>GroupingsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\LoginSsoPage.xaml.cs">
|
||||
<DependentUpon>LoginSsoPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\SetPasswordPage.xaml.cs">
|
||||
<DependentUpon>ResetMasterPasswordPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Send\SendGroupingsPage\SendGroupingsPage.xaml.cs">
|
||||
<DependentUpon>SendGroupingsPage.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
|
||||
<Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs">
|
||||
<DependentUpon>LoginPasswordlessPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Pages\Accounts\LoginPasswordlessRequestPage.xaml.cs">
|
||||
<DependentUpon>LoginPasswordlessRequestPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Lists\" />
|
||||
<Folder Include="Lists\ItemLayouts\" />
|
||||
<Folder Include="Lists\DataTemplateSelectors\" />
|
||||
<Folder Include="Lists\ItemLayouts\CustomFields\" />
|
||||
<Folder Include="Lists\ItemViewModels\" />
|
||||
<Folder Include="Lists\ItemViewModels\CustomFields\" />
|
||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||
<Folder Include="Utilities\AccountManagement\" />
|
||||
<Folder Include="Controls\DateTime\" />
|
||||
<Folder Include="Controls\IconLabelButton\" />
|
||||
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
||||
<Folder Include="Utilities\Automation\" />
|
||||
<Folder Include="Utilities\Prompts\" />
|
||||
<Folder Include="Controls\Settings\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Styles\Black.xaml.cs">
|
||||
<DependentUpon>Black.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Styles\Nord.xaml.cs">
|
||||
<DependentUpon>Nord.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Styles\Variables.xaml.cs">
|
||||
<DependentUpon>Variables.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Styles\Light.xaml.cs">
|
||||
<DependentUpon>Light.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Styles\Dark.xaml.cs">
|
||||
<DependentUpon>Dark.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Styles\iOS.xaml.cs">
|
||||
<DependentUpon>iOS.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Styles\Android.xaml.cs">
|
||||
<DependentUpon>Android.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
||||
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.da.Designer.cs">
|
||||
<DependentUpon>AppResources.da.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.de.Designer.cs">
|
||||
<DependentUpon>AppResources.de.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>AppResources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.es.Designer.cs">
|
||||
<DependentUpon>AppResources.es.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.fi.Designer.cs">
|
||||
<DependentUpon>AppResources.fi.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.fr.Designer.cs">
|
||||
<DependentUpon>AppResources.fr.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.hi.Designer.cs">
|
||||
<DependentUpon>AppResources.hi.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.hr.Designer.cs">
|
||||
<DependentUpon>AppResources.hr.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.hu.Designer.cs">
|
||||
<DependentUpon>AppResources.hu.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.id.Designer.cs">
|
||||
<DependentUpon>AppResources.id.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.it.Designer.cs">
|
||||
<DependentUpon>AppResources.it.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.ja.Designer.cs">
|
||||
<DependentUpon>AppResources.ja.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.nl.Designer.cs">
|
||||
<DependentUpon>AppResources.nl.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.pl.Designer.cs">
|
||||
<DependentUpon>AppResources.pl.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.pt-BR.Designer.cs">
|
||||
<DependentUpon>AppResources.pt-BR.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.pt-PT.Designer.cs">
|
||||
<DependentUpon>AppResources.pt-PT.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.ro.Designer.cs">
|
||||
<DependentUpon>AppResources.ro.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.ru.Designer.cs">
|
||||
<DependentUpon>AppResources.ru.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.sk.Designer.cs">
|
||||
<DependentUpon>AppResources.sk.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.sv.Designer.cs">
|
||||
<DependentUpon>AppResources.sv.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.th.Designer.cs">
|
||||
<DependentUpon>AppResources.th.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.tr.Designer.cs">
|
||||
<DependentUpon>AppResources.tr.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.uk.Designer.cs">
|
||||
<DependentUpon>AppResources.uk.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.vi.Designer.cs">
|
||||
<DependentUpon>AppResources.vi.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.zh-Hans.Designer.cs">
|
||||
<DependentUpon>AppResources.zh-Hans.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
<Compile Update="Resources\AppResources.zh-Hant.Designer.cs">
|
||||
<DependentUpon>AppResources.zh-Hant.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\AppResources.cs.resx">
|
||||
<LastGenOutput>AppResources.cs.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.da.resx">
|
||||
<LastGenOutput>AppResources.da.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.de.resx">
|
||||
<LastGenOutput>AppResources.de.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.es.resx">
|
||||
<LastGenOutput>AppResources.es.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.fi.resx">
|
||||
<LastGenOutput>AppResources.fi.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.fr.resx">
|
||||
<LastGenOutput>AppResources.fr.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.hi.resx">
|
||||
<LastGenOutput>AppResources.hi.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.hr.resx">
|
||||
<LastGenOutput>AppResources.hr.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.hu.resx">
|
||||
<LastGenOutput>AppResources.hu.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.id.resx">
|
||||
<LastGenOutput>AppResources.id.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.it.resx">
|
||||
<LastGenOutput>AppResources.it.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.ja.resx">
|
||||
<LastGenOutput>AppResources.ja.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.nl.resx">
|
||||
<LastGenOutput>AppResources.nl.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.pl.resx">
|
||||
<LastGenOutput>AppResources.pl.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.pt-BR.resx">
|
||||
<LastGenOutput>AppResources.pt-BR.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.pt-PT.resx">
|
||||
<LastGenOutput>AppResources.pt-PT.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.ro.resx">
|
||||
<LastGenOutput>AppResources.ro.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.ru.resx">
|
||||
<LastGenOutput>AppResources.ru.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.sk.resx">
|
||||
<LastGenOutput>AppResources.sk.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.sv.resx">
|
||||
<LastGenOutput>AppResources.sv.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.th.resx">
|
||||
<LastGenOutput>AppResources.th.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.tr.resx">
|
||||
<LastGenOutput>AppResources.tr.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.uk.resx">
|
||||
<LastGenOutput>AppResources.uk.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.vi.resx">
|
||||
<LastGenOutput>AppResources.vi.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.zh-Hans.resx">
|
||||
<LastGenOutput>AppResources.zh-Hans.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\AppResources.zh-Hant.resx">
|
||||
<LastGenOutput>AppResources.zh-Hant.Designer.cs</LastGenOutput>
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Behaviors\" />
|
||||
<None Remove="Xamarin.CommunityToolkit" />
|
||||
<None Remove="Lists\" />
|
||||
<None Remove="Lists\DataTemplates\" />
|
||||
<None Remove="Lists\DataTemplateSelectors\" />
|
||||
<None Remove="Lists\DataTemplates\CustomFields\" />
|
||||
<None Remove="Lists\ItemViewModels\" />
|
||||
<None Remove="Lists\ItemViewModels\CustomFields\" />
|
||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||
<None Remove="Utilities\AccountManagement\" />
|
||||
<None Remove="Controls\DateTime\" />
|
||||
<None Remove="Controls\IconLabelButton\" />
|
||||
<None Remove="MessagePack" />
|
||||
<None Remove="MessagePack.MSBuild.Tasks" />
|
||||
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
||||
<None Remove="Utilities\Automation\" />
|
||||
<None Remove="Utilities\Prompts\" />
|
||||
<None Remove="Controls\Settings\" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
|
||||
<AndroidEnableMultiDex>True</AndroidEnableMultiDex>
|
||||
<UseInterpreter>False</UseInterpreter>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<RunAOTCompilation>False</RunAOTCompilation>
|
||||
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<CodesignProvision>Automatic</CodesignProvision>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
||||
<UseInterpreter>true</UseInterpreter>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|iossimulator-x64'">
|
||||
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'">
|
||||
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<CodesignProvision>$(ReleaseCodesignProvision)</CodesignProvision>
|
||||
<CodesignKey>$(ReleaseCodesignKey)</CodesignKey>
|
||||
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
||||
<UseInterpreter>true</UseInterpreter>
|
||||
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
|
||||
<!--This is needed for PCLCrypto to work correctly-->
|
||||
<TrimmerRootAssembly Include="System.Security.Cryptography" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidNativeLibrary Include="Platforms\Android\lib\arm64-v8a\libargon2.so" />
|
||||
<AndroidNativeLibrary Include="Platforms\Android\lib\armeabi-v7a\libargon2.so" />
|
||||
<AndroidNativeLibrary Include="Platforms\Android\lib\x86\libargon2.so" />
|
||||
<AndroidNativeLibrary Include="Platforms\Android\lib\x86_64\libargon2.so" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Camera.MAUI" Version="1.4.4" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.17" />
|
||||
<PackageReference Include="MessagePack" Version="2.5.124" />
|
||||
<PackageReference Include="MessagePack.MSBuild.Tasks" Version="2.5.124">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="zxcvbn-core" Version="7.0.92" />
|
||||
<PackageReference Include="CommunityToolkit.Maui" Version="5.2.0" />
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
|
||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
|
||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
|
||||
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
||||
<PackageReference Include="AsyncAwaitBestPractices.MVVM" Version="6.0.6" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Effects\" />
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Resources\AppIcon\" />
|
||||
<Folder Include="Resources\Splash\" />
|
||||
<Folder Include="Platforms\Android\Accessibility\" />
|
||||
<Folder Include="Platforms\Android\Autofill\" />
|
||||
<Folder Include="Platforms\Android\Push\" />
|
||||
<Folder Include="Platforms\Android\Receivers\" />
|
||||
<Folder Include="Platforms\Android\Services\" />
|
||||
<Folder Include="Platforms\Android\Tiles\" />
|
||||
<Folder Include="Platforms\Android\Utilities\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
|
||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
|
||||
<PackageReference Include="Xamarin.Firebase.Messaging" Version="123.1.2.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="!$(DefineConstants.Contains(FDROID))">
|
||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="5.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-mdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-mdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxhdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxhdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxxhdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-hdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-hdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-mdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-mdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-hdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-hdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxxhdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxhdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xxhdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-xhdpi\logo_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-xhdpi\yubikey.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-xhdpi\logo_white_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-xxhdpi\logo_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-xxhdpi\yubikey.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-xxhdpi\logo_white_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\yubikey.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
|
||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo_white%402x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\more_vert%402x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo_white%403x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo%403x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\more_vert%403x.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\more_vert.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo_white.png" />
|
||||
<BundleResource Include="Platforms\iOS\Resources\logo%402x.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" />
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MauiImage Include="Resources\cog_settings.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\ext_act.png" />
|
||||
<MauiImage Include="Resources\ext_more.png" />
|
||||
<MauiImage Include="Resources\ext_use.png" />
|
||||
<MauiImage Include="Resources\generate.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\info.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\lock.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\login.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\logo_white.png" />
|
||||
<MauiImage Include="Resources\logo.png" />
|
||||
<MauiImage Include="Resources\more_vert.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\more.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\send.svg">
|
||||
<BaseSize>24,24</BaseSize>
|
||||
</MauiImage>
|
||||
<MauiImage Include="Resources\yubikey.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' AND '$(IncludeBitwardeniOSExtensions)' == 'True'">
|
||||
<ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj">
|
||||
<IsAppExtension>true</IsAppExtension>
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\iOS.Extension\iOS.Extension.csproj">
|
||||
<IsAppExtension>true</IsAppExtension>
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\iOS.ShareExtension\iOS.ShareExtension.csproj">
|
||||
<IsAppExtension>true</IsAppExtension>
|
||||
<IsWatchApp>false</IsWatchApp>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND '$(IncludeBitwardenWatchOSApp)' == 'True'">
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
|
||||
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
|
||||
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
|
||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'">watchsimulator</WatchAppConfiguration>
|
||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'">watchos</WatchAppConfiguration>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND Exists('$(WatchAppBundleFullPath)') ">
|
||||
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND Exists('$(WatchAppBundleFullPath)') ">
|
||||
<CreateAppBundleDependsOn>
|
||||
_CopyWatchOS2AppsToBundle;
|
||||
$(CreateAppBundleDependsOn);
|
||||
</CreateAppBundleDependsOn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
|
||||
<GoogleServicesJson Include="Platforms\Android\google-services.json.enc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Platforms\iOS\Resources\logo.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo_white%402x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\more_vert%402x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo_white%403x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo%403x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\more_vert%403x.png" />
|
||||
<None Remove="Platforms\iOS\Resources\more_vert.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo_white.png" />
|
||||
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.App">
|
||||
<Application.Resources>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -1,543 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
namespace Bit.App
|
||||
{
|
||||
public partial class App : Application, IAccountsManagerHost
|
||||
{
|
||||
public const string POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE = "popAllAndGoToTabGenerator";
|
||||
public const string POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE = "popAllAndGoToTabMyVault";
|
||||
public const string POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE = "popAllAndGoToTabSend";
|
||||
public const string POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE = "popAllAndGoToAutofillCiphers";
|
||||
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IAccountsManager _accountsManager;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IConfigService _configService;
|
||||
private static bool _isResumed;
|
||||
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
|
||||
private static bool _pendingCheckPasswordlessLoginRequests;
|
||||
private static object _processingLoginRequestLock = new object();
|
||||
|
||||
public App(AppOptions appOptions)
|
||||
{
|
||||
Options = appOptions ?? new AppOptions();
|
||||
if (Options.IosExtension)
|
||||
{
|
||||
Current = this;
|
||||
return;
|
||||
}
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||
_configService = ServiceContainer.Resolve<IConfigService>();
|
||||
|
||||
_accountsManager.Init(() => Options, this);
|
||||
|
||||
Bootstrap();
|
||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (message.Command == "showDialog")
|
||||
{
|
||||
var details = message.Data as DialogDetails;
|
||||
var confirmed = true;
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||
AppResources.Ok : details.ConfirmText;
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||
{
|
||||
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||
details.CancelText);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
||||
}
|
||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||
});
|
||||
}
|
||||
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
ResumedAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
else if (message.Command == "slept")
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
await SleptAsync();
|
||||
}
|
||||
}
|
||||
else if (message.Command == "migrated")
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
|
||||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
|
||||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
Options.OtpData = new OtpData((string)message.Data);
|
||||
}
|
||||
|
||||
await Device.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
if (Current.MainPage is TabsPage tabsPage)
|
||||
{
|
||||
while (tabsPage.Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
await tabsPage.Navigation.PopModalAsync(false);
|
||||
}
|
||||
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
|
||||
{
|
||||
Options.MyVaultTile = false;
|
||||
tabsPage.ResetToVaultPage();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
|
||||
{
|
||||
Options.GeneratorTile = false;
|
||||
tabsPage.ResetToGeneratorPage();
|
||||
}
|
||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
|
||||
{
|
||||
tabsPage.ResetToSendPage();
|
||||
}
|
||||
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||
{
|
||||
tabsPage.ResetToVaultPage();
|
||||
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (message.Command == "convertAccountToKeyConnector")
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await Application.Current.MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
});
|
||||
}
|
||||
else if (message.Command == Constants.ForceUpdatePassword)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await Application.Current.MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new UpdateTempPasswordPage()));
|
||||
});
|
||||
}
|
||||
else if (message.Command == Constants.ForceSetPassword)
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
|
||||
}
|
||||
else if (message.Command == "syncCompleted")
|
||||
{
|
||||
await _configService.GetAsync(true);
|
||||
}
|
||||
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||
|| message.Command == "unlocked"
|
||||
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||
{
|
||||
lock (_processingLoginRequestLock)
|
||||
{
|
||||
// lock doesn't allow for async execution
|
||||
CheckPasswordlessLoginRequestsAsync().Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task CheckPasswordlessLoginRequestsAsync()
|
||||
{
|
||||
if (!_isResumed)
|
||||
{
|
||||
_pendingCheckPasswordlessLoginRequests = true;
|
||||
return;
|
||||
}
|
||||
_pendingCheckPasswordlessLoginRequests = false;
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
|
||||
if (notification == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await CheckShouldSwitchActiveUserAsync(notification))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay to wait for the vault page to appear
|
||||
await Task.Delay(2000);
|
||||
// if there is a request modal opened ignore all incoming requests
|
||||
if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
|
||||
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
|
||||
{
|
||||
PubKey = loginRequestData.PublicKey,
|
||||
Id = loginRequestData.Id,
|
||||
IpAddress = loginRequestData.RequestIpAddress,
|
||||
Email = await _stateService.GetEmailAsync(),
|
||||
FingerprintPhrase = loginRequestData.FingerprintPhrase,
|
||||
RequestDate = loginRequestData.CreationDate,
|
||||
DeviceType = loginRequestData.RequestDeviceType,
|
||||
Origin = loginRequestData.Origin
|
||||
});
|
||||
await _stateService.SetPasswordlessLoginNotificationAsync(null);
|
||||
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
|
||||
if (!loginRequestData.IsExpired)
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> CheckShouldSwitchActiveUserAsync(PasswordlessRequestNotification notification)
|
||||
{
|
||||
var activeUserId = await _stateService.GetActiveUserIdAsync();
|
||||
if (notification.UserId == activeUserId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
|
||||
if (result == AppResources.Ok)
|
||||
{
|
||||
await _stateService.SetActiveUserAsync(notification.UserId);
|
||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public AppOptions Options { get; private set; }
|
||||
|
||||
protected async override void OnStart()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
||||
_isResumed = true;
|
||||
await ClearCacheIfNeededAsync();
|
||||
Prime();
|
||||
if (string.IsNullOrWhiteSpace(Options.Uri))
|
||||
{
|
||||
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
|
||||
_stateService);
|
||||
if (!updated)
|
||||
{
|
||||
SyncIfNeeded();
|
||||
}
|
||||
}
|
||||
if (_pendingCheckPasswordlessLoginRequests)
|
||||
{
|
||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||
}
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
// Reset delay on every start
|
||||
_vaultTimeoutService.DelayLockAndLogoutMs = null;
|
||||
}
|
||||
|
||||
await _configService.GetAsync();
|
||||
_messagingService.Send("startEventTimer");
|
||||
}
|
||||
|
||||
protected async override void OnSleep()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
|
||||
_isResumed = false;
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
var isLocked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!isLocked)
|
||||
{
|
||||
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
||||
}
|
||||
if (!SetTabsPageFromAutofill(isLocked))
|
||||
{
|
||||
ClearAutofillUri();
|
||||
}
|
||||
await SleptAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
||||
_isResumed = true;
|
||||
if (_pendingCheckPasswordlessLoginRequests)
|
||||
{
|
||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||
}
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
ResumedAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SleptAsync()
|
||||
{
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
await ClearSensitiveFieldsAsync();
|
||||
_messagingService.Send("stopEventTimer");
|
||||
}
|
||||
|
||||
private async Task ResumedAsync()
|
||||
{
|
||||
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||
await ClearSensitiveFieldsAsync();
|
||||
_messagingService.Send("startEventTimer");
|
||||
await UpdateThemeAsync();
|
||||
await ClearCacheIfNeededAsync();
|
||||
Prime();
|
||||
SyncIfNeeded();
|
||||
if (Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage)
|
||||
{
|
||||
await lockPage.PromptBiometricAfterResumeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateThemeAsync()
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
ThemeManager.SetTheme(Current.Resources);
|
||||
_messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ClearSensitiveFieldsAsync()
|
||||
{
|
||||
await Device.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
_messagingService.Send(Constants.ClearSensitiveFields);
|
||||
});
|
||||
}
|
||||
|
||||
private void SetCulture()
|
||||
{
|
||||
// Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077
|
||||
new System.Globalization.ThaiBuddhistCalendar();
|
||||
new System.Globalization.HijriCalendar();
|
||||
new System.Globalization.UmAlQuraCalendar();
|
||||
}
|
||||
|
||||
private async Task ClearCacheIfNeededAsync()
|
||||
{
|
||||
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
||||
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
|
||||
{
|
||||
var task = Task.Run(() => _fileService.ClearCacheAsync());
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAutofillUri()
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
|
||||
{
|
||||
Options.Uri = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetTabsPageFromAutofill(bool isLocked)
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
|
||||
!Options.FromAutofillFramework)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
Options.Uri = null;
|
||||
if (isLocked)
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new LockPage());
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.MainPage = new TabsPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Prime()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var word = EEFLongWordList.Instance.List[1];
|
||||
var parsedDomain = DomainName.TryParse("https://bitwarden.com", out var domainName);
|
||||
});
|
||||
}
|
||||
|
||||
private void Bootstrap()
|
||||
{
|
||||
InitializeComponent();
|
||||
SetCulture();
|
||||
ThemeManager.SetTheme(Current.Resources);
|
||||
Current.RequestedThemeChanged += (s, a) =>
|
||||
{
|
||||
UpdateThemeAsync();
|
||||
};
|
||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||
}
|
||||
|
||||
private void SyncIfNeeded()
|
||||
{
|
||||
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var lastSync = await _syncService.GetLastSyncAsync();
|
||||
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _syncService.FullSyncAsync(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SetPreviousPageInfoAsync()
|
||||
{
|
||||
PreviousPageInfo lastPageBeforeLock = null;
|
||||
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
|
||||
if (topPage is NavigationPage navPage)
|
||||
{
|
||||
if (navPage.CurrentPage is CipherDetailsPage cipherDetailsPage)
|
||||
{
|
||||
lastPageBeforeLock = new PreviousPageInfo
|
||||
{
|
||||
Page = "view",
|
||||
CipherId = cipherDetailsPage.ViewModel.CipherId
|
||||
};
|
||||
}
|
||||
else if (navPage.CurrentPage is CipherAddEditPage cipherAddEditPage && cipherAddEditPage.ViewModel.EditMode)
|
||||
{
|
||||
lastPageBeforeLock = new PreviousPageInfo
|
||||
{
|
||||
Page = "edit",
|
||||
CipherId = cipherAddEditPage.ViewModel.CipherId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
|
||||
}
|
||||
|
||||
public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
|
||||
{
|
||||
switch (navTarget)
|
||||
{
|
||||
case NavigationTarget.HomeLogin:
|
||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||
break;
|
||||
case NavigationTarget.Login:
|
||||
if (navParams is LoginNavigationParams loginParams)
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
|
||||
}
|
||||
break;
|
||||
case NavigationTarget.Lock:
|
||||
if (navParams is LockNavigationParams lockParams)
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.MainPage = new NavigationPage(new LockPage(Options));
|
||||
}
|
||||
break;
|
||||
case NavigationTarget.Home:
|
||||
Current.MainPage = new TabsPage(Options);
|
||||
break;
|
||||
case NavigationTarget.AddEditCipher:
|
||||
Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
|
||||
break;
|
||||
case NavigationTarget.AutofillCiphers:
|
||||
case NavigationTarget.OtpCipherSelection:
|
||||
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||
break;
|
||||
case NavigationTarget.SendAddEdit:
|
||||
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// This behavior prevents the Editor to be automatically scrolled to the bottom on focus.
|
||||
/// This is needed due to this Xamarin Forms issue: https://github.com/xamarin/Xamarin.Forms/issues/2233
|
||||
/// </summary>
|
||||
public class EditorPreventAutoBottomScrollingOnFocusedBehavior : Behavior<Editor>
|
||||
{
|
||||
public static readonly BindableProperty ParentScrollViewProperty
|
||||
= BindableProperty.Create(nameof(ParentScrollView), typeof(ScrollView), typeof(EditorPreventAutoBottomScrollingOnFocusedBehavior));
|
||||
|
||||
public ScrollView ParentScrollView
|
||||
{
|
||||
get => (ScrollView)GetValue(ParentScrollViewProperty);
|
||||
set => SetValue(ParentScrollViewProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnAttachedTo(Editor bindable)
|
||||
{
|
||||
base.OnAttachedTo(bindable);
|
||||
|
||||
bindable.Focused += OnFocused;
|
||||
}
|
||||
|
||||
private void OnFocused(object sender, FocusEventArgs e)
|
||||
{
|
||||
if (DeviceInfo.Platform.Equals(DevicePlatform.iOS) && ParentScrollView != null)
|
||||
{
|
||||
ParentScrollView.ScrollToAsync(ParentScrollView.ScrollX, ParentScrollView.ScrollY, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetachingFrom(Editor bindable)
|
||||
{
|
||||
bindable.Focused -= OnFocused;
|
||||
|
||||
base.OnDetachingFrom(bindable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
||||
x:Name="_mainOverlay"
|
||||
x:DataType="controls:AccountSwitchingOverlayViewModel"
|
||||
x:Class="Bit.App.Controls.AccountSwitchingOverlayView"
|
||||
BackgroundColor="#22000000"
|
||||
Padding="0"
|
||||
IsVisible="False">
|
||||
<StackLayout
|
||||
x:Name="_accountListContainer"
|
||||
VerticalOptions="Fill"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
BackgroundColor="Transparent">
|
||||
<Frame
|
||||
Padding="0"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="Start"
|
||||
xct:ShadowEffect.Color="Black"
|
||||
xct:ShadowEffect.Radius="10"
|
||||
xct:ShadowEffect.OffsetY="3">
|
||||
<ListView
|
||||
x:Name="_accountListView"
|
||||
ItemsSource="{Binding BindingContext.AccountViews, Source={x:Reference _mainOverlay}}"
|
||||
BackgroundColor="{DynamicResource BackgroundColor}"
|
||||
VerticalOptions="Start"
|
||||
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
|
||||
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never"
|
||||
AutomationId="AccountListView">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="view:AccountView">
|
||||
<controls:AccountViewCell
|
||||
Account="{Binding .}"
|
||||
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||
AutomationId="AccountViewCell"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<ListView.Effects>
|
||||
<effects:ScrollViewContentInsetAdjustmentBehaviorEffect />
|
||||
</ListView.Effects>
|
||||
</ListView>
|
||||
</Frame>
|
||||
<BoxView
|
||||
BackgroundColor="Transparent"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="FillAndExpand">
|
||||
<BoxView.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
|
||||
</BoxView.GestureRecognizers>
|
||||
</BoxView>
|
||||
</StackLayout>
|
||||
</ContentView>
|
||||
@@ -1,194 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class AccountSwitchingOverlayView : ContentView
|
||||
{
|
||||
public static readonly BindableProperty MainPageProperty = BindableProperty.Create(
|
||||
nameof(MainPage),
|
||||
typeof(ContentPage),
|
||||
typeof(AccountSwitchingOverlayView),
|
||||
defaultBindingMode: BindingMode.OneWay);
|
||||
|
||||
public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
|
||||
nameof(MainFab),
|
||||
typeof(View),
|
||||
typeof(AccountSwitchingOverlayView),
|
||||
defaultBindingMode: BindingMode.OneWay);
|
||||
|
||||
public ContentPage MainPage
|
||||
{
|
||||
get => (ContentPage)GetValue(MainPageProperty);
|
||||
set => SetValue(MainPageProperty, value);
|
||||
}
|
||||
|
||||
public View MainFab
|
||||
{
|
||||
get => (View)GetValue(MainFabProperty);
|
||||
set => SetValue(MainFabProperty, value);
|
||||
}
|
||||
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
|
||||
public AccountSwitchingOverlayView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
|
||||
onException: ex => _logger.Value.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
||||
onException: ex => _logger.Value.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
LongPressAccountCommand = new AsyncCommand<AccountViewCellViewModel>(LongPressAccountAsync,
|
||||
onException: ex => _logger.Value.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
|
||||
|
||||
public ICommand ToggleVisibililtyCommand { get; }
|
||||
|
||||
public ICommand SelectAccountCommand { get; }
|
||||
|
||||
public ICommand LongPressAccountCommand { get; }
|
||||
|
||||
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
||||
|
||||
public bool LongPressAccountEnabled { get; set; } = true;
|
||||
|
||||
public Action AfterHide { get; set; }
|
||||
|
||||
public async Task ToggleVisibilityAsync()
|
||||
{
|
||||
if (IsVisible)
|
||||
{
|
||||
await HideAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ShowAsync()
|
||||
{
|
||||
if (ViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ViewModel.RefreshAccountViewsAsync();
|
||||
|
||||
await Device.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
// start listView in default (off-screen) position
|
||||
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
|
||||
|
||||
// re-measure in case accounts have been removed without changing screens
|
||||
if (ViewModel.AccountViews != null)
|
||||
{
|
||||
_accountListView.HeightRequest = AccountListRowHeight * ViewModel.AccountViews.Count;
|
||||
}
|
||||
|
||||
// set overlay opacity to zero before making visible and start fade-in
|
||||
Opacity = 0;
|
||||
IsVisible = true;
|
||||
this.FadeTo(1, 100);
|
||||
|
||||
if (Device.RuntimePlatform == Device.Android && MainFab != null)
|
||||
{
|
||||
// start fab fade-out
|
||||
MainFab.FadeTo(0, 200);
|
||||
}
|
||||
|
||||
// slide account list into view
|
||||
await _accountListContainer.TranslateTo(0, 0, 200, Easing.SinOut);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task HideAsync()
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
// already hidden, don't animate again
|
||||
return;
|
||||
}
|
||||
// Not all animations are awaited. This is intentional to allow multiple simultaneous animations.
|
||||
await Device.InvokeOnMainThreadAsync(async () =>
|
||||
{
|
||||
// start overlay fade-out
|
||||
this.FadeTo(0, 200);
|
||||
|
||||
if (Device.RuntimePlatform == Device.Android && MainFab != null)
|
||||
{
|
||||
// start fab fade-in
|
||||
MainFab.FadeTo(1, 200);
|
||||
}
|
||||
|
||||
// slide account list out of view
|
||||
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 200, Easing.SinIn);
|
||||
|
||||
// remove overlay
|
||||
IsVisible = false;
|
||||
|
||||
AfterHide?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
private async void FreeSpaceOverlay_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await HideAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(100);
|
||||
await HideAsync();
|
||||
|
||||
ViewModel?.SelectAccountCommand?.Execute(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
||||
{
|
||||
if (!LongPressAccountEnabled || !item.IsAccount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.Delay(100);
|
||||
await HideAsync();
|
||||
|
||||
ViewModel?.LongPressAccountCommand?.Execute(
|
||||
new Tuple<ContentPage, AccountViewCellViewModel>(MainPage, item));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class AccountSwitchingOverlayViewModel : ExtendedViewModel
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
|
||||
public AccountSwitchingOverlayViewModel(IStateService stateService,
|
||||
IMessagingService messagingService,
|
||||
ILogger logger)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_messagingService = messagingService;
|
||||
|
||||
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
|
||||
onException: ex => logger.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
|
||||
onException: ex => logger.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
// this needs to be a new list every time for the binding to get updated,
|
||||
// XF doesn't currentlyl provide a direct way to update on same instance
|
||||
// https://github.com/xamarin/Xamarin.Forms/issues/1950
|
||||
public List<AccountView> AccountViews => _stateService?.AccountViews is null ? null : new List<AccountView>(_stateService.AccountViews);
|
||||
|
||||
public bool AllowActiveAccountSelection { get; set; }
|
||||
|
||||
public bool AllowAddAccountRow { get; set; }
|
||||
|
||||
public ICommand SelectAccountCommand { get; }
|
||||
|
||||
public ICommand LongPressAccountCommand { get; }
|
||||
|
||||
public bool FromIOSExtension { get; set; }
|
||||
|
||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||
{
|
||||
if (!item.AccountView.IsAccount)
|
||||
{
|
||||
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.AccountView.IsActive)
|
||||
{
|
||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||
if (FromIOSExtension)
|
||||
{
|
||||
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
|
||||
}
|
||||
}
|
||||
else if (AllowActiveAccountSelection)
|
||||
{
|
||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LongPressAccountAsync(Tuple<ContentPage, AccountViewCellViewModel> item)
|
||||
{
|
||||
var (page, account) = item;
|
||||
if (account.AccountView.IsAccount)
|
||||
{
|
||||
await AppHelpers.AccountListOptions(page, account);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshAccountViewsAsync()
|
||||
{
|
||||
await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
|
||||
|
||||
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(AccountViews)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||
x:Class="Bit.App.Controls.AccountViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:Name="_accountView"
|
||||
x:DataType="controls:AccountViewCellViewModel">
|
||||
<Grid RowSpacing="0"
|
||||
ColumnSpacing="0"
|
||||
xct:TouchEffect.NativeAnimation="True"
|
||||
xct:TouchEffect.Command="{Binding SelectAccountCommand, Source={x:Reference _accountView}}"
|
||||
xct:TouchEffect.CommandParameter="{Binding .}"
|
||||
xct:TouchEffect.LongPressCommand="{Binding LongPressAccountCommand, Source={x:Reference _accountView}}"
|
||||
xct:TouchEffect.LongPressCommandParameter="{Binding .}">
|
||||
|
||||
<Grid.Resources>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid
|
||||
IsVisible="{Binding IsAccount}"
|
||||
VerticalOptions="CenterAndExpand">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
Grid.Column="0"
|
||||
Source="{Binding AvatarImageSource}"
|
||||
HorizontalOptions="Center"
|
||||
Margin="10,0"
|
||||
VerticalOptions="Center" />
|
||||
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
RowSpacing="1"
|
||||
VerticalOptions="Center">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label
|
||||
Grid.Row="0"
|
||||
Text="{Binding AccountView.Email}"
|
||||
IsVisible="{Binding IsActive}"
|
||||
StyleClass="accountlist-title, accountlist-title-platform"
|
||||
LineBreakMode="TailTruncation"
|
||||
AutomationId="AccountEmailLabel" />
|
||||
<Label
|
||||
Grid.Row="0"
|
||||
Text="{Binding AccountView.Email}"
|
||||
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
||||
StyleClass="accountlist-title, accountlist-title-platform"
|
||||
TextColor="{DynamicResource MutedColor}"
|
||||
LineBreakMode="TailTruncation"
|
||||
AutomationId="AccountEmailLabel" />
|
||||
<Label
|
||||
Grid.Row="1"
|
||||
IsVisible="{Binding ShowHostname}"
|
||||
Text="{Binding AccountView.Hostname}"
|
||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||
LineBreakMode="TailTruncation"
|
||||
AutomationId="AccountHostUrlLabel" />
|
||||
<Label
|
||||
Grid.Row="2"
|
||||
Text="{u:I18n AccountUnlocked}"
|
||||
IsVisible="{Binding IsUnlockedAndNotActive}"
|
||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||
FontAttributes="Italic"
|
||||
TextTransform="Lowercase"
|
||||
LineBreakMode="TailTruncation"
|
||||
AutomationId="AccountStatusLabel" />
|
||||
<Label
|
||||
Grid.Row="2"
|
||||
Text="{u:I18n AccountLocked}"
|
||||
IsVisible="{Binding IsLockedAndNotActive}"
|
||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||
FontAttributes="Italic"
|
||||
TextTransform="Lowercase"
|
||||
LineBreakMode="TailTruncation"
|
||||
AutomationId="AccountStatusLabel" />
|
||||
<Label
|
||||
Grid.Row="2"
|
||||
Text="{u:I18n AccountLoggedOut}"
|
||||
IsVisible="{Binding IsLoggedOutAndNotActive}"
|
||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||
FontAttributes="Italic"
|
||||
TextTransform="Lowercase"
|
||||
LineBreakMode="TailTruncation"
|
||||
AutomationId="AccountStatusLabel" />
|
||||
</Grid>
|
||||
|
||||
<controls:IconLabel
|
||||
Grid.Column="2"
|
||||
Text="{Binding AuthStatusIconNotActive}"
|
||||
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
||||
Margin="12,0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
AutomationId="InactiveVaultIcon" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="2"
|
||||
Text="{Binding AuthStatusIconActive}"
|
||||
IsVisible="{Binding IsActive}"
|
||||
Margin="12,0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
AutomationId="ActiveVaultIcon" />
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
IsVisible="{Binding IsAccount, Converter={StaticResource inverseBool}}"
|
||||
VerticalOptions="CenterAndExpand">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
Grid.Column="0"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="Center"
|
||||
Margin="14,0"
|
||||
WidthRequest="{OnPlatform 24, iOS=24, Android=26}"
|
||||
HeightRequest="{OnPlatform 24, iOS=24, Android=26}"
|
||||
Source="plus.png"
|
||||
xct:IconTintColorEffect.TintColor="{DynamicResource TextColor}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
<Label
|
||||
Text="{u:I18n AddAccount}"
|
||||
StyleClass="accountlist-title, accountlist-title-platform"
|
||||
LineBreakMode="TailTruncation"
|
||||
VerticalOptions="Center"
|
||||
Grid.Column="1"
|
||||
AutomationId="AddAccountButton" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ViewCell>
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class AccountViewCell : ViewCell
|
||||
{
|
||||
public static readonly BindableProperty AccountProperty = BindableProperty.Create(
|
||||
nameof(Account), typeof(AccountView), typeof(AccountViewCell));
|
||||
|
||||
public static readonly BindableProperty SelectAccountCommandProperty = BindableProperty.Create(
|
||||
nameof(SelectAccountCommand), typeof(ICommand), typeof(AccountViewCell));
|
||||
|
||||
public static readonly BindableProperty LongPressAccountCommandProperty = BindableProperty.Create(
|
||||
nameof(LongPressAccountCommand), typeof(ICommand), typeof(AccountViewCell));
|
||||
|
||||
public AccountViewCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AccountView Account
|
||||
{
|
||||
get => GetValue(AccountProperty) as AccountView;
|
||||
set => SetValue(AccountProperty, value);
|
||||
}
|
||||
|
||||
public ICommand SelectAccountCommand
|
||||
{
|
||||
get => GetValue(SelectAccountCommandProperty) as ICommand;
|
||||
set => SetValue(SelectAccountCommandProperty, value);
|
||||
}
|
||||
|
||||
public ICommand LongPressAccountCommand
|
||||
{
|
||||
get => GetValue(LongPressAccountCommandProperty) as ICommand;
|
||||
set => SetValue(LongPressAccountCommandProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
if (propertyName == AccountProperty.PropertyName)
|
||||
{
|
||||
if (Account == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BindingContext = new AccountViewCellViewModel(Account);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class AccountViewCellViewModel : ExtendedViewModel
|
||||
{
|
||||
private AccountView _accountView;
|
||||
private AvatarImageSource _avatar;
|
||||
|
||||
public AccountViewCellViewModel(AccountView accountView)
|
||||
{
|
||||
AccountView = accountView;
|
||||
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
||||
?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email, AccountView.AvatarColor);
|
||||
}
|
||||
|
||||
public AccountView AccountView
|
||||
{
|
||||
get => _accountView;
|
||||
set => SetProperty(ref _accountView, value);
|
||||
}
|
||||
|
||||
public AvatarImageSource AvatarImageSource
|
||||
{
|
||||
get => _avatar;
|
||||
set => SetProperty(ref _avatar, value);
|
||||
}
|
||||
|
||||
public bool IsAccount
|
||||
{
|
||||
get => AccountView.IsAccount;
|
||||
}
|
||||
|
||||
public bool ShowHostname
|
||||
{
|
||||
get => !string.IsNullOrWhiteSpace(AccountView.Hostname);
|
||||
}
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get => AccountView.IsActive;
|
||||
}
|
||||
|
||||
public bool IsUnlocked
|
||||
{
|
||||
get => AccountView.AuthStatus == AuthenticationStatus.Unlocked;
|
||||
}
|
||||
|
||||
public bool IsUnlockedAndNotActive
|
||||
{
|
||||
get => IsUnlocked && !IsActive;
|
||||
}
|
||||
|
||||
public bool IsLocked
|
||||
{
|
||||
get => AccountView.AuthStatus == AuthenticationStatus.Locked;
|
||||
}
|
||||
|
||||
public bool IsLockedAndNotActive
|
||||
{
|
||||
get => IsLocked && !IsActive;
|
||||
}
|
||||
|
||||
public bool IsLoggedOut
|
||||
{
|
||||
get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut;
|
||||
}
|
||||
|
||||
public bool IsLoggedOutAndNotActive
|
||||
{
|
||||
get => IsLoggedOut && !IsActive;
|
||||
}
|
||||
|
||||
public string AuthStatusIconActive
|
||||
{
|
||||
get => BitwardenIcons.CheckCircle;
|
||||
}
|
||||
|
||||
public string AuthStatusIconNotActive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsUnlocked)
|
||||
{
|
||||
return BitwardenIcons.Unlock;
|
||||
}
|
||||
return BitwardenIcons.Lock;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.AuthenticatorViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
StyleClass="list-row, list-row-platform"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
x:DataType="pages:GroupingsPageTOTPListItem"
|
||||
ColumnDefinitions="40,*,40,Auto,40"
|
||||
RowSpacing="0"
|
||||
Padding="0,10,0,0"
|
||||
RowDefinitions="*,*">
|
||||
|
||||
<Grid.Resources>
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
</Grid.Resources>
|
||||
|
||||
<controls:IconLabel
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<ff:CachedImage
|
||||
Grid.Column="0"
|
||||
BitmapOptimizations="True"
|
||||
ErrorPlaceholder="login.png"
|
||||
LoadingPlaceholder="login.png"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding ShowIconImage}"
|
||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
VerticalTextAlignment="Center"
|
||||
VerticalOptions="Fill"
|
||||
StyleClass="list-title, list-title-platform"
|
||||
Text="{Binding Cipher.Name}" />
|
||||
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
VerticalTextAlignment="Center"
|
||||
VerticalOptions="Fill"
|
||||
StyleClass="list-subtitle, list-subtitle-platform"
|
||||
Text="{Binding Cipher.SubTitle}" />
|
||||
|
||||
<controls:CircularProgressbarView
|
||||
Progress="{Binding Progress}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="CenterAndExpand" />
|
||||
|
||||
<Label
|
||||
Text="{Binding TotpSec, Mode=OneWay}"
|
||||
Style="{DynamicResource textTotp}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
StyleClass="text-sm"
|
||||
HorizontalTextAlignment="Center"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalTextAlignment="Center"
|
||||
VerticalOptions="Fill" />
|
||||
|
||||
<StackLayout
|
||||
Grid.Row="0"
|
||||
Grid.Column="3"
|
||||
Margin="3,0,2,0"
|
||||
Spacing="5"
|
||||
Grid.RowSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalOptions="Fill"
|
||||
VerticalOptions="Fill">
|
||||
|
||||
<controls:MonoLabel
|
||||
Text="{Binding TotpCodeFormattedStart, Mode=OneWay}"
|
||||
Style="{DynamicResource textTotp}"
|
||||
StyleClass="text-lg"
|
||||
HorizontalTextAlignment="Center"
|
||||
VerticalTextAlignment="Center"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="FillAndExpand" />
|
||||
|
||||
<controls:MonoLabel
|
||||
Text="{Binding TotpCodeFormattedEnd, Mode=OneWay}"
|
||||
Style="{DynamicResource textTotp}"
|
||||
StyleClass="text-lg"
|
||||
HorizontalTextAlignment="Center"
|
||||
VerticalTextAlignment="Center"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="FillAndExpand" />
|
||||
</StackLayout>
|
||||
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding CopyCommand}"
|
||||
CommandParameter="LoginTotp"
|
||||
Grid.Row="0"
|
||||
Grid.Column="4"
|
||||
Grid.RowSpan="2"
|
||||
Padding="0,0,1,0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n CopyTotp}" />
|
||||
</controls:ExtendedGrid>
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class AuthenticatorViewCell : ExtendedGrid
|
||||
{
|
||||
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
|
||||
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
|
||||
|
||||
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
|
||||
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
|
||||
|
||||
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
|
||||
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
|
||||
|
||||
public AuthenticatorViewCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Command CopyCommand { get; set; }
|
||||
|
||||
public CipherView Cipher
|
||||
{
|
||||
get => GetValue(CipherProperty) as CipherView;
|
||||
set => SetValue(CipherProperty, value);
|
||||
}
|
||||
|
||||
public bool? WebsiteIconsEnabled
|
||||
{
|
||||
get => (bool)GetValue(WebsiteIconsEnabledProperty);
|
||||
set => SetValue(WebsiteIconsEnabledProperty, value);
|
||||
}
|
||||
|
||||
public long TotpSec
|
||||
{
|
||||
get => (long)GetValue(TotpSecProperty);
|
||||
set => SetValue(TotpSecProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowIconImage
|
||||
{
|
||||
get => WebsiteIconsEnabled ?? false
|
||||
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||
&& IconImageSource != null;
|
||||
}
|
||||
|
||||
private string _iconImageSource = string.Empty;
|
||||
public string IconImageSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||
{
|
||||
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||
}
|
||||
return _iconImageSource;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Utilities;
|
||||
using SkiaSharp;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class AvatarImageSource : StreamImageSource
|
||||
{
|
||||
private readonly string _text;
|
||||
private readonly string _id;
|
||||
private readonly string _color;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj is AvatarImageSource avatar)
|
||||
{
|
||||
return avatar._id == _id && avatar._text == _text && avatar._color == _color;
|
||||
}
|
||||
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
|
||||
|
||||
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
|
||||
{
|
||||
_id = userId;
|
||||
_text = name;
|
||||
if (string.IsNullOrWhiteSpace(_text))
|
||||
{
|
||||
_text = email;
|
||||
}
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
|
||||
|
||||
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
|
||||
{
|
||||
OnLoadingStarted();
|
||||
userToken.Register(CancellationTokenSource.Cancel);
|
||||
var result = Draw();
|
||||
OnLoadingCompleted(CancellationTokenSource.IsCancellationRequested);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private Stream Draw()
|
||||
{
|
||||
string chars;
|
||||
string upperCaseText = null;
|
||||
|
||||
if (string.IsNullOrEmpty(_text))
|
||||
{
|
||||
chars = "..";
|
||||
}
|
||||
else if (_text?.Length > 1)
|
||||
{
|
||||
upperCaseText = _text.ToUpper();
|
||||
chars = GetFirstLetters(upperCaseText, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
chars = upperCaseText = _text.ToUpper();
|
||||
}
|
||||
|
||||
var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
|
||||
var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
|
||||
var size = 50;
|
||||
|
||||
using (var bitmap = new SKBitmap(size * 2,
|
||||
size * 2,
|
||||
SKImageInfo.PlatformColorType,
|
||||
SKAlphaType.Premul))
|
||||
{
|
||||
using (var canvas = new SKCanvas(bitmap))
|
||||
{
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
using (var paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
Color = SKColor.Parse(bgColor)
|
||||
})
|
||||
{
|
||||
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
||||
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
|
||||
var radius = midX - midX / 5;
|
||||
|
||||
using (var circlePaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
StrokeJoin = SKStrokeJoin.Miter,
|
||||
Color = SKColor.Parse(bgColor)
|
||||
})
|
||||
{
|
||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||
|
||||
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||
var textSize = midX / 1.3f;
|
||||
using (var textPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Style = SKPaintStyle.Fill,
|
||||
Color = SKColor.Parse(textColor),
|
||||
TextSize = textSize,
|
||||
TextAlign = SKTextAlign.Center,
|
||||
Typeface = typeface
|
||||
})
|
||||
{
|
||||
var rect = new SKRect();
|
||||
textPaint.MeasureText(chars, ref rect);
|
||||
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
|
||||
|
||||
using (var img = SKImage.FromBitmap(bitmap))
|
||||
{
|
||||
var data = img.Encode(SKEncodedImageFormat.Png, 100);
|
||||
return data?.AsStream(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFirstLetters(string data, int charCount)
|
||||
{
|
||||
var sanitizedData = data.Trim();
|
||||
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length > 1 && charCount <= 2)
|
||||
{
|
||||
var text = string.Empty;
|
||||
for (var i = 0; i < charCount; i++)
|
||||
{
|
||||
text += parts[i][0];
|
||||
}
|
||||
return text;
|
||||
}
|
||||
if (sanitizedData.Length > 2)
|
||||
{
|
||||
return sanitizedData.Substring(0, 2);
|
||||
}
|
||||
return sanitizedData;
|
||||
}
|
||||
|
||||
private Color StringToColor(string str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
return Color.FromHex("#33ffffff");
|
||||
}
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
{
|
||||
hash = str[i] + ((hash << 5) - hash);
|
||||
}
|
||||
var color = "#FF";
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var value = (hash >> (i * 8)) & 0xff;
|
||||
var base16 = "00" + Convert.ToString(value, 16);
|
||||
color += base16.Substring(base16.Length - 2);
|
||||
}
|
||||
return Color.FromHex(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public interface IAvatarImageSourcePool
|
||||
{
|
||||
AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color);
|
||||
}
|
||||
|
||||
public class AvatarImageSourcePool : IAvatarImageSourcePool
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
|
||||
|
||||
public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color)
|
||||
{
|
||||
var key = $"{userId}{name}{email}{color}";
|
||||
if (!_cache.TryGetValue(key, out var avatar))
|
||||
{
|
||||
avatar = new AvatarImageSource(userId, name, email, color);
|
||||
if (!_cache.TryAdd(key, avatar)
|
||||
&&
|
||||
!_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.
|
||||
{
|
||||
// if add and get after fails, then something wrong is going on with this method.
|
||||
throw new InvalidOperationException("Something is wrong creating the avatar image");
|
||||
}
|
||||
}
|
||||
return avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.CipherViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
StyleClass="list-row, list-row-platform"
|
||||
RowSpacing="0"
|
||||
ColumnSpacing="0"
|
||||
x:DataType="controls:CipherViewCellViewModel"
|
||||
AutomationId="CipherCell">
|
||||
|
||||
<Grid.Resources>
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
||||
<u:IconImageConverter x:Key="iconImageConverter"/>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" x:Name="_iconColumn" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="60" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:IconLabel
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
||||
AutomationProperties.IsInAccessibleTree="False"
|
||||
AutomationId="CipherTypeIcon" />
|
||||
|
||||
<ff:CachedImage
|
||||
x:Name="_iconImage"
|
||||
Grid.Column="0"
|
||||
BitmapOptimizations="True"
|
||||
ErrorPlaceholder="login.png"
|
||||
LoadingPlaceholder="login.png"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Margin="9"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
Aspect="AspectFit"
|
||||
IsVisible="{Binding ShowIconImage}"
|
||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="False"
|
||||
AutomationId="CipherWebsiteIcon" />
|
||||
|
||||
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
StyleClass="list-title, list-title-platform"
|
||||
Text="{Binding Cipher.Name}"
|
||||
AutomationId="CipherNameLabel" />
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
StyleClass="list-subtitle, list-subtitle-platform"
|
||||
Text="{Binding Cipher.SubTitle}"
|
||||
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
|
||||
Converter={StaticResource stringHasValueConverter}}"
|
||||
AutomationId="CipherSubTitleLabel" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
||||
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Shared}"
|
||||
AutomationId="CipherInCollectionIcon" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="2"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
|
||||
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Attachments}"
|
||||
AutomationId="CipherWithAttachmentsIcon" />
|
||||
</Grid>
|
||||
|
||||
<controls:MiButton
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||
Clicked="MoreButton_Clicked"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
HorizontalOptions="EndAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}"
|
||||
AutomationId="CipherOptionsButton" />
|
||||
|
||||
</controls:ExtendedGrid>
|
||||
@@ -1,82 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class CipherViewCell : ExtendedGrid
|
||||
{
|
||||
private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
|
||||
private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
|
||||
|
||||
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
|
||||
nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay);
|
||||
|
||||
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
|
||||
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell));
|
||||
|
||||
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
||||
nameof(ButtonCommand), typeof(ICommand), typeof(CipherViewCell));
|
||||
|
||||
public CipherViewCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var fontScale = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService").GetSystemFontSizeScale();
|
||||
_iconColumn.Width = new GridLength(ICON_COLUMN_DEFAULT_WIDTH * fontScale, GridUnitType.Absolute);
|
||||
_iconImage.WidthRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
|
||||
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
|
||||
}
|
||||
|
||||
public bool? WebsiteIconsEnabled
|
||||
{
|
||||
get => (bool)GetValue(WebsiteIconsEnabledProperty);
|
||||
set => SetValue(WebsiteIconsEnabledProperty, value);
|
||||
}
|
||||
|
||||
public CipherView Cipher
|
||||
{
|
||||
get => GetValue(CipherProperty) as CipherView;
|
||||
set => SetValue(CipherProperty, value);
|
||||
}
|
||||
|
||||
public ICommand ButtonCommand
|
||||
{
|
||||
get => GetValue(ButtonCommandProperty) as ICommand;
|
||||
set => SetValue(ButtonCommandProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
if (propertyName == CipherProperty.PropertyName)
|
||||
{
|
||||
if (Cipher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BindingContext = new CipherViewCellViewModel(Cipher, WebsiteIconsEnabled ?? false);
|
||||
}
|
||||
else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
|
||||
{
|
||||
if (Cipher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((CipherViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoreButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var cipher = ((sender as MiButton)?.BindingContext as CipherViewCellViewModel)?.Cipher;
|
||||
if (cipher != null)
|
||||
{
|
||||
ButtonCommand?.Execute(cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class CipherViewCellViewModel : ExtendedViewModel
|
||||
{
|
||||
private CipherView _cipher;
|
||||
private bool _websiteIconsEnabled;
|
||||
private string _iconImageSource = string.Empty;
|
||||
|
||||
public CipherViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
|
||||
{
|
||||
Cipher = cipherView;
|
||||
WebsiteIconsEnabled = websiteIconsEnabled;
|
||||
}
|
||||
|
||||
public CipherView Cipher
|
||||
{
|
||||
get => _cipher;
|
||||
set => SetProperty(ref _cipher, value);
|
||||
}
|
||||
|
||||
public bool WebsiteIconsEnabled
|
||||
{
|
||||
get => _websiteIconsEnabled;
|
||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||
}
|
||||
|
||||
public bool ShowIconImage
|
||||
{
|
||||
get => WebsiteIconsEnabled
|
||||
&& !string.IsNullOrWhiteSpace(Cipher.LaunchUri)
|
||||
&& IconImageSource != null;
|
||||
}
|
||||
|
||||
public string IconImageSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||
{
|
||||
_iconImageSource = IconImageHelper.GetIconImage(Cipher);
|
||||
}
|
||||
return _iconImageSource;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.Forms;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class CircularProgressbarView : SKCanvasView
|
||||
{
|
||||
private Circle _circle;
|
||||
|
||||
public static readonly BindableProperty ProgressProperty = BindableProperty.Create(
|
||||
nameof(Progress), typeof(double), typeof(CircularProgressbarView), propertyChanged: OnProgressChanged);
|
||||
|
||||
public static readonly BindableProperty RadiusProperty = BindableProperty.Create(
|
||||
nameof(Radius), typeof(float), typeof(CircularProgressbarView), 15f);
|
||||
|
||||
public static readonly BindableProperty StrokeWidthProperty = BindableProperty.Create(
|
||||
nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f);
|
||||
|
||||
public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(
|
||||
nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromHex("175DDC"));
|
||||
|
||||
public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create(
|
||||
nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromHex("dd4b39"));
|
||||
|
||||
public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create(
|
||||
nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.White);
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get { return (double)GetValue(ProgressProperty); }
|
||||
set { SetValue(ProgressProperty, value); }
|
||||
}
|
||||
|
||||
public float Radius
|
||||
{
|
||||
get => (float)GetValue(RadiusProperty);
|
||||
set => SetValue(RadiusProperty, value);
|
||||
}
|
||||
public float StrokeWidth
|
||||
{
|
||||
get => (float)GetValue(StrokeWidthProperty);
|
||||
set => SetValue(StrokeWidthProperty, value);
|
||||
}
|
||||
|
||||
public Color ProgressColor
|
||||
{
|
||||
get => (Color)GetValue(ProgressColorProperty);
|
||||
set => SetValue(ProgressColorProperty, value);
|
||||
}
|
||||
|
||||
public Color EndingProgressColor
|
||||
{
|
||||
get => (Color)GetValue(EndingProgressColorProperty);
|
||||
set => SetValue(EndingProgressColorProperty, value);
|
||||
}
|
||||
|
||||
public Color BackgroundProgressColor
|
||||
{
|
||||
get => (Color)GetValue(BackgroundProgressColorProperty);
|
||||
set => SetValue(BackgroundProgressColorProperty, value);
|
||||
}
|
||||
|
||||
private static void OnProgressChanged(BindableObject bindable, object oldvalue, object newvalue)
|
||||
{
|
||||
var context = bindable as CircularProgressbarView;
|
||||
context.InvalidateSurface();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
if (propertyName == nameof(Progress))
|
||||
{
|
||||
_circle = new Circle(Radius * (float)DeviceDisplay.MainDisplayInfo.Density, (info) => new SKPoint((float)info.Width / 2, (float)info.Height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
|
||||
{
|
||||
base.OnPaintSurface(e);
|
||||
if (_circle != null)
|
||||
{
|
||||
_circle.CalculateCenter(e.Info);
|
||||
e.Surface.Canvas.Clear();
|
||||
DrawCircle(e.Surface.Canvas, _circle, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, BackgroundProgressColor.ToSKColor());
|
||||
DrawArc(e.Surface.Canvas, _circle, () => (float)Progress, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, ProgressColor.ToSKColor(), EndingProgressColor.ToSKColor());
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCircle(SKCanvas canvas, Circle circle, float strokewidth, SKColor color)
|
||||
{
|
||||
canvas.DrawCircle(circle.Center, circle.Redius,
|
||||
new SKPaint()
|
||||
{
|
||||
StrokeWidth = strokewidth,
|
||||
Color = color,
|
||||
IsStroke = true,
|
||||
IsAntialias = true
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawArc(SKCanvas canvas, Circle circle, Func<float> progress, float strokewidth, SKColor color, SKColor progressEndColor)
|
||||
{
|
||||
var progressValue = progress();
|
||||
var angle = progressValue * 3.6f;
|
||||
canvas.DrawArc(circle.Rect, 270, angle, false,
|
||||
new SKPaint()
|
||||
{
|
||||
StrokeWidth = strokewidth,
|
||||
Color = progressValue < 20f ? progressEndColor : color,
|
||||
IsStroke = true,
|
||||
IsAntialias = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class Circle
|
||||
{
|
||||
private readonly Func<SKImageInfo, SKPoint> _centerFunc;
|
||||
|
||||
public Circle(float redius, Func<SKImageInfo, SKPoint> centerFunc)
|
||||
{
|
||||
_centerFunc = centerFunc;
|
||||
Redius = redius;
|
||||
}
|
||||
public SKPoint Center { get; set; }
|
||||
public float Redius { get; set; }
|
||||
public SKRect Rect => new SKRect(Center.X - Redius, Center.Y - Redius, Center.X + Redius, Center.Y + Redius);
|
||||
|
||||
public void CalculateCenter(SKImageInfo argsInfo)
|
||||
{
|
||||
Center = _centerFunc(argsInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class CustomLabel : Label
|
||||
{
|
||||
public CustomLabel()
|
||||
{
|
||||
}
|
||||
|
||||
public int? FontWeight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<Grid
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:Class="Bit.App.Controls.DateTimePicker"
|
||||
ColumnDefinitions="*,*">
|
||||
<controls:ExtendedDatePicker
|
||||
x:Name="_datePicker"
|
||||
Grid.Column="0"
|
||||
NullableDate="{Binding Date, Mode=TwoWay}"
|
||||
Format="d"
|
||||
AutomationProperties.IsInAccessibleTree="True" />
|
||||
<controls:ExtendedTimePicker
|
||||
x:Name="_timePicker"
|
||||
Grid.Column="1"
|
||||
NullableTime="{Binding Time, Mode=TwoWay}"
|
||||
Format="t"
|
||||
AutomationProperties.IsInAccessibleTree="True" />
|
||||
</Grid>
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.CommunityToolkit.UI.Views;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class DateTimePicker : Grid
|
||||
{
|
||||
public DateTimePicker()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
|
||||
if (propertyName == nameof(BindingContext)
|
||||
&&
|
||||
BindingContext is DateTimeViewModel dateTimeViewModel)
|
||||
{
|
||||
AutomationProperties.SetName(_datePicker, dateTimeViewModel.DateName);
|
||||
AutomationProperties.SetName(_timePicker, dateTimeViewModel.TimeName);
|
||||
|
||||
_datePicker.PlaceHolder = dateTimeViewModel.DatePlaceholder;
|
||||
_timePicker.PlaceHolder = dateTimeViewModel.TimePlaceholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LazyDateTimePicker : LazyView<DateTimePicker>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class DateTimeViewModel : ExtendedViewModel
|
||||
{
|
||||
DateTime? _date;
|
||||
TimeSpan? _time;
|
||||
|
||||
public DateTimeViewModel(string dateName, string timeName)
|
||||
{
|
||||
DateName = dateName;
|
||||
TimeName = timeName;
|
||||
}
|
||||
|
||||
public Action<DateTime?> OnDateChanged { get; set; }
|
||||
public Action<TimeSpan?> OnTimeChanged { get; set; }
|
||||
|
||||
public DateTime? Date
|
||||
{
|
||||
get => _date;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _date, value))
|
||||
{
|
||||
OnDateChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public TimeSpan? Time
|
||||
{
|
||||
get => _time;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _time, value))
|
||||
{
|
||||
OnTimeChanged?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DateName { get; }
|
||||
public string TimeName { get; }
|
||||
|
||||
public string DatePlaceholder { get; set; }
|
||||
public string TimePlaceholder { get; set; }
|
||||
|
||||
public DateTime? DateTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Date.HasValue)
|
||||
{
|
||||
if (Time.HasValue)
|
||||
{
|
||||
return Date.Value.Add(Time.Value);
|
||||
}
|
||||
return Date;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
Date = value?.Date;
|
||||
Time = value?.Date.TimeOfDay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Linq;
|
||||
using Xamarin.CommunityToolkit.Converters;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedCollectionView : CollectionView
|
||||
{
|
||||
public string ExtraDataForLogging { get; set; }
|
||||
}
|
||||
|
||||
public class SelectionChangedEventArgsConverter : BaseNullableConverterOneWay<SelectionChangedEventArgs, object>
|
||||
{
|
||||
public override object? ConvertFrom(SelectionChangedEventArgs? value)
|
||||
{
|
||||
return value?.CurrentSelection.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedDatePicker : DatePicker
|
||||
{
|
||||
private string _format;
|
||||
|
||||
public static readonly BindableProperty PlaceHolderProperty = BindableProperty.Create(
|
||||
nameof(PlaceHolder), typeof(string), typeof(ExtendedDatePicker));
|
||||
|
||||
public string PlaceHolder
|
||||
{
|
||||
get { return (string)GetValue(PlaceHolderProperty); }
|
||||
set { SetValue(PlaceHolderProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty NullableDateProperty = BindableProperty.Create(
|
||||
nameof(NullableDate), typeof(DateTime?), typeof(ExtendedDatePicker));
|
||||
|
||||
public DateTime? NullableDate
|
||||
{
|
||||
get { return (DateTime?)GetValue(NullableDateProperty); }
|
||||
set
|
||||
{
|
||||
SetValue(NullableDateProperty, value);
|
||||
UpdateDate();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDate()
|
||||
{
|
||||
if (NullableDate.HasValue)
|
||||
{
|
||||
if (_format != null)
|
||||
{
|
||||
Format = _format;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Format = PlaceHolder;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
if (BindingContext != null)
|
||||
{
|
||||
_format = Format;
|
||||
UpdateDate();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
|
||||
if (propertyName == DateProperty.PropertyName || (propertyName == IsFocusedProperty.PropertyName &&
|
||||
!IsFocused && (Date.ToString("d") ==
|
||||
DateTime.Now.ToString("d"))))
|
||||
{
|
||||
NullableDate = Date;
|
||||
UpdateDate();
|
||||
}
|
||||
|
||||
if (propertyName == NullableDateProperty.PropertyName)
|
||||
{
|
||||
if (NullableDate.HasValue)
|
||||
{
|
||||
Date = NullableDate.Value;
|
||||
if (Date.ToString(_format) == DateTime.Now.ToString(_format))
|
||||
{
|
||||
UpdateDate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedGrid : Grid
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Bit.App.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedSearchBar : SearchBar
|
||||
{
|
||||
public ExtendedSearchBar()
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
if (ThemeManager.UsingLightTheme)
|
||||
{
|
||||
TextColor = Color.Black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedSlider : Slider
|
||||
{
|
||||
public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create(
|
||||
nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.FromHex("b5b5b5"));
|
||||
|
||||
public Color ThumbBorderColor
|
||||
{
|
||||
get => (Color)GetValue(ThumbBorderColorProperty);
|
||||
set => SetValue(ThumbBorderColorProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedStackLayout : StackLayout
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedStepper : Stepper
|
||||
{
|
||||
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
|
||||
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.White);
|
||||
|
||||
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
|
||||
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Black);
|
||||
|
||||
public Color StepperBackgroundColor
|
||||
{
|
||||
get => (Color)GetValue(StepperBackgroundColorProperty);
|
||||
set => SetValue(StepperBackgroundColorProperty, value);
|
||||
}
|
||||
|
||||
public Color StepperForegroundColor
|
||||
{
|
||||
get => (Color)GetValue(StepperForegroundColorProperty);
|
||||
set => SetValue(StepperForegroundColorProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedTimePicker : TimePicker
|
||||
{
|
||||
private string _format;
|
||||
|
||||
public static readonly BindableProperty PlaceHolderProperty = BindableProperty.Create(
|
||||
nameof(PlaceHolder), typeof(string), typeof(ExtendedTimePicker));
|
||||
|
||||
public string PlaceHolder
|
||||
{
|
||||
get { return (string)GetValue(PlaceHolderProperty); }
|
||||
set { SetValue(PlaceHolderProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly BindableProperty NullableTimeProperty = BindableProperty.Create(
|
||||
nameof(NullableTime), typeof(TimeSpan?), typeof(ExtendedTimePicker));
|
||||
|
||||
public TimeSpan? NullableTime
|
||||
{
|
||||
get { return (TimeSpan?)GetValue(NullableTimeProperty); }
|
||||
set
|
||||
{
|
||||
SetValue(NullableTimeProperty, value);
|
||||
UpdateTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTime()
|
||||
{
|
||||
if (NullableTime.HasValue)
|
||||
{
|
||||
if (_format != null)
|
||||
{
|
||||
Format = _format;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Format = PlaceHolder;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
if (BindingContext != null)
|
||||
{
|
||||
_format = Format;
|
||||
UpdateTime();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
|
||||
if (propertyName == TimeProperty.PropertyName || (propertyName == IsFocusedProperty.PropertyName &&
|
||||
!IsFocused && (Time.ToString("t") ==
|
||||
DateTime.Now.TimeOfDay.ToString("t"))))
|
||||
{
|
||||
NullableTime = Time;
|
||||
UpdateTime();
|
||||
}
|
||||
|
||||
if (propertyName == NullableTimeProperty.PropertyName)
|
||||
{
|
||||
if (NullableTime.HasValue)
|
||||
{
|
||||
Time = NullableTime.Value;
|
||||
if (Time.ToString(_format) == DateTime.Now.TimeOfDay.ToString(_format))
|
||||
{
|
||||
UpdateTime();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class ExtendedToolbarItem : ToolbarItem
|
||||
{
|
||||
public bool UseOriginalImage { get; set; }
|
||||
|
||||
// HACK: For the issue of correctly updating the avatar toolbar item color on iOS
|
||||
// we need to subscribe to the PropertyChanged event of the ToolbarItem on the CustomNavigationRenderer
|
||||
// The problem is that there are a lot of private places where the navigation renderer disposes objects
|
||||
// that we don't have access to, and that we should in order to properly prevent memory leaks
|
||||
// So as a hack solution we have this OnAppearing/OnDisappearing actions and methods to be called on page lifecycle
|
||||
// to subscribe/unsubscribe indirectly on the CustomNavigationRenderer
|
||||
public Action OnAppearingAction { get; set; }
|
||||
public Action OnDisappearingAction { get; set; }
|
||||
|
||||
public void OnAppearing()
|
||||
{
|
||||
OnAppearingAction?.Invoke();
|
||||
}
|
||||
|
||||
public void OnDisappearing()
|
||||
{
|
||||
OnDisappearingAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<ContentView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:Class="Bit.App.Controls.ExternalLinkItemView"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:Name="_contentView">
|
||||
<ContentView.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding GoToLinkCommand, Mode=OneWay, Source={x:Reference _contentView}}" />
|
||||
</ContentView.GestureRecognizers>
|
||||
<StackLayout
|
||||
Orientation="Horizontal">
|
||||
<controls:CustomLabel
|
||||
Text="{Binding Title, Mode=OneWay, Source={x:Reference _contentView}}"
|
||||
HorizontalOptions="StartAndExpand"
|
||||
LineBreakMode="TailTruncation" />
|
||||
|
||||
<controls:IconLabel
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ShareSquare}}"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
HorizontalOptions="End"
|
||||
VerticalOptions="Center"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{Binding Title, Mode=OneWay, Source={x:Reference _contentView}}" />
|
||||
|
||||
</StackLayout>
|
||||
</ContentView>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class ExternalLinkItemView : ContentView
|
||||
{
|
||||
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
|
||||
nameof(Title), typeof(string), typeof(ExternalLinkItemView), null, BindingMode.OneWay);
|
||||
|
||||
public static readonly BindableProperty GoToLinkCommandProperty = BindableProperty.Create(
|
||||
nameof(GoToLinkCommand), typeof(ICommand), typeof(ExternalLinkItemView));
|
||||
|
||||
public ExternalLinkItemView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return (string)GetValue(TitleProperty); }
|
||||
set { SetValue(TitleProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand GoToLinkCommand
|
||||
{
|
||||
get => GetValue(GoToLinkCommandProperty) as ICommand;
|
||||
set => SetValue(GoToLinkCommandProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class HybridWebView : View
|
||||
{
|
||||
private Action<string> _func;
|
||||
|
||||
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: nameof(Uri),
|
||||
returnType: typeof(string), declaringType: typeof(HybridWebView), defaultValue: default(string));
|
||||
|
||||
public string Uri
|
||||
{
|
||||
get { return (string)GetValue(UriProperty); }
|
||||
set { SetValue(UriProperty, value); }
|
||||
}
|
||||
|
||||
public void RegisterAction(Action<string> callback)
|
||||
{
|
||||
_func = callback;
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
_func = null;
|
||||
}
|
||||
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
_func?.Invoke(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Bit.App.Effects;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class IconButton : Button
|
||||
{
|
||||
public IconButton()
|
||||
{
|
||||
Padding = 0;
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "bwi-font";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "bwi-font.ttf#bwi-font";
|
||||
break;
|
||||
}
|
||||
|
||||
Effects.Add(new RemoveFontPaddingEffect());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Bit.App.Effects;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class IconLabel : Label
|
||||
{
|
||||
public bool ShouldUpdateFontSizeDynamicallyForAccesibility { get; set; }
|
||||
|
||||
public IconLabel()
|
||||
{
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "bwi-font";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "bwi-font.ttf#bwi-font";
|
||||
break;
|
||||
}
|
||||
|
||||
Effects.Add(new RemoveFontPaddingEffect());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Frame xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.IconLabelButton"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:Name="_iconLabelButton"
|
||||
HeightRequest="45"
|
||||
Padding="1"
|
||||
StyleClass="btn-icon-secondary"
|
||||
BackgroundColor="{Binding IconLabelBorderColor, Source={x:Reference _iconLabelButton}}"
|
||||
BorderColor="Transparent"
|
||||
HasShadow="False">
|
||||
<Frame.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding ButtonCommand, Source={x:Reference _iconLabelButton}}" />
|
||||
</Frame.GestureRecognizers>
|
||||
<Frame
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
CornerRadius="{Binding CornerRadius, Source={x:Reference _iconLabelButton}}"
|
||||
BackgroundColor="{Binding IconLabelBackgroundColor, Source={x:Reference _iconLabelButton}}"
|
||||
BorderColor="Transparent"
|
||||
IsClippedToBounds="True"
|
||||
HasShadow="False">
|
||||
<StackLayout
|
||||
Orientation="Horizontal"
|
||||
HorizontalOptions="Center">
|
||||
<controls:IconLabel
|
||||
VerticalOptions="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
FontSize="Large"
|
||||
TextColor="{Binding IconLabelColor, Source={x:Reference _iconLabelButton}}"
|
||||
Text="{Binding Icon, Source={x:Reference _iconLabelButton}}">
|
||||
</controls:IconLabel>
|
||||
<Label
|
||||
VerticalOptions="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextColor="{Binding IconLabelColor, Source={x:Reference _iconLabelButton}}"
|
||||
FontSize="Medium"
|
||||
Text="{Binding Label, Source={x:Reference _iconLabelButton}}"/>
|
||||
</StackLayout>
|
||||
</Frame>
|
||||
</Frame>
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class IconLabelButton : Frame
|
||||
{
|
||||
public static readonly BindableProperty IconProperty = BindableProperty.Create(
|
||||
nameof(Icon), typeof(string), typeof(IconLabelButton));
|
||||
|
||||
public static readonly BindableProperty LabelProperty = BindableProperty.Create(
|
||||
nameof(Label), typeof(string), typeof(IconLabelButton));
|
||||
|
||||
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
||||
nameof(ButtonCommand), typeof(ICommand), typeof(IconLabelButton));
|
||||
|
||||
public static readonly BindableProperty IconLabelColorProperty = BindableProperty.Create(
|
||||
nameof(IconLabelColor), typeof(Color), typeof(IconLabelButton), Color.White);
|
||||
|
||||
public static readonly BindableProperty IconLabelBackgroundColorProperty = BindableProperty.Create(
|
||||
nameof(IconLabelBackgroundColor), typeof(Color), typeof(IconLabelButton), Color.White);
|
||||
|
||||
public static readonly BindableProperty IconLabelBorderColorProperty = BindableProperty.Create(
|
||||
nameof(IconLabelBorderColor), typeof(Color), typeof(IconLabelButton), Color.White);
|
||||
|
||||
public IconLabelButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get => GetValue(IconProperty) as string;
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public string Label
|
||||
{
|
||||
get => GetValue(LabelProperty) as string;
|
||||
set => SetValue(LabelProperty, value);
|
||||
}
|
||||
|
||||
public ICommand ButtonCommand
|
||||
{
|
||||
get => GetValue(ButtonCommandProperty) as ICommand;
|
||||
set => SetValue(ButtonCommandProperty, value);
|
||||
}
|
||||
|
||||
public Color IconLabelColor
|
||||
{
|
||||
get { return (Color)GetValue(IconLabelColorProperty); }
|
||||
set { SetValue(IconLabelColorProperty, value); }
|
||||
}
|
||||
|
||||
public Color IconLabelBackgroundColor
|
||||
{
|
||||
get { return (Color)GetValue(IconLabelBackgroundColorProperty); }
|
||||
set { SetValue(IconLabelBackgroundColorProperty, value); }
|
||||
}
|
||||
|
||||
public Color IconLabelBorderColor
|
||||
{
|
||||
get { return (Color)GetValue(IconLabelBorderColorProperty); }
|
||||
set { SetValue(IconLabelBorderColorProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class MiButton : Button
|
||||
{
|
||||
public MiButton()
|
||||
{
|
||||
Padding = 0;
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "Material Icons";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "MaterialIcons_Regular.ttf#Material Icons";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class MiLabel : Label
|
||||
{
|
||||
public MiLabel()
|
||||
{
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "Material Icons";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "MaterialIcons_Regular.ttf#Material Icons";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class MonoEntry : Entry
|
||||
{
|
||||
public MonoEntry()
|
||||
{
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "Menlo-Regular";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class MonoLabel : Label
|
||||
{
|
||||
public MonoLabel()
|
||||
{
|
||||
switch (Device.RuntimePlatform)
|
||||
{
|
||||
case Device.iOS:
|
||||
FontFamily = "Menlo-Regular";
|
||||
break;
|
||||
case Device.Android:
|
||||
FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public interface IPasswordStrengthable
|
||||
{
|
||||
string Password { get; }
|
||||
List<string> UserInputs { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using Bit.Core.Attributes;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public enum PasswordStrengthLevel
|
||||
{
|
||||
[LocalizableEnum("Weak")]
|
||||
VeryWeak,
|
||||
[LocalizableEnum("Weak")]
|
||||
Weak,
|
||||
[LocalizableEnum("Good")]
|
||||
Good,
|
||||
[LocalizableEnum("Strong")]
|
||||
Strong
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<StackLayout
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="controls:PasswordStrengthViewModel"
|
||||
x:Class="Bit.App.Controls.PasswordStrengthProgressBar"
|
||||
StyleClass="box">
|
||||
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:LocalizableEnumConverter x:Key="localizableEnum" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
|
||||
<ProgressBar
|
||||
x:Name="_progressBar"
|
||||
u:ProgressBarExtensions.AnimatedProgress="{Binding PasswordStrength}"
|
||||
ScaleY="2" />
|
||||
|
||||
<Label
|
||||
x:Name="_progressLabel"
|
||||
Text="{Binding PasswordStrengthLevel, Converter={StaticResource localizableEnum}, TargetNullValue=' ' }"
|
||||
StyleClass="box-footer-label" />
|
||||
|
||||
</StackLayout>
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class PasswordStrengthProgressBar : StackLayout
|
||||
{
|
||||
public static readonly BindableProperty PasswordStrengthLevelProperty = BindableProperty.Create(
|
||||
nameof(PasswordStrengthLevel),
|
||||
typeof(PasswordStrengthLevel),
|
||||
typeof(PasswordStrengthProgressBar),
|
||||
propertyChanged: OnControlPropertyChanged);
|
||||
|
||||
public static readonly BindableProperty VeryWeakColorProperty = BindableProperty.Create(
|
||||
nameof(VeryWeakColor),
|
||||
typeof(Color),
|
||||
typeof(PasswordStrengthProgressBar),
|
||||
propertyChanged: OnControlPropertyChanged);
|
||||
|
||||
public static readonly BindableProperty WeakColorProperty = BindableProperty.Create(
|
||||
nameof(WeakColor),
|
||||
typeof(Color),
|
||||
typeof(PasswordStrengthProgressBar),
|
||||
propertyChanged: OnControlPropertyChanged);
|
||||
|
||||
public static readonly BindableProperty GoodColorProperty = BindableProperty.Create(
|
||||
nameof(GoodColor),
|
||||
typeof(Color),
|
||||
typeof(PasswordStrengthProgressBar),
|
||||
propertyChanged: OnControlPropertyChanged);
|
||||
|
||||
public static readonly BindableProperty StrongColorProperty = BindableProperty.Create(
|
||||
nameof(StrongColor),
|
||||
typeof(Color),
|
||||
typeof(PasswordStrengthProgressBar),
|
||||
propertyChanged: OnControlPropertyChanged);
|
||||
|
||||
public PasswordStrengthLevel? PasswordStrengthLevel
|
||||
{
|
||||
get { return (PasswordStrengthLevel?)GetValue(PasswordStrengthLevelProperty); }
|
||||
set { SetValue(PasswordStrengthLevelProperty, value); }
|
||||
}
|
||||
|
||||
public Color VeryWeakColor
|
||||
{
|
||||
get { return (Color)GetValue(VeryWeakColorProperty); }
|
||||
set { SetValue(VeryWeakColorProperty, value); }
|
||||
}
|
||||
|
||||
public Color WeakColor
|
||||
{
|
||||
get { return (Color)GetValue(WeakColorProperty); }
|
||||
set { SetValue(WeakColorProperty, value); }
|
||||
}
|
||||
|
||||
public Color GoodColor
|
||||
{
|
||||
get { return (Color)GetValue(GoodColorProperty); }
|
||||
set { SetValue(GoodColorProperty, value); }
|
||||
}
|
||||
|
||||
public Color StrongColor
|
||||
{
|
||||
get { return (Color)GetValue(StrongColorProperty); }
|
||||
set { SetValue(StrongColorProperty, value); }
|
||||
}
|
||||
|
||||
public PasswordStrengthProgressBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
SetBinding(PasswordStrengthProgressBar.PasswordStrengthLevelProperty, new Binding() { Path = nameof(PasswordStrengthViewModel.PasswordStrengthLevel) });
|
||||
UpdateColors();
|
||||
}
|
||||
|
||||
private static void OnControlPropertyChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
(bindable as PasswordStrengthProgressBar)?.UpdateColors();
|
||||
}
|
||||
|
||||
public void UpdateColors()
|
||||
{
|
||||
if (_progressBar == null || _progressLabel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_progressBar.ProgressColor = GetColorForStrength();
|
||||
_progressLabel.TextColor = _progressBar.ProgressColor;
|
||||
}
|
||||
|
||||
private Color GetColorForStrength()
|
||||
{
|
||||
switch (PasswordStrengthLevel)
|
||||
{
|
||||
case Controls.PasswordStrengthLevel.VeryWeak:
|
||||
return VeryWeakColor;
|
||||
case Controls.PasswordStrengthLevel.Weak:
|
||||
return WeakColor;
|
||||
case Controls.PasswordStrengthLevel.Good:
|
||||
return GoodColor;
|
||||
case Controls.PasswordStrengthLevel.Strong:
|
||||
return StrongColor;
|
||||
default:
|
||||
return Color.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class PasswordStrengthViewModel : ExtendedViewModel
|
||||
{
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly IPasswordStrengthable _passwordStrengthable;
|
||||
private double _passwordStrength;
|
||||
private Color _passwordColor;
|
||||
private PasswordStrengthLevel? _passwordStrengthLevel;
|
||||
|
||||
public PasswordStrengthViewModel(IPasswordStrengthable passwordStrengthable)
|
||||
{
|
||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||
_passwordStrengthable = passwordStrengthable;
|
||||
}
|
||||
|
||||
public double PasswordStrength
|
||||
{
|
||||
get => _passwordStrength;
|
||||
set => SetProperty(ref _passwordStrength, value);
|
||||
}
|
||||
|
||||
public PasswordStrengthLevel? PasswordStrengthLevel
|
||||
{
|
||||
get => _passwordStrengthLevel;
|
||||
set => SetProperty(ref _passwordStrengthLevel, value);
|
||||
}
|
||||
|
||||
public List<string> GetPasswordStrengthUserInput(string email) => _passwordGenerationService.GetPasswordStrengthUserInput(email);
|
||||
|
||||
public void CalculatePasswordStrength()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_passwordStrengthable.Password))
|
||||
{
|
||||
PasswordStrength = 0;
|
||||
PasswordStrengthLevel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var passwordStrength = _passwordGenerationService.PasswordStrength(_passwordStrengthable.Password, _passwordStrengthable.UserInputs);
|
||||
// The passwordStrength.Score is 0..4, convertion was made to be used as a progress directly by the control 0..1
|
||||
PasswordStrength = (passwordStrength.Score + 1f) / 5f;
|
||||
if (PasswordStrength <= 0.4f)
|
||||
{
|
||||
PasswordStrengthLevel = Controls.PasswordStrengthLevel.VeryWeak;
|
||||
}
|
||||
else if (PasswordStrength <= 0.6f)
|
||||
{
|
||||
PasswordStrengthLevel = Controls.PasswordStrengthLevel.Weak;
|
||||
}
|
||||
else if (PasswordStrength <= 0.8f)
|
||||
{
|
||||
PasswordStrengthLevel = Controls.PasswordStrengthLevel.Good;
|
||||
}
|
||||
else
|
||||
{
|
||||
PasswordStrengthLevel = Controls.PasswordStrengthLevel.Strong;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
[Obsolete]
|
||||
public class RepeaterView : StackLayout
|
||||
{
|
||||
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
|
||||
nameof(ItemTemplate), typeof(DataTemplate), typeof(RepeaterView), default(DataTemplate));
|
||||
|
||||
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
|
||||
nameof(ItemsSource), typeof(ICollection), typeof(RepeaterView), null, BindingMode.OneWay,
|
||||
propertyChanged: ItemsSourceChanging);
|
||||
|
||||
public RepeaterView()
|
||||
{
|
||||
Spacing = 0;
|
||||
}
|
||||
|
||||
public ICollection ItemsSource
|
||||
{
|
||||
get => GetValue(ItemsSourceProperty) as ICollection;
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
|
||||
public DataTemplate ItemTemplate
|
||||
{
|
||||
get => GetValue(ItemTemplateProperty) as DataTemplate;
|
||||
set => SetValue(ItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object sender,
|
||||
NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
|
||||
{
|
||||
Populate();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
if (propertyName == ItemTemplateProperty.PropertyName || propertyName == ItemsSourceProperty.PropertyName)
|
||||
{
|
||||
Populate();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBindingContextChanged()
|
||||
{
|
||||
base.OnBindingContextChanged();
|
||||
Populate();
|
||||
}
|
||||
|
||||
protected virtual View ViewFor(object item)
|
||||
{
|
||||
View view = null;
|
||||
var template = ItemTemplate;
|
||||
if (template != null)
|
||||
{
|
||||
if (template is DataTemplateSelector selector)
|
||||
{
|
||||
template = selector.SelectTemplate(item, this);
|
||||
}
|
||||
var content = template.CreateContent();
|
||||
view = content is View ? content as View : ((ViewCell)content).View;
|
||||
view.BindingContext = item;
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
private void Populate()
|
||||
{
|
||||
if (ItemsSource != null)
|
||||
{
|
||||
Children.Clear();
|
||||
foreach (var item in ItemsSource)
|
||||
{
|
||||
Children.Add(ViewFor(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ItemsSourceChanging(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
if (oldValue != null && oldValue is INotifyCollectionChanged ov)
|
||||
{
|
||||
ov.CollectionChanged -= (bindable as RepeaterView).OnCollectionChanged;
|
||||
}
|
||||
if (newValue != null && newValue is INotifyCollectionChanged nv)
|
||||
{
|
||||
nv.CollectionChanged += (bindable as RepeaterView).OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class SelectableLabel : Label
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.SendViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
StyleClass="list-row, list-row-platform"
|
||||
RowSpacing="0"
|
||||
ColumnSpacing="0"
|
||||
x:DataType="controls:SendViewCellViewModel">
|
||||
|
||||
<Grid.Resources>
|
||||
<u:SendIconGlyphConverter x:Key="sendIconGlyphConverter"/>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" x:Name="_iconColumn" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="60" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:IconLabel
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
Text="{Binding Send, Converter={StaticResource sendIconGlyphConverter}}"
|
||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
StyleClass="list-title, list-title-platform"
|
||||
Text="{Binding Send.Name}"
|
||||
AutomationId="SendNameLabel" />
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="6"
|
||||
StyleClass="list-subtitle, list-subtitle-platform"
|
||||
Text="{Binding Send.DisplayDate}"
|
||||
AutomationId="SendDateLabel" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ExclamationTriangle}}"
|
||||
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Disabled}"
|
||||
AutomationId="DisabledSendLabel" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="2"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Key}}"
|
||||
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Password}"
|
||||
AutomationId="PasswordProtectedSendLabel" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="3"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Ban}}"
|
||||
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n MaxAccessCountReached}"
|
||||
AutomationId="SendMaxAccessCountReachedLabel" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="4"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clock}}"
|
||||
IsVisible="{Binding Send.Expired, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Expired}"
|
||||
AutomationId="ExpiredSendLabel" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="5"
|
||||
Grid.Row="0"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n PendingDelete}"
|
||||
AutomationId="SendWithPendingDeletionLabel" />
|
||||
</Grid>
|
||||
|
||||
<controls:MiButton
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||
Clicked="MoreButton_Clicked"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
HorizontalOptions="EndAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}"
|
||||
AutomationId="SendOptionsButton" />
|
||||
|
||||
</controls:ExtendedGrid>
|
||||
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class SendViewCell : ExtendedGrid
|
||||
{
|
||||
public static readonly BindableProperty SendProperty = BindableProperty.Create(
|
||||
nameof(Send), typeof(SendView), typeof(SendViewCell), default(SendView), BindingMode.OneWay);
|
||||
|
||||
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
||||
nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell));
|
||||
|
||||
public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
|
||||
nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay);
|
||||
|
||||
public SendViewCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_iconColumn.Width = new GridLength(40 * deviceActionService.GetSystemFontSizeScale(), GridUnitType.Absolute);
|
||||
}
|
||||
|
||||
public SendView Send
|
||||
{
|
||||
get => GetValue(SendProperty) as SendView;
|
||||
set => SetValue(SendProperty, value);
|
||||
}
|
||||
|
||||
public Command<SendView> ButtonCommand
|
||||
{
|
||||
get => GetValue(ButtonCommandProperty) as Command<SendView>;
|
||||
set => SetValue(ButtonCommandProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowOptions
|
||||
{
|
||||
get => (bool)GetValue(ShowOptionsProperty);
|
||||
set => SetValue(ShowOptionsProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(string propertyName = null)
|
||||
{
|
||||
base.OnPropertyChanged(propertyName);
|
||||
if (propertyName == SendProperty.PropertyName)
|
||||
{
|
||||
if (Send == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BindingContext = new SendViewCellViewModel(Send, ShowOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private void MoreButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var send = ((sender as MiButton)?.BindingContext as SendViewCellViewModel)?.Send;
|
||||
if (send != null)
|
||||
{
|
||||
ButtonCommand?.Execute(send);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class SendViewCellViewModel : ExtendedViewModel
|
||||
{
|
||||
private SendView _send;
|
||||
private bool _showOptions;
|
||||
|
||||
public SendViewCellViewModel(SendView sendView, bool showOptions)
|
||||
{
|
||||
Send = sendView;
|
||||
ShowOptions = showOptions;
|
||||
}
|
||||
|
||||
public SendView Send
|
||||
{
|
||||
get => _send;
|
||||
set => SetProperty(ref _send, value);
|
||||
}
|
||||
|
||||
public bool ShowOptions
|
||||
{
|
||||
get => _showOptions;
|
||||
set => SetProperty(ref _showOptions, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Bit.App.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class BaseSettingItemView : ContentView
|
||||
{
|
||||
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
|
||||
nameof(Title), typeof(string), typeof(SwitchItemView), null);
|
||||
|
||||
public static readonly BindableProperty SubtitleProperty = BindableProperty.Create(
|
||||
nameof(Subtitle), typeof(string), typeof(SwitchItemView), null);
|
||||
|
||||
public string Title
|
||||
{
|
||||
get { return (string)GetValue(TitleProperty); }
|
||||
set { SetValue(TitleProperty, value); }
|
||||
}
|
||||
|
||||
public string Subtitle
|
||||
{
|
||||
get { return (string)GetValue(SubtitleProperty); }
|
||||
set { SetValue(SubtitleProperty, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<controls:BaseSettingItemView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:Class="Bit.App.Controls.SettingChooserItemView"
|
||||
x:Name="_contentView"
|
||||
ControlTemplate="{StaticResource SettingControlTemplate}">
|
||||
<controls:BaseSettingItemView.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding ChooseCommand, Mode=OneWay, Source={x:Reference _contentView}}" />
|
||||
</controls:BaseSettingItemView.GestureRecognizers>
|
||||
|
||||
<controls:CustomLabel
|
||||
Text="{Binding DisplayValue, Source={x:Reference _contentView}}"
|
||||
HorizontalTextAlignment="End"
|
||||
TextColor="{DynamicResource MutedColor}"
|
||||
StyleClass="list-sub" />
|
||||
|
||||
</controls:BaseSettingItemView>
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class SettingChooserItemView : BaseSettingItemView
|
||||
{
|
||||
public static readonly BindableProperty DisplayValueProperty = BindableProperty.Create(
|
||||
nameof(DisplayValue), typeof(string), typeof(SettingChooserItemView), null);
|
||||
|
||||
public static readonly BindableProperty ChooseCommandProperty = BindableProperty.Create(
|
||||
nameof(ChooseCommand), typeof(ICommand), typeof(ExternalLinkItemView));
|
||||
|
||||
public string DisplayValue
|
||||
{
|
||||
get { return (string)GetValue(DisplayValueProperty); }
|
||||
set { SetValue(DisplayValueProperty, value); }
|
||||
}
|
||||
|
||||
public SettingChooserItemView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ICommand ChooseCommand
|
||||
{
|
||||
get => GetValue(ChooseCommandProperty) as ICommand;
|
||||
set => SetValue(ChooseCommandProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<controls:BaseSettingItemView
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:Class="Bit.App.Controls.SwitchItemView"
|
||||
x:Name="_contentView"
|
||||
ControlTemplate="{StaticResource SettingControlTemplate}">
|
||||
<controls:BaseSettingItemView.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="ContentView_Tapped" />
|
||||
</controls:BaseSettingItemView.GestureRecognizers>
|
||||
|
||||
<Switch
|
||||
x:Name="_switch"
|
||||
HeightRequest="20"
|
||||
Scale="{OnPlatform iOS=0.8, Android=1}"
|
||||
IsToggled="{Binding IsToggled, Mode=TwoWay, Source={x:Reference _contentView}}"
|
||||
AutomationId="{Binding SwitchAutomationId, Mode=OneWay, Source={x:Reference _contentView}}"/>
|
||||
</controls:BaseSettingItemView>
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class SwitchItemView : BaseSettingItemView
|
||||
{
|
||||
public static readonly BindableProperty IsToggledProperty = BindableProperty.Create(
|
||||
nameof(IsToggled), typeof(bool), typeof(SwitchItemView), null, BindingMode.TwoWay);
|
||||
|
||||
public static readonly BindableProperty SwitchAutomationIdProperty = BindableProperty.Create(
|
||||
nameof(SwitchAutomationId), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay);
|
||||
|
||||
public static readonly BindableProperty ToggleSwitchCommandProperty = BindableProperty.Create(
|
||||
nameof(ToggleSwitchCommand), typeof(ICommand), typeof(ExternalLinkItemView));
|
||||
|
||||
public SwitchItemView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public bool IsToggled
|
||||
{
|
||||
get { return (bool)GetValue(IsToggledProperty); }
|
||||
set { SetValue(IsToggledProperty, value); }
|
||||
}
|
||||
|
||||
public string SwitchAutomationId
|
||||
{
|
||||
get { return (string)GetValue(SwitchAutomationIdProperty); }
|
||||
set { SetValue(SwitchAutomationIdProperty, value); }
|
||||
}
|
||||
|
||||
public ICommand ToggleSwitchCommand
|
||||
{
|
||||
get => GetValue(ToggleSwitchCommandProperty) as ICommand;
|
||||
set => SetValue(ToggleSwitchCommandProperty, value);
|
||||
}
|
||||
|
||||
void ContentView_Tapped(System.Object sender, System.EventArgs e)
|
||||
{
|
||||
_switch.IsToggled = !_switch.IsToggled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public class FabShadowEffect : RoutingEffect
|
||||
{
|
||||
public FabShadowEffect()
|
||||
: base("Bitwarden.FabShadowEffect")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public class FixedSizeEffect : RoutingEffect
|
||||
{
|
||||
public FixedSizeEffect()
|
||||
: base("Bitwarden.FixedSizeEffect")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public class NoEmojiKeyboardEffect : RoutingEffect
|
||||
{
|
||||
public NoEmojiKeyboardEffect()
|
||||
: base("Bitwarden.NoEmojiKeyboardEffect")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public class RemoveFontPaddingEffect : RoutingEffect
|
||||
{
|
||||
public RemoveFontPaddingEffect()
|
||||
: base("Bitwarden.RemoveFontPaddingEffect")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public class ScrollEnabledEffect : RoutingEffect
|
||||
{
|
||||
public static readonly BindableProperty IsScrollEnabledProperty =
|
||||
BindableProperty.CreateAttached("IsScrollEnabled", typeof(bool), typeof(ScrollEnabledEffect), true);
|
||||
|
||||
public static bool GetIsScrollEnabled(BindableObject view)
|
||||
{
|
||||
return (bool)view.GetValue(IsScrollEnabledProperty);
|
||||
}
|
||||
|
||||
public static void SetIsScrollEnabled(BindableObject view, bool value)
|
||||
{
|
||||
view.SetValue(IsScrollEnabledProperty, value);
|
||||
}
|
||||
|
||||
public ScrollEnabledEffect()
|
||||
: base("Bitwarden.ScrollEnabledEffect")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public enum ScrollContentInsetAdjustmentBehavior
|
||||
{
|
||||
Automatic,
|
||||
ScrollableAxes,
|
||||
Never,
|
||||
Always
|
||||
}
|
||||
|
||||
public class ScrollViewContentInsetAdjustmentBehaviorEffect : RoutingEffect
|
||||
{
|
||||
public static readonly BindableProperty ContentInsetAdjustmentBehaviorProperty =
|
||||
BindableProperty.CreateAttached("ContentInsetAdjustmentBehavior", typeof(ScrollContentInsetAdjustmentBehavior), typeof(ScrollViewContentInsetAdjustmentBehaviorEffect), ScrollContentInsetAdjustmentBehavior.Automatic);
|
||||
|
||||
public static ScrollContentInsetAdjustmentBehavior GetContentInsetAdjustmentBehavior(BindableObject view)
|
||||
{
|
||||
return (ScrollContentInsetAdjustmentBehavior)view.GetValue(ContentInsetAdjustmentBehaviorProperty);
|
||||
}
|
||||
|
||||
public static void SetContentInsetAdjustmentBehavior(BindableObject view, ScrollContentInsetAdjustmentBehavior value)
|
||||
{
|
||||
view.SetValue(ContentInsetAdjustmentBehaviorProperty, value);
|
||||
}
|
||||
|
||||
public ScrollViewContentInsetAdjustmentBehaviorEffect()
|
||||
: base($"Bitwarden.{nameof(ScrollViewContentInsetAdjustmentBehaviorEffect)}")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Effects
|
||||
{
|
||||
public class TabBarEffect : RoutingEffect
|
||||
{
|
||||
public TabBarEffect()
|
||||
: base("Bitwarden.TabBarEffect")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.DataTemplateSelectors
|
||||
{
|
||||
public class CustomFieldItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate TextTemplate { get; set; }
|
||||
public DataTemplate BooleanTemplate { get; set; }
|
||||
public DataTemplate LinkedTemplate { get; set; }
|
||||
public DataTemplate HiddenTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case BooleanCustomFieldItemViewModel _:
|
||||
return BooleanTemplate;
|
||||
case LinkedCustomFieldItemViewModel _:
|
||||
return LinkedTemplate;
|
||||
case HiddenCustomFieldItemViewModel _:
|
||||
return HiddenTemplate;
|
||||
default:
|
||||
return TextTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<StackLayout
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.BooleanCustomFieldItemLayout"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:BooleanCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="BooleanCustomFieldNameLabel" />
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
StyleClass="box-value"
|
||||
VerticalOptions="FillAndExpand"
|
||||
VerticalTextAlignment="Center"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2" />
|
||||
<controls:IconLabel
|
||||
Text="{Binding BooleanValue, Mode=OneWay, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Checkbox}}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0, 5, 0, 0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="BooleanCustomFieldValueLabel" />
|
||||
<Switch
|
||||
IsToggled="{Binding BooleanValue}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationId="BooleanCustomFieldValueToggle" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
</StackLayout>
|
||||
@@ -1,12 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class BooleanCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public BooleanCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.HiddenCustomFieldItemLayout"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:HiddenCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
AutomationId="HiddenCustomFieldNameLabel" />
|
||||
<StackLayout
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Converter={StaticResource inverseBool}}">
|
||||
<controls:MonoLabel
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
IsVisible="{Binding ShowHiddenValue}"
|
||||
AutomationId="HiddenCustomFieldValueLabel" />
|
||||
<controls:MonoLabel
|
||||
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
IsVisible="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
||||
<controls:MonoEntry
|
||||
Text="{Binding Field.Value}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
|
||||
IsEnabled="{Binding ShowViewHidden}"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{Binding Field.Name}"
|
||||
AutomationId="HiddenCustomFieldValueEntry">
|
||||
<Entry.Keyboard>
|
||||
<Keyboard x:FactoryMethod="Create">
|
||||
<x:Arguments>
|
||||
<KeyboardFlags>None</KeyboardFlags>
|
||||
</x:Arguments>
|
||||
</Keyboard>
|
||||
</Entry.Keyboard>
|
||||
</controls:MonoEntry>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowHiddenValue, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||
Command="{Binding ToggleHiddenValueCommand}"
|
||||
IsVisible="{Binding ShowViewHidden}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationId="HiddenCustomFieldShowValueButton" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding CopyFieldCommand}"
|
||||
IsVisible="{Binding ShowCopyButton}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Copy}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
||||
@@ -1,12 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class HiddenCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public HiddenCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.LinkedCustomFieldItemLayout"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:LinkedCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
AutomationId="LinkedCustomFieldNameLabel" />
|
||||
<controls:IconLabel
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="LinkedCustomFieldValueLabel" />
|
||||
<StackLayout
|
||||
StyleClass="box-row, box-row-input"
|
||||
IsVisible="{Binding IsEditing}">
|
||||
<Picker
|
||||
x:Name="_linkedFieldOptionPicker"
|
||||
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
||||
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
||||
ItemDisplayBinding="{Binding Key}"
|
||||
StyleClass="box-value"
|
||||
AutomationId="LinkedCustomFieldValuePicker" />
|
||||
</StackLayout>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}"
|
||||
AutomationId="LinkedCustomFieldOptionsButton" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
||||
@@ -1,12 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class LinkedCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public LinkedCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.TextCustomFieldItemLayout"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="cfvm:TextCustomFieldItemViewModel"
|
||||
Spacing="0" Padding="0">
|
||||
<StackLayout.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
||||
</ResourceDictionary>
|
||||
</StackLayout.Resources>
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{Binding Field.Name, Mode=OneWay}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
AutomationId="TextCustomFieldNameLabel" />
|
||||
<Label
|
||||
Text="{Binding ValueText, Mode=OneWay}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||
AutomationId="TextCustomFieldValueLabel" />
|
||||
<Entry
|
||||
Text="{Binding Field.Value}"
|
||||
StyleClass="box-value"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{Binding Field.Name}"
|
||||
AutomationId="TextCustomFieldValueEntry" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding CopyFieldCommand}"
|
||||
IsVisible="{Binding ShowCopyButton}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Copy}"
|
||||
AutomationId="TextCustomFieldCopyValue" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||
Command="{Binding FieldOptionsCommand}"
|
||||
IsVisible="{Binding IsEditing}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Options}"
|
||||
AutomationId="TextCustomFieldOptionsButton" />
|
||||
</Grid>
|
||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||
</StackLayout>
|
||||
@@ -1,12 +0,0 @@
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
||||
{
|
||||
public partial class TextCustomFieldItemLayout : StackLayout
|
||||
{
|
||||
public TextCustomFieldItemLayout()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public abstract class BaseCustomFieldItemViewModel : ExtendedViewModel, ICustomFieldItemViewModel
|
||||
{
|
||||
protected FieldView _field;
|
||||
protected bool _isEditing;
|
||||
private string[] _additionalFieldProperties = new string[]
|
||||
{
|
||||
nameof(ValueText),
|
||||
nameof(ShowCopyButton)
|
||||
};
|
||||
|
||||
public BaseCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand)
|
||||
{
|
||||
_field = field;
|
||||
_isEditing = isEditing;
|
||||
FieldOptionsCommand = new Command(() => fieldOptionsCommand?.Execute(this));
|
||||
}
|
||||
|
||||
public FieldView Field
|
||||
{
|
||||
get => _field;
|
||||
set => SetProperty(ref _field, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ValueText),
|
||||
nameof(ShowCopyButton),
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsEditing => _isEditing;
|
||||
|
||||
public virtual bool ShowCopyButton => false;
|
||||
|
||||
public virtual string ValueText => _field.Value;
|
||||
|
||||
public ICommand FieldOptionsCommand { get; }
|
||||
|
||||
public void TriggerFieldChanged()
|
||||
{
|
||||
TriggerPropertyChanged(nameof(Field), _additionalFieldProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class BooleanCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
public BooleanCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
}
|
||||
|
||||
public bool BooleanValue
|
||||
{
|
||||
get => bool.TryParse(Field.Value, out var boolVal) && boolVal;
|
||||
set
|
||||
{
|
||||
Field.Value = value.ToString().ToLower();
|
||||
TriggerPropertyChanged(nameof(BooleanValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public interface ICustomFieldItemFactory
|
||||
{
|
||||
ICustomFieldItemViewModel CreateCustomFieldItem(FieldView field,
|
||||
bool isEditing,
|
||||
CipherView cipher,
|
||||
IPasswordPromptable passwordPromptable,
|
||||
ICommand copyFieldCommand,
|
||||
ICommand fieldOptionsCommand);
|
||||
}
|
||||
|
||||
public class CustomFieldItemFactory : ICustomFieldItemFactory
|
||||
{
|
||||
readonly II18nService _i18nService;
|
||||
readonly IEventService _eventService;
|
||||
|
||||
public CustomFieldItemFactory(II18nService i18nService, IEventService eventService)
|
||||
{
|
||||
_i18nService = i18nService;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
public ICustomFieldItemViewModel CreateCustomFieldItem(FieldView field,
|
||||
bool isEditing,
|
||||
CipherView cipher,
|
||||
IPasswordPromptable passwordPromptable,
|
||||
ICommand copyFieldCommand,
|
||||
ICommand fieldOptionsCommand)
|
||||
{
|
||||
switch (field.Type)
|
||||
{
|
||||
case FieldType.Text:
|
||||
return new TextCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, copyFieldCommand);
|
||||
case FieldType.Boolean:
|
||||
return new BooleanCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand);
|
||||
case FieldType.Hidden:
|
||||
return new HiddenCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, cipher, passwordPromptable, _eventService, copyFieldCommand);
|
||||
case FieldType.Linked:
|
||||
return new LinkedCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, cipher, _i18nService);
|
||||
default:
|
||||
throw new NotImplementedException("There is no custom field item for field type " + field.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class HiddenCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
private readonly CipherView _cipher;
|
||||
private readonly IPasswordPromptable _passwordPromptable;
|
||||
private readonly IEventService _eventService;
|
||||
private bool _showHiddenValue;
|
||||
|
||||
public HiddenCustomFieldItemViewModel(FieldView field,
|
||||
bool isEditing,
|
||||
ICommand fieldOptionsCommand,
|
||||
CipherView cipher,
|
||||
IPasswordPromptable passwordPromptable,
|
||||
IEventService eventService,
|
||||
ICommand copyFieldCommand)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
_cipher = cipher;
|
||||
_passwordPromptable = passwordPromptable;
|
||||
_eventService = eventService;
|
||||
|
||||
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
|
||||
ToggleHiddenValueCommand = new AsyncCommand(ToggleHiddenValueAsync, (Func<bool>)null, ex =>
|
||||
{
|
||||
#if !FDROID
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
public ICommand CopyFieldCommand { get; }
|
||||
|
||||
public ICommand ToggleHiddenValueCommand { get; set; }
|
||||
|
||||
public bool ShowHiddenValue
|
||||
{
|
||||
get => _showHiddenValue;
|
||||
set => SetProperty(ref _showHiddenValue, value);
|
||||
}
|
||||
|
||||
public bool ShowViewHidden => _cipher.ViewPassword || (_isEditing && _field.NewField);
|
||||
|
||||
public override bool ShowCopyButton => !_isEditing && _cipher.ViewPassword && !string.IsNullOrWhiteSpace(Field.Value);
|
||||
|
||||
public async Task ToggleHiddenValueAsync()
|
||||
{
|
||||
if (!_isEditing && !await _passwordPromptable.PromptPasswordAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ShowHiddenValue = !ShowHiddenValue;
|
||||
if (ShowHiddenValue && (!_isEditing || _cipher?.Id != null))
|
||||
{
|
||||
await _eventService.CollectAsync(
|
||||
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public interface ICustomFieldItemViewModel
|
||||
{
|
||||
FieldView Field { get; set; }
|
||||
|
||||
bool ShowCopyButton { get; }
|
||||
|
||||
void TriggerFieldChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class LinkedCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
private readonly CipherView _cipher;
|
||||
private readonly II18nService _i18nService;
|
||||
private int _linkedFieldOptionSelectedIndex;
|
||||
|
||||
public LinkedCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand, CipherView cipher, II18nService i18nService)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
_cipher = cipher;
|
||||
_i18nService = i18nService;
|
||||
|
||||
LinkedFieldOptionSelectedIndex = Field.LinkedId.HasValue
|
||||
? LinkedFieldOptions.FindIndex(lfo => lfo.Value == Field.LinkedId.Value)
|
||||
: 0;
|
||||
|
||||
if (isEditing && Field.LinkedId is null)
|
||||
{
|
||||
field.LinkedId = LinkedFieldOptions[0].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ValueText
|
||||
{
|
||||
get
|
||||
{
|
||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||
return $"{BitwardenIcons.Link} {_i18nService.T(i18nKey)}";
|
||||
}
|
||||
}
|
||||
|
||||
public int LinkedFieldOptionSelectedIndex
|
||||
{
|
||||
get => _linkedFieldOptionSelectedIndex;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _linkedFieldOptionSelectedIndex, value))
|
||||
{
|
||||
LinkedFieldValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions
|
||||
{
|
||||
get => _cipher.LinkedFieldOptions
|
||||
.Select(kvp => new KeyValuePair<string, LinkedIdType>(_i18nService.T(kvp.Key), kvp.Value))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private void LinkedFieldValueChanged()
|
||||
{
|
||||
if (Field != null && LinkedFieldOptionSelectedIndex > -1)
|
||||
{
|
||||
Field.LinkedId = LinkedFieldOptions.Find(lfo =>
|
||||
lfo.Value == LinkedFieldOptions[LinkedFieldOptionSelectedIndex].Value).Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Bit.Core.Models.View;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
||||
{
|
||||
public class TextCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
||||
{
|
||||
public TextCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand, ICommand copyFieldCommand)
|
||||
: base(field, isEditing, fieldOptionsCommand)
|
||||
{
|
||||
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
|
||||
}
|
||||
|
||||
public override bool ShowCopyButton => !_isEditing && !string.IsNullOrWhiteSpace(Field.Value);
|
||||
|
||||
public ICommand CopyFieldCommand { get; }
|
||||
}
|
||||
}
|
||||
41
src/App/MauiProgram.cs
Normal file
41
src/App/MauiProgram.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace Bit.App
|
||||
{
|
||||
public class MauiProgram
|
||||
{
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
return Core.MauiProgram.ConfigureMauiAppBuilder(
|
||||
effects =>
|
||||
{
|
||||
#if IOS
|
||||
iOS.Core.Utilities.iOSCoreHelpers.ConfigureMAUIEffects(effects);
|
||||
#endif
|
||||
},
|
||||
handlers =>
|
||||
{
|
||||
#if ANDROID
|
||||
Bit.App.Handlers.EntryHandlerMappings.Setup();
|
||||
Bit.App.Handlers.EditorHandlerMappings.Setup();
|
||||
Bit.App.Handlers.LabelHandlerMappings.Setup();
|
||||
Bit.App.Handlers.PickerHandlerMappings.Setup();
|
||||
Bit.App.Handlers.SearchBarHandlerMappings.Setup();
|
||||
Bit.App.Handlers.SwitchHandlerMappings.Setup();
|
||||
Bit.App.Handlers.DatePickerHandlerMappings.Setup();
|
||||
Bit.App.Handlers.SliderHandlerMappings.Setup();
|
||||
Bit.App.Handlers.StepperHandlerMappings.Setup();
|
||||
Bit.App.Handlers.TimePickerHandlerMappings.Setup();
|
||||
Bit.App.Handlers.ButtonHandlerMappings.Setup();
|
||||
Bit.App.Handlers.ToolbarHandlerMappings.Setup();
|
||||
|
||||
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
|
||||
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
|
||||
handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler));
|
||||
#else
|
||||
iOS.Core.Utilities.iOSCoreHelpers.ConfigureMAUIHandlers(handlers);
|
||||
#endif
|
||||
},
|
||||
initUseMauiApp: true
|
||||
).Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class AppOptions
|
||||
{
|
||||
public bool MyVaultTile { get; set; }
|
||||
public bool GeneratorTile { get; set; }
|
||||
public bool FromAutofillFramework { get; set; }
|
||||
public CipherType? FillType { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public CipherType? SaveType { get; set; }
|
||||
public string SaveName { get; set; }
|
||||
public string SaveUsername { get; set; }
|
||||
public string SavePassword { get; set; }
|
||||
public string SaveCardName { get; set; }
|
||||
public string SaveCardNumber { get; set; }
|
||||
public string SaveCardExpMonth { get; set; }
|
||||
public string SaveCardExpYear { get; set; }
|
||||
public string SaveCardCode { get; set; }
|
||||
public bool IosExtension { get; set; }
|
||||
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
|
||||
public bool CopyInsteadOfShareAfterSaving { get; set; }
|
||||
public bool HideAccountSwitcher { get; set; }
|
||||
public OtpData? OtpData { get; set; }
|
||||
|
||||
public void SetAllFrom(AppOptions o)
|
||||
{
|
||||
if (o == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
MyVaultTile = o.MyVaultTile;
|
||||
GeneratorTile = o.GeneratorTile;
|
||||
FromAutofillFramework = o.FromAutofillFramework;
|
||||
FillType = o.FillType;
|
||||
Uri = o.Uri;
|
||||
SaveType = o.SaveType;
|
||||
SaveName = o.SaveName;
|
||||
SaveUsername = o.SaveUsername;
|
||||
SavePassword = o.SavePassword;
|
||||
SaveCardName = o.SaveCardName;
|
||||
SaveCardNumber = o.SaveCardNumber;
|
||||
SaveCardExpMonth = o.SaveCardExpMonth;
|
||||
SaveCardExpYear = o.SaveCardExpYear;
|
||||
SaveCardCode = o.SaveCardCode;
|
||||
IosExtension = o.IosExtension;
|
||||
CreateSend = o.CreateSend;
|
||||
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
|
||||
HideAccountSwitcher = o.HideAccountSwitcher;
|
||||
OtpData = o.OtpData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class DialogDetails
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string ConfirmText { get; set; }
|
||||
public string CancelText { get; set; }
|
||||
public string Type { get; set; }
|
||||
public int DialogId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public abstract class BaseNotificationData
|
||||
{
|
||||
public abstract string Type { get; }
|
||||
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class PasswordlessNotificationData : BaseNotificationData
|
||||
{
|
||||
public const string TYPE = "passwordlessNotificationData";
|
||||
|
||||
public override string Type => TYPE;
|
||||
|
||||
public int TimeoutInMinutes { get; set; }
|
||||
|
||||
public string UserEmail { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
public class PlatformCulture
|
||||
{
|
||||
public PlatformCulture(string platformCultureString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(platformCultureString))
|
||||
{
|
||||
throw new ArgumentException("Expected culture identifier.", nameof(platformCultureString));
|
||||
}
|
||||
|
||||
// .NET expects dash, not underscore
|
||||
PlatformString = platformCultureString.Replace("_", "-");
|
||||
var dashIndex = PlatformString.IndexOf("-", StringComparison.Ordinal);
|
||||
if (dashIndex > 0)
|
||||
{
|
||||
var parts = PlatformString.Split('-');
|
||||
LanguageCode = parts[0];
|
||||
LocaleCode = parts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
LanguageCode = PlatformString;
|
||||
LocaleCode = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string PlatformString { get; private set; }
|
||||
public string LanguageCode { get; private set; }
|
||||
public string LocaleCode { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return PlatformString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class BaseChangePasswordViewModel : BaseViewModel
|
||||
{
|
||||
protected readonly IPlatformUtilsService _platformUtilsService;
|
||||
protected readonly IStateService _stateService;
|
||||
protected readonly IPolicyService _policyService;
|
||||
protected readonly IPasswordGenerationService _passwordGenerationService;
|
||||
protected readonly II18nService _i18nService;
|
||||
protected readonly ICryptoService _cryptoService;
|
||||
protected readonly IDeviceActionService _deviceActionService;
|
||||
protected readonly IApiService _apiService;
|
||||
protected readonly ISyncService _syncService;
|
||||
|
||||
private bool _showPassword;
|
||||
private bool _isPolicyInEffect;
|
||||
private string _policySummary;
|
||||
private MasterPasswordPolicyOptions _policy;
|
||||
|
||||
protected BaseChangePasswordViewModel()
|
||||
{
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_passwordGenerationService =
|
||||
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
}
|
||||
|
||||
public bool ShowPassword
|
||||
{
|
||||
get => _showPassword;
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsPolicyInEffect
|
||||
{
|
||||
get => _isPolicyInEffect;
|
||||
set => SetProperty(ref _isPolicyInEffect, value);
|
||||
}
|
||||
|
||||
public string PolicySummary
|
||||
{
|
||||
get => _policySummary;
|
||||
set => SetProperty(ref _policySummary, value);
|
||||
}
|
||||
|
||||
public MasterPasswordPolicyOptions Policy
|
||||
{
|
||||
get => _policy;
|
||||
set => SetProperty(ref _policy, value);
|
||||
}
|
||||
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string MasterPassword { get; set; }
|
||||
public string ConfirmMasterPassword { get; set; }
|
||||
public string Hint { get; set; }
|
||||
|
||||
public virtual async Task InitAsync(bool forceSync = false)
|
||||
{
|
||||
if (forceSync)
|
||||
{
|
||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||
await task.ContinueWith(async (t) => await CheckPasswordPolicy());
|
||||
}
|
||||
else
|
||||
{
|
||||
await CheckPasswordPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPasswordPolicy()
|
||||
{
|
||||
Policy = await _policyService.GetMasterPasswordPolicyOptions();
|
||||
IsPolicyInEffect = Policy?.InEffect() ?? false;
|
||||
if (!IsPolicyInEffect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bullet = "\n" + "".PadLeft(4) + "\u2022 ";
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(_i18nService.T("MasterPasswordPolicyInEffect"));
|
||||
if (Policy.MinComplexity > 0)
|
||||
{
|
||||
sb.Append(bullet)
|
||||
.Append(string.Format(_i18nService.T("PolicyInEffectMinComplexity"), Policy.MinComplexity));
|
||||
}
|
||||
if (Policy.MinLength > 0)
|
||||
{
|
||||
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectMinLength"), Policy.MinLength));
|
||||
}
|
||||
if (Policy.RequireUpper)
|
||||
{
|
||||
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectUppercase"));
|
||||
}
|
||||
if (Policy.RequireLower)
|
||||
{
|
||||
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectLowercase"));
|
||||
}
|
||||
if (Policy.RequireNumbers)
|
||||
{
|
||||
sb.Append(bullet).Append(_i18nService.T("PolicyInEffectNumbers"));
|
||||
}
|
||||
if (Policy.RequireSpecial)
|
||||
{
|
||||
sb.Append(bullet).Append(string.Format(_i18nService.T("PolicyInEffectSpecial"), "!@#$%^&*"));
|
||||
}
|
||||
PolicySummary = sb.ToString();
|
||||
}
|
||||
|
||||
protected async Task<bool> ValidateMasterPasswordAsync()
|
||||
{
|
||||
if (Connectivity.NetworkAccess == NetworkAccess.None)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(MasterPassword))
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
if (IsPolicyInEffect)
|
||||
{
|
||||
var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync());
|
||||
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs);
|
||||
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage,
|
||||
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.MasterPasswordLengthValMessageX, Constants.MasterPasswordMinimumChars),
|
||||
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (MasterPassword != ConfirmMasterPassword)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordConfirmationValMessage,
|
||||
AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.Accounts.DeleteAccountPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:DeleteAccountViewModel"
|
||||
Title="{Binding PageTitle}">
|
||||
<ContentPage.BindingContext>
|
||||
<pages:DeleteAccountViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Resources>
|
||||
<Style TargetType="Label" x:Key="lblDescription">
|
||||
<Setter Property="FontSize" Value="{OnPlatform Android=Large, iOS=Small}" />
|
||||
</Style>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ContentPage.Content>
|
||||
<Grid Padding="20, 30" RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Source="ic_warning"
|
||||
WidthRequest="28"
|
||||
HeightRequest="25"
|
||||
HorizontalOptions="Start" />
|
||||
<Label
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Text="{u:I18n DeletingYourAccountIsPermanent}"
|
||||
HorizontalOptions="Start"
|
||||
StyleClass="title-danger"
|
||||
Margin="0,15,0,0"/>
|
||||
<Label
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Text="{u:I18n DeleteAccountExplanation}"
|
||||
Style="{StaticResource lblDescription}"
|
||||
HorizontalOptions="Start"
|
||||
Margin="0,6,50,0"
|
||||
Opacity="0.6" />
|
||||
<Button
|
||||
Grid.Row="3"
|
||||
Text="{u:I18n DeleteAccount}"
|
||||
StyleClass="btn-danger"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Start"
|
||||
Margin="0,20,0,0"
|
||||
Padding="16,0"
|
||||
CornerRadius="2"
|
||||
TextTransform="Uppercase"
|
||||
Clicked="DeleteAccount_Clicked"/>
|
||||
<Button
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Text="{u:I18n Cancel}"
|
||||
StyleClass="btn-secondary"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Start"
|
||||
Margin="0,20,0,0"
|
||||
Padding="16,0"
|
||||
CornerRadius="2"
|
||||
TextTransform="Uppercase"
|
||||
Clicked="Close_Clicked" />
|
||||
</Grid>
|
||||
</ContentPage.Content>
|
||||
</pages:BaseContentPage>
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages.Accounts
|
||||
{
|
||||
public partial class DeleteAccountPage : BaseContentPage
|
||||
{
|
||||
DeleteAccountViewModel _vm;
|
||||
|
||||
public DeleteAccountPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as DeleteAccountViewModel;
|
||||
_vm.Page = this;
|
||||
}
|
||||
|
||||
private async void Close_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteAccount_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
await _vm.DeleteAccountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class DeleteAccountViewModel : BaseViewModel
|
||||
{
|
||||
readonly IPlatformUtilsService _platformUtilsService;
|
||||
readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
|
||||
readonly ILogger _logger;
|
||||
|
||||
public DeleteAccountViewModel()
|
||||
{
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
PageTitle = AppResources.DeleteAccount;
|
||||
}
|
||||
|
||||
public async Task DeleteAccountAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
|
||||
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
await _verificationActionsFlowHelper
|
||||
.Configure(VerificationFlowAction.DeleteAccount,
|
||||
null,
|
||||
AppResources.DeleteAccount,
|
||||
true)
|
||||
.ValidateAndExecuteAsync();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
_logger.Exception(ex);
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDeleteAccountActionFlowExecutioner : IActionFlowExecutioner { }
|
||||
|
||||
public class DeleteAccountActionFlowExecutioner : IDeleteAccountActionFlowExecutioner
|
||||
{
|
||||
readonly IApiService _apiService;
|
||||
readonly IMessagingService _messagingService;
|
||||
readonly IPlatformUtilsService _platformUtilsService;
|
||||
readonly IDeviceActionService _deviceActionService;
|
||||
readonly ILogger _logger;
|
||||
|
||||
public DeleteAccountActionFlowExecutioner(IApiService apiService,
|
||||
IMessagingService messagingService,
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
IDeviceActionService deviceActionService,
|
||||
ILogger logger)
|
||||
{
|
||||
_apiService = apiService;
|
||||
_messagingService = messagingService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_deviceActionService = deviceActionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute(IActionFlowParmeters parameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
|
||||
|
||||
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
|
||||
{
|
||||
MasterPasswordHash = parameters.VerificationType == Core.Enums.VerificationType.MasterPassword ? parameters.Secret : (string)null,
|
||||
OTP = parameters.VerificationType == Core.Enums.VerificationType.OTP ? parameters.Secret : (string)null
|
||||
});
|
||||
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
_messagingService.Send("logout");
|
||||
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.YourAccountHasBeenPermanentlyDeleted);
|
||||
}
|
||||
catch (ApiException apiEx)
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
if (apiEx?.Error != null)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(apiEx.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
_logger.Exception(ex);
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.EnvironmentPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
x:DataType="pages:EnvironmentPageViewModel"
|
||||
Title="{Binding PageTitle}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<pages:EnvironmentPageViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||
<ToolbarItem Text="{u:I18n Save}" Command="{Binding SubmitCommand}" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ScrollView>
|
||||
<StackLayout Spacing="20">
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="{u:I18n SelfHostedEnvironment, Header=True}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{u:I18n ServerUrl}"
|
||||
StyleClass="box-label" />
|
||||
<Entry
|
||||
Text="{Binding BaseUrl}"
|
||||
Keyboard="Url"
|
||||
Placeholder="ex. https://bitwarden.company.com"
|
||||
StyleClass="box-value"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding SubmitCommand}"
|
||||
AutomationId="ServerUrlEntry"/>
|
||||
</StackLayout>
|
||||
<Label
|
||||
Text="{u:I18n SelfHostedEnvironmentFooter}"
|
||||
StyleClass="box-footer-label" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box">
|
||||
<StackLayout StyleClass="box-row-header">
|
||||
<Label Text="{u:I18n CustomEnvironment, Header=True}"
|
||||
StyleClass="box-header, box-header-platform" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{u:I18n WebVaultUrl}"
|
||||
StyleClass="box-label" />
|
||||
<Entry
|
||||
x:Name="_webVaultEntry"
|
||||
Text="{Binding WebVaultUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value"
|
||||
AutomationId="WebVaultUrlEntry"/>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{u:I18n ApiUrl}"
|
||||
StyleClass="box-label" />
|
||||
<Entry
|
||||
x:Name="_apiEntry"
|
||||
Text="{Binding ApiUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value"
|
||||
AutomationId="ApiUrlEntry"/>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{u:I18n IdentityUrl}"
|
||||
StyleClass="box-label" />
|
||||
<Entry
|
||||
x:Name="_identityEntry"
|
||||
Text="{Binding IdentityUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value"
|
||||
AutomationId="IdentityUrlEntry"/>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
Text="{u:I18n IconsUrl}"
|
||||
StyleClass="box-label" />
|
||||
<Entry
|
||||
x:Name="_iconsEntry"
|
||||
Text="{Binding IconsUrl}"
|
||||
Keyboard="Url"
|
||||
StyleClass="box-value"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding SubmitCommand}"
|
||||
AutomationId="IconsUrlEntry"/>
|
||||
</StackLayout>
|
||||
<Label
|
||||
Text="{u:I18n CustomEnvironmentFooter}"
|
||||
StyleClass="box-footer-label" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
</pages:BaseContentPage>
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class EnvironmentPage : BaseContentPage
|
||||
{
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly EnvironmentPageViewModel _vm;
|
||||
|
||||
public EnvironmentPage()
|
||||
{
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
InitializeComponent();
|
||||
_vm = BindingContext as EnvironmentPageViewModel;
|
||||
_vm.Page = this;
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
ToolbarItems.RemoveAt(0);
|
||||
}
|
||||
|
||||
_webVaultEntry.ReturnType = ReturnType.Next;
|
||||
_webVaultEntry.ReturnCommand = new Command(() => _apiEntry.Focus());
|
||||
_apiEntry.ReturnType = ReturnType.Next;
|
||||
_apiEntry.ReturnCommand = new Command(() => _identityEntry.Focus());
|
||||
_identityEntry.ReturnType = ReturnType.Next;
|
||||
_identityEntry.ReturnCommand = new Command(() => _iconsEntry.Focus());
|
||||
_vm.SubmitSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
|
||||
_vm.CloseAction = async () =>
|
||||
{
|
||||
await Navigation.PopModalAsync();
|
||||
};
|
||||
}
|
||||
|
||||
private async Task SubmitSuccessAsync()
|
||||
{
|
||||
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
||||
await Navigation.PopModalAsync();
|
||||
}
|
||||
|
||||
private void Close_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
_vm.CloseAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class EnvironmentPageViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IEnvironmentService _environmentService;
|
||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
|
||||
public EnvironmentPageViewModel()
|
||||
{
|
||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||
|
||||
PageTitle = AppResources.Settings;
|
||||
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
if (_environmentService.SelectedRegion != Region.SelfHosted ||
|
||||
_environmentService.BaseUrl == Region.US.BaseUrl() ||
|
||||
_environmentService.BaseUrl == Region.EU.BaseUrl())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BaseUrl = _environmentService.BaseUrl;
|
||||
WebVaultUrl = _environmentService.WebVaultUrl;
|
||||
ApiUrl = _environmentService.ApiUrl;
|
||||
IdentityUrl = _environmentService.IdentityUrl;
|
||||
IconsUrl = _environmentService.IconsUrl;
|
||||
NotificationsUrls = _environmentService.NotificationsUrl;
|
||||
}
|
||||
|
||||
public ICommand SubmitCommand { get; }
|
||||
public string BaseUrl { get; set; }
|
||||
public string ApiUrl { get; set; }
|
||||
public string IdentityUrl { get; set; }
|
||||
public string WebVaultUrl { get; set; }
|
||||
public string IconsUrl { get; set; }
|
||||
public string NotificationsUrls { get; set; }
|
||||
public Action SubmitSuccessAction { get; set; }
|
||||
public Action CloseAction { get; set; }
|
||||
|
||||
public async Task SubmitAsync()
|
||||
{
|
||||
if (!ValidateUrls())
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.EnvironmentPageUrlsError, AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
var urls = new Core.Models.Data.EnvironmentUrlData
|
||||
{
|
||||
Base = BaseUrl,
|
||||
Api = ApiUrl,
|
||||
Identity = IdentityUrl,
|
||||
WebVault = WebVaultUrl,
|
||||
Icons = IconsUrl,
|
||||
Notifications = NotificationsUrls
|
||||
};
|
||||
var resUrls = await _environmentService.SetRegionAsync(urls.Region, urls);
|
||||
|
||||
// re-set urls since service can change them, ex: prefixing https://
|
||||
BaseUrl = resUrls.Base;
|
||||
WebVaultUrl = resUrls.WebVault;
|
||||
ApiUrl = resUrls.Api;
|
||||
IdentityUrl = resUrls.Identity;
|
||||
IconsUrl = resUrls.Icons;
|
||||
NotificationsUrls = resUrls.Notifications;
|
||||
|
||||
SubmitSuccessAction?.Invoke();
|
||||
}
|
||||
|
||||
public bool ValidateUrls()
|
||||
{
|
||||
bool IsUrlValid(string url)
|
||||
{
|
||||
return string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute);
|
||||
}
|
||||
|
||||
return IsUrlValid(BaseUrl)
|
||||
&& IsUrlValid(ApiUrl)
|
||||
&& IsUrlValid(IdentityUrl)
|
||||
&& IsUrlValid(WebVaultUrl)
|
||||
&& IsUrlValid(IconsUrl);
|
||||
}
|
||||
|
||||
private void OnSubmitException(Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user