feat&refact: improve stle and add feature article(uncompleted)
This commit is contained in:
parent
7d3f129748
commit
4f84abfeee
|
@ -14,3 +14,6 @@ export 'package:rijig_mobile/globaldata/trash/trash_viewmodel.dart';
|
|||
export 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
||||
export 'package:rijig_mobile/features/home/repositories/about_repository.dart';
|
||||
export 'package:rijig_mobile/features/home/service/about_service.dart';
|
||||
export 'package:rijig_mobile/globaldata/article/article_repository.dart';
|
||||
export 'package:rijig_mobile/globaldata/article/article_service.dart';
|
||||
export 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
||||
|
|
|
@ -12,4 +12,5 @@ void init() {
|
|||
);
|
||||
sl.registerFactory(() => AboutViewModel(AboutService(AboutRepository())));
|
||||
sl.registerFactory(() => AboutDetailViewModel(AboutService(AboutRepository())));
|
||||
sl.registerFactory(() => ArticleViewModel(ArticleService(ArticleRepository())));
|
||||
}
|
||||
|
|
|
@ -32,9 +32,9 @@ class Tulisan {
|
|||
);
|
||||
}
|
||||
|
||||
static TextStyle body({Color? color}) {
|
||||
static TextStyle body({Color? color, double? fontsize}) {
|
||||
return GoogleFonts.spaceMono(
|
||||
fontSize: 16.sp,
|
||||
fontSize: fontsize?.sp ?? 16.sp,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: color ?? blackNavyColor,
|
||||
);
|
||||
|
@ -47,6 +47,18 @@ class Tulisan {
|
|||
color: color ?? blackNavyColor,
|
||||
);
|
||||
}
|
||||
|
||||
static TextStyle customText({
|
||||
Color? color,
|
||||
double? fontsize,
|
||||
FontWeight? fontWeight,
|
||||
}) {
|
||||
return GoogleFonts.spaceGrotesk(
|
||||
fontSize: fontsize?.sp ?? 16.sp,
|
||||
fontWeight: fontWeight ?? FontWeight.w400,
|
||||
color: color ?? blackNavyColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================padding custom=====================
|
||||
|
|
|
@ -91,12 +91,12 @@ class _NavigationPageState extends State<NavigationPage> {
|
|||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Iconsax.home_2),
|
||||
activeIcon: Icon(Iconsax.home_2, size: 28),
|
||||
activeIcon: Icon(Iconsax.home_2, size: 26),
|
||||
label: 'Beranda',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Iconsax.note_favorite),
|
||||
activeIcon: Icon(Iconsax.note_favorite, size: 28),
|
||||
activeIcon: Icon(Iconsax.note_favorite, size: 26),
|
||||
label: 'Aktivitas',
|
||||
),
|
||||
const BottomNavigationBarItem(
|
||||
|
@ -105,45 +105,61 @@ class _NavigationPageState extends State<NavigationPage> {
|
|||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Iconsax.shopping_cart),
|
||||
activeIcon: Icon(Iconsax.shopping_cart, size: 28),
|
||||
activeIcon: Icon(Iconsax.shopping_cart, size: 26),
|
||||
label: 'Keranjang',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Iconsax.user),
|
||||
activeIcon: Icon(Iconsax.user, size: 28),
|
||||
activeIcon: Icon(Iconsax.user, size: 26),
|
||||
label: 'Profil',
|
||||
),
|
||||
],
|
||||
selectedLabelStyle: const TextStyle(fontSize: 14),
|
||||
unselectedLabelStyle: const TextStyle(fontSize: 12),
|
||||
selectedLabelStyle: Tulisan.customText(
|
||||
color: secondaryColor,
|
||||
fontsize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelStyle: Tulisan.customText(
|
||||
color: whiteColor,
|
||||
fontsize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||
floatingActionButton: SizedBox(
|
||||
floatingActionButton: Container(
|
||||
width: 78,
|
||||
height: 78,
|
||||
child: FloatingActionButton(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [secondaryColor, primaryColor],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
border: Border.all(color: whiteColor, width: 4),
|
||||
),
|
||||
child: RawMaterialButton(
|
||||
onPressed: () {
|
||||
router.push("/requestpickup");
|
||||
},
|
||||
backgroundColor: primaryColor,
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(color: Colors.white, width: 4),
|
||||
),
|
||||
shape: const CircleBorder(),
|
||||
elevation: 0,
|
||||
highlightElevation: 0,
|
||||
hoverColor: Colors.blue,
|
||||
splashColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Icon(Iconsax.archive_2, color: whiteColor, size: 30),
|
||||
Text("mulai", style: TextStyle(color: whiteColor)),
|
||||
Icon(Iconsax.archive_2, color: Colors.white, size: 30),
|
||||
Text(
|
||||
"Mulai",
|
||||
style: Tulisan.customText(
|
||||
color: whiteColor,
|
||||
fontsize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
||||
|
||||
class ArticleScreen extends StatefulWidget {
|
||||
const ArticleScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ArticleScreen> createState() => _ArticleScreenState();
|
||||
}
|
||||
|
||||
class _ArticleScreenState extends State<ArticleScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Fetch data setelah frame build pertama
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Provider.of<ArticleViewModel>(context, listen: false).loadArticles();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String? baseUrl = dotenv.env["BASE_URL"];
|
||||
|
||||
return Consumer<ArticleViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text("Error: ${viewModel.errorMessage}"));
|
||||
}
|
||||
|
||||
if (viewModel.articles.isEmpty) {
|
||||
return const Center(child: Text("Tidak ada artikel ditemukan."));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: viewModel.articles.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final article = viewModel.articles[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 3,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.network(
|
||||
"$baseUrl${article.coverImage}",
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Icon(Icons.image_not_supported),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
article.heading,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
"By ${article.author} • ${article.publishedAt}",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
onTap: () {
|
||||
// Navigasi ke detail (jika tersedia)
|
||||
// router.push('/article-detail', extra: article.articleId);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
|
||||
|
@ -7,7 +9,9 @@ import 'package:iconsax_flutter/iconsax_flutter.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:rijig_mobile/core/utils/guide.dart';
|
||||
import 'package:rijig_mobile/features/home/presentation/components/about_comp.dart';
|
||||
import 'package:rijig_mobile/features/home/presentation/components/article_list.dart';
|
||||
import 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
||||
import 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
||||
import 'package:rijig_mobile/widget/card_withicon.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
|
@ -28,6 +32,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
context,
|
||||
listen: false,
|
||||
).getAboutList();
|
||||
await Provider.of<ArticleViewModel>(
|
||||
context,
|
||||
listen: false,
|
||||
).loadArticles();
|
||||
},
|
||||
backgroundColor: whiteColor,
|
||||
indicatorBuilder: (context, controller) {
|
||||
|
@ -130,9 +138,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
],
|
||||
),
|
||||
),
|
||||
Gap(15),
|
||||
ArticleScreen(),
|
||||
],
|
||||
),
|
||||
Gap(20),
|
||||
// Gap(20),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
class ArticleModel {
|
||||
final String articleId;
|
||||
final String title;
|
||||
final String coverImage;
|
||||
final String author;
|
||||
final String heading;
|
||||
final String content;
|
||||
final String publishedAt;
|
||||
final String updatedAt;
|
||||
|
||||
ArticleModel({
|
||||
required this.articleId,
|
||||
required this.title,
|
||||
required this.coverImage,
|
||||
required this.author,
|
||||
required this.heading,
|
||||
required this.content,
|
||||
required this.publishedAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory ArticleModel.fromJson(Map<String, dynamic> json) {
|
||||
return ArticleModel(
|
||||
articleId: json['article_id'],
|
||||
title: json['title'],
|
||||
coverImage: json['coverImage'],
|
||||
author: json['author'],
|
||||
heading: json['heading'],
|
||||
content: json['content'],
|
||||
publishedAt: json['publishedAt'],
|
||||
updatedAt: json['updatedAt'],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:rijig_mobile/core/api/api_services.dart';
|
||||
import 'package:rijig_mobile/globaldata/article/article_model.dart';
|
||||
|
||||
class ArticleRepository {
|
||||
final Https _https = Https();
|
||||
|
||||
Future<List<ArticleModel>> fetchArticles() async {
|
||||
final response = await _https.get('/article-rijik/view-article');
|
||||
debugPrint("reponse article: $response");
|
||||
final List data = response['data'];
|
||||
return data.map((json) => ArticleModel.fromJson(json)).toList();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
import 'package:rijig_mobile/globaldata/article/article_model.dart';
|
||||
import 'package:rijig_mobile/globaldata/article/article_repository.dart';
|
||||
|
||||
class ArticleService {
|
||||
final ArticleRepository _repository;
|
||||
|
||||
ArticleService(this._repository);
|
||||
|
||||
Future<List<ArticleModel>> getAllArticles() async {
|
||||
try {
|
||||
return await _repository.fetchArticles();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load articles: $e');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:rijig_mobile/globaldata/article/article_model.dart';
|
||||
import 'package:rijig_mobile/globaldata/article/article_service.dart';
|
||||
|
||||
class ArticleViewModel extends ChangeNotifier {
|
||||
final ArticleService _articleService;
|
||||
|
||||
ArticleViewModel(this._articleService);
|
||||
|
||||
List<ArticleModel> _articles = [];
|
||||
List<ArticleModel> get articles => _articles;
|
||||
|
||||
bool isLoading = false;
|
||||
String? errorMessage;
|
||||
|
||||
Future<void> loadArticles() async {
|
||||
isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
_articles = await _articleService.getAllArticles();
|
||||
} catch (e) {
|
||||
errorMessage = e.toString();
|
||||
} finally {
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ class MyApp extends StatelessWidget {
|
|||
ChangeNotifierProvider(create: (_) => sl<TrashViewModel>()),
|
||||
ChangeNotifierProvider(create: (_) => sl<AboutViewModel>()),
|
||||
ChangeNotifierProvider(create: (_) => sl<AboutDetailViewModel>()),
|
||||
ChangeNotifierProvider(create: (_) => sl<ArticleViewModel>()),
|
||||
],
|
||||
child: ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
|
|
Loading…
Reference in New Issue