feat: add feature about detail and prepare repsponse
This commit is contained in:
parent
0fbbb807d9
commit
9bbfd4529a
|
|
@ -11,3 +11,6 @@ export 'package:rijig_mobile/features/auth/service/otp_service.dart';
|
||||||
export 'package:rijig_mobile/globaldata/trash/trash_repository.dart';
|
export 'package:rijig_mobile/globaldata/trash/trash_repository.dart';
|
||||||
export 'package:rijig_mobile/globaldata/trash/trash_service.dart';
|
export 'package:rijig_mobile/globaldata/trash/trash_service.dart';
|
||||||
export 'package:rijig_mobile/globaldata/trash/trash_viewmodel.dart';
|
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';
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,9 @@ void init() {
|
||||||
sl.registerFactory(() => OtpViewModel(OtpService(OtpRepository())));
|
sl.registerFactory(() => OtpViewModel(OtpService(OtpRepository())));
|
||||||
sl.registerFactory(() => LogoutViewModel(LogoutService(LogoutRepository())));
|
sl.registerFactory(() => LogoutViewModel(LogoutService(LogoutRepository())));
|
||||||
|
|
||||||
sl.registerFactory(() => TrashViewModel(TrashCategoryService(TrashCategoryRepository())));
|
sl.registerFactory(
|
||||||
|
() => TrashViewModel(TrashCategoryService(TrashCategoryRepository())),
|
||||||
|
);
|
||||||
|
sl.registerFactory(() => AboutViewModel(AboutService(AboutRepository())));
|
||||||
|
sl.registerFactory(() => AboutDetailViewModel(AboutService(AboutRepository())));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,5 +39,13 @@ final router = GoRouter(
|
||||||
),
|
),
|
||||||
GoRoute(path: '/cart', builder: (context, state) => CartScreen()),
|
GoRoute(path: '/cart', builder: (context, state) => CartScreen()),
|
||||||
GoRoute(path: '/profil', builder: (context, state) => ProfilScreen()),
|
GoRoute(path: '/profil', builder: (context, state) => ProfilScreen()),
|
||||||
|
|
||||||
|
GoRoute(
|
||||||
|
path: '/aboutdetail',
|
||||||
|
builder: (context, state) {
|
||||||
|
dynamic data = state.extra;
|
||||||
|
return AboutDetailScreenComp(data: data);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ export 'package:go_router/go_router.dart';
|
||||||
export 'package:rijig_mobile/core/utils/navigation.dart';
|
export 'package:rijig_mobile/core/utils/navigation.dart';
|
||||||
export 'package:rijig_mobile/features/activity/activity_screen.dart';
|
export 'package:rijig_mobile/features/activity/activity_screen.dart';
|
||||||
export 'package:rijig_mobile/features/cart/cart_screen.dart';
|
export 'package:rijig_mobile/features/cart/cart_screen.dart';
|
||||||
export 'package:rijig_mobile/features/home/home_screen.dart';
|
export 'package:rijig_mobile/features/home/presentation/screen/home_screen.dart';
|
||||||
export 'package:rijig_mobile/features/profil/profil_screen.dart';
|
export 'package:rijig_mobile/features/profil/profil_screen.dart';
|
||||||
export 'package:rijig_mobile/features/requestpick/requestpickup_screen.dart';
|
export 'package:rijig_mobile/features/requestpick/presentation/screen/requestpickup_screen.dart';
|
||||||
export 'package:rijig_mobile/features/auth/presentation/screen/inputpin_screen.dart';
|
export 'package:rijig_mobile/features/auth/presentation/screen/inputpin_screen.dart';
|
||||||
export 'package:rijig_mobile/features/auth/presentation/screen/login_screen.dart';
|
export 'package:rijig_mobile/features/auth/presentation/screen/login_screen.dart';
|
||||||
export 'package:rijig_mobile/features/auth/presentation/screen/otp_screen.dart';
|
export 'package:rijig_mobile/features/auth/presentation/screen/otp_screen.dart';
|
||||||
export 'package:rijig_mobile/features/auth/presentation/screen/verifpin_screen.dart';
|
export 'package:rijig_mobile/features/auth/presentation/screen/verifpin_screen.dart';
|
||||||
export 'package:rijig_mobile/features/launch/onboardingpage_screen.dart';
|
export 'package:rijig_mobile/features/launch/onboardingpage_screen.dart';
|
||||||
export 'package:rijig_mobile/features/launch/splash_screen.dart';
|
export 'package:rijig_mobile/features/launch/splash_screen.dart';
|
||||||
|
export 'package:rijig_mobile/features/home/presentation/components/about_detail_comp.dart';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:rijig_mobile/core/utils/guide.dart';
|
||||||
import 'package:rijig_mobile/core/router.dart';
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
import 'package:rijig_mobile/features/activity/activity_screen.dart';
|
import 'package:rijig_mobile/features/activity/activity_screen.dart';
|
||||||
import 'package:rijig_mobile/features/cart/cart_screen.dart';
|
import 'package:rijig_mobile/features/cart/cart_screen.dart';
|
||||||
import 'package:rijig_mobile/features/home/home_screen.dart';
|
import 'package:rijig_mobile/features/home/presentation/screen/home_screen.dart';
|
||||||
import 'package:rijig_mobile/features/profil/profil_screen.dart';
|
import 'package:rijig_mobile/features/profil/profil_screen.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:iconsax_flutter/iconsax_flutter.dart';
|
|
||||||
import 'package:rijig_mobile/core/utils/guide.dart';
|
|
||||||
import 'package:rijig_mobile/features/home/components/product_card.dart';
|
|
||||||
import 'package:rijig_mobile/features/home/model/product.dart';
|
|
||||||
import 'package:rijig_mobile/widget/card_withicon.dart';
|
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
|
||||||
const HomeScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<HomeScreen> createState() => _HomeScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: whiteColor,
|
|
||||||
body: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: PaddingCustom().paddingHorizontalVertical(16, 20),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Rijig",
|
|
||||||
style: Tulisan.heading(color: primaryColor),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Icon(Iconsax.notification),
|
|
||||||
Gap(10),
|
|
||||||
Icon(Iconsax.message_2),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Gap(20),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
CardWithIcon(
|
|
||||||
icon: Icons.account_circle,
|
|
||||||
text: 'Users',
|
|
||||||
number: '245',
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
CardWithIcon(
|
|
||||||
icon: Icons.shopping_cart,
|
|
||||||
text: 'Orders',
|
|
||||||
number: '178',
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Gap(20),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Important!",
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(15),
|
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SpecialOfferCard(
|
|
||||||
image: "assets/image/Image Banner 2.png",
|
|
||||||
category: "Smartphone",
|
|
||||||
numOfBrands: 18,
|
|
||||||
press: () {},
|
|
||||||
),
|
|
||||||
Gap(10),
|
|
||||||
SpecialOfferCard(
|
|
||||||
image: "assets/image/Image Banner 3.png",
|
|
||||||
category: "Fashion",
|
|
||||||
numOfBrands: 24,
|
|
||||||
press: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Gap(20),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Artikel",
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
...List.generate(demoProducts.length, (index) {
|
|
||||||
if (demoProducts[index].isPopular) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 20),
|
|
||||||
child: ProductCard(
|
|
||||||
product: demoProducts[index],
|
|
||||||
onPress: () {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}),
|
|
||||||
const SizedBox(width: 20),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpecialOfferCard extends StatelessWidget {
|
|
||||||
const SpecialOfferCard({
|
|
||||||
super.key,
|
|
||||||
required this.category,
|
|
||||||
required this.image,
|
|
||||||
required this.numOfBrands,
|
|
||||||
required this.press,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String category, image;
|
|
||||||
final int numOfBrands;
|
|
||||||
final GestureTapCallback press;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: press,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 242,
|
|
||||||
height: 100,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Image.asset(image, fit: BoxFit.cover),
|
|
||||||
Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Colors.black54,
|
|
||||||
Colors.black38,
|
|
||||||
Colors.black26,
|
|
||||||
Colors.transparent,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 15,
|
|
||||||
vertical: 10,
|
|
||||||
),
|
|
||||||
child: Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: "$category\n",
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextSpan(text: "$numOfBrands Brands"),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
class AboutModel {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String coverImage;
|
||||||
|
final String createdAt;
|
||||||
|
final String updatedAt;
|
||||||
|
|
||||||
|
AboutModel({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.coverImage,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AboutModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AboutModel(
|
||||||
|
id: json['id'],
|
||||||
|
title: json['title'],
|
||||||
|
coverImage: json['cover_image'],
|
||||||
|
createdAt: json['created_at'],
|
||||||
|
updatedAt: json['updated_at'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutDetailModel {
|
||||||
|
final String id;
|
||||||
|
final String aboutId; // nullable jika perlu
|
||||||
|
final String imageDetail;
|
||||||
|
final String description;
|
||||||
|
final String createdAt;
|
||||||
|
final String updatedAt;
|
||||||
|
|
||||||
|
AboutDetailModel({
|
||||||
|
required this.id,
|
||||||
|
required this.aboutId,
|
||||||
|
required this.imageDetail,
|
||||||
|
required this.description,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory AboutDetailModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AboutDetailModel(
|
||||||
|
id: json['id'],
|
||||||
|
aboutId: json['about_id'] ?? '', // Use empty string or null as fallback
|
||||||
|
imageDetail: json['image_detail'],
|
||||||
|
description: json['description'],
|
||||||
|
createdAt: json['created_at'],
|
||||||
|
updatedAt: json['updated_at'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
import 'package:carousel_slider/carousel_slider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
||||||
|
import 'package:rijig_mobile/features/requestpick/presentation/screen/requestpickup_screen.dart';
|
||||||
|
|
||||||
|
class AboutComponent extends StatefulWidget {
|
||||||
|
const AboutComponent({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
AboutComponentState createState() => AboutComponentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutComponentState extends State<AboutComponent> {
|
||||||
|
int _current = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Provider.of<AboutViewModel>(context, listen: false).getAboutList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final String? baseUrl = dotenv.env["BASE_URL"];
|
||||||
|
|
||||||
|
return Consumer<AboutViewModel>(
|
||||||
|
builder: (context, viewModel, child) {
|
||||||
|
if (viewModel.isLoading) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return SkeletonCard();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModel.errorMessage != null) {
|
||||||
|
return Center(child: Text(viewModel.errorMessage!));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModel.aboutList == null || viewModel.aboutList!.isEmpty) {
|
||||||
|
return Center(child: Text("No data available"));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> imageSliders =
|
||||||
|
viewModel.aboutList!.map((about) {
|
||||||
|
return {
|
||||||
|
"iconPath": "$baseUrl${about.coverImage}",
|
||||||
|
"route": about.id,
|
||||||
|
"title": about.title,
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
CarouselSlider(
|
||||||
|
items:
|
||||||
|
imageSliders.map((imageData) {
|
||||||
|
return InkWell(
|
||||||
|
child: Container(
|
||||||
|
height: 150,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: NetworkImage(imageData["iconPath"]),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
top: 10,
|
||||||
|
left: 10,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
child: Text(
|
||||||
|
imageData["title"],
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
debugPrint("Tapped on ${imageData['route']}");
|
||||||
|
// imageData["route"];
|
||||||
|
router.push("/aboutdetail", extra: imageData["route"]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
options: CarouselOptions(
|
||||||
|
autoPlay: true,
|
||||||
|
autoPlayInterval: Duration(seconds: 8),
|
||||||
|
enlargeCenterPage: true,
|
||||||
|
height: 150,
|
||||||
|
onPageChanged: (index, reason) {
|
||||||
|
setState(() {
|
||||||
|
_current = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children:
|
||||||
|
imageSliders.asMap().entries.map((entry) {
|
||||||
|
return GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
width: 8.0,
|
||||||
|
height: 8.0,
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8.0,
|
||||||
|
horizontal: 4.0,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: (Theme.of(context).brightness ==
|
||||||
|
Brightness.dark
|
||||||
|
? Colors.blue
|
||||||
|
: Colors.blue)
|
||||||
|
.withOpacity(_current == entry.key ? 0.9 : 0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
||||||
|
import 'package:rijig_mobile/features/requestpick/presentation/screen/requestpickup_screen.dart';
|
||||||
|
import 'package:rijig_mobile/widget/appbar.dart';
|
||||||
|
|
||||||
|
class AboutDetailScreenComp extends StatefulWidget {
|
||||||
|
final String data;
|
||||||
|
const AboutDetailScreenComp({super.key, required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AboutDetailScreenComp> createState() => _AboutDetailScreenCompState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AboutDetailScreenCompState extends State<AboutDetailScreenComp> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
context.read<AboutDetailViewModel>().getDetail(widget.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final String? baseurl = dotenv.env['BASE_URL'];
|
||||||
|
return Scaffold(
|
||||||
|
appBar: CustomAppBar(judul: "About Detail"),
|
||||||
|
body: Consumer<AboutDetailViewModel>(
|
||||||
|
builder: (context, vm, _) {
|
||||||
|
if (vm.isLoading) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 2,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return SkeletonCard();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (vm.errorMessage != null) return Text(vm.errorMessage!);
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: vm.details.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final detail = vm.details[index];
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Image.network("$baseurl${detail.imageDetail}"),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(detail.description),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:iconsax_flutter/iconsax_flutter.dart';
|
||||||
|
import 'package:rijig_mobile/core/utils/guide.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/presentation/components/about_comp.dart';
|
||||||
|
import 'package:rijig_mobile/widget/card_withicon.dart';
|
||||||
|
|
||||||
|
class HomeScreen extends StatefulWidget {
|
||||||
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: whiteColor,
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: PaddingCustom().paddingHorizontalVertical(16, 20),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Rijig",
|
||||||
|
style: Tulisan.heading(color: primaryColor),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Icon(Iconsax.notification),
|
||||||
|
Gap(10),
|
||||||
|
Icon(Iconsax.message_2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Gap(20),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
CardWithIcon(
|
||||||
|
icon: Icons.account_circle,
|
||||||
|
text: 'Users',
|
||||||
|
number: '245',
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
CardWithIcon(
|
||||||
|
icon: Icons.shopping_cart,
|
||||||
|
text: 'Orders',
|
||||||
|
number: '178',
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Gap(20),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Important!",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(15),
|
||||||
|
Container(
|
||||||
|
height:
|
||||||
|
250, // Tentukan tinggi yang sesuai untuk AboutComponent
|
||||||
|
child: AboutComponent(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Gap(20),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Artikel",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/service/about_service.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/model/about_model.dart';
|
||||||
|
|
||||||
|
class AboutViewModel extends ChangeNotifier {
|
||||||
|
final AboutService _aboutService;
|
||||||
|
|
||||||
|
AboutViewModel(this._aboutService);
|
||||||
|
|
||||||
|
bool isLoading = false;
|
||||||
|
String? errorMessage;
|
||||||
|
List<AboutModel>? aboutList;
|
||||||
|
|
||||||
|
Future<void> getAboutList() async {
|
||||||
|
isLoading = true;
|
||||||
|
errorMessage = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
aboutList = await _aboutService.getAboutList();
|
||||||
|
} catch (e) {
|
||||||
|
errorMessage = "Error: ${e.toString()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutDetailViewModel extends ChangeNotifier {
|
||||||
|
final AboutService service;
|
||||||
|
|
||||||
|
AboutDetailViewModel(this.service);
|
||||||
|
|
||||||
|
bool isLoading = false;
|
||||||
|
String? errorMessage;
|
||||||
|
List<AboutDetailModel> details = [];
|
||||||
|
|
||||||
|
Future<void> getDetail(String aboutId) async {
|
||||||
|
isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
details = await service.getAboutDetail(aboutId);
|
||||||
|
} catch (e) {
|
||||||
|
errorMessage = "Failed to fetch: $e";
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/core/api/api_services.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/model/about_model.dart';
|
||||||
|
|
||||||
|
class AboutRepository {
|
||||||
|
final Https _https = Https();
|
||||||
|
|
||||||
|
Future<List<AboutModel>> getAboutList() async {
|
||||||
|
final response = await _https.get('/about');
|
||||||
|
debugPrint("response about: $response");
|
||||||
|
final List data = response['data'] ?? [];
|
||||||
|
return data.map((e) => AboutModel.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AboutDetailModel>> getAboutDetail(String id) async {
|
||||||
|
final response = await _https.get('/about/$id');
|
||||||
|
debugPrint("response about detail: $response");
|
||||||
|
final List aboutDetail = response['data']['about_detail'] ?? [];
|
||||||
|
return aboutDetail.map((e) => AboutDetailModel.fromJson(e)).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import 'package:rijig_mobile/features/home/repositories/about_repository.dart';
|
||||||
|
import 'package:rijig_mobile/features/home/model/about_model.dart';
|
||||||
|
|
||||||
|
class AboutService {
|
||||||
|
final AboutRepository _aboutRepository;
|
||||||
|
|
||||||
|
AboutService(this._aboutRepository);
|
||||||
|
|
||||||
|
Future<List<AboutModel>> getAboutList() async {
|
||||||
|
try {
|
||||||
|
return await _aboutRepository.getAboutList();
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load About list: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AboutDetailModel>> getAboutDetail(String id) async {
|
||||||
|
try {
|
||||||
|
return await _aboutRepository.getAboutDetail(id);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load About Detail: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ class RequestPickScreen extends StatelessWidget {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
Provider.of<TrashViewModel>(context, listen: false).loadCategories();
|
Provider.of<TrashViewModel>(context, listen: false).loadCategories();
|
||||||
});
|
});
|
||||||
final String? _baseUrl = dotenv.env["BASE_URL"];
|
final String? baseUrl = dotenv.env["BASE_URL"];
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: CustomAppBar(judul: "Pilih sampah"),
|
appBar: CustomAppBar(judul: "Pilih sampah"),
|
||||||
|
|
@ -49,7 +49,7 @@ class RequestPickScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Image.network(
|
leading: Image.network(
|
||||||
"$_baseUrl${category.icon}",
|
"$baseUrl${category.icon}",
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|
@ -31,6 +31,8 @@ class MyApp extends StatelessWidget {
|
||||||
ChangeNotifierProvider(create: (_) => sl<LogoutViewModel>()),
|
ChangeNotifierProvider(create: (_) => sl<LogoutViewModel>()),
|
||||||
|
|
||||||
ChangeNotifierProvider(create: (_) => sl<TrashViewModel>()),
|
ChangeNotifierProvider(create: (_) => sl<TrashViewModel>()),
|
||||||
|
ChangeNotifierProvider(create: (_) => sl<AboutViewModel>()),
|
||||||
|
ChangeNotifierProvider(create: (_) => sl<AboutDetailViewModel>()),
|
||||||
],
|
],
|
||||||
child: ScreenUtilInit(
|
child: ScreenUtilInit(
|
||||||
designSize: const Size(375, 812),
|
designSize: const Size(375, 812),
|
||||||
|
|
|
||||||
24
pubspec.lock
24
pubspec.lock
|
|
@ -25,6 +25,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
carousel_slider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: carousel_slider
|
||||||
|
sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -150,6 +158,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_carousel_widget:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_carousel_widget
|
||||||
|
sha256: "6473e6df04bfafea70efd58251fe5945d5aa8d19461575c1b9d83643f08e0c77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
flutter_dotenv:
|
flutter_dotenv:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -573,6 +589,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
smooth_page_indicator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: smooth_page_indicator
|
||||||
|
sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,14 @@ environment:
|
||||||
sdk: ^3.7.2
|
sdk: ^3.7.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
carousel_slider: ^5.0.0
|
||||||
concentric_transition: ^1.0.3
|
concentric_transition: ^1.0.3
|
||||||
connectivity_plus: ^6.1.4
|
connectivity_plus: ^6.1.4
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
device_info_plus: ^11.4.0
|
device_info_plus: ^11.4.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_carousel_widget: ^3.1.0
|
||||||
flutter_dotenv: ^5.2.1
|
flutter_dotenv: ^5.2.1
|
||||||
flutter_screenutil: ^5.9.3
|
flutter_screenutil: ^5.9.3
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
|
|
@ -30,6 +32,7 @@ dependencies:
|
||||||
provider: ^6.1.4
|
provider: ^6.1.4
|
||||||
shared_preferences: ^2.3.3
|
shared_preferences: ^2.3.3
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
|
smooth_page_indicator: ^1.2.1
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue