diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index f883a241e..d6a695e4c 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -68,6 +68,7 @@ + diff --git a/src/Android/Renderers/BoxedView/Cells/EntryCellRenderer.cs b/src/Android/Renderers/BoxedView/Cells/EntryCellRenderer.cs new file mode 100644 index 000000000..14700f67c --- /dev/null +++ b/src/Android/Renderers/BoxedView/Cells/EntryCellRenderer.cs @@ -0,0 +1,328 @@ +using Android.Content; +using Android.Content.Res; +using Android.OS; +using Android.Runtime; +using Android.Text; +using Android.Text.Method; +using Android.Views; +using Android.Views.InputMethods; +using Android.Widget; +using Java.Lang; +using System; +using System.ComponentModel; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(Bit.App.Controls.BoxedView.EntryCell), + typeof(Bit.Droid.Renderers.BoxedView.EntryCellRenderer))] +namespace Bit.Droid.Renderers.BoxedView +{ + [Preserve(AllMembers = true)] + public class EntryCellRenderer : BaseCellRenderer + { } + + [Preserve(AllMembers = true)] + public class EntryCellView : BaseCellView, ITextWatcher, TextView.IOnFocusChangeListener, + TextView.IOnEditorActionListener + { + private CustomEditText _editText; + + public EntryCellView(Context context, Cell cell) + : base(context, cell) + { + _editText = new CustomEditText(context); + + _editText.Focusable = true; + _editText.ImeOptions = ImeAction.Done; + _editText.SetOnEditorActionListener(this); + + _editText.OnFocusChangeListener = this; + _editText.SetSingleLine(true); + _editText.Ellipsize = TextUtils.TruncateAt.End; + + _editText.InputType |= InputTypes.TextFlagNoSuggestions; // Disabled spell check + _editText.Background.Alpha = 0; // Hide underline + + _editText.ClearFocusAction = DoneEdit; + Click += EntryCellView_Click; + + using(var lParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, + ViewGroup.LayoutParams.WrapContent)) + { + CellContent.AddView(_editText, lParams); + } + } + + App.Controls.BoxedView.EntryCell _EntryCell => Cell as App.Controls.BoxedView.EntryCell; + + public override void UpdateCell() + { + UpdateValueText(); + UpdateValueTextColor(); + UpdateValueTextFontSize(); + UpdateKeyboard(); + UpdatePlaceholder(); + UpdateAccentColor(); + UpdateTextAlignment(); + UpdateIsPassword(); + base.UpdateCell(); + } + + public override void CellPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.CellPropertyChanged(sender, e); + if(e.PropertyName == App.Controls.BoxedView.EntryCell.ValueTextProperty.PropertyName) + { + UpdateValueText(); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.ValueTextFontSizeProperty.PropertyName) + { + UpdateWithForceLayout(UpdateValueTextFontSize); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.ValueTextColorProperty.PropertyName) + { + UpdateWithForceLayout(UpdateValueTextColor); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.KeyboardProperty.PropertyName) + { + UpdateKeyboard(); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.PlaceholderProperty.PropertyName) + { + UpdatePlaceholder(); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.AccentColorProperty.PropertyName) + { + UpdateAccentColor(); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.TextAlignmentProperty.PropertyName) + { + UpdateTextAlignment(); + } + else if(e.PropertyName == App.Controls.BoxedView.EntryCell.IsPasswordProperty.PropertyName) + { + UpdateIsPassword(); + } + } + + public override void ParentPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.ParentPropertyChanged(sender, e); + if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellValueTextColorProperty.PropertyName) + { + UpdateValueTextColor(); + } + else if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellValueTextFontSizeProperty.PropertyName) + { + UpdateWithForceLayout(UpdateValueTextFontSize); + } + else if(e.PropertyName == App.Controls.BoxedView.BoxedView.CellAccentColorProperty.PropertyName) + { + UpdateAccentColor(); + } + } + + protected override void Dispose(bool disposing) + { + if(disposing) + { + Click -= EntryCellView_Click; + _editText.RemoveFromParent(); + _editText.SetOnEditorActionListener(null); + _editText.RemoveTextChangedListener(this); + _editText.OnFocusChangeListener = null; + _editText.ClearFocusAction = null; + _editText.Dispose(); + _editText = null; + } + base.Dispose(disposing); + } + + protected override void SetEnabledAppearance(bool isEnabled) + { + if(isEnabled) + { + _editText.Enabled = true; + _editText.Alpha = 1.0f; + } + else + { + _editText.Enabled = false; + _editText.Alpha = 0.3f; + } + base.SetEnabledAppearance(isEnabled); + } + + private void EntryCellView_Click(object sender, EventArgs e) + { + _editText.RequestFocus(); + ShowKeyboard(_editText); + } + + private void UpdateValueText() + { + _editText.RemoveTextChangedListener(this); + if(_editText.Text != _EntryCell.ValueText) + { + _editText.Text = _EntryCell.ValueText; + } + _editText.AddTextChangedListener(this); + } + + private void UpdateValueTextFontSize() + { + if(_EntryCell.ValueTextFontSize > 0) + { + _editText.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_EntryCell.ValueTextFontSize); + } + else if(CellParent != null) + { + _editText.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)CellParent.CellValueTextFontSize); + } + } + + private void UpdateValueTextColor() + { + if(_EntryCell.ValueTextColor != Color.Default) + { + _editText.SetTextColor(_EntryCell.ValueTextColor.ToAndroid()); + } + else if(CellParent != null && CellParent.CellValueTextColor != Color.Default) + { + _editText.SetTextColor(CellParent.CellValueTextColor.ToAndroid()); + } + } + + private void UpdateKeyboard() + { + _editText.InputType = _EntryCell.Keyboard.ToInputType() | InputTypes.TextFlagNoSuggestions; + } + + private void UpdateIsPassword() + { + _editText.TransformationMethod = _EntryCell.IsPassword ? new PasswordTransformationMethod() : null; + } + + private void UpdatePlaceholder() + { + _editText.Hint = _EntryCell.Placeholder; + _editText.SetHintTextColor(Android.Graphics.Color.Rgb(210, 210, 210)); + } + + private void UpdateTextAlignment() + { + _editText.Gravity = _EntryCell.TextAlignment.ToAndroidHorizontal(); + } + + private void UpdateAccentColor() + { + if(_EntryCell.AccentColor != Color.Default) + { + ChangeTextViewBack(_EntryCell.AccentColor.ToAndroid()); + } + else if(CellParent != null && CellParent.CellAccentColor != Color.Default) + { + ChangeTextViewBack(CellParent.CellAccentColor.ToAndroid()); + } + } + + private void ChangeTextViewBack(Android.Graphics.Color accent) + { + var colorlist = new ColorStateList( + new int[][] + { + new int[]{Android.Resource.Attribute.StateFocused}, + new int[]{-Android.Resource.Attribute.StateFocused}, + }, + new int[] { + Android.Graphics.Color.Argb(255,accent.R,accent.G,accent.B), + Android.Graphics.Color.Argb(255, 200, 200, 200) + }); + _editText.Background.SetTintList(colorlist); + } + + private void DoneEdit() + { + var entryCell = (IEntryCellController)Cell; + entryCell.SendCompleted(); + _editText.ClearFocus(); + ClearFocus(); + } + + private void HideKeyboard(Android.Views.View inputView) + { + using(var inputMethodManager = (InputMethodManager)_Context.GetSystemService(Context.InputMethodService)) + { + IBinder windowToken = inputView.WindowToken; + if(windowToken != null) + { + inputMethodManager.HideSoftInputFromWindow(windowToken, HideSoftInputFlags.None); + } + } + } + + private void ShowKeyboard(Android.Views.View inputView) + { + using(var inputMethodManager = (InputMethodManager)_Context.GetSystemService(Context.InputMethodService)) + { + inputMethodManager.ShowSoftInput(inputView, ShowFlags.Forced); + inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly); + } + } + + bool TextView.IOnEditorActionListener.OnEditorAction(TextView v, ImeAction actionId, KeyEvent e) + { + if(actionId == ImeAction.Done || (actionId == ImeAction.ImeNull && e.KeyCode == Keycode.Enter)) + { + HideKeyboard(v); + DoneEdit(); + } + return true; + } + + void ITextWatcher.AfterTextChanged(IEditable s) + { } + + void ITextWatcher.BeforeTextChanged(ICharSequence s, int start, int count, int after) + { } + + void ITextWatcher.OnTextChanged(ICharSequence s, int start, int before, int count) + { + _EntryCell.ValueText = s?.ToString(); + } + + void IOnFocusChangeListener.OnFocusChange(Android.Views.View v, bool hasFocus) + { + if(hasFocus) + { + // Show underline when on focus. + _editText.Background.Alpha = 100; + } + else + { + // Hide underline + _editText.Background.Alpha = 0; + } + } + } + + [Preserve(AllMembers = true)] + internal class CustomEditText : EditText + { + public CustomEditText(Context context) + : base(context) + { } + + public Action ClearFocusAction { get; set; } + + public override bool OnKeyPreIme(Keycode keyCode, KeyEvent e) + { + if(keyCode == Keycode.Back && e.Action == KeyEventActions.Up) + { + ClearFocus(); + ClearFocusAction?.Invoke(); + } + return base.OnKeyPreIme(keyCode, e); + } + } +} diff --git a/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs b/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs index 1337d5f0a..84224ed65 100644 --- a/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs +++ b/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs @@ -25,13 +25,12 @@ namespace Bit.Droid.Renderers.BoxedView ValueLabel = new TextView(context); ValueLabel.SetSingleLine(true); ValueLabel.Ellipsize = TextUtils.TruncateAt.End; - ValueLabel.Gravity = GravityFlags.Right; + ValueLabel.Gravity = GravityFlags.Left; - var textParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, - ViewGroup.LayoutParams.WrapContent); - using(textParams) + using(var lParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, + ViewGroup.LayoutParams.WrapContent)) { - CellContent.AddView(ValueLabel, textParams); + CellContent.AddView(ValueLabel, lParams); } } diff --git a/src/Android/Renderers/RendererUtils.cs b/src/Android/Renderers/RendererUtils.cs index 8a66a58e9..249032922 100644 --- a/src/Android/Renderers/RendererUtils.cs +++ b/src/Android/Renderers/RendererUtils.cs @@ -38,7 +38,7 @@ namespace Bit.Droid.Renderers } } - public static GravityFlags ToAndroidVertical(this Xamarin.Forms.TextAlignment formsAlignment) + public static GravityFlags ToAndroidHorizontal(this Xamarin.Forms.TextAlignment formsAlignment) { switch(formsAlignment) { @@ -49,7 +49,7 @@ namespace Bit.Droid.Renderers case Xamarin.Forms.TextAlignment.End: return GravityFlags.Right | GravityFlags.CenterVertical; default: - return GravityFlags.Right | GravityFlags.CenterVertical; + return GravityFlags.Left | GravityFlags.CenterVertical; } } diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 69550ff96..87c491658 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -15,7 +15,7 @@ namespace Bit.App { InitializeComponent(); - ThemeManager.SetTheme("dark"); + ThemeManager.SetTheme("light"); MainPage = new TabsPage(); } diff --git a/src/App/Controls/BoxedView/Cells/EntryCell.cs b/src/App/Controls/BoxedView/Cells/EntryCell.cs new file mode 100644 index 000000000..5bcf14d86 --- /dev/null +++ b/src/App/Controls/BoxedView/Cells/EntryCell.cs @@ -0,0 +1,102 @@ +using System; +using Xamarin.Forms; + +namespace Bit.App.Controls.BoxedView +{ + public class EntryCell : BaseCell, IEntryCellController + { + public static BindableProperty ValueTextProperty = BindableProperty.Create( + nameof(ValueText), typeof(string), typeof(EntryCell), default(string), + defaultBindingMode: BindingMode.TwoWay); + // propertyChanging: ValueTextPropertyChanging); + + public static BindableProperty ValueTextColorProperty = BindableProperty.Create( + nameof(ValueTextColor), typeof(Color), typeof(EntryCell), default(Color), + defaultBindingMode: BindingMode.OneWay); + + public static BindableProperty ValueTextFontSizeProperty = BindableProperty.Create( + nameof(ValueTextFontSize), typeof(double), typeof(EntryCell), -1.0, + defaultBindingMode: BindingMode.OneWay); + + public static BindableProperty KeyboardProperty = BindableProperty.Create( + nameof(Keyboard), typeof(Keyboard), typeof(EntryCell), Keyboard.Default, + defaultBindingMode: BindingMode.OneWay); + + public static BindableProperty PlaceholderProperty = BindableProperty.Create( + nameof(Placeholder), typeof(string), typeof(EntryCell), default(string), + defaultBindingMode: BindingMode.OneWay); + + public static BindableProperty TextAlignmentProperty = BindableProperty.Create( + nameof(TextAlignment), typeof(TextAlignment), typeof(EntryCell), TextAlignment.Start, + defaultBindingMode: BindingMode.OneWay); + + public static BindableProperty AccentColorProperty = BindableProperty.Create( + nameof(AccentColor), typeof(Color), typeof(EntryCell), default(Color), + defaultBindingMode: BindingMode.OneWay); + + public static BindableProperty IsPasswordProperty = BindableProperty.Create( + nameof(IsPassword), typeof(bool), typeof(EntryCell), default(bool), + defaultBindingMode: BindingMode.OneWay); + + public string ValueText + { + get => (string)GetValue(ValueTextProperty); + set => SetValue(ValueTextProperty, value); + } + + public Color ValueTextColor + { + get => (Color)GetValue(ValueTextColorProperty); + set => SetValue(ValueTextColorProperty, value); + } + + [TypeConverter(typeof(FontSizeConverter))] + public double ValueTextFontSize + { + get => (double)GetValue(ValueTextFontSizeProperty); + set => SetValue(ValueTextFontSizeProperty, value); + } + + public Keyboard Keyboard + { + get => (Keyboard)GetValue(KeyboardProperty); + set => SetValue(KeyboardProperty, value); + } + + public string Placeholder + { + get => (string)GetValue(PlaceholderProperty); + set => SetValue(PlaceholderProperty, value); + } + + public TextAlignment TextAlignment + { + get => (TextAlignment)GetValue(TextAlignmentProperty); + set => SetValue(TextAlignmentProperty, value); + } + + public Color AccentColor + { + get => (Color)GetValue(AccentColorProperty); + set => SetValue(AccentColorProperty, value); + } + + public bool IsPassword + { + get => (bool)GetValue(IsPasswordProperty); + set => SetValue(IsPasswordProperty, value); + } + + public event EventHandler Completed; + + public void SendCompleted() + { + Completed?.Invoke(this, EventArgs.Empty); + } + + private static void ValueTextPropertyChanging(BindableObject bindable, object oldValue, object newValue) + { + // Check changes + } + } +} diff --git a/src/App/Pages/SettingsPage.xaml b/src/App/Pages/SettingsPage.xaml index da33251a0..310d40ad4 100644 --- a/src/App/Pages/SettingsPage.xaml +++ b/src/App/Pages/SettingsPage.xaml @@ -11,9 +11,22 @@ - - - + + + + + + + +