Gallery App dengan Fitur Favorit Zapp.run Flutter

Gallery App dengan Fitur Favorit

📱 Gambaran Umum
Aplikasi Flutter ini adalah contoh implementasi komponen UI/UX modern dengan fitur responsif, manajemen state, dan navigasi multi-halaman. Aplikasi ini menampilkan galeri gambar dengan kemampuan menandai gambar favorit.






 🎯 Fitur Utama

 1. Antarmuka Responsif
- Beradaptasi otomatis dengan ukuran layar berbeda (mobile & tablet)
- Menggunakan **LayoutBuilder** untuk mendeteksi constraints
- Tampilan berbeda untuk layout sempit (single column) dan lebar (grid view)

 2. Manajemen State
- Menggunakan **StatefulWidget** untuk UI yang dinamis
- Implementasi **setState()** untuk update tampilan
- Contoh perbedaan Stateless vs Stateful Widget

 3. Navigasi Multi-Halaman
- **BottomNavigationBar** dengan 3 halaman: Home, Favorites, Profile
- **Navigator.push()** dan **Navigator.pop()** untuk navigasi ke halaman detail
- Transisi antar halaman yang smooth

 4. Dynamic Theme
- Toggle tema gelap/terang dengan IconButton di AppBar
- Menggunakan **ThemeMode** untuk mengontrol tema aplikasi
- Terintegrasi dengan ThemeData light dan dark

 5. Fitur Favorit
- Sistem manajemen favorit dengan class **FavoritesManager**
- Bisa menambah/menghapus gambar dari daftar favorit
- Notifikasi Snackbar saat menambah favorit
- Halaman terpisah untuk melihat daftar favorit

 6. Layout Komponen
- Implementasi **Column** dan **Row** untuk tata letak
- Penggunaan **Expanded** dan **Flexible** untuk pembagian ruang
- **GridView** untuk menampilkan gambar dalam grid

 🏗️ Struktur Kode


import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ThemeMode _themeMode = ThemeMode.light;

  void _toggleTheme(bool isDark) {
    setState(() {
      _themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gallery App with Favorites',
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      themeMode: _themeMode,
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int _selectedIndex = 0;
  final List<Widget> _pages = [
    const HomeContent(),
    const FavoritesScreen(),
    const ProfileScreen(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gallery App'),
        actions: [
          IconButton(
            icon: const Icon(Icons.brightness_6),
            onPressed: () {
              final myAppState = context.findAncestorStateOfType<_MyAppState>();
              if (myAppState != null) {
                final isDark = Theme.of(context).brightness == Brightness.dark;
                myAppState._toggleTheme(!isDark);
              }
            },
          ),
        ],
      ),
      body: _pages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.favorite),
            label: 'Favorites',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  const HomeContent({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Column(
          children: [
            // Header
            Container(
              height: constraints.maxHeight * 0.15,
              color: Colors.blue,
              child: Center(
                child: Text(
                  'Gallery App',
                  style: TextStyle(
                    fontSize: constraints.maxWidth > 600 ? 24 : 18,
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            
            // Konten utama dengan Row/Column yang responsif
            Expanded(
              child: constraints.maxWidth > 600
                  ? _buildWideLayout(context)
                  : _buildNarrowLayout(context),
            ),
            
            // Footer
            Container(
              height: constraints.maxHeight * 0.1,
              color: Colors.grey[300],
              child: Center(
                child: Text(
                  'Footer Section © 2023',
                  style: TextStyle(
                    fontSize: constraints.maxWidth > 600 ? 16 : 12,
                  ),
                ),
              ),
            ),
          ],
        );
      },
    );
  }

  Widget _buildNarrowLayout(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: [
          const SizedBox(height: 16),
          const Text(
            'Image Gallery',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          _buildImageCard(
            imageUrl: 'https://picsum.photos/300/200?random=1',
            title: 'Pemandangan 1',
          ),
          _buildImageCard(
            imageUrl: 'https://picsum.photos/300/200?random=2',
            title: 'Pemandangan 2',
          ),
          _buildImageCard(
            imageUrl: 'https://picsum.photos/300/200?random=3',
            title: 'Pemandangan 3',
          ),
          _buildImageCard(
            imageUrl: 'https://picsum.photos/300/200?random=4',
            title: 'Pemandangan 4',
          ),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const DetailScreen()),
              );
            },
            child: const Text('Lihat Detail'),
          ),
          const SizedBox(height: 16),
        ],
      ),
    );
  }

  Widget _buildWideLayout(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Row(
        children: [
          Expanded(
            flex: 2,
            child: Column(
              children: [
                const Text(
                  'Image Gallery',
                  style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 16),
                Expanded(
                  child: GridView.count(
                    crossAxisCount: 2,
                    children: [
                      _buildImageCard(
                        imageUrl: 'https://picsum.photos/300/200?random=1',
                        title: 'Pemandangan 1',
                      ),
                      _buildImageCard(
                        imageUrl: 'https://picsum.photos/300/200?random=2',
                        title: 'Pemandangan 2',
                      ),
                      _buildImageCard(
                        imageUrl: 'https://picsum.photos/300/200?random=3',
                        title: 'Pemandangan 3',
                      ),
                      _buildImageCard(
                        imageUrl: 'https://picsum.photos/300/200?random=4',
                        title: 'Pemandangan 4',
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(width: 16),
          Expanded(
            flex: 1,
            child: Column(
              children: [
                const Text(
                  'Info',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 16),
                const Text('Ini adalah aplikasi galeri gambar dengan fitur favorit.'),
                const SizedBox(height: 16),
                ElevatedButton(
                  onPressed: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) => const DetailScreen()),
                    );
                  },
                  child: const Text('Lihat Detail'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildImageCard({required String imageUrl, required String title}) {
    return Card(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.network(
            imageUrl,
            height: 150,
            width: double.infinity,
            fit: BoxFit.cover,
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    title,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.favorite_border),
                  onPressed: () {
                    // Akan menambahkan ke favorit
                    FavoritesManager.addToFavorites(imageUrl, title);
                    ScaffoldMessenger.of(KeyManager.scaffoldKey.currentContext!).showSnackBar(
                      SnackBar(content: Text('$title ditambahkan ke favorit')),
                    );
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail Screen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.network(
              'https://picsum.photos/300/200?random=5',
              width: 300,
              height: 200,
              fit: BoxFit.cover,
            ),
            const SizedBox(height: 20),
            const Text(
              'Ini adalah halaman detail dengan gambar',
              style: TextStyle(fontSize: 20),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Kembali'),
            ),
          ],
        ),
      ),
    );
  }
}

class FavoritesScreen extends StatefulWidget {
  const FavoritesScreen({super.key});

  @override
  State<FavoritesScreen> createState() => _FavoritesScreenState();
}

class _FavoritesScreenState extends State<FavoritesScreen> {
  List<Map<String, String>> favoriteItems = [];

  @override
  void initState() {
    super.initState();
    favoriteItems = FavoritesManager.getFavorites();
  }

  void _removeFavorite(int index) {
    setState(() {
      FavoritesManager.removeFromFavorites(favoriteItems[index]['imageUrl']!);
      favoriteItems.removeAt(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: favoriteItems.isEmpty
          ? const Center(
              child: Text('Belum ada gambar favorit'),
            )
          : ListView.builder(
              itemCount: favoriteItems.length,
              itemBuilder: (context, index) {
                return ListTile(
                  leading: Image.network(
                    favoriteItems[index]['imageUrl']!,
                    width: 50,
                    height: 50,
                    fit: BoxFit.cover,
                  ),
                  title: Text(favoriteItems[index]['title']!),
                  trailing: IconButton(
                    icon: const Icon(Icons.favorite, color: Colors.red),
                    onPressed: () => _removeFavorite(index),
                  ),
                );
              },
            ),
    );
  }
}

class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const CircleAvatar(
            radius: 50,
            backgroundImage: NetworkImage('https://via.placeholder.com/100'),
          ),
          const SizedBox(height: 16),
          const Text(
            'John Doe',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'johndoe@example.com',
            style: TextStyle(fontSize: 16),
          ),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: () {
              // Aksi untuk mengedit profil
            },
            child: const Text('Edit Profil'),
          ),
        ],
      ),
    );
  }
}

class FavoritesManager {
  static List<Map<String, String>> _favorites = [];

  static void addToFavorites(String imageUrl, String title) {
    // Cek apakah sudah ada di favorit
    if (!_favorites.any((item) => item['imageUrl'] == imageUrl)) {
      _favorites.add({
        'imageUrl': imageUrl,
        'title': title,
      });
    }
  }

  static void removeFromFavorites(String imageUrl) {
    _favorites.removeWhere((item) => item['imageUrl'] == imageUrl);
  }

  static List<Map<String, String>> getFavorites() {
    return _favorites;
  }
}

class KeyManager {
  static final GlobalKey<ScaffoldMessengerState> scaffoldKey = GlobalKey<ScaffoldMessengerState>();
}



 Widget Utama:
- **MyApp**: Root widget dengan toggle theme
- **HomeScreen**: Scaffold utama dengan BottomNavigationBar
- **HomeContent**: Halaman beranda dengan galeri gambar
- **FavoritesScreen**: Halaman daftar favorit
- **ProfileScreen**: Halaman profil pengguna
- **DetailScreen**: Halaman detail dengan navigasi push/pop

 Kelas Pendukung:
- **FavoritesManager**: Kelas untuk mengelola data favorit
- **KeyManager**: Kelas untuk mengelola global key

💡 Konsep Penting

 State Management
- **StatefulWidget** untuk data yang berubah
- **setState()** memicu rebuild widget
- **StatelessWidget** untuk UI statis

Responsive Design
- Media query dengan **LayoutBuilder**
- Adaptasi layout berdasarkan screen size
- Penggunaan constraints.maxWidth untuk conditional rendering

 Navigator System
- **Navigator.push()** untuk beralih ke halaman baru
- **Navigator.pop()** untuk kembali ke halaman sebelumnya
- BottomNavigationBar untuk navigasi utama

🎨 UI Components
- **AppBar** dengan actions untuk toggle theme
- **Card** untuk menampilkan gambar dan informasi
- **ListView** dan **GridView** untuk layout yang berbeda
- **CircleAvatar** untuk tampilan profil
- **SnackBar** untuk notifikasi interaksi

📦 Cara Menjalankan
1. Simpan kode dalam file `main.dart`
2. Jalankan dengan `flutter run`
3. Test responsivitas dengan resize emulator/window
4. Test toggle theme dengan ikon brightness di AppBar
5. Test fitur favorit dengan menekan ikon hati pada gambar

Aplikasi ini merupakan contoh lengkap implementasi fitur-fitur fundamental Flutter yang dapat dikembangkan lebih lanjut untuk keperluan production.

Komentar