[Shell,iOS,Android] Add Colors, Font, HorizontalAlignment. Keyboard, Focus to SearchH...
authorRui Marinho <me@ruimarinho.net>
Thu, 9 May 2019 17:42:19 +0000 (10:42 -0700)
committerSamantha Houts <samhouts@users.noreply.github.com>
Thu, 9 May 2019 17:42:18 +0000 (10:42 -0700)
* [Shell,iOS] Add Colors, Font , HorizontalAlignment and keyboard support to SearchHandler

* {Android]Allow set colors and customize text on Shell SearchHandler

* [Android] Update icons color on SearchView

* [Android] Update Placeholdericon color from one place

* [iOS] Update shell cancel button colours

* [iOS,Android] Update IconPlaceholder color

* [Core] Fix missing region

* [Controls] Add shell SearchHandlerPage

* [iOS] Fix alignment changes on SearchHandlerAppearanceTraccker

* [Shell,Core] Add focus to SearchHandler

* [Android] Add SearchHandler focus

* [iOS] Add SearchHandler focus

* [Controls] Reuse dynamic gallery for SearchHandler

* [Controls] Fixes to gallery

* [Android] Remove cancel button code

* [Android] Add extension point for SearchHandlerAppearanceTracker

* [Android] Fix spelling

* fix bad merge

fixes #3125
fixes #5768

14 files changed:
Xamarin.Forms.Controls/Controls/ColorPicker.cs
Xamarin.Forms.Controls/GalleryPages/DynamicViewGallery.cs
Xamarin.Forms.Controls/XamStore/StoreShell.xaml
Xamarin.Forms.Controls/XamStore/Views/SearchHandlerPage.cs [new file with mode: 0644]
Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
Xamarin.Forms.Core/Shell/SearchHandler.cs
Xamarin.Forms.Platform.Android/Renderers/SearchHandlerAppearanceTracker.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs
Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
Xamarin.Forms.Platform.iOS/Renderers/FormattedStringExtensions.cs
Xamarin.Forms.Platform.iOS/Renderers/SearchHandlerAppearanceTracker.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs
Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
Xamarin.Forms.sln.DotSettings

index 7808a3f..1c5d7e2 100644 (file)
@@ -17,7 +17,7 @@ namespace Xamarin.Forms.Controls
 
                Label _titleLabel;
                Slider[] _sliders;
-               BoxView _box;
+               Frame _box;
                Label _hexLabel;
                Switch _useDefault;
 
@@ -69,11 +69,13 @@ namespace Xamarin.Forms.Controls
                                grid.AddChild(_sliders[i], 1, i + 1);
                        }
 
-                       _box = new BoxView
+                       _box = new Frame
                        {
-                               Color = Color,
+                               BackgroundColor = Color,
                                HorizontalOptions = LayoutOptions.Fill,
                                VerticalOptions = LayoutOptions.Fill,
+                               BorderColor = Color.Black,
+
                        };
                        grid.AddChild(_box, 2, 1, 1, 3);
 
@@ -89,6 +91,14 @@ namespace Xamarin.Forms.Controls
                        Content = grid;
                }
 
+               public void InitWithColor(Color color)
+               {
+                       _sliders[0].Value = color.R * 255;
+                       _sliders[1].Value = color.G * 255;
+                       _sliders[2].Value = color.B * 255;
+                       _sliders[3].Value = color.A * 255;
+               }
+
                public string Title
                {
                        get => (string)GetValue(TitleProperty);
@@ -133,7 +143,7 @@ namespace Xamarin.Forms.Controls
                        {
                                var color = picker.UseDefault ? Color.Default : picker.Color;
                                picker._hexLabel.Text = color.IsDefault ? "<default>" : ColorToHex(color);
-                               picker._box.Color = color;
+                               picker._box.BackgroundColor = color;
                                picker.ColorPicked?.Invoke(picker, new ColorPickedEventArgs(color));
                        }
                }
index e8dbe31..b8a6791 100644 (file)
@@ -2,31 +2,14 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
+using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Controls
 {
+       [Preserve(AllMembers = true)]
        public class DynamicViewGallery : ContentPage
        {
-               Dictionary<string, (Func<View> ctor, NamedAction[] methods)> _testedTypes;
-
-               HashSet<string> _exceptProperties = new HashSet<string>
-               {
-                       AutomationIdProperty.PropertyName,
-                       ClassIdProperty.PropertyName,
-                       "StyleId",
-               };
-
-               View _element;
-
-               StackLayout _propertyLayout;
-
-               StackLayout _pageContent;
-
-               Picker _selector;
-
-               public DynamicViewGallery()
-               {
-                       _testedTypes = new Dictionary<string, (Func<View> ctor, NamedAction[] methods)>
+               internal static Dictionary<string, (Func<object> ctor, NamedAction[] methods)> TestedTypes = new Dictionary<string, (Func<object> ctor, NamedAction[] methods)>
                        {
                                { nameof(ActivityIndicator), (() => new ActivityIndicator() { IsRunning = false }, null) },
                                { nameof(ProgressBar), (() => new ProgressBar(), null) },
@@ -45,10 +28,28 @@ namespace Xamarin.Forms.Controls
                                { nameof(TimePicker), (() => new TimePicker(), null) },
                                { nameof(ListView), (() => new ListView(), null) },
                                { nameof(BoxView), (() => new BoxView(), null) },
+
                        };
 
+               internal static HashSet<string> ExceptProperties = new HashSet<string>
+               {
+                       AutomationIdProperty.PropertyName,
+                       ClassIdProperty.PropertyName,
+                       "StyleId",
+               };
+
+               View _element;
+
+               StackLayout _propertyLayout;
+
+               StackLayout _pageContent;
+
+               Picker _selector;
+
+               public DynamicViewGallery()
+               {
                        _selector = new Picker();
-                       foreach (var item in _testedTypes)
+                       foreach (var item in TestedTypes)
                                _selector.Items.Add(item.Key.ToString());
                        _selector.SelectedIndexChanged += TypeSelected;
 
@@ -106,15 +107,22 @@ namespace Xamarin.Forms.Controls
 
                        var elementType = _element.GetType();
 
+                       GetProperties(_element, elementType, _propertyLayout);
+
+                       _pageContent.Children.Add(_element);
+               }
+
+               internal static void GetProperties(BindableObject element, Type elementType, StackLayout propertyLayout)
+               {
                        var publicProperties = elementType
-                               .GetProperties(BindingFlags.Public | BindingFlags.Instance)
-                               .Where(p => p.CanRead && p.CanWrite && !_exceptProperties.Contains(p.Name));
+                                                       .GetProperties(BindingFlags.Public | BindingFlags.Instance)
+                                                       .Where(p => p.CanRead && p.CanWrite && !ExceptProperties.Contains(p.Name));
 
                        // BindableProperty used to clean property values
                        var bindableProperties = elementType
                                .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
                                .Where(p => p.FieldType.IsAssignableFrom(typeof(BindableProperty)))
-                               .Select(p => (BindableProperty)p.GetValue(_element));
+                               .Select(p => (BindableProperty)p.GetValue(element));
 
                        foreach (var property in publicProperties)
                        {
@@ -123,29 +131,33 @@ namespace Xamarin.Forms.Controls
                                        var colorPicker = new ColorPicker
                                        {
                                                Title = property.Name,
-                                               Color = (Color)property.GetValue(_element)
+                                               Color = (Color)property.GetValue(element)
                                        };
-                                       colorPicker.ColorPicked += (_, e) => property.SetValue(_element, e.Color);
-                                       _propertyLayout.Children.Add(colorPicker);
+                                       colorPicker.ColorPicked += (_, e) => property.SetValue(element, e.Color);
+                                       propertyLayout.Children.Add(colorPicker);
                                }
                                else if (property.PropertyType == typeof(string))
                                {
-                                       _propertyLayout.Children.Add(CreateStringPicker(property));
+                                       propertyLayout.Children.Add(CreateStringPicker(property, element));
                                }
                                else if (property.PropertyType == typeof(double) ||
                                        property.PropertyType == typeof(float) ||
                                        property.PropertyType == typeof(int))
                                {
-                                       _propertyLayout.Children.Add(
-                                               CreateValuePicker(property, bindableProperties.FirstOrDefault(p => p.PropertyName == property.Name)));
+                                       propertyLayout.Children.Add(
+                                               CreateValuePicker(property, bindableProperties.FirstOrDefault(p => p.PropertyName == property.Name), element));
                                }
                                else if (property.PropertyType == typeof(bool))
                                {
-                                       _propertyLayout.Children.Add(CreateBooleanPicker(property));
+                                       propertyLayout.Children.Add(CreateBooleanPicker(property, element));
                                }
                                else if (property.PropertyType == typeof(Thickness))
                                {
-                                       _propertyLayout.Children.Add(CreateThicknessPicker(property));
+                                       propertyLayout.Children.Add(CreateThicknessPicker(property, element));
+                               }
+                               else if (property.PropertyType == typeof(TextAlignment))
+                               {
+                                       propertyLayout.Children.Add(CreateEnumPicker(property, element, typeof(TextAlignment)));
                                }
                                else
                                {
@@ -153,10 +165,11 @@ namespace Xamarin.Forms.Controls
                                }
                        }
 
-                       var customMethods = _testedTypes[elementType.Name].methods;
+                       var customMethods = TestedTypes[elementType.Name].methods;
                        if (customMethods != null)
                        {
-                               _propertyLayout.Children.Add(new Label {
+                               propertyLayout.Children.Add(new Label
+                               {
                                        Text = "Custom methods",
                                        FontSize = 20,
                                        Margin = 6
@@ -164,17 +177,15 @@ namespace Xamarin.Forms.Controls
 
                                foreach (var method in customMethods)
                                {
-                                       _propertyLayout.Children.Add(new Button
+                                       propertyLayout.Children.Add(new Button
                                        {
                                                Text = method.Name,
                                                FontAttributes = FontAttributes.Bold,
                                                Padding = 6,
-                                               Command = new Command(() => method.Action(_element))
+                                               Command = new Command(() => method.Action(element))
                                        });
                                }
                        }
-
-                       _pageContent.Children.Add(_element);
                }
 
                void TypeSelected(object sender, EventArgs e)
@@ -182,7 +193,7 @@ namespace Xamarin.Forms.Controls
                        var oldElement = _element;
                        try
                        {
-                               _element = _testedTypes[(string)_selector.SelectedItem].ctor();
+                               _element = TestedTypes[(string)_selector.SelectedItem].ctor() as View;
                        }
                        catch
                        {
@@ -191,7 +202,7 @@ namespace Xamarin.Forms.Controls
                        OnElementUpdated(oldElement);
                }
 
-               Dictionary<string, (double min, double max)> _minMaxProperties = new Dictionary<string, (double min, double max)>
+               static Dictionary<string, (double min, double max)> _minMaxProperties = new Dictionary<string, (double min, double max)>
                {
                        { ScaleProperty.PropertyName, (0d, 1d) },
                        { ScaleXProperty.PropertyName, (0d, 1d) },
@@ -204,7 +215,7 @@ namespace Xamarin.Forms.Controls
                        { PaddingElement.PaddingProperty.PropertyName, (-100, 100) },
                };
 
-               Grid CreateValuePicker(PropertyInfo property, BindableProperty bindableProperty)
+               static Grid CreateValuePicker(PropertyInfo property, BindableProperty bindableProperty, BindableObject element)
                {
                        var min = 0d;
                        var max = 100d;
@@ -215,7 +226,7 @@ namespace Xamarin.Forms.Controls
                        }
 
                        var isInt = property.PropertyType == typeof(int);
-                       var value = isInt ? (int)property.GetValue(_element) : (double)property.GetValue(_element);
+                       var value = isInt ? (int)property.GetValue(element) : (double)property.GetValue(element);
                        var slider = new Slider(min, max, value);
 
                        var actions = new Grid
@@ -243,7 +254,7 @@ namespace Xamarin.Forms.Controls
                                        HeightRequest = 28,
                                        Margin = 0,
                                        Padding = 0,
-                                       Command = new Command(() => _element.ClearValue(bindableProperty))
+                                       Command = new Command(() => element.ClearValue(bindableProperty))
                                }, 1, 0);
                        }
 
@@ -256,9 +267,9 @@ namespace Xamarin.Forms.Controls
                        slider.ValueChanged += (_, e) =>
                        {
                                if (isInt)
-                                       property.SetValue(_element, (int)e.NewValue);
+                                       property.SetValue(element, (int)e.NewValue);
                                else
-                                       property.SetValue(_element, e.NewValue);
+                                       property.SetValue(element, e.NewValue);
                                valueLabel.Text = e.NewValue.ToString(isInt ? "0" : "0.#");
                        };
 
@@ -268,7 +279,44 @@ namespace Xamarin.Forms.Controls
                        return actions;
                }
 
-               Grid CreateThicknessPicker(PropertyInfo property)
+               static Grid CreateEnumPicker(PropertyInfo property, BindableObject element, Type elementType)
+               {
+                       var grid = new Grid
+                       {
+                               Padding = 0,
+                               RowSpacing = 3,
+                               ColumnSpacing = 3,
+                               ColumnDefinitions =
+                                               {
+                                                       new ColumnDefinition { Width = 100 },
+                                                       new ColumnDefinition { Width = GridLength.Star }
+                                               },
+                       };
+                       grid.AddChild(new Label { Text = property.Name, FontAttributes = FontAttributes.Bold }, 0, 0);
+                       var picker = new Picker { Title = property.Name };
+                       foreach (var item in Enum.GetNames(elementType))
+                               picker.Items.Add(item);
+                       
+                       picker.SelectedItem = property.GetValue(element).ToString();
+                       grid.AddChild(picker, 1, 0);
+                       picker.SelectedIndexChanged += (_, e) =>
+                       {
+                               var newEnumValue = Enum.Parse(elementType, picker.SelectedItem.ToString());
+                               property.SetValue(element, newEnumValue);
+                       };
+                       element.PropertyChanged += (_, e) =>
+                       {
+                               if (e.PropertyName == property.Name)
+                               {
+                                       var newVal = property.GetValue(element);
+                                       if (newVal.ToString() != picker.SelectedItem.ToString())
+                                               picker.SelectedItem = newVal;
+                               }
+                       };
+                       return grid;
+               }
+
+               static Grid CreateThicknessPicker(PropertyInfo property, BindableObject element)
                {
                        var grid = new Grid
                        {
@@ -284,7 +332,7 @@ namespace Xamarin.Forms.Controls
                        };
                        grid.AddChild(new Label { Text = property.Name, FontAttributes = FontAttributes.Bold }, 0, 0, 2);
 
-                       var val = (Thickness)property.GetValue(_element);
+                       var val = (Thickness)property.GetValue(element);
                        var sliders = new Slider[4];
                        var valueLabels = new Label[4];
                        for (int i = 0; i < 4; i++)
@@ -324,7 +372,7 @@ namespace Xamarin.Forms.Controls
 
                        void ThicknessChanged(object sender, ValueChangedEventArgs e)
                        {
-                               property.SetValue(_element, new Thickness(sliders[0].Value, sliders[1].Value, sliders[2].Value, sliders[3].Value));
+                               property.SetValue(element, new Thickness(sliders[0].Value, sliders[1].Value, sliders[2].Value, sliders[3].Value));
                                for (int i = 0; i < valueLabels.Length; i++)
                                        valueLabels[i].Text = sliders[i].Value.ToString("0");
                        }
@@ -332,7 +380,7 @@ namespace Xamarin.Forms.Controls
                        return grid;
                }
 
-               Grid CreateBooleanPicker(PropertyInfo property)
+               static Grid CreateBooleanPicker(PropertyInfo property, BindableObject element)
                {
                        var grid = new Grid
                        {
@@ -348,17 +396,17 @@ namespace Xamarin.Forms.Controls
                        grid.AddChild(new Label { Text = property.Name, FontAttributes = FontAttributes.Bold }, 0, 0);
                        var boolSwitch = new Switch
                        {
-                               IsToggled = (bool)property.GetValue(_element),
+                               IsToggled = (bool)property.GetValue(element),
                                HorizontalOptions = LayoutOptions.Center,
                                VerticalOptions = LayoutOptions.Center
                        };
-                       boolSwitch.Toggled += (_, e) => property.SetValue(_element, e.Value);
+                       boolSwitch.Toggled += (_, e) => property.SetValue(element, e.Value);
                        grid.AddChild(boolSwitch, 1, 0);
-                       _element.PropertyChanged += (_, e) =>
+                       element.PropertyChanged += (_, e) =>
                        {
                                if (e.PropertyName == property.Name)
                                {
-                                       var newVal = (bool)property.GetValue(_element);
+                                       var newVal = (bool)property.GetValue(element);
                                        if (newVal != boolSwitch.IsToggled)
                                                boolSwitch.IsToggled = newVal;
                                }
@@ -367,7 +415,7 @@ namespace Xamarin.Forms.Controls
                        return grid;
                }
 
-               Grid CreateStringPicker(PropertyInfo property)
+               static Grid CreateStringPicker(PropertyInfo property, BindableObject element)
                {
                        var grid = new Grid
                        {
@@ -378,17 +426,17 @@ namespace Xamarin.Forms.Controls
                        grid.AddChild(new Label { Text = property.Name, FontAttributes = FontAttributes.Bold }, 0, 0);
                        var entry = new Entry
                        {
-                               Text = (string)property.GetValue(_element),
+                               Text = (string)property.GetValue(element),
                                HorizontalOptions = LayoutOptions.FillAndExpand,
                                VerticalOptions = LayoutOptions.FillAndExpand
                        };
-                       entry.TextChanged += (_, e) => property.SetValue(_element, e.NewTextValue);
+                       entry.TextChanged += (_, e) => property.SetValue(element, e.NewTextValue);
                        grid.AddChild(entry, 0, 1);
-                       _element.PropertyChanged += (_, e) =>
+                       element.PropertyChanged += (_, e) =>
                        {
                                if (e.PropertyName == property.Name)
                                {
-                                       var newVal = (string)property.GetValue(_element);
+                                       var newVal = (string)property.GetValue(element);
                                        if (newVal != entry.Text)
                                                entry.Text = newVal;
                                }
@@ -397,14 +445,14 @@ namespace Xamarin.Forms.Controls
                        return grid;
                }
 
-               class NamedAction
+               internal class NamedAction
                {
                        public string Name { get; set; }
 
-                       public Action<View> Action { get; set; }
+                       public Action<object> Action { get; set; }
                }
 
-               (Func<View> ctor, NamedAction[] methods) GetPicker()
+               static (Func<View> ctor, NamedAction[] methods) GetPicker()
                {
                        return (ctor: () =>
                        {
index df23046..fd8c7b6 100644 (file)
@@ -33,7 +33,8 @@
        <Shell.FlyoutHeader>
                <local:FlyoutHeader />
        </Shell.FlyoutHeader>
-
+    
+    <ShellContent Title="Search" Route="search" ContentTemplate="{DataTemplate local:SearchHandlerPage}" />
        <ShellSection FlyoutDisplayOptions="AsMultipleItems" Route="apps" Title="My apps &amp; games" Icon="grid.png" Style="{StaticResource GreenShell}">
                <ShellContent Route="updates" Title="Updates" ContentTemplate="{DataTemplate local:UpdatesPage}" />
                <ShellContent Route="installed" Title="Installed" ContentTemplate="{DataTemplate local:InstalledPage}" />
diff --git a/Xamarin.Forms.Controls/XamStore/Views/SearchHandlerPage.cs b/Xamarin.Forms.Controls/XamStore/Views/SearchHandlerPage.cs
new file mode 100644 (file)
index 0000000..3469ab6
--- /dev/null
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.PlatformConfiguration;
+using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
+using static Xamarin.Forms.Controls.DynamicViewGallery;
+using static Xamarin.Forms.Controls.XamStore.BasePage;
+
+namespace Xamarin.Forms.Controls.XamStore
+{
+       [Preserve(AllMembers = true)]
+       public class SearchHandlerPage : ContentPage
+       {
+               SearchHandler _searchHandler;
+               StackLayout _propertyLayout;
+               const string SearchHandlerKey = nameof(SearchHandler);
+
+               public SearchHandlerPage()
+               {
+                       On<iOS>().SetUseSafeArea(true);
+
+                       var searchHandlerKey = nameof(SearchHandler);
+
+                       TestedTypes.Add(SearchHandlerKey, (AddSearchHandler, new NamedAction[] { new NamedAction { Name = nameof(Focus), Action = FocusUnfocusSearchHandler } }));
+
+                       _searchHandler = TestedTypes[SearchHandlerKey].ctor() as SearchHandler;
+
+                       _propertyLayout = new StackLayout
+                       {
+                               Spacing = 10,
+                               Padding = 10,
+                               Children = { new Button { Text = "Show/Hide SearchHandler", Command = new Command(()=> ShowHideSearchHandler()) }}
+                       };
+                       Content = new StackLayout
+                       {
+                               Children = {
+                                       new Label { Text = "Test SearchHandler on IOs that this text appears", HorizontalTextAlignment = TextAlignment.Center },
+                                       new ScrollView { Content = _propertyLayout }
+                               }
+                       };
+
+                       GetProperties(_searchHandler, typeof(SearchHandler), _propertyLayout);
+
+               }
+
+               void FocusUnfocusSearchHandler(object searchHandler)
+               {
+                       var sh = searchHandler as SearchHandler;
+                       if (sh == null)
+                               return;
+
+                       if (sh.IsFocused)
+                               sh.Unfocus();
+                       else
+                               sh.Focus();
+               }
+
+               void ShowHideSearchHandler()
+               {
+                       if (_searchHandler == null)
+                       {
+                               _searchHandler = TestedTypes[SearchHandlerKey].ctor() as SearchHandler;
+                       }
+                       else
+                       {
+                               _searchHandler = null;
+
+                               Shell.SetSearchHandler(this, _searchHandler);
+                       }
+               }
+
+               internal CustomSearchHandler AddSearchHandler()
+               {
+                       var searchHandler = new CustomSearchHandler();
+
+                       searchHandler.BackgroundColor = Color.Orange;
+                       searchHandler.CancelButtonColor = Color.Pink;
+                       searchHandler.TextColor = Color.White;
+                       searchHandler.PlaceholderColor = Color.Yellow;
+                       searchHandler.HorizontalTextAlignment = TextAlignment.Center;
+                       searchHandler.ShowsResults = true;
+
+                       searchHandler.Keyboard = Keyboard.Numeric;
+
+                       searchHandler.FontFamily = "ChalkboardSE-Regular";
+                       searchHandler.FontAttributes = FontAttributes.Bold;
+
+                       searchHandler.ClearIconName = "Clear";
+                       searchHandler.ClearIconHelpText = "Clears the search field text";
+
+                       searchHandler.ClearPlaceholderName = "Voice Search";
+                       searchHandler.ClearPlaceholderHelpText = "Start voice search";
+
+                       searchHandler.QueryIconName = "Search";
+                       searchHandler.QueryIconHelpText = "Press to search app";
+
+                       searchHandler.Placeholder = "Type to search";
+                       searchHandler.ClearPlaceholderEnabled = true;
+                       searchHandler.ClearPlaceholderIcon = "mic.png";
+
+                       Shell.SetSearchHandler(this, searchHandler);
+                       return searchHandler;
+               }
+
+       }
+}
+
index 6c2ee9a..601cb5d 100644 (file)
@@ -343,8 +343,18 @@ namespace Xamarin.Forms.Controls.XamStore
                {
                        var searchHandler = new CustomSearchHandler();
 
+                       searchHandler.BackgroundColor = Color.Orange;
+                       searchHandler.CancelButtonColor = Color.Pink;
+                       searchHandler.TextColor = Color.White;
+                       searchHandler.PlaceholderColor = Color.Yellow;
+                       searchHandler.HorizontalTextAlignment = TextAlignment.Center;
                        searchHandler.ShowsResults = true;
 
+                       searchHandler.Keyboard = Keyboard.Numeric;
+
+                       searchHandler.FontFamily = "ChalkboardSE-Regular";
+                       searchHandler.FontAttributes = FontAttributes.Bold;
+
                        searchHandler.ClearIconName = "Clear";
                        searchHandler.ClearIconHelpText = "Clears the search field text";
 
index 293f56c..6482b99 100644 (file)
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Windows.Input;
+using Xamarin.Forms.Internals;
+using static Xamarin.Forms.VisualElement;
 
 namespace Xamarin.Forms
 {
-       public class SearchHandler : BindableObject, ISearchHandlerController
+       public class SearchHandler : BindableObject, ISearchHandlerController, IPlaceholderElement, IFontElement, ITextElement, ITextAlignmentElement
        {
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public static readonly BindablePropertyKey IsFocusedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsFocused),
+                       typeof(bool), typeof(VisualElement), default(bool), propertyChanged: OnIsFocusedPropertyChanged);
+
+               public event EventHandler<EventArgs> Focused;
+               public event EventHandler<EventArgs> Unfocused;
+
+               public static readonly BindableProperty IsFocusedProperty = IsFocusedPropertyKey.BindableProperty;
+
+               public bool IsFocused
+               {
+                       get { return (bool)GetValue(IsFocusedProperty); }
+               }
+
+               static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
+               {
+                       var element = (SearchHandler)bindable;
+
+                       if (element == null)
+                       {
+                               return;
+                       }
+
+                       var isFocused = (bool)newvalue;
+                       if (isFocused)
+                       {
+                               element.Focused?.Invoke(element, new EventArgs());
+                               element.OnFocused();
+                       }
+                       else
+                       {
+                               element.Unfocused?.Invoke(element, new EventArgs());
+                               element.OnUnfocus();
+                       }
+               }
+
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public void SetIsFocused(bool value)
+               {
+                       SetValueCore(IsFocusedPropertyKey.BindableProperty, value);
+               }
+               [EditorBrowsable(EditorBrowsableState.Never)]
+               public event EventHandler<FocusRequestArgs> FocusChangeRequested;
+
+               public bool Focus()
+               {
+                       if (IsFocused)
+                               return true;
+
+                       if (FocusChangeRequested == null)
+                               return false;
+
+                       var arg = new FocusRequestArgs { Focus = true };
+                       FocusChangeRequested(this, arg);
+                       return arg.Result;
+               }
+
+               public void Unfocus()
+               {
+                       if (!IsFocused)
+                               return;
+
+                       FocusChangeRequested?.Invoke(this, new FocusRequestArgs());
+               }
+
+               protected virtual void OnFocused()
+               {
+                       
+               }
+
+               protected virtual void OnUnfocus()
+               {
+                       
+               }
+
+               public static readonly BindableProperty KeyboardProperty = BindableProperty.Create(nameof(Keyboard), typeof(Keyboard), typeof(SearchHandler), Keyboard.Default, coerceValue: (o, v) => (Keyboard)v ?? Keyboard.Default);
+
+               public Keyboard Keyboard
+               {
+                       get { return (Keyboard)GetValue(KeyboardProperty); }
+                       set { SetValue(KeyboardProperty, value); }
+               }
+
+               public static readonly BindableProperty HorizontalTextAlignmentProperty = TextAlignmentElement.HorizontalTextAlignmentProperty;
+
+               void ITextAlignmentElement.OnHorizontalTextAlignmentPropertyChanged(TextAlignment oldValue, TextAlignment newValue)
+               {
+               }
+
+               public TextAlignment HorizontalTextAlignment
+               {
+                       get { return (TextAlignment)GetValue(TextAlignmentElement.HorizontalTextAlignmentProperty); }
+                       set { SetValue(TextAlignmentElement.HorizontalTextAlignmentProperty, value); }
+               }
+
+               public static readonly BindableProperty TextColorProperty = TextElement.TextColorProperty;
+
+               void ITextElement.OnTextColorPropertyChanged(Color oldValue, Color newValue)
+               {
+               }
+
+               public Color TextColor
+               {
+                       get { return (Color)GetValue(TextElement.TextColorProperty); }
+                       set { SetValue(TextElement.TextColorProperty, value); }
+               }
+
+               public static readonly BindableProperty CancelButtonColorProperty = BindableProperty.Create(nameof(CancelButtonColor), typeof(Color), typeof(SearchHandler), default(Color));
+
+               public static readonly BindableProperty FontFamilyProperty = FontElement.FontFamilyProperty;
+
+               public static readonly BindableProperty FontSizeProperty = FontElement.FontSizeProperty;
+
+               public static readonly BindableProperty FontAttributesProperty = FontElement.FontAttributesProperty;
+
+               public static readonly BindableProperty PlaceholderProperty = PlaceholderElement.PlaceholderProperty;
+
+               public static readonly BindableProperty PlaceholderColorProperty = PlaceholderElement.PlaceholderColorProperty;
+
+               public Color CancelButtonColor
+               {
+                       get { return (Color)GetValue(CancelButtonColorProperty); }
+                       set { SetValue(CancelButtonColorProperty, value); }
+               }
+
+
+               public FontAttributes FontAttributes
+               {
+                       get { return (FontAttributes)GetValue(FontAttributesProperty); }
+                       set { SetValue(FontAttributesProperty, value); }
+               }
+
+               public string FontFamily
+               {
+                       get { return (string)GetValue(FontFamilyProperty); }
+                       set { SetValue(FontFamilyProperty, value); }
+               }
+
+               [TypeConverter(typeof(FontSizeConverter))]
+               public double FontSize
+               {
+                       get { return (double)GetValue(FontSizeProperty); }
+                       set { SetValue(FontSizeProperty, value); }
+               }
+
+               void IFontElement.OnFontFamilyChanged(string oldValue, string newValue)
+               {
+               }
+
+               void IFontElement.OnFontSizeChanged(double oldValue, double newValue)
+               {
+               }
+
+               double IFontElement.FontSizeDefaultValueCreator() =>
+                       Device.GetNamedSize(NamedSize.Default, typeof(SearchHandler));
+
+               void IFontElement.OnFontAttributesChanged(FontAttributes oldValue, FontAttributes newValue)
+               {
+               }
+
+               void IFontElement.OnFontChanged(Font oldValue, Font newValue)
+               {
+               }
+
+               public Color PlaceholderColor
+               {
+                       get => (Color)GetValue(PlaceholderElement.PlaceholderColorProperty);
+                       set => SetValue(PlaceholderElement.PlaceholderColorProperty, value);
+               }
+
+               public string Placeholder
+               {
+                       get => (string)GetValue(PlaceholderElement.PlaceholderProperty);
+                       set => SetValue(PlaceholderElement.PlaceholderProperty, value);
+               }
+
+               public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(SearchHandler), Color.Default);
+
+               public Color BackgroundColor
+               {
+                       get { return (Color)GetValue(BackgroundColorProperty); }
+                       set { SetValue(BackgroundColorProperty, value); }
+               }
+
                event EventHandler<ListProxyChangedEventArgs> ISearchHandlerController.ListProxyChanged
                {
                        add { _listProxyChanged += value; }
@@ -101,9 +289,6 @@ namespace Xamarin.Forms
                public static readonly BindableProperty ItemTemplateProperty =
                        BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(SearchHandler), null, BindingMode.OneTime);
 
-               public static readonly BindableProperty PlaceholderProperty =
-                       BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(SearchHandler), null, BindingMode.OneTime);
-
                public static readonly BindableProperty QueryIconHelpTextProperty =
                        BindableProperty.Create(nameof(QueryIconHelpText), typeof(string), typeof(SearchHandler), null, BindingMode.OneTime,
                                propertyChanged: (b, o, n) => ((SearchHandler)b).UpdateAutomationProperties());
@@ -223,12 +408,6 @@ namespace Xamarin.Forms
                        set { SetValue(ItemTemplateProperty, value); }
                }
 
-               public string Placeholder
-               {
-                       get { return (string)GetValue(PlaceholderProperty); }
-                       set { SetValue(PlaceholderProperty, value); }
-               }
-
                public string Query
                {
                        get { return (string)GetValue(QueryProperty); }
diff --git a/Xamarin.Forms.Platform.Android/Renderers/SearchHandlerAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/SearchHandlerAppearanceTracker.cs
new file mode 100644 (file)
index 0000000..fa16373
--- /dev/null
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Android.App;
+using Android.Content;
+using Android.Graphics;
+using Android.OS;
+using Android.Runtime;
+using Android.Text;
+using Android.Util;
+using Android.Views;
+using Android.Widget;
+using AView = Android.Views.View;
+using AImageButton = Android.Widget.ImageButton;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Platform.Android
+{
+       public class SearchHandlerAppearanceTracker : IDisposable
+       {
+               SearchHandler _searchHandler;
+               bool _disposed;
+               AView _control;
+               EditText _editText;
+               InputTypes _inputType;
+               TextColorSwitcher _textColorSwitcher;
+               TextColorSwitcher _hintColorSwitcher;
+
+               public SearchHandlerAppearanceTracker(IShellSearchView searchView)
+               {
+                       _searchHandler = searchView.SearchHandler;
+                       _control = searchView.View;
+                       _searchHandler.PropertyChanged += SearchHandlerPropertyChanged;
+                       _searchHandler.FocusChangeRequested += SearchHandlerFocusChangeRequested;
+                       _editText = (_control as ViewGroup).GetChildrenOfType<EditText>().FirstOrDefault();
+                       _textColorSwitcher = new TextColorSwitcher(_editText.TextColors, false);
+                       _hintColorSwitcher = new TextColorSwitcher(_editText.HintTextColors, false);
+                       UpdateSearchBarColors();
+                       UpdateFont();
+                       UpdateTextAlignment();
+                       UpdateInputType();
+               }
+
+               protected virtual void SearchHandlerFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
+               {
+                       e.Result = true;
+
+                       if (e.Focus)
+                       {
+                               _control?.RequestFocus();
+                               _control?.PostShowKeyboard();
+                       }
+                       else
+                       {
+                               _control.ClearFocus();
+                               _control.HideKeyboard();
+                       }
+               }
+
+               protected virtual void SearchHandlerPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+               {
+                       if (e.Is(SearchHandler.BackgroundColorProperty))
+                       {
+                               UpdateBackgroundColor();
+                       }
+                       else if (e.Is(SearchHandler.TextColorProperty))
+                       {
+                               UpdateTextColor();
+                       }
+                       else if (e.IsOneOf(SearchHandler.PlaceholderColorProperty))
+                       {
+                               UpdatePlaceholderColor();
+                       }
+                       else if (e.IsOneOf(SearchHandler.FontFamilyProperty, SearchHandler.FontAttributesProperty, SearchHandler.FontSizeProperty))
+                       {
+                               UpdateFont();
+                       }
+                       else if (e.Is(SearchHandler.CancelButtonColorProperty))
+                       {
+                               UpdateCancelButtonColor();
+                       }
+                       else if (e.Is(SearchHandler.KeyboardProperty))
+                       {
+                               UpdateInputType();
+                       }
+                       else if (e.Is(SearchHandler.HorizontalTextAlignmentProperty))
+                       {
+                               UpdateTextAlignment();
+                       }
+               }
+
+               void UpdateSearchBarColors()
+               {
+                       UpdateBackgroundColor();
+                       UpdateTextColor();
+                       UpdatePlaceholderColor();
+                       UpdateCancelButtonColor();
+               }
+
+               void UpdateFont()
+               {
+                       _editText.Typeface = _searchHandler.ToTypeface();
+                       _editText.SetTextSize(ComplexUnitType.Sp, (float)_searchHandler.FontSize);
+               }
+
+               void UpdatePlaceholderColor()
+               {
+                       _hintColorSwitcher?.UpdateTextColor(_editText, _searchHandler.PlaceholderColor, _editText.SetHintTextColor);
+               }
+
+               void UpdateTextAlignment()
+               {
+                       _editText.UpdateHorizontalAlignment(_searchHandler.HorizontalTextAlignment, _control.Context.HasRtlSupport(), Xamarin.Forms.TextAlignment.Center.ToVerticalGravityFlags());
+               }
+
+               void UpdateBackgroundColor()
+               {
+                       var linearLayout = (_control as ViewGroup).GetChildrenOfType<LinearLayout>().FirstOrDefault();
+                       linearLayout.SetBackgroundColor(_searchHandler.BackgroundColor.ToAndroid());
+               }
+               
+               void UpdateCancelButtonColor()
+               {
+                       //For now we are using the clear icon 
+                       //we should add a new cancel button that unfocus and hides keyboard
+                       UpdateClearIconColor();
+               }
+
+               void UpdateClearIconColor()
+               {
+                       UpdateImageButtonIconColor(nameof(SearchHandler.ClearIcon),_searchHandler.CancelButtonColor);
+               }
+
+               void UpdateClearPlaceholderIconColor()
+               {
+                       UpdateImageButtonIconColor(nameof(SearchHandler.ClearPlaceholderIcon), _searchHandler.TextColor);
+               }
+
+               void UpdateTextColor()
+               {
+                       var textColor = _searchHandler.TextColor;
+                       _textColorSwitcher?.UpdateTextColor(_editText, textColor);
+                       UpdateImageButtonIconColor("SearchIcon", textColor);
+                       UpdateClearPlaceholderIconColor();
+                       //we need to set the cursor to
+               }
+               void UpdateImageButtonIconColor(string tagName, Color toColor)
+               {
+                       var image = _control.FindViewWithTag(tagName) as AImageButton;
+                       if (image != null && image.Drawable != null)
+                       {
+                               if (!toColor.IsDefault)
+                                       image.Drawable.SetColorFilter(toColor.ToAndroid(), PorterDuff.Mode.SrcIn);
+                               else
+                                       image.Drawable.ClearColorFilter();
+                       }
+               }
+
+               void UpdateInputType()
+               {
+                       var keyboard = _searchHandler.Keyboard;
+
+                       _inputType = keyboard.ToInputType();
+                       bool isSpellCheckEnableSet = false;
+                       bool isSpellCheckEnable = false;
+                       // model.IsSet(InputView.IsSpellCheckEnabledProperty)
+                       if (!(keyboard is Internals.CustomKeyboard))
+                       {
+                               if (isSpellCheckEnableSet)
+                               {
+                                       if ((_inputType & InputTypes.TextFlagNoSuggestions) != InputTypes.TextFlagNoSuggestions)
+                                       {
+                                               if (!isSpellCheckEnable)
+                                                       _inputType = _inputType | InputTypes.TextFlagNoSuggestions;
+                                       }
+                               }
+                       }
+                       _editText.InputType = _inputType;
+
+                       if (keyboard == Keyboard.Numeric)
+                       {
+                               _editText.KeyListener = GetDigitsKeyListener(_inputType);
+                       }
+               }
+
+               protected virtual global::Android.Text.Method.NumberKeyListener GetDigitsKeyListener(InputTypes inputTypes)
+               {
+                       // Override this in a custom renderer to use a different NumberKeyListener
+                       // or to filter out input types you don't want to allow
+                       // (e.g., inputTypes &= ~InputTypes.NumberFlagSigned to disallow the sign)
+                       return LocalizedDigitsKeyListener.Create(inputTypes);
+               }
+
+               protected virtual void Dispose(bool disposing)
+               {
+                       if (_disposed)
+                               return;
+
+                       _disposed = true;
+
+                       if (disposing)
+                       {
+                               if (_searchHandler != null)
+                               {
+                                       _searchHandler.FocusChangeRequested -= SearchHandlerFocusChangeRequested;
+                                       _searchHandler.PropertyChanged -= SearchHandlerPropertyChanged;
+                               }
+                               _searchHandler = null;
+                               _control = null;
+                       }
+               }
+
+               public void Dispose()
+               {
+                       Dispose(true);
+               }
+       }
+}
\ No newline at end of file
index 9913eb8..8f50369 100644 (file)
@@ -10,7 +10,6 @@ using Java.Lang;
 using System;
 using System.ComponentModel;
 using System.Threading.Tasks;
-using Xamarin.Forms.Internals;
 using Xamarin.Forms.Platform.Android.FastRenderers;
 using AColor = Android.Graphics.Color;
 using AView = Android.Views.View;
@@ -42,6 +41,13 @@ namespace Xamarin.Forms.Platform.Android
                void IShellSearchView.LoadView()
                {
                        LoadView(SearchHandler);
+                       if (_searchHandlerAppearanceTracker == null)
+                               _searchHandlerAppearanceTracker = CreateSearchHandlerAppearanceTracker();
+               }
+
+               protected virtual SearchHandlerAppearanceTracker CreateSearchHandlerAppearanceTracker()
+               {
+                       return new SearchHandlerAppearanceTracker(this);
                }
 
                #endregion IShellSearchView
@@ -91,6 +97,7 @@ namespace Xamarin.Forms.Platform.Android
                AImageButton _searchButton;
                AppCompatAutoCompleteTextView _textBlock;
                bool _disposed;
+               SearchHandlerAppearanceTracker _searchHandlerAppearanceTracker;
 
                public ShellSearchView(Context context, IShellContext shellContext) : base(context)
                {
@@ -122,6 +129,7 @@ namespace Xamarin.Forms.Platform.Android
                        {
                                _disposed = true;
 
+                               _searchHandlerAppearanceTracker?.Dispose();
                                SearchHandler.PropertyChanged -= OnSearchHandlerPropertyChanged;
 
                                _textBlock.ItemClick -= OnTextBlockItemClicked;
@@ -149,6 +157,7 @@ namespace Xamarin.Forms.Platform.Android
                        _cardView = null;
                        _clearPlaceholderButton = null;
                        _shellContext = null;
+                       _searchHandlerAppearanceTracker = null;
 
                        SearchHandler = null;
                }
@@ -164,7 +173,6 @@ namespace Xamarin.Forms.Platform.Android
                        using (lp = new LayoutParams(LP.MatchParent, LP.MatchParent))
                                _cardView.LayoutParameters = lp;
 
-
                        var linearLayout = new LinearLayout(context);
                        using (lp = new LP(LP.MatchParent, LP.MatchParent))
                                linearLayout.LayoutParameters = lp;
@@ -174,7 +182,7 @@ namespace Xamarin.Forms.Platform.Android
 
                        int padding = (int)context.ToPixels(8);
 
-                       _searchButton = CreateImageButton(context, searchHandler, SearchHandler.QueryIconProperty, Resource.Drawable.abc_ic_search_api_material, padding, 0);
+                       _searchButton = CreateImageButton(context, searchHandler, SearchHandler.QueryIconProperty, Resource.Drawable.abc_ic_search_api_material, padding, 0, "SearchIcon");
 
                        lp = new LinearLayout.LayoutParams(0, LP.MatchParent)
                        {
@@ -199,16 +207,16 @@ namespace Xamarin.Forms.Platform.Android
                        _textBlock.SetDropDownBackgroundDrawable(new ClipDrawableWrapper(_textBlock.DropDownBackground));
 
                        // A note on accessibility. The _textBlocks hint is what android defaults to reading in the screen
-                       // reader. Therefor we do not need to set something else.
+                       // reader. Therefore, we do not need to set something else.
 
-                       _clearButton = CreateImageButton(context, searchHandler, SearchHandler.ClearIconProperty, Resource.Drawable.abc_ic_clear_material, 0, padding);
-                       _clearPlaceholderButton = CreateImageButton(context, searchHandler, SearchHandler.ClearPlaceholderIconProperty, -1, 0, padding);
+                       _clearButton = CreateImageButton(context, searchHandler, SearchHandler.ClearIconProperty, Resource.Drawable.abc_ic_clear_material, 0, padding, nameof(SearchHandler.ClearIcon));
+                       _clearPlaceholderButton = CreateImageButton(context, searchHandler, SearchHandler.ClearPlaceholderIconProperty, -1, 0, padding, nameof(SearchHandler.ClearPlaceholderIcon));
 
                        linearLayout.AddView(_searchButton);
                        linearLayout.AddView(_textBlock);
                        linearLayout.AddView(_clearButton);
                        linearLayout.AddView(_clearPlaceholderButton);
-
+               
                        UpdateClearButtonState();
 
                        // hook all events down here to avoid getting events while doing setup
@@ -218,7 +226,7 @@ namespace Xamarin.Forms.Platform.Android
                        _clearButton.Click += OnClearButtonClicked;
                        _clearPlaceholderButton.Click += OnClearPlaceholderButtonClicked;
                        _searchButton.Click += OnSearchButtonClicked;
-
+                       
                        AddView(_cardView);
 
                        linearLayout.Dispose();
@@ -293,9 +301,10 @@ namespace Xamarin.Forms.Platform.Android
                {
                }
 
-               AImageButton CreateImageButton(Context context, BindableObject bindable, BindableProperty property, int defaultImage, int leftMargin, int rightMargin)
+               AImageButton CreateImageButton(Context context, BindableObject bindable, BindableProperty property, int defaultImage, int leftMargin, int rightMargin, string tag)
                {
                        var result = new AImageButton(context);
+                       result.Tag = tag;
                        result.SetPadding(0, 0, 0, 0);
                        result.Focusable = false;
                        result.SetScaleType(ImageView.ScaleType.FitCenter);
@@ -364,7 +373,7 @@ namespace Xamarin.Forms.Platform.Android
                        {
                                base.Draw(canvas);
 
-                               // Step 1: Clip out the top shadow that was drawn as it wont look right when ligned up
+                               // Step 1: Clip out the top shadow that was drawn as it wont look right when lined up
                                var paint = new Paint
                                {
                                        Color = AColor.Black
index 4aa0de8..fa0ceb0 100644 (file)
     <Compile Include="Renderers\MotionEventHelper.cs" />
     <Compile Include="Renderers\PageContainer.cs" />
     <Compile Include="Renderers\ScrollViewContainer.cs" />
+    <Compile Include="Renderers\SearchHandlerAppearanceTracker.cs" />
     <Compile Include="Renderers\ShellBottomNavViewAppearanceTracker.cs" />
     <Compile Include="Renderers\ShellItemRenderer.cs" />
     <Compile Include="Renderers\ShellContentFragment.cs" />
index ffc3071..61ba295 100644 (file)
@@ -54,7 +54,7 @@ namespace Xamarin.Forms.Platform.MacOS
                        return attributed;
                }
                
-               internal static NSAttributedString ToAttributed(this Span span, Element owner, Color defaultForegroundColor, TextAlignment textAlignment, double lineHeight = -1.0)
+               internal static NSAttributedString ToAttributed(this Span span, BindableObject owner, Color defaultForegroundColor, TextAlignment textAlignment, double lineHeight = -1.0)
                {
                        if (span == null)
                                return null;
@@ -135,7 +135,7 @@ namespace Xamarin.Forms.Platform.MacOS
                        return attrString;
                }
 
-               internal static NSAttributedString ToAttributed(this FormattedString formattedString, Element owner,
+               internal static NSAttributedString ToAttributed(this FormattedString formattedString, BindableObject owner,
                        Color defaultForegroundColor, TextAlignment textAlignment = TextAlignment.Start, double lineHeight = -1.0)
                {
                        if (formattedString == null)
diff --git a/Xamarin.Forms.Platform.iOS/Renderers/SearchHandlerAppearanceTracker.cs b/Xamarin.Forms.Platform.iOS/Renderers/SearchHandlerAppearanceTracker.cs
new file mode 100644 (file)
index 0000000..aacab4d
--- /dev/null
@@ -0,0 +1,364 @@
+using System;
+using CoreGraphics;
+using Foundation;
+using UIKit;
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       public class SearchHandlerAppearanceTracker : IDisposable
+       {
+               UIColor _cancelButtonTextColorDefaultDisabled;
+               UIColor _cancelButtonTextColorDefaultHighlighted;
+               UIColor _cancelButtonTextColorDefaultNormal;
+               UIColor _defaultTextColor;
+               UIColor _defaultTintColor;
+               UIColor _defaultClearIconTintColor;
+               UIColor _defaultPlaceholderTintColor;
+               bool _hasCustomBackground;
+               UIColor _defaultBackgroundColor;
+               SearchHandler _searchHandler;
+               UISearchBar _uiSearchBar;
+               UIToolbar _numericAccessoryView;
+               bool _disposed;
+
+               public SearchHandlerAppearanceTracker(UISearchBar searchBar, SearchHandler searchHandler)
+               {
+                       _searchHandler = searchHandler;
+                       _searchHandler.PropertyChanged += SearchHandlerPropertyChanged;
+                       _searchHandler.FocusChangeRequested += SearchHandlerFocusChangeRequested;
+                       _uiSearchBar = searchBar;
+                       _uiSearchBar.OnEditingStarted += OnEditingStarted;
+                       _uiSearchBar.OnEditingStopped += OnEditingEnded;
+                       _uiSearchBar.TextChanged += OnTextChanged;
+                       _uiSearchBar.ShowsCancelButton = true;
+                       GetDefaultSearchBarColors(_uiSearchBar);
+                       var uiTextField = searchBar.FindDescendantView<UITextField>();
+                       UpdateSearchBarColors();
+                       UpdateSearchBarTextAlignment(uiTextField);
+                       UpdateFont(uiTextField);
+                       UpdateKeyboard();
+               }
+
+               public void UpdateSearchBarColors()
+               {
+                       var cancelButton = _uiSearchBar.FindDescendantView<UIButton>();
+                       var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
+
+                       UpdateTextColor(uiTextField);
+                       UpdateSearchBarPlaceholder(uiTextField);
+                       UpdateCancelButtonColor(cancelButton);
+                       UpdateSearchBarBackgroundColor(uiTextField);
+               }
+
+               void SearchHandlerFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e)
+               {
+                       if (e.Focus)
+                               e.Result = _uiSearchBar.BecomeFirstResponder();
+               }
+
+               void SearchHandlerPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+               {
+                       if (e.Is(SearchHandler.BackgroundColorProperty))
+                       {
+                               UpdateSearchBarBackgroundColor(_uiSearchBar.FindDescendantView<UITextField>());
+                       }
+                       else if (e.Is(SearchHandler.TextColorProperty))
+                       {
+                               UpdateTextColor(_uiSearchBar.FindDescendantView<UITextField>());
+                       }
+                       else if (e.IsOneOf(SearchHandler.PlaceholderColorProperty, SearchHandler.PlaceholderProperty))
+                       {
+                               UpdateSearchBarPlaceholder(_uiSearchBar.FindDescendantView<UITextField>());
+                       }
+                       else if (e.IsOneOf(SearchHandler.FontFamilyProperty, SearchHandler.FontAttributesProperty, SearchHandler.FontSizeProperty))
+                       {
+                               UpdateFont(_uiSearchBar.FindDescendantView<UITextField>());
+                       }
+                       else if (e.Is(SearchHandler.CancelButtonColorProperty))
+                       {
+                               UpdateCancelButtonColor(_uiSearchBar.FindDescendantView<UIButton>());
+                       }
+                       else if (e.Is(SearchHandler.KeyboardProperty))
+                       {
+                               UpdateKeyboard();
+                       }
+                       else if (e.Is(SearchHandler.HorizontalTextAlignmentProperty))
+                       {
+                               UpdateSearchBarTextAlignment(_uiSearchBar.FindDescendantView<UITextField>());
+                       }
+               }
+
+               void GetDefaultSearchBarColors(UISearchBar searchBar)
+               {
+                       _defaultTintColor = searchBar.BarTintColor;
+
+                       var cancelButton = searchBar.FindDescendantView<UIButton>();
+                       if (cancelButton != null)
+                       {
+                               _cancelButtonTextColorDefaultNormal = cancelButton.TitleColor(UIControlState.Normal);
+                               _cancelButtonTextColorDefaultHighlighted = cancelButton.TitleColor(UIControlState.Highlighted);
+                               _cancelButtonTextColorDefaultDisabled = cancelButton.TitleColor(UIControlState.Disabled);
+                       }
+               }
+
+               void UpdateFont(UITextField textField)
+               {
+                       if (textField == null)
+                               return;
+
+                       textField.Font = _searchHandler.ToUIFont();
+               }
+
+               void UpdateSearchBarBackgroundColor(UITextField textField)
+               {
+                       if (textField == null)
+                               return;
+
+                       var backGroundColor = _searchHandler.BackgroundColor;
+
+                       if (!_hasCustomBackground && backGroundColor.IsDefault)
+                               return;
+
+                       var backgroundView = textField.Subviews[0];
+
+                       if (backGroundColor.IsDefault)
+                       {
+                               backgroundView.Layer.CornerRadius = 0;
+                               backgroundView.ClipsToBounds = false;
+                               backgroundView.BackgroundColor = _defaultBackgroundColor;
+                       }
+
+                       _hasCustomBackground = true;
+
+                       backgroundView.Layer.CornerRadius = 10;
+                       backgroundView.ClipsToBounds = true;
+                       if (_defaultBackgroundColor == null)
+                               _defaultBackgroundColor = backgroundView.BackgroundColor;
+                       backgroundView.BackgroundColor = backGroundColor.ToUIColor();
+               }
+
+               void UpdateCancelButtonColor(UIButton cancelButton)
+               {
+                       if (cancelButton == null)
+                               return;
+
+                       var cancelColor = _searchHandler.CancelButtonColor;
+                       if (cancelColor.IsDefault)
+                       {
+                               cancelButton.SetTitleColor(_cancelButtonTextColorDefaultNormal, UIControlState.Normal);
+                               cancelButton.SetTitleColor(_cancelButtonTextColorDefaultHighlighted, UIControlState.Highlighted);
+                               cancelButton.SetTitleColor(_cancelButtonTextColorDefaultDisabled, UIControlState.Disabled);
+                       }
+                       else
+                       {
+                               var cancelUIColor = cancelColor.ToUIColor();
+                               cancelButton.SetTitleColor(cancelUIColor, UIControlState.Normal);
+                               cancelButton.SetTitleColor(cancelUIColor, UIControlState.Highlighted);
+                               cancelButton.SetTitleColor(cancelUIColor, UIControlState.Disabled);
+                       }
+
+                       UpdateClearIconColor(cancelColor);
+               }
+
+               void UpdateSearchBarPlaceholder(UITextField textField)
+               {
+                       if (textField == null)
+                               return;
+
+                       var formatted = (FormattedString)_searchHandler.Placeholder ?? string.Empty;
+                       var targetColor = _searchHandler.PlaceholderColor;
+                       var placeHolderColor = targetColor.IsDefault ? ColorExtensions.SeventyPercentGrey.ToColor() : targetColor;
+                       textField.AttributedPlaceholder = formatted.ToAttributed(_searchHandler, placeHolderColor, _searchHandler.HorizontalTextAlignment);
+
+                       //Center placeholder
+                       //var width = (_uiSearchBar.Frame.Width / 2) - textField.AttributedPlaceholder.Size.Width;
+
+                       //var paddingView = new UIImageView(new CGRect(0, 0, width, _uiSearchBar.Frame.Height));
+
+                       //textField.LeftView = paddingView;
+                       //var paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: searchBar.frame.height))
+
+                       //searchBarTextField.leftView = paddingView
+
+                       //searchBarTextField.leftViewMode = .unlessEditing
+               }
+
+               void UpdateTextColor(UITextField textField)
+               {
+                       if (textField == null)
+                               return;
+
+                       _defaultTextColor = _defaultTextColor ?? textField.TextColor;
+                       var targetColor = _searchHandler.TextColor;
+
+                       textField.TextColor = targetColor.IsDefault ? _defaultTextColor : targetColor.ToUIColor();
+                       UpdateSearchBarTintColor(targetColor);
+                       UpdateSearchButtonIconColor(targetColor);
+                       UpdateClearPlaceholderIconColor(targetColor);
+               }
+
+               void UpdateSearchBarTintColor(Color targetColor)
+               {
+                       _uiSearchBar.TintColor = targetColor.IsDefault ? _defaultTintColor : targetColor.ToUIColor();
+               }
+
+               void UpdateSearchButtonIconColor(Color targetColor)
+               {
+                       var imageView = _uiSearchBar.FindDescendantView<UITextField>()?.LeftView as UIImageView;
+
+                       _defaultClearIconTintColor = _defaultClearIconTintColor ?? imageView.TintColor;
+
+                       SetSearchBarIconColor(imageView, targetColor, _defaultClearIconTintColor);
+               }
+
+               void UpdateClearPlaceholderIconColor(Color targetColor)
+               {
+                       var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
+
+                       var uiButton = uiTextField.FindDescendantView<UIButton>();
+
+                       if (uiButton == null)
+                               return;
+
+                       _defaultPlaceholderTintColor = _defaultPlaceholderTintColor ?? uiButton?.ImageView?.TintColor;
+
+
+                       SetSearchBarIconColor(uiButton, targetColor, _defaultPlaceholderTintColor);
+                       uiButton.TintColor = targetColor.IsDefault ? _defaultPlaceholderTintColor : targetColor.ToUIColor();
+               }
+
+               void UpdateClearIconColor(Color targetColor)
+               {
+                       var uiTextField = _uiSearchBar.FindDescendantView<UITextField>();
+
+                       var uiButton = uiTextField.ValueForKey(new NSString("clearButton")) as UIButton;
+
+                       if (uiButton == null)
+                               return;
+
+                       _defaultClearIconTintColor = _defaultClearIconTintColor ?? uiButton?.TintColor;
+
+                       SetSearchBarIconColor(uiButton, targetColor, _defaultClearIconTintColor);
+
+               }
+
+               void UpdateSearchBarTextAlignment(UITextField textField)
+               {
+                       if (textField == null)
+                               return;
+
+                       textField.TextAlignment = _searchHandler.HorizontalTextAlignment.ToNativeTextAlignment(EffectiveFlowDirection.Explicit);
+               }
+
+               void UpdateKeyboard()
+               {
+                       var keyboard = _searchHandler.Keyboard;
+                       _uiSearchBar.ApplyKeyboard(keyboard);
+
+                       // iPhone does not have an enter key on numeric keyboards
+                       if (Device.Idiom == TargetIdiom.Phone && (keyboard == Keyboard.Numeric || keyboard == Keyboard.Telephone))
+                       {
+                               _numericAccessoryView = _numericAccessoryView ?? CreateNumericKeyboardAccessoryView();
+                               _uiSearchBar.InputAccessoryView = _numericAccessoryView;
+                       }
+                       else
+                       {
+                               _uiSearchBar.InputAccessoryView = null;
+                       }
+
+                       _uiSearchBar.ReloadInputViews();
+               }
+
+               void OnEditingEnded(object sender, EventArgs e)
+               {
+                       _searchHandler.SetIsFocused(false);
+               }
+
+               void OnEditingStarted(object sender, EventArgs e)
+               {
+                       UpdateCancelButtonColor(_uiSearchBar.FindDescendantView<UIButton>());
+                       _searchHandler.SetIsFocused(true);
+                       //ElementController?.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
+               }
+
+               void OnTextChanged(object sender, UISearchBarTextChangedEventArgs e)
+               {
+                       UpdateCancelButtonColor(_uiSearchBar.FindDescendantView<UIButton>());
+               }
+
+
+               void OnSearchButtonClicked(object sender, EventArgs e)
+               {
+                       ((ISearchHandlerController)_searchHandler).QueryConfirmed();
+                       _uiSearchBar.ResignFirstResponder();
+               }
+
+               UIToolbar CreateNumericKeyboardAccessoryView()
+               {
+                       var keyboardWidth = UIScreen.MainScreen.Bounds.Width;
+                       var accessoryView = new UIToolbar(new CGRect(0, 0, keyboardWidth, 44)) { BarStyle = UIBarStyle.Default, Translucent = true };
+
+                       var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
+                       var searchButton = new UIBarButtonItem(UIBarButtonSystemItem.Search, OnSearchButtonClicked);
+                       accessoryView.SetItems(new[] { spacer, searchButton }, false);
+
+                       return accessoryView;
+               }
+
+               static void SetSearchBarIconColor(UIImageView imageView, Color targetColor, UIColor defaultTintColor)
+               {
+                       var icon = imageView?.Image;
+
+                       if (icon == null || (targetColor.IsDefault && defaultTintColor == null))
+                               return;
+
+                       var newIcon = icon.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
+                       imageView.TintColor = targetColor.IsDefault ? defaultTintColor : targetColor.ToUIColor();
+                       imageView.Image = newIcon;
+               }
+
+               static void SetSearchBarIconColor(UIButton button, Color targetColor, UIColor defaultTintColor)
+               {
+                       var icon = button.ImageView?.Image;
+
+                       if (icon == null || (targetColor.IsDefault && defaultTintColor == null))
+                               return;
+
+                       var newIcon = icon.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
+                       button.SetImage(newIcon, UIControlState.Normal);
+                       button.SetImage(newIcon, UIControlState.Selected);
+                       button.SetImage(newIcon, UIControlState.Highlighted);
+                       button.TintColor = button.ImageView.TintColor = targetColor.IsDefault ? defaultTintColor : targetColor.ToUIColor();
+               }
+
+               protected virtual void Dispose(bool disposing)
+               {
+                       if (_disposed)
+                               return;
+
+                       _disposed = true;
+
+                       if (disposing)
+                       {
+                               if (_uiSearchBar != null)
+                               {
+                                       _uiSearchBar.OnEditingStarted -= OnEditingStarted;
+                                       _uiSearchBar.OnEditingStopped -= OnEditingEnded;
+                                       _uiSearchBar.TextChanged -= OnTextChanged;
+                               }
+                               if (_searchHandler != null)
+                               {
+                                       _searchHandler.FocusChangeRequested -= SearchHandlerFocusChangeRequested;
+                                       _searchHandler.PropertyChanged -= SearchHandlerPropertyChanged;
+                               }
+                               _searchHandler = null;
+                               _uiSearchBar = null;
+                       }
+               }
+
+               public void Dispose()
+               {
+                       Dispose(true);
+               }
+       }
+}
index 663241f..8230b98 100644 (file)
@@ -56,6 +56,7 @@ namespace Xamarin.Forms.Platform.iOS
                SearchHandler _searchHandler;
                Page _page;
                NSCache _nSCache;
+               SearchHandlerAppearanceTracker _searchHandlerAppearanceTracker;
 
                BackButtonBehavior BackButtonBehavior { get; set; }
                UINavigationItem NavigationItem { get; set; }
@@ -119,12 +120,14 @@ namespace Xamarin.Forms.Platform.iOS
                {
                        if (oldPage != null)
                        {
+                               oldPage.Appearing -= PageAppearing;
                                oldPage.PropertyChanged -= OnPagePropertyChanged;
                                ((INotifyCollectionChanged)oldPage.ToolbarItems).CollectionChanged -= OnToolbarItemsChanged;
                        }
 
                        if (newPage != null)
                        {
+                               newPage.Appearing += PageAppearing;
                                newPage.PropertyChanged += OnPagePropertyChanged;
                                ((INotifyCollectionChanged)newPage.ToolbarItems).CollectionChanged += OnToolbarItemsChanged;
                                SetBackButtonBehavior(Shell.GetBackButtonBehavior(newPage));
@@ -455,6 +458,7 @@ namespace Xamarin.Forms.Platform.iOS
 
                void AttachSearchController()
                {
+
                        if (SearchHandler.ShowsResults)
                        {
                                _resultsRenderer = _context.CreateShellSearchResultsRenderer();
@@ -507,6 +511,8 @@ namespace Xamarin.Forms.Platform.iOS
                        }
 
                        searchBar.ShowsBookmarkButton = SearchHandler.ClearPlaceholderEnabled;
+
+                       _searchHandlerAppearanceTracker = new SearchHandlerAppearanceTracker(searchBar, SearchHandler);
                }
 
                void BookmarkButtonClicked(object sender, EventArgs e)
@@ -516,6 +522,8 @@ namespace Xamarin.Forms.Platform.iOS
 
                void DettachSearchController()
                {
+                       _searchHandlerAppearanceTracker.Dispose();
+                       _searchHandlerAppearanceTracker = null;
                        if (Forms.IsiOS11OrNewer)
                        {
                                RemoveSearchController(NavigationItem);
@@ -545,7 +553,17 @@ namespace Xamarin.Forms.Platform.iOS
                async void SetSearchBarIcon(UISearchBar searchBar, ImageSource source, UISearchBarIcon icon)
                {
                        var result = await source.GetNativeImageAsync();
-                       searchBar.SetImageforSearchBarIcon(result, icon, UIControlState.Normal);
+                       var newResult = result.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
+                       searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Normal);
+                       searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Highlighted);
+                       searchBar.SetImageforSearchBarIcon(newResult, icon, UIControlState.Selected);
+               }
+
+               void PageAppearing(object sender, EventArgs e)
+               {
+                       //UIKIt will try to override our colors when the SearchController is inside the NavigationBar
+                       //Best way was to force them to be set again when page is Appearing / ViewDidLoad
+                       _searchHandlerAppearanceTracker?.UpdateSearchBarColors();
                }
 
                #endregion SearchHandler
@@ -563,6 +581,8 @@ namespace Xamarin.Forms.Platform.iOS
                        {
                                if (disposing)
                                {
+                                       _searchHandlerAppearanceTracker?.Dispose();
+                                       Page.Appearing -= PageAppearing;
                                        Page.PropertyChanged -= OnPagePropertyChanged;
                                        ((INotifyCollectionChanged)Page.ToolbarItems).CollectionChanged -= OnToolbarItemsChanged;
                                        ((IShellController)_context.Shell).RemoveFlyoutBehaviorObserver(this);
@@ -573,10 +593,11 @@ namespace Xamarin.Forms.Platform.iOS
                                SetBackButtonBehavior(null);
                                _rendererRef = null;
                                NavigationItem = null;
+                               _searchHandlerAppearanceTracker = null;
                                _disposed = true;
                        }
                }
 
                #endregion IDisposable Support
        }
-}
\ No newline at end of file
+}
index 4967756..6bc8399 100644 (file)
     <Compile Include="Extensions\FontExtensions.Shared.cs" />
     <Compile Include="Renderers\ButtonLayoutManager.cs" />
     <Compile Include="Renderers\IButtonLayoutRenderer.cs" />
+    <Compile Include="Renderers\SearchHandlerAppearanceTracker.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Resources\StringResources.ar.resx" />
index 79e46ba..9711a3b 100644 (file)
        <s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=64B48C7709A839499C5E49DBCE502A37/@KeyIndexDefined">True</s:Boolean>
        <s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File64B48C7709A839499C5E49DBCE502A37/@KeyIndexDefined">True</s:Boolean>
        <s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File64B48C7709A839499C5E49DBCE502A37/RelativePriority/@EntryValue">1</s:Double>
+       <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
+       <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
        <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>