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/presentation/viewmodel/about_vmod.dart';
|
||||||
export 'package:rijig_mobile/features/home/repositories/about_repository.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/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(() => AboutViewModel(AboutService(AboutRepository())));
|
||||||
sl.registerFactory(() => AboutDetailViewModel(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(
|
return GoogleFonts.spaceMono(
|
||||||
fontSize: 16.sp,
|
fontSize: fontsize?.sp ?? 16.sp,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: color ?? blackNavyColor,
|
color: color ?? blackNavyColor,
|
||||||
);
|
);
|
||||||
|
@ -47,6 +47,18 @@ class Tulisan {
|
||||||
color: color ?? blackNavyColor,
|
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=====================
|
// =====================padding custom=====================
|
||||||
|
|
|
@ -91,12 +91,12 @@ class _NavigationPageState extends State<NavigationPage> {
|
||||||
items: [
|
items: [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Iconsax.home_2),
|
icon: Icon(Iconsax.home_2),
|
||||||
activeIcon: Icon(Iconsax.home_2, size: 28),
|
activeIcon: Icon(Iconsax.home_2, size: 26),
|
||||||
label: 'Beranda',
|
label: 'Beranda',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Iconsax.note_favorite),
|
icon: Icon(Iconsax.note_favorite),
|
||||||
activeIcon: Icon(Iconsax.note_favorite, size: 28),
|
activeIcon: Icon(Iconsax.note_favorite, size: 26),
|
||||||
label: 'Aktivitas',
|
label: 'Aktivitas',
|
||||||
),
|
),
|
||||||
const BottomNavigationBarItem(
|
const BottomNavigationBarItem(
|
||||||
|
@ -105,45 +105,61 @@ class _NavigationPageState extends State<NavigationPage> {
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Iconsax.shopping_cart),
|
icon: Icon(Iconsax.shopping_cart),
|
||||||
activeIcon: Icon(Iconsax.shopping_cart, size: 28),
|
activeIcon: Icon(Iconsax.shopping_cart, size: 26),
|
||||||
label: 'Keranjang',
|
label: 'Keranjang',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Iconsax.user),
|
icon: Icon(Iconsax.user),
|
||||||
activeIcon: Icon(Iconsax.user, size: 28),
|
activeIcon: Icon(Iconsax.user, size: 26),
|
||||||
label: 'Profil',
|
label: 'Profil',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
selectedLabelStyle: const TextStyle(fontSize: 14),
|
selectedLabelStyle: Tulisan.customText(
|
||||||
unselectedLabelStyle: const TextStyle(fontSize: 12),
|
color: secondaryColor,
|
||||||
|
fontsize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
unselectedLabelStyle: Tulisan.customText(
|
||||||
|
color: whiteColor,
|
||||||
|
fontsize: 12,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
floatingActionButton: SizedBox(
|
floatingActionButton: Container(
|
||||||
width: 78,
|
width: 78,
|
||||||
height: 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: () {
|
onPressed: () {
|
||||||
router.push("/requestpickup");
|
router.push("/requestpickup");
|
||||||
},
|
},
|
||||||
backgroundColor: primaryColor,
|
shape: const CircleBorder(),
|
||||||
shape: const CircleBorder(
|
|
||||||
side: BorderSide(color: Colors.white, width: 4),
|
|
||||||
),
|
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
highlightElevation: 0,
|
|
||||||
hoverColor: Colors.blue,
|
|
||||||
splashColor: Colors.transparent,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
children: [
|
||||||
Icon(Iconsax.archive_2, color: whiteColor, size: 30),
|
Icon(Iconsax.archive_2, color: Colors.white, size: 30),
|
||||||
Text("mulai", style: TextStyle(color: whiteColor)),
|
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 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
|
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:provider/provider.dart';
|
||||||
import 'package:rijig_mobile/core/utils/guide.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/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/features/home/presentation/viewmodel/about_vmod.dart';
|
||||||
|
import 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
||||||
import 'package:rijig_mobile/widget/card_withicon.dart';
|
import 'package:rijig_mobile/widget/card_withicon.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
|
@ -28,6 +32,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).getAboutList();
|
).getAboutList();
|
||||||
|
await Provider.of<ArticleViewModel>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
).loadArticles();
|
||||||
},
|
},
|
||||||
backgroundColor: whiteColor,
|
backgroundColor: whiteColor,
|
||||||
indicatorBuilder: (context, controller) {
|
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<TrashViewModel>()),
|
||||||
ChangeNotifierProvider(create: (_) => sl<AboutViewModel>()),
|
ChangeNotifierProvider(create: (_) => sl<AboutViewModel>()),
|
||||||
ChangeNotifierProvider(create: (_) => sl<AboutDetailViewModel>()),
|
ChangeNotifierProvider(create: (_) => sl<AboutDetailViewModel>()),
|
||||||
|
ChangeNotifierProvider(create: (_) => sl<ArticleViewModel>()),
|
||||||
],
|
],
|
||||||
child: ScreenUtilInit(
|
child: ScreenUtilInit(
|
||||||
designSize: const Size(375, 812),
|
designSize: const Size(375, 812),
|
||||||
|
|
Loading…
Reference in New Issue