mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
sending an receiving works
This commit is contained in:
parent
e0d420b78d
commit
59146674b6
16 changed files with 217 additions and 83 deletions
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/providers/api_provider.dart';
|
||||||
import 'package:twonly/src/providers/db_provider.dart';
|
import 'package:twonly/src/providers/db_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:twonly/src/providers/download_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -61,6 +62,7 @@ void main() async {
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => MessagesChangeProvider()),
|
ChangeNotifierProvider(create: (_) => MessagesChangeProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => DownloadChangeProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => ContactChangeProvider()),
|
ChangeNotifierProvider(create: (_) => ContactChangeProvider()),
|
||||||
],
|
],
|
||||||
child: MyApp(settingsController: settingsController),
|
child: MyApp(settingsController: settingsController),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/main.dart';
|
import 'package:twonly/main.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||||
|
import 'package:twonly/src/providers/download_change_provider.dart';
|
||||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/onboarding_view.dart';
|
import 'package:twonly/src/views/onboarding_view.dart';
|
||||||
|
|
@ -22,6 +23,7 @@ Function(bool) globalCallbackConnectionState = (a) {};
|
||||||
// these two callbacks are called on updated to the corresponding database
|
// these two callbacks are called on updated to the corresponding database
|
||||||
Function globalCallBackOnContactChange = () {};
|
Function globalCallBackOnContactChange = () {};
|
||||||
Function(int) globalCallBackOnMessageChange = (a) {};
|
Function(int) globalCallBackOnMessageChange = (a) {};
|
||||||
|
Function(List<int>, bool) globalCallBackOnDownloadChange = (a, b) {};
|
||||||
|
|
||||||
/// The Widget that configures your application.
|
/// The Widget that configures your application.
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatefulWidget {
|
||||||
|
|
@ -61,6 +63,10 @@ class _MyAppState extends State<MyApp> {
|
||||||
context.read<ContactChangeProvider>().update();
|
context.read<ContactChangeProvider>().update();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
globalCallBackOnDownloadChange = (token, add) {
|
||||||
|
context.read<DownloadChangeProvider>().update(token, add);
|
||||||
|
};
|
||||||
|
|
||||||
globalCallBackOnMessageChange = (userId) {
|
globalCallBackOnMessageChange = (userId) {
|
||||||
context.read<MessagesChangeProvider>().updateLastMessageFor(userId);
|
context.read<MessagesChangeProvider>().updateLastMessageFor(userId);
|
||||||
};
|
};
|
||||||
|
|
@ -73,6 +79,7 @@ class _MyAppState extends State<MyApp> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// disable globalCallbacks to the flutter tree
|
// disable globalCallbacks to the flutter tree
|
||||||
globalCallbackConnectionState = (a) {};
|
globalCallbackConnectionState = (a) {};
|
||||||
|
globalCallBackOnDownloadChange = (a, b) {};
|
||||||
globalCallBackOnContactChange = () {};
|
globalCallBackOnContactChange = () {};
|
||||||
globalCallBackOnMessageChange = (a) {};
|
globalCallBackOnMessageChange = (a) {};
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
@ -143,7 +150,8 @@ class _MyAppState extends State<MyApp> {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return snapshot.data!
|
return snapshot.data!
|
||||||
? HomeView(
|
? HomeView(
|
||||||
settingsController: widget.settingsController)
|
settingsController: widget.settingsController,
|
||||||
|
)
|
||||||
: _showOnboarding
|
: _showOnboarding
|
||||||
? OnboardingView(
|
? OnboardingView(
|
||||||
callbackOnSuccess: () {
|
callbackOnSuccess: () {
|
||||||
|
|
@ -152,10 +160,12 @@ class _MyAppState extends State<MyApp> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: RegisterView(callbackOnSuccess: () {
|
: RegisterView(
|
||||||
|
callbackOnSuccess: () {
|
||||||
_isUserCreated = isUserCreated();
|
_isUserCreated = isUserCreated();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
|
import 'package:twonly/src/providers/download_change_provider.dart';
|
||||||
|
|
||||||
enum MessageSendState {
|
enum MessageSendState {
|
||||||
received,
|
received,
|
||||||
|
|
@ -12,21 +15,32 @@ enum MessageSendState {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageSendStateIcon extends StatelessWidget {
|
class MessageSendStateIcon extends StatelessWidget {
|
||||||
final MessageSendState state;
|
final DbMessage message;
|
||||||
final MessageKind kind;
|
final MainAxisAlignment mainAxisAlignment;
|
||||||
final bool isDownloaded;
|
|
||||||
|
|
||||||
const MessageSendStateIcon(this.state, this.isDownloaded, this.kind,
|
const MessageSendStateIcon(this.message,
|
||||||
{super.key});
|
{super.key, this.mainAxisAlignment = MainAxisAlignment.end});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget icon = Placeholder();
|
Widget icon = Placeholder();
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
Color color = kind.getColor(Theme.of(context).colorScheme.primary);
|
Color color =
|
||||||
|
message.messageKind.getColor(Theme.of(context).colorScheme.primary);
|
||||||
|
|
||||||
switch (state) {
|
Widget loaderIcon = Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 1, color: color),
|
||||||
|
),
|
||||||
|
SizedBox(width: 2),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (message.getSendState()) {
|
||||||
case MessageSendState.receivedOpened:
|
case MessageSendState.receivedOpened:
|
||||||
icon = Icon(Icons.crop_square, size: 14, color: color);
|
icon = Icon(Icons.crop_square, size: 14, color: color);
|
||||||
text = "Received";
|
text = "Received";
|
||||||
|
|
@ -45,29 +59,38 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
case MessageSendState.sending:
|
case MessageSendState.sending:
|
||||||
case MessageSendState.receiving:
|
case MessageSendState.receiving:
|
||||||
icon = Row(
|
icon = loaderIcon;
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
child: CircularProgressIndicator(strokeWidth: 1, color: color),
|
|
||||||
),
|
|
||||||
SizedBox(width: 2),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
text = "Sending";
|
text = "Sending";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDownloaded) {
|
if (!message.isDownloaded) {
|
||||||
text = "Tap do load";
|
text = "Tap do load";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isDownloading = false;
|
||||||
|
if (message.messageContent != null &&
|
||||||
|
message.messageContent!.downloadToken != null) {
|
||||||
|
isDownloading = context
|
||||||
|
.watch<DownloadChangeProvider>()
|
||||||
|
.currentlyDownloading
|
||||||
|
.contains(message.messageContent!.downloadToken!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloading) {
|
||||||
|
text = "Downloading";
|
||||||
|
icon = loaderIcon;
|
||||||
|
}
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisAlignment: mainAxisAlignment,
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
Text(text, style: TextStyle(fontSize: 12)),
|
Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,17 @@ class DbMessages extends CvModelBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future _updateByOtherMessageId(
|
||||||
|
int fromUserId, int messageId, Map<String, dynamic> data) async {
|
||||||
|
await dbProvider.db!.update(
|
||||||
|
tableName,
|
||||||
|
data,
|
||||||
|
where: "$columnMessageOtherId = ?",
|
||||||
|
whereArgs: [messageId],
|
||||||
|
);
|
||||||
|
globalCallBackOnMessageChange(fromUserId);
|
||||||
|
}
|
||||||
|
|
||||||
// this ensures that the message id can be spoofed by another person
|
// this ensures that the message id can be spoofed by another person
|
||||||
static Future _updateByMessageIdOther(
|
static Future _updateByMessageIdOther(
|
||||||
int fromUserId, int messageId, Map<String, dynamic> data) async {
|
int fromUserId, int messageId, Map<String, dynamic> data) async {
|
||||||
|
|
@ -250,14 +261,15 @@ class DbMessages extends CvModelBase {
|
||||||
globalCallBackOnMessageChange(fromUserId);
|
globalCallBackOnMessageChange(fromUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future userOpenedMessage(int messageId) async {
|
static Future userOpenedOtherMessage(
|
||||||
|
int otherMessageId, int fromUserId) async {
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
columnMessageOpenedAt: DateTime.now().toIso8601String(),
|
columnMessageOpenedAt: DateTime.now().toIso8601String(),
|
||||||
};
|
};
|
||||||
await _updateByMessageId(messageId, data);
|
await _updateByOtherMessageId(fromUserId, otherMessageId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future userOpenedMessageOtherUser(
|
static Future otherUserOpenedMyMessage(
|
||||||
int fromUserId, int messageId, DateTime openedAt) async {
|
int fromUserId, int messageId, DateTime openedAt) async {
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
columnMessageOpenedAt: openedAt.toIso8601String(),
|
columnMessageOpenedAt: openedAt.toIso8601String(),
|
||||||
|
|
|
||||||
|
|
@ -854,11 +854,15 @@ class ApplicationData_UploadData extends $pb.GeneratedMessage {
|
||||||
class ApplicationData_DownloadData extends $pb.GeneratedMessage {
|
class ApplicationData_DownloadData extends $pb.GeneratedMessage {
|
||||||
factory ApplicationData_DownloadData({
|
factory ApplicationData_DownloadData({
|
||||||
$core.List<$core.int>? uploadToken,
|
$core.List<$core.int>? uploadToken,
|
||||||
|
$core.int? offset,
|
||||||
}) {
|
}) {
|
||||||
final $result = create();
|
final $result = create();
|
||||||
if (uploadToken != null) {
|
if (uploadToken != null) {
|
||||||
$result.uploadToken = uploadToken;
|
$result.uploadToken = uploadToken;
|
||||||
}
|
}
|
||||||
|
if (offset != null) {
|
||||||
|
$result.offset = offset;
|
||||||
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
ApplicationData_DownloadData._() : super();
|
ApplicationData_DownloadData._() : super();
|
||||||
|
|
@ -867,6 +871,7 @@ class ApplicationData_DownloadData extends $pb.GeneratedMessage {
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.DownloadData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.DownloadData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
|
||||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'uploadToken', $pb.PbFieldType.OY)
|
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'uploadToken', $pb.PbFieldType.OY)
|
||||||
|
..a<$core.int>(2, _omitFieldNames ? '' : 'offset', $pb.PbFieldType.OU3)
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -899,6 +904,15 @@ class ApplicationData_DownloadData extends $pb.GeneratedMessage {
|
||||||
$core.bool hasUploadToken() => $_has(0);
|
$core.bool hasUploadToken() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearUploadToken() => clearField(1);
|
void clearUploadToken() => clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.int get offset => $_getIZ(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set offset($core.int v) { $_setUnsignedInt32(1, v); }
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasOffset() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearOffset() => clearField(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ApplicationData_ApplicationData {
|
enum ApplicationData_ApplicationData {
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@ const ApplicationData_DownloadData$json = {
|
||||||
'1': 'DownloadData',
|
'1': 'DownloadData',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'upload_token', '3': 1, '4': 1, '5': 12, '10': 'uploadToken'},
|
{'1': 'upload_token', '3': 1, '4': 1, '5': 12, '10': 'uploadToken'},
|
||||||
|
{'1': 'offset', '3': 2, '4': 1, '5': 13, '10': 'offset'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -204,8 +205,8 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||||
'cl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgAS'
|
'cl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgAS'
|
||||||
'gDUgZ1c2VySWQaEAoOR2V0VXBsb2FkVG9rZW4aWwoKVXBsb2FkRGF0YRIhCgx1cGxvYWRfdG9r'
|
'gDUgZ1c2VySWQaEAoOR2V0VXBsb2FkVG9rZW4aWwoKVXBsb2FkRGF0YRIhCgx1cGxvYWRfdG9r'
|
||||||
'ZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDVIGb2Zmc2V0EhIKBGRhdGEYAy'
|
'ZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDVIGb2Zmc2V0EhIKBGRhdGEYAy'
|
||||||
'ABKAxSBGRhdGEaMQoMRG93bmxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2Fk'
|
'ABKAxSBGRhdGEaSQoMRG93bmxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2Fk'
|
||||||
'VG9rZW5CEQoPQXBwbGljYXRpb25EYXRh');
|
'VG9rZW4SFgoGb2Zmc2V0GAIgASgNUgZvZmZzZXRCEQoPQXBwbGljYXRpb25EYXRh');
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
const Response$json = {
|
const Response$json = {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
static const ErrorCode OnlyOneSessionAllowed = ErrorCode._(1010, _omitEnumNames ? '' : 'OnlyOneSessionAllowed');
|
static const ErrorCode OnlyOneSessionAllowed = ErrorCode._(1010, _omitEnumNames ? '' : 'OnlyOneSessionAllowed');
|
||||||
static const ErrorCode UploadLimitReached = ErrorCode._(1011, _omitEnumNames ? '' : 'UploadLimitReached');
|
static const ErrorCode UploadLimitReached = ErrorCode._(1011, _omitEnumNames ? '' : 'UploadLimitReached');
|
||||||
static const ErrorCode InvalidUpdateToken = ErrorCode._(1012, _omitEnumNames ? '' : 'InvalidUpdateToken');
|
static const ErrorCode InvalidUpdateToken = ErrorCode._(1012, _omitEnumNames ? '' : 'InvalidUpdateToken');
|
||||||
|
static const ErrorCode InvalidOffset = ErrorCode._(1013, _omitEnumNames ? '' : 'InvalidOffset');
|
||||||
|
|
||||||
static const $core.List<ErrorCode> values = <ErrorCode> [
|
static const $core.List<ErrorCode> values = <ErrorCode> [
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
@ -46,6 +47,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
OnlyOneSessionAllowed,
|
OnlyOneSessionAllowed,
|
||||||
UploadLimitReached,
|
UploadLimitReached,
|
||||||
InvalidUpdateToken,
|
InvalidUpdateToken,
|
||||||
|
InvalidOffset,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ const ErrorCode$json = {
|
||||||
{'1': 'OnlyOneSessionAllowed', '2': 1010},
|
{'1': 'OnlyOneSessionAllowed', '2': 1010},
|
||||||
{'1': 'UploadLimitReached', '2': 1011},
|
{'1': 'UploadLimitReached', '2': 1011},
|
||||||
{'1': 'InvalidUpdateToken', '2': 1012},
|
{'1': 'InvalidUpdateToken', '2': 1012},
|
||||||
|
{'1': 'InvalidOffset', '2': 1013},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -43,5 +44,6 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
||||||
'VzZXJuYW1lTm90Rm91bmQQ7QcSFQoQVXNlcm5hbWVOb3RWYWxpZBDuBxIVChBJbnZhbGlkUHVi'
|
'VzZXJuYW1lTm90Rm91bmQQ7QcSFQoQVXNlcm5hbWVOb3RWYWxpZBDuBxIVChBJbnZhbGlkUHVi'
|
||||||
'bGljS2V5EO8HEiAKG1Nlc3Npb25BbHJlYWR5QXV0aGVudGljYXRlZBDwBxIcChdTZXNzaW9uTm'
|
'bGljS2V5EO8HEiAKG1Nlc3Npb25BbHJlYWR5QXV0aGVudGljYXRlZBDwBxIcChdTZXNzaW9uTm'
|
||||||
'90QXV0aGVudGljYXRlZBDxBxIaChVPbmx5T25lU2Vzc2lvbkFsbG93ZWQQ8gcSFwoSVXBsb2Fk'
|
'90QXV0aGVudGljYXRlZBDxBxIaChVPbmx5T25lU2Vzc2lvbkFsbG93ZWQQ8gcSFwoSVXBsb2Fk'
|
||||||
'TGltaXRSZWFjaGVkEPMHEhcKEkludmFsaWRVcGRhdGVUb2tlbhD0Bw==');
|
'TGltaXRSZWFjaGVkEPMHEhcKEkludmFsaWRVcGRhdGVUb2tlbhD0BxISCg1JbnZhbGlkT2Zmc2'
|
||||||
|
'V0EPUH');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/main.dart';
|
import 'package:twonly/main.dart';
|
||||||
|
import 'package:twonly/src/app.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
|
|
@ -56,7 +57,7 @@ Future sendTextMessage(Int64 target, String message) async {
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await encryptAndSendMessage(target, msg);
|
encryptAndSendMessage(target, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
||||||
|
|
@ -122,7 +123,7 @@ Future sendImage(List<Int64> userIds, String imagePath) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
Future tryDownloadMedia(List<int> mediaToken, {bool force = false}) async {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
// TODO: create option to enable download via mobile data
|
// TODO: create option to enable download via mobile data
|
||||||
final List<ConnectivityResult> connectivityResult =
|
final List<ConnectivityResult> connectivityResult =
|
||||||
|
|
@ -132,32 +133,39 @@ Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Logger("tryDownloadMedia").info("Downloading: $imageToken");
|
Logger("tryDownloadMedia").info("Downloading: $mediaToken");
|
||||||
apiProvider.triggerDownload(imageToken);
|
int offset = 0;
|
||||||
|
final box = await getMediaStorage();
|
||||||
|
Uint8List? media = box.get("$mediaToken");
|
||||||
|
if (media != null && media.isNotEmpty) {
|
||||||
|
offset = media.length;
|
||||||
|
}
|
||||||
|
globalCallBackOnDownloadChange(mediaToken, true);
|
||||||
|
apiProvider.triggerDownload(mediaToken, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future userOpenedMessage(int fromUserId, int messageId) async {
|
Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async {
|
||||||
await DbMessages.userOpenedMessage(messageId);
|
await DbMessages.userOpenedOtherMessage(messageOtherId, fromUserId);
|
||||||
|
|
||||||
encryptAndSendMessage(
|
encryptAndSendMessage(
|
||||||
Int64(fromUserId),
|
Int64(fromUserId),
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.opened,
|
kind: MessageKind.opened,
|
||||||
messageId: messageId,
|
messageId: messageOtherId,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> getDownloadedMedia(
|
Future<Uint8List?> getDownloadedMedia(
|
||||||
List<int> mediaToken, int messageId) async {
|
List<int> mediaToken, int messageOtherId) async {
|
||||||
final box = await getMediaStorage();
|
final box = await getMediaStorage();
|
||||||
Uint8List? media = box.get("${mediaToken}_downloaded");
|
Uint8List? media = box.get("${mediaToken}_downloaded");
|
||||||
int fromUserId = box.get("${mediaToken}_fromUserId");
|
int fromUserId = box.get("${mediaToken}_fromUserId");
|
||||||
await userOpenedMessage(fromUserId, messageId);
|
await userOpenedOtherMessage(fromUserId, messageOtherId);
|
||||||
box.delete(mediaToken.toString());
|
// box.delete(mediaToken.toString());
|
||||||
box.put("${mediaToken}_downloaded", "deleted");
|
// box.put("${mediaToken}_downloaded", "deleted");
|
||||||
box.delete("${mediaToken}_fromUserId");
|
// box.delete("${mediaToken}_fromUserId");
|
||||||
return media;
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
Uint8List downloadedBytes;
|
Uint8List downloadedBytes;
|
||||||
if (buffered != null) {
|
if (buffered != null) {
|
||||||
if (data.offset != buffered.length) {
|
if (data.offset != buffered.length) {
|
||||||
return client.Response()..error = ErrorCode.BadRequest;
|
// Logger("handleDownloadData").error(object)
|
||||||
|
return client.Response()..error = ErrorCode.InvalidOffset;
|
||||||
}
|
}
|
||||||
var b = BytesBuilder();
|
var b = BytesBuilder();
|
||||||
b.add(buffered);
|
b.add(buffered);
|
||||||
|
|
@ -70,7 +71,9 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
|
|
||||||
if (data.fin) {
|
if (data.fin) {
|
||||||
SignalHelper.getSignalStore();
|
SignalHelper.getSignalStore();
|
||||||
int fromUserId = box.get("${data.uploadToken}_fromUserId")!;
|
int? fromUserId = box.get("${data.uploadToken}_fromUserId");
|
||||||
|
if (fromUserId != null) {
|
||||||
|
print(fromUserId);
|
||||||
Uint8List? rawBytes =
|
Uint8List? rawBytes =
|
||||||
await SignalHelper.decryptBytes(downloadedBytes, Int64(fromUserId));
|
await SignalHelper.decryptBytes(downloadedBytes, Int64(fromUserId));
|
||||||
|
|
||||||
|
|
@ -80,6 +83,8 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
|
||||||
|
|
||||||
box.delete(boxId);
|
box.delete(boxId);
|
||||||
globalCallBackOnMessageChange(fromUserId);
|
globalCallBackOnMessageChange(fromUserId);
|
||||||
|
globalCallBackOnDownloadChange(data.uploadToken, false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
box.put(boxId, downloadedBytes);
|
box.put(boxId, downloadedBytes);
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +107,7 @@ Future<client.Response> handleNewMessage(
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MessageKind.opened:
|
case MessageKind.opened:
|
||||||
await DbMessages.userOpenedMessageOtherUser(
|
await DbMessages.otherUserOpenedMyMessage(
|
||||||
fromUserId.toInt(),
|
fromUserId.toInt(),
|
||||||
message.messageId!,
|
message.messageId!,
|
||||||
message.timestamp,
|
message.timestamp,
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,11 @@ class ApiProvider {
|
||||||
return await _sendRequestV0(req);
|
return await _sendRequestV0(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> triggerDownload(List<int> token) async {
|
Future<Result> triggerDownload(List<int> token, int offset) async {
|
||||||
var get = ApplicationData_DownloadData()..uploadToken = token;
|
log.info("Offset: ${offset}");
|
||||||
|
var get = ApplicationData_DownloadData()
|
||||||
|
..uploadToken = token
|
||||||
|
..offset = offset;
|
||||||
var appData = ApplicationData()..downloaddata = get;
|
var appData = ApplicationData()..downloaddata = get;
|
||||||
var req = createClientToServerFromApplicationData(appData);
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
return await _sendRequestV0(req);
|
return await _sendRequestV0(req);
|
||||||
|
|
|
||||||
16
lib/src/providers/download_change_provider.dart
Normal file
16
lib/src/providers/download_change_provider.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
class DownloadChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
|
final HashSet<List<int>> _currentlyDownloading = HashSet<List<int>>();
|
||||||
|
|
||||||
|
HashSet<List<int>> get currentlyDownloading => _currentlyDownloading;
|
||||||
|
|
||||||
|
void update(List<int> token, bool add) {
|
||||||
|
if (add) {
|
||||||
|
_currentlyDownloading.add(token);
|
||||||
|
} else {
|
||||||
|
_currentlyDownloading.remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,10 @@ import 'package:twonly/src/model/messages_model.dart';
|
||||||
/// for every contact.
|
/// for every contact.
|
||||||
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
||||||
|
final Map<int, int> _changeCounter = <int, int>{};
|
||||||
|
|
||||||
Map<int, DbMessage> get lastMessage => _lastMessage;
|
Map<int, DbMessage> get lastMessage => _lastMessage;
|
||||||
|
Map<int, int> get changeCounter => _changeCounter;
|
||||||
|
|
||||||
void updateLastMessageFor(int targetUserId) async {
|
void updateLastMessageFor(int targetUserId) async {
|
||||||
DbMessage? last =
|
DbMessage? last =
|
||||||
|
|
@ -14,6 +17,10 @@ class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
_lastMessage[last.otherUserId] = last;
|
_lastMessage[last.otherUserId] = last;
|
||||||
}
|
}
|
||||||
|
if (!changeCounter.containsKey(targetUserId)) {
|
||||||
|
changeCounter[targetUserId] = 0;
|
||||||
|
}
|
||||||
|
changeCounter[targetUserId] = changeCounter[targetUserId]! + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() async {
|
void init() async {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cv/cv.dart';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
@ -17,10 +18,10 @@ class ChatListEntry extends StatelessWidget {
|
||||||
final DbMessage message;
|
final DbMessage message;
|
||||||
final Contact user;
|
final Contact user;
|
||||||
final bool lastMessageFromSameUser;
|
final bool lastMessageFromSameUser;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool right = message.messageOtherId == null;
|
bool right = message.messageOtherId == null;
|
||||||
|
|
||||||
MessageSendState state = message.getSendState();
|
MessageSendState state = message.getSendState();
|
||||||
|
|
||||||
Widget child = Container();
|
Widget child = Container();
|
||||||
|
|
@ -29,15 +30,15 @@ class ChatListEntry extends StatelessWidget {
|
||||||
case MessageKind.textMessage:
|
case MessageKind.textMessage:
|
||||||
child = Container(
|
child = Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width *
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
0.8, // Maximum 80% of the screen width
|
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: 4, horizontal: 10), // Add some padding around the text
|
vertical: 4, horizontal: 10), // Add some padding around the text
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: right
|
color: right
|
||||||
? Colors.deepPurpleAccent
|
? const Color.fromARGB(107, 124, 77, 255)
|
||||||
: Colors.blueAccent, // Set the background color
|
: const Color.fromARGB(
|
||||||
|
83, 68, 137, 255), // Set the background color
|
||||||
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -71,7 +72,7 @@ class ChatListEntry extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
width: 200,
|
width: 150,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: color, // Set the background color
|
color: color, // Set the background color
|
||||||
|
|
@ -79,10 +80,13 @@ class ChatListEntry extends StatelessWidget {
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
||||||
),
|
),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
child: MessageSendStateIcon(
|
child: MessageSendStateIcon(
|
||||||
state,
|
message,
|
||||||
message.isDownloaded,
|
mainAxisAlignment:
|
||||||
message.messageKind,
|
right ? MainAxisAlignment.center : MainAxisAlignment.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -94,7 +98,7 @@ class ChatListEntry extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: lastMessageFromSameUser
|
padding: lastMessageFromSameUser
|
||||||
? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10)
|
? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10)
|
||||||
: EdgeInsets.all(10),
|
: EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10),
|
||||||
child: child),
|
child: child),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -112,34 +116,50 @@ class ChatItemDetailsView extends StatefulWidget {
|
||||||
|
|
||||||
class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
List<DbMessage> _messages = [];
|
List<DbMessage> _messages = [];
|
||||||
|
int lastChangeCounter = 0;
|
||||||
final TextEditingController newMessageController = TextEditingController();
|
final TextEditingController newMessageController = TextEditingController();
|
||||||
|
HashSet<int> alreadyReportedOpened = HashSet<int>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadAsync();
|
_loadAsync(updateOpenStatus: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _loadAsync() async {
|
Future _loadAsync({bool updateOpenStatus = false}) async {
|
||||||
_messages =
|
_messages =
|
||||||
await DbMessages.getAllMessagesForUser(widget.user.userId.toInt());
|
await DbMessages.getAllMessagesForUser(widget.user.userId.toInt());
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
if (updateOpenStatus) {
|
||||||
|
_messages.where((x) => x.messageOpenedAt == null).forEach((message) {
|
||||||
|
if (message.messageOtherId != null &&
|
||||||
|
message.messageKind == MessageKind.textMessage) {
|
||||||
|
if (!alreadyReportedOpened.contains(message.messageOtherId!)) {
|
||||||
|
userOpenedOtherMessage(
|
||||||
|
message.otherUserId, message.messageOtherId!);
|
||||||
|
alreadyReportedOpened.add(message.messageOtherId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _sendMessage() async {
|
Future _sendMessage() async {
|
||||||
String text = newMessageController.text;
|
String text = newMessageController.text;
|
||||||
if (text == "") return;
|
if (text == "") return;
|
||||||
sendTextMessage(widget.user.userId, newMessageController.text);
|
await sendTextMessage(widget.user.userId, newMessageController.text);
|
||||||
|
_loadAsync();
|
||||||
newMessageController.clear();
|
newMessageController.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final messages = context.watch<MessagesChangeProvider>().lastMessage;
|
final changeCounter = context.watch<MessagesChangeProvider>().changeCounter;
|
||||||
if (messages.containsKey(widget.user.userId.toInt()) &&
|
if (changeCounter.containsKey(widget.user.userId.toInt())) {
|
||||||
_messages.isNotEmpty) {
|
if (changeCounter[widget.user.userId.toInt()] != lastChangeCounter) {
|
||||||
final lastMessage = messages[widget.user.userId.toInt()];
|
_loadAsync(updateOpenStatus: true);
|
||||||
if (lastMessage!.messageId != _messages[0].messageId) {
|
lastChangeCounter = changeCounter[widget.user.userId.toInt()]!;
|
||||||
_loadAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// messages = messages.reversed.toList();
|
// messages = messages.reversed.toList();
|
||||||
|
|
@ -163,7 +183,10 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
_messages[i].messageOtherId != null);
|
_messages[i].messageOtherId != null);
|
||||||
}
|
}
|
||||||
return ChatListEntry(
|
return ChatListEntry(
|
||||||
_messages[i], widget.user, lastMessageFromSameUser);
|
_messages[i],
|
||||||
|
widget.user,
|
||||||
|
lastMessageFromSameUser,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,7 @@ class _UserListItem extends State<UserListItem> {
|
||||||
title: Text(widget.user.displayName),
|
title: Text(widget.user.displayName),
|
||||||
subtitle: Row(
|
subtitle: Row(
|
||||||
children: [
|
children: [
|
||||||
MessageSendStateIcon(state, widget.lastMessage.isDownloaded,
|
MessageSendStateIcon(widget.lastMessage),
|
||||||
widget.lastMessage.messageKind),
|
|
||||||
Text("•"),
|
Text("•"),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -190,13 +189,19 @@ class _UserListItem extends State<UserListItem> {
|
||||||
tryDownloadMedia(token, force: true);
|
tryDownloadMedia(token, force: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state == MessageSendState.received &&
|
||||||
|
widget.lastMessage.containsOtherMedia()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
if (state == MessageSendState.received &&
|
|
||||||
widget.lastMessage.containsOtherMedia()) {
|
|
||||||
return MediaViewerView(widget.user, widget.lastMessage);
|
return MediaViewerView(widget.user, widget.lastMessage);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
return ChatItemDetailsView(user: widget.user);
|
return ChatItemDetailsView(user: widget.user);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
|
|
||||||
Future _initAsync() async {
|
Future _initAsync() async {
|
||||||
List<int> token = widget.message.messageContent!.downloadToken!;
|
List<int> token = widget.message.messageContent!.downloadToken!;
|
||||||
_imageByte = await getDownloadedMedia(token, widget.message.messageId);
|
_imageByte =
|
||||||
|
await getDownloadedMedia(token, widget.message.messageOtherId!);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue