diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 12c7de4ec..63b5a73c5 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -298,7 +298,6 @@ - diff --git a/src/Android/AutofillActivity.cs b/src/Android/AutofillActivity.cs index 7c69d1cb7..cc08824cf 100644 --- a/src/Android/AutofillActivity.cs +++ b/src/Android/AutofillActivity.cs @@ -7,17 +7,17 @@ using Android.App; using Android.Content; using Android.OS; using Android.Runtime; -using Bit.App.Models; +using Android.Views; namespace Bit.Android { - [Activity(Label = "bitwarden Autofill", + [Activity(Label = "bitwarden", + Icon = "@drawable/icon", LaunchMode = global::Android.Content.PM.LaunchMode.SingleInstance, - Theme = "@style/android:Theme.Material.Light")] + WindowSoftInputMode = SoftInput.StateHidden)] public class AutofillActivity : Activity { private string _lastQueriedUri; - public static Credentials LastCredentials; protected override void OnCreate(Bundle bundle) @@ -25,12 +25,11 @@ namespace Bit.Android base.OnCreate(bundle); _lastQueriedUri = Intent.GetStringExtra("uri"); - var intent = new Intent(this, typeof(AutofillSelectLoginActivity)); + var intent = new Intent(this, typeof(MainActivity)); intent.PutExtra("uri", _lastQueriedUri); StartActivityForResult(intent, 123); } - protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); @@ -62,4 +61,4 @@ namespace Bit.Android public string Uri; } } -} +} \ No newline at end of file diff --git a/src/Android/AutofillSelectLoginActivity.cs b/src/Android/AutofillSelectLoginActivity.cs deleted file mode 100644 index 8277b283a..000000000 --- a/src/Android/AutofillSelectLoginActivity.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Android.App; -using Android.Content; -using Android.OS; - -namespace Bit.Android -{ - [Activity(LaunchMode = global::Android.Content.PM.LaunchMode.SingleInstance)] - public class AutofillSelectLoginActivity : Activity - { - protected override void OnCreate(Bundle bundle) - { - base.OnCreate(bundle); - var uri = Intent.GetStringExtra("uri"); - - Intent data = new Intent(); - data.PutExtra("uri", uri); - data.PutExtra("username", "user123"); - data.PutExtra("password", "pass123"); - - if(Parent == null) - { - SetResult(Result.Ok, data); - } - else - { - Parent.SetResult(Result.Ok, data); - } - - Finish(); - } - } -} \ No newline at end of file diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 6ce3c6e3c..d6d3e9f2a 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -14,6 +14,7 @@ using System.Reflection; using Xamarin.Forms.Platform.Android; using Xamarin.Forms; using System.Threading.Tasks; +using Bit.App.Models.Page; namespace Bit.Android { @@ -27,6 +28,12 @@ namespace Bit.Android protected override void OnCreate(Bundle bundle) { + var uri = Intent.GetStringExtra("uri"); + if(uri != null && !Resolver.IsSet) + { + MainApplication.SetIoc(Application); + } + var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build(); StrictMode.SetThreadPolicy(policy); @@ -55,6 +62,7 @@ namespace Bit.Android .SetValue(null, Color.FromHex("d2d6de")); LoadApplication(new App.App( + uri, Resolver.Resolve(), Resolver.Resolve(), Resolver.Resolve(), @@ -70,6 +78,31 @@ namespace Bit.Android { RateApp(); }); + + MessagingCenter.Subscribe( + Xamarin.Forms.Application.Current, "Autofill", (sender, args) => + { + ReturnCredentials(args); + }); + } + + private void ReturnCredentials(VaultListPageModel.Login login) + { + Intent data = new Intent(); + data.PutExtra("uri", login.Uri.Value); + data.PutExtra("username", login.Username); + data.PutExtra("password", login.Password.Value); + + if(Parent == null) + { + SetResult(Result.Ok, data); + } + else + { + Parent.SetResult(Result.Ok, data); + } + + Finish(); } protected override void OnPause() diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index 3fbef035c..4924a3863 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -43,7 +43,7 @@ namespace Bit.Android if(!Resolver.IsSet) { - SetIoc(); + SetIoc(this); } } @@ -178,16 +178,16 @@ namespace Bit.Android } } - private void SetIoc() + public static void SetIoc(Application application) { - UserDialogs.Init(this); + UserDialogs.Init(application); var container = new UnityContainer(); container // Android Stuff - .RegisterInstance(ApplicationContext) - .RegisterInstance(this) + .RegisterInstance(application.ApplicationContext) + .RegisterInstance(application) // Services .RegisterType(new ContainerControlledLifetimeManager()) .RegisterType(new ContainerControlledLifetimeManager()) diff --git a/src/App/App.cs b/src/App/App.cs index 6f226120b..a0aec127e 100644 --- a/src/App/App.cs +++ b/src/App/App.cs @@ -19,6 +19,7 @@ namespace Bit.App { public class App : Application { + private readonly string _uri; private readonly IDatabaseService _databaseService; private readonly IConnectivity _connectivity; private readonly IUserDialogs _userDialogs; @@ -31,6 +32,7 @@ namespace Bit.App private readonly ILocalizeService _localizeService; public App( + string uri, IAuthService authService, IConnectivity connectivity, IUserDialogs userDialogs, @@ -42,6 +44,7 @@ namespace Bit.App IGoogleAnalyticsService googleAnalyticsService, ILocalizeService localizeService) { + _uri = uri; _databaseService = databaseService; _connectivity = connectivity; _userDialogs = userDialogs; @@ -56,7 +59,11 @@ namespace Bit.App SetCulture(); SetStyles(); - if(authService.IsAuthenticated) + if(authService.IsAuthenticated && _uri != null) + { + MainPage = new ExtendedNavigationPage(new VaultAutofillListLoginsPage(_uri)); + } + else if(authService.IsAuthenticated) { MainPage = new MainPage(); } diff --git a/src/App/App.csproj b/src/App/App.csproj index 70dd0c079..5edd86f79 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -134,6 +134,7 @@ + diff --git a/src/App/Models/Page/VaultListPageModel.cs b/src/App/Models/Page/VaultListPageModel.cs index b3e3bec58..cc6ef2f71 100644 --- a/src/App/Models/Page/VaultListPageModel.cs +++ b/src/App/Models/Page/VaultListPageModel.cs @@ -8,6 +8,8 @@ namespace Bit.App.Models.Page { public class Login { + private string _baseDomain; + public Login(Models.Login login) { Id = login.Id; @@ -24,6 +26,37 @@ namespace Bit.App.Models.Page public string Username { get; set; } public Lazy Password { get; set; } public Lazy Uri { get; set; } + + public string BaseDomain + { + get + { + if(_baseDomain != null) + { + return _baseDomain; + } + + if(string.IsNullOrWhiteSpace(Uri.Value)) + { + return null; + } + + Uri uri; + if(!System.Uri.TryCreate(Uri.Value, UriKind.Absolute, out uri)) + { + return null; + } + + DomainName domain; + if(!DomainName.TryParse(uri.Host, out domain)) + { + return null; + } + + _baseDomain = domain.BaseDomain; + return _baseDomain; + } + } } public class Folder : List diff --git a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs new file mode 100644 index 000000000..e73e6df80 --- /dev/null +++ b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs @@ -0,0 +1,179 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Acr.UserDialogs; +using Bit.App.Abstractions; +using Bit.App.Controls; +using Bit.App.Models.Page; +using Bit.App.Resources; +using Xamarin.Forms; +using XLabs.Ioc; +using Bit.App.Utilities; +using PushNotification.Plugin.Abstractions; +using Plugin.Settings.Abstractions; +using Plugin.Connectivity.Abstractions; +using System.Collections.Generic; +using System.Threading; +using Bit.App.Models; + +namespace Bit.App.Pages +{ + public class VaultAutofillListLoginsPage : ExtendedContentPage + { + private readonly IFolderService _folderService; + private readonly ILoginService _loginService; + private readonly IUserDialogs _userDialogs; + private readonly IConnectivity _connectivity; + private readonly IClipboardService _clipboardService; + private readonly ISyncService _syncService; + private readonly IPushNotification _pushNotification; + private readonly IDeviceInfoService _deviceInfoService; + private readonly ISettings _settings; + private CancellationTokenSource _filterResultsCancellationTokenSource; + private readonly DomainName _domainName; + + public VaultAutofillListLoginsPage(string uriString) + : base(true) + { + Uri uri; + if(Uri.TryCreate(uriString, UriKind.RelativeOrAbsolute, out uri) && + DomainName.TryParse(uri.Host, out _domainName)) { } + + _folderService = Resolver.Resolve(); + _loginService = Resolver.Resolve(); + _connectivity = Resolver.Resolve(); + _userDialogs = Resolver.Resolve(); + _clipboardService = Resolver.Resolve(); + _syncService = Resolver.Resolve(); + _pushNotification = Resolver.Resolve(); + _deviceInfoService = Resolver.Resolve(); + _settings = Resolver.Resolve(); + + Init(); + } + public ExtendedObservableCollection PresentationLogins { get; private set; } + = new ExtendedObservableCollection(); + + public ListView ListView { get; set; } + + private void Init() + { + MessagingCenter.Subscribe(Application.Current, "SyncCompleted", (sender, success) => + { + if(success) + { + _filterResultsCancellationTokenSource = FetchAndLoadVault(); + } + }); + + ToolbarItems.Add(new AddLoginToolBarItem(this)); + + ListView = new ListView(ListViewCachingStrategy.RecycleElement) + { + ItemsSource = PresentationLogins, + HasUnevenRows = true, + ItemTemplate = new DataTemplate(() => new VaultListViewCell(this)) + }; + + if(Device.OS == TargetPlatform.iOS) + { + ListView.RowHeight = -1; + } + + ListView.ItemSelected += LoginSelected; + + Title = AppResources.Logins; + + Content = ListView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + _filterResultsCancellationTokenSource = FetchAndLoadVault(); + } + + private CancellationTokenSource FetchAndLoadVault() + { + var cts = new CancellationTokenSource(); + _settings.AddOrUpdateValue(Constants.FirstVaultLoad, false); + + if(PresentationLogins.Count > 0 && _syncService.SyncInProgress) + { + return cts; + } + + _filterResultsCancellationTokenSource?.Cancel(); + + Task.Run(async () => + { + var logins = await _loginService.GetAllAsync(); + var filteredLogins = logins + .Select(s => new VaultListPageModel.Login(s)) + .Where(s => s.BaseDomain != null && s.BaseDomain == _domainName.BaseDomain) + .OrderBy(s => s.Name) + .ThenBy(s => s.Username) + .ToArray(); + + PresentationLogins.ResetWithRange(filteredLogins); + }, cts.Token); + + return cts; + } + + private void LoginSelected(object sender, SelectedItemChangedEventArgs e) + { + var login = e.SelectedItem as VaultListPageModel.Login; + MessagingCenter.Send(Application.Current, "Autofill", login); + } + + private async void AddLogin() + { + var page = new VaultAddLoginPage(); + await Navigation.PushForDeviceAsync(page); + } + + private class AddLoginToolBarItem : ToolbarItem + { + private readonly VaultAutofillListLoginsPage _page; + + public AddLoginToolBarItem(VaultAutofillListLoginsPage page) + { + _page = page; + Text = AppResources.Add; + Icon = "plus"; + Clicked += ClickedItem; + } + + private void ClickedItem(object sender, EventArgs e) + { + _page.AddLogin(); + } + } + + private class VaultListViewCell : LabeledDetailCell + { + private VaultAutofillListLoginsPage _page; + + public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter), + typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null); + + public VaultListViewCell(VaultAutofillListLoginsPage page) + { + _page = page; + + SetBinding(LoginParameterProperty, new Binding(".")); + Label.SetBinding(Label.TextProperty, s => s.Name); + Detail.SetBinding(Label.TextProperty, s => s.Username); + + BackgroundColor = Color.White; + } + + public VaultListPageModel.Login LoginParameter + { + get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; } + set { SetValue(LoginParameterProperty, value); } + } + } + } +} diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 9b8bca3e4..f8819f4ac 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -56,6 +56,7 @@ namespace Bit.iOS manager.DisableMetricsManager = manager.DisableFeedbackManager = manager.DisableUpdateManager = true; LoadApplication(new App.App( + null, Resolver.Resolve(), Resolver.Resolve(), Resolver.Resolve(),