diff --git a/src/App/App.csproj b/src/App/App.csproj
index 1ffdf334a..a904f0ba6 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -97,6 +97,7 @@
+
@@ -110,6 +111,7 @@
+
diff --git a/src/App/Constants.cs b/src/App/Constants.cs
index 6d5bb8e18..19ecb9b71 100644
--- a/src/App/Constants.cs
+++ b/src/App/Constants.cs
@@ -18,5 +18,8 @@
public const string PushInitialPromptShown = "push:initialPromptShown";
public const string PushLastRegistrationDate = "push:lastRegistrationDate";
+
+ public const string ExtensionStarted = "extension:started";
+ public const string ExtensionActivated = "extension:activated";
}
}
diff --git a/src/App/Models/Page/AppExtensionPageModel.cs b/src/App/Models/Page/AppExtensionPageModel.cs
new file mode 100644
index 000000000..956fd1a69
--- /dev/null
+++ b/src/App/Models/Page/AppExtensionPageModel.cs
@@ -0,0 +1,47 @@
+using System;
+using System.ComponentModel;
+using Plugin.Settings.Abstractions;
+
+namespace Bit.App.Models.Page
+{
+ public class AppExtensionPageModel : INotifyPropertyChanged
+ {
+ private readonly ISettings _settings;
+
+ public AppExtensionPageModel(ISettings settings)
+ {
+ _settings = settings;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public bool Started
+ {
+ get { return _settings.GetValueOrDefault(Constants.ExtensionStarted, false); }
+ set
+ {
+ _settings.AddOrUpdateValue(Constants.ExtensionStarted, true);
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Started)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(NotStarted)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(StartedAndNotActivated)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(StartedAndActivated)));
+ }
+ }
+
+ public bool Activated
+ {
+ get { return _settings.GetValueOrDefault(Constants.ExtensionActivated, false); }
+ set
+ {
+ _settings.AddOrUpdateValue(Constants.ExtensionActivated, value);
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(Activated)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(StartedAndNotActivated)));
+ PropertyChanged(this, new PropertyChangedEventArgs(nameof(StartedAndActivated)));
+ }
+ }
+
+ public bool NotStarted => !Started;
+ public bool StartedAndNotActivated => Started && !Activated;
+ public bool StartedAndActivated => Started && Activated;
+ }
+}
diff --git a/src/App/Pages/Tools/ToolsExtensionPage.cs b/src/App/Pages/Tools/ToolsExtensionPage.cs
new file mode 100644
index 000000000..1541a1f73
--- /dev/null
+++ b/src/App/Pages/Tools/ToolsExtensionPage.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Threading.Tasks;
+using Acr.UserDialogs;
+using Bit.App.Controls;
+using Bit.App.Models.Page;
+using Plugin.Settings.Abstractions;
+using Xamarin.Forms;
+using XLabs.Ioc;
+
+namespace Bit.App.Pages
+{
+ public class ToolsExtensionPage : ExtendedContentPage
+ {
+ private readonly IUserDialogs _userDialogs;
+ private readonly ISettings _settings;
+
+ public ToolsExtensionPage()
+ {
+ _userDialogs = Resolver.Resolve();
+ _settings = Resolver.Resolve();
+ Model = new AppExtensionPageModel(_settings);
+
+ Init();
+ }
+
+ public AppExtensionPageModel Model { get; private set; }
+
+ public void Init()
+ {
+ // Not Started
+
+ var notStartedLabel = new Label
+ {
+ Text = "Get instant access to your passwords!",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ LineBreakMode = LineBreakMode.WordWrap,
+ FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
+ };
+
+ var notStartedSublabel = new Label
+ {
+ Text = "To turn on bitwarden in Safari and other apps, tap \"more\" on the second row of the menu.",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ LineBreakMode = LineBreakMode.WordWrap
+ };
+
+ var notStartedImage = new Image
+ {
+ Source = "",
+ VerticalOptions = LayoutOptions.CenterAndExpand,
+ HorizontalOptions = LayoutOptions.Center
+ };
+
+ var notStartedButton = new Button
+ {
+ Text = "Enable App Extension",
+ Command = new Command(() => ActivateExtension()),
+ VerticalOptions = LayoutOptions.End,
+ HorizontalOptions = LayoutOptions.Fill,
+ Style = (Style)Application.Current.Resources["btn-primary"]
+ };
+
+ var notStartedStackLayout = new StackLayout
+ {
+ Orientation = StackOrientation.Vertical,
+ Spacing = 20,
+ Padding = new Thickness(30, 40),
+ Children = { notStartedLabel, notStartedSublabel, notStartedImage, notStartedButton }
+ };
+
+ notStartedStackLayout.SetBinding(IsVisibleProperty, m => m.NotStarted);
+
+ // Not Activated
+
+ var notActivatedLabel = new Label
+ {
+ Text = "Almost done!",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ LineBreakMode = LineBreakMode.WordWrap,
+ FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
+ };
+
+ var notActivatedSublabel = new Label
+ {
+ Text = "Tap the bitwarden icon in the menu to launch the extension.",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ LineBreakMode = LineBreakMode.WordWrap
+ };
+
+ var notActivatedImage = new Image
+ {
+ Source = "",
+ VerticalOptions = LayoutOptions.CenterAndExpand,
+ HorizontalOptions = LayoutOptions.Center
+ };
+
+ var notActivatedButton = new Button
+ {
+ Text = "Enable App Extension",
+ Command = new Command(() => ActivateExtension()),
+ VerticalOptions = LayoutOptions.End,
+ HorizontalOptions = LayoutOptions.Fill,
+ Style = (Style)Application.Current.Resources["btn-primary"]
+ };
+
+ var notActivatedStackLayout = new StackLayout
+ {
+ Orientation = StackOrientation.Vertical,
+ Spacing = 20,
+ Padding = new Thickness(30, 40),
+ Children = { notActivatedLabel, notActivatedSublabel, notActivatedImage, notActivatedButton }
+ };
+
+ notActivatedStackLayout.SetBinding(IsVisibleProperty, m => m.StartedAndNotActivated);
+
+ // Activated
+
+ var activatedLabel = new Label
+ {
+ Text = "You're ready to log in!",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ LineBreakMode = LineBreakMode.WordWrap,
+ FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
+ };
+
+ var activatedSublabel = new Label
+ {
+ Text = "In Safari, find bitwarden using the share icon (hint: scroll to the right on the second row of the menu).",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Center,
+ HorizontalTextAlignment = TextAlignment.Center,
+ LineBreakMode = LineBreakMode.WordWrap
+ };
+
+ var activatedImage = new Image
+ {
+ Source = "",
+ VerticalOptions = LayoutOptions.CenterAndExpand,
+ HorizontalOptions = LayoutOptions.Center
+ };
+
+ var activatedButton = new Button
+ {
+ Text = "See Supported Apps",
+ Command = new Command(() => Device.OpenUri(new Uri("https://bitwarden.com"))),
+ VerticalOptions = LayoutOptions.End,
+ HorizontalOptions = LayoutOptions.Fill,
+ Style = (Style)Application.Current.Resources["btn-primary"]
+ };
+
+ var activatedStackLayout = new StackLayout
+ {
+ Orientation = StackOrientation.Vertical,
+ Spacing = 20,
+ Padding = new Thickness(30, 40),
+ Children = { activatedLabel, activatedSublabel, activatedImage, activatedButton }
+ };
+
+ activatedStackLayout.SetBinding(IsVisibleProperty, m => m.StartedAndActivated);
+
+ var stackLayout = new StackLayout
+ {
+ Children = { notStartedStackLayout, notActivatedStackLayout, activatedStackLayout }
+ };
+
+ if(Device.OS == TargetPlatform.iOS)
+ {
+ ToolbarItems.Add(new DismissModalToolBarItem(this, "Close"));
+ }
+
+ Title = "App Extension";
+ Content = stackLayout;
+ BindingContext = Model;
+
+ MessagingCenter.Subscribe(Application.Current, "EnabledAppExtension", (sender, enabled) =>
+ {
+ Model.Started = true;
+ Model.Activated = enabled;
+ });
+ }
+
+ private void ActivateExtension()
+ {
+ MessagingCenter.Send(Application.Current, "ShowAppExtension");
+ }
+ }
+}
diff --git a/src/App/Pages/Tools/ToolsPage.cs b/src/App/Pages/Tools/ToolsPage.cs
index 83e831513..24fd77596 100644
--- a/src/App/Pages/Tools/ToolsPage.cs
+++ b/src/App/Pages/Tools/ToolsPage.cs
@@ -4,7 +4,6 @@ using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
-using Plugin.Connectivity.Abstractions;
using Xamarin.Forms;
using XLabs.Ioc;
@@ -26,6 +25,7 @@ namespace Bit.App.Pages
var generatorCell = new ToolsViewCell("Password Generator", "Automatically generate strong, unique passwords for your logins.", "refresh");
generatorCell.Tapped += GeneratorCell_Tapped;
var extensionCell = new ToolsViewCell("bitwarden App Extension", "Use bitwarden in Safari and other apps to auto-fill your logins.", "upload");
+ extensionCell.Tapped += ExtensionCell_Tapped;
var webCell = new ToolsViewCell("bitwarden Web Vault", "Manage your logins from any web browser with the bitwarden web vault.", "globe");
webCell.Tapped += WebCell_Tapped;
var importCell = new ToolsViewCell("Import Logins", "Quickly bulk import your logins from other password management apps.", "cloudup");
@@ -58,6 +58,11 @@ namespace Bit.App.Pages
Content = table;
}
+ private void ExtensionCell_Tapped(object sender, EventArgs e)
+ {
+ Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsExtensionPage()));
+ }
+
private void GeneratorCell_Tapped(object sender, EventArgs e)
{
Navigation.PushModalAsync(new ExtendedNavigationPage(new ToolsPasswordGeneratorPage()));
diff --git a/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs b/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs
index 8d020c881..c49f8afc7 100644
--- a/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs
+++ b/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs
@@ -87,7 +87,7 @@ namespace Bit.App.Pages
{
table.RowHeight = -1;
table.EstimatedRowHeight = 44;
- ToolbarItems.Add(new DismissModalToolBarItem(this, "Cancel"));
+ ToolbarItems.Add(new DismissModalToolBarItem(this, "Close"));
}
var stackLayout = new StackLayout
diff --git a/src/iOS.Extension/Info.plist b/src/iOS.Extension/Info.plist
index 7c6083377..aa62481d7 100644
--- a/src/iOS.Extension/Info.plist
+++ b/src/iOS.Extension/Info.plist
@@ -42,6 +42,7 @@
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "org.appextension.change-password-action"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "org.appextension.fill-webview-action"
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "org.appextension.fill-browser-action"
+ || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.8bit.bitwarden.extension-setup"
).@count == $extensionItem.attachments.@count
).@count == 1
NSExtensionPointName
diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs
index 94e69436e..e18823e2b 100644
--- a/src/iOS/AppDelegate.cs
+++ b/src/iOS/AppDelegate.cs
@@ -54,6 +54,22 @@ namespace Bit.iOS
UINavigationBar.Appearance.ShadowImage = new UIImage();
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
+ MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, "ShowAppExtension", (sender) =>
+ {
+ var itemProvider = new NSItemProvider(new NSDictionary(), "com.8bit.bitwarden.extension-setup");
+ var extensionItem = new NSExtensionItem();
+ extensionItem.Attachments = new NSItemProvider[] { itemProvider };
+ var activityViewController = new UIActivityViewController(new NSExtensionItem[] { extensionItem }, null);
+ activityViewController.CompletionHandler = (activityType, completed) =>
+ {
+ MessagingCenter.Send(Xamarin.Forms.Application.Current, "EnabledAppExtension",
+ completed && activityType == "com.8bit.bitwarden.find-login-action-extension");
+ };
+
+ UIApplication.SharedApplication.KeyWindow.RootViewController.ModalViewController
+ .PresentViewController(activityViewController, true, null);
+ });
+
return base.FinishedLaunching(app, options);
}