Construindo um App para Condomínios com Flutter 3.41: UI e Estrutura Inicial (Parte 1)
Este artigo irá mostrar como criar um projeto voltado a Moradores de Condomínios Online, usando o Flutter (versão 3.41+) como Framework e sua linguagem Dart. A partir disso, irei demonstrar os primeiros passos para a criação da tela inicial, focando em uma experiência de usuário (UX) moderna com Material 3.
Gerenciar o dia a dia de um condomínio (reservas, portaria, boletos, avisos) exige um aplicativo com navegação clara e acesso rápido às ferramentas mais usadas. Vamos construir essa fundação agora!
🚀 Passo 1: Configuração Inicial e Tema
Crie o seu projeto no terminal:
flutter create condo_app cd condo_app
Para garantir que o aplicativo tenha um visual moderno e tipografia agradável, adicione o pacote google_fonts no seu pubspec.yaml:
dependencies:
flutter:
sdk: flutter
google_fonts: ^6.1.0
(Rode flutter pub get em seguida).
Vamos configurar o Material 3 no main.dart. Para aplicativos de moradia e segurança, cores como Azul ou Índigo transmitem confiança.
Atualize o lib/main.dart:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'modules/base/main_layout.dart';
void main() {
runApp(const CondoApp());
}
class CondoApp extends StatelessWidget {
const CondoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Condomínio Online',
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
textTheme: GoogleFonts.interTextTheme(Theme.of(context).textTheme),
),
home: const MainLayout(),
debugShowCheckedModeBanner: false,
);
}
}
🧭 Passo 2: O Menu Inferior (Navigation Bar)
O Material 3 introduziu o NavigationBar, que é mais robusto e elegante que o antigo BottomNavigationBar. Ele será nossa base para navegar entre as três áreas principais: Home, Histórico e Perfil.
Crie lib/modules/base/main_layout.dart:
import 'package:flutter/material.dart';
import '../home/home_screen.dart';
class MainLayout extends StatefulWidget {
const MainLayout({super.key});
@override
State<MainLayout> createState() => _MainLayoutState();
}
class _MainLayoutState extends State<MainLayout> {
int _currentIndex = 0;
// Lista de telas que serão renderizadas
final List<Widget> _screens = [
const HomeScreen(),
const Center(child: Text('Histórico (Em breve)')),
const Center(child: Text('Perfil (Em breve)')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_currentIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.history_outlined),
selectedIcon: Icon(Icons.history),
label: 'Histórico',
),
NavigationDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: 'Perfil',
),
],
),
);
}
}
🏢 Passo 3: A Tela Inicial (Home Screen)
Aqui é onde a mágica acontece. A tela precisa ter uma Barra de Busca, uma área de Destaques (Acesso Rápido) e um Grid com todos os Recursos do condomínio.
Vamos usar um CustomScrollView para permitir que a busca e os destaques rolem junto com a tela de forma fluida.
Crie lib/modules/home/home_screen.dart:
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: SafeArea(
child: CustomScrollView(
slivers: [
// 1. Cabeçalho de Boas-vindas
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Olá, Morador', style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold)),
Text('Apto 402 - Bloco B', style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey[600])),
],
),
),
),
// 2. Barra de Busca (SearchBar do Material 3)
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: SearchBar(
hintText: 'Buscar recursos ou avisos...',
leading: Icon(Icons.search),
elevation: MaterialStatePropertyAll(1),
),
),
),
// 3. Área de Destaques
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 24.0, bottom: 8.0, left: 16.0),
child: Text('Acesso Rápido', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
),
),
SliverToBoxAdapter(
child: SizedBox(
height: 100,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
_buildHighlightCard(context, icon: Icons.videocam, title: 'Câmeras', color: Colors.blue),
_buildHighlightCard(context, icon: Icons.key, title: 'Acionamentos', color: Colors.orange),
_buildHighlightCard(context, icon: Icons.insert_invitation, title: 'Convites', color: Colors.green),
],
),
),
),
// 4. Grid de Recursos Completos
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 24.0, bottom: 16.0, left: 16.0),
child: Text('Recursos do Condomínio', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 0.85, // Ajusta a altura dos itens do grid
),
delegate: SliverChildListDelegate([
_buildResourceItem(context, icon: Icons.app_registration, title: 'Cadastro'),
_buildResourceItem(context, icon: Icons.history_toggle_off, title: 'Acessos'),
_buildResourceItem(context, icon: Icons.calendar_month, title: 'Agendamento'),
_buildResourceItem(context, icon: Icons.campaign, title: 'Mural'),
_buildResourceItem(context, icon: Icons.support_agent, title: 'Ocorrências'),
_buildResourceItem(context, icon: Icons.qr_code_2, title: 'QR Code'),
_buildResourceItem(context, icon: Icons.local_shipping, title: 'Entregas'),
_buildResourceItem(context, icon: Icons.receipt_long, title: 'Boletos'),
_buildResourceItem(context, icon: Icons.find_in_page, title: 'Perdidos'),
_buildResourceItem(context, icon: Icons.record_voice_over, title: 'Manifestações'),
]),
),
),
// Espaçamento no final
const SliverToBoxAdapter(child: SizedBox(height: 32)),
],
),
),
);
}
// Widget Auxiliar para os Cards de Destaque
Widget _buildHighlightCard(BuildContext context, {required IconData icon, required String title, required Color color}) {
return Container(
width: 120,
margin: const EdgeInsets.only(right: 12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 32),
const SizedBox(height: 8),
Text(title, style: TextStyle(color: color, fontWeight: FontWeight.bold)),
],
),
),
),
);
}
// Widget Auxiliar para os Itens do Grid
Widget _buildResourceItem(BuildContext context, {required IconData icon, required String title}) {
return Column(
children: [
Expanded(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {},
child: Icon(icon, size: 32, color: Theme.of(context).colorScheme.primary),
),
),
),
),
const SizedBox(height: 8),
Text(
title,
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
);
}
}
✅ Conclusão
Neste primeiro artigo, estabelecemos a espinha dorsal visual do nosso aplicativo para condomínios.
Utilizando o Flutter 3.41+, tiramos proveito nativo do Material 3, criando uma interface limpa, com uma tipografia elegante e componentes modernos como o SearchBar e o NavigationBar. A separação entre “Acesso Rápido” e o grid completo de “Recursos” garante que o morador encontre o que precisa em poucos segundos, seja abrir o portão (Acionamentos) ou checar uma Encomenda.
Na próxima parte, começaremos a dar vida a essas funções integrando a lógica de estado (BLoC) e desenhando as telas internas!