update profile and html content
This commit is contained in:
@@ -9,10 +9,12 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown, // bisa dihapus jika hanya ingin satu arah
|
||||
]);
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
|
||||
class CharacterView extends StatefulWidget {
|
||||
final String title;
|
||||
final String imagePath;
|
||||
final String content;
|
||||
|
||||
const CharacterView({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.imagePath,
|
||||
required this.content,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_CharacterViewState createState() => _CharacterViewState();
|
||||
}
|
||||
|
||||
class _CharacterViewState extends State<CharacterView> {
|
||||
String _content = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_content = widget.content;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 10,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(40),
|
||||
bottomRight: Radius.circular(40),
|
||||
),
|
||||
color: Color.fromARGB(225, 79, 76, 182),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 0),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(left: 18.0, right: 18, bottom: 18),
|
||||
child: Text(_content),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class ListDetailScreen extends StatefulWidget {
|
||||
final String title;
|
||||
final String? filepath;
|
||||
|
||||
const ListDetailScreen({super.key, required this.title});
|
||||
const ListDetailScreen({super.key, required this.title, this.filepath});
|
||||
|
||||
@override
|
||||
State<ListDetailScreen> createState() => _ListDetailScreenState();
|
||||
@@ -11,6 +14,8 @@ class ListDetailScreen extends StatefulWidget {
|
||||
|
||||
class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
List<bool> checklist = [];
|
||||
late WebViewController _controller;
|
||||
late String? fPath = widget.filepath;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -25,58 +30,72 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
"Paragraf keempat yang melengkapi pembahasan.",
|
||||
];
|
||||
|
||||
void _loadContent(String path) async {
|
||||
if (path.startsWith('http')) {
|
||||
// Load dari Internet
|
||||
_controller.loadRequest(Uri.parse(path));
|
||||
} else {
|
||||
// Load dari assets lokal
|
||||
String fileHtml = await rootBundle.loadString(path);
|
||||
_controller.loadHtmlString(fileHtml);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(title: Text(widget.title)),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Gambar di kiri atas dengan teks di kanan
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/default.png', // Ganti dengan gambar yang sesuai
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Ini adalah detail dari ${widget.title}.",
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Paragraf dengan checklist
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: paragraphs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(paragraphs[index]),
|
||||
trailing: Checkbox(
|
||||
value: checklist[index],
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checklist[index] = value!;
|
||||
});
|
||||
},
|
||||
body:
|
||||
(fPath != null && fPath!.isNotEmpty)
|
||||
? WebViewWidget(controller: _controller)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Gambar di kiri atas dengan teks di kanan
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/default.png', // Ganti dengan gambar yang sesuai
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Ini adalah detail dari ${widget.title}.",
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Paragraf dengan checklist
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: paragraphs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(paragraphs[index]),
|
||||
trailing: Checkbox(
|
||||
value: checklist[index],
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checklist[index] = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:furibase/components/buildcard_info.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:furibase/components/card_list.dart';
|
||||
import 'package:furibase/components/collection_container%20copy.dart';
|
||||
import 'package:furibase/components/main_menu.dart';
|
||||
import 'package:furibase/components/topbar_container.dart';
|
||||
// import 'package:furibase/components/main_menu.dart';
|
||||
// import 'package:furibase/components/topbar_container.dart';
|
||||
import 'package:furibase/helpers/color_helper.dart';
|
||||
import 'package:furibase/screen/pustaka/pustaka_detail_screen.dart';
|
||||
import 'package:list_detail_extension/list_detail_extension.dart';
|
||||
|
||||
// import 'package:furibase/screen/pustaka/pustaka_detail_screen.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
class ListEducation extends StatefulWidget {
|
||||
const ListEducation({super.key});
|
||||
@@ -16,6 +23,17 @@ class ListEducation extends StatefulWidget {
|
||||
|
||||
class _ListEducationState extends State<ListEducation> {
|
||||
TextEditingController searchController = TextEditingController();
|
||||
late final WebViewController _controller;
|
||||
late String urlOrAssetPath;
|
||||
bool _shouldShowWebView = false;
|
||||
|
||||
// load extension
|
||||
final listDetailExtension = ListDetailExtension();
|
||||
|
||||
Future<void> _initExtension() async {
|
||||
await listDetailExtension.load();
|
||||
}
|
||||
|
||||
List<String> allItems = [
|
||||
"Matematika",
|
||||
"Fisika",
|
||||
@@ -24,20 +42,83 @@ class _ListEducationState extends State<ListEducation> {
|
||||
"Sejarah",
|
||||
"Geografi",
|
||||
];
|
||||
List<String> filteredItems = [];
|
||||
|
||||
final List<Map<String, dynamic>> _listContent = [
|
||||
{
|
||||
"title": "Taburan Lezat untuk Nasi!",
|
||||
"image": "assets/icons/healthy.svg",
|
||||
"color": "#cdd0ee",
|
||||
"category": "Kesehatan",
|
||||
"body":
|
||||
"""Tahukah kamu bahwa di Jepang ada bumbu tabur yang bisa membuat nasi
|
||||
menjadi lebih enak?
|
||||
""",
|
||||
"file": "assets/html/furikake/index.html",
|
||||
},
|
||||
{
|
||||
"title": "Makan Sehat dengan Gizi Seimbang",
|
||||
"image": "assets/icons/Safety.svg",
|
||||
"color": "#cef1da",
|
||||
"category": "Keselamatan",
|
||||
"body":
|
||||
"""upaya makan kita sehat, ada aturan "Isi Piringku" yang bisa kita ikuti:
|
||||
🍽️ Separuh piring: Sayur dan buah 🍽️ Seperempat piring: Karbohidrat (seperti nasi atau roti)
|
||||
🍽️ Seperempat piring: Protein (seperti ayam atau tempe) 🍽️ Sedikit lemak sehat
|
||||
""",
|
||||
"file": "assets/html/index.html",
|
||||
},
|
||||
];
|
||||
List<Map<String, dynamic>> filteredItems = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
filteredItems = List.from(allItems);
|
||||
filteredItems = List.from(_listContent);
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
_shouldShowWebView = true;
|
||||
_controller =
|
||||
WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
|
||||
urlOrAssetPath = "assets/makanan_bergizi/index.html";
|
||||
_loadContent(urlOrAssetPath);
|
||||
_initExtension();
|
||||
}
|
||||
}
|
||||
|
||||
void _loadContent(String path) async {
|
||||
if (_shouldShowWebView && _controller != null) {
|
||||
if (path.startsWith('http')) {
|
||||
// Load dari Internet
|
||||
_controller.loadRequest(Uri.parse(path));
|
||||
} else {
|
||||
// Load dari assets lokal
|
||||
// String fileHtml = await rootBundle.loadString(path);
|
||||
String fileHtml = await DefaultAssetBundle.of(
|
||||
context,
|
||||
).loadString('assets/html/index.html');
|
||||
_controller.loadHtmlString(fileHtml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filterSearch(String query) {
|
||||
setState(() {
|
||||
if (query.isEmpty) {
|
||||
filteredItems = List.from(_listContent);
|
||||
} else {
|
||||
filteredItems =
|
||||
_listContent.where((item) {
|
||||
return item["title"].toLowerCase().contains(query.toLowerCase());
|
||||
}).toList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void filterByCategory(String category) {
|
||||
setState(() {
|
||||
filteredItems =
|
||||
allItems
|
||||
.where((item) => item.toLowerCase().contains(query.toLowerCase()))
|
||||
.toList();
|
||||
_listContent.where((item) {
|
||||
return item["category"].toLowerCase() == category.toLowerCase();
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,32 +152,7 @@ class _ListEducationState extends State<ListEducation> {
|
||||
},
|
||||
];
|
||||
|
||||
final List<Map<String, dynamic>> _listContent = [
|
||||
{
|
||||
"title": "Taburan Lezat untuk Nasi!",
|
||||
"image": "assets/icons/healthy.svg",
|
||||
"color": "#cdd0ee",
|
||||
"category": "kesehatan",
|
||||
"body":
|
||||
"""Tahukah kamu bahwa di Jepang ada bumbu tabur yang bisa membuat nasi
|
||||
menjadi lebih enak? Namanya Furikake! Kata "furikake" berasal dari bahasa Jepang, yaitu "furi"
|
||||
yang berarti menaburkan dan "kake" yang berarti menuangkan. Jadi,
|
||||
furikake adalah bumbu yang ditaburkan di atas nasi supaya lebih lezat!
|
||||
""",
|
||||
},
|
||||
{
|
||||
"title": "Makan Sehat dengan Gizi Seimbang",
|
||||
"image": "assets/icons/Safety.svg",
|
||||
"color": "#cef1da",
|
||||
"category": "keselamatan",
|
||||
"body":
|
||||
"""upaya makan kita sehat, ada aturan "Isi Piringku" yang bisa kita ikuti:
|
||||
🍽️ Separuh piring: Sayur dan buah 🍽️ Seperempat piring: Karbohidrat (seperti nasi atau roti)
|
||||
🍽️ Seperempat piring: Protein (seperti ayam atau tempe) 🍽️ Sedikit lemak sehat
|
||||
""",
|
||||
},
|
||||
];
|
||||
List<Map<String, dynamic>> filteredCollections =
|
||||
final List<Map<String, dynamic>> filteredCollections =
|
||||
_collections
|
||||
.where(
|
||||
(item) => item["label"].toLowerCase().contains(
|
||||
@@ -104,415 +160,239 @@ class _ListEducationState extends State<ListEducation> {
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
actions: <Widget>[],
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header Section
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(225, 79, 76, 182),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Container(
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.search,
|
||||
color: Color.fromARGB(137, 180, 172, 172),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged:
|
||||
filterSearch, // Memanggil filter saat mengetik
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Cari pelajaran...',
|
||||
hintStyle: TextStyle(color: Colors.black54),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (searchController.text.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.black54,
|
||||
),
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
filterSearch('');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children:
|
||||
filteredCollections.map((item) {
|
||||
return CollectionContainer(
|
||||
label: item["label"]!,
|
||||
imageSvg: item["image"]!,
|
||||
iconw: 20,
|
||||
iconh: 20,
|
||||
width: 70,
|
||||
height: 70,
|
||||
textColor: Colors.black,
|
||||
lblSize: 10,
|
||||
colorContiner: ColorHelper.hexToColor(item["color"]),
|
||||
onTapAc:
|
||||
() => {
|
||||
searchController.clear(),
|
||||
filterSearch(item["category"]),
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_shouldShowWebView && _controller != null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
|
||||
// Search Bar
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
// child: Container(
|
||||
// height: 47,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(20),
|
||||
// color: Colors.grey.shade200,
|
||||
// ),
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
// child: Row(
|
||||
// children: [
|
||||
// const Icon(
|
||||
// Icons.search,
|
||||
// color: Color.fromARGB(137, 180, 172, 172),
|
||||
// size: 18,
|
||||
// ),
|
||||
// const SizedBox(width: 8),
|
||||
// Expanded(
|
||||
// child: TextField(
|
||||
// controller: searchController,
|
||||
// onChanged: filterSearch, // Memanggil filter saat mengetik
|
||||
// style: const TextStyle(color: Colors.black, fontSize: 14),
|
||||
// decoration: const InputDecoration(
|
||||
// border: InputBorder.none,
|
||||
// hintText: 'Cari pelajaran...',
|
||||
// hintStyle: TextStyle(color: Colors.black54),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// if (searchController.text.isNotEmpty)
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.close, color: Colors.black54),
|
||||
// onPressed: () {
|
||||
// searchController.clear();
|
||||
// filterSearch('');
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// Collection Items dengan Filter
|
||||
SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: Container(
|
||||
actions: <Widget>[],
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header Section
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(224, 145, 145, 155),
|
||||
color: const Color.fromARGB(225, 79, 76, 182),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(25),
|
||||
bottomLeft: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 5),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width / 4,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Divider(
|
||||
color: const Color.fromARGB(255, 155, 147, 147),
|
||||
height: 20,
|
||||
child: Container(
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.search,
|
||||
color: Color.fromARGB(137, 180, 172, 172),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged:
|
||||
filterSearch, // Memanggil filter saat mengetik
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Cari pelajaran...',
|
||||
hintStyle: TextStyle(color: Colors.black54),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (searchController.text.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.black54,
|
||||
),
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
filterSearch('');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
// vertical: 10,
|
||||
),
|
||||
child:
|
||||
filteredItems.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
"Tidak ada hasil",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 20.0,
|
||||
left: 20,
|
||||
right: 20,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
222,
|
||||
181,
|
||||
133,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
right: 20,
|
||||
left: 20,
|
||||
),
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 1, // 2 Kolom
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
childAspectRatio: 2.5,
|
||||
),
|
||||
itemCount: _listContent.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
child: CardList(
|
||||
title:
|
||||
_listContent[index]["title"] ??
|
||||
"",
|
||||
body:
|
||||
_listContent[index]["body"] ??
|
||||
"",
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(
|
||||
context,
|
||||
) => ListDetailScreen(
|
||||
title:
|
||||
filteredItems[index],
|
||||
imagePath:
|
||||
filteredItems[index],
|
||||
/* paragraphs: [
|
||||
"""
|
||||
<h1>Enter the main heading, usually the same as the title.</h1>
|
||||
<p>Be <b>bold</b> in stating your key points. Put them in a list: </p>
|
||||
<ul>
|
||||
<li>The first item in your list</li>
|
||||
<li>The second item; <i>italicize</i> key words</li>
|
||||
</ul>
|
||||
<p>Improve your image by including an image. </p>
|
||||
<p><img src="https://via.placeholder.com/150" alt="A Great HTML Resource"></p>
|
||||
<p>Add a link to your favorite <a href="https://www.dummies.com/">Web site</a>.</p>
|
||||
<hr>
|
||||
<p>Finally, link to <a href="page2.html">another page</a> in your own Web site.</p>
|
||||
<p>© Wiley Publishing, 2011</p>
|
||||
""",
|
||||
], */
|
||||
paragraphs: """
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Furikake: Taburan Lezat untuk Nasi!</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
max-width: 100%;
|
||||
max-height:100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #d32f2f;
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
margin: 10px 0 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #f57c00;
|
||||
font-size: 1.3rem;
|
||||
margin: 20px 0 10px 0;
|
||||
font-weight: 500;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
font-size: 0.95rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.kanji {
|
||||
font-size: 2.2rem;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin: 10px 0;
|
||||
font-family: 'Noto Sans JP', sans-serif;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.main-image {
|
||||
width: 100%;
|
||||
margin: 15px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main-image img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.main-image p {
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.ingredients {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.ingredient {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ingredient img {
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ingredient p {
|
||||
font-size: 0.85rem;
|
||||
margin-top: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #f5f5f5;
|
||||
padding: 12px;
|
||||
margin: 15px 0;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Furikake: Taburan Lezat untuk Nasi!</h1>
|
||||
|
||||
<div class="kanji">振り掛け</div>
|
||||
|
||||
<p>Tahukah kamu bahwa di Jepang ada bumbu tabur yang
|
||||
bisa membuat nasi menjadi lebih enak? Namanya Furikake! Kata "furikake" berasal dari bahasa Jepang,
|
||||
yaitu "furi" yang berarti menaburkan dan "kake" yang berarti menuangkan.
|
||||
Jadi, furikake adalah bumbu yang ditaburkan di atas nasi supaya lebih lezat!</p>
|
||||
""",
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children:
|
||||
filteredCollections.map((item) {
|
||||
return CollectionContainer(
|
||||
label: item["label"]!,
|
||||
imageSvg: item["image"]!,
|
||||
iconw: 20,
|
||||
iconh: 20,
|
||||
width: 70,
|
||||
height: 70,
|
||||
textColor: Colors.black,
|
||||
lblSize: 10,
|
||||
colorContiner: ColorHelper.hexToColor(
|
||||
item["color"],
|
||||
),
|
||||
onTapAc:
|
||||
() => {
|
||||
searchController.clear(),
|
||||
filterByCategory(item["category"]),
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bottom Navigation
|
||||
// MainMenu(),
|
||||
],
|
||||
),
|
||||
);
|
||||
// Collection Items dengan Filter
|
||||
SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(223, 200, 200, 206),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 5),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width / 4,
|
||||
),
|
||||
child: Divider(
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
thickness: 2,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
// vertical: 10,
|
||||
),
|
||||
child:
|
||||
filteredItems.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
"Tidak ada hasil",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 10.0,
|
||||
left: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
222,
|
||||
181,
|
||||
133,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
right: 20,
|
||||
left: 20,
|
||||
),
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 1, // 2 Kolom
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
childAspectRatio: 2.5,
|
||||
),
|
||||
itemCount: _listContent.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
child: CardList(
|
||||
title:
|
||||
_listContent[index]["title"] ??
|
||||
"",
|
||||
body:
|
||||
_listContent[index]["body"] ??
|
||||
"",
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(
|
||||
context,
|
||||
) => listDetailExtension.buildPage({
|
||||
'title':
|
||||
filteredItems[index]['category'],
|
||||
'imagePath':
|
||||
filteredItems[index]['image'],
|
||||
'filepath':
|
||||
filteredItems[index]['file'],
|
||||
'paragraphs':
|
||||
"""
|
||||
""" +
|
||||
"<h1> ${_listContent[index]["title"]} </h1> <p> ${_listContent[index]["body"]}",
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(40),
|
||||
child: Text("WebView tidak tersedia untuk platform ini."),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
|
||||
class ListDetailScreen extends StatefulWidget {
|
||||
final String title;
|
||||
final String imagePath;
|
||||
final String paragraphs;
|
||||
final String filepath;
|
||||
|
||||
const ListDetailScreen({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.imagePath,
|
||||
required this.paragraphs,
|
||||
required this.filepath,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -19,11 +22,13 @@ class ListDetailScreen extends StatefulWidget {
|
||||
|
||||
class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
late List<bool> _readStatus;
|
||||
String _htmlContent = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_readStatus = List.generate(widget.paragraphs.length, (index) => false);
|
||||
loadHtml();
|
||||
}
|
||||
|
||||
void _toggleReadStatus(int index) {
|
||||
@@ -32,6 +37,15 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> loadHtml() async {
|
||||
String html = await DefaultAssetBundle.of(
|
||||
context,
|
||||
).loadString(widget.filepath);
|
||||
setState(() {
|
||||
_htmlContent = html;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -85,7 +99,7 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
// ),
|
||||
const SizedBox(height: 0),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(left: 18.0, right: 18, bottom: 18),
|
||||
child:
|
||||
// ListView.builder(
|
||||
@@ -156,7 +170,46 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
// ),
|
||||
Html(
|
||||
style: {"body": Style(color: const Color.fromRGBO(0, 0, 0, 1))},
|
||||
data: widget.paragraphs,
|
||||
// data: widget.paragraphs,
|
||||
data: _htmlContent,
|
||||
extensions: [
|
||||
TagExtension(
|
||||
tagsToExtend: {"img"},
|
||||
builder: (ExtensionContext context) {
|
||||
final attrs = context.attributes;
|
||||
final src = attrs['src'] ?? '';
|
||||
|
||||
if (src.isNotEmpty) {
|
||||
if (src.startsWith('assets/html/')) {
|
||||
return Image.asset(
|
||||
src,
|
||||
width: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Text('Image not found'),
|
||||
);
|
||||
} else if (!src.startsWith('http')) {
|
||||
return Image.asset(
|
||||
'assets/images/$src',
|
||||
width: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Text('Image not found'),
|
||||
);
|
||||
} else {
|
||||
return Image.network(
|
||||
src,
|
||||
width: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Text('Image failed to load'),
|
||||
);
|
||||
}
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -18,6 +18,8 @@ class AkunSaya extends StatefulWidget {
|
||||
class _AkunSayaState extends State<AkunSaya> {
|
||||
dynamic _profileImage;
|
||||
dynamic _headerImage;
|
||||
ImageProvider? imageProvider;
|
||||
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
Future<void> _pickImage(bool isProfile) async {
|
||||
@@ -35,13 +37,19 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
} else {
|
||||
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
final imageFile = File(pickedFile.path);
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = File(pickedFile.path);
|
||||
_profileImage = imageFile;
|
||||
} else {
|
||||
_headerImage = File(pickedFile.path);
|
||||
_headerImage = imageFile;
|
||||
}
|
||||
|
||||
imageProvider =
|
||||
kIsWeb ? MemoryImage(_profileImage) : FileImage(_profileImage);
|
||||
});
|
||||
} else {
|
||||
imageProvider = AssetImage("assets/images/luffy.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +105,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 40),
|
||||
children: [
|
||||
TextFormField(
|
||||
initialValue: "Cepot",
|
||||
initialValue: "Luffy Kun",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Nama',
|
||||
@@ -106,7 +114,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "cpt09",
|
||||
initialValue: "luffy01",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Nama Pengguna',
|
||||
@@ -169,7 +177,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
: null,
|
||||
child:
|
||||
_profileImage == null
|
||||
? Icon(Icons.person, size: 50, color: Colors.white)
|
||||
? Image.asset("assets/images/luffy.png")
|
||||
: null,
|
||||
),
|
||||
CircleAvatar(
|
||||
@@ -184,14 +192,14 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
|
||||
Positioned(
|
||||
top: 130,
|
||||
left: MediaQuery.of(context).size.width / 2 - 50,
|
||||
left: MediaQuery.of(context).size.width / 2 - 30,
|
||||
child: GestureDetector(
|
||||
onTap: () => _pickImage(true),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
Text(
|
||||
"Nama Akun",
|
||||
"Luffy kun",
|
||||
style: TextStyle(
|
||||
color: Colors.deepPurple,
|
||||
fontSize: 18,
|
||||
|
||||
@@ -24,6 +24,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
// final ImagePicker _picker = ImagePicker();
|
||||
dynamic _profileImage;
|
||||
dynamic _headerImage;
|
||||
ImageProvider? imageProvider;
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
Future<void> _pickImage(bool isProfile) async {
|
||||
@@ -41,13 +42,19 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
} else {
|
||||
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
final imageFile = File(pickedFile.path);
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = File(pickedFile.path);
|
||||
_profileImage = imageFile;
|
||||
} else {
|
||||
_headerImage = File(pickedFile.path);
|
||||
_headerImage = imageFile;
|
||||
}
|
||||
|
||||
imageProvider =
|
||||
kIsWeb ? MemoryImage(_profileImage) : FileImage(_profileImage);
|
||||
});
|
||||
} else {
|
||||
imageProvider = AssetImage("assets/images/luffy.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,10 +142,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
: null,
|
||||
child:
|
||||
_profileImage == null
|
||||
? Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: Colors.white,
|
||||
? Image.asset(
|
||||
"assets/images/luffy.png",
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -162,9 +167,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Name",
|
||||
"Luffy",
|
||||
style: TextStyle(
|
||||
color: Colors.blueAccent,
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -172,9 +184,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Edit Profile",
|
||||
"Luffy Kun",
|
||||
style: TextStyle(
|
||||
color: Colors.blueAccent,
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user