É um aplicativo de criptografia e descriptografia de texto. O usuário digita um texto, o app envia para uma API REST, e recebe de volta o texto criptografado (ou descriptografado).
main.dartvoid main() {
runApp(const App());
}
Todo app Flutter começa aqui. runApp() recebe o widget raiz e monta a árvore de widgets na tela.
app.dartclass App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(...), // tema escuro com cores douradas
home: const CryptoPage(),
);
}
}
MaterialApp configura o app inteiro (tema, navegação, título).CryptoPage.O projeto segue Clean Architecture dividida em 3 camadas:
lib/
├── core/ → Utilitários globais (rede, erros, constantes)
└── features/
└── crypto/
├── data/ → Implementação concreta (API, models)
├── domain/ → Regras de negócio puras (entidades, contratos, use cases)
└── presentation/ → UI e gerenciamento de estado (Cubit/Bloc)
A ideia central: cada camada só conhece a camada acima dela. A UI não sabe como a API funciona, e a lógica de negócio não sabe que Flutter existe.
lib/core/constants/api_constants.dart)class ApiConstants {
static const String baseUrl = 'https://hub-api.sistemavirtual.com.br/api';
static const String encryptEndpoint = '/Crypto/encrypt';
static const String decryptEndpoint = '/Crypto/decrypt';
static const Duration timeout = Duration(seconds: 30);
}
Centraliza URLs e configurações. Se a API mudar, você altera só aqui.
O projeto usa dois níveis de erro:
Exceptions (camada data — erros técnicos em lib/core/errors/exceptions.dart):
ServerException — servidor respondeu com erro HTTPNetworkException — sem internet ou timeoutFailures (camada domain — erros de negócio em lib/core/errors/failures.dart):
sealed class Failure {
final String message;
}
ServerFailure — erro do servidor com código HTTPNoConnectionFailure — sem internetTimeoutFailure — requisição demorou demaisUnknownFailure — erro inesperadoA palavra-chave sealed garante que o compilador sabe todos os tipos possíveis — útil no switch.
ConnectivityService (lib/core/network/connectivity_service.dart) — verifica se tem internet antes de fazer requisição:
Future<bool> hasConnection() async {
final results = await _connectivity.checkConnectivity();
return results.any((result) => result != ConnectivityResult.none);
}
DioClient (lib/core/network/dio_client.dart) — cria uma instância do Dio (cliente HTTP) com configurações padrão (timeout, headers, base URL).
ClipboardHelper (lib/core/utils/clipboard_helper.dart) — copia texto para a área de transferência do dispositivo.
lib/features/crypto/domain/entities/crypto_result.dart)class CryptoResult {
final String value; // o texto criptografado ou descriptografado
}
Entidade pura — não sabe nada sobre JSON, API, ou Flutter.
lib/features/crypto/domain/repositories/crypto_repository.dart)abstract class CryptoRepository {
Future<Either<Failure, CryptoResult>> encrypt(String text);
Future<Either<Failure, CryptoResult>> decrypt(String bytes);
}
Either<Failure, CryptoResult> vem do pacote dartz. É um tipo funcional que representa:
Isso elimina try/catch espalhado pelo código. Quem chama o método recebe um "ou deu certo, ou deu errado" de forma explícita.
lib/features/crypto/domain/usecases/)class EncryptUseCase {
final CryptoRepository _repository;
Future<Either<Failure, CryptoResult>> call(String text) {
return _repository.encrypt(text);
}
}
Cada Use Case faz uma coisa só. Aqui é só um repasse, mas em apps maiores teria validações, combinação de repositórios, etc.
lib/features/crypto/data/models/)Representam o JSON que vai/vem da API:
// Request: {"text": "Carlos"}
class EncryptRequestModel {
Map<String, dynamic> toJson() => {'text': text};
}
// Response: {"bytes": "tsbZ4eHU"}
class EncryptResponseModel {
factory EncryptResponseModel.fromJson(Map<String, dynamic> json) {
return EncryptResponseModel(bytes: json['bytes'] as String);
}
}
toJson() converte Dart → JSON (para enviar à API)fromJson() converte JSON → Dart (para receber da API)lib/features/crypto/data/datasources/crypto_remote_data_source.dart)class CryptoRemoteDataSourceImpl implements CryptoRemoteDataSource {
final Dio _dio;
Future<EncryptResponseModel> encrypt(EncryptRequestModel request) async {
final response = await _dio.post(endpoint, data: request.toJson());
return EncryptResponseModel.fromJson(response.data);
}
}
Faz a chamada HTTP real. Se der erro, lança ServerException ou NetworkException.
lib/features/crypto/data/repositories/crypto_repository_impl.dart)class CryptoRepositoryImpl implements CryptoRepository {
Future<Either<Failure, CryptoResult>> encrypt(String text) async {
// 1. Verifica internet
if (!await _connectivityService.hasConnection()) {
return const Left(NoConnectionFailure());
}
// 2. Tenta chamar a API
try {
final response = await _dataSource.encrypt(...);
return Right(CryptoResult(response.bytes)); // sucesso!
} on ServerException catch (e) {
return Left(ServerFailure(...)); // erro do servidor
} on NetworkException catch (e) {
return Left(TimeoutFailure()); // timeout
}
}
}
Aqui é onde Exceptions viram Failures. A camada de apresentação nunca vê exceptions — só Failures com mensagens amigáveis.
lib/features/crypto/presentation/cubit/crypto_state.dart)sealed class CryptoState {}
class CryptoInitial extends CryptoState {} // tela limpa
class CryptoLoading extends CryptoState {} // carregando...
class CryptoSuccess extends CryptoState { // resultado pronto
final String result;
}
class CryptoFailure extends CryptoState { // erro
final String message;
final bool canRetry;
}
Cada estado possível da tela é uma classe. O sealed garante que o compilador verifica se você tratou todos os casos.
lib/features/crypto/presentation/cubit/crypto_cubit.dart)class CryptoCubit extends Cubit<CryptoState> {
Future<void> encrypt(String text) async {
emit(const CryptoLoading()); // mostra loading
final result = await _encryptUseCase(text);
result.fold(
(failure) => emit(CryptoFailure(...)), // erro
(success) => emit(CryptoSuccess(...)), // sucesso
);
}
}
O Cubit é uma versão simplificada do Bloc. Ele:
Funcionalidades extras:
retry() — repete a última operação (útil quando dá erro de rede)clear() — reseta tudo para o estado iniciallib/features/crypto/presentation/pages/crypto_page.dart)Monta a estrutura com:
AppBar com gradiente dourado e abas (Criptografar / Descriptografar)TabBarView com duas instâncias de CryptoTabCryptoCubit (estados independentes)A montagem das dependências acontece aqui (manual, sem injeção de dependência):
CryptoCubit _buildCubit() {
final dio = DioClient.createDio();
final dataSource = CryptoRemoteDataSourceImpl(dio);
final repository = CryptoRepositoryImpl(...);
return CryptoCubit(
encryptUseCase: EncryptUseCase(repository),
decryptUseCase: DecryptUseCase(repository),
);
}
CryptoTab (lib/features/crypto/presentation/widgets/crypto_tab.dart)É um StatefulWidget porque precisa de um TextEditingController. Contém:
ResultCard quando sucesso ou ErrorBanner quando erroUsa BlocBuilder para reagir ao estado:
BlocBuilder<CryptoCubit, CryptoState>(
builder: (context, state) {
if (state is CryptoSuccess) ResultCard(result: state.result);
if (state is CryptoFailure) ErrorBanner(message: state.message);
},
);
ClipboardHelperUsuário digita "Carlos" → clica "Criptografar"
↓
CryptoTab._submit() → CryptoCubit.encrypt("Carlos")
↓
Cubit emite CryptoLoading → UI mostra spinner
↓
EncryptUseCase.call("Carlos") → CryptoRepository.encrypt("Carlos")
↓
CryptoRepositoryImpl: verifica internet → chama DataSource
↓
DataSource: POST /Crypto/encrypt {"text": "Carlos"} → recebe {"bytes": "tsbZ4eHU"}
↓
Repository retorna Right(CryptoResult("tsbZ4eHU"))
↓
Cubit emite CryptoSuccess("tsbZ4eHU") → UI mostra ResultCard
| Conceito | Onde aparece | O que faz |
|---|---|---|
sealed class |
States, Failures | Garante que todos os subtipos são conhecidos em compile-time |
final class |
States | Impede herança adicional |
const constructors |
Em quase tudo | Otimiza memória — objetos imutáveis reutilizáveis |
Either<L, R> |
Repository, Use Cases | Tratamento funcional de erros (sem throw/catch) |
part of / part |
Cubit + State | Divide um arquivo lógico em dois físicos |
abstract class |
Repository, DataSource | Define contratos (interfaces) |
factory constructor |
Models | Cria instância a partir de JSON |
Pattern matching (switch) |
_mapFailureMessage |
Dart 3 — match por tipo com desestruturação |
StatelessWidget |
App, CryptoPage, ResultCard | Widget sem estado interno mutável |
StatefulWidget |
CryptoTab | Widget com estado interno (TextEditingController) |
| Pacote | Função |
|---|---|
flutter_bloc |
Gerenciamento de estado (Cubit) |
dio |
Cliente HTTP (requisições à API) |
connectivity_plus |
Verificar conexão com internet |
dartz |
Tipos funcionais (Either) |
flutter_launcher_icons |
Gerar ícones do app |
| Pacote | Função |
|---|---|
bloc_test |
Testar Cubits/Blocs |
mocktail |
Criar mocks para testes |
glados |
Property-based testing |
flutter_lints |
Regras de lint/estilo |
O app segue o conceito "Premium Gold & Silver Minimalist":
| Elemento | Cor | Hex |
|---|---|---|
| Fundo principal | Deep Charcoal | #2C343C |
| Cards/Inputs | Midnight Blue-Grey | #1E252B |
| Bordas/Divisores | Grid Slate | #3D4852 |
| Destaque principal | Gold Main | #D4AF37 |
| Brilho/Gradiente | Gold Light | #F1D592 |
| Texto secundário | Silver/Platinum | #C0C0C0 |
| Sucesso | Green | #00C853 |
| Erro | Red | #FF3D00 |
# Instalar dependências
flutter pub get
# Rodar no dispositivo/emulador
flutter run
# Rodar testes
flutter test
lib/features/nova_feature/data/, domain/, presentation/domain/ — defina a entidade e o contrato do repositóriodata/ — models, datasource, repositorypresentation/ — cubit/state e widgets