Repository: concretesolutions/canarinho
Branch: master
Commit: 29858bb998d8
Files: 77
Total size: 196.4 KB
Directory structure:
gitextract_bmhm5g9q/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── android_master.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE.txt
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── build.gradle
├── canarinho/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── br/
│ └── com/
│ └── concrete/
│ └── canarinho/
│ ├── DigitoPara.java
│ ├── formatador/
│ │ ├── Formatador.java
│ │ ├── FormatadorBase.java
│ │ ├── FormatadorBoleto.java
│ │ ├── FormatadorCEP.java
│ │ ├── FormatadorCPFCNPJ.java
│ │ ├── FormatadorLinhaDigitavel.java
│ │ ├── FormatadorTelefone.java
│ │ └── FormatadorValor.java
│ ├── validator/
│ │ ├── Validador.java
│ │ ├── ValidadorBoleto.java
│ │ ├── ValidadorCEP.java
│ │ ├── ValidadorCNPJ.java
│ │ ├── ValidadorCPF.java
│ │ ├── ValidadorCPFCNPJ.java
│ │ └── ValidadorTelefone.java
│ └── watcher/
│ ├── BaseCanarinhoTextWatcher.java
│ ├── BoletoBancarioTextWatcher.java
│ ├── CEPTextWatcher.java
│ ├── CPFCNPJTextWatcher.java
│ ├── MascaraNumericaTextWatcher.java
│ ├── TelefoneTextWatcher.java
│ ├── ValorMonetarioWatcher.java
│ └── evento/
│ ├── EventoDeValidacao.java
│ └── EventoDeValidacaoDeBoleto.java
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── sample/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── br/
│ │ └── com/
│ │ └── concrete/
│ │ └── canarinho/
│ │ └── sample/
│ │ ├── BugOnApi28Test.java
│ │ └── DemoWatchersInstrumentationTest.java
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── br/
│ │ │ └── com/
│ │ │ └── concrete/
│ │ │ └── canarinho/
│ │ │ └── sample/
│ │ │ └── ui/
│ │ │ ├── activity/
│ │ │ │ └── MainActivity.java
│ │ │ ├── adapter/
│ │ │ │ └── WatchersPagerAdapter.java
│ │ │ ├── fragment/
│ │ │ │ ├── BaseWatcherFragment.java
│ │ │ │ ├── CanarinhoValorMonetarioWatcherFragment.java
│ │ │ │ └── WatcherFragment.java
│ │ │ └── model/
│ │ │ └── Watchers.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── fragment_canarinho_watcher.xml
│ │ │ ├── fragment_valor_monetario_watcher.xml
│ │ │ └── main_activity.xml
│ │ └── values/
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test/
│ └── java/
│ └── br/
│ └── com/
│ └── concrete/
│ └── canarinho/
│ └── test/
│ ├── TesteFormatadorBOLETO.java
│ ├── TesteFormatadorCEP.java
│ ├── TesteFormatadorCNPJ.java
│ ├── TesteFormatadorCPF.java
│ ├── TesteFormatadorCPFCNPJ.java
│ ├── TesteFormatadorLinhaDigitavel.java
│ ├── TesteFormatadorTelefone.java
│ ├── TesteFormatadorValor.java
│ ├── TesteValidadores.java
│ └── watcher/
│ ├── BoletoTextWatcherTest.java
│ └── ValorMonetarioWatcherTest.java
├── settings.gradle
└── tools/
├── linters/
│ ├── checkstyle/
│ │ ├── checkstyle.xml
│ │ └── suppressions.xml
│ ├── linters.gradle
│ └── pmd/
│ └── pmd-ruleset.xml
└── publish.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/android_master.yml
================================================
name: Android Pull Request Master CI
on:
pull_request:
branches:
- 'master'
jobs:
Instrumented_Test:
runs-on: macOS-latest
strategy:
matrix:
api-level: [27, 29]
steps:
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- name: Instrumented Tests
uses: reactivecircus/android-emulator-runner@v1
with:
api-level: ${{ matrix.api-level }}
script: ./gradlew connectedCheck
unitTests:
name: Unit Tests
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.9
uses: actions/setup-java@v1
with:
java-version: 1.9
- name: Unit tests
run: bash ./gradlew test
linters:
name: Linters
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.9
uses: actions/setup-java@v1
with:
java-version: 1.9
- name: Checkstyle
run: bash ./gradlew checkstyle
- name: PMD
run: bash ./gradlew pmd
================================================
FILE: .gitignore
================================================
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.idea/
*.iml
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## 2.0.3
- Migração do bintray para Github Package Registry
## 2.0.2
- Atualização androidx
- Migração CI para Github Actions
- Migração jitpack.io
## 2.0.1
- Corrige bug no formatador da API 28
- Corrige bug do formatador numérico com símbolo
## 2.0.0
- Alterado o pacote da bilbioteca. Junto a reformulação do nome da própria Concrete (remoção de Solutions)
- Corrige issue #19. Obrigado a @luisfernandezbr pelo fix
- Atualiza sistema de build
### Quebras de API
- Corrige grafia do validador de telefone (TELFONE -> TELEFONE).
## 1.2.0
- Adicionada configuração de `ValorMonetarioWatcher`
- É possível deixar o símbolo de Real
- É possível manter os zeros quando o campo é apagado em lote
- Adicionado Builder para criação de watchers de valor
## 1.1.1:
- Corrigido bug de ArrayIndexOutOfBounds no `BoletoBancarioTextWatcher` após apagar em lote
- Adicionado teste de regressão para o caso acima na JVM que será executado no Travis
## 1.1.0:
- Refatorados testes instrumentados
- Adicionado construtor para máscara genérica
## 1.0.0:
- Removida necessidade de ter um validador/evento de validação na criação de Watchers
## 0.1.0:
- Ajustes no código antes da versão 1.0
## 0.0.9:
- Correção final para o formatador/validador de boleto. Tanto normal quanto tributo.
## 0.0.8:
- Adição de formatdor/validador de CEP
- Correção de formatdor monetário ao rotacionar a tela
## 0.0.7:
- Correção de validador de boleto quando setando o código de boleto inteiro (caso de uso: recebendo de um leitor de imagens)
## 0.0.6:
- Watcher de CPF/CNPJ simultâneos
## 0.0.5:
- Release no JCenter
## 0.0.4:
- Watcher para valor monetário no padão Real (vírgula para casas de milhar e ponto para casas decimais)
- Re-estruturação do projeto para gerar os binários na pasta correta do Bintray
## 0.0.3:
- Série de ajustes para Travis e JCenter (ainda não publicado)
## 0.0.2:
- Formatadores de telefone
## 0.0.1:
- Release inicial
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at bruno.silva@concrete.com.br. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
================================================
FILE: PULL_REQUEST_TEMPLATE.md
================================================
# Título para o PullRequest
O que acha de deixar uma descrição simples sobre o seu PR?
## Tipo de mudança
- [ ] Bug fix
- [ ] Nova Feature
- [ ] Melhoria
- [ ] Outra?
## Está relacionada a uma issue?
Cole o link da issue aqui.
## Quer adicionar outra informação?
Basta escrever aqui.
================================================
FILE: README.md
================================================
# Android Canarinho

Esta biblioteca é um conjunto de utilitários para trabalhar com padrões brasileiros no Android.
Inspirado em: https://github.com/caelum/caelum-stella.
O foco aqui é o Android. Portanto, não é compatível com aplicações Java puras.
Entre os padrões implementados temos:
- Formatador e validador de CPF
- Formatador e validador de CNPJ
- Formatador e validador de boleto bancário (e linha digitável)
- Formatador e validador de CEP
- Formatador de telefone
- [Formatador de valores financeiros](#formatador-de-valor-financeiro-no-padrão-real) (vírgula para milhares e ponto para decimais com duas casas)
Estes são utilizados para implementar `TextWatcher`s que formatam e validam a digitação do usuário.
## Exemplo de uso:
### Validar um CPF
```java
if (Validador.CPF.ehValido(cpf))
Toast.makeText(context, "Válido!", Toast.LENGTH_SHORT).show();
else
Toast.makeText(context, "Inválido!", Toast.LENGTH_SHORT).show();
```
### Formatar um CPF
```java
String cpfFormatado = Formatador.CPF.formata(usuario.getCpf());
```
### Formatar um EditText para CPF sem validação
```java
cpfEditText.addTextChangedListener(new MascaraNumericaTextWatcher("###.###.###-##"));
```
### Formatar um EditText para CPF com validação
```java
cpfEditText.addTextChangedListener(new MascaraNumericaTextWatcher.Builder()
.paraMascara("###.###.###-##")
.comCallbackDeValidacao(new SampleEventoDeValidacao(context))
.comValidador(Validador.CPF)
.build());
```
## Formatador de valor financeiro no padrão Real
Para deixar um usuário digitar valores monetários no padrão Real, basta adicionar um `ValorMonetarioWatcher` e alguns atributos ao `EditText`
```java
// Padrão sem símbolo de Real
editText.addTextChangedListener(new ValorMonetarioWatcher());
editText.append("1234567890");
assertThat(editText.getText().toString(), is("12.345.678,90"));
// Customizado com símbolo e mantendo zeros ao apagar em lote
editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()
.comSimboloReal()
.comMantemZerosAoLimpar()
.build());
editText.append("1234567890");
assertThat(editText.getText().toString(), is("R$ 12.345.678,90"));
editText.getText().clear();
assertThat(editText.getText().toString(), is("R$ 0,00"));
```
Exemplo de declaração no layout:
```xml
```
## Callback de validação
Os `TextWatcher`s possuem a possibilidade de avisar o usuário conforme ele está digitando sobre algum erro de campo.
Para isso, usamos um `EventoDeValidacao` que possui os seguintes callbacks:
- `void invalido(String valorAtual, String mensagem)`: chamado quando o valor está inválido
- `void parcialmenteValido(String valorAtual)`: chamado quando o valor ainda não está completo e também não está inválido
- `void totalmenteValido(String valorAtual)`: chamado quando o valor está completo e válido
Um exemplo de implementação:
```java
public class SampleEventoDeValidacao implements EventoDeValidacao {
private final TextInputLayout textInputLayout;
public SampleEventoDeValidacao(TextInputLayout textInputLayout) {
this.textInputLayout = textInputLayout;
}
@Override
public void invalido(String valorAtual, String mensagem) {
textInputLayout.setError(mensagem);
}
@Override
public void parcialmenteValido(String valorAtual) {
textInputLayout.setErrorEnabled(false);
textInputLayout.setError(null);
}
@Override
public void totalmenteValido(String valorAtual) {
new AlertDialog.Builder(textInputLayout.getContext())
.setTitle("Campo válido!")
.setMessage(valorAtual)
.show();
}
}
```
Veja exemplos de implementação no sample.
## Changelog
Ver [CHANGELOG.md](CHANGELOG.md)
## Arquitetura
- `Formatador`: Formata, desformata e verifica se um valor está formatado e se pode ser formatado. Opera com valores completos.
- `Validador`: Valida de duas formas: absoluta (true ou false) e atualizando um objeto de validação (`ResultadoParcial`).
- Watchers: implementações de `TextWatcher`s para formatação e validação contínua (conforme a digitação do usuário).
Para exemplos, verifique os testes na pasta sample.
## Gradle
`allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}`
`implementation 'br.com.concrete:canarinho:{latest version}'`
## ATENÇÃO
Este projeto é desenvolvido de boa vontade e com o intuito de ajudar. No entanto, todo o desenvolvimento é feito SEM GARANTIAS.
## LICENÇA
Este projeto é disponibilizado sob a licença Apache vesão 2.0. Ver declaração no arquivo LICENSE.txt
================================================
FILE: build.gradle
================================================
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
================================================
FILE: canarinho/.gitignore
================================================
/build
================================================
FILE: canarinho/build.gradle
================================================
apply plugin: 'com.android.library'
apply from: "$rootDir/tools/linters/linters.gradle"
apply from: "$rootDir/tools/publish.gradle"
ext {
publishedGroupId = 'br.com.concrete'
libraryName = 'Android Canarinho'
artifact = 'canarinho'
libraryDescription = 'Utilitários Android para padrões Brasileiros'
libraryVersion = '2.0.3'
licenseName = 'The Apache Software License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
allLicenses = ["Apache-2.0"]
}
android {
compileSdkVersion 31
defaultConfig {
minSdkVersion 21
targetSdkVersion 31
}
}
================================================
FILE: canarinho/src/main/AndroidManifest.xml
================================================
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/DigitoPara.java
================================================
package br.com.concrete.canarinho;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Uma fluent interface para o cálculo de dígitos, que é usado em diversos boletos e
* documentos.
*
* Para exemplificar, o dígito do trecho 0000039104766 para os multiplicadores indo de
* 2 a 7 e usando módulo 11 é a seguinte:
*
*
* 0 0 0 0 0 3 9 1 0 4 7 6 6 (trecho numérico)
* 2 7 6 5 4 3 2 7 6 5 4 3 2 (multiplicadores, da direita para a esquerda e ciclando)
* ----------------------------------------- multiplicações algarismo a algarismo
* 0 0 0 0 0 9 18 7 0 20 28 18 12 -- soma = 112
*
*
* Tira-se o módulo dessa soma e, então, calcula-se o complementar do módulo e, se o número
* for 0, 10 ou 11, o dígito passa a ser 1.
*
*
* soma = 112
* soma % 11 = 2
* 11 - (soma % 11) = 9
*
*
* NOTE: Esta é uma versão otimizada para Android inspirada em
* https://github.com/caelum/caelum-stella/blob/master/stella-core/src/main/java/br/com/caelum/stella/DigitoPara.java
*
*/
public final class DigitoPara {
private final List numero = new LinkedList<>();
private final List multiplicadores;
private final boolean complementar;
private final int modulo;
private final boolean somarIndividual;
private final SparseArray substituicoes;
private DigitoPara(Builder builder) {
multiplicadores = builder.multiplicadores;
complementar = builder.complementar;
modulo = builder.modulo;
somarIndividual = builder.somarIndividual;
substituicoes = builder.substituicoes;
}
/**
* Faz a soma geral das multiplicações dos algarismos pelos multiplicadores, tira o
* módulo e devolve seu complementar.
*
* @param trecho Bloco para calcular o dígito
* @return String o dígito vindo do módulo com o número passado e configurações extra.
*/
public final String calcula(String trecho) {
numero.clear();
final char[] digitos = trecho.toCharArray();
for (char digito : digitos) {
numero.add(Character.getNumericValue(digito));
}
Collections.reverse(numero);
int soma = 0;
int multiplicadorDaVez = 0;
for (int i = 0; i < numero.size(); i++) {
final int multiplicador = multiplicadores.get(multiplicadorDaVez);
final int total = numero.get(i) * multiplicador;
soma += somarIndividual ? somaDigitos(total) : total;
multiplicadorDaVez = proximoMultiplicador(multiplicadorDaVez);
}
int resultado = soma % modulo;
if (complementar) {
resultado = modulo - resultado;
}
if (substituicoes.get(resultado) != null) {
return substituicoes.get(resultado);
}
return String.valueOf(resultado);
}
/*
* soma os dígitos do número (até 2)
*
* Ex: 18 => 9 (1+8), 12 => 3 (1+2)
*/
private int somaDigitos(int total) {
return (total / 10) + (total % 10);
}
/*
* Devolve o próximo multiplicador a ser usado, isto é, a próxima posição da lista de
* multiplicadores ou, se chegar ao fim da lista, a primeira posição, novamente.
*/
private int proximoMultiplicador(int multiplicadorDaVez) {
int multiplicador = multiplicadorDaVez + 1;
if (multiplicador == multiplicadores.size()) {
multiplicador = 0;
}
return multiplicador;
}
/**
* Builder com interface fluente para criação de instâncias configuradas de
* {@link DigitoPara}.
*/
public static final class Builder {
private List multiplicadores = new ArrayList<>();
private boolean complementar;
private int modulo;
private boolean somarIndividual;
private final SparseArray substituicoes = new SparseArray<>();
/**
* TODO Javadoc pendente.
*
* @param modulo Inteiro pelo qual o resto será tirado e também seu complementar.
* O valor padrão é 11.
* @return this
*/
public final Builder mod(int modulo) {
this.modulo = modulo;
return this;
}
/**
* Para multiplicadores (ou pesos) sequenciais e em ordem crescente, esse método permite
* criar a lista de multiplicadores que será usada ciclicamente, caso o número base seja
* maior do que a sequência de multiplicadores. Por padrão os multiplicadores são iniciados
* de 2 a 9. No momento em que você inserir outro valor este default será sobrescrito.
*
* @param inicio Primeiro número do intervalo sequencial de multiplicadores
* @param fim Último número do intervalo sequencial de multiplicadores
* @return this
*/
public final Builder comMultiplicadoresDeAte(int inicio, int fim) {
this.multiplicadores.clear();
for (int i = inicio; i <= fim; i++) {
multiplicadores.add(i);
}
return this;
}
/**
*
* Indica se, ao calcular o módulo, a soma dos resultados da multiplicação deve ser
* considerado digito a dígito.
*
* Ex: 2 X 9 = 18, irá somar 9 (1 + 8) invés de 18 ao total.
*
* @return this
*/
public final Builder somandoIndividualmente() {
this.somarIndividual = true;
return this;
}
/**
* É comum que os geradores de dígito precisem do complementar do módulo em vez
* do módulo em sí. Então, a chamada desse método habilita a flag que é usada
* no método mod para decidir se o resultado devolvido é o módulo puro ou seu
* complementar.
*
* @return this
*/
public final Builder complementarAoModulo() {
this.complementar = true;
return this;
}
/**
* Troca por uma String caso encontre qualquer dos inteiros passados como argumento.
*
* @param substituto String para substituir
* @param i varargs de inteiros a serem substituídos
* @return this
*/
public final Builder trocandoPorSeEncontrar(String substituto, Integer... i) {
substituicoes.clear();
for (Integer integer : i) {
substituicoes.put(integer, substituto);
}
return this;
}
/**
* Há documentos em que os multiplicadores não usam todos os números de um intervalo
* ou alteram sua ordem. Nesses casos, a lista de multiplicadores pode ser passada
* através de varargs.
*
* @param multiplicadoresEmOrdem Sequência de inteiros com os multiplicadores em ordem
* @return this
*/
public final Builder comMultiplicadores(Integer... multiplicadoresEmOrdem) {
this.multiplicadores.clear();
this.multiplicadores.addAll(Arrays.asList(multiplicadoresEmOrdem));
return this;
}
/**
* Método responsável por criar o DigitoPara.
* Este método inicializará os seguintes valores padrões:
*
* - multiplicadores: 2 a 9
* - módulo: 11
*
*
* @return A instância imutável de DigitoPara
*/
public final DigitoPara build() {
if (multiplicadores.size() == 0) {
comMultiplicadoresDeAte(2, 9);
}
if (modulo == 0) {
mod(11);
}
return new DigitoPara(this);
}
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/Formatador.java
================================================
package br.com.concrete.canarinho.formatador;
import java.util.regex.Pattern;
/**
* Interface de formatação. Formata valores completos. Útil caso receba o valor
* desformatado de uma API.
*/
public interface Formatador {
// Formatadores
/**
* Singleton de formatação de CEP.
*/
Formatador CEP = new FormatadorBase(
Padroes.CEP_FORMATADO,
"$1-$2",
Padroes.CEP_DESFORMATADO,
"$1$2"
);
/**
* Singleton de formatação de CPF.
*/
Formatador CPF = new FormatadorBase(
Padroes.CPF_FORMATADO,
"$1.$2.$3-$4",
Padroes.CPF_DESFORMATADO,
"$1$2$3$4"
);
/**
* Singleton de formatação de CNPJ.
*/
Formatador CNPJ = new FormatadorBase(
Padroes.CNPJ_FORMATADO,
"$1.$2.$3/$4-$5",
Padroes.CNPJ_DESFORMATADO,
"$1$2$3$4$5"
);
/**
* Singleton de formatação de CPF e CNPJ.
*/
Formatador CPF_CNPJ = FormatadorCPFCNPJ.getInstance();
/**
* Singleton de formatação de valores monetários.
*/
FormatadorValor VALOR = FormatadorValor.getInstance(false);
/**
* Singleton de formatação de valores monetários com símbolo do Real.
*/
FormatadorValor VALOR_COM_SIMBOLO = FormatadorValor.getInstance(true);
/**
* Singleton de formatação de boletos bancários.
*/
Formatador BOLETO = FormatadorBoleto.getInstance();
/**
* Singleton de formatação de telefones (DDD) número.
*/
Formatador TELEFONE = FormatadorTelefone.getInstance();
/**
* Singleton de formatação de linha digitável. Transforma números do código de barras em linha
* digitável.
*/
Formatador LINHA_DIGITAVEL = FormatadorLinhaDigitavel.getInstance();
/**
* Formata um valor COMPLETO. Deve falhar caso o valor não esteja completo.
*
* @param value valor a formatar
* @return REsultado da formatação
*/
String formata(String value);
/**
* Desformata um valor.
*
* @param value Valor a desformatar
* @return Resultado da desformatação
*/
String desformata(String value);
/**
* Verifica se um parâmetro está formatado.
*
* @param value Valor para verificar
* @return True se estiver formatado, falso caso contrário.
*/
boolean estaFormatado(String value);
/**
* Verifica se um parâmetro pode ser formatado.
*
* @param value Valor para verificar
* @return True se puder ser formatado, falso caso contrário.
*/
boolean podeSerFormatado(String value);
/**
* Classe para guardar os padrões de experssões regulares usados no framework.
*/
abstract class Padroes {
public static final Pattern PADRAO_SOMENTE_NUMEROS = Pattern.compile("[^0-9]");
public static final Pattern CEP_FORMATADO = Pattern.compile("(\\d{5})-(\\d{3})");
public static final Pattern CEP_DESFORMATADO = Pattern.compile("(\\d{5})(\\d{3})");
public static final Pattern CNPJ_FORMATADO = Pattern.compile(
"(\\d{2})[.](\\d{3})[.](\\d{3})/(\\d{4})-(\\d{2})"
);
public static final Pattern CNPJ_DESFORMATADO = Pattern.compile(
"(\\d{2})(\\d{3})(\\d{3})(\\d{4})(\\d{2})"
);
public static final Pattern CPF_FORMATADO = Pattern.compile(
"(\\d{3})[.](\\d{3})[.](\\d{3})-(\\d{2})"
);
public static final Pattern CPF_DESFORMATADO = Pattern.compile(
"(\\d{3})(\\d{3})(\\d{3})(\\d{2})"
);
private Padroes() {
}
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorBase.java
================================================
package br.com.concrete.canarinho.formatador;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Classe utilitária para a implementação de todos os formatadores que precisam apenas da aplicação
* de {@link Pattern}s.
*/
final class FormatadorBase implements Formatador {
private final Pattern formatted;
private final String formattedReplacement;
private final Pattern unformatted;
private final String unformattedReplacement;
/**
* Constrói um formatador que recebe a configuração de formatação e substituição.
*
* @param formatted Pattern de regex para formatar o conteúdo
* @param formattedReplacement String com as posições de substituição dos grupos encontrados por regex
* @param unformatted Pattern de regex para DESformatar o conteúdo
* @param unformattedReplacement String com as posições de substituição dos grupos encontrados por regex
*/
FormatadorBase(
Pattern formatted,
String formattedReplacement,
Pattern unformatted,
String unformattedReplacement) {
this.formatted = formatted;
this.formattedReplacement = formattedReplacement;
this.unformatted = unformatted;
this.unformattedReplacement = unformattedReplacement;
}
@Override
public final String formata(String value) throws IllegalArgumentException {
if (value == null) {
throw new IllegalArgumentException("Value may not be null.");
}
if (formatted.matcher(value).matches()) {
return value;
}
return matchAndReplace(unformatted.matcher(value), formattedReplacement);
}
@Override
public final String desformata(String value) throws IllegalArgumentException {
if (value == null) {
throw new IllegalArgumentException("Value may not be null.");
}
if (unformatted.matcher(value).matches()) {
return value;
}
final Matcher matcher = formatted.matcher(value);
return matchAndReplace(matcher, unformattedReplacement);
}
@Override
public final boolean estaFormatado(String value) {
if (value == null) {
throw new IllegalArgumentException("value must not be null");
}
return formatted.matcher(value).matches();
}
@Override
public final boolean podeSerFormatado(String value) {
return value != null && unformatted.matcher(value).matches();
}
private String matchAndReplace(Matcher matcher, String replacement) {
if (matcher.matches()) {
return matcher.replaceAll(replacement);
}
throw new IllegalArgumentException("Valor não está formatado propriamente.");
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorBoleto.java
================================================
package br.com.concrete.canarinho.formatador;
import java.util.regex.Pattern;
/**
* Formatador especializado para linha digitável de boletos bancários. Ele detecta automaticamente
* se o boleto é do tipo tributos ou de conveniados. Caso o boleto comece com o número '8', então
* é aplicada a formatação: 888888888888 888888888888 888888888888 888888888888. Caso contrário,
* usa-se o padrão 99999.99999 99999.999999 99999.999999 9 99999999999999.
*/
public final class FormatadorBoleto implements Formatador {
private static final Pattern TRIBUTO_FORMATADO =
Pattern.compile("(\\d{12})\\s(\\d{12})\\s(\\d{12})\\s(\\d{12})");
private static final Pattern TRIBUTO_DESFORMATADO =
Pattern.compile("(\\d{12})(\\d{12})(\\d{12})(\\d{12})");
private static final FormatadorBase FORMATADOR_TRIBUTOS = new FormatadorBase(
TRIBUTO_FORMATADO,
"$1 $2 $3 $4",
TRIBUTO_DESFORMATADO,
"$1$2$3$4"
);
private static final Pattern NORMAL_FORMATADO = Pattern.compile(
"(\\d{5})[.](\\d{5})\\s(\\d{5})[.](\\d{6})\\s(\\d{5})[.](\\d{6})\\s(\\d)\\s(\\d{14})"
);
private static final Pattern NORMAL_DESFORMATADO = Pattern.compile(
"(\\d{5})(\\d{5})(\\d{5})(\\d{6})(\\d{5})(\\d{6})(\\d{1})(\\d{14})"
);
private static final FormatadorBase FORMATADOR_NORMAL = new FormatadorBase(
NORMAL_FORMATADO,
"$1.$2 $3.$4 $5.$6 $7 $8",
NORMAL_DESFORMATADO,
"$1$2$3$4$5$6$7$8"
);
// No instance creation
private FormatadorBoleto() {
}
@Override
public String formata(String value) {
if (ehTributo(value)) {
return FORMATADOR_TRIBUTOS.formata(value);
}
return FORMATADOR_NORMAL.formata(value);
}
@Override
public String desformata(String value) {
if (ehTributo(value)) {
return FORMATADOR_TRIBUTOS.desformata(value);
}
return FORMATADOR_NORMAL.desformata(value);
}
@Override
public boolean estaFormatado(String value) {
if (ehTributo(value)) {
return FORMATADOR_TRIBUTOS.estaFormatado(value);
}
return FORMATADOR_NORMAL.estaFormatado(value);
}
@Override
public boolean podeSerFormatado(String value) {
if (ehTributo(value)) {
return FORMATADOR_TRIBUTOS.podeSerFormatado(value);
}
return FORMATADOR_NORMAL.podeSerFormatado(value);
}
private boolean ehTributo(String value) {
if (value == null) {
throw new IllegalArgumentException("Valor não pode ser nulo");
}
return value.charAt(0) == '8';
}
static FormatadorBoleto getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final FormatadorBoleto INSTANCE = new FormatadorBoleto();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorCEP.java
================================================
package br.com.concrete.canarinho.formatador;
/**
* Formatador para CEP. Segue o padrão 99999-999.
*/
public final class FormatadorCEP implements Formatador {
private FormatadorCEP() {
}
static FormatadorCEP getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public String formata(final String value) {
return Formatador.CEP.formata(value);
}
@Override
public String desformata(final String value) {
return Formatador.CEP.desformata(value);
}
@Override
public boolean estaFormatado(final String value) {
return Formatador.CEP.estaFormatado(value);
}
@Override
public boolean podeSerFormatado(final String value) {
if (value == null) {
return false;
}
return Formatador.CEP.podeSerFormatado(value);
}
private static class SingletonHolder {
private static final FormatadorCEP INSTANCE = new FormatadorCEP();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorCPFCNPJ.java
================================================
package br.com.concrete.canarinho.formatador;
/**
* Formatador para CPF e CNPJ no mesmo campo. Formata como CPF até 11 dígitos numéricos. Depois
* formata como CNPJ.
*/
public final class FormatadorCPFCNPJ implements Formatador {
private FormatadorCPFCNPJ() {
}
static FormatadorCPFCNPJ getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public String formata(final String value) {
if (ehCpf(value)) {
return Formatador.CPF.formata(value);
}
return Formatador.CNPJ.formata(value);
}
@Override
public String desformata(final String value) {
if (ehCpf(value)) {
return Formatador.CPF.desformata(value);
}
return Formatador.CNPJ.desformata(value);
}
@Override
public boolean estaFormatado(final String value) {
if (ehCpf(value)) {
return Formatador.CPF.estaFormatado(value);
}
return Formatador.CNPJ.estaFormatado(value);
}
@Override
public boolean podeSerFormatado(final String value) {
if (value == null) {
return false;
}
if (ehCpf(value)) {
return Formatador.CPF.podeSerFormatado(value);
}
return Formatador.CNPJ.podeSerFormatado(value);
}
private boolean ehCpf(String value) {
if (value == null) {
throw new IllegalArgumentException("Valor não pode ser nulo");
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value)
.replaceAll("");
return desformatado.length() < 12;
}
private static class SingletonHolder {
private static final FormatadorCPFCNPJ INSTANCE = new FormatadorCPFCNPJ();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorLinhaDigitavel.java
================================================
package br.com.concrete.canarinho.formatador;
import br.com.concrete.canarinho.DigitoPara;
import br.com.concrete.canarinho.validator.ValidadorBoleto;
/**
* Transforma a linha digitável de um boleto em um código de boleto e vice-versa. Use o metodo
* {@link #formata(String)} para transformar a linha digitavel em boleto e
* {@link #desformata(String)}.
* Para verificar se um valor esta em linha digitável ou em boleto, usar os métodos:
*
* - {@link #estaFormatado(String)}: indicará se está em formata de boleto
* - {@link #podeSerFormatado(String)}: indicará se é uma linha digitável
*
*/
public final class FormatadorLinhaDigitavel implements Formatador {
private FormatadorLinhaDigitavel() {
}
static FormatadorLinhaDigitavel getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public String formata(String value) {
if (value == null || value.length() != 44) {
throw new IllegalArgumentException("Linha digitável deve conter 44 caracteres. "
+ "Valor possui " + value + " caracteres");
}
if (value.startsWith("8")) {
final String primeiroBloco = value.substring(0, 11);
final String segundoBloco = value.substring(11, 22);
final String terceiroBloco = value.substring(22, 33);
final String quartoBloco = value.substring(33, 44);
// o terceiro dígito é o de valor real que define se será mod 10 ou mod 11
final boolean ehMod10 = value.charAt(2) == '6' || value.charAt(2) == '7';
final DigitoPara mod = ehMod10 ? ValidadorBoleto.MOD_10 : ValidadorBoleto.MOD_11;
final String primeiroDigito = mod.calcula(primeiroBloco);
final String segundoDigito = mod.calcula(segundoBloco);
final String terceiroDigito = mod.calcula(terceiroBloco);
final String quartoDigito = mod.calcula(quartoBloco);
return primeiroBloco + primeiroDigito + segundoBloco + segundoDigito
+ terceiroBloco + terceiroDigito + quartoBloco + quartoDigito;
}
String primeiroBloco = value.substring(0, 4); // 4
String segundoBloco = value.substring(4, 19); // 15
String terceiroBloco = value.substring(19, 24); // 5
String quartoBloco = value.substring(24, 34); // 10
String quintoBloco = value.substring(34, 44); // 10
// 1 - 3 - 4 - 5 - 2
final StringBuilder codigoOrdenado = new StringBuilder(primeiroBloco)
.append(terceiroBloco)
.append(quartoBloco)
.append(quintoBloco)
.append(segundoBloco);
primeiroBloco = codigoOrdenado.substring(0, 9);
segundoBloco = codigoOrdenado.substring(9, 19);
terceiroBloco = codigoOrdenado.substring(19, 29);
quartoBloco = codigoOrdenado.substring(29);
final String primeiroDigito = ValidadorBoleto.MOD_10.calcula(primeiroBloco);
final String segundoDigito = ValidadorBoleto.MOD_10.calcula(segundoBloco);
final String terceiroDigito = ValidadorBoleto.MOD_10.calcula(terceiroBloco);
return primeiroBloco + primeiroDigito + segundoBloco + segundoDigito
+ terceiroBloco + terceiroDigito + quartoBloco;
}
@Override
public String desformata(String valor) {
if (valor == null || "".equals(valor)) {
throw new IllegalArgumentException("Valor não pode estar nulo.");
}
String valorDesformatadao = Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor)
.replaceAll("");
if (valorDesformatadao.charAt(0) == '8') {
if (valorDesformatadao.length() != 48) {
throw new IllegalArgumentException(
"Valor para boletos que iniciam com 8 deve conter 48 dígitos"
);
}
final StringBuilder builder = new StringBuilder(valorDesformatadao);
final String primeiroBloco = builder.substring(0, 11);
final String segundoBloco = builder.substring(12, 23);
final String terceiroBloco = builder.substring(24, 35);
final String quartoBloco = builder.substring(36, 47);
return "" + primeiroBloco + segundoBloco + terceiroBloco + quartoBloco;
}
if (valorDesformatadao.length() != 47) {
throw new IllegalArgumentException("Valor para boletos deve conter 47 digitos");
}
String primeiroBloco = valorDesformatadao.substring(0, 9);
String segundoBloco = valorDesformatadao.substring(10, 20);
String terceiroBloco = valorDesformatadao.substring(21, 31);
String quartoBloco = valorDesformatadao.substring(32, valorDesformatadao.length());
final StringBuilder boletoOrdenado = new StringBuilder(primeiroBloco)
.append(segundoBloco)
.append(terceiroBloco)
.append(quartoBloco);
// 1 - 3 - 4 - 5 - 2
primeiroBloco = boletoOrdenado.substring(0, 4); // 4
segundoBloco = boletoOrdenado.substring(29, 44); // 15
terceiroBloco = boletoOrdenado.substring(4, 9); // 5
quartoBloco = boletoOrdenado.substring(9, 19); // 10
final String quintoBloco = boletoOrdenado.substring(19, 29); // 10
return "" + primeiroBloco + segundoBloco + terceiroBloco + quartoBloco + quintoBloco;
}
@Override
public boolean estaFormatado(String value) {
return Formatador.BOLETO.estaFormatado(value);
}
@Override
public boolean podeSerFormatado(String value) {
return Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value)
.replaceAll("")
.length() == 44;
}
private static class SingletonHolder {
private static final FormatadorLinhaDigitavel INSTANCE = new FormatadorLinhaDigitavel();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorTelefone.java
================================================
package br.com.concrete.canarinho.formatador;
import java.util.regex.Pattern;
/**
* Formata no padrão de telefone brasileiro: (99) 99999-9999 ou (99) 9999-9999.
*/
public final class FormatadorTelefone implements Formatador {
private static final Pattern NOVE_DIGITOS_FORMATADO = Pattern.compile(
"\\((\\d{2})\\)\\s(\\d{5})-(\\d{4})"
);
private static final Pattern NOVE_DIGITOS_DESFORMATADO = Pattern.compile(
"(\\d{2})(\\d{5})(\\d{4})"
);
private static final Pattern OITO_DIGITOS_FORMATADO = Pattern.compile(
"\\((\\d{2})\\)\\s(\\d{4})-(\\d{4})"
);
private static final Pattern OITO_DIGITOS_DESFORMATADO = Pattern.compile(
"(\\d{2})(\\d{4})(\\d{4})"
);
private static final FormatadorBase FORMATADOR_NOVE_DIGITOS = new FormatadorBase(
NOVE_DIGITOS_FORMATADO,
"($1) $2-$3",
NOVE_DIGITOS_DESFORMATADO,
"$1$2$3"
);
private static final FormatadorBase FORMATADOR_OITO_DIGITOS = new FormatadorBase(
OITO_DIGITOS_FORMATADO,
"($1) $2-$3",
OITO_DIGITOS_DESFORMATADO,
"$1$2$3"
);
private FormatadorTelefone() {
}
static FormatadorTelefone getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public String formata(String value) {
if (ehNoveDigitos(value)) {
return FORMATADOR_NOVE_DIGITOS.formata(value);
}
return FORMATADOR_OITO_DIGITOS.formata(value);
}
@Override
public String desformata(String value) {
if (ehNoveDigitos(value)) {
return FORMATADOR_NOVE_DIGITOS.desformata(value);
}
return FORMATADOR_OITO_DIGITOS.desformata(value);
}
@Override
public boolean estaFormatado(String value) {
if (ehNoveDigitos(value)) {
return FORMATADOR_NOVE_DIGITOS.estaFormatado(value);
}
return FORMATADOR_OITO_DIGITOS.estaFormatado(value);
}
@Override
public boolean podeSerFormatado(String value) {
if (ehNoveDigitos(value)) {
return FORMATADOR_NOVE_DIGITOS.podeSerFormatado(value);
}
return FORMATADOR_OITO_DIGITOS.podeSerFormatado(value);
}
private boolean ehNoveDigitos(String value) {
if (value == null) {
throw new IllegalArgumentException("Valor não pode ser nulo");
}
return Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value)
.replaceAll("")
.length() > 10;
}
private static class SingletonHolder {
private static final FormatadorTelefone INSTANCE = new FormatadorTelefone();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorValor.java
================================================
package br.com.concrete.canarinho.formatador;
import android.os.Build;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* Formatador de valores monetários. Possui duas versões:
*
* - Com símbolo do Real {@link Formatador#VALOR_COM_SIMBOLO}
* - Sem símbolo do Real {@link Formatador#VALOR}
*
*/
public final class FormatadorValor implements Formatador {
private static final DecimalFormat FORMATADOR_MOEDA = (DecimalFormat)
NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
private static final Pattern PADRAO_DECIMAL = Pattern
.compile("^\\d+(\\.\\d{1,2})?$");
private static final Pattern PADRAO_MOEDA = Pattern
.compile("\\d{1,3}(\\.\\d{3})*(,\\d{2})?");
private static final String SIMBOLO_REAL = "R$ ";
static {
final DecimalFormatSymbols decimalSymbols = FORMATADOR_MOEDA.getDecimalFormatSymbols();
decimalSymbols.setCurrencySymbol("");
FORMATADOR_MOEDA.setMinimumFractionDigits(2);
FORMATADOR_MOEDA.setDecimalFormatSymbols(decimalSymbols);
FORMATADOR_MOEDA.setNegativePrefix("-");
FORMATADOR_MOEDA.setNegativeSuffix("");
FORMATADOR_MOEDA.setPositivePrefix("");
FORMATADOR_MOEDA.setParseBigDecimal(true);
}
private final boolean adicionaSimboloReal;
// No instance creation
private FormatadorValor(boolean comSimboloReal) {
adicionaSimboloReal = comSimboloReal;
}
/**
* Busca uma instância do formatador com símbolo ou sem.
*
* @param comSimboloReal Flag para saber qual instância buscar.
* @return FormatadorValor de acordo com a flag
*/
static FormatadorValor getInstance(boolean comSimboloReal) {
return comSimboloReal
? SingletonHolder.INSTANCE_COM_SIMBOLO
: SingletonHolder.INSTANCE_SEM_SIMBOLO;
}
@Override
public String formata(String value) {
final String resultado = FORMATADOR_MOEDA.format(new BigDecimal(value));
return adicionaSimboloReal ? SIMBOLO_REAL + resultado : resultado;
}
@Override
public String desformata(String value) {
String realValue = value;
if (value.startsWith(SIMBOLO_REAL)) {
int offset = value.indexOf(SIMBOLO_REAL) + SIMBOLO_REAL.length();
realValue = value.substring(offset);
}
final BigDecimal valor;
if (Build.VERSION.SDK_INT < 28) {
valor = (BigDecimal) FORMATADOR_MOEDA.parse(realValue,
new ParsePosition(0));
} else {
// Implementando o parse manual devido a um bug na API 28
valor = new BigDecimal(realValue.replaceAll("\\.", "")
.replace(",", "."));
}
return valor.toPlainString();
}
@Override
public boolean estaFormatado(String value) {
if (value == null) {
throw new IllegalArgumentException("Valor não pode ser nulo");
}
return PADRAO_MOEDA.matcher(value).matches();
}
@Override
public boolean podeSerFormatado(String value) {
if (value == null) {
throw new IllegalArgumentException("Valor não pode ser nulo");
}
return PADRAO_DECIMAL.matcher(value).matches();
}
private static class SingletonHolder {
private static final FormatadorValor INSTANCE_SEM_SIMBOLO = new FormatadorValor(false);
private static final FormatadorValor INSTANCE_COM_SIMBOLO = new FormatadorValor(true);
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/Validador.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
/**
* Interface de validação de campos. Há basicamente duas formas de validação:
*
* - Uma {@link String} completa
* - Um {@link Editable} e um {@link br.com.concrete.canarinho.validator.Validador.ResultadoParcial}
*
* No primeiro caso o retorno será: true ou false. No segundo caso, o resultado será sempre
* atualizado no objeto {@link br.com.concrete.canarinho.validator.Validador.ResultadoParcial} passado.
*/
public interface Validador {
/**
* Referência para o singleton de validação de CPF.
*/
Validador CPF = ValidadorCPF.getInstance();
/**
* Referência para o singleton de validação de CNPJ.
*/
Validador CNPJ = ValidadorCNPJ.getInstance();
/**
* Referência para o singleton de validação de boleto.
*/
Validador BOLETO = ValidadorBoleto.getInstance();
/**
* Referência para o singleton de validação de telefone.
*/
Validador TELEFONE = ValidadorTelefone.getInstance();
/**
* Referência para o singleton de validação de CEP.
*/
Validador CEP = ValidadorCEP.getInstance();
/**
* Valida uma {@link String} completa.
*
* @param valor Valor a ser validado
* @return true se estiver válida e false caso contrário
*/
boolean ehValido(String valor);
/**
* Valida um {@link Editable} retornando o
* {@link br.com.concrete.canarinho.validator.Validador.ResultadoParcial}.
*
* @param valor Editable
* @param resultadoParcial Objeto com o estado da validação
* @return Objeto com o estado da validação atualizado
*/
ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial);
/**
* Value Object com o estado da validação.
*/
class ResultadoParcial {
private boolean valido;
private boolean parcialmenteValido = true;
private String mensagem;
public boolean isValido() {
return valido;
}
public boolean isParcialmenteValido() {
return parcialmenteValido;
}
public String getMensagem() {
return mensagem;
}
/**
* Ajusta a validação com o valor de "totalmente válido".
*
* @param valido Flag totalmenteValido
* @return Fluent Interface "this"
*/
public ResultadoParcial totalmenteValido(boolean valido) {
this.valido = valido;
return this;
}
/**
* Ajusta a validação com o valor de "totalmente válido".
*
* @param parcialmenteValido Flag parcialmenteValido
* @return Fluent Interface "this"
*/
public ResultadoParcial parcialmenteValido(boolean parcialmenteValido) {
this.parcialmenteValido = parcialmenteValido;
return this;
}
/**
* Ajusta a mensagem de erro.
*
* @param mensagem Mensagem usada na apresentação do erro.
* @return Fluent Interface "this"
*/
public ResultadoParcial mensagem(String mensagem) {
this.mensagem = mensagem;
return this;
}
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorBoleto.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import br.com.concrete.canarinho.DigitoPara;
import br.com.concrete.canarinho.formatador.Formatador;
import java.util.regex.Pattern;
/**
* Implementação de @{link Validador} para boleto.
*
* @see Validador
*/
public final class ValidadorBoleto implements Validador {
/**
* Instância de módulo 10 para cálculo de digito verificador de boleto.
*/
public static final DigitoPara MOD_10 = new DigitoPara.Builder()
.mod(10)
.comMultiplicadores(2, 1)
.somandoIndividualmente()
.trocandoPorSeEncontrar("0", 10)
.complementarAoModulo()
.build();
/**
* Instância de módulo 11 para cálculo de digito verificador de boleto.
*/
public static final DigitoPara MOD_11 = new DigitoPara.Builder()
.trocandoPorSeEncontrar("0", 10, 11)
.complementarAoModulo()
.build();
private static final Pattern PADRAO_PARA_LIMPAR = Pattern.compile("[\\s.]");
private static final Pattern PADRAO_APENAS_NUMEROS = Pattern.compile("[\\d]*");
// No instance creation
private ValidadorBoleto() {
}
public static ValidadorBoleto getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public boolean ehValido(String valor) {
if (valor == null) {
throw new IllegalArgumentException("Campos não podem ser nulos");
}
String valorSemFormatacao = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
return ehValido(new SpannableStringBuilder(valorSemFormatacao), new ResultadoParcial()).isValido();
}
@Override
public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {
if (resultadoParcial == null || valor == null) {
throw new IllegalArgumentException("Campos não podem ser nulos");
}
final String valorDesformatado = PADRAO_PARA_LIMPAR.matcher(valor).replaceAll("");
if (!PADRAO_APENAS_NUMEROS.matcher(valorDesformatado).matches()) {
throw new IllegalArgumentException("Apenas números, '.' e espaços são válidos");
}
resultadoParcial.totalmenteValido(false);
if (valorDesformatado.length() == 0) {
return resultadoParcial
.parcialmenteValido(true)
.mensagem("");
}
return ehTributo(valorDesformatado)
? validaTributo(valorDesformatado, resultadoParcial)
: validaNormal(valorDesformatado, resultadoParcial);
}
private ResultadoParcial validaNormal(String valor, ResultadoParcial resultadoParcial) {
if (!validaBloco(valor, resultadoParcial, MOD_10, 10, 0, "Primeiro")) {
return resultadoParcial;
}
if (!validaBloco(valor, resultadoParcial, MOD_10, 21, 10, "Segundo")) {
return resultadoParcial;
}
if (!validaBloco(valor, resultadoParcial, MOD_10, 32, 21, "Terceiro")) {
return resultadoParcial;
}
if (valor.length() < 47) {
return resultadoParcial;
}
return resultadoParcial.parcialmenteValido(true).totalmenteValido(true);
}
private ResultadoParcial validaTributo(String valor, ResultadoParcial resultadoParcial) {
if (valor.length() < 3) {
return resultadoParcial.parcialmenteValido(true);
}
// A validação precisa levar em conta o terceiro dígito
final boolean ehMod10 = valor.charAt(2) == '6' || valor.charAt(2) == '7';
final DigitoPara digitoPara = ehMod10 ? MOD_10 : MOD_11;
if (!validaBloco(valor, resultadoParcial, digitoPara, 12, 0, "Primeiro")) {
return resultadoParcial;
}
if (!validaBloco(valor, resultadoParcial, digitoPara, 24, 12, "Segundo")) {
return resultadoParcial;
}
if (!validaBloco(valor, resultadoParcial, digitoPara, 36, 24, "Terceiro")) {
return resultadoParcial;
}
if (!validaBloco(valor, resultadoParcial, digitoPara, 48, 36, "Quarto")) {
return resultadoParcial;
}
// Retorna bloco válido
return resultadoParcial.parcialmenteValido(true).totalmenteValido(true);
}
private boolean ehTributo(CharSequence valor) {
return valor.charAt(0) == '8';
}
private boolean validaBloco(String valor, ResultadoParcial resultadoParcial, DigitoPara mod,
int tamanhoMinimo, int st, String mensagem) {
if (valor.length() < tamanhoMinimo) {
resultadoParcial.parcialmenteValido(true);
return false;
}
final int end = tamanhoMinimo - 1;
// Valida primeiro bloco
final char digito = mod.calcula(valor.subSequence(st, end).toString()).charAt(0);
if (digito != valor.charAt(end)) {
return resultadoParcial
.mensagem(mensagem + " bloco inválido")
.parcialmenteValido(false)
.isParcialmenteValido();
}
return true;
}
private static class SingletonHolder {
private static final ValidadorBoleto INSTANCE = new ValidadorBoleto();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCEP.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
import br.com.concrete.canarinho.formatador.Formatador;
/**
* Implementação de @{link Validador} para CEP (Código de endereçamento Postal).
*
* @see Validador
*/
public final class ValidadorCEP implements Validador {
// No instance creation
private ValidadorCEP() {
}
public static ValidadorCEP getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public boolean ehValido(String valor) {
if (valor == null || valor.length() < 8) {
return false;
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
return desformatado.length() == 8;
}
@Override
public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {
if (resultadoParcial == null || valor == null) {
throw new IllegalArgumentException("Valores não podem ser nulos");
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
if (!ehValido(desformatado)) {
return resultadoParcial
.parcialmenteValido(desformatado.length() < 8)
.mensagem("CEP inválido")
.totalmenteValido(false);
}
return resultadoParcial
.parcialmenteValido(true)
.totalmenteValido(true);
}
private static class SingletonHolder {
private static final ValidadorCEP INSTANCE = new ValidadorCEP();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCNPJ.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
import br.com.concrete.canarinho.DigitoPara;
import br.com.concrete.canarinho.formatador.Formatador;
/**
* Implementação de @{link Validador} para CNPJ.
*
* @see Validador
*/
public final class ValidadorCNPJ implements Validador {
private static final DigitoPara DIGITO_PARA_CNPJ = new DigitoPara.Builder()
.complementarAoModulo()
.trocandoPorSeEncontrar("0", 10, 11)
.build();
// No instance creation
private ValidadorCNPJ() {
}
public static ValidadorCNPJ getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public boolean ehValido(String value) {
if (value == null || value.length() < 14) {
return false;
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value).replaceAll("");
if (desformatado.length() != 14) {
return false;
}
final String cnpjSemDigitos = desformatado.substring(0, desformatado.length() - 2);
final String digitos = desformatado.substring(desformatado.length() - 2);
final String dig1 = DIGITO_PARA_CNPJ.calcula(cnpjSemDigitos);
final String dig2 = DIGITO_PARA_CNPJ.calcula(cnpjSemDigitos + dig1);
return (dig1 + dig2).equals(digitos);
}
@Override
public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {
if (resultadoParcial == null || valor == null) {
throw new IllegalArgumentException("Valores não podem ser nulos");
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
if (!ehValido(desformatado)) {
return resultadoParcial
.parcialmenteValido(desformatado.length() < 14)
.mensagem("CNPJ inválido")
.totalmenteValido(false);
}
return resultadoParcial
.parcialmenteValido(true)
.totalmenteValido(true);
}
private static class SingletonHolder {
private static final ValidadorCNPJ INSTANCE = new ValidadorCNPJ();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCPF.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
import br.com.concrete.canarinho.DigitoPara;
import br.com.concrete.canarinho.formatador.Formatador;
/**
* Implementação de @{link Validador} para CPF.
*
* @see Validador
*/
public final class ValidadorCPF implements Validador {
private static final DigitoPara DIGITO_PARA_CPF = new DigitoPara.Builder()
.comMultiplicadoresDeAte(2, 11)
.complementarAoModulo()
.trocandoPorSeEncontrar("0", 10, 11)
.build();
// No instance creation
private ValidadorCPF() {
}
static ValidadorCPF getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public boolean ehValido(String value) {
if (value == null || value.length() < 11) {
return false;
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value).replaceAll("");
if (desformatado.length() != 11) {
return false;
}
if (estaNaListaNegra(desformatado)) {
return false;
}
final String cpfSemDigito = desformatado.substring(0, desformatado.length() - 2);
final String digitos = desformatado.substring(desformatado.length() - 2);
final String dig1 = DIGITO_PARA_CPF.calcula(cpfSemDigito);
final String dig2 = DIGITO_PARA_CPF.calcula(cpfSemDigito + dig1);
return (dig1 + dig2).equals(digitos);
}
@Override
public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {
if (resultadoParcial == null || valor == null) {
throw new IllegalArgumentException("Valores não podem ser nulos");
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
if (!ehValido(desformatado)) {
return resultadoParcial
.parcialmenteValido(desformatado.length() < 11)
.mensagem("CPF inválido")
.totalmenteValido(false);
}
return resultadoParcial
.parcialmenteValido(true)
.totalmenteValido(true);
}
// De acordo ao cálculo dos digitos verificadores, os CPFs abaixo são válidos, entretanto os mesmo
// são considerados inválidos pela Receita Federal
// 00000000000, 11111111111, 22222222222, 33333333333, 44444444444, 55555555555,
// 66666666666, 77777777777, 88888888888, 99999999999, 12345678909
private boolean estaNaListaNegra(String valor) {
boolean igual = true;
for (int i = 1; i < 11 && igual; i++) {
if (valor.charAt(i) != valor.charAt(0)) {
igual = false;
}
}
return igual || valor.equals("12345678909");
}
private static class SingletonHolder {
private static final ValidadorCPF INSTANCE = new ValidadorCPF();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCPFCNPJ.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
import br.com.concrete.canarinho.formatador.Formatador;
public final class ValidadorCPFCNPJ implements Validador {
// No instance creation
private ValidadorCPFCNPJ() {
}
public static ValidadorCPFCNPJ getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public boolean ehValido(final String valor) {
if (valor == null || (valor.length() != 11 && valor.length() != 14)) {
return false;
}
if (ehCpf(valor)) {
return ValidadorCPF.getInstance().ehValido(valor);
}
return ValidadorCNPJ.getInstance().ehValido(valor);
}
@Override
public ResultadoParcial ehValido(final Editable valor, final ResultadoParcial resultadoParcial) {
if (resultadoParcial == null || valor == null) {
throw new IllegalArgumentException("Valores não podem ser nulos");
}
if (ehCpf(valor.toString())) {
return ValidadorCPF.getInstance().ehValido(valor, resultadoParcial);
}
return ValidadorCNPJ.getInstance().ehValido(valor, resultadoParcial);
}
private boolean ehCpf(String valor) {
if (valor == null) {
throw new IllegalArgumentException("Valor não pode ser nulo");
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
return desformatado.length() < 12;
}
private static class SingletonHolder {
private static final ValidadorCPFCNPJ INSTANCE = new ValidadorCPFCNPJ();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorTelefone.java
================================================
package br.com.concrete.canarinho.validator;
import android.text.Editable;
import br.com.concrete.canarinho.formatador.Formatador;
public final class ValidadorTelefone implements Validador {
// No instance creation
private ValidadorTelefone() {
}
public static ValidadorTelefone getInstance() {
return SingletonHolder.INSTANCE;
}
@Override
public boolean ehValido(String valor) {
if (valor == null || valor.length() < 10) {
return false;
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
return desformatado.length() == 10 || desformatado.length() == 11;
}
@Override
public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {
if (resultadoParcial == null || valor == null) {
throw new IllegalArgumentException("Valores não podem ser nulos");
}
final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll("");
if (!ehValido(desformatado)) {
return resultadoParcial
.parcialmenteValido(desformatado.length() < 11)
.mensagem("Telefone inválido")
.totalmenteValido(false);
}
return resultadoParcial
.parcialmenteValido(true)
.totalmenteValido(true);
}
private static class SingletonHolder {
private static final ValidadorTelefone INSTANCE = new ValidadorTelefone();
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/BaseCanarinhoTextWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.Selection;
import android.text.TextWatcher;
import br.com.concrete.canarinho.formatador.Formatador;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
/**
* Classe base para Watchers que possuem máscara e efetuam validação.
*
* @see Validador
*/
public abstract class BaseCanarinhoTextWatcher implements TextWatcher {
private boolean mudancaInterna = false;
private int tamanhoAnterior = 0;
private EventoDeValidacao eventoDeValidacao;
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Não faz nada aqui
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Não faz nada aqui
}
public boolean isMudancaInterna() {
return mudancaInterna;
}
@SuppressWarnings("unchecked")
public T getEventoDeValidacao() {
return (T) eventoDeValidacao;
}
public void setEventoDeValidacao(EventoDeValidacao eventoDeValidacao) {
this.eventoDeValidacao = eventoDeValidacao;
}
/**
* Utilitário para implementações de Watcher customizadas.
* Verifica se a ação foi de apagar um caracter
*
* @param s o Editable em uso
* @return True case a ação foi uma deleção e false caso contrário
*/
protected boolean isApagouCaracter(Editable s) {
return tamanhoAnterior > s.length();
}
/**
* Utilitário para implementações de Watcher customizadas.
* Utilitário para atualizar o Editable com flags de atualização.
*
* @param validador Validador utilizado para verificar o input
* @param resultadoParcial Objeto de validação
* @param s Editable em uso
* @param builder Valor atual da string
*/
// Usa o Editable para atualizar o Editable
// O cursor SEMPRE sera posicionado no final do conteúdo
protected void atualizaTexto(Validador validador, Validador.ResultadoParcial resultadoParcial,
Editable s, StringBuilder builder) {
tamanhoAnterior = builder.length();
mudancaInterna = true;
s.replace(0, s.length(), builder, 0, builder.length());
if (builder.toString().equals(s.toString())) {
// TODO: estudar implantar a manutenção da posição do cursor
Selection.setSelection(s, builder.length());
}
efetuaValidacao(validador, resultadoParcial, s);
mudancaInterna = false;
}
/**
* Método que efetua a validação em si.
*
* @param validador Validador utilizado para verificar o input
* @param resultadoParcial Objeto de validação
* @param s Editable em uso
*/
// CUIDADO AO ATUALIZAR O Editable AQUI!!!
protected void efetuaValidacao(Validador validador, Validador.ResultadoParcial resultadoParcial, Editable s) {
if (validador == null) {
return;
}
if (eventoDeValidacao == null) {
validador.ehValido(s.toString());
return;
}
validador.ehValido(s, resultadoParcial);
if (!resultadoParcial.isParcialmenteValido()) {
eventoDeValidacao.invalido(s.toString(), resultadoParcial.getMensagem());
} else if (!resultadoParcial.isValido()) {
eventoDeValidacao.parcialmenteValido(s.toString());
} else {
eventoDeValidacao.totalmenteValido(s.toString());
}
}
/**
* Implementação genérica para adição ou remoção de caracter.
*
* @param s Editable em uso
* @param mascara máscara do Watcher
* @return Builder com o valor final
*/
protected StringBuilder trataAdicaoRemocaoDeCaracter(Editable s, char[] mascara) {
return isApagouCaracter(s)
? trataRemocaoDeCaracter(s, mascara)
: trataAdicaoDeCaracter(s, mascara);
}
private StringBuilder trataAdicaoDeCaracter(Editable s, char[] mascara) {
return carregarMascara(s.toString(), mascara);
}
// Só é chamado após uma deleção, portanto, é seguro chamar mascara[s.length()]
private StringBuilder trataRemocaoDeCaracter(Editable s, char[] mascara) {
final StringBuilder builder = new StringBuilder(s);
// Obtém a posição do último caracter excluído
final int posicaoUltimoCaracter = mascara.length > s.length() ? s.length() : mascara.length - 1;
// Verifica se o último caracter que foi excluído fazia parte da máscara
final boolean ultimoCaracterEraMascara = mascara[posicaoUltimoCaracter] != '#';
// Se o último caracter excluído fazia parte da máscara,
// deve excluir até o primeiro caracter que não faz parte da máscara
if (ultimoCaracterEraMascara) {
boolean encontrouCaracterValido = false;
while (builder.length() > 0 && !encontrouCaracterValido) {
encontrouCaracterValido = mascara[builder.length() - 1] == '#';
builder.deleteCharAt(builder.length() - 1);
}
}
// Caso haja mais de um caracter de formatação (da máscara) faz um loop
// até chegar em um caracter que não seja de formatação
while (builder.length() > 0 && mascara[builder.length() - 1] != '#') {
builder.deleteCharAt(builder.length() - 1);
}
return carregarMascara(builder.toString(), mascara);
}
private StringBuilder carregarMascara(String s, char[] mascara) {
final StringBuilder builder = new StringBuilder();
final String str = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(s).replaceAll("");
// Só carregará a máscara se existir algum valor informado
if (str.length() > 0) {
int j = 0; // Acompanha a posição nos dígitos
// É recomendado não usar enhanced for em Android
for (int i = 0; i < mascara.length; i++) {
final char charMascara = mascara[i];
if (charMascara != '#') { // '#' -> caracter de formatação
builder.append(charMascara);
continue;
}
if (j >= str.length()) {
break;
}
builder.append(str.charAt(j));
j++;
}
}
return builder;
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/BoletoBancarioTextWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.InputFilter;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.validator.ValidadorBoleto;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacaoDeBoleto;
import java.util.Arrays;
/**
* {@link android.text.TextWatcher} responsável por formatar e validar um {@link
* android.widget.EditText} para boletos. Para usar este componente basta criar uma instância e
* chamar {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.
*/
public final class BoletoBancarioTextWatcher extends BaseCanarinhoTextWatcher {
private static final char[] BOLETO_NORMAL = "#####.##### #####.###### #####.###### # ##############".toCharArray();
private static final char[] BOLETO_TRIBUTO = "############ ############ ############ ############".toCharArray();
private static final InputFilter[] FILTRO_TRIBUTO = new InputFilter[]{
new InputFilter.LengthFilter(BOLETO_TRIBUTO.length)};
private static final InputFilter[] FILTRO_NORMAL = new InputFilter[]{
new InputFilter.LengthFilter(BOLETO_NORMAL.length)};
private final Validador validador = ValidadorBoleto.getInstance();
private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();
/**
* TODO Javadoc pendente.
*
* @param callbackErros a descrever
*/
public BoletoBancarioTextWatcher(EventoDeValidacao callbackErros) {
setEventoDeValidacao(callbackErros);
}
@Override
public final void afterTextChanged(Editable s) {
// retorna se a String é menor que o mínimo de caracteres
// para haver uma formatação ou se a mudança foi disparada
// pelo método atualizaTexto
if (isMudancaInterna()) {
return;
}
// Trata o caso em que tudo é apagado em lote
if (s.length() < 3) {
resultadoParcial.mensagem(null).parcialmenteValido(false).totalmenteValido(false);
if (getEventoDeValidacao() != null) {
getEventoDeValidacao().parcialmenteValido("");
}
}
if (s.length() == 0) {
verificaFiltro(s, false);
return;
}
final boolean tributo = ehTributo(s);
final char[] mascara = tributo ? BOLETO_TRIBUTO : BOLETO_NORMAL;
verificaFiltro(s, tributo);
// Trata deleção e adição de forma diferente (só formata em adições)
final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);
atualizaTexto(validador, resultadoParcial, s, builder);
}
public Validador.ResultadoParcial getResultadoParcial() {
return resultadoParcial;
}
@Override
protected void efetuaValidacao(Validador validador, Validador.ResultadoParcial resultadoParcial, Editable s) {
validador.ehValido(s, resultadoParcial);
final EventoDeValidacao callbackErros = getEventoDeValidacao();
if (callbackErros == null) {
return;
}
final String valorAtual = s.toString();
if (!resultadoParcial.isParcialmenteValido()) {
final String mensagem = resultadoParcial.getMensagem();
callbackErros.invalido(valorAtual, mensagem);
if (callbackErros instanceof EventoDeValidacaoDeBoleto) {
final int bloco;
if (mensagem.startsWith("Primeiro")) {
bloco = 1;
} else if (mensagem.startsWith("Segundo")) {
bloco = 2;
} else if (mensagem.startsWith("Terceiro")) {
bloco = 3;
} else if (mensagem.startsWith("Quarto")) {
bloco = 4;
} else {
throw new IllegalArgumentException("Valor não reconhecido para bloco");
}
((EventoDeValidacaoDeBoleto) callbackErros).invalido(valorAtual, bloco);
}
} else if (!resultadoParcial.isValido()) {
callbackErros.parcialmenteValido(valorAtual);
} else {
callbackErros.totalmenteValido(valorAtual);
}
}
private void verificaFiltro(final Editable s, final boolean tributo) {
// Filtro de tamanho
if (tributo && !Arrays.equals(s.getFilters(), FILTRO_TRIBUTO)) {
s.setFilters(FILTRO_TRIBUTO);
} else if (!tributo && !Arrays.equals(s.getFilters(), FILTRO_NORMAL)) {
s.setFilters(FILTRO_NORMAL);
}
}
// Boletos iniciados com 8 são tributos ou de concessionárias
private boolean ehTributo(Editable e) {
return e.charAt(0) == '8';
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/CEPTextWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.InputFilter;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.validator.ValidadorCEP;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
/**
* {@link android.text.TextWatcher} responsável por formatar e validar um {@link android.widget.EditText} para CEPs.
* Para usar este componente basta criar uma instância e chamar
* {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.
*/
public final class CEPTextWatcher extends BaseCanarinhoTextWatcher {
private static final char[] CEP_DIGITOS = "#####-###".toCharArray();
private static final InputFilter[] FILTRO_OITO_DIGITOS = new InputFilter[]{
new InputFilter.LengthFilter(CEP_DIGITOS.length)};
private final Validador validador = ValidadorCEP.getInstance();
private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();
/**
* Inicializa o validador com um callback de erros.
*
* @param callbackErros Instância que será chamada quando houverem erros.
*/
public CEPTextWatcher(EventoDeValidacao callbackErros) {
setEventoDeValidacao(callbackErros);
}
@Override
public void afterTextChanged(Editable s) {
if (isMudancaInterna()) {
return;
}
s.setFilters(FILTRO_OITO_DIGITOS);
final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, CEP_DIGITOS);
atualizaTexto(validador, resultadoParcial, s, builder);
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/CPFCNPJTextWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.InputFilter;
import br.com.concrete.canarinho.formatador.Formatador;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.validator.ValidadorCPFCNPJ;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
/**
* {@link android.text.TextWatcher} responsável por formatar e validar um
* {@link android.widget.EditText} para CPF / CNPJ.
* Para usar este componente basta criar uma instância e chamar
* {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.
*/
public class CPFCNPJTextWatcher extends BaseCanarinhoTextWatcher {
private static final char[] CPF = "###.###.###-##".toCharArray();
private static final char[] CNPJ = "##.###.###/####-##".toCharArray();
private static final InputFilter[] FILTRO_CPF_CNPJ = new InputFilter[]{new InputFilter.LengthFilter(CNPJ.length)};
private final Validador validador = ValidadorCPFCNPJ.getInstance();
private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();
/**
* TODO Javadoc pendente.
*/
public CPFCNPJTextWatcher() {
}
/**
* TODO Javadoc pendente.
*
* @param callbackErros a descrever
*/
public CPFCNPJTextWatcher(EventoDeValidacao callbackErros) {
setEventoDeValidacao(callbackErros);
}
@Override
public void afterTextChanged(final Editable s) {
if (isMudancaInterna()) {
return;
}
s.setFilters(FILTRO_CPF_CNPJ);
final char[] mascara = ehCpf(s) ? CPF : CNPJ;
final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);
atualizaTexto(validador, resultadoParcial, s, builder);
}
// Verifica se o valor informado é cpf
private boolean ehCpf(Editable e) {
return Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(e).replaceAll("").length() < 12;
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/MascaraNumericaTextWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.InputFilter;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
import java.util.Arrays;
/**
* Máscara c/ validação genérica para campos numéricos.
*
* @see br.com.concrete.canarinho.watcher.MascaraNumericaTextWatcher.Builder
*/
public final class MascaraNumericaTextWatcher extends BaseCanarinhoTextWatcher {
private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();
private final Validador validador;
private final char[] mascara;
private final InputFilter[] filtroNumerico;
/**
* Construtor para adicionar uma máscara sem validação.
*
* @param mascara Máscara para efetuar a formatação
*/
public MascaraNumericaTextWatcher(String mascara) {
this(new Builder().paraMascara(mascara));
}
private MascaraNumericaTextWatcher(Builder builder) {
this.mascara = builder.mascara.toCharArray();
this.validador = builder.validador;
final int length = mascara.length;
this.filtroNumerico = new InputFilter[]{new InputFilter.LengthFilter(length)};
setEventoDeValidacao(builder.eventoDeValidacao);
}
@Override
public void afterTextChanged(Editable s) {
// retorna se a mudança foi disparada pelo método atualizaTexto
if (isMudancaInterna()) {
return;
}
// Filtro de tamanho
if (!Arrays.equals(s.getFilters(), filtroNumerico)) {
s.setFilters(filtroNumerico);
}
final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);
atualizaTexto(validador, resultadoParcial, s, builder);
}
/**
* Builder para construção de máscaras que validam.
*/
public static final class Builder {
private Validador validador;
private EventoDeValidacao eventoDeValidacao;
private String mascara;
/**
* O validador que será usado. Será chamada a implementação de
* {@link Validador#ehValido(Editable, Validador.ResultadoParcial)}
*
* @param validador Implementação de {@link Validador}
* @return this para interface fluente
*/
public Builder comValidador(Validador validador) {
this.validador = validador;
return this;
}
/**
* Para cada caracter digitado será validado de acordo com o Validador e o callback
* correspondente ao resultado da validação será chamado para que a interface possa ser atualizada.
*
* @param callbackErros {@link EventoDeValidacao} que será chamado durante a validação
* @return this para interface fluente
*/
public Builder comCallbackDeValidacao(EventoDeValidacao callbackErros) {
this.eventoDeValidacao = callbackErros;
return this;
}
/**
* A máscara só pode conter os caracteres '#' no lugar dos números. Assim, a máscara
* '#####-##' irá aceitar apenas números no lugar de '#'. Ao digitar, o usuário irá ver:
* '12345-67'.
*
* @param mascara Máscara
* @return this para interface fluente
*/
public Builder paraMascara(String mascara) {
this.mascara = mascara;
return this;
}
/**
* Constrói a máscara.
*
* @return A instância imutável da máscara.
*/
public final MascaraNumericaTextWatcher build() {
if (mascara == null || mascara.isEmpty() || !mascara.contains("#")) {
throw new IllegalArgumentException("Máscara precisa conter ao menos um caracter '#'");
}
return new MascaraNumericaTextWatcher(this);
}
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/TelefoneTextWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.InputFilter;
import br.com.concrete.canarinho.formatador.Formatador;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.validator.ValidadorTelefone;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
/**
* {@link android.text.TextWatcher} responsável por formatar e validar um
* {@link android.widget.EditText} para telefones.
* Para usar este componente basta criar uma instância e chamar
* {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.
*/
public final class TelefoneTextWatcher extends BaseCanarinhoTextWatcher {
private static final char[] TELEFONE_OITO_DIGITOS = "(##) ####-####".toCharArray();
private static final char[] TELEFONE_NOVE_DIGITOS = "(##) #####-####".toCharArray();
private static final InputFilter[] FILTRO_NOVE_DIGITOS = new InputFilter[]{
new InputFilter.LengthFilter(TELEFONE_NOVE_DIGITOS.length)};
private final Validador validador = ValidadorTelefone.getInstance();
private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();
/**
* TODO Javadoc pendente.
*
* @param callbackErros a descrever
*/
public TelefoneTextWatcher(EventoDeValidacao callbackErros) {
setEventoDeValidacao(callbackErros);
}
@Override
public void afterTextChanged(Editable s) {
if (isMudancaInterna()) {
return;
}
s.setFilters(FILTRO_NOVE_DIGITOS);
final char[] mascara = ehNoveDigitos(s) ? TELEFONE_NOVE_DIGITOS : TELEFONE_OITO_DIGITOS;
final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);
atualizaTexto(validador, resultadoParcial, s, builder);
}
// Verifica se o telefone possui 9 dígitos
private boolean ehNoveDigitos(Editable e) {
return Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(e).replaceAll("").length() > 10;
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/ValorMonetarioWatcher.java
================================================
package br.com.concrete.canarinho.watcher;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
import android.text.TextWatcher;
import br.com.concrete.canarinho.formatador.Formatador;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* TextWatcher para valores monetários.
*/
public class ValorMonetarioWatcher implements TextWatcher {
private final boolean mantemZerosAoLimpar;
private final Formatador formatador;
private boolean mudancaInterna;
/**
* Constrói uma instância sem símbolo de Real (R$).
*/
public ValorMonetarioWatcher() {
this(false, true);
}
/**
* Constrói uma instância com opção de símbolo de Real (R$).
*
* @param comSimboloReal Flag para acrescentar ou não o símbolo
* @param mantemZerosAoLimpar Sempre que não houver números (apagar em lote) manter zeros
*/
ValorMonetarioWatcher(boolean comSimboloReal, boolean mantemZerosAoLimpar) {
this.formatador = comSimboloReal
? Formatador.VALOR_COM_SIMBOLO
: Formatador.VALOR;
this.mantemZerosAoLimpar = mantemZerosAoLimpar;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Não faz nada aqui
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Não faz nada aqui
}
@Override
public void afterTextChanged(Editable s) {
if (mudancaInterna) {
return;
}
final String somenteNumeros = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS
.matcher(s.toString())
.replaceAll("");
// afterTextChanged é chamado ao rotacionar o dispositivo,
// essa condição evita que ao rotacionar a tela com o campo vazio ocorra NumberFormatException
if (somenteNumeros.length() == 0) {
if (mantemZerosAoLimpar) {
atualizaTexto(s, formatador.formata("000"));
}
return;
}
final BigDecimal resultado = new BigDecimal(somenteNumeros)
.divide(new BigDecimal(100))
.setScale(2, RoundingMode.HALF_DOWN);
atualizaTexto(s, formatador.formata(resultado.toPlainString()));
}
private void atualizaTexto(Editable editable, String valor) {
mudancaInterna = true;
final InputFilter[] oldFilters = editable.getFilters();
editable.setFilters(new InputFilter[] {});
editable.replace(0, editable.length(), valor);
editable.setFilters(oldFilters);
if (valor.equals(editable.toString())) {
// TODO: estudar implantar a manutenção da posição do cursor
Selection.setSelection(editable, valor.length());
}
mudancaInterna = false;
}
/**
* Builder para facilitar a construção de instâncias de {@link ValorMonetarioWatcher}.
*/
public static class Builder {
private boolean mantemZerosAoLimpar;
private boolean simboloReal;
/**
* Manterá os zeros ao limpar o campo.
*
* @return this Fluent interface
*/
public Builder comMantemZerosAoLimpar() {
this.mantemZerosAoLimpar = true;
return this;
}
/**
* Inclui o símbolo de real na formatação.
*
* @return this Fluent interface
*/
public Builder comSimboloReal() {
this.simboloReal = true;
return this;
}
/**
* Constrói a instância.
*
* @return Watcher para ser usado
*/
public ValorMonetarioWatcher build() {
return new ValorMonetarioWatcher(simboloReal, mantemZerosAoLimpar);
}
}
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/evento/EventoDeValidacao.java
================================================
package br.com.concrete.canarinho.watcher.evento;
/**
* Interface para quem estiver usando este TextWatcher poder ter uma ação quando um erro de validação acontecer.
* Os métodos desta interface serão chamados quando for digitado um NOVO caracter e quando for APAGADO um caracter.
*/
public interface EventoDeValidacao {
/**
* Invocado quando os números digitados estão inválidos. Pode ser apenas um trecho ou o número completo.
*
* @param valorAtual O valor após a digitação.
* @param mensagem A mensagem de erro da validação.
*/
void invalido(String valorAtual, String mensagem);
/**
* Invocado quando os números digitados estão parcialmente válidos. Quando o número estiver completamente válido
* será chamado o callback {@link #totalmenteValido(String)}.
*
* @param valorAtual O valor após a digitação.
*/
void parcialmenteValido(String valorAtual);
/**
* Invocado quando a máscara está completa e os números são válidos.
*
* @param valorAtual O valor após a digitação.
*/
void totalmenteValido(String valorAtual);
}
================================================
FILE: canarinho/src/main/java/br/com/concrete/canarinho/watcher/evento/EventoDeValidacaoDeBoleto.java
================================================
package br.com.concrete.canarinho.watcher.evento;
/**
* Evento de validação específico para boletos que permite saber qual o bloco que
* contém caracteres inválidos.
*/
public interface EventoDeValidacaoDeBoleto extends EventoDeValidacao {
/**
* Invocado quando os números digitados estão inválidos. Pode ser apenas um trecho ou o número completo.
*
* @param valorAtual O valor após a digitação.
* @param blocoInvalido O bloco com valor inválido
*/
void invalido(String valorAtual, int blocoInvalido);
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
================================================
FILE: gradle.properties
================================================
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
android.useAndroidX=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: sample/.gitignore
================================================
/build
================================================
FILE: sample/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
android {
compileSdkVersion 31
defaultConfig {
applicationId 'br.com.concrete.canarinho.sample'
minSdkVersion 21
targetSdkVersion 31
versionCode 1
versionName '1.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
debug {
testCoverageEnabled true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
implementation project(':canarinho')
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
androidTestUtil 'androidx.test:orchestrator:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation 'junit:junit:4.13.2'
testImplementation "org.robolectric:robolectric:4.7.3"
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2'
}
jacoco {
toolVersion = '0.8.3'
}
task fullCoverageReport(type: JacocoReport) {
dependsOn 'createDebugCoverageReport'
dependsOn 'testDebugUnitTest'
reports {
xml.enabled true
html.enabled true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
'**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTree])
executionData.from = fileTree(dir: "$buildDir", includes: [
"jacoco/testDebugUnitTest.exec",
"outputs/code_coverage/connected/*coverage.ec"
])
}
================================================
FILE: sample/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/victornascimento/Android/Sdk/linters/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: sample/src/androidTest/java/br/com/concrete/canarinho/sample/BugOnApi28Test.java
================================================
package br.com.concrete.canarinho.sample;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import org.junit.Test;
import org.junit.runner.RunWith;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class BugOnApi28Test {
@Test
@SdkSuppress(minSdkVersion = 28)
public void moneyFormatSuccessfulRunsOnApi28() {
final String value = "1.000.000,00";
assertEquals("1000000.00", Formatador.VALOR.desformata(value));
}
@Test
@SdkSuppress(maxSdkVersion = 27)
public void moneyFormatSuccessfulRunsOnApi27() {
final String value = "1.000.000,00";
assertEquals("1000000.00", Formatador.VALOR.desformata(value));
}
}
================================================
FILE: sample/src/androidTest/java/br/com/concrete/canarinho/sample/DemoWatchersInstrumentationTest.java
================================================
package br.com.concrete.canarinho.sample;
import android.os.Build;
import android.view.View;
import android.widget.EditText;
import android.widget.SearchView;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import br.com.concrete.canarinho.sample.ui.activity.MainActivity;
import br.com.concrete.canarinho.sample.ui.model.Watchers;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.clearText;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.pressBack;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.supportsInputMethods;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
@RunWith(AndroidJUnit4.class)
public class DemoWatchersInstrumentationTest {
@Rule
public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class);
@Test
public void consegueDigitarUmBoletoNormalValido() {
navigateToTab(Watchers.BOLETO_BANCARIO);
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(typeText("34199310130010011560900500990007800000000000000"));
onView(withText("Campo válido!")).check(matches(isDisplayed()));
}
@Test
public void consegueValidarUmBoletoSetandoOCodigoInteiro() {
navigateToTab(Watchers.BOLETO_BANCARIO);
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(paste("34199310130010011560900500990007800000000000000"));
onView(withText("Campo válido!")).check(matches(isDisplayed()));
}
@Test
public void consegueValidarUmBoletoTributoSetandoOCodigoInteiro() {
navigateToTab(Watchers.BOLETO_BANCARIO);
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(paste("812345678901812345678901812345678901812345678901"));
onView(withText("Campo válido!")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarUmBoletoNormalComBlocosInvalidos() {
navigateToTab(Watchers.BOLETO_BANCARIO);
// primeiro
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(typeText("23790125016"));
onView(withText("Primeiro bloco inválido")).check(matches(isDisplayed()));
// segundo
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(clearText(), typeText("2379012301600000030054"));
onView(withText("Segundo bloco inválido")).check(matches(isDisplayed()));
// terceiro
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(clearText(), typeText("23790123016000000005325000456708"));
onView(withText("Terceiro bloco inválido")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarUmBoletoTributoValido() {
navigateToTab(Watchers.BOLETO_BANCARIO);
// Boleto válido
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(typeText("848600000015523301622010506101307129620012111220"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()))
.perform(pressBack());
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(clearText(), typeText("836600000019078800481000998854924516001265611135"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()))
.perform(pressBack());
}
@Test
public void consegueDigitarUmBoletoNormalComBlocosInvalidosComMensagemCustomizada() {
navigateToTab(Watchers.BOLETO_BANCARIO_MSG_CUSTOM);
// primeiro
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(typeText("23790125016"));
onView(withText("Primeira mensagem")).check(matches(isDisplayed()));
// segundo
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(clearText(), typeText("2379012301600000030054"));
onView(withText("Segunda mensagem")).check(matches(isDisplayed()));
// terceiro
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(clearText(), typeText("23790123016000000005325000456708"));
onView(withText("Terceira mensagem")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarUmCPFValido() {
navigateToTab(Watchers.CPF);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("46574356636"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()))
.perform(pressBack());
}
@Test
public void consegueDigitarUmCPFInvalido() {
navigateToTab(Watchers.CPF);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("46574356637"));
onView(withText("CPF inválido")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarUmCNPJValido() {
navigateToTab(Watchers.CNPJ);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("95621433000170"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()))
.perform(pressBack());
}
@Test
public void consegueDigitarUmCNPJInvalido() {
navigateToTab(Watchers.CNPJ);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("95621433000180"));
onView(withText("CNPJ inválido")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarUmTelefoneValido() {
navigateToTab(Watchers.TELEFONE);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("1112345678"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()))
.perform(pressBack());
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("11123456789"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()))
.perform(pressBack());
}
@Test
public void consegueDigitarUmValorMonetarioFormatado() throws InterruptedException {
navigateToTab(Watchers.VALOR_MONETARIO);
Thread.sleep(200L);
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(typeText("1"))
.check(matches(withText("R$ 0,01"))) // inicia com 0,0X
.perform(typeText("2"))
.check(matches(withText("R$ 0,12"))) // 0,XY
.perform(typeText("3"))
.check(matches(withText("R$ 1,23"))) // X,YZ
.perform(typeText("4"))
.check(matches(withText("R$ 12,34"))) // etc, etc, etc
.perform(typeText("5"))
.check(matches(withText("R$ 123,45")))
.perform(typeText("6"))
.check(matches(withText("R$ 1.234,56")))
.perform(typeText("7"))
.check(matches(withText("R$ 12.345,67")))
.perform(typeText("8"))
.check(matches(withText("R$ 123.456,78")))
.perform(typeText("9"))
.check(matches(withText("R$ 1.234.567,89")))
.perform(clearText())
.check(matches(withText("R$ 0,00")));
}
@Test
public void consegueDigitarCPFCNPJValido() {
navigateToTab(Watchers.CPF_CNPJ);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("46574356636"));
onView(withText("Campo válido!")).check(matches(isDisplayed())).perform(pressBack());
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(clearText(), typeText("95621433000170"));
onView(withText("Campo válido!")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarCPFCNPJInvalido() {
navigateToTab(Watchers.CPF_CNPJ);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("46574356637"));
onView(withText("CPF inválido")).check(matches(isDisplayed()));
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(clearText(), typeText("15621433000180"));
onView(withText("CNPJ inválido")).check(matches(isDisplayed()));
}
@Test
public void consegueDigitarUmCEPValido() {
navigateToTab(Watchers.CEP);
onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText("49025090"));
onView(withText("Campo válido!"))
.check(matches(isDisplayed()));
}
@Test
public void consegueUtilizarUmaMascaraGenericaSemValidadorOuEvento() {
navigateToTab(Watchers.MASCARA_GENERICA);
onView(allOf(withId(R.id.edit_text), isDisplayed()))
.perform(typeText("12345"))
.check(matches(withText("1-2-3-4-5")));
}
private void navigateToTab(Watchers watcher) {
onView(
allOf(
withText(watcher.getTitle()),
isDescendantOfA(withId(R.id.tabs)))
)
.perform(scrollTo(), click());
}
private ViewAction paste(final String type) {
return new ViewAction() {
@Override
public Matcher getConstraints() {
// noinspection unchecked
Matcher matchers = allOf(isDisplayed());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return allOf(matchers, supportsInputMethods());
} else {
// SearchView does not support input methods itself (rather it delegates to an internal text
// view for input).
return allOf(matchers, anyOf(supportsInputMethods(), isAssignableFrom(SearchView.class)));
}
}
@Override
public String getDescription() {
return "Straight typing into view";
}
@Override
public void perform(UiController uiController, View view) {
((EditText) view).setText(type);
}
};
}
}
================================================
FILE: sample/src/main/AndroidManifest.xml
================================================
================================================
FILE: sample/src/main/java/br/com/concrete/canarinho/sample/ui/activity/MainActivity.java
================================================
package br.com.concrete.canarinho.sample.ui.activity;
import android.os.Bundle;
import com.google.android.material.tabs.TabLayout;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.ViewPager;
import br.com.concrete.canarinho.sample.R;
import br.com.concrete.canarinho.sample.ui.adapter.WatchersPagerAdapter;
/** */
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(true);
supportActionBar.setDisplayShowTitleEnabled(false);
}
final ViewPager viewPager = findViewById(R.id.viewpager);
viewPager.setAdapter(new WatchersPagerAdapter(getSupportFragmentManager()));
final TabLayout tabs = findViewById(R.id.tabs);
tabs.setupWithViewPager(viewPager);
}
}
================================================
FILE: sample/src/main/java/br/com/concrete/canarinho/sample/ui/adapter/WatchersPagerAdapter.java
================================================
package br.com.concrete.canarinho.sample.ui.adapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import br.com.concrete.canarinho.sample.ui.model.Watchers;
public class WatchersPagerAdapter extends FragmentPagerAdapter {
private Watchers[] models = Watchers.values();
public WatchersPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@Override
public Fragment getItem(int position) {
return models[position].buildFragment();
}
@Override
public int getCount() {
return models.length;
}
@Override
public CharSequence getPageTitle(int position) {
return models[position].getTitle();
}
}
================================================
FILE: sample/src/main/java/br/com/concrete/canarinho/sample/ui/fragment/BaseWatcherFragment.java
================================================
package br.com.concrete.canarinho.sample.ui.fragment;
import androidx.fragment.app.Fragment;
import br.com.concrete.canarinho.sample.ui.model.Watchers;
public abstract class BaseWatcherFragment extends Fragment {
protected Watchers model;
public void setModel(Watchers model) {
this.model = model;
}
}
================================================
FILE: sample/src/main/java/br/com/concrete/canarinho/sample/ui/fragment/CanarinhoValorMonetarioWatcherFragment.java
================================================
package br.com.concrete.canarinho.sample.ui.fragment;
import android.os.Bundle;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.material.textfield.TextInputLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import br.com.concrete.canarinho.sample.R;
import br.com.concrete.canarinho.sample.ui.model.Watchers;
import br.com.concrete.canarinho.watcher.ValorMonetarioWatcher;
public class CanarinhoValorMonetarioWatcherFragment extends BaseWatcherFragment {
private EditText watcherEdit;
private TextView watcherTitle;
private TextInputLayout watcherInputLayout;
private TextWatcher currentWatcher;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View layout = inflater.inflate(R.layout.fragment_valor_monetario_watcher, null);
watcherTitle = layout.findViewById(R.id.watcher_title);
watcherInputLayout = layout.findViewById(R.id.edit_input_layout);
watcherEdit = layout.findViewById(R.id.edit_text);
setModel(Watchers.VALOR_MONETARIO);
bind(model);
return layout;
}
public CanarinhoValorMonetarioWatcherFragment bind(Watchers model) {
watcherTitle.setText(model.getTitle());
watcherEdit.setHint(model.getHint());
if (currentWatcher != null) {
watcherEdit.removeTextChangedListener(currentWatcher);
}
currentWatcher = new ValorMonetarioWatcher.Builder()
.comMantemZerosAoLimpar()
.comSimboloReal()
.build();
watcherEdit.addTextChangedListener(currentWatcher);
return this;
}
}
================================================
FILE: sample/src/main/java/br/com/concrete/canarinho/sample/ui/fragment/WatcherFragment.java
================================================
package br.com.concrete.canarinho.sample.ui.fragment;
import android.os.Bundle;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.material.textfield.TextInputLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import br.com.concrete.canarinho.sample.R;
import br.com.concrete.canarinho.sample.ui.model.Watchers;
public class WatcherFragment extends BaseWatcherFragment {
private EditText watcherEdit;
private TextView watcherTitle;
private TextInputLayout watcherInputLayout;
private TextWatcher currentWatcher;
public static WatcherFragment newInstance(Watchers model) {
final WatcherFragment watcherFragment = new WatcherFragment();
watcherFragment.setArguments(new Bundle());
watcherFragment.setModel(model);
return watcherFragment;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View layout = inflater.inflate(R.layout.fragment_canarinho_watcher, null);
watcherTitle = layout.findViewById(R.id.watcher_title);
watcherInputLayout = layout.findViewById(R.id.edit_input_layout);
watcherEdit = layout.findViewById(R.id.edit_text);
bind(model);
return layout;
}
public WatcherFragment bind(Watchers model) {
if (currentWatcher != null) {
watcherEdit.removeTextChangedListener(currentWatcher);
}
currentWatcher = model.setupWatcher(watcherInputLayout);
watcherTitle.setText(model.getTitle());
watcherEdit.setHint(model.getHint());
watcherEdit.addTextChangedListener(currentWatcher);
return this;
}
}
================================================
FILE: sample/src/main/java/br/com/concrete/canarinho/sample/ui/model/Watchers.java
================================================
package br.com.concrete.canarinho.sample.ui.model;
import android.text.TextWatcher;
import com.google.android.material.textfield.TextInputLayout;
import androidx.appcompat.app.AlertDialog;
import br.com.concrete.canarinho.sample.ui.fragment.BaseWatcherFragment;
import br.com.concrete.canarinho.sample.ui.fragment.CanarinhoValorMonetarioWatcherFragment;
import br.com.concrete.canarinho.sample.ui.fragment.WatcherFragment;
import br.com.concrete.canarinho.validator.Validador;
import br.com.concrete.canarinho.watcher.BoletoBancarioTextWatcher;
import br.com.concrete.canarinho.watcher.CEPTextWatcher;
import br.com.concrete.canarinho.watcher.CPFCNPJTextWatcher;
import br.com.concrete.canarinho.watcher.MascaraNumericaTextWatcher;
import br.com.concrete.canarinho.watcher.TelefoneTextWatcher;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;
import br.com.concrete.canarinho.watcher.evento.EventoDeValidacaoDeBoleto;
/**
*/
public enum Watchers {
BOLETO_BANCARIO("Boleto Bancário", "Digite um boleto válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new BoletoBancarioTextWatcher(new SampleEventoDeValidacao(textInputLayout));
}
},
BOLETO_BANCARIO_MSG_CUSTOM("Boleto Bancário com mensagem", "Digite um boleto válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new BoletoBancarioTextWatcher(new EventoDeValidacaoBoleto(textInputLayout));
}
},
CPF("CPF", "Digite um CPF válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new MascaraNumericaTextWatcher.Builder()
.paraMascara("###.###.###-##")
.comCallbackDeValidacao(new SampleEventoDeValidacao(textInputLayout))
.comValidador(Validador.CPF)
.build();
}
},
CNPJ("CNPJ", "Digite um CNPJ válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new MascaraNumericaTextWatcher.Builder()
.paraMascara("##.###.###/####-##")
.comCallbackDeValidacao(new SampleEventoDeValidacao(textInputLayout))
.comValidador(Validador.CNPJ)
.build();
}
},
TELEFONE("Telefone", "Digite um telefone válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new TelefoneTextWatcher(new SampleEventoDeValidacao(textInputLayout));
}
},
CPF_CNPJ("CPF e CNPJ", "Digite um CPF ou CNPJ válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new CPFCNPJTextWatcher(new SampleEventoDeValidacao(textInputLayout));
}
},
CEP("CEP", "Digite um CEP válido") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new CEPTextWatcher(new SampleEventoDeValidacao(textInputLayout));
}
},
MASCARA_GENERICA("Máscara genérica", "5 números") {
@Override
public WatcherFragment buildFragment() {
return WatcherFragment.newInstance(this);
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return new MascaraNumericaTextWatcher("#-#-#-#-#");
}
},
VALOR_MONETARIO("Valor monetário", "Digite um valor monetário") {
@Override
public CanarinhoValorMonetarioWatcherFragment buildFragment() {
return new CanarinhoValorMonetarioWatcherFragment();
}
@Override
public TextWatcher setupWatcher(TextInputLayout textInputLayout) {
return null;
}
};
private final String title;
private final String hint;
Watchers(String title, String hint) {
this.hint = hint;
this.title = title;
}
public String getTitle() {
return title;
}
public String getHint() {
return hint;
}
public abstract BaseWatcherFragment buildFragment();
public abstract TextWatcher setupWatcher(TextInputLayout textInputLayout);
/**
* {@link EventoDeValidacao} que mostra uma mensagem de erro no {@link TextInputLayout} passado
* como argumento.
*/
public static class SampleEventoDeValidacao implements EventoDeValidacao {
TextInputLayout textInputLayout;
public SampleEventoDeValidacao(TextInputLayout textInputLayout) {
this.textInputLayout = textInputLayout;
}
@Override
public void invalido(String valorAtual, String mensagem) {
textInputLayout.setError(mensagem);
}
@Override
public void parcialmenteValido(String valorAtual) {
textInputLayout.setErrorEnabled(false);
textInputLayout.setError(null);
}
@Override
public void totalmenteValido(String valorAtual) {
new AlertDialog.Builder(textInputLayout.getContext())
.setTitle("Campo válido!")
.setMessage(valorAtual)
.show();
}
}
/**
* {@link EventoDeValidacao} que valida blocos de boleto.
*
* @see SampleEventoDeValidacao
*/
public static class EventoDeValidacaoBoleto
extends SampleEventoDeValidacao
implements EventoDeValidacaoDeBoleto {
EventoDeValidacaoBoleto(TextInputLayout textInputLayout) {
super(textInputLayout);
}
@Override
public void invalido(String valorAtual, int blocoInvalido) {
if (blocoInvalido == 1)
textInputLayout.setError("Primeira mensagem");
else if (blocoInvalido == 2)
textInputLayout.setError("Segunda mensagem");
else if (blocoInvalido == 3)
textInputLayout.setError("Terceira mensagem");
else if (blocoInvalido == 4)
textInputLayout.setError("Quarta mensagem");
}
}
}
================================================
FILE: sample/src/main/res/layout/fragment_canarinho_watcher.xml
================================================
================================================
FILE: sample/src/main/res/layout/fragment_valor_monetario_watcher.xml
================================================
================================================
FILE: sample/src/main/res/layout/main_activity.xml
================================================
================================================
FILE: sample/src/main/res/values/strings.xml
================================================
Android Canarinho
================================================
FILE: sample/src/main/res/values/styles.xml
================================================
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorBOLETO.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorBOLETO {
@Test
public void consegueFormatar() {
assertThat(Formatador.BOLETO.formata("12345123451234512345612345123456712345678901234"),
is("12345.12345 12345.123456 12345.123456 7 12345678901234"));
assertThat(Formatador.BOLETO.formata("23790123016000000005325000456704364670000013580"),
is("23790.12301 60000.000053 25000.456704 3 64670000013580"));
assertThat(Formatador.BOLETO.formata("812345678901812345678901812345678901812345678901"),
is("812345678901 812345678901 812345678901 812345678901"));
assertThat(Formatador.BOLETO.formata("23790.12301 60000.000053 25000.456704 3 64670000013580"),
is("23790.12301 60000.000053 25000.456704 3 64670000013580"));
assertThat(Formatador.BOLETO.formata("812345678901 812345678901 812345678901 812345678901"),
is("812345678901 812345678901 812345678901 812345678901"));
}
@Test
public void consegueDesformatar() {
assertThat(Formatador.BOLETO.desformata("12345.12345 12345.123456 12345.123456 7 12345678901234"),
is("12345123451234512345612345123456712345678901234"));
assertThat(Formatador.BOLETO.desformata("23790.12301 60000.000053 25000.456704 3 64670000013580"),
is("23790123016000000005325000456704364670000013580"));
assertThat(Formatador.BOLETO.desformata("812345678901 812345678901 812345678901 812345678901"),
is("812345678901812345678901812345678901812345678901"));
assertThat(Formatador.BOLETO.desformata("23790123016000000005325000456704364670000013580"),
is("23790123016000000005325000456704364670000013580"));
assertThat(Formatador.BOLETO.desformata("812345678901812345678901812345678901812345678901"),
is("812345678901812345678901812345678901812345678901"));
}
@Test
public void consegueDizerSeEstaFormatado() {
assertThat(Formatador.BOLETO.estaFormatado("12345.12345 12345.123456 12345.123456 7 12345678901234"), is(true));
assertThat(Formatador.BOLETO.estaFormatado("23790.12301 60000.000053 25000.456704 3 64670000013580"), is(true));
assertThat(Formatador.BOLETO.estaFormatado("812345678901812345678901812345678901812345678901"), is(false));
assertThat(Formatador.BOLETO.estaFormatado("23790123016000000005325000456704364670000013580"), is(false));
assertThat(Formatador.BOLETO.estaFormatado("812345678901812345678901812345678901812345678901"), is(false));
}
@Test
public void consegueDizerSePodeFormatar() {
// TODO
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCEP.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorCEP {
@Test
public void consegueFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CEP.formata("00000-000"), is("00000-000"));
assertThat(Formatador.CEP.formata("00000000"), is("00000-000"));
assertThat(Formatador.CEP.formata("12345-123"), is("12345-123"));
assertThat(Formatador.CEP.formata("12345678"), is("12345-678"));
assertThrowsFormat("");
assertThrowsFormat("123123");
}
@Test
public void consegueDesformatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CEP.desformata("00000-000"), is("00000000"));
assertThat(Formatador.CEP.desformata("00000000"), is("00000000"));
assertThat(Formatador.CEP.desformata("12345-123"), is("12345123"));
assertThat(Formatador.CEP.desformata("12345678"), is("12345678"));
assertThrowsDesformat("");
assertThrowsDesformat("123123");
}
@Test
public void consegueDizerSeEstaFormatado() {
// Gerados automaticamente para testes
assertThat(Formatador.CEP.estaFormatado("12345-678"), is(true));
assertThat(Formatador.CEP.estaFormatado("12345678"), is(false));
assertThat(Formatador.CEP.estaFormatado("00000-000"), is(true));
assertThat(Formatador.CEP.estaFormatado("12345-67"), is(false));
assertThat(Formatador.CEP.estaFormatado("047486"), is(false));
assertThat(Formatador.CEP.estaFormatado(""), is(false));
try {
Formatador.CEP.estaFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSePodeFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CEP.podeSerFormatado("12345-678"), is(false));
assertThat(Formatador.CEP.podeSerFormatado("12345678"), is(true));
assertThat(Formatador.CEP.podeSerFormatado("020"), is(false));
assertThat(Formatador.CEP.podeSerFormatado(""), is(false));
assertThat(Formatador.CEP.podeSerFormatado(null), is(false));
}
private void assertThrowsFormat(String valor) {
try {
Formatador.CEP.formata(valor);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
private void assertThrowsDesformat(String valor) {
try {
Formatador.CEP.desformata(valor);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCNPJ.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorCNPJ {
@Test
public void consegueFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CNPJ.formata("50.713.534/0001-33"), is("50.713.534/0001-33"));
assertThat(Formatador.CNPJ.formata("50713534000133"), is("50.713.534/0001-33"));
assertThat(Formatador.CNPJ.formata("72.606.598/0001-78"), is("72.606.598/0001-78"));
assertThat(Formatador.CNPJ.formata("72606598000178"), is("72.606.598/0001-78"));
assertThat(Formatador.CNPJ.formata("23.106.535/0001-47"), is("23.106.535/0001-47"));
assertThat(Formatador.CNPJ.formata("23106535000147"), is("23.106.535/0001-47"));
assertThrowsFormat("");
assertThrowsFormat("123123");
assertThrowsFormat("23.106.535/0001 - 47");
assertThrowsFormat(" 23.106.535/0001-47 ");
}
@Test
public void consegueDesformatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CNPJ.desformata("50.713.534/0001-33"), is("50713534000133"));
assertThat(Formatador.CNPJ.desformata("50713534000133"), is("50713534000133"));
assertThat(Formatador.CNPJ.desformata("72.606.598/0001-78"), is("72606598000178"));
assertThat(Formatador.CNPJ.desformata("72606598000178"), is("72606598000178"));
assertThat(Formatador.CNPJ.desformata("23.106.535/0001-47"), is("23106535000147"));
assertThat(Formatador.CNPJ.desformata("23106535000147"), is("23106535000147"));
assertThrowsDesformat("");
assertThrowsDesformat("123123");
assertThrowsDesformat("123.123.123 -12");
assertThrowsDesformat(" 047.486.777-32 ");
}
@Test
public void consegueDizerSeEstaFormatado() {
// Gerados automaticamente para testes
assertThat(Formatador.CNPJ.estaFormatado("72.606.598/0001-78"), is(true));
assertThat(Formatador.CNPJ.estaFormatado("72606598000178"), is(false));
assertThat(Formatador.CNPJ.estaFormatado("23.106.535/0001-47"), is(true));
assertThat(Formatador.CNPJ.estaFormatado("23106535000147"), is(false));
assertThat(Formatador.CNPJ.estaFormatado("047486"), is(false));
assertThat(Formatador.CNPJ.estaFormatado(""), is(false));
try {
Formatador.CNPJ.estaFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSePodeFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CNPJ.podeSerFormatado("23.106.535/0001-47"), is(false));
assertThat(Formatador.CNPJ.podeSerFormatado("23106535000147"), is(true));
assertThat(Formatador.CNPJ.podeSerFormatado("020"), is(false));
assertThat(Formatador.CNPJ.podeSerFormatado(""), is(false));
assertThat(Formatador.CNPJ.podeSerFormatado(null), is(false));
}
private void assertThrowsFormat(String valor) {
try {
Formatador.CNPJ.formata(valor);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
private void assertThrowsDesformat(String valor) {
try {
Formatador.CNPJ.desformata(valor);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCPF.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorCPF {
@Test
public void consegueFormatarCPF() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF.formata("545.586.262-66"), is("545.586.262-66"));
assertThat(Formatador.CPF.formata("54558626266"), is("545.586.262-66"));
assertThat(Formatador.CPF.formata("020.724.833-87"), is("020.724.833-87"));
assertThat(Formatador.CPF.formata("02072483387"), is("020.724.833-87"));
assertThat(Formatador.CPF.formata("188.527.045-31"), is("188.527.045-31"));
assertThat(Formatador.CPF.formata("18852704531"), is("188.527.045-31"));
assertThat(Formatador.CPF.formata("047.486.777-32"), is("047.486.777-32"));
assertThat(Formatador.CPF.formata("04748677732"), is("047.486.777-32"));
assertThrowsFormat("");
assertThrowsFormat("123123");
assertThrowsFormat("123.123.123 -12");
assertThrowsFormat(" 047.486.777-32 ");
}
@Test
public void consegueDesformatarCPF() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF.desformata("545.586.262-66"), is("54558626266"));
assertThat(Formatador.CPF.desformata("54558626266"), is("54558626266"));
assertThat(Formatador.CPF.desformata("020.724.833-87"), is("02072483387"));
assertThat(Formatador.CPF.desformata("02072483387"), is("02072483387"));
assertThat(Formatador.CPF.desformata("188.527.045-31"), is("18852704531"));
assertThat(Formatador.CPF.desformata("18852704531"), is("18852704531"));
assertThat(Formatador.CPF.desformata("047.486.777-32"), is("04748677732"));
assertThat(Formatador.CPF.desformata("04748677732"), is("04748677732"));
assertThrowsDesformat("");
assertThrowsDesformat("123123");
assertThrowsDesformat("123.123.123 -12");
assertThrowsDesformat(" 047.486.777-32 ");
}
@Test
public void consegueDizerSeEstaFormatado() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF.estaFormatado("545.586.262-66"), is(true));
assertThat(Formatador.CPF.estaFormatado("54558626266"), is(false));
assertThat(Formatador.CPF.estaFormatado("020.724.833-87"), is(true));
assertThat(Formatador.CPF.estaFormatado("02072483387"), is(false));
assertThat(Formatador.CPF.estaFormatado("188.527.045-31"), is(true));
assertThat(Formatador.CPF.estaFormatado("18852704531"), is(false));
assertThat(Formatador.CPF.estaFormatado("047.486.777-32"), is(true));
assertThat(Formatador.CPF.estaFormatado("04748677732"), is(false));
assertThat(Formatador.CPF.estaFormatado("047486"), is(false));
assertThat(Formatador.CPF.estaFormatado(""), is(false));
try {
Formatador.CPF.estaFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSePodeFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF.podeSerFormatado("545.586.262-66"), is(false));
assertThat(Formatador.CPF.podeSerFormatado("54558626266"), is(true));
assertThat(Formatador.CPF.podeSerFormatado("020"), is(false));
assertThat(Formatador.CPF.podeSerFormatado(""), is(false));
assertThat(Formatador.CPF.podeSerFormatado(null), is(false));
}
private void assertThrowsFormat(String valor) {
try {
Formatador.CPF.formata(valor);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
private void assertThrowsDesformat(String valor) {
try {
Formatador.CPF.desformata(valor);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCPFCNPJ.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorCPFCNPJ {
@Test
public void consegueFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.formata("50.713.534/0001-33"), is("50.713.534/0001-33"));
assertThat(Formatador.CPF_CNPJ.formata("50713534000133"), is("50.713.534/0001-33"));
assertThat(Formatador.CPF_CNPJ.formata("72.606.598/0001-78"), is("72.606.598/0001-78"));
assertThat(Formatador.CPF_CNPJ.formata("72606598000178"), is("72.606.598/0001-78"));
assertThat(Formatador.CPF_CNPJ.formata("23.106.535/0001-47"), is("23.106.535/0001-47"));
assertThat(Formatador.CPF_CNPJ.formata("23106535000147"), is("23.106.535/0001-47"));
assertThrowsFormat("");
assertThrowsFormat("123123");
assertThrowsFormat("23.106.535/0001 - 47");
assertThrowsFormat(" 23.106.535/0001-47 ");
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.formata("545.586.262-66"), is("545.586.262-66"));
assertThat(Formatador.CPF_CNPJ.formata("54558626266"), is("545.586.262-66"));
assertThat(Formatador.CPF_CNPJ.formata("020.724.833-87"), is("020.724.833-87"));
assertThat(Formatador.CPF_CNPJ.formata("02072483387"), is("020.724.833-87"));
assertThat(Formatador.CPF_CNPJ.formata("188.527.045-31"), is("188.527.045-31"));
assertThat(Formatador.CPF_CNPJ.formata("18852704531"), is("188.527.045-31"));
assertThat(Formatador.CPF_CNPJ.formata("047.486.777-32"), is("047.486.777-32"));
assertThat(Formatador.CPF_CNPJ.formata("04748677732"), is("047.486.777-32"));
assertThrowsFormat("");
assertThrowsFormat("123123");
assertThrowsFormat("123.123.123 -12");
assertThrowsFormat(" 047.486.777-32 ");
}
@Test
public void consegueDesformatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.desformata("50.713.534/0001-33"), is("50713534000133"));
assertThat(Formatador.CPF_CNPJ.desformata("50713534000133"), is("50713534000133"));
assertThat(Formatador.CPF_CNPJ.desformata("72.606.598/0001-78"), is("72606598000178"));
assertThat(Formatador.CPF_CNPJ.desformata("72606598000178"), is("72606598000178"));
assertThat(Formatador.CPF_CNPJ.desformata("23.106.535/0001-47"), is("23106535000147"));
assertThat(Formatador.CPF_CNPJ.desformata("23106535000147"), is("23106535000147"));
assertThrowsDesformat("");
assertThrowsDesformat("123123");
assertThrowsDesformat("123.123.123 -12");
assertThrowsDesformat(" 047.486.777-32 ");
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.desformata("545.586.262-66"), is("54558626266"));
assertThat(Formatador.CPF_CNPJ.desformata("54558626266"), is("54558626266"));
assertThat(Formatador.CPF_CNPJ.desformata("020.724.833-87"), is("02072483387"));
assertThat(Formatador.CPF_CNPJ.desformata("02072483387"), is("02072483387"));
assertThat(Formatador.CPF_CNPJ.desformata("188.527.045-31"), is("18852704531"));
assertThat(Formatador.CPF_CNPJ.desformata("18852704531"), is("18852704531"));
assertThat(Formatador.CPF_CNPJ.desformata("047.486.777-32"), is("04748677732"));
assertThat(Formatador.CPF_CNPJ.desformata("04748677732"), is("04748677732"));
assertThrowsDesformat("");
assertThrowsDesformat("123123");
assertThrowsDesformat("123.123.123 -12");
assertThrowsDesformat(" 047.486.777-32 ");
}
@Test
public void consegueDizerSeEstaFormatado() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.estaFormatado("72.606.598/0001-78"), is(true));
assertThat(Formatador.CPF_CNPJ.estaFormatado("72606598000178"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado("23.106.535/0001-47"), is(true));
assertThat(Formatador.CPF_CNPJ.estaFormatado("23106535000147"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado("047486"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado(""), is(false));
try {
Formatador.CPF_CNPJ.estaFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.estaFormatado("545.586.262-66"), is(true));
assertThat(Formatador.CPF_CNPJ.estaFormatado("54558626266"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado("020.724.833-87"), is(true));
assertThat(Formatador.CPF_CNPJ.estaFormatado("02072483387"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado("188.527.045-31"), is(true));
assertThat(Formatador.CPF_CNPJ.estaFormatado("18852704531"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado("047.486.777-32"), is(true));
assertThat(Formatador.CPF_CNPJ.estaFormatado("04748677732"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado("047486"), is(false));
assertThat(Formatador.CPF_CNPJ.estaFormatado(""), is(false));
try {
Formatador.CPF_CNPJ.estaFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSePodeFormatar() {
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.podeSerFormatado("23.106.535/0001-47"), is(false));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado("23106535000147"), is(true));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado("020"), is(false));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado(""), is(false));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado(null), is(false));
// Gerados automaticamente para testes
assertThat(Formatador.CPF_CNPJ.podeSerFormatado("545.586.262-66"), is(false));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado("54558626266"), is(true));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado("020"), is(false));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado(""), is(false));
assertThat(Formatador.CPF_CNPJ.podeSerFormatado(null), is(false));
}
private void assertThrowsFormat(String valor) {
try {
Formatador.CPF_CNPJ.formata(valor);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
private void assertThrowsDesformat(String valor) {
try {
Formatador.CPF_CNPJ.desformata(valor);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorLinhaDigitavel.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorLinhaDigitavel {
@Test
public void consegueFormatarEDesformatar() {
assertThat(Formatador.LINHA_DIGITAVEL.desformata("812345678901812345678901812345678901812345678901"),
is("81234567890812345678908123456789081234567890"));
final String original = "23790123016000000005325000456704964680000013580";
final String boleto = Formatador.LINHA_DIGITAVEL.desformata(original);
final String linhaDigitavel = Formatador.LINHA_DIGITAVEL.formata(boleto);
assertThat(linhaDigitavel, equalTo(original));
}
@Test
public void consegueDizerSeEstaFormatado() {
assertThat(Formatador.LINHA_DIGITAVEL.estaFormatado("81234567890812345678908123456789081234567890"), is(false));
assertThat(Formatador.LINHA_DIGITAVEL.estaFormatado("812345678901 812345678901 812345678901 812345678901"), is(true));
assertThat(Formatador.LINHA_DIGITAVEL.estaFormatado("23790.12301 60000.000053 25000.456704 9 64680000013580"), is(true));
}
@Test
public void consegueDizerSePodeFormatar() {
assertThat(Formatador.LINHA_DIGITAVEL.podeSerFormatado("8123456789081234567890812345678908123456"), is(false));
assertThat(Formatador.LINHA_DIGITAVEL.podeSerFormatado("23790.1230 60000.00005 25000.45670 9 64680000013580"), is(true));
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorTelefone.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorTelefone {
@Test
public void consegueFormatar() {
assertThat(Formatador.TELEFONE.formata("1112345678"),
is("(11) 1234-5678"));
assertThat(Formatador.TELEFONE.formata("11123456789"),
is("(11) 12345-6789"));
try {
Formatador.TELEFONE.formata(null);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDesformatar() {
assertThat(Formatador.TELEFONE.desformata("(11) 1234-5678"),
is("1112345678"));
assertThat(Formatador.TELEFONE.desformata("(11) 12345-6789"),
is("11123456789"));
try {
Formatador.TELEFONE.desformata(null);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSeEstaFormatado() {
assertThat(Formatador.TELEFONE.estaFormatado("(11) 1234-5678"), is(true));
assertThat(Formatador.TELEFONE.estaFormatado("(11) 12345-6789"), is(true));
assertThat(Formatador.TELEFONE.estaFormatado("(11) 1234-56789"), is(false));
assertThat(Formatador.TELEFONE.estaFormatado("1112345678"), is(false));
assertThat(Formatador.TELEFONE.estaFormatado("11123456789"), is(false));
try {
Formatador.TELEFONE.estaFormatado(null);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSePodeFormatar() {
assertThat(Formatador.TELEFONE.podeSerFormatado("11"), is(false));
assertThat(Formatador.TELEFONE.podeSerFormatado("111234567890"), is(false));
assertThat(Formatador.TELEFONE.podeSerFormatado("1112345678"), is(true));
assertThat(Formatador.TELEFONE.podeSerFormatado("11123456789"), is(true));
try {
Formatador.TELEFONE.podeSerFormatado(null);
fail("Deveria ter jogado exceção!!!");
} catch (IllegalArgumentException e) {
}
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorValor.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.formatador.Formatador;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class TesteFormatadorValor {
@Test
public void consegueFormatar() {
assertThat(Formatador.VALOR.formata("1.00"), is("1,00"));
assertThat(Formatador.VALOR.formata("1.0"), is("1,00"));
assertThat(Formatador.VALOR.formata("1"), is("1,00"));
assertThat(Formatador.VALOR.formata("1000"), is("1.000,00"));
assertThat(Formatador.VALOR.formata("1.23"), is("1,23"));
assertThat(Formatador.VALOR.formata("1.233"), is("1,23"));
assertThat(Formatador.VALOR.formata("1.01"), is("1,01"));
assertThat(Formatador.VALOR.formata("1.1"), is("1,10"));
assertThat(Formatador.VALOR.formata("1234.56"), is("1.234,56"));
assertThat(Formatador.VALOR.formata("1234.5"), is("1.234,50"));
}
@Test
public void consegueFormatarComSimbolo() {
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1.00"), is("R$ 1,00"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1.0"), is("R$ 1,00"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1"), is("R$ 1,00"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1000"), is("R$ 1.000,00"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1.23"), is("R$ 1,23"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1.233"), is("R$ 1,23"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1.01"), is("R$ 1,01"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1.1"), is("R$ 1,10"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1234.56"), is("R$ 1.234,56"));
assertThat(Formatador.VALOR_COM_SIMBOLO.formata("1234.5"), is("R$ 1.234,50"));
}
@Test
public void consegueDesformatar() {
assertThat(Formatador.VALOR.desformata("1,00"), is("1.00"));
assertThat(Formatador.VALOR.desformata("1.234,10"), is("1234.10"));
assertThat(Formatador.VALOR.desformata("0,10"), is("0.10"));
}
@Test
public void consegueDesformatarComSimbolo() {
assertThat(Formatador.VALOR_COM_SIMBOLO.desformata("R$ 1,00"), is("1.00"));
assertThat(Formatador.VALOR_COM_SIMBOLO.desformata("R$ 1.234,10"), is("1234.10"));
assertThat(Formatador.VALOR_COM_SIMBOLO.desformata("R$ 0,10"), is("0.10"));
}
@Test
public void consegueDizerSeEstaFormatado() {
assertThat(Formatador.VALOR.estaFormatado("1,00"), is(true));
assertThat(Formatador.VALOR.estaFormatado("1.234,10"), is(true));
assertThat(Formatador.VALOR.estaFormatado("1.34,10"), is(false));
try {
Formatador.VALOR.estaFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
@Test
public void consegueDizerSePodeFormatar() {
assertThat(Formatador.VALOR.podeSerFormatado("1.00"), is(true));
assertThat(Formatador.VALOR.podeSerFormatado("10"), is(true));
assertThat(Formatador.VALOR.podeSerFormatado("10.1"), is(true));
assertThat(Formatador.VALOR.podeSerFormatado("10,10"), is(false));
try {
Formatador.VALOR.podeSerFormatado(null);
fail("Should have thrown!!!");
} catch (IllegalArgumentException e) {
}
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/TesteValidadores.java
================================================
package br.com.concrete.canarinho.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.validator.Validador;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.is;
@RunWith(AndroidJUnit4.class)
public class TesteValidadores {
@Test
public void consegueValidarCPF() {
// Gerados automaticamente para testes
assertThat(Validador.CPF.ehValido("545.586.262-66"), is(true));
assertThat(Validador.CPF.ehValido("655.274.921-02"), is(true));
assertThat(Validador.CPF.ehValido("020.724.833-87"), is(true));
assertThat(Validador.CPF.ehValido("188.527.045-31"), is(true));
assertThat(Validador.CPF.ehValido("047.486.777-32"), is(true));
assertThat(Validador.CPF.ehValido("54558626266"), is(true));
assertThat(Validador.CPF.ehValido("65527492102"), is(true));
assertThat(Validador.CPF.ehValido("02072483387"), is(true));
assertThat(Validador.CPF.ehValido("18852704531"), is(true));
assertThat(Validador.CPF.ehValido("04748677732"), is(true));
assertThat(Validador.CPF.ehValido("545.111.262-66"), is(false));
assertThat(Validador.CPF.ehValido("655.111.921-02"), is(false));
assertThat(Validador.CPF.ehValido("020.111.833-87"), is(false));
assertThat(Validador.CPF.ehValido("188.111.045-31"), is(false));
assertThat(Validador.CPF.ehValido("047.111.777-32"), is(false));
assertThat(Validador.CPF.ehValido("54111626266"), is(false));
assertThat(Validador.CPF.ehValido("65111492102"), is(false));
assertThat(Validador.CPF.ehValido("02111483387"), is(false));
assertThat(Validador.CPF.ehValido("18111704531"), is(false));
assertThat(Validador.CPF.ehValido("04111677732"), is(false));
}
@Test
public void consegueValidarCNPJ() {
// Gerados automaticamente para testes
assertThat(Validador.CNPJ.ehValido("50.713.534/0001-33"), is(true));
assertThat(Validador.CNPJ.ehValido("77.135.038/0001-04"), is(true));
assertThat(Validador.CNPJ.ehValido("58.613.246/0001-19"), is(true));
assertThat(Validador.CNPJ.ehValido("50713534000133"), is(true));
assertThat(Validador.CNPJ.ehValido("77135038000104"), is(true));
// Gerados automaticamente para testes
assertThat(Validador.CNPJ.ehValido("50.713.111/0001-33"), is(false));
assertThat(Validador.CNPJ.ehValido("77.135.111/0001-04"), is(false));
assertThat(Validador.CNPJ.ehValido("50713531110133"), is(false));
assertThat(Validador.CNPJ.ehValido("77135031110104"), is(false));
}
@Test
public void consegueValidarBoletoNormal() {
// boleto bradesco
assertThat(Validador.BOLETO
.ehValido("23790.12301 60000.000053 25000.456704 9 64680000013580"), is(true));
// boleto bradesco
assertThat(Validador.BOLETO
.ehValido("23790123016000000005325000456704964680000013580"), is(true));
// boleto banco do brasil
assertThat(Validador.BOLETO
.ehValido("00199.38414 90480.002550 84666.970219 4 64290000007726"), is(true));
// Boleto itau desformatado
assertThat(Validador.BOLETO
.ehValido("34191750090000159091820521070001664890002370000"), is(true));
// Boleto itau
assertThat(Validador.BOLETO
.ehValido("34191.75009 00001.590918 20521.070001 6 64890002370000"), is(true));
// boleto Banestes
assertThat(Validador.BOLETO
.ehValido("02190.00015 05000.000017 23452.021696 2 25230000034000"), is(true));
// boleto Caixa
assertThat(Validador.BOLETO
.ehValido("10491.44338 55119.000002 00000.000141 3 25230000093423"), is(true));
// boleto Sudameris
assertThat(Validador.BOLETO
.ehValido("34790.12001 12345.695006 10502.000002 5 25230000034000"), is(true));
}
@Test
public void consegueValidarBoletoTributoSemSerTaxa() {
assertThat(Validador.BOLETO
.ehValido("848600000015 523301622010 506101307129 620012111220"), is(true));
}
@Test
public void consegueValidarBoletoTributoDeTaxa() {
assertThat(Validador.BOLETO
.ehValido("836600000019 078800481000 998854924516 001265611135"), is(true));
assertThat(Validador.BOLETO
.ehValido("826100000007 265400971429 620232390612 725103150621"), is(true));
}
@Test
public void consegueValidarTelefone() {
assertThat(Validador.TELEFONE.ehValido("1112345678"), is(true));
assertThat(Validador.TELEFONE.ehValido("11123456789"), is(true));
assertThat(Validador.TELEFONE.ehValido("(11) 12345-6789"), is(true));
assertThat(Validador.TELEFONE.ehValido("111234567890"), is(false));
assertThat(Validador.TELEFONE.ehValido("1112345"), is(false));
}
@Test
public void consegueValidarCEP() {
assertThat(Validador.CEP.ehValido("12345678"), is(true));
assertThat(Validador.CEP.ehValido("12345-678"), is(true));
assertThat(Validador.CEP.ehValido("12345-67"), is(false));
assertThat(Validador.CEP.ehValido("1234-678"), is(false));
assertThat(Validador.CEP.ehValido(""), is(false));
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/watcher/BoletoTextWatcherTest.java
================================================
package br.com.concrete.canarinho.test.watcher;
import android.widget.EditText;
import com.google.android.material.textfield.TextInputLayout;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import br.com.concrete.canarinho.sample.ui.activity.MainActivity;
import br.com.concrete.canarinho.sample.ui.model.Watchers;
import br.com.concrete.canarinho.watcher.BoletoBancarioTextWatcher;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
public class BoletoTextWatcherTest {
private BoletoBancarioTextWatcher watcher;
private EditText editText;
@Before
public void setUp() {
final ActivityScenario scenario = ActivityScenario.launch(MainActivity.class);
scenario.onActivity(new ActivityScenario.ActivityAction() {
@Override
public void perform(MainActivity activity) {
final TextInputLayout textInputLayout = new TextInputLayout(activity);
textInputLayout.addView(editText = new EditText(activity));
final Watchers.SampleEventoDeValidacao sampleEventoDeValidacao =
new Watchers.SampleEventoDeValidacao(textInputLayout);
editText.addTextChangedListener(watcher = new BoletoBancarioTextWatcher(sampleEventoDeValidacao));
activity.setContentView(textInputLayout);
}
});
}
@Test
public void typing_canValidateEmptyState() {
editText.append("");
assertThat(editText.getText().toString(), is(""));
assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));
}
@Test
public void typing_canValidateProperCharacters() {
editText.append("1bas2nas3lamsd4");
assertThat(editText.getText().toString(), is("1234"));
assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));
}
@Test
public void deleting_canEmptyEditText() {
editText.append("1234");
assertThat(editText.getText().toString(), is("1234"));
assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));
editText.getEditableText().clear();
assertThat(editText.getText().toString(), is(""));
}
// Teste de regressão
@Test
public void deleting_afterEmptyingEditTextItKeepsValidatingInput() {
editText.append("1234");
assertThat(editText.getText().toString(), is("1234"));
assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));
editText.getEditableText().clear();
assertThat(editText.getText().toString(), is(""));
// menos caracteres que o tamanho inicial para saber qual máscara aplicar
editText.append("$$");
assertThat(editText.getText().toString(), is(""));
}
}
================================================
FILE: sample/src/test/java/br/com/concrete/canarinho/test/watcher/ValorMonetarioWatcherTest.java
================================================
package br.com.concrete.canarinho.test.watcher;
import android.app.Activity;
import android.widget.EditText;
import com.google.android.material.textfield.TextInputLayout;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.robolectric.android.controller.ActivityController;
import br.com.concrete.canarinho.watcher.ValorMonetarioWatcher;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.robolectric.Robolectric.buildActivity;
@RunWith(AndroidJUnit4.class)
public class ValorMonetarioWatcherTest {
private EditText editText;
@Before
public void setUp() {
final ActivityController activityController = buildActivity(Activity.class);
final Activity activity = activityController.create().get();
activityController.start().resume().visible();
TextInputLayout textInputLayout;
activity.setContentView(textInputLayout = new TextInputLayout(activity));
textInputLayout.addView(editText = new EditText(activity));
editText.setText("0,00");
}
@Test
public void watcher_formataOk() {
editText.addTextChangedListener(new ValorMonetarioWatcher());
editText.append("1234567890");
assertThat(editText.getText().toString(), is("12.345.678,90"));
}
@Test
public void watcher_formataOkComSimbolo() {
editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()
.comSimboloReal()
.comMantemZerosAoLimpar()
.build());
editText.append("1234567890");
assertThat(editText.getText().toString(), is("R$ 12.345.678,90"));
}
@Test
public void watcher_canEmptyTextAndKeepZeroes() {
editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()
.comSimboloReal()
.comMantemZerosAoLimpar()
.build());
editText.append("1234567890");
assertThat(editText.getText().toString(), is("R$ 12.345.678,90"));
editText.getText().clear();
assertThat(editText.getText().toString(), is("R$ 0,00"));
editText.getText().append('1');
assertThat(editText.getText().toString(), is("R$ 0,01"));
}
@Test
public void watcher_canEmptyTextWithoutZeroes() {
editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()
.comSimboloReal()
.build());
editText.append("1234567890");
assertThat(editText.getText().toString(), is("R$ 12.345.678,90"));
editText.getText().clear();
assertThat(editText.getText().toString(), is(""));
editText.getText().append('1');
assertThat(editText.getText().toString(), is("R$ 0,01"));
}
}
================================================
FILE: settings.gradle
================================================
include ':sample', ':canarinho'
================================================
FILE: tools/linters/checkstyle/checkstyle.xml
================================================
================================================
FILE: tools/linters/checkstyle/suppressions.xml
================================================
================================================
FILE: tools/linters/linters.gradle
================================================
apply plugin: 'checkstyle'
apply plugin: 'pmd'
check.dependsOn 'checkstyle', 'pmd'
checkstyle {
toolVersion '8.17'
configFile file("$rootDir/tools/linters/checkstyle/checkstyle.xml")
configProperties.checkstyleSuppressionsPath = file(
"$rootDir/tools/linters/checkstyle/suppressions.xml"
).absolutePath
}
pmd {
toolVersion = '6.11.0'
ignoreFailures = false
ruleSetFiles = files("$rootDir/tools/linters/pmd/pmd-ruleset.xml")
ruleSets = []
}
task checkstyle(type: Checkstyle, group: 'verification') {
source 'src'
include '**/*.java'
exclude '**/gen/**'
exclude '**/test/**'
exclude '**/androidTest/**'
exclude '**/R.java'
exclude '**/BuildConfig.java'
classpath = files()
}
task pmd(type: Pmd, group: 'verification') {
source 'src'
include '**/*.java'
exclude('**/gen/**', '**/debug/**')
reports {
xml.enabled = true
html.enabled = true
xml.destination file("$projectDir/build/reports/pmd/pmd.xml")
html.destination file("$projectDir/build/reports/pmd/pmd.html")
}
}
================================================
FILE: tools/linters/pmd/pmd-ruleset.xml
================================================
Custom ruleset for Android application
.*/R.java
.*/gen/.*
================================================
FILE: tools/publish.gradle
================================================
apply plugin: 'maven-publish'
afterEvaluate {
publishing {
publications {
stable(MavenPublication) {
groupId 'br.com.concrete'
artifactId 'canarinho'
version '2.0.3'
from components.release
}
}
repositories {
maven {
url = String.valueOf(System.getenv("GPR_URL"))
credentials {
username = String.valueOf(System.getenv("GPR_USER"))
password = String.valueOf(System.getenv("GPR_PASSWORD"))
}
}
}
}
}