feat: add feature get trash category
This commit is contained in:
parent
9f869503b2
commit
0fbbb807d9
|
|
@ -0,0 +1,13 @@
|
|||
export 'package:get_it/get_it.dart';
|
||||
export 'package:rijig_mobile/features/auth/presentation/viewmodel/login_vmod.dart';
|
||||
export 'package:rijig_mobile/features/auth/presentation/viewmodel/logout_vmod.dart';
|
||||
export 'package:rijig_mobile/features/auth/presentation/viewmodel/otp_vmod.dart';
|
||||
export 'package:rijig_mobile/features/auth/repositories/login_repository.dart';
|
||||
export 'package:rijig_mobile/features/auth/repositories/logout_repository.dart';
|
||||
export 'package:rijig_mobile/features/auth/repositories/otp_repository.dart';
|
||||
export 'package:rijig_mobile/features/auth/service/login_service.dart';
|
||||
export 'package:rijig_mobile/features/auth/service/logout_service.dart';
|
||||
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_service.dart';
|
||||
export 'package:rijig_mobile/globaldata/trash/trash_viewmodel.dart';
|
||||
|
|
@ -1,13 +1,4 @@
|
|||
import 'package:get_it/get_it.dart';
|
||||
import 'package:rijig_mobile/features/auth/presentation/viewmodel/login_vmod.dart';
|
||||
import 'package:rijig_mobile/features/auth/presentation/viewmodel/logout_vmod.dart';
|
||||
import 'package:rijig_mobile/features/auth/presentation/viewmodel/otp_vmod.dart';
|
||||
import 'package:rijig_mobile/features/auth/repositories/login_repository.dart';
|
||||
import 'package:rijig_mobile/features/auth/repositories/logout_repository.dart';
|
||||
import 'package:rijig_mobile/features/auth/repositories/otp_repository.dart';
|
||||
import 'package:rijig_mobile/features/auth/service/login_service.dart';
|
||||
import 'package:rijig_mobile/features/auth/service/logout_service.dart';
|
||||
import 'package:rijig_mobile/features/auth/service/otp_service.dart';
|
||||
import 'package:rijig_mobile/core/container/export_vmod.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
|
||||
|
|
@ -15,4 +6,6 @@ void init() {
|
|||
sl.registerFactory(() => LoginViewModel(LoginService(LoginRepository())));
|
||||
sl.registerFactory(() => OtpViewModel(OtpService(OtpRepository())));
|
||||
sl.registerFactory(() => LogoutViewModel(LogoutService(LogoutRepository())));
|
||||
|
||||
sl.registerFactory(() => TrashViewModel(TrashCategoryService(TrashCategoryRepository())));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// ignore_for_file: unrelated_type_equality_checks
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rijig_mobile/core/router.dart';
|
||||
|
|
|
|||
|
|
@ -1,14 +1,88 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rijig_mobile/globaldata/trash/trash_viewmodel.dart';
|
||||
import 'package:rijig_mobile/widget/appbar.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class RequestPickScreen extends StatelessWidget {
|
||||
const RequestPickScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Future.microtask(() {
|
||||
// ignore: use_build_context_synchronously
|
||||
Provider.of<TrashViewModel>(context, listen: false).loadCategories();
|
||||
});
|
||||
final String? _baseUrl = dotenv.env["BASE_URL"];
|
||||
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(judul: "Pilih Sampah"),
|
||||
body: Center(child: Text("pilih sampah anda")),
|
||||
appBar: CustomAppBar(judul: "Pilih sampah"),
|
||||
body: Consumer<TrashViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading) {
|
||||
return ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) {
|
||||
return SkeletonCard();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: viewModel.trashCategoryResponse?.categories.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final category =
|
||||
viewModel.trashCategoryResponse!.categories[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 15,
|
||||
),
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Image.network(
|
||||
"$_baseUrl${category.icon}",
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
title: Text(category.name),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SkeletonCard extends StatelessWidget {
|
||||
const SkeletonCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[100]!,
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: ListTile(
|
||||
leading: Container(width: 50, height: 50, color: Colors.white),
|
||||
title: Container(width: 100, height: 15, color: Colors.white),
|
||||
subtitle: Container(width: 150, height: 10, color: Colors.white),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
class Category {
|
||||
final String id;
|
||||
final String name;
|
||||
final String icon;
|
||||
final String createdAt;
|
||||
final String updatedAt;
|
||||
|
||||
Category({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.icon,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory Category.fromJson(Map<String, dynamic> json) {
|
||||
return Category(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
icon: json['icon'],
|
||||
createdAt: json['createdAt'],
|
||||
updatedAt: json['updatedAt'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TrashCategoryResponse {
|
||||
final List<Category> categories;
|
||||
final String message;
|
||||
final int total;
|
||||
|
||||
TrashCategoryResponse({
|
||||
required this.categories,
|
||||
required this.message,
|
||||
required this.total,
|
||||
});
|
||||
|
||||
factory TrashCategoryResponse.fromJson(Map<String, dynamic> json) {
|
||||
return TrashCategoryResponse(
|
||||
categories:
|
||||
(json['data'] as List).map((e) => Category.fromJson(e)).toList(),
|
||||
message: json['meta']['message'],
|
||||
total: json['meta']['total'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:rijig_mobile/core/api/api_services.dart';
|
||||
import 'package:rijig_mobile/globaldata/trash/trash_model.dart';
|
||||
|
||||
class TrashCategoryRepository {
|
||||
final Https _https = Https();
|
||||
|
||||
Future<TrashCategoryResponse> fetchCategories() async {
|
||||
final response = await _https.get('/trash/categories');
|
||||
return TrashCategoryResponse.fromJson(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import 'package:rijig_mobile/globaldata/trash/trash_model.dart';
|
||||
import 'package:rijig_mobile/globaldata/trash/trash_repository.dart';
|
||||
|
||||
class TrashCategoryService {
|
||||
final TrashCategoryRepository _trashCategoryRepository;
|
||||
|
||||
TrashCategoryService(this._trashCategoryRepository);
|
||||
|
||||
Future<TrashCategoryResponse> getCategories() async {
|
||||
try {
|
||||
return await _trashCategoryRepository.fetchCategories();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load categories: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:rijig_mobile/globaldata/trash/trash_model.dart';
|
||||
import 'package:rijig_mobile/globaldata/trash/trash_service.dart';
|
||||
|
||||
class TrashViewModel extends ChangeNotifier {
|
||||
final TrashCategoryService _trashCategoryService;
|
||||
|
||||
TrashViewModel(this._trashCategoryService);
|
||||
|
||||
bool isLoading = false;
|
||||
String? errorMessage;
|
||||
TrashCategoryResponse? trashCategoryResponse;
|
||||
|
||||
Future<void> loadCategories() async {
|
||||
isLoading = true;
|
||||
errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
trashCategoryResponse = await _trashCategoryService.getCategories();
|
||||
} catch (e) {
|
||||
errorMessage = "Error: ${e.toString()}";
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,7 @@ import 'package:intl/date_symbol_data_local.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:rijig_mobile/core/container/injection_container.dart';
|
||||
import 'package:rijig_mobile/core/router.dart';
|
||||
import 'package:rijig_mobile/features/auth/presentation/viewmodel/login_vmod.dart';
|
||||
import 'package:rijig_mobile/features/auth/presentation/viewmodel/logout_vmod.dart';
|
||||
import 'package:rijig_mobile/features/auth/presentation/viewmodel/otp_vmod.dart';
|
||||
import 'package:rijig_mobile/core/container/export_vmod.dart';
|
||||
|
||||
void main() async {
|
||||
await dotenv.load(fileName: "server/.env.dev");
|
||||
|
|
@ -31,6 +29,8 @@ class MyApp extends StatelessWidget {
|
|||
ChangeNotifierProvider(create: (_) => sl<LoginViewModel>()),
|
||||
ChangeNotifierProvider(create: (_) => sl<OtpViewModel>()),
|
||||
ChangeNotifierProvider(create: (_) => sl<LogoutViewModel>()),
|
||||
|
||||
ChangeNotifierProvider(create: (_) => sl<TrashViewModel>()),
|
||||
],
|
||||
child: ScreenUtilInit(
|
||||
designSize: const Size(375, 812),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,258 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class PaymentItem extends StatelessWidget {
|
||||
final String title;
|
||||
final String? category;
|
||||
final IconData iconData;
|
||||
final String amount;
|
||||
|
||||
const PaymentItem(
|
||||
{super.key,
|
||||
required this.title,
|
||||
this.category,
|
||||
required this.iconData,
|
||||
required this.amount});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 100,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
color: const Color(0xFF2c3135),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.white.withOpacity(0.05),
|
||||
offset: const Offset(-10, -10),
|
||||
spreadRadius: 0,
|
||||
blurRadius: 10),
|
||||
BoxShadow(
|
||||
color: Colors.black87.withOpacity(0.3),
|
||||
offset: const Offset(10, 10),
|
||||
spreadRadius: 0,
|
||||
blurRadius: 10)
|
||||
]),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: NeumorphicCircle(
|
||||
innerShadow: false,
|
||||
outerShadow: true,
|
||||
backgroundColor: const Color(0xFF2c3135),
|
||||
shadowColor: Colors.black87,
|
||||
highlightColor: Colors.white.withOpacity(0.05),
|
||||
child: Icon(
|
||||
iconData,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
(category == null)
|
||||
? const SizedBox.shrink()
|
||||
: Text(category!,
|
||||
style: TextStyle(
|
||||
color: Colors.yellow.shade200.withOpacity(0.7),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600))
|
||||
],
|
||||
)),
|
||||
Text(amount,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NeumorphicCircle extends StatelessWidget {
|
||||
final bool innerShadow;
|
||||
final bool outerShadow;
|
||||
final Color highlightColor;
|
||||
final Color shadowColor;
|
||||
final Color backgroundColor;
|
||||
final Widget? child;
|
||||
|
||||
const NeumorphicCircle(
|
||||
{super.key,
|
||||
required this.innerShadow,
|
||||
required this.outerShadow,
|
||||
required this.highlightColor,
|
||||
required this.shadowColor,
|
||||
required this.backgroundColor,
|
||||
this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(alignment: Alignment.center, children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: (outerShadow)
|
||||
? [
|
||||
BoxShadow(
|
||||
color: highlightColor,
|
||||
offset: const Offset(-10, -10),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 0),
|
||||
BoxShadow(
|
||||
color: shadowColor,
|
||||
offset: const Offset(10, 10),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 0)
|
||||
]
|
||||
: null)),
|
||||
(innerShadow)
|
||||
? ClipPath(
|
||||
clipper: HighlightClipper(),
|
||||
child: CircleInnerHighlight(
|
||||
highlightColor: highlightColor,
|
||||
backgroundColor: backgroundColor,
|
||||
))
|
||||
: const SizedBox.shrink(),
|
||||
(innerShadow)
|
||||
? ClipPath(
|
||||
clipper: ShadowClipper(),
|
||||
child: CircleInnerShadow(
|
||||
shadowColor: shadowColor,
|
||||
backgroundColor: backgroundColor,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
(child != null) ? child! : const SizedBox.shrink()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class CircleInnerShadow extends StatelessWidget {
|
||||
final Color shadowColor;
|
||||
final Color backgroundColor;
|
||||
|
||||
const CircleInnerShadow(
|
||||
{super.key, required this.shadowColor, required this.backgroundColor});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
backgroundColor,
|
||||
shadowColor,
|
||||
],
|
||||
center: const AlignmentDirectional(0.05, 0.05),
|
||||
focal: const AlignmentDirectional(0, 0),
|
||||
radius: 0.5,
|
||||
focalRadius: 0,
|
||||
stops: const [0.75, 1.0],
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0, 0.45],
|
||||
colors: [backgroundColor.withOpacity(0), backgroundColor])),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CircleInnerHighlight extends StatelessWidget {
|
||||
final Color highlightColor;
|
||||
final Color backgroundColor;
|
||||
|
||||
const CircleInnerHighlight(
|
||||
{super.key, required this.highlightColor, required this.backgroundColor});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
backgroundColor,
|
||||
highlightColor,
|
||||
],
|
||||
center: const AlignmentDirectional(-0.05, -0.05),
|
||||
focal: const AlignmentDirectional(-0.05, -0.05),
|
||||
radius: 0.6,
|
||||
focalRadius: 0.1,
|
||||
stops: const [0.75, 1.0],
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0.55, 1],
|
||||
colors: [backgroundColor, backgroundColor.withOpacity(0)])),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShadowClipper extends CustomClipper<Path> {
|
||||
@override
|
||||
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
Path path = Path();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(size.width, 0);
|
||||
path.lineTo(0, size.height);
|
||||
path.close();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightClipper extends CustomClipper<Path> {
|
||||
@override
|
||||
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
Path path = Path();
|
||||
path.moveTo(size.width, 0);
|
||||
path.lineTo(0, size.height);
|
||||
path.lineTo(size.width, size.height);
|
||||
path.close();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
|
@ -560,6 +560,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shimmer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shimmer
|
||||
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ dependencies:
|
|||
pin_code_fields: ^8.0.1
|
||||
provider: ^6.1.4
|
||||
shared_preferences: ^2.3.3
|
||||
shimmer: ^3.0.0
|
||||
uuid: ^4.5.1
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue