Browse Source

fix notification task

phase2_dev
Đại Võ 1 year ago
parent
commit
d663abfe40
11 changed files with 269 additions and 70 deletions
  1. +5
    -0
      lib/custom_model/NotificationDTO.dart
  2. +30
    -4
      lib/data/repository/repository.dart
  3. +9
    -0
      lib/main.dart
  4. +30
    -7
      lib/presentation/screens/notification/sc_notification.dart
  5. +24
    -1
      lib/presentation/screens/plot/bloc/plot_bloc.dart
  6. +21
    -0
      lib/presentation/screens/plot/models/area_filter.request.dart
  7. +6
    -0
      lib/presentation/screens/plot/models/crop_filter_request.dart
  8. +42
    -18
      lib/presentation/screens/plot/sc_plot.dart
  9. +60
    -27
      lib/presentation/screens/task/bloc/task_bloc.dart
  10. +7
    -1
      lib/presentation/screens/task/task_page.dart
  11. +35
    -12
      lib/services/firebase_notification_service.dart

+ 5
- 0
lib/custom_model/NotificationDTO.dart View File

@@ -4,6 +4,7 @@ class NotificationDTO {
String? message;
int? tbCropId;
int? tbEntityId;
int? externalId;
String? contents;
String? createdDate;
String? sendDate;
@@ -16,6 +17,7 @@ class NotificationDTO {
this.message,
this.tbCropId,
this.tbEntityId,
this.externalId,
this.contents,
this.createdDate,
this.sendDate,
@@ -27,6 +29,7 @@ class NotificationDTO {
this.contents = noti.contents;
this.tbCropId = noti.tbCropId;
this.tbEntityId = noti.tbEntityId;
this.externalId = noti.externalId;
this.subject = noti.subject;
this.message = noti.message;
this.createdDate = noti.createdDate;
@@ -40,6 +43,7 @@ class NotificationDTO {
message = json['message'];
tbCropId = json['tbCropId'];
tbEntityId = json['tbEntityId'];
externalId = json['externalId'];
contents = json['contents'];
createdDate = json['createdDate'];
sendDate = json['sendDate'];
@@ -54,6 +58,7 @@ class NotificationDTO {
data['message'] = this.message;
data['tbCropId'] = this.tbCropId;
data['tbEntityId'] = this.tbEntityId;
data['externalId'] = this.externalId;
data['contents'] = this.contents;
data['createdDate'] = this.createdDate;
data['sendDate'] = this.sendDate;

+ 30
- 4
lib/data/repository/repository.dart View File

@@ -30,11 +30,13 @@ import 'package:farm_tpf/presentation/screens/task/models/employee.dart';
import 'package:farm_tpf/presentation/screens/task/models/supply_filter.dart';
import 'package:farm_tpf/presentation/screens/task/models/task_request.dart';
import 'package:farm_tpf/utils/const_common.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../../presentation/screens/codes/models/stamp_type.dart';
import '../../presentation/screens/login/models/request_user.dart';
import '../../presentation/screens/login/models/response_user.dart';
import '../../presentation/screens/plot/models/area_filter.request.dart';
import '../../presentation/screens/task/models/task.dart';
import '../../presentation/screens/task/models/task_filter_request.dart';
import '../../presentation/screens/task/models/task_update_request.dart';
@@ -457,10 +459,20 @@ class Repository {
required TaskFilterRequest filter,
}) async {
try {
// var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size&sort=createdDate,${filter.sort ?? 'asc'}';
var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size';
var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size&sort=deadline,${filter.sort ?? 'asc'}';
var status = <bool>[];
if (filter.status != null) {
status = filter.status!.map((e) {
if (e == describeEnum(TaskStatus.completed)) {
return true;
} else {
return false;
}
}).toList();
}

var res = await dio.post(url, data: {
// 'status': filter.status,
'completed': status,
"crop_id": filter.cropId,
});

@@ -534,6 +546,19 @@ class Repository {
}
}

//api/tb-entities/area
Future<List<AreaFilter>> getAreasFilter() async {
try {
var url = '${ConstCommon.baseUrl}/api/tb-entities/area';
var res = await dio.get(
url,
);
return (res.data as List).map((e) => AreaFilter.fromJson(e)).toList();
} catch (e) {
rethrow;
}
}

// Crop
Future<List<TbCropDTO>> crops({
int page = 0,
@@ -541,10 +566,11 @@ class Repository {
required CropFilterRequest filter,
}) async {
try {
// var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size&sort=createdDate,${filter.sort ?? 'asc'}';
var url = '${ConstCommon.baseUrl}/api/tb-crops/list?page=$page&size=$size';
var res = await dio.post(url, data: {
'tbSuppliesIds': filter.supplyIds,
'netHouseIds': filter.netHouseIds,
'code': filter.code,
});

return (res.data as List).map((e) => TbCropDTO.fromJson(e)).toList();

+ 9
- 0
lib/main.dart View File

@@ -9,6 +9,8 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'app.dart';
@@ -18,6 +20,13 @@ import 'environment/app_config.dart';
import 'presentation/screens/task/bloc/task_bloc.dart';

final GlobalKey<NavigatorState> globalNavigator = GlobalKey<NavigatorState>();

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
// handle action
print('message from background: $notificationResponse');
}

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();


+ 30
- 7
lib/presentation/screens/notification/sc_notification.dart View File

@@ -5,6 +5,7 @@ import 'package:farm_tpf/presentation/custom_widgets/dash_line_widget.dart';
import 'package:farm_tpf/presentation/custom_widgets/loading_list_page.dart';
import 'package:farm_tpf/presentation/screens/notification/update_count_noti_bloc.dart';
import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart';
import 'package:farm_tpf/presentation/screens/task/task_detail_page.dart';
import 'package:farm_tpf/utils/NotificationsBloc.dart';
import 'package:farm_tpf/utils/const_icons.dart';
import 'package:farm_tpf/utils/pref.dart';
@@ -216,13 +217,15 @@ class ItemInfinityWidget extends StatelessWidget {
onTap: () {
if (item.contents == "ENV_UPDATE") {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => PlotDetailScreen(
cropType: type,
cropId: item.tbCropId ?? -1,
initialIndex: 0,
))).then((value) {
context,
MaterialPageRoute(
builder: (BuildContext context) => PlotDetailScreen(
cropType: type,
cropId: item.tbCropId ?? -1,
initialIndex: 0,
),
),
).then((value) {
if (item.isRead == 0) {
_updateReadNotification(
context: context,
@@ -254,6 +257,26 @@ class ItemInfinityWidget extends StatelessWidget {
currentReachedMax: currentReachedMax);
}
});
} else if (item.contents == "TODO_LIST_CREATE" || item.contents == 'TODO_LIST_UPDATE') {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => TaskDetailPage(
taskId: item.externalId ?? -1,
),
),
).then((value) {
if (item.isRead == 0) {
_updateReadNotification(
context: context,
unread: unread,
read: read,
item: item,
currentItems: currentItems,
currentPage: currentPage,
currentReachedMax: currentReachedMax);
}
});
} else {}
}),
Container(padding: EdgeInsets.only(left: 16, right: 16), child: DashLineWidget())

+ 24
- 1
lib/presentation/screens/plot/bloc/plot_bloc.dart View File

@@ -5,6 +5,7 @@ import 'package:equatable/equatable.dart';
import 'package:farm_tpf/custom_model/TbCropDTO.dart';
import 'package:farm_tpf/data/api/app_exception.dart';
import 'package:farm_tpf/data/repository/repository.dart';
import 'package:farm_tpf/presentation/screens/plot/models/area_filter.request.dart';
import 'package:farm_tpf/presentation/screens/plot/models/crop_filter_request.dart';
import 'package:farm_tpf/presentation/screens/task/models/supply_filter.dart';
import 'package:flutter/material.dart';
@@ -20,10 +21,15 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> {
PlotBloc({required this.repository}) : super(PlotInitial());
static int pageSize = 20;

final searchCtl = TextEditingController();
var supplyRaws = <SupplyFilter>[];
var supplies = ValueNotifier(<ItemDropDown>[]);
var selectedSupply = ValueNotifier(<ItemDropDown>[]);

var areaRaws = <AreaFilter>[];
var areas = ValueNotifier(<ItemDropDown>[]);
var selectedArea = ValueNotifier(<ItemDropDown>[]);

@override
Stream<PlotState> mapEventToState(
PlotEvent event,
@@ -82,7 +88,17 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> {
)
.toList()
: <int>[];
var filter = CropFilterRequest()..supplyIds = supplyIds;
var areaIds = selectedArea.value.length > 0
? selectedArea.value
.map(
(e) => int.tryParse(e.key ?? '') ?? -1,
)
.toList()
: <int>[];
var filter = CropFilterRequest()
..supplyIds = supplyIds
..netHouseIds = areaIds
..code = searchCtl.text;
return await repository.crops(page: 0, filter: filter);
}

@@ -97,6 +113,13 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> {
(e) => ItemDropDown(key: e.id?.toString(), value: e.name),
)
.toList();

areaRaws = await repository.getAreasFilter();
areas.value = areaRaws
.map(
(e) => ItemDropDown(key: e.id?.toString(), value: e.name),
)
.toList();
// emit(CreateStampPrepareDataSuccessful());
} catch (e) {
print(e);

+ 21
- 0
lib/presentation/screens/plot/models/area_filter.request.dart View File

@@ -0,0 +1,21 @@
class AreaFilter {
int? id;
String? name;

AreaFilter({
this.id,
this.name,
});

AreaFilter.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
return data;
}
}

+ 6
- 0
lib/presentation/screens/plot/models/crop_filter_request.dart View File

@@ -1,18 +1,24 @@
class CropFilterRequest {
List<int>? supplyIds;
List<int>? netHouseIds;
String? sort;
String? code;

CropFilterRequest({this.supplyIds, this.sort});

CropFilterRequest.fromJson(Map<String, dynamic> json) {
supplyIds = json['tbSuppliesIds'].cast<String>();
netHouseIds = json['netHouseIds'].cast<String>();
sort = json['sort'];
code = json['code'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['tbSuppliesIds'] = this.supplyIds;
data['netHouseIds'] = this.netHouseIds;
data['sort'] = this.sort;
data['code'] = this.code;
return data;
}
}

+ 42
- 18
lib/presentation/screens/plot/sc_plot.dart View File

@@ -71,31 +71,55 @@ class _PlotListScreenState extends State<PlotListScreen> {
),
),
WidgetSearch(
searchController: TextEditingController(),
searchController: bloc.searchCtl,
onPressed: (value) {
FocusScope.of(context).requestFocus(FocusNode());
BlocProvider.of<PlotBloc>(context).add(OnSearch(searchString: value));
bloc.add(OnRefresh());
},
),
ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.selectedSupply,
builder: (context, selecteds, _) {
return ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.supplies,
builder: (context, status, _) {
return MultipleSelectBottomSheet(
dataSources: status,
initValue: selecteds,
onSelected: (val) {
bloc.selectedSupply.value = val;
Helpers.hideKeyboard(context);
bloc.add(OnRefresh());
Row(
children: [
ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.selectedSupply,
builder: (context, selecteds, _) {
return ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.supplies,
builder: (context, status, _) {
return MultipleSelectBottomSheet(
dataSources: status,
initValue: selecteds,
onSelected: (val) {
bloc.selectedSupply.value = val;
Helpers.hideKeyboard(context);
bloc.add(OnRefresh());
},
hint: 'Giống',
);
},
hint: 'Giống',
);
},
);
},
),
ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.selectedArea,
builder: (context, selecteds, _) {
return ValueListenableBuilder<List<ItemDropDown>>(
valueListenable: bloc.areas,
builder: (context, status, _) {
return MultipleSelectBottomSheet(
dataSources: status,
initValue: selecteds,
onSelected: (val) {
bloc.selectedArea.value = val;
Helpers.hideKeyboard(context);
bloc.add(OnRefresh());
},
hint: 'Khu vực',
);
},
);
},
),
],
),
Expanded(
child: BlocBuilder<PlotBloc, PlotState>(

+ 60
- 27
lib/presentation/screens/task/bloc/task_bloc.dart View File

@@ -21,6 +21,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
final Repository repository;
int pageSize = 20;
TaskBloc(this.repository) : super(TaskInitial());
bool isFetching = false; // Add this flag
var status = ValueNotifier(
TaskStatus.values
.map(
@@ -43,41 +44,70 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
Stream<TaskState> mapEventToState(
TaskEvent event,
) async* {
if (event is DataFetched && !(state is TaskSuccess && ((state as TaskSuccess).hasReachedMax ?? false))) {
try {
if (state is TaskInitial) {
yield (TaskLoading());
final response = await getListTask(0);
yield TaskSuccess(
items: response,
page: 0,
hasReachedMax: response.length < pageSize ? true : false,
);
}
if (state is TaskSuccess) {
final currentState = state as TaskSuccess;
if (currentState.hasReachedMax ?? false) {
return;
if (event is DataFetched) {
if (state is TaskInitial || (state is TaskSuccess && (!((state as TaskSuccess).hasReachedMax ?? false)))) {
try {
final currentState = state;
int nextPage = 0;
if (currentState is TaskSuccess) {
nextPage = ((currentState.items?.length ?? 0) / 20).floor();
}
int page = (currentState.page ?? 0) + 1;
final response = await getListTask(page);

isFetching = true;
final response = await getListTask(nextPage);
final hasReachedMax = response.length < pageSize;
isFetching = false;
yield response.isEmpty
? currentState.copyWith(hasReachedMax: true)
: TaskSuccess(
items: (currentState.items ?? []) + response,
page: (currentState.page ?? 0) + 1,
hasReachedMax: false,
);
? (currentState is TaskSuccess ? currentState.copyWith(hasReachedMax: true) : TaskSuccess(items: [], hasReachedMax: true))
: (currentState is TaskSuccess
? TaskSuccess(items: (currentState.items ?? []) + response, hasReachedMax: hasReachedMax)
: TaskSuccess(items: response, hasReachedMax: hasReachedMax));
} catch (e) {
isFetching = false;
var errorString = AppException.handleError(e);
yield (TaskFailure(errorString: errorString));
}
} catch (e) {
var errorString = AppException.handleError(e);
yield (TaskFailure(errorString: errorString));
}
}

// if (event is DataFetched && !(state is TaskSuccess && ((state as TaskSuccess).hasReachedMax ?? false))) {
// try {
// if (state is TaskInitial) {
// yield (TaskLoading());
// final response = await getListTask(0);
// yield TaskSuccess(
// items: response,
// page: 0,
// hasReachedMax: response.length < pageSize ? true : false,
// );
// }
// if (state is TaskSuccess) {
// final currentState = state as TaskSuccess;
// // if (currentState.hasReachedMax ?? false) {
// // return;
// // }
// int page = (currentState.page ?? 0) + 1;
// final response = await getListTask(page);
// yield response.isEmpty
// ? currentState.copyWith(hasReachedMax: true)
// : TaskSuccess(
// items: (currentState.items ?? []) + response,
// page: (currentState.page ?? 0) + 1,
// hasReachedMax: false,
// );
// }
// } catch (e) {
// var errorString = AppException.handleError(e);
// yield (TaskFailure(errorString: errorString));
// }
// }
if (event is OnRefresh) {
try {
isFetching = true;
yield (TaskLoading());
final response = await getListTask(0);

isFetching = false;
var items = <Task>[];
response.forEach((element) {
items.add(Task.clone(element));
@@ -88,12 +118,15 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
hasReachedMax: items.length < pageSize ? true : false,
);
} catch (e) {
isFetching = false;
yield (TaskFailure(errorString: AppException.handleError(e)));
}
} else if (event is OnSearch) {
try {
isFetching = true;
yield (TaskLoading());
final response = await getListTask(0);
isFetching = false;
yield TaskSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false);
} catch (e) {
yield (TaskFailure(errorString: AppException.handleError(e)));
@@ -106,7 +139,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> {
..cropId = cropId
..sort = sort.value
..status = selectedStatus.value.map((e) => e.key ?? '').toList();
return await repository.tasks(page: 0, filter: filter);
return await repository.tasks(page: page, filter: filter);
}

Future<void> updateStatusTask(

+ 7
- 1
lib/presentation/screens/task/task_page.dart View File

@@ -47,6 +47,7 @@ class _TaskPageState extends State<TaskPage> {
bloc.add(DataFetched());

_scrollController.addListener(() {
if (bloc.isFetching) return;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll < _scrollThreshold) {
@@ -78,7 +79,7 @@ class _TaskPageState extends State<TaskPage> {
builder: (context, sort, _) {
return Button2Icon(
leftIcon: (sort == describeEnum(SortType.asc)) ? CupertinoIcons.arrow_up : CupertinoIcons.arrow_down,
title: 'Ngày tạo',
title: 'Ngày hoàn thành',
onPressed: () {
if (sort == describeEnum(SortType.asc)) {
bloc.sort.value = describeEnum(SortType.desc);
@@ -130,6 +131,9 @@ class _TaskPageState extends State<TaskPage> {
),
],
),
const SizedBox(
height: 4,
),
Expanded(
child: mainBody(),
),
@@ -154,6 +158,8 @@ class _TaskPageState extends State<TaskPage> {
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
print('index: $index');
print('state.items: ${state.items?.length}');
return index >= (state.items ?? []).length
? BottomLoader()
: ItemTask(

+ 35
- 12
lib/services/firebase_notification_service.dart View File

@@ -1,7 +1,9 @@
import 'dart:convert';

import 'package:farm_tpf/presentation/screens/task/task_detail_page.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get/get.dart';
import '../presentation/screens/plot_detail/sc_plot_detail.dart';
import '../utils/pref.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@@ -42,7 +44,7 @@ class FirebaseNotificationService {
FirebaseMessaging.onMessage.listen(
(RemoteMessage message) {
print('Got a message whilst in the foreground!');
print('Message data: $message');
print('Message data: ${messaging.toString()}');
// increase count unread notification
// countNotificationUnRead.value = countNotificationUnRead.value + 1;

@@ -84,8 +86,8 @@ class FirebaseNotificationService {
android: androidDetails,
);

var dataPayload = json.encode(message.data);
try {
var dataPayload = json.encode(message.data);
// var entityType = message.data['entity_type'];
// var entityData = message.data['entity_data'];
// var systemMessage = message.data['system_message'];
@@ -103,12 +105,6 @@ class FirebaseNotificationService {
},
);

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
// handle action
print('message from background: $notificationResponse');
}

//Interaction Message from background
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('message from background: ${message}');
@@ -125,6 +121,7 @@ class FirebaseNotificationService {

static Future onDidReceiveLocalNotification(int? id, String? title, String? body, String? payload) async {
// display a dialog with the notification details, tap ok to go to another page
print('handle touch on foreground');
}

static Future selectNotification(String? payload) async {
@@ -146,10 +143,36 @@ class FirebaseNotificationService {
}

static void handleNavigateScreen(dynamic data) {
print(data);
try {
if (data['contents'] == 'TODO_LIST_CREATE' || data['contents'] == 'TODO_LIST_UPDATE') {
Get.to(() => TaskDetailPage(taskId: int.tryParse(data['externalId']) ?? -1));
} else if (data['contents'] == 'ENV_UPDATE') {
Get.to(
() => PlotDetailScreen(
cropType: int.tryParse(data['tbCropType']) ?? -1,
cropId: int.tryParse(data['tbCropId']) ?? -1,
initialIndex: 0,
),
);
} else if (data['contents'] == 'PIC_UPDATE') {
Get.to(
() => PlotDetailScreen(
cropType: int.tryParse(data['tbCropType']) ?? -1,
cropId: int.tryParse(data['tbCropId']) ?? -1,
initialIndex: 1,
),
);
} else {
print(data['contents']);
}
print(data);
} catch (e) {
print(e);
}
}
}

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print('Handling a background message: ${message}');
static Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print('Handling a background message: ${message}');
_handleMessage(message);
}
}

Loading…
Cancel
Save