feat: Refactor code structure and optimize performance across multiple modules

This commit is contained in:
Achintha Isuru
2025-11-17 23:29:28 -05:00
parent 831570f2e0
commit a64cbd9edf
1508 changed files with 105319 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:injectable/injectable.dart';
import 'package:krow/core/application/clients/api/api_client.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/features/clock_manual/data/clock_manual_gql.dart';
@singleton
class NotificationsApiProvider {
final ApiClient _client;
NotificationsApiProvider({required ApiClient client}) : _client = client;
Future<void> trackClientClockin(String positionStaffId) async {
}
}

View File

@@ -0,0 +1,47 @@
import 'package:flutter/foundation.dart';
import 'package:injectable/injectable.dart';
import 'package:krow/features/notificatins/domain/notification_entity.dart';
import '../domain/notification_repository.dart';
import 'notifications_api_provider.dart';
@Singleton(as: NotificationsRepository)
class NotificationsRepositoryImpl implements NotificationsRepository {
final NotificationsApiProvider apiProvider;
NotificationsRepositoryImpl({required this.apiProvider});
@override
Future<List<NotificationEntity>> fetchNotifications() async {
try {
// final response = await apiProvider.fetchNotifications();
// return response;
if (!kDebugMode) {
return [
NotificationEntity(
id: '1',
title: 'Test Notification',
body: 'This is a test notification',
type: NotificationType.general,
dateTime: DateTime.now(),
),
NotificationEntity(
id: '2',
title: 'Invoice Notification',
body: 'This is an invoice notification',
type: NotificationType.invoice,
dateTime: DateTime.now().subtract(Duration(days: 1)),
),
];
} else {
return [];
}
} catch (e) {
debugPrint('Error fetching notifications: $e');
rethrow;
}
}
@override
Future<void> readNotification(String id) async {}
}

View File

@@ -0,0 +1,24 @@
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import '../../../../core/application/di/injectable.dart';
import '../notification_entity.dart';
import '../notification_repository.dart';
part 'notifications_event.dart';
part 'notifications_state.dart';
class NotificationsBloc extends Bloc<NotificationsEvent, NotificationsState> {
NotificationsBloc() : super(NotificationsState()) {
on<NotificationsInitEvent>(_onNotificationsInit);
}
void _onNotificationsInit(NotificationsInitEvent event, emit) async {
var notifications =
await getIt<NotificationsRepository>().fetchNotifications();
emit(state.copyWith(
notifications: notifications,
));
}
}

View File

@@ -0,0 +1,7 @@
part of 'notifications_bloc.dart';
@immutable
sealed class NotificationsEvent {}
class NotificationsInitEvent extends NotificationsEvent {
}

View File

@@ -0,0 +1,20 @@
part of 'notifications_bloc.dart';
@immutable
class NotificationsState {
final bool inLoading;
final List<NotificationEntity> notifications;
NotificationsState({this.inLoading = false, this.notifications = const []});
NotificationsState copyWith({
bool? inLoading,
List<NotificationEntity>? notifications,
}) {
return NotificationsState(
inLoading: inLoading ?? this.inLoading,
notifications: notifications ?? this.notifications,
);
}
}

View File

@@ -0,0 +1,41 @@
enum NotificationType {
general,
invoice,
event,
}
class NotificationEntity{
final String id;
final String title;
final String body;
final NotificationType type;
final DateTime dateTime;
final bool isRead;
NotificationEntity({
required this.title,
required this.body,
required this.dateTime,
required this.id,
required this.type,
this.isRead = false,
});
NotificationEntity copyWith({
String? id,
String? title,
String? body,
NotificationType? type,
DateTime? dateTime,
bool? isRead,
}) {
return NotificationEntity(
id: id ?? this.id,
title: title ?? this.title,
body: body ?? this.body,
type: type ?? this.type,
dateTime: dateTime ?? this.dateTime,
isRead: isRead ?? this.isRead,
);
}
}

View File

@@ -0,0 +1,6 @@
import 'notification_entity.dart';
abstract class NotificationsRepository {
Future<List<NotificationEntity>> fetchNotifications();
Future<void> readNotification(String id);
}

View File

@@ -0,0 +1,115 @@
import 'package:auto_route/annotations.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart' show Gap;
import 'package:intl/intl.dart';
import 'package:krow/core/presentation/widgets/scroll_layout_helper.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
import 'package:krow/features/notificatins/domain/notification_entity.dart';
import '../../../core/presentation/gen/assets.gen.dart';
import '../../../core/presentation/styles/kw_text_styles.dart';
import '../../../core/presentation/styles/theme.dart';
import '../../../core/presentation/widgets/ui_kit/kw_button.dart';
@RoutePage()
class NotificationDetailsScreen extends StatelessWidget {
final NotificationEntity notification;
const NotificationDetailsScreen({super.key, required this.notification});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: KwAppBar(
titleText: notification.type.name,
),
body: ScrollLayoutHelper(
upperWidget: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Gap(16),
_buildTitle(),
Gap(24),
Text(notification.title, style: AppTextStyles.headingH1),
Gap(8),
Text(notification.body,
style: AppTextStyles.bodyMediumReg
.copyWith(color: AppColors.blackBlack)),
],
),
lowerWidget: _buildFooter(context),
),
);
}
Widget _buildFooter(BuildContext context) {
return Column(
children: [
Gap(24),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Best regards',
style: AppTextStyles.bodyMediumReg
.copyWith(color: AppColors.blackBlack)),
Gap(2),
Text(
'KROW Team',
style: AppTextStyles.bodyMediumSmb,
),
],
),
Spacer(),
Assets.images.logo.svg(
colorFilter: const ColorFilter.mode(
AppColors.bgColorDark,
BlendMode.srcIn,
)),
],
),
Gap(24),
KwButton.primary(
label: 'Go to',
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
Row _buildTitle() {
return Row(
children: [
_buildIcon(),
Gap(12),
Text(notification.body, style: AppTextStyles.bodyMediumMed),
Spacer(),
Text(DateFormat('hh:mm a').format(notification.dateTime),
style: AppTextStyles.bodyMediumMed)
],
);
}
Container _buildIcon() {
return Container(
height: 36,
width: 36,
decoration: BoxDecoration(
color: AppColors.bgColorDark,
shape: BoxShape.circle,
),
child: Center(
child: Assets.images.icons.receiptSearch.svg(
height: 16,
width: 16,
colorFilter: const ColorFilter.mode(
AppColors.primaryMint,
BlendMode.srcIn,
)),
),
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../domain/bloc/notifications_bloc.dart';
@RoutePage()
class NotificationFlowScreen extends StatelessWidget {
const NotificationFlowScreen({super.key});
@override
Widget build(BuildContext context) {
return AutoRouter();
}
}

View File

@@ -0,0 +1,132 @@
import 'package:auto_route/annotations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
import 'package:krow/features/notificatins/presentation/widgets/notification_list_item.dart';
import '../../../core/presentation/gen/assets.gen.dart';
import '../../../core/presentation/styles/kw_text_styles.dart';
import '../../../core/presentation/styles/theme.dart';
import '../domain/bloc/notifications_bloc.dart';
@RoutePage()
class NotificationsListScreen extends StatefulWidget {
const NotificationsListScreen({super.key});
@override
State<NotificationsListScreen> createState() =>
_NotificationsListScreenState();
}
class _NotificationsListScreenState extends State<NotificationsListScreen> {
@override
void initState() {
super.initState();
}
Future<void> _refreshNotifications() async {
context.read<NotificationsBloc>().add(NotificationsInitEvent());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<NotificationsBloc, NotificationsState>(
builder: (context, state) {
final notifications = state.notifications;
final todayNotifications =
notifications.where((n) => isToday(n.dateTime)).toList();
final thisWeekNotifications =
notifications.where((n) => isThisWeek(n.dateTime)).toList();
return Scaffold(
appBar: KwAppBar(
centerTitle: false,
titleText: 'Alerts',
),
body: RefreshIndicator(
onRefresh: _refreshNotifications,
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: CustomScrollView(
slivers:state.notifications.isEmpty?[
SliverToBoxAdapter(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Gap(100),
Assets.images.icons.calendar.svg(height: 36,width: 36, colorFilter: const ColorFilter.mode(AppColors.blackGray, BlendMode.srcIn)),
Gap(16),
Text(
'No notifications yet',
style: AppTextStyles.headingH3.copyWith(color: AppColors.blackGray),
),
],
),
),
]: [
SliverGap(24),
if (todayNotifications.isNotEmpty)
SliverToBoxAdapter(
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Today',
style: AppTextStyles.bodySmallMed,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final notification = todayNotifications[index];
return NotificationListItem(notification: notification);
},
childCount: todayNotifications.length,
),
),
if (thisWeekNotifications.isNotEmpty)
SliverToBoxAdapter(
child: const Padding(
padding: EdgeInsets.only(bottom: 8),
child: Text(
'This week',
style: AppTextStyles.bodySmallMed,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final notification = thisWeekNotifications[index];
return NotificationListItem(notification: notification);
},
childCount: thisWeekNotifications.length,
),
),
],
),
),
),
);
},
);
}
bool isToday(DateTime date) {
final now = DateTime.now();
return date.year == now.year &&
date.month == now.month &&
date.day == now.day;
}
bool isThisWeek(DateTime date) {
final now = DateTime.now();
final startOfWeek = now.subtract(Duration(days: now.weekday - 1));
final endOfWeek = startOfWeek.add(Duration(days: 6));
return date.isAfter(startOfWeek) &&
date.isBefore(endOfWeek) &&
!isToday(date);
}
}

View File

@@ -0,0 +1,107 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart' show Gap;
import 'package:intl/intl.dart';
import 'package:krow/core/application/routing/routes.gr.dart';
import 'package:krow/core/presentation/styles/kw_box_decorations.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/features/notificatins/domain/notification_entity.dart';
import '../../../../core/presentation/gen/assets.gen.dart';
import '../../../../core/presentation/styles/theme.dart';
class NotificationListItem extends StatelessWidget {
final NotificationEntity notification;
NotificationListItem({required this.notification})
: super(key: Key(notification.id));
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0, bottom: 4.0),
child: GestureDetector(
onTap: () {
context.router.push(NotificationDetailsRoute(notification: notification));
},
child: Container(
decoration: KwBoxDecorations.white12,
padding: const EdgeInsets.all(12),
height: 72,
child: Row(
children: [
_buildIcon(),
Gap(12),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Text(
notification.title,
style: AppTextStyles.bodyMediumMed,
),
const Spacer(),
Text(
DateFormat('hh:mm a').format(notification.dateTime),
style: AppTextStyles.bodyMediumMed,
),
]),
Gap(8),
Text(
notification.title,
overflow: TextOverflow.ellipsis,
style: AppTextStyles.bodySmallMed,
),
],
),
)
],
),
),
),
);
}
_buildIcon() {
return Stack(
children: [
Container(
height: 48,
width: 48,
decoration: BoxDecoration(
color: AppColors.bgColorDark,
shape: BoxShape.circle,
),
child: Center(
child: Assets.images.icons.receiptSearch.svg(
height: 24,
width: 24,
colorFilter: const ColorFilter.mode(
AppColors.primaryMint,
BlendMode.srcIn,
)),
),
),
if (notification.isRead == false)
Positioned(
right: 0,
top: 0,
child: Container(
height: 12,
width: 12,
decoration: BoxDecoration(
color: AppColors.statusError,
border: Border.all(
color: AppColors.grayWhite,
width: 2,
),
shape: BoxShape.circle,
),
),
)
],
);
}
}