Browse Source

Screen list notification

master
daivph 5 years ago
parent
commit
46722260da
11 changed files with 712 additions and 5 deletions
  1. +58
    -0
      lib/custom_model/NotificationDTO.dart
  2. +42
    -0
      lib/custom_model/NotificationObjectDTO.dart
  3. +18
    -0
      lib/custom_model/UpdateNoti.dart
  4. +3
    -3
      lib/data/api/dio_provider.dart
  5. +7
    -0
      lib/data/api/rest_client.dart
  6. +35
    -0
      lib/data/api/rest_client.g.dart
  7. +23
    -0
      lib/data/repository/repository.dart
  8. +134
    -0
      lib/presentation/screens/notification/bloc/noti_bloc.dart
  9. +45
    -0
      lib/presentation/screens/notification/bloc/noti_event.dart
  10. +40
    -0
      lib/presentation/screens/notification/bloc/noti_state.dart
  11. +307
    -2
      lib/presentation/screens/notification/sc_notification.dart

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

@@ -0,0 +1,58 @@
class NotificationDTO {
int id;
String subject;
String message;
int tbCropId;
int tbEntityId;
String contents;
String createdDate;
String sendDate;
int isRead;

NotificationDTO(
{this.id,
this.subject,
this.message,
this.tbCropId,
this.tbEntityId,
this.contents,
this.createdDate,
this.sendDate,
this.isRead});
NotificationDTO.clone(NotificationDTO noti) {
this.id = noti.id;
this.contents = noti.contents;
this.tbCropId = noti.tbCropId;
this.tbEntityId = noti.tbEntityId;
this.subject = noti.subject;
this.message = noti.message;
this.createdDate = noti.createdDate;
this.sendDate = noti.sendDate;
this.isRead = noti.isRead;
}
NotificationDTO.fromJson(Map<String, dynamic> json) {
id = json['id'];
subject = json['subject'];
message = json['message'];
tbCropId = json['tbCropId'];
tbEntityId = json['tbEntityId'];
contents = json['contents'];
createdDate = json['createdDate'];
sendDate = json['sendDate'];
isRead = json['isRead'];
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['subject'] = this.subject;
data['message'] = this.message;
data['tbCropId'] = this.tbCropId;
data['tbEntityId'] = this.tbEntityId;
data['contents'] = this.contents;
data['createdDate'] = this.createdDate;
data['sendDate'] = this.sendDate;
data['isRead'] = this.isRead;
return data;
}
}

+ 42
- 0
lib/custom_model/NotificationObjectDTO.dart View File

@@ -0,0 +1,42 @@
import 'NotificationDTO.dart';

class NotificationObjectDTO {
int numberUnreadPage;
int numberReadPage;
int numberUnreadTotal;
int numberReadTotal;
List<NotificationDTO> notificationDTO;

NotificationObjectDTO(
{this.numberUnreadPage,
this.numberReadPage,
this.numberUnreadTotal,
this.numberReadTotal,
this.notificationDTO});

NotificationObjectDTO.fromJson(Map<String, dynamic> json) {
numberUnreadPage = json['numberUnreadPage'];
numberReadPage = json['numberReadPage'];
numberUnreadTotal = json['numberUnreadTotal'];
numberReadTotal = json['numberReadTotal'];
if (json['tbnotificationDTOs'] != null) {
notificationDTO = new List<NotificationDTO>();
json['tbnotificationDTOs'].forEach((v) {
notificationDTO.add(new NotificationDTO.fromJson(v));
});
}
}

Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['numberUnreadPage'] = this.numberUnreadPage;
data['numberReadPage'] = this.numberReadPage;
data['numberUnreadTotal'] = this.numberUnreadTotal;
data['numberReadTotal'] = this.numberReadTotal;
if (this.notificationDTO != null) {
data['tbnotificationDTOs'] =
this.notificationDTO.map((v) => v.toJson()).toList();
}
return data;
}
}

+ 18
- 0
lib/custom_model/UpdateNoti.dart View File

@@ -0,0 +1,18 @@
class UpdateNoti {
int id;
int isRead;

UpdateNoti({this.id, this.isRead});

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

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

+ 3
- 3
lib/data/api/dio_provider.dart View File

@@ -36,14 +36,14 @@ class HttpLogInterceptor extends InterceptorsWrapper {

@override
Future onResponse(Response response) {
// log("onResponse: $response");
log("onResponse: $response");
return super.onResponse(response);
}

@override
Future onError(DioError err) {
// log("onError: $err\n"
// "Response: ${err.response}");
log("onError: $err\n"
"Response: ${err.response}");
return super.onError(err);
}
}

+ 7
- 0
lib/data/api/rest_client.dart View File

@@ -4,6 +4,7 @@ import 'package:farm_tpf/custom_model/Device.dart';
import 'package:farm_tpf/custom_model/EnvironmentParameter.dart';
import 'package:farm_tpf/custom_model/Harvest.dart';
import 'package:farm_tpf/custom_model/Supply.dart';
import 'package:farm_tpf/custom_model/UpdateNoti.dart';
import 'package:farm_tpf/custom_model/WaterType.dart';
import 'package:farm_tpf/custom_model/account.dart';
import 'package:farm_tpf/custom_model/password.dart';
@@ -63,6 +64,12 @@ abstract class RestClient {
@GET("/api/listDeviceForActivity")
Future<List<Device>> getDeviceForActivity({@DioOptions() Options options});

@PUT("/api/notifications/update-all")
Future<void> updateAllNotification(@Body() String status);

@PUT("/api/notifications/update")
Future<void> updateNoti(@Body() UpdateNoti updateNoti);

//Crop
@GET(
"/api/tb-crops-detail-for-app/{cropId}?page={page}&size={size}&sort=executeDate,DESC")

+ 35
- 0
lib/data/api/rest_client.g.dart View File

@@ -282,6 +282,41 @@ class _RestClient implements RestClient {
return value;
}

@override
updateAllNotification(status) async {
ArgumentError.checkNotNull(status, 'status');
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _data = status;
await _dio.request<void>('/api/notifications/update-all',
queryParameters: queryParameters,
options: RequestOptions(
method: 'PUT',
headers: <String, dynamic>{},
extra: _extra,
baseUrl: baseUrl),
data: _data);
return null;
}

@override
updateNoti(updateNoti) async {
ArgumentError.checkNotNull(updateNoti, 'updateNoti');
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(updateNoti?.toJson() ?? <String, dynamic>{});
await _dio.request<void>('/api/notifications/update',
queryParameters: queryParameters,
options: RequestOptions(
method: 'PUT',
headers: <String, dynamic>{},
extra: _extra,
baseUrl: baseUrl),
data: _data);
return null;
}

@override
getCropDetail(cropId, {page = 0, size = 20}) async {
ArgumentError.checkNotNull(cropId, 'cropId');

+ 23
- 0
lib/data/repository/repository.dart View File

@@ -6,7 +6,10 @@ import 'package:farm_tpf/custom_model/CropPlot.dart';
import 'package:farm_tpf/custom_model/Device.dart';
import 'package:farm_tpf/custom_model/EnvironmentParameter.dart';
import 'package:farm_tpf/custom_model/Harvest.dart';
import 'package:farm_tpf/custom_model/NotificationDTO.dart';
import 'package:farm_tpf/custom_model/NotificationObjectDTO.dart';
import 'package:farm_tpf/custom_model/Supply.dart';
import 'package:farm_tpf/custom_model/UpdateNoti.dart';
import 'package:farm_tpf/custom_model/WaterType.dart';
import 'package:farm_tpf/custom_model/user.dart';
import 'package:farm_tpf/custom_model/user_request.dart';
@@ -92,6 +95,26 @@ class Repository {
return client.getDeviceForActivity(options: op);
}

Future<void> updateAllNotification(String status) {
final client = RestClient(dio);
return client.updateAllNotification(status);
}

Future<void> updateNoti(UpdateNoti updateNoti) {
final client = RestClient(dio);
return client.updateNoti(updateNoti);
}

Future<NotificationObjectDTO> getNotifications(
{int page = 0, int size = 20}) async {
var url = ConstCommon.baseUrl +
"/api/notifications-current-user?page=$page&size=$size&sort=sendDate,DESC";
var response = await dio.get(url);

final value = NotificationObjectDTO.fromJson(response.data);
return value;
}

Object getInstanceClass() {
var instanceClass;
if (1 == 1) {

+ 134
- 0
lib/presentation/screens/notification/bloc/noti_bloc.dart View File

@@ -0,0 +1,134 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:farm_tpf/custom_model/NotificationDTO.dart';
import 'package:farm_tpf/custom_model/NotificationObjectDTO.dart';
import 'package:farm_tpf/custom_model/UpdateNoti.dart';
import 'package:farm_tpf/data/api/app_exception.dart';
import 'package:farm_tpf/data/repository/repository.dart';
import 'package:farm_tpf/utils/const_string.dart';
import 'package:meta/meta.dart';

part 'noti_event.dart';
part 'noti_state.dart';

class NotiBloc extends Bloc<NotiEvent, NotiState> {
final Repository repository;
NotiBloc({@required this.repository}) : super(NotiInitial());
static int pageSize = 20;

@override
Stream<NotiState> mapEventToState(
NotiEvent event,
) async* {
if (event is DataFetched &&
!(state is NotiSuccess && (state as NotiSuccess).hasReachedMax)) {
try {
if (state is NotiInitial) {
yield NotiLoadding();
final response =
await repository.getNotifications(page: 0, size: pageSize);
yield NotiSuccess(
unread: response.numberUnreadTotal,
read: response.numberReadTotal,
items: response.notificationDTO,
page: 0,
hasReachedMax:
response.notificationDTO.length < pageSize ? true : false);
}
if (state is NotiSuccess) {
final currentState = state as NotiSuccess;
int page = currentState.page + 1;
final response =
await repository.getNotifications(page: page, size: pageSize);
yield response.notificationDTO.isEmpty
? currentState.copyWith(hasReachedMax: true)
: NotiSuccess(
unread: response.numberUnreadTotal,
read: response.numberReadTotal,
items: currentState.items + response.notificationDTO,
page: currentState.page + 1,
hasReachedMax: false);
}
} catch (e) {
yield NotiFailure(errorString: AppException.handleError(e));
}
}
if (event is OnRefresh) {
yield NotiLoadding();
try {
final response =
await repository.getNotifications(page: 0, size: pageSize);
List<NotificationDTO> items = new List<NotificationDTO>();
response.notificationDTO
.forEach((e) => items.add(NotificationDTO.clone(e)));
yield NotiSuccess(
unread: response.numberUnreadTotal,
read: response.numberReadTotal,
items: items,
page: 0,
hasReachedMax:
response.notificationDTO.length < pageSize ? true : false);
} catch (e) {
yield NotiFailure(errorString: AppException.handleError(e));
}
}

if (event is OnUpdate) {
yield NotiLoadding();
try {
//Change status notification if mark read
if (event.currentItemId != null) {
var updateNoti = UpdateNoti()
..id = event.currentItemId
..isRead = 1;
await repository.updateNoti(updateNoti);
}

yield NotiSuccess(
unread: event.unread,
read: event.read,
items: event.currentItems,
page: event.currentPage,
hasReachedMax: event.hasReachedMax);
} catch (e) {
yield NotiFailure(errorString: exception_common);
}
} else if (event is MarkAllNotificationUpdate) {
yield NotiLoadding();
try {
await repository.updateAllNotification(event.status);
final response =
await repository.getNotifications(page: 0, size: pageSize);
List<NotificationDTO> items = new List<NotificationDTO>();
response.notificationDTO
.forEach((e) => items.add(NotificationDTO.clone(e)));
yield NotiSuccess(
unread: response.numberUnreadTotal,
read: response.numberReadTotal,
items: items,
page: 0,
hasReachedMax:
response.notificationDTO.length < pageSize ? true : false);
} catch (e) {
yield NotiFailure(errorString: exception_common);
}
} else if (event is ReceiveDataFromSocket) {
List<NotificationDTO> updatedItems = new List<NotificationDTO>();
event.updatedItemObject.notificationDTO.forEach((e) {
updatedItems.add(NotificationDTO.clone(e));
});

event.currentItems.forEach((e) {
updatedItems.add(NotificationDTO.clone(e));
});
yield NotiSuccess(
unread: event.updatedItemObject.numberUnreadTotal,
read: event.updatedItemObject.numberReadTotal,
items: updatedItems,
page: event.page,
hasReachedMax: updatedItems.length < pageSize ? true : false);
}
}
}

+ 45
- 0
lib/presentation/screens/notification/bloc/noti_event.dart View File

@@ -0,0 +1,45 @@
part of 'noti_bloc.dart';

abstract class NotiEvent extends Equatable {
const NotiEvent();

@override
List<Object> get props => [];
}

class DataFetched extends NotiEvent {}

class OnRefresh extends NotiEvent {}

class OnUpdate<T> extends NotiEvent {
final int unread;
final int read;
final int currentItemId;
final List<T> currentItems;
final int currentPage;
final bool hasReachedMax;
OnUpdate(
{@required this.unread,
@required this.read,
this.currentItemId,
@required this.currentItems,
@required this.currentPage,
@required this.hasReachedMax});
}

class MarkAllNotificationUpdate extends NotiEvent {
final String status;
MarkAllNotificationUpdate({@required this.status});
}

class ReceiveDataFromSocket extends NotiEvent {
final List<NotificationDTO> currentItems;
final int page;
final bool hasReachedMax;
final NotificationObjectDTO updatedItemObject;
ReceiveDataFromSocket(
{@required this.currentItems,
this.page,
this.hasReachedMax,
this.updatedItemObject});
}

+ 40
- 0
lib/presentation/screens/notification/bloc/noti_state.dart View File

@@ -0,0 +1,40 @@
part of 'noti_bloc.dart';

abstract class NotiState extends Equatable {
const NotiState();

@override
List<Object> get props => [];
}

class NotiInitial extends NotiState {}

class NotiLoadding extends NotiState {}

class NotiFailure extends NotiState {
final String errorString;
NotiFailure({@required this.errorString});
}

class NotiSuccess<T> extends NotiState {
final int unread;
final int read;
final List<T> items;
final int page;
final bool hasReachedMax;

const NotiSuccess(
{this.unread, this.read, this.items, this.page, this.hasReachedMax});

NotiSuccess copyWith({List<T> items, int page, bool hasReachedMax}) {
return NotiSuccess(
unread: unread ?? this.unread,
read: read ?? this.read,
items: items ?? this.items,
page: page ?? this.page,
hasReachedMax: hasReachedMax ?? this.hasReachedMax);
}

@override
List<Object> get props => [items, hasReachedMax];
}

+ 307
- 2
lib/presentation/screens/notification/sc_notification.dart View File

@@ -1,4 +1,15 @@
import 'package:farm_tpf/custom_model/NotificationDTO.dart';
import 'package:farm_tpf/data/repository/repository.dart';
import 'package:farm_tpf/presentation/custom_widgets/bottom_loader.dart';
import 'package:farm_tpf/presentation/custom_widgets/loading_list_page.dart';
import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart';
import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_information.dart';
import 'package:farm_tpf/utils/pref.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:farm_tpf/utils/formatter.dart';

import 'bloc/noti_bloc.dart';

class NotificationScreen extends StatefulWidget {
@override
@@ -6,12 +17,306 @@ class NotificationScreen extends StatefulWidget {
}

class _NotificationScreenState extends State<NotificationScreen> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
NotiBloc(repository: Repository())..add(DataFetched()),
child: HoldInfinityWidget(),
);
}
}

class HoldInfinityWidget extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Thông báo"),
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text("Thông báo"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.done_all),
onPressed: () {
BlocProvider.of<NotiBloc>(context)
.add(MarkAllNotificationUpdate(status: "1"));
})
],
),
body: InfinityView());
}
}

class InfinityView extends StatefulWidget {
@override
_InfinityViewState createState() => _InfinityViewState();
}

class _InfinityViewState extends State<InfinityView> {
final _scrollController = ScrollController();
final _scrollThreshold = 250.0;
NotiBloc _notiBloc;
List<NotificationDTO> currentItems = new List<NotificationDTO>();
int latestId = 0;
int currentPage = 0;
bool currentHasReachedMax = true;
bool isUpdatingFromApi = true;

var pref = LocalPref();
// final SocketService socketService = injector.get<SocketService>();
var token;
Future<Null> getSharedPrefs() async {
_scrollController.addListener(() {
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.position.pixels;
if (maxScroll - currentScroll < _scrollThreshold) {
_notiBloc.add(DataFetched());
}
});
token = await pref.getString(DATA_CONST.TOKEN_KEY);
// socketService.initial(token);
_notiBloc = BlocProvider.of<NotiBloc>(context);
}

@override
void initState() {
getSharedPrefs();
super.initState();
}

@override
Widget build(BuildContext context) {
return BlocConsumer<NotiBloc, NotiState>(
listener: (context, state) {
//Handle Socket
/*
if (state is NotiLoadding) {
isUpdatingFromApi = true;
}
if (state is NotiSuccess) {
currentItems = List<NotificationDTO>.from(state.items);
latestId = int.parse(currentItems[0].id) > latestId
? int.parse(currentItems[0].id)
: latestId;
socketService.createSocketNotificationConnection(latestId, (data) {
print("receive data");
if (isUpdatingFromApi == false) {
print("Update from socket");
var notis =
NotificationObjectDTO.fromJson(data, NotificationDTO());
_notiBloc.add(ReceiveDataFromSocket(
currentItems: currentItems,
page: currentPage,
hasReachedMax: currentHasReachedMax,
updatedItemObject: notis));
} else {
//dont need update from socket
print("dont need update from socket");
}
}, (error) {});
isUpdatingFromApi = false;
}
*/
},
builder: (context, state) {
if (state is NotiFailure) {
return Center(child: Text(state.errorString));
}
if (state is NotiSuccess) {
if (state.items.isEmpty) {
return Center(child: Text("Dữ liệu rỗng"));
}
currentItems = List<NotificationDTO>.from(state.items);
currentPage = state.page;
currentHasReachedMax = state.hasReachedMax;
return Column(
children: <Widget>[
Container(
child:
countNotification(unread: state.unread, read: state.read),
),
Expanded(
child: RefreshIndicator(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return index >= state.items.length
? BottomLoader()
: ItemInfinityWidget(
unread: state.unread,
read: state.read,
currentItems: currentItems,
item: state.items[index],
currentPage: state.page,
currentReachedMax: state.hasReachedMax,
);
},
itemCount: state.hasReachedMax
? state.items.length
: state.items.length + 1,
controller: _scrollController,
),
onRefresh: () async {
_notiBloc.add(OnRefresh());
}))
],
);
}
return Center(
child: LoadingListPage(),
);
},
);
}

Widget countNotification({num unread, num read}) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: OutlineButton(
child: RichText(
text: TextSpan(
text: "Chưa đọc ",
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
children: <TextSpan>[
TextSpan(
text: "($unread)",
style: TextStyle(color: Colors.blue)),
])),
onPressed: () {})),
Expanded(
child: OutlineButton(
child: RichText(
text: TextSpan(
text: "Đã đọc ",
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
children: <TextSpan>[
TextSpan(
text: "($read)",
style: TextStyle(color: Colors.grey)),
])),
onPressed: () {}))
],
),
);
}

@override
void dispose() {
_scrollController.dispose();
// socketService.disconnect();
super.dispose();
}
}

class ItemInfinityWidget extends StatelessWidget {
final int unread;
final int read;
final NotificationDTO item;
final List<NotificationDTO> currentItems;
final int currentPage;
final bool currentReachedMax;

const ItemInfinityWidget(
{Key key,
@required this.unread,
@required this.read,
@required this.currentItems,
@required this.item,
@required this.currentPage,
@required this.currentReachedMax})
: super(key: key);

@override
Widget build(BuildContext context) {
return GestureDetector(
child: Card(
margin: EdgeInsets.all(4.0),
child: ListTile(
title: Text(item.subject),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(item.message),
Text(
item.sendDate.format_DDMMYY_HHmm(),
style: TextStyle(
color: item.isRead == 1 ? Colors.grey : Colors.blue),
),
],
),
leading: Icon(
Icons.notifications_active,
color: item.isRead == 1 ? Colors.grey : Colors.blue,
))),
onTap: () {
if (item.contents == "ENV_UPDATE") {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
PlotDetailScreen(cropId: item.tbCropId))).then((value) {
if (item.isRead == 0) {
_updateReadNotification(
context: context,
unread: unread,
read: read,
item: item,
currentItems: currentItems,
currentPage: currentPage,
currentReachedMax: currentReachedMax);
}
});
} else if (item.contents == "PIC_UPDATE") {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => PlotInformationScreen(
cropId: item.tbCropId,
))).then((value) {
if (item.isRead == 0) {
_updateReadNotification(
context: context,
unread: unread,
read: read,
item: item,
currentItems: currentItems,
currentPage: currentPage,
currentReachedMax: currentReachedMax);
}
});
} else {}
});
}

_updateReadNotification(
{BuildContext context,
int unread,
int read,
NotificationDTO item,
List<NotificationDTO> currentItems,
int currentPage,
bool currentReachedMax}) {
List<NotificationDTO> updatedItems = new List<NotificationDTO>();
currentItems.forEach((e) {
if (e.id == item.id) {
e.isRead = 1;
} else {}
updatedItems.add(NotificationDTO.clone(e));
});

BlocProvider.of<NotiBloc>(context).add(OnUpdate<NotificationDTO>(
unread: unread - 1,
read: read + 1,
currentItemId: item.id,
currentItems: updatedItems,
currentPage: currentPage,
hasReachedMax: currentReachedMax));
}
}

Loading…
Cancel
Save