1 / 26

UI/UX Mobile

Project

Master Design System & Component Reusable

🎨 Kelas 11
📚 Semester 1
🚀 BAB 11

📍 Peta Materi

Perjalanan dari Design System hingga Production-Grade App

🎨 Design System

Fondasi konsistensi visual & behavior aplikasi yang scalable

🌈 Color & Typography

Psikologi warna & kemampuan readable untuk UX maksimal

🧱 Components

Widget reusable yang mempercepat development & maintain

🎭 Theme System

Light & Dark mode yang seamless untuk semua device

✨ Animasi & States

Loading, Splash, Empty states yang engaging & informatif

🛍️ Mini Project

Aplikasi E-Commerce UI dengan semua konsep terintegrasi

🏠 UI vs UX

Analogi Rumah: Apa Bedanya?

🎨 UI (User Interface)

= Tampilan Rumah

✓ Warna cat dinding
✓ Furnitur & dekorasi
✓ Pintu & jendela
✓ Tata letak interior

❤️ UX (User Experience)

= Kenyamanan Tinggal

✓ Mudah navigasi
✓ Ventilasi baik
✓ Privasi terjamin
✓ Efisien & praktis

💡 Rumah mewah (UI bagus) tapi sulit navigasi (UX buruk)? Orang tetap pilih rumah sederhana yang nyaman!

🍳 Design System

= Resep Restoran Bintang Lima

📋 Color Tokens

Palet warna terdefinisi untuk konsistensi visual

🔤 Typography Scale

Font sizes & weights yang harmonis & readable

📐 Spacing System

Margin & padding proporsional (8px base grid)

🎭 Components

Widget reusable dengan consistent behavior

🎯 Principles

Guideline: minimalist, accessible, performant

📚 Documentation

Semua rules tercatat untuk team alignment

🎨 Color Palette

Setiap warna membangkitkan perasaan & psikologi berbeda

FF6B6B

Primary

🔥 Energy, Passion, Alert

5E72E4

Secondary

💜 Trust, Creative, Premium

00D4FF

Accent

🌊 Fresh, Modern, Info

🔤 Typography

Font yang tepat membuat teks lebih readable & memorable

POPPINS

Display font — Bold, eye-catching, headlines (Poppins 900)

Inter Regular — Comfortable for body text and description. Highly readable and accessible at any size. Perfect for main content.

Body text, description (Inter 400)

const button = () => <Button />

Code block, technical info (Space Mono)

🧱 Component Reusable

= LEGO Block: Bangun UI kompleks dari blok sederhana

✓ Hemat Waktu

Tidak perlu membuat widget dari nol berulang kali

✓ Konsistensi

Semua button, card, input terlihat & behave sama

✓ Mudah Update

Ubah 1 component → seluruh app terupdate

✓ Maintainable

Code terstruktur, mudah dibaca & dimodifikasi

✓ Testable

Unit test per component lebih efisien

✓ Scalable

Tambah fitur tanpa breaking existing code

class CustomButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;
  final Color bgColor;

  const CustomButton({
    required this.label,
    required this.onPressed,
    this.bgColor = Colors.blue,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: bgColor,
        padding: EdgeInsets.symmetric(horizontal: 24, vertical: 14),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      ),
      onPressed: onPressed,
      child: Text(label, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
    );
  }
}

🎭 Theme System

Satu toggle, seluruh app ganti warna — Dark/Light mode seamless

☀️ Light Theme

Cocok untuk siang hari, kontras tinggi, mata fresh

✓ Tinggi perceived brightness
✓ Kontras maksimal
✓ Ideal untuk outdoor

🌙 Dark Theme

Cocok untuk malam hari, hemat baterai, estetik premium

✓ Mengurangi eye strain
✓ Hemat baterai OLED
✓ Lebih premium modern

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(brightness: Brightness.light, primaryColor: Colors.blue),
      darkTheme: ThemeData(brightness: Brightness.dark, primaryColor: Colors.blueAccent),
      themeMode: ThemeMode.system,
      home: HomeScreen(),
    );
  }
}

🌙 Dark Mode Implementation

Implementasi dark mode yang seamless untuk UX & aksesibilitas

👁️ Kenyamanan

Mata lebih nyaman baca di kondisi gelap

🔋 Efisiensi

OLED screen hemat 15-20% battery

✨ Estetik

Dark mode terlihat premium & modern

⚠️ Pro Tip: Gunakan dark gray (#121212) bukan black (#000000) untuk less eye strain

✨ Animasi: Gerakan Hidup

Animasi membuat UI lebih engaging, natural, dan memorable

🎞️ Fade

Elemen gradually muncul/hilang dengan smooth opacity

↗️ Slide

Elemen bergerak dari posisi ke posisi lain

📐 Scale

Elemen membesar/mengecil dengan smooth transform

🔄 Rotate

Elemen berputar smooth tanpa jerky motion

🎈 Bounce

Elemen bounce dengan overshoot untuk kesan fun

🌊 Parallax

Layers bergerak beda kecepatan untuk depth

⚡ Duration: 200-500ms micro-interactions, 500-1000ms major animations

🎬 Implicit Animation

Widget yang otomatis animate saat state berubah

AnimatedContainer

Size, warna, position smooth saat state update

AnimatedOpacity

Transparansi berubah gradual tanpa controller

AnimatedPositioned

Posisi di Stack berubah smooth

AnimatedAlign

Alignment dari child berubah smooth

TweenAnimationBuilder

Custom property terupdate smooth

AnimatedDefaultTextStyle

Text style berubah smooth tanpa rebuild

class ImplicitAnimDemo extends StatefulWidget {
  @override
  State<ImplicitAnimDemo> createState() => _ImplicitAnimDemoState();
}

class _ImplicitAnimDemoState extends State<ImplicitAnimDemo> {
  bool isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => isExpanded = !isExpanded),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 500),
        curve: Curves.easeInOut,
        width: isExpanded ? 200 : 100,
        height: isExpanded ? 200 : 100,
        decoration: BoxDecoration(
          color: isExpanded ? Colors.blue : Colors.red,
          borderRadius: BorderRadius.circular(isExpanded ? 20 : 10),
        ),
        child: Center(child: Text('TAP')),
      ),
    );
  }
}

🎭 Explicit Animation & Hero

Kontrol penuh + magic shared element transition

🎮 AnimationController

Kontrol penuh: forward, reverse, repeat, stop

✓ Duration custom
✓ Curve control
✓ Listener callback
✓ Start/stop kapan saja

🚀 Hero Animation

Elemen "terbang" antar screen dengan smooth

✓ Shared element
✓ Smooth morphing
✓ Zero setup
✓ Works with Navigator

// Screen 1: Image dengan Hero wrapper
Hero(
  tag: 'product-image',
  child: Image.asset('assets/product.png', width: 100, height: 100),
)

// Screen 2: Sama tag, ukuran beda → otomatis terbang & morph!
Hero(
  tag: 'product-image',
  child: Image.asset('assets/product.png', width: 300, height: 300),
)

🎬 Splash Screen

Kesan pertama penting! Splash screen yang baik meningkatkan perceived quality

🎯 Tujuan

✓ Brand showcase
✓ Loading prep
✓ Emotional hook

⏱️ Durasi

2-3 detik cukup untuk kesan tapi tidak mengganggu

🎨 Design

✓ Minimal
✓ Logo besar
✓ Subtle animation

class SplashScreen extends StatefulWidget {
  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: Duration(seconds: 2), vsync: this)..forward();
    Future.delayed(Duration(seconds: 3), () {
      Navigator.pushReplacementNamed(context, '/home');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blue,
      body: Center(
        child: FadeTransition(
          opacity: _controller,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Image.asset('assets/logo.png', width: 120),
              SizedBox(height: 20),
              Text('MyApp', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)),
            ],
          ),
        ),
      ),
    );
  }
}

📭 Empty State

Jangan biarkan user bingung — guide mereka dengan empati & action

🎨 Ilustrasi

Gambar/icon lucu & relatable untuk kesan friendly

💬 Pesan Jelas

Jelaskan kenapa kosong & apa yang bisa dilakukan

🔘 CTA Button

Tombol prominent untuk trigger action relevan

❌ Jangan: "No data" kosong
✅ Lakukan: "Belum ada order 📦 Mulai belanja sekarang!" + button

⏳ Loading State

Beri user feedback bahwa app "bekerja" — jangan biarkan layar membeku

🔄 Spinner

Lingkaran berputar — simple & efektif untuk loading umum

📊 Progress Bar

Garis yang fill untuk show progress pada long task

💀 Skeleton

Placeholder layout sebelum data nyata — terasa lebih cepat!

✨ Shimmer

Efek "kilau" moving di skeleton untuk sophisticated feel

💡 Skeleton loading terasa 40% lebih cepat dibanding spinner kosong!

🛍️ Mini Project: E-Commerce UI

Aplikasikan SEMUA konsep menjadi aplikasi nyata production-grade!

🎬 Screen 1: Splash

Animasi loading dengan logo, brand message, 2-3 detik

🏠 Screen 2: Home

AppBar custom, banner promo, category chips, featured products

📋 Screen 3: List

GridView dengan ProductCard reusable, filter, search

🔍 Screen 4: Detail

Galeri foto, rating, deskripsi lengkap, add to cart

🛒 Screen 5: Cart

List item, quantity control, total, checkout button

👤 Screen 6: Profile

Avatar, info, order history, dark mode toggle

💻 Project: HomeScreen Code

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('E-Commerce', style: TextStyle(fontWeight: FontWeight.bold)),
        actions: [IconButton(icon: Icon(Icons.shopping_cart), onPressed: () {})],
      ),
      body: SingleChildScrollView(
        child: Column(children: [
          Container(height: 180, color: Colors.blueAccent,
            child: Center(child: Text('Special Promo 50%', style: TextStyle(color: Colors.white, fontSize: 24)))),
          GridView.builder(
            shrinkWrap: true,
            physics: NeverScrollableScrollPhysics(),
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
            itemCount: 10,
            itemBuilder: (context, index) => ProductCard(id: index),
          ),
        ]),
      ),
    );
  }
}

🧩 Project: ProductCard Widget

class ProductCard extends StatelessWidget {
  final int id;
  const ProductCard({required this.id});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(children: [
        Expanded(child: Container(color: Colors.grey[300], child: Icon(Icons.image, size: 50))),
        Padding(padding: EdgeInsets.all(8),
          child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
            Text('Product $id', style: TextStyle(fontWeight: FontWeight.bold)),
            SizedBox(height: 4),
            Text('Rp 150.000', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold)),
            SizedBox(height: 4),
            Row(children: [
              Icon(Icons.star, size: 14, color: Colors.amber),
              SizedBox(width: 4),
              Text('4.5', style: TextStyle(fontSize: 12)),
            ]),
          ]),
        ),
      ]),
    );
  }
}

🛍️ Project: Detail & Cart

class ProductDetailScreen extends StatefulWidget {
  @override
  State<ProductDetailScreen> createState() => _ProductDetailScreenState();
}

class _ProductDetailScreenState extends State<ProductDetailScreen> {
  int quantity = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Product Detail')),
      body: SingleChildScrollView(
        child: Column(children: [
          Container(height: 300, color: Colors.grey[300], child: Icon(Icons.image, size: 100)),
          Padding(padding: EdgeInsets.all(16),
            child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
              Text('Premium T-Shirt', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
              SizedBox(height: 8),
              Text('Rp 150.000', style: TextStyle(fontSize: 18, color: Colors.blue, fontWeight: FontWeight.bold)),
              SizedBox(height: 20),
              Row(children: [
                IconButton(icon: Icon(Icons.remove), onPressed: () => setState(() => quantity--)),
                Text('$quantity'),
                IconButton(icon: Icon(Icons.add), onPressed: () => setState(() => quantity++)),
              ]),
              SizedBox(height: 20),
              ElevatedButton(onPressed: () => print('Added'), child: Text('Add to Cart'),
                style: ElevatedButton.styleFrom(minimumSize: Size(double.infinity, 50)),
              ),
            ]),
          ),
        ]),
      ),
    );
  }
}

🌙 Dark Mode Integration

class ThemeProvider extends ChangeNotifier {
  bool isDarkMode = false;

  void toggleTheme() {
    isDarkMode = !isDarkMode;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => ThemeProvider(),
      child: Consumer<ThemeProvider>(
        builder: (context, themeProvider, _) {
          return MaterialApp(
            theme: ThemeData.light(),
            darkTheme: ThemeData.dark(),
            themeMode: themeProvider.isDarkMode ? ThemeMode.dark : ThemeMode.light,
            home: HomeScreen(),
          );
        },
      ),
    );
  }
}

// Toggle Button
Consumer<ThemeProvider>(
  builder: (context, themeProvider, _) {
    return IconButton(
      icon: Icon(themeProvider.isDarkMode ? Icons.light_mode : Icons.dark_mode),
      onPressed: () => themeProvider.toggleTheme(),
    );
  },
)

🎉 Project Complete!

Selamat! Kamu sudah membangun E-Commerce UI production-grade dengan semua fitur!

✅ Splash Screen

Dengan animasi loading yang engaging

✅ Product Management

List, detail, cart dengan full functionality

✅ Dark Mode

Toggle global dengan state management

✅ Responsive

Optimal di semua ukuran device

✅ Code Quality

Clean, maintainable, production-ready

✅ Animations

Smooth, engaging, user-delightful

Kamu siap jadi Mobile UI/UX Designer profesional! 🚀✨

💪 Latihan 1: Design System Milikmu

Buat design system unik dengan color, typography, dan 3 custom widgets

📋 Spesifikasi:

1. Color Palette — 5 warna + dokumentasi psychological meaning
2. Typography — Minimal 3 font dari Google Fonts dengan size scale
3. Widgets — CustomButton, CustomCard, CustomAppBar yang reusable
4. Theme — Light & Dark theme yang konsisten

⭐ Rubrik (100 poin)

Desain (40%): Kreatif, harmonis
Konsistensi (30%): Warna & font consistent
Code (20%): Clean & maintainable
Dokumentasi (10%): Clear comments

📂 Output

lib/
├── constants/colors.dart
├── widgets/custom_*.dart
└── themes/app_theme.dart

💪 Latihan 2: Dark Mode

Implementasikan dark mode complete dengan persistence & smooth transitions

✓ Step 1

Setup ThemeProvider (ChangeNotifier)

✓ Step 2

Simpan ke SharedPreferences

✓ Step 3

Buat toggle UI yang seamless

📌 Checkpoint: Toggle 1x → seluruh app terupdate smooth 🎯

🏆 Tugas Akhir: Profile Screen

Bangun Profile Screen dengan loading skeleton, empty state, dark mode

📋 Spesifikasi:

• Header: Avatar, nama, email dengan skeleton loading
• Menu: My Orders, Wishlist, Settings, Logout
• Loading: Skeleton card untuk simulated fetch
• Empty: Friendly message jika user baru
• Dark Mode: Full support seamless

🎯 Checklist

□ UI modern & attractive
□ Loading state ✓
□ Empty state ✓
□ Dark mode ✓
□ Smooth animations ✓

📅 Deadline

Akhir minggu
Submit: GitHub link / APK
Format: Main branch clean

🎓 Ringkasan BAB 11

Kamu sudah menguasai UI/UX Mobile Development A sampai Z! 🎉

🎨 Design System

Konsistensi visual dengan color, typography, components

✨ Animasi & Interaksi

Implicit, explicit, hero animation yang engaging

🎬 State Management

Loading, empty, error states dengan UX informatif

🛍️ Production App

E-Commerce UI ready-to-scale dengan semua konsep

"Design bukan hanya tampilan,
tapi tentang menciptakan pengalaman yang memorable!" 💖

Keep creating, keep improving! 🚀✨