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!


Please follow and like us:
error0
fb-share-icon
Tweet 20
fb-share-icon20