feat: legacy mobile apps created
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
part of 'notifications_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class NotificationsEvent {}
|
||||
|
||||
class NotificationsInitEvent extends NotificationsEvent {
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import 'notification_entity.dart';
|
||||
|
||||
abstract class NotificationsRepository {
|
||||
Future<List<NotificationEntity>> fetchNotifications();
|
||||
Future<void> readNotification(String id);
|
||||
}
|
||||
@@ -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,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user