Layout responsif adalah desain tata letak yang dapat menyesuaikan tampilannya secara otomatis berdasarkan ukuran layar perangkat (misalnya, smartphone, tablet, desktop).
Navigasi adalah cara pengguna berpindah antar halaman atau layar di dalam aplikasi.
Stateful widget adalah komponen yang bisa berubah atau memperbarui tampilannya ketika data internal atau statusnya berubah.
Dynamic theme adalah kemampuan aplikasi untuk mengubah tema (warna, font, gaya) secara dinamis, biasanya berdasarkan preferensi pengguna atau kondisi tertentu (misalnya mode terang/gelap).
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pola Hidup Sehat untuk Wanita',
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Color.fromARGB(255, 240, 49, 230),
colorScheme: ColorScheme.light(
primary: Color.fromARGB(255, 237, 40, 227)!,
secondary: Colors.teal,
),
scaffoldBackgroundColor: Colors.grey[50],
appBarTheme: AppBarTheme(
backgroundColor: Color.fromARGB(255, 240, 45, 231),
elevation: 4,
titleTextStyle: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
),
iconTheme: IconThemeData(color: Colors.white),
),
cardTheme: CardTheme(
elevation: 6,
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
textTheme: TextTheme(
headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
subtitle1: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
bodyText1: TextStyle(fontSize: 16),
bodyText2: TextStyle(fontSize: 14),
),
),
darkTheme: ThemeData(
brightness: Brightness.dark,
primaryColor: Color.fromARGB(255, 240, 24, 190),
colorScheme: ColorScheme.dark(
primary: Color.fromARGB(255, 220, 18, 220)!,
secondary: Colors.tealAccent,
),
scaffoldBackgroundColor: Colors.grey[900],
appBarTheme: AppBarTheme(
backgroundColor: Color.fromARGB(255, 227, 19, 189),
elevation: 4,
titleTextStyle: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
),
iconTheme: IconThemeData(color: Colors.white),
),
cardTheme: CardTheme(
elevation: 6,
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
textTheme: TextTheme(
headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white),
subtitle1: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white70),
bodyText1: TextStyle(fontSize: 16, color: Colors.white70),
bodyText2: TextStyle(fontSize: 14, color: Colors.white60),
),
),
themeMode: _isDarkMode ? ThemeMode.dark : ThemeMode.light,
home: HomePage(
isDarkMode: _isDarkMode,
onThemeToggle: _toggleTheme,
),
);
}
}
class HomePage extends StatelessWidget {
final bool isDarkMode;
final VoidCallback onThemeToggle;
HomePage({ required this.isDarkMode, required this.onThemeToggle });
final List<_ContentItem> items = [
_ContentItem(
title: 'Makanan Sehat: Sayur',
imagePath: 'assets/sayuran.jpg',
cara: 'Konsumsi sayur segar minimal 1‑2 jenis saat satu kali makan. Variasi warna (hijau, merah, kuning) agar nutrisi lengkap.',
penjelasan: 'Sayur kaya serat, vitamin, mineral, membantu pencernaan, mencegah sembelit, dan mendukung sistem imun. Memasak ringan agar vitamin tidak hilang. '
),
_ContentItem(
title: 'Makanan Sehat: Buah',
imagePath: 'assets/buah.jpg',
cara: 'Makan buah utuh sebagai camilan atau setelah makan. Pilih buah lokal yang matang. Hindari ditambah gula.',
penjelasan: 'Buah mengandung vitamin dan antioksidan, membantu detoks tubuh, menjaga kulit dan kesehatan mata. Serat buah baik untuk perut.'
),
_ContentItem(
title: 'Olahraga',
imagePath: 'assets/olahraga.jpg',
cara: 'Lakukan olahraga ringan‑sedang seperti jalan cepat 30 menit, yoga atau pilates 3‑4 kali seminggu.',
penjelasan: 'Olahraga meningkatkan sirkulasi darah, menjaga berat badan, meningkatkan mood, memperkuat tulang dan otot.'
),
_ContentItem(
title: 'Urutan Skincare',
imagePath: 'assets/skincare.jpg',
cara: 'Bersihkan wajah → toner → serum → pelembab → sunscreen di pagi hari. Malam hari bisa ditambah malam treatment seperti eksfoliasi atau masker.',
penjelasan: 'Perawatan kulit sebaiknya dimulai dari yang ringan ke berat. Sunscreen penting agar kulit tidak kusam & terlindungi dari sinar UV.'
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pola Hidup Sehat untuk Wanita'),
actions: [
IconButton(
icon: Icon(isDarkMode ? Icons.wb_sunny : Icons.nightlight_round),
onPressed: onThemeToggle,
tooltip: isDarkMode ? 'Mode Terang' : 'Mode Gelap',
),
],
),
body: LayoutBuilder(
builder: (ctx, constraints) {
double width = constraints.maxWidth;
int crossAxisCount;
if (width >= 1200) {
crossAxisCount = 3;
} else if (width >= 800) {
crossAxisCount = 2;
} else {
crossAxisCount = 1;
}
return GridView.builder(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 4/3, // lebar : tinggi
),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ContentCard(item: item);
},
);
}
),
);
}
}
class ContentCard extends StatelessWidget {
final _ContentItem item;
ContentCard({ required this.item });
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
// navigasi ke halaman detail
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(item: item),
),
);
},
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 6,
clipBehavior: Clip.hardEdge,
child: Stack(
children: [
// Gambar sebagai latar
Positioned.fill(
child: Image.asset(
item.imagePath,
fit: BoxFit.cover,
),
),
// overlay gelap untuk teks agar terbaca
Positioned.fill(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.black.withOpacity(0.5), Colors.transparent],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
),
),
),
// Judul di bagian bawah
Positioned(
left: 12,
right: 12,
bottom: 12,
child: Text(
item.title,
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
shadows: [
Shadow(
blurRadius: 4,
color: Colors.black45,
offset: Offset(0, 2),
)
],
),
),
),
],
),
),
);
}
}
class DetailPage extends StatelessWidget {
final _ContentItem item;
DetailPage({ required this.item });
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(item.title),
),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Gambar besar
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.asset(
item.imagePath,
fit: BoxFit.cover,
),
),
SizedBox(height: 16),
Text(
'Cara Caranya:',
style: Theme.of(context).textTheme.headline6,
),
SizedBox(height: 8),
Text(
item.cara,
style: Theme.of(context).textTheme.bodyText1,
),
SizedBox(height: 16),
Text(
'Penjelasan:',
style: Theme.of(context).textTheme.headline6,
),
SizedBox(height: 8),
Text(
item.penjelasan,
style: Theme.of(context).textTheme.bodyText1,
),
],
),
),
),
);
}
}
class _ContentItem {
final String title;
final String imagePath;
final String cara;
final String penjelasan;
_ContentItem({
required this.title,
required this.imagePath,
required this.cara,
required this.penjelasan,
});
}
Comments
Post a Comment