304 lines
9.7 KiB
Dart
304 lines
9.7 KiB
Dart
import 'dart:collection';
|
|
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:birzha/models/categories/category.dart';
|
|
import 'package:birzha/models/chatroom/message.dart';
|
|
import 'package:birzha/models/exceptions/exception.dart';
|
|
import 'package:birzha/models/products/post.dart';
|
|
import 'package:birzha/models/settings/settingsModel.dart';
|
|
import 'package:birzha/models/user/user.dart';
|
|
import 'package:birzha/models/user/userManager.dart';
|
|
import 'package:birzha/services/requests.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:birzha/core/manager/manager.dart';
|
|
import 'package:birzha/core/orm/orm.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class Chatroom extends Manager<_ChatroomData> implements _ChatroomData {
|
|
Set<LocalMessage> _availableLocalMessages = {};
|
|
Set<RemoteMessage> get _availableRemoteMessages => {
|
|
for (var data in jSON['messages'] ?? []) RemoteMessage(data: {...data})
|
|
};
|
|
|
|
Set<Message> get messages => UnmodifiableSetView({..._availableLocalMessages, ..._availableRemoteMessages});
|
|
|
|
int get lastIndexOfLocalMessages => _lastMessageInBufferId;
|
|
|
|
void _clearSentMessages() {
|
|
_availableLocalMessages.removeWhere((element) => element.status == MessagesStatus.sent);
|
|
}
|
|
|
|
@override
|
|
get jSON => {...dataSync.jSON};
|
|
|
|
@override
|
|
set jSON(other) {}
|
|
|
|
int _lastMessageInBufferId = -1;
|
|
|
|
Chatroom({required Map<String, dynamic> data}) : super(_ChatroomData(data: {...data}));
|
|
|
|
factory Chatroom.init({required Map<String, dynamic> data}) {
|
|
var newData = {...data};
|
|
newData['id'] = data['chatroom_id'];
|
|
newData['messages'] = [];
|
|
return Chatroom(data: {...newData});
|
|
}
|
|
|
|
@override
|
|
int get primaryKey => jSON[primaryKeyField];
|
|
|
|
@override
|
|
String get primaryKeyField => 'id';
|
|
|
|
@override
|
|
Vendor? get sender => dataSync.sender;
|
|
|
|
@override
|
|
String get subTitile => dataSync.subTitile;
|
|
|
|
@override
|
|
String get title => dataSync.title;
|
|
|
|
@override
|
|
int get unread => dataSync.unread;
|
|
|
|
@override
|
|
bool get hasNewMessages => dataSync.hasNewMessages;
|
|
|
|
@override
|
|
@protected
|
|
_ChatroomData get dataSync => super.dataSync;
|
|
|
|
@override
|
|
@protected
|
|
get value => super.value;
|
|
|
|
@override
|
|
@protected
|
|
set value(other) {
|
|
super.value = other;
|
|
}
|
|
|
|
@override
|
|
void listenerCallBack(TaskResult<_ChatroomData?> result, String taskKey) {
|
|
_statusTable[taskKey] = result.status;
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
Future<void> destroyTask(String taskId) async {
|
|
_statusTable.remove(taskId);
|
|
return super.destroyTask(taskId);
|
|
}
|
|
|
|
Map<String, TaskStatus> _statusTable = {};
|
|
|
|
TaskStatus getStatusByKey(String key) {
|
|
return _statusTable[key] ?? TaskStatus.None;
|
|
}
|
|
|
|
void loadPastMessages(BuildContext context, [VoidCallback? onDone]) {
|
|
addTask(Task(
|
|
key: 'load',
|
|
computation: () async {
|
|
try {
|
|
var response = await _loadMoreRequest(AppUserManager.of(context).dataSync.token ?? "", dataSync, _howManyMessagesToSkip);
|
|
var decoded = jsonDecode(response.body);
|
|
var isSuccess = decoded['status_code'] == 200 || decoded['status_code'] == '200';
|
|
if (!isSuccess) {
|
|
throw AppExceptions.recognizer(decoded);
|
|
} else {
|
|
var data = decoded['data'];
|
|
var newMessages = data['messages'];
|
|
List processed = [];
|
|
if (newMessages is Iterable) {
|
|
processed = [...newMessages];
|
|
} else {
|
|
var mapMessages = newMessages as Map;
|
|
processed = [for (var key in mapMessages.keys) mapMessages[key]];
|
|
}
|
|
if (onDone != null) {
|
|
onDone();
|
|
}
|
|
return dataSync.copy..jSON['messages'] = [...jSON['messages'], ...processed.reversed];
|
|
}
|
|
} catch (ex) {
|
|
if (onDone != null) {
|
|
onDone();
|
|
}
|
|
AppExceptions.exceptionHandler(context, ex);
|
|
throw ex;
|
|
}
|
|
}));
|
|
}
|
|
|
|
void refresh(BuildContext context, [VoidCallback? onDone]) {
|
|
addTask(Task(
|
|
key: 'load',
|
|
computation: () async {
|
|
try {
|
|
var response = await _loadMoreRequest(AppUserManager.of(context).dataSync.token ?? "", dataSync, 0);
|
|
var decoded = jsonDecode(response.body);
|
|
var isSuccess = decoded['status_code'] == 200 || decoded['status_code'] == '200';
|
|
if (!isSuccess) {
|
|
throw AppExceptions.recognizer(decoded);
|
|
} else {
|
|
var data = decoded['data'];
|
|
var newMessages = data['messages'];
|
|
List processed = [];
|
|
if (newMessages is Iterable) {
|
|
processed = [...newMessages];
|
|
} else {
|
|
var mapMessages = newMessages as Map;
|
|
processed = [for (var key in mapMessages.keys) mapMessages[key]];
|
|
}
|
|
if (onDone != null) {
|
|
onDone();
|
|
}
|
|
_clearSentMessages();
|
|
return dataSync.copy
|
|
..jSON['messages'] = [
|
|
...processed.reversed,
|
|
...jSON['messages'],
|
|
];
|
|
}
|
|
} catch (ex, trace) {
|
|
print(trace);
|
|
if (onDone != null) {
|
|
onDone();
|
|
}
|
|
AppExceptions.exceptionHandler(context, ex);
|
|
throw ex;
|
|
}
|
|
}));
|
|
}
|
|
|
|
int get _howManyMessagesToSkip {
|
|
var sentMessages = messages.where((element) => element.status == MessagesStatus.sent);
|
|
return sentMessages.length;
|
|
}
|
|
|
|
void retextMessage(BuildContext context, Message message) {
|
|
_availableLocalMessages.remove(message);
|
|
notifyListeners();
|
|
textMessage(context, message.text);
|
|
}
|
|
|
|
void textMessage(BuildContext context, String text) {
|
|
addTask(Task(
|
|
computation: () async {
|
|
var message = LocalMessage(date: DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch), id: _lastMessageInBufferId, text: text);
|
|
try {
|
|
_lastMessageInBufferId--;
|
|
message.status = MessagesStatus.sending;
|
|
_availableLocalMessages = {message, ..._availableLocalMessages};
|
|
notifyListeners();
|
|
var response = await _sendMessageRequest(
|
|
message,
|
|
AppUserManager.of(context).dataSync.token ?? "",
|
|
dataSync,
|
|
);
|
|
var decoded = jsonDecode(response.body);
|
|
var isSuccess = decoded['status_code'] == 200 || decoded['status_code'] == '200';
|
|
if (!isSuccess) {
|
|
throw AppExceptions.recognizer(decoded);
|
|
} else {
|
|
message.status = MessagesStatus.sent;
|
|
return dataSync.copy..jSON['last_message'] = {...message.toRemoteMessage().jSON};
|
|
}
|
|
} catch (ex) {
|
|
message.status = MessagesStatus.failed;
|
|
AppExceptions.exceptionHandler(context, ex);
|
|
throw ex;
|
|
}
|
|
},
|
|
key: 'message' + (_lastMessageInBufferId).toString()));
|
|
}
|
|
|
|
@protected
|
|
@override
|
|
_ChatroomData get copy => throw UnimplementedError();
|
|
|
|
static Chatroom of(BuildContext context, {bool listen = false}) {
|
|
return Provider.of<Chatroom>(context, listen: listen);
|
|
}
|
|
}
|
|
|
|
Future<http.Response> _sendMessageRequest(LocalMessage message, String token, _ChatroomData chatroom) {
|
|
return http.post(baseUrl(path: kApiPath + '/messages/' + chatroom.primaryKey.toString()), body: jsonEncode({'msg': message.text}), headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer $token',
|
|
});
|
|
}
|
|
|
|
Future<http.Response> _loadMoreRequest(String token, _ChatroomData chatroom, int skip) {
|
|
return http.get(baseUrl(path: kApiPath + '/messages/chatroom/' + chatroom.primaryKey.toString() + '/load-more', queryParameters: {'skip': skip.toString()}),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer $token',
|
|
});
|
|
}
|
|
|
|
class _ChatroomData extends Post with CopyAbleMixin<_ChatroomData> {
|
|
_ChatroomData({required Map<String, dynamic> data}) : super(data);
|
|
|
|
Vendor? get sender {
|
|
if (jSON['vendor'] != null) {
|
|
return Vendor(data: {...jSON['vendor']});
|
|
}
|
|
if (jSON['users'] is! Iterable) {
|
|
return null;
|
|
}
|
|
var users = jSON['users'] as Iterable;
|
|
if (users.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
return Vendor(data: {...users.first});
|
|
}
|
|
|
|
String get title => (sender?.name.isEmpty ?? true) ? (sender?.phone ?? "") : sender!.name;
|
|
String get subTitile => jSON['last_message'] is! Map ? '' : RemoteMessage(data: {...jSON['last_message']}).text;
|
|
int get unread => jSON['count_unread_messages'] is! int ? 0 : jSON['count_unread_messages'];
|
|
bool get hasNewMessages => unread > 0;
|
|
|
|
@override
|
|
_ChatroomData get copy => _ChatroomData(data: {...jSON});
|
|
}
|
|
|
|
class ChatroomSerializer extends RemoteCategory<Chatroom> {
|
|
ChatroomSerializer() : super(data: {'id': -5});
|
|
|
|
@override
|
|
get copy => ChatroomSerializer();
|
|
|
|
@override
|
|
String get name => 'messages'.translation;
|
|
|
|
@override
|
|
Chatroom postBuilderDelegate(Map<String, dynamic> data) => Chatroom(data: {...data});
|
|
|
|
@override
|
|
Uri postUri(BuildContext context, int page) {
|
|
return baseUrl(path: kApiPath + '/messages');
|
|
}
|
|
|
|
@override
|
|
Future<List<Chatroom>> getPosts(BuildContext context, int page) {
|
|
return FutureGetList<Chatroom>(
|
|
postUri(context, page),
|
|
parser: (response) {
|
|
var decoded = jsonDecode(response.body);
|
|
var chatrooms = decoded['data']['chatrooms'] as Iterable;
|
|
return [
|
|
for (var chatroomRaw in chatrooms) Chatroom(data: {...chatroomRaw})
|
|
];
|
|
},
|
|
).fetch({'Accept': 'application/json', 'Authorization': 'Bearer ${AppUserManager.of(context).dataSync.token}'});
|
|
}
|
|
|
|
@override
|
|
bool get needPagination => false;
|
|
}
|