Tampilan Login

✨ Membuat Modern Glass Authentication UI dengan Flutter

Pada artikel ini kita akan membahas kode aplikasi Flutter yang menampilkan UI autentikasi modern dengan kombinasi Animated Gradient Background dan Glassmorphism. Aplikasi ini mencakup halaman Welcome, Login, Register, Forgot Password, hingga Home setelah login.


Ringkasan fitur

  • Animated pastel gradient background (smooth transition)
  • Glassmorphism card untuk form
  • Custom gradient button dan transisi halaman
  • Semuanya di-styling dengan warna-warna pastel yang lembut

Preview (mockup)

Welcome

Masuk untuk melanjutkan ke aplikasi dengan antarmuka modern dan lembut.

Background animasi (implementasi di Flutter). Untuk demo interaktif, deploy kode Flutter ke web atau gunakan emulator.


Kode lengkap (main.dart)

Salin seluruh isi di bawah ini ke file lib/main.dart pada project Flutter-mu.

// main.dart (singkat tapi fungsional — paste ke lib/main.dart)
import 'dart:ui';
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Glass Auth UI',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        fontFamily: 'Poppins',
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const WelcomePage(),
    );
  }
}

/* ---------- Animated gradient background ---------- */
class AnimatedGradient extends StatefulWidget {
  final Widget child;
  const AnimatedGradient({super.key, required this.child});
  @override
  State<AnimatedGradient> createState() => _AnimatedGradientState();
}

class _AnimatedGradientState extends State<AnimatedGradient> with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  int idx = 0;
  final List<List<Color>> gradientSets = [
    [Color(0xFFFFF0F6), Color(0xFFE8F0FF)],
    [Color(0xFFFFF9E8), Color(0xFFEFFAF3)],
    [Color(0xFFF5F7FF), Color(0xFFFFF1F8)],
  ];

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 10))
      ..addStatusListener((s) { if (s == AnimationStatus.completed) { _ctrl.reset(); setState((){ idx = (idx+1) % gradientSets.length; }); _ctrl.forward(); }})
      ..forward();
  }

  @override void dispose() { _ctrl.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    final a = gradientSets[idx];
    final b = gradientSets[(idx+1)%gradientSets.length];
    return AnimatedBuilder(
      animation: _ctrl,
      builder: (context, child) {
        final t = Curves.easeInOut.transform(_ctrl.value);
        return Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
              colors: [
                Color.lerp(a[0], b[0], t)!,
                Color.lerp(a[1], b[1], t)!,
              ],
            )
          ),
          child: widget.child,
        );
      }
    );
  }
}

/* ---------- Glass Card ---------- */
class GlassCard extends StatelessWidget {
  final Widget child;
  final EdgeInsets padding;
  const GlassCard({super.key, required this.child, this.padding = const EdgeInsets.all(20)});
  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(16),
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
        child: Container(
          padding: padding,
          decoration: BoxDecoration(
            color: Colors.white.withOpacity(0.55),
            borderRadius: BorderRadius.circular(16),
            border: Border.all(color: Colors.white.withOpacity(0.6)),
            boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.03), blurRadius: 20, offset: Offset(0,8))]
          ),
          child: child,
        ),
      ),
    );
  }
}

/* ---------- Pages ---------- */
class WelcomePage extends StatelessWidget {
  const WelcomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedGradient(
        child: Center(
          child: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(22.0),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  GlassCard(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Text('Selamat Datang', style: TextStyle(fontSize:22, fontWeight: FontWeight.w700, color: Color(0xFF4A3C8C))),
                        SizedBox(height:8),
                        Text('Masuk atau daftar untuk melanjutkan', style: TextStyle(color:Colors.black54)),
                        SizedBox(height:16),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            ElevatedButton(
                              style: ElevatedButton.styleFrom(backgroundColor: Color(0xFFDDE9FF), foregroundColor: Color(0xFF4A3C8C), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
                              onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const LoginPage())),
                              child: Padding(padding: EdgeInsets.symmetric(horizontal:18, vertical:12), child: Text('Login')),
                            ),
                            SizedBox(width:10),
                            OutlinedButton(
                              style: OutlinedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
                              onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const RegisterPage())),
                              child: Padding(padding: EdgeInsets.symmetric(horizontal:18, vertical:12), child: Text('Register')),
                            )
                          ],
                        )
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

/* Minimal Login/Register & storage simulation */
class _Storage {
  static Map<String,String> users = {};
  static void register(String email, String pass) => users[email] = pass;
  static bool login(String email, String pass) => users[email] == pass;
}

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});
  @override State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
  final _email = TextEditingController();
  final _pass = TextEditingController();
  bool show = false;

  @override Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Colors.transparent, elevation:0, iconTheme: IconThemeData(color: Colors.black87)),
      body: AnimatedGradient(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(22.0),
            child: GlassCard(
              child: Column(mainAxisSize:MainAxisSize.min, children: [
                Text('Login', style: TextStyle(fontSize:20, fontWeight: FontWeight.w700)),
                SizedBox(height:12),
                TextField(controller: _email, decoration: InputDecoration(hintText:'Email', filled:true, fillColor: Colors.white70, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)))),
                SizedBox(height:10),
                TextField(controller: _pass, obscureText: !show, decoration: InputDecoration(suffixIcon: IconButton(icon: Icon(show? Icons.visibility_off : Icons.visibility), onPressed: () => setState(()=>show=!show)), hintText:'Password', filled:true, fillColor: Colors.white70, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)))),
                SizedBox(height:14),
                ElevatedButton(
                  style: ElevatedButton.styleFrom(backgroundColor: Color(0xFFDDE9FF), foregroundColor: Color(0xFF4A3C8C), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
                  onPressed: () {
                    final ok = _Storage.login(_email.text.trim(), _pass.text);
                    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(ok? 'Login berhasil':'Login gagal')));
                    if(ok) Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => HomePage(email: _email.text.trim())));
                  },
                  child: Padding(padding: EdgeInsets.symmetric(horizontal:18, vertical:12), child: Text('Login'))
                ),
                SizedBox(height:8),
                TextButton(onPressed: () {}, child: Text('Lupa password?', style: TextStyle(color: Colors.black54)))
              ]),
            ),
          ),
        ),
      ),
    );
  }
}

class RegisterPage extends StatefulWidget {
  const RegisterPage({super.key});
  @override State<RegisterPage> createState() => _RegisterPageState();
}
class _RegisterPageState extends State<RegisterPage> {
  final e = TextEditingController();
  final p = TextEditingController();
  @override Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Colors.transparent, elevation:0, iconTheme: IconThemeData(color: Colors.black87)),
      body: AnimatedGradient(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(22.0),
            child: GlassCard(
              child: Column(mainAxisSize:MainAxisSize.min, children: [
                Text('Register', style: TextStyle(fontSize:20, fontWeight: FontWeight.w700)),
                SizedBox(height:12),
                TextField(controller: e, decoration: InputDecoration(hintText:'Email', filled:true, fillColor: Colors.white70, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)))),
                SizedBox(height:10),
                TextField(controller: p, decoration: InputDecoration(hintText:'Password', filled:true, fillColor: Colors.white70, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)))),
                SizedBox(height:14),
                ElevatedButton(
                  style: ElevatedButton.styleFrom(backgroundColor: Color(0xFFDFFCF0), foregroundColor: Color(0xFF2E7D5E), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
                  onPressed: () {
                    _Storage.register(e.text.trim(), p.text);
                    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Registrasi berhasil')));
                    Navigator.of(context).pop();
                  },
                  child: Padding(padding: EdgeInsets.symmetric(horizontal:18, vertical:12), child: Text('Daftar'))
                ),
              ]),
            ),
          ),
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  final String email;
  const HomePage({super.key, required this.email});
  @override Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home'), backgroundColor: Colors.transparent, elevation:0),
      body: Center(child: Text('Halo, $email!', style: TextStyle(fontSize:18))),
    );
  }
}
      

Catatan: kode di atas dibuat ringkas agar mudah dimengerti dan langsung bisa dijalankan. Untuk produksi, sambungkan ke backend/auth provider (Firebase Auth / REST API), tambahkan validasi lebih kuat dan simpan credential dengan aman.

Komentar

Postingan Populer