diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 573d0a0ef..1a58f0a73 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -196,6 +196,7 @@ + diff --git a/src/Android/Controls/ExtendedEntryRenderer.cs b/src/Android/Controls/ExtendedEntryRenderer.cs new file mode 100644 index 000000000..a1e39d5f4 --- /dev/null +++ b/src/Android/Controls/ExtendedEntryRenderer.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel; +using Android.Graphics; +using Android.Text; +using Android.Text.Method; +using Bit.Android.Controls; +using Bit.App.Controls; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))] +namespace Bit.Android.Controls +{ + public class ExtendedEntryRenderer : EntryRenderer + { + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + var view = (ExtendedEntry)Element; + + if(Control != null && e.NewElement != null && e.NewElement.IsPassword) + { + Control.SetTypeface(Typeface.Default, TypefaceStyle.Normal); + Control.TransformationMethod = new PasswordTransformationMethod(); + } + + SetBorder(view); + SetPlaceholderTextColor(view); + SetMaxLength(view); + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var view = (ExtendedEntry)Element; + + if(e.PropertyName == ExtendedEntry.HasBorderProperty.PropertyName + || e.PropertyName == ExtendedEntry.HasOnlyBottomBorderProperty.PropertyName + || e.PropertyName == ExtendedEntry.BorderColorProperty.PropertyName) + { + SetBorder(view); + } + if(e.PropertyName == ExtendedEntry.PlaceholderTextColorProperty.PropertyName) + { + SetPlaceholderTextColor(view); + } + else + { + base.OnElementPropertyChanged(sender, e); + if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + { + Control.SetBackgroundColor(view.BackgroundColor.ToAndroid()); + } + } + } + + private void SetBorder(ExtendedEntry view) + { + //Not suported on Android + } + + private void SetMaxLength(ExtendedEntry view) + { + Control.SetFilters(new IInputFilter[] { new InputFilterLengthFilter(view.MaxLength) }); + } + + private void SetPlaceholderTextColor(ExtendedEntry view) + { + if(view.PlaceholderTextColor != Xamarin.Forms.Color.Default) + { + Control.SetHintTextColor(view.PlaceholderTextColor.ToAndroid()); + } + } + } +} diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml index eda2b1f0c..8b466acce 100644 --- a/src/Android/Properties/AndroidManifest.xml +++ b/src/Android/Properties/AndroidManifest.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/src/App/App.csproj b/src/App/App.csproj index 9c88afb94..2ce30f8b0 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -44,6 +44,10 @@ + + + + diff --git a/src/App/Controls/BottomBorderEntry.cs b/src/App/Controls/BottomBorderEntry.cs new file mode 100644 index 000000000..6c947c842 --- /dev/null +++ b/src/App/Controls/BottomBorderEntry.cs @@ -0,0 +1,13 @@ +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class BottomBorderEntry : ExtendedEntry + { + public BottomBorderEntry() + { + HasBorder = HasOnlyBottomBorder = true; + BorderColor = Color.FromHex("d2d6de"); + } + } +} diff --git a/src/App/Controls/EntryLabel.cs b/src/App/Controls/EntryLabel.cs new file mode 100644 index 000000000..5db9ad725 --- /dev/null +++ b/src/App/Controls/EntryLabel.cs @@ -0,0 +1,13 @@ +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class EntryLabel : Label + { + public EntryLabel() + { + FontSize = 14; + TextColor = Color.FromHex("777777"); + } + } +} diff --git a/src/App/Controls/ExtendedEntry.cs b/src/App/Controls/ExtendedEntry.cs new file mode 100644 index 000000000..7e27c1254 --- /dev/null +++ b/src/App/Controls/ExtendedEntry.cs @@ -0,0 +1,53 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class ExtendedEntry : Entry + { + public static readonly BindableProperty HasBorderProperty = + BindableProperty.Create(nameof(HasBorder), typeof(bool), typeof(ExtendedEntry), true); + + public static readonly BindableProperty HasOnlyBottomBorderProperty = + BindableProperty.Create(nameof(HasOnlyBottomBorder), typeof(bool), typeof(ExtendedEntry), false); + + public static readonly BindableProperty BorderColorProperty = + BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(ExtendedEntry), Color.Default); + + public static readonly BindableProperty PlaceholderTextColorProperty = + BindableProperty.Create(nameof(PlaceholderTextColor), typeof(Color), typeof(ExtendedEntry), Color.Default); + + public static readonly BindableProperty MaxLengthProperty = + BindableProperty.Create(nameof(MaxLength), typeof(int), typeof(ExtendedEntry), int.MaxValue); + + public bool HasBorder + { + get { return (bool)GetValue(HasBorderProperty); } + set { SetValue(HasBorderProperty, value); } + } + + public bool HasOnlyBottomBorder + { + get { return (bool)GetValue(HasOnlyBottomBorderProperty); } + set { SetValue(HasOnlyBottomBorderProperty, value); } + } + + public Color BorderColor + { + get { return (Color)GetValue(BorderColorProperty); } + set { SetValue(BorderColorProperty, value); } + } + + public Color PlaceholderTextColor + { + get { return (Color)GetValue(PlaceholderTextColorProperty); } + set { SetValue(PlaceholderTextColorProperty, value); } + } + + public int MaxLength + { + get { return (int)GetValue(MaxLengthProperty); } + set { SetValue(MaxLengthProperty, value); } + } + } +} diff --git a/src/App/Controls/ExtendedTabbedPage.cs b/src/App/Controls/ExtendedTabbedPage.cs new file mode 100644 index 000000000..7c16a1fd3 --- /dev/null +++ b/src/App/Controls/ExtendedTabbedPage.cs @@ -0,0 +1,26 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Controls +{ + public class ExtendedTabbedPage : TabbedPage + { + public static readonly BindableProperty TintColorProperty = + BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(ExtendedTabbedPage), Color.White); + + public static readonly BindableProperty BarTintColorProperty = + BindableProperty.Create(nameof(BarTintColor), typeof(Color), typeof(ExtendedTabbedPage), Color.White); + + public Color TintColor + { + get { return (Color)GetValue(TintColorProperty); } + set { SetValue(TintColorProperty, value); } + } + + public Color BarTintColor + { + get { return (Color)GetValue(BarTintColorProperty); } + set { SetValue(BarTintColorProperty, value); } + } + } +} diff --git a/src/App/Pages/MainPage.cs b/src/App/Pages/MainPage.cs index 9de9e3feb..894f9bc19 100644 --- a/src/App/Pages/MainPage.cs +++ b/src/App/Pages/MainPage.cs @@ -1,13 +1,17 @@ using System; +using Bit.App.Controls; using Bit.App.Resources; using Xamarin.Forms; namespace Bit.App.Pages { - public class MainPage : TabbedPage + public class MainPage : ExtendedTabbedPage { public MainPage() { + BarTintColor = Color.FromHex("222d32"); + TintColor = Color.FromHex("ffffff"); + var settingsNavigation = new NavigationPage(new SettingsPage()); var vaultNavigation = new NavigationPage(new VaultListPage()); var syncNavigation = new NavigationPage(new SyncPage()); diff --git a/src/App/Pages/VaultAddSitePage.cs b/src/App/Pages/VaultAddSitePage.cs index 5f650cbfb..63921647d 100644 --- a/src/App/Pages/VaultAddSitePage.cs +++ b/src/App/Pages/VaultAddSitePage.cs @@ -2,6 +2,7 @@ using System.Linq; using Acr.UserDialogs; using Bit.App.Abstractions; +using Bit.App.Controls; using Bit.App.Models; using Bit.App.Resources; using Plugin.Connectivity.Abstractions; @@ -31,8 +32,8 @@ namespace Bit.App.Pages { var folders = _folderService.GetAllAsync().GetAwaiter().GetResult().OrderBy(f => f.Name?.Decrypt()); - var uriEntry = new Entry { Keyboard = Keyboard.Url }; - var nameEntry = new Entry(); + var uriEntry = new BottomBorderEntry { Keyboard = Keyboard.Url }; + var nameEntry = new BottomBorderEntry(); var folderPicker = new Picker { Title = AppResources.Folder }; folderPicker.Items.Add(AppResources.FolderNone); folderPicker.SelectedIndex = 0; @@ -40,22 +41,22 @@ namespace Bit.App.Pages { folderPicker.Items.Add(folder.Name.Decrypt()); } - var usernameEntry = new Entry(); - var passwordEntry = new Entry { IsPassword = true }; + var usernameEntry = new BottomBorderEntry(); + var passwordEntry = new BottomBorderEntry { IsPassword = true }; var notesEditor = new Editor(); - var stackLayout = new StackLayout(); - stackLayout.Children.Add(new Label { Text = AppResources.URI }); + var stackLayout = new StackLayout { Padding = new Thickness(15) }; + stackLayout.Children.Add(new EntryLabel { Text = AppResources.URI }); stackLayout.Children.Add(uriEntry); - stackLayout.Children.Add(new Label { Text = AppResources.Name }); + stackLayout.Children.Add(new EntryLabel { Text = AppResources.Name }); stackLayout.Children.Add(nameEntry); - stackLayout.Children.Add(new Label { Text = AppResources.Folder }); + stackLayout.Children.Add(new EntryLabel { Text = AppResources.Folder }); stackLayout.Children.Add(folderPicker); - stackLayout.Children.Add(new Label { Text = AppResources.Username }); + stackLayout.Children.Add(new EntryLabel { Text = AppResources.Username }); stackLayout.Children.Add(usernameEntry); - stackLayout.Children.Add(new Label { Text = AppResources.Password }); + stackLayout.Children.Add(new EntryLabel { Text = AppResources.Password }); stackLayout.Children.Add(passwordEntry); - stackLayout.Children.Add(new Label { Text = AppResources.Notes }); + stackLayout.Children.Add(new EntryLabel { Text = AppResources.Notes }); stackLayout.Children.Add(notesEditor); var scrollView = new ScrollView diff --git a/src/iOS/Controls/ExtendedEntryRenderer.cs b/src/iOS/Controls/ExtendedEntryRenderer.cs new file mode 100644 index 000000000..480a950ed --- /dev/null +++ b/src/iOS/Controls/ExtendedEntryRenderer.cs @@ -0,0 +1,95 @@ +using System; +using System.ComponentModel; +using Bit.App.Controls; +using Bit.iOS.Controls; +using CoreAnimation; +using CoreGraphics; +using Foundation; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))] +namespace Bit.iOS.Controls +{ + public class ExtendedEntryRenderer : EntryRenderer + { + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + var view = e.NewElement as ExtendedEntry; + if(view != null) + { + SetBorder(view); + SetPlaceholderTextColor(view); + SetMaxLength(view); + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + var view = (ExtendedEntry)Element; + + if(e.PropertyName == ExtendedEntry.HasBorderProperty.PropertyName + || e.PropertyName == ExtendedEntry.HasOnlyBottomBorderProperty.PropertyName + || e.PropertyName == ExtendedEntry.BorderColorProperty.PropertyName) + { + SetBorder(view); + } + if(e.PropertyName == ExtendedEntry.PlaceholderTextColorProperty.PropertyName) + { + SetPlaceholderTextColor(view); + } + } + + private void SetBorder(ExtendedEntry view) + { + if(view.HasBorder && view.HasOnlyBottomBorder) + { + var borderLayer = new CALayer(); + borderLayer.MasksToBounds = true; + borderLayer.Frame = new CGRect(0f, Frame.Height / 2, Frame.Width, 1f); + borderLayer.BorderColor = view.BorderColor.ToCGColor(); + borderLayer.BorderWidth = 1.0f; + + Control.Layer.AddSublayer(borderLayer); + Control.BorderStyle = UITextBorderStyle.None; + } + else if(view.HasBorder) + { + Control.BorderStyle = UITextBorderStyle.Line; + } + else + { + Control.BorderStyle = UITextBorderStyle.None; + } + } + + private void SetMaxLength(ExtendedEntry view) + { + Control.ShouldChangeCharacters = (textField, range, replacementString) => + { + var newLength = textField.Text.Length + replacementString.Length - range.Length; + return newLength <= view.MaxLength; + }; + } + + private void SetPlaceholderTextColor(ExtendedEntry view) + { + if(string.IsNullOrEmpty(view.Placeholder) == false && view.PlaceholderTextColor != Color.Default) + { + var placeholderString = new NSAttributedString( + view.Placeholder, + new UIStringAttributes() + { + ForegroundColor = view.PlaceholderTextColor.ToUIColor() + }); + + Control.AttributedPlaceholder = placeholderString; + } + } + } +} diff --git a/src/iOS/Controls/ExtendedTabbedPageRenderer.cs b/src/iOS/Controls/ExtendedTabbedPageRenderer.cs new file mode 100644 index 000000000..ef064d3fa --- /dev/null +++ b/src/iOS/Controls/ExtendedTabbedPageRenderer.cs @@ -0,0 +1,23 @@ +using System; +using Bit.App.Controls; +using Bit.iOS.Controls; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))] +namespace Bit.iOS.Controls +{ + public class ExtendedTabbedPageRenderer: TabbedRenderer + { + protected override void OnElementChanged(VisualElementChangedEventArgs e) + { + base.OnElementChanged(e); + + var page = (ExtendedTabbedPage)Element; + + TabBar.TintColor = page.TintColor.ToUIColor(); + TabBar.BarTintColor = page.BarTintColor.ToUIColor(); + TabBar.BackgroundColor = page.BackgroundColor.ToUIColor(); + } + } +} diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist index a27212cb0..6d46c8d50 100644 --- a/src/iOS/Info.plist +++ b/src/iOS/Info.plist @@ -22,7 +22,7 @@ UIInterfaceOrientationLandscapeRight MinimumOSVersion - 8.0 + 9.3 CFBundleDisplayName bitwarden CFBundleIdentifier diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj index 91259c0f1..4065adba6 100644 --- a/src/iOS/iOS.csproj +++ b/src/iOS/iOS.csproj @@ -23,9 +23,19 @@ prompt 4 false - i386, x86_64 + x86_64 None - true + True + 9.3 + False + False + False + False + False + False + True + False + none @@ -91,6 +101,8 @@ Entitlements.plist + +