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(
debugShowCheckedModeBanner: false,
title: 'Netflix Login',
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.black,
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.grey[900],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(color: Colors.grey),
),
),
),
home: const LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
bool rememberMe = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
const Text(
"NGAWIFLIX",
style: TextStyle(
color: Colors.red,
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
const Text(
"Sign In",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
TextField(
controller: emailController,
decoration: const InputDecoration(
hintText: "Email or mobile number",
),
),
const SizedBox(height: 16),
TextField(
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
hintText: "Password",
),
),
const SizedBox(height: 24),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 14),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JadwalBioskop()),
);
},
child: const Text(
"Sign In",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 16),
Row(
children: const [
Expanded(child: Divider(color: Colors.grey)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text("OR"),
),
Expanded(child: Divider(color: Colors.grey)),
],
),
const SizedBox(height: 16),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey[850],
padding: const EdgeInsets.symmetric(vertical: 14),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LoginCodePage()),
);
},
child: const Text(
"Use a Sign-In Code",
style: TextStyle(color: Colors.white),
),
),
const SizedBox(height: 16),
Center(
child: TextButton(
onPressed: () {},
child: const Text("Forgot password?"),
),
),
const SizedBox(height: 8),
Row(
children: [
Checkbox(
value: rememberMe,
onChanged: (value) {
setState(() {
rememberMe = value ?? false;
});
},
activeColor: Colors.white,
checkColor: Colors.black,
),
const Text("Remember me"),
],
),
const SizedBox(height: 30),
Center(
child: RichText(
text: const TextSpan(
style: TextStyle(color: Colors.white),
children: [
TextSpan(text: "New to Netflix? "),
TextSpan(
text: "Sign up now.",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white),
),
],
),
),
),
const SizedBox(height: 24),
const Text(
"This page is protected by Google reCAPTCHA to ensure you're not a bot. Learn more.",
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
),
),
);
}
}
/// LOGIN DENGAN SIGN-IN CODE
class LoginCodePage extends StatefulWidget {
const LoginCodePage({super.key});
@override
State<LoginCodePage> createState() => _LoginCodePageState();
}
class _LoginCodePageState extends State<LoginCodePage> {
final TextEditingController emailController = TextEditingController();
bool rememberMe = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 40),
const Text(
"NGAWIFLIX",
style: TextStyle(
color: Colors.red,
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
const Text(
"Sign In",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
TextField(
controller: emailController,
decoration: const InputDecoration(
hintText: "Email or mobile number",
),
),
const SizedBox(height: 8),
const Text(
"Message and data rates may apply",
style: TextStyle(color: Colors.grey, fontSize: 12),
),
const SizedBox(height: 16),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 14),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JadwalBioskop()),
);
},
child: const Text(
"Send Sign-In Code",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 16),
Row(
children: const [
Expanded(child: Divider(color: Colors.grey)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text("OR"),
),
Expanded(child: Divider(color: Colors.grey)),
],
),
const SizedBox(height: 16),
OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey[850],
padding: const EdgeInsets.symmetric(vertical: 14),
),
onPressed: () {
Navigator.pop(context); // balik ke login password
},
child: const Text(
"Use password",
style: TextStyle(color: Colors.white),
),
),
const SizedBox(height: 16),
Center(
child: TextButton(
onPressed: () {},
child: const Text("Forgot Email or Phone Number?"),
),
),
const SizedBox(height: 8),
Row(
children: [
Checkbox(
value: rememberMe,
onChanged: (value) {
setState(() {
rememberMe = value ?? false;
});
},
activeColor: Colors.white,
checkColor: Colors.black,
),
const Text("Remember me"),
],
),
const SizedBox(height: 30),
Center(
child: RichText(
text: const TextSpan(
style: TextStyle(color: Colors.white),
children: [
TextSpan(text: "New to Netflix? "),
TextSpan(
text: "Sign up now.",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white),
),
],
),
),
),
const SizedBox(height: 24),
const Text(
"This page is protected by Google reCAPTCHA to ensure you're not a bot. Learn more.",
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
),
),
);
}
}
/// HOME PAGE
class JadwalBioskop extends StatefulWidget {
@override
State<JadwalBioskop> createState() => _JadwalBioskopState();
}
class _JadwalBioskopState extends State<JadwalBioskop> {
List<Map<String, String>> filmList = [
{
"judul": "Ambaruwo",
"genre": "Horror, Sci-Fi",
"jam": "14:14, 17:17, 00:00",
"poster": "https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/2c1bf404b3494dbe88d2cfb529a07d8f~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=96c9dbee&x-expires=1756436400&x-signature=hy0B0aIA8wrqS8GHYZ%2Fc5d2My98%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
"deskripsi":
"Si Hytam kembali bangkit untuk menghytamkan kalian! Saksikan aksi seru ini di"
},
{
"judul": "Ambarawuhi",
"genre": "Horror, Adventure",
"jam": "10:00, 13:00, 16:00",
"poster":
"https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/0159708740824b7395ae7b79c0ccf029~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=58793e05&x-expires=1756436400&x-signature=7N62%2BwV6uxoottmwKwsBMBU2cF8%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=sg1&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
"deskripsi":
"Sekelompok KKN dari kota Ngawi yang diterror karena melakukan hal terlarang..."
},
{
"judul": "AMBATUKAM: one for all",
"genre": "Adventure",
"jam": "10:00, 13:00, 16:00",
"poster":
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSoTKE46hTPb_M8oWrv8yX7YRRAWYPOCRDEOQ&s",
"deskripsi": "Petualangan seru melawan desa misterius..."
},
{
"judul": "imut adalah maut",
"genre": "Romance, Drama",
"jam": "10:00, 13:00, 16:00",
"poster":
"https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/fff9a32b4474448482aea36881835dce~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=6ec2b866&x-expires=1756436400&x-signature=u13SV9baoxGbHOa2UbHGp3w94kQ%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
"deskripsi": "drama rumah tangga yang diganggu oleh siimut"
},
{
"judul": "Anggrek mekar berduri",
"genre": "Drama",
"jam": "-",
"poster":
"https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/bee02b17c3aa485fb23c8d42359dca9d~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=d5dc6895&x-expires=1756436400&x-signature=G3NJ9I%2BrORo%2FaEFoEY%2BLdQ56CJA%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
"deskripsi": "-"
},
{
"judul": "Ambabelle",
"genre": "Horror, shounen",
"jam": "-",
"poster":
"https://p16-sign-va.tiktokcdn.com/tos-maliva-i-photomode-us/033b71378e464c6ab3d974eb1b20cd86~tplv-photomode-image-v1:q70.webp?dr=1334&refresh_token=3164494f&x-expires=1756436400&x-signature=3UVFVwpJDuyDBbnSOdhSmWZIUAA%3D&t=5897f7ec&ps=b40d0ec8&shp=d05b14bd&shcp=1d1a97fc&idc=my&s=AWEME_DETAIL&biz_tag=tt_photomode&sc=image",
"deskripsi": "Film horror luar yang dibuat ulang di ngawi city"
},
];
String searchQuery = "";
void tambahFilm() {
showFormDialog();
}
void ubahFilm(int index) {
var film = filmList[index];
showFormDialog(editIndex: index, film: film);
}
void showFormDialog({int? editIndex, Map<String, String>? film}) {
String judul = film?["judul"] ?? "";
String genre = film?["genre"] ?? "";
String jam = film?["jam"] ?? "";
String poster = film?["poster"] ?? "";
String deskripsi = film?["deskripsi"] ?? "";
showDialog(
context: context,
builder: (context) {
return AlertDialog(
backgroundColor: Colors.grey[900],
title: Text(
editIndex == null ? "Tambah Film" : "Ubah Film",
style: const TextStyle(color: Colors.white),
),
content: SingleChildScrollView(
child: Column(
children: [
buildInput("Judul", (val) => judul = val, judul),
buildInput("Genre", (val) => genre = val, genre),
buildInput("Jam Tayang", (val) => jam = val, jam),
buildInput("URL Poster", (val) => poster = val, poster),
buildInput("Deskripsi", (val) => deskripsi = val, deskripsi,
maxLines: 3),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Batal", style: TextStyle(color: Colors.red)),
),
ElevatedButton(
onPressed: () {
setState(() {
if (editIndex == null) {
filmList.add({
"judul": judul,
"genre": genre,
"jam": jam,
"poster": poster,
"deskripsi": deskripsi,
});
} else {
filmList[editIndex] = {
"judul": judul,
"genre": genre,
"jam": jam,
"poster": poster,
"deskripsi": deskripsi,
};
}
});
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900],
),
child: const Text("Simpan",
style: TextStyle(color: Colors.white)),
),
],
);
},
);
}
static Widget buildInput(
String label, Function(String) onChanged, String initialValue,
{int maxLines = 1}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: TextField(
controller: TextEditingController(text: initialValue),
style: const TextStyle(color: Colors.white),
onChanged: onChanged,
maxLines: maxLines,
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.grey[800],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
),
),
);
}
void hapusFilm(int index) {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Colors.grey[900],
title: const Text("Konfirmasi",
style: TextStyle(color: Colors.white)),
content: const Text("Yakin ingin menghapus film ini?",
style: TextStyle(color: Colors.white70)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child:
const Text("Batal", style: TextStyle(color: Colors.grey)),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900],
),
onPressed: () {
setState(() {
filmList.removeAt(index);
});
Navigator.pop(context);
},
child:
const Text("Hapus", style: TextStyle(color: Colors.white)),
),
],
),
);
}
void detailFilm(Map<String, String> film) {
showDialog(
context: context,
builder: (context) {
final size = MediaQuery.of(context).size;
return Dialog(
backgroundColor: Colors.grey[900],
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 520,
maxHeight: size.height * 0.85,
),
child: SingleChildScrollView(
child: Column(
children: [
ClipRRect(
borderRadius:
const BorderRadius.vertical(top: Radius.circular(12)),
child: AspectRatio(
aspectRatio: 2 / 3,
child: Image.network(film["poster"]!,
fit: BoxFit.cover),
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(film["judul"]!,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white)),
const SizedBox(height: 4),
Text(film["genre"]!,
style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 8),
Text("Jam: ${film["jam"]}",
style:
const TextStyle(color: Colors.white70)),
const SizedBox(height: 12),
Text(film["deskripsi"]!,
style:
const TextStyle(color: Colors.white)),
const SizedBox(height: 16),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900]),
onPressed: () => Navigator.pop(context),
child: const Text("Keluar",
style: TextStyle(color: Colors.white)),
),
),
],
),
),
],
),
),
),
);
},
);
}
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
List<Map<String, String>> filteredList = filmList
.where((film) => film["judul"]!
.toLowerCase()
.contains(searchQuery.toLowerCase()))
.toList();
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text("Ngawiflix",
style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: Colors.red[900],
actions: [
IconButton(
onPressed: tambahFilm,
icon: const Icon(Icons.add, color: Colors.white),
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
onChanged: (value) => setState(() => searchQuery = value),
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
hintText: "Cari judul film...",
hintStyle: const TextStyle(color: Colors.grey),
prefixIcon: const Icon(Icons.search, color: Colors.white),
filled: true,
fillColor: Colors.grey[900],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
),
),
),
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: width < 420 ? 360 : 240,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.55,
),
itemCount: filteredList.length,
itemBuilder: (context, index) {
var film = filteredList[index];
return Card(
color: Colors.grey[900],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: InkWell(
onTap: () => detailFilm(film),
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12)),
child: Image.network(
film["poster"]!,
fit: BoxFit.cover,
width: double.infinity,
errorBuilder: (_, __, ___) => const Center(
child: Icon(Icons.broken_image)),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(film["judul"]!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
maxLines: 2,
overflow: TextOverflow.ellipsis),
const SizedBox(height: 4),
Text(film["genre"]!,
style: const TextStyle(
color: Colors.grey, fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis),
const SizedBox(height: 4),
Text("Jam: ${film["jam"]}",
style: const TextStyle(
color: Colors.white70, fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis),
const SizedBox(height: 8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.edit,
color: Colors.yellow),
onPressed: () => ubahFilm(index),
),
IconButton(
icon: const Icon(Icons.delete,
color: Colors.red),
onPressed: () => hapusFilm(index),
),
],
),
],
),
),
],
),
);
},
),
),
],
),
);
}
}
Komentar
Posting Komentar