Skip to content

Instantly share code, notes, and snippets.

@felipeslongo
Last active April 29, 2019 21:41
Show Gist options
  • Save felipeslongo/b240c61f3a73b8d272fcc4fe200cd1ed to your computer and use it in GitHub Desktop.
Save felipeslongo/b240c61f3a73b8d272fcc4fe200cd1ed to your computer and use it in GitHub Desktop.
AppAutoCompleteTextView customizado pra um comportamento especifico.
using System;
using System.Linq;
using System.Threading.Tasks;
using Android.Widget;
namespace App.Droid.Extensions
{
public static class AutoCompleteTextView_MostrarSugestoes
{
public static void MostrarSugestoesAoInteragir(this AutoCompleteTextView @this)
{
@this.MostrarSugestoesOnFocus();
@this.MostrarSugestoesOnClick();
@this.MostrarSugestoesQuantoTextoVazio();
}
public static void MostrarSugestoesOnFocus(this AutoCompleteTextView @this)
{
@this.FocusChange += (sender, args) =>
{
if (args.HasFocus)
@this.ShowDropDown();
};
}
public static void MostrarSugestoesOnClick(this AutoCompleteTextView @this)
=> @this.Click += (sender, args) => @this.ShowDropDown();
public static void MostrarSugestoesQuantoTextoVazio(this AutoCompleteTextView @this)
{
@this.TextChanged += async (sender, args) =>
{
if (!string.IsNullOrEmpty(@this.Text))
return;
await Task.Delay(TimeSpan.FromMilliseconds(300));
@this.ShowDropDown();
};
}
}
}
using System;
using System.Diagnostics.Tracing;
using System.Reactive;
using System.Reactive.Subjects;
using Android.Widget;
using Core.ValueTypes;
namespace App.Droid.Extensions
{
public static class AutoCompleteTextView_NotificarQuandoTextoDigitadoForIgualASugestaoUnica
{
public static IObservable<Java.Lang.Object> NotificarQuandoTextoDigitadoForIgualASugestaoUnica(
this AutoCompleteTextView @this,
Func<string, string, bool> compararTestoComASugestaoUnica)
{
var observable = new Subject<Java.Lang.Object>();
@this.FocusChange += (sender, e) =>
{
if (e.HasFocus)
return;
var view = (AutoCompleteTextView) sender;
if (view.Adapter.Count != 1)
return;
var itemDaSugestaoUnica = view.Adapter.GetItem(0);
if (!compararTestoComASugestaoUnica(itemDaSugestaoUnica.ToString(), view.Text))
return;
observable.OnNext(itemDaSugestaoUnica);
};
return observable;
}
}
}
using System;
using Android.Text;
using Android.Views;
using Android.Widget;
using App.Droid.Utils;
namespace App.Droid.Extensions
{
public static class EditText_ConfigurarBotaoDeLimpar
{
public static void ConfigurarBotaoDeLimpar(this EditText @this, Action clearAction)
{
@this.AfterTextChanged += DefinirIconeSeBaseandoNaPresencaDeTexto;
@this.FocusChange += DefinirIconeSeBaseandoNaPresencaDeTexto;
ConfigurarAcaoDeLimparAoClicarNoIcone(@this, clearAction);
}
private static void DefinirIconeSeBaseandoNaPresencaDeTexto(object sender, View.FocusChangeEventArgs e) =>
DefinirIconeSeBaseandoNaPresencaDeTexto((EditText)sender, !string.IsNullOrEmpty((sender as EditText).Text));
private static void DefinirIconeSeBaseandoNaPresencaDeTexto(object sender, AfterTextChangedEventArgs args) =>
DefinirIconeSeBaseandoNaPresencaDeTexto((EditText)sender,args.Editable.Length() > 0);
private static void DefinirIconeSeBaseandoNaPresencaDeTexto(EditText @this, bool textoPreenchido)
{
var iconeDeLimpar = textoPreenchido && @this.HasFocus ? Resource.Drawable.ic_content_clear : 0;
@this.SetCompoundDrawablesWithIntrinsicBounds(0, 0, iconeDeLimpar, 0);
}
private static void ConfigurarAcaoDeLimparAoClicarNoIcone(EditText view, Action clearAction) =>
TextViewDrawableTouchEventHandler.ConfigurarClickNoDrawableDaDireita(view, clearAction);
}
}
using System;
namespace Core.ViewModels
{
public class SelectItemViewModel
{
internal const int IdDeOpcaoVazia = 0;
public SelectItemViewModel(int id, string texto, int ordem, bool selecionado)
{
if (string.IsNullOrWhiteSpace(texto))
throw new ArgumentNullException(nameof(texto), NãoFoiInformadoOTextoDoItem);
Id = id;
Texto = texto;
Ordem = ordem;
SelecionadoDefault = selecionado;
Selecionado = SelecionadoDefault;
}
protected SelectItemViewModel(SelectItemViewModel viewModelOrigem) : this(
viewModelOrigem.Id,
viewModelOrigem.Texto,
viewModelOrigem.Ordem,
viewModelOrigem.Selecionado)
{}
protected virtual string NãoFoiInformadoOTextoDoItem => "Não foi informado o texto de apresentação do item";
public int Id { get; }
public string Texto { get; }
public int Ordem { get; }
public bool SelecionadoDefault { get; }
public bool OpcaoVazia => Id == IdDeOpcaoVazia;
public bool Selecionado { get; internal set; }
public static SelectItemViewModel MakeOpcaoVazia()
=> new SelectItemViewModel(IdDeOpcaoVazia, "---------", int.MinValue, false);
public override string ToString() => Texto;
}
}
using NUnit.Framework;
using Core.ViewModels;
namespace Shared.Tests.ViewModels
{
[TestFixture]
public class SelectItemViewModelTests
{
[Test]
public void DeveConstruirUmaOpcaoQueRepresentaVazio()
{
var retorno = SelectItemViewModel.MakeOpcaoVazia();
Assert.IsTrue(retorno.OpcaoVazia);
}
}
}
using System;
using System.Linq;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Widget;
using App.Droid.Resources;
using Core.ValueTypes;
using Core.ViewModels;
using App.Droid.Extensions;
namespace App.Droid.Views
{
public class SelectView : SelectView<SelectViewModel>
{
public SelectView(SelectViewModel listaViewModel, AppCompatAutoCompleteTextView view, Action<int, string> actionSelecionarRow = null)
: base(listaViewModel, view, actionSelecionarRow)
{
}
}
public class SelectView<TViewModel> where TViewModel : SelectViewModel
{
public SelectView(TViewModel listaViewModel, AppCompatAutoCompleteTextView view, Action<int, string> actionSelecionarRow = null)
{
View = view;
ViewModel = listaViewModel;
ActionSelecionarRow = actionSelecionarRow;
InicializarAutoComplete();
}
protected TViewModel ViewModel { get; }
protected Action<int, string> ActionSelecionarRow { get; }
protected AppCompatAutoCompleteTextView View { get; }
private void InicializarAutoComplete()
{
var adapter = new ArrayAdapter<SelectItemViewModel>(
View.Context,
Resource.Layout.select_dialog_item_material,
ViewModel.Itens.ToList());
View.Threshold = 1;
View.MostrarSugestoesAoInteragir();
View.Adapter = adapter;
View.ItemClick += ViewOnItemClickSelecionarItemNaViewModel;
View.NotificarQuandoTextoDigitadoForIgualASugestaoUnica(StringInsensivelESemAcentos.Compare)
.Subscribe(SelecionarItemNaViewModelSeTiverUmaUnicaSugestaoQueBateComOTextoDigitado);
View.FocusChange += ViewOnFocusChangeAtribuirAViewTextoDaViewModel;
View.ConfigurarBotaoDeLimpar(() => AtribuirTexto(string.Empty));
}
private void ViewOnItemClickSelecionarItemNaViewModel(object sender, AdapterView.ItemClickEventArgs e)
{
var itemSelecionado = e.GetItem().Cast<SelectItemViewModel>();
ViewModel.SelecionarItem(itemSelecionado);
ActionSelecionarRow?.Invoke(itemSelecionado.Id, itemSelecionado.Texto);
}
private void ViewOnFocusChangeAtribuirAViewTextoDaViewModel(object sender, View.FocusChangeEventArgs e)
{
if (!e.HasFocus)
AtribuirTexto();
}
private void SelecionarItemNaViewModelSeTiverUmaUnicaSugestaoQueBateComOTextoDigitado(Java.Lang.Object itemSugeridoUnico)
=> ViewModel.SelecionarItem(itemSugeridoUnico.Cast<SelectItemViewModel>());
protected void AtribuirTexto() => AtribuirTexto(ViewModel.Selecionado.Texto);
private void AtribuirTexto(string valor) => View.SetText(valor, false);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Core.Entities;
using Core.Enums;
namespace Core.ViewModels
{
public class SelectViewModel
{
private readonly List<SelectItemViewModel> _itens = new List<SelectItemViewModel>();
public SelectViewModel(int id, string nome, bool makeOpcaoVazia = true)
{
if (string.IsNullOrWhiteSpace(nome))
throw new ArgumentNullException(nameof(nome), NãoFoiInformadoONome);
Id = id;
Nome = nome;
if (makeOpcaoVazia)
_itens.Add(MakeOpcaoVazia());
}
protected virtual string NãoFoiInformadoONome => "Não foi informado o nome";
public int Id { get; }
public string Nome { get; }
public IReadOnlyList<SelectItemViewModel> Itens => _itens;
public SelectItemViewModel Default =>
Itens.FirstOrDefault(i => i.SelecionadoDefault) ?? Itens.First();
public SelectItemViewModel Selecionado => _itens.FirstOrDefault(i => i.Selecionado) ?? Default;
public virtual SelectItemViewModel MakeOpcaoVazia() => SelectItemViewModel.MakeOpcaoVazia();
public void AdicionarItem(SelectItemViewModel item) => AdicionarItens(item);
public void AdicionarItens(IEnumerable<SelectItemViewModel> itens) => AdicionarItens(itens.ToArray());
public void AdicionarItens(params SelectItemViewModel[] itens)
{
_itens.AddRange(itens);
InicializarItens();
}
protected void InicializarItens()
{
_itens.Sort(CompararItems);
RemoverSelecaoDosNaoSelecionados();
}
private void RemoverSelecaoDosNaoSelecionados()
{
foreach (var itemNaoSelecionado in ObterItensComIdDiferenteDoSelecionado())
itemNaoSelecionado.Selecionado = false;
}
private IEnumerable<SelectItemViewModel> ObterItensComIdDiferenteDoSelecionado()
{
var selecionado = Selecionado;
return _itens.Where(i => i.Id != selecionado.Id);
}
public void SelecionarItem(SelectItemViewModel item) => SelecionarItem(item.Id);
public void SelecionarItem(int id)
{
Selecionado.Selecionado = false;
_itens.First(i => i.Id == id).Selecionado = true;
}
private static int CompararItems(SelectItemViewModel x, SelectItemViewModel y)
{
if (x == y) return (int) Comparacao.Igual;
if (x.Ordem == y.Ordem) return x.Id.CompareTo(y.Id);
return x.Ordem.CompareTo(y.Ordem);
}
}
}
using System;
using System.Linq;
using NUnit.Framework;
using Core.ViewModels;
namespace Core.Tests.ViewModels
{
[TestFixture]
public class SelectViewModelTests
{
public const int IdPadrao = 1;
public const int IdAlternativo = 2;
[TestFixture]
public class SelecionarItemTests : SelectViewModelTests
{
[Test]
public void RetornarOpcaoSelecionadaDepoisDeInvocarSelecionarItem()
{
var viewModel = new SelectViewModel(IdPadrao, "selectView");
var itemSelecionado = new SelectItemViewModel(2, "item2", 0, false);
viewModel.AdicionarItem(new SelectItemViewModel(1, "item1", 0, false));
viewModel.AdicionarItem(itemSelecionado);
viewModel.AdicionarItem(new SelectItemViewModel(3, "item3", 0, false));
viewModel.SelecionarItem(itemSelecionado.Id);
Assert.AreEqual(itemSelecionado.Id, viewModel.Selecionado.Id);
Assert.IsTrue(itemSelecionado.Selecionado);
}
[Test]
public void RemoverSelecaoDeUmaOpcaoAoSelecionarOutraOpcao()
{
var viewModel = new SelectViewModel(IdPadrao, "selectView");
var itemSelecionadoAnterior = new SelectItemViewModel(1, "item1", 0, true);
var itemSelecionadoNovo = new SelectItemViewModel(2, "item1", 0, false);
viewModel.AdicionarItem(itemSelecionadoAnterior);
viewModel.AdicionarItem(itemSelecionadoNovo);
viewModel.SelecionarItem(itemSelecionadoNovo.Id);
Assert.AreEqual(itemSelecionadoNovo.Id, viewModel.Selecionado.Id);
Assert.IsFalse(itemSelecionadoAnterior.Selecionado);
}
[Test]
public void DispararExcecaoAoSelecionarItemNaoExistente()
{
var viewModel = new SelectViewModel(IdPadrao, "selectView");
Assert.Throws<InvalidOperationException>(() => viewModel.SelecionarItem(1));
}
}
[TestFixture]
public class Ordenacao : SelectViewModelTests
{
[Test]
public void ItensDevemSerOrdenadosPelaOrdem()
{
var viewModel = new SelectViewModel(IdPadrao, "selectView");
var item1 = new SelectItemViewModel(1, "item", 3, false);
var item2 = new SelectItemViewModel(2, "item", 2, false);
var item3 = new SelectItemViewModel(3, "item", 1, false);
viewModel.AdicionarItens(item1, item2, item3);
var idsEsperadosOrdenados = new []{3, 2, 1};
var idsRetornadosOrdenados = viewModel.Itens.SkipWhile(i => i.OpcaoVazia).Select(i => i.Id);
CollectionAssert.AreEqual(idsEsperadosOrdenados, idsRetornadosOrdenados);
}
[Test]
public void ItensDevemSerOrdenadosPeloIdSeOrdemEmpatar()
{
var listaViewModel = new SelectViewModel(IdPadrao, "selectView");
var item3 = new SelectItemViewModel(3, "item", 1, false);
var item2 = new SelectItemViewModel(2, "item", 1, false);
var item1 = new SelectItemViewModel(1, "item", 1, false);
listaViewModel.AdicionarItens(item3, item2, item1);
var idsEsperadosOrdenados = new []{1,2,3};
var idsRetornadosOrdenados = listaViewModel.Itens.SkipWhile(i => i.OpcaoVazia).Select(i => i.Id);
CollectionAssert.AreEqual(idsEsperadosOrdenados, idsRetornadosOrdenados);
}
}
[TestFixture]
public class SelecionadoEhUnico : SelectViewModelTests
{
[Test]
public void SomenteUmaOpcaoDeveTerEstadoSelecionadoQuandoMultiplosSelecionados()
{
var viewModel = new SelectViewModel(IdPadrao, "selectView");
var unicoQueDevePermanecerSelecionado = new SelectItemViewModel(1, "item2", 0, true);
viewModel.AdicionarItem(unicoQueDevePermanecerSelecionado);
viewModel.AdicionarItem(new SelectItemViewModel(2, "item1", 0, true));
viewModel.AdicionarItem(new SelectItemViewModel(3, "item1", 0, true));
Assert.IsTrue(unicoQueDevePermanecerSelecionado.Selecionado);
var itensQueNaoDevemEstarSelecionados =
viewModel.Itens.Where(i => i != unicoQueDevePermanecerSelecionado);
Assert.IsTrue(itensQueNaoDevemEstarSelecionados.All(i => !i.Selecionado));
}
}
}
}
using System;
using Android.Graphics.Drawables;
using Android.Views;
using Android.Widget;
namespace App.Droid.Utils
{
public static class TextViewDrawableTouchEventHandler
{
const int DrawableDaEsquerda = 0;
const int DrawableDeCima = 1;
const int DrawableDaDireita = 2;
const int DrawableDeBaixo = 3;
public static void ConfigurarClickNoDrawableDaDireita(EditText view, Action action) =>
view.Touch += (sender, args) => DispararAcaoAoClicarNoDrawableDaDireita(sender, args, action);
private static void DispararAcaoAoClicarNoDrawableDaDireita(object sender, View.TouchEventArgs e, Action action)
{
var view = (EditText) sender;
e.Handled = false;
if (!EventoDeClique(e))
return;
var drawable = view.GetCompoundDrawables()[DrawableDaDireita];
if (drawable == null)
return;
if (!ZonaClicavelDoDrawableDaDireita(e, view, drawable))
return;
action();
e.Handled = true;
}
private static bool ZonaClicavelDoDrawableDaDireita(View.TouchEventArgs e, EditText view, Drawable drawable)
{
var pontoNoEixoXClicadoPeloUsuario = e.Event.RawX;
var pontoNoEixoXQueSeparaAViewDoSeuDrawableDaDireita = view.Right - drawable.Bounds.Width();
return pontoNoEixoXClicadoPeloUsuario >= pontoNoEixoXQueSeparaAViewDoSeuDrawableDaDireita;
}
private static bool EventoDeClique(View.TouchEventArgs e) =>
e.Event.Action == MotionEventActions.Up;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment