outlet list

This commit is contained in:
efrilm
2026-05-12 16:47:36 +07:00
parent c2727a0995
commit 3541fc725c
10 changed files with 1159 additions and 74 deletions
+12 -2
View File
@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:line_icons/line_icons.dart';
import '../../../application/home/home_bloc.dart';
import '../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart';
import '../../../common/constant/app_constant.dart';
import '../../../common/theme/theme.dart';
import '../../../injection.dart';
@@ -22,8 +23,17 @@ class HomePage extends StatefulWidget implements AutoRouteWrapper {
State<HomePage> createState() => _HomePageState();
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) => getIt<HomeBloc>()..add(HomeEvent.fetchedDashboard()),
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
getIt<HomeBloc>()..add(HomeEvent.fetchedDashboard()),
),
BlocProvider(
create: (context) => getIt<OutletListLoaderBloc>()
..add(const OutletListLoaderEvent.fetched()),
),
],
child: this,
);
}
@@ -1,16 +1,129 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.dart';
import '../../../../common/theme/theme.dart';
import '../../../../domain/outlet/outlet.dart';
import '../../../components/spacer/spacer.dart';
import '../../../components/widgets/particle_card.dart';
class HomePromoBanner extends StatelessWidget {
const HomePromoBanner({super.key});
static const _outlets = [
{'name': 'ENAKLO RAWAMANGUNG', 'isHealthy': true},
{'name': 'ENAKLO WOKU PEDAS\nKELAPA GADING', 'isHealthy': true},
];
@override
Widget build(BuildContext context) {
return BlocBuilder<OutletListLoaderBloc, OutletListLoaderState>(
builder: (context, state) {
if (state.isFetching && state.outlets.isEmpty) {
return const _PromoBannerSkeleton();
}
if (state.outlets.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.fromLTRB(
AppValue.padding,
24,
AppValue.padding,
0,
),
child: Row(
children: [
for (int i = 0; i < state.outlets.length; i++) ...[
Expanded(
child: _OutletCard(outlet: state.outlets[i]),
),
if (i < state.outlets.length - 1) const SpaceWidth(12),
],
],
),
);
},
);
}
}
class _OutletCard extends StatelessWidget {
final Outlet outlet;
const _OutletCard({required this.outlet});
@override
Widget build(BuildContext context) {
return ParticleCard(
height: 110,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decorationOpacity: 0.8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Spacer(),
_buildHealthIndicator(outlet.isActive),
],
),
const SpaceHeight(6),
Text(
outlet.name,
style: AppStyle.sm.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w800,
height: 1.25,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
Widget _buildHealthIndicator(bool isActive) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: isActive
? AppColor.success.withOpacity(0.9)
: AppColor.error.withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: (isActive ? AppColor.success : AppColor.error)
.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isActive ? Icons.check_circle : Icons.warning_rounded,
color: AppColor.white,
size: 12,
),
const SpaceWidth(4),
Text(
isActive ? 'Sehat' : 'Tidak Sehat',
style: AppStyle.xs.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w700,
fontSize: 9,
),
),
],
),
);
}
}
class _PromoBannerSkeleton extends StatelessWidget {
const _PromoBannerSkeleton();
@override
Widget build(BuildContext context) {
@@ -23,82 +136,20 @@ class HomePromoBanner extends StatelessWidget {
),
child: Row(
children: [
for (int i = 0; i < _outlets.length; i++) ...[
Expanded(
child: ParticleCard(
height: 110,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
decorationOpacity: 0.8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Spacer(),
_buildHealthIndicator(_outlets[i]['isHealthy'] as bool),
],
),
const SpaceHeight(6),
Text(
_outlets[i]['name'] as String,
style: AppStyle.sm.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w800,
height: 1.25,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
if (i < _outlets.length - 1) const SpaceWidth(12),
],
Expanded(child: _skeletonCard()),
const SpaceWidth(12),
Expanded(child: _skeletonCard()),
],
),
);
}
Widget _buildHealthIndicator(bool isHealthy) {
Widget _skeletonCard() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
height: 110,
decoration: BoxDecoration(
color: isHealthy
? AppColor.success.withOpacity(0.9)
: AppColor.error.withOpacity(0.9),
color: AppColor.border,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: (isHealthy ? AppColor.success : AppColor.error)
.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isHealthy ? Icons.check_circle : Icons.warning_rounded,
color: AppColor.white,
size: 12,
),
const SpaceWidth(4),
Text(
isHealthy ? 'Sehat' : 'Tidak Sehat',
style: AppStyle.xs.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w700,
fontSize: 9,
),
),
],
),
);
}