mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
clean code
This commit is contained in:
parent
2f3af42771
commit
5cea69c224
22 changed files with 941 additions and 717 deletions
|
|
@ -4,7 +4,8 @@ import 'package:twonly/src/providers/api_provider.dart';
|
|||
import 'package:twonly/src/providers/db_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'src/app.dart';
|
||||
import 'src/settings/settings_controller.dart';
|
||||
|
|
@ -54,7 +55,8 @@ void main() async {
|
|||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => NotifyProvider()),
|
||||
ChangeNotifierProvider(create: (_) => MessagesChangeProvider()),
|
||||
ChangeNotifierProvider(create: (_) => ContactChangeProvider()),
|
||||
],
|
||||
child: MyApp(settingsController: settingsController),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/onboarding_view.dart';
|
||||
import 'package:twonly/src/views/home_view.dart';
|
||||
|
|
@ -12,9 +12,16 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||
import 'dart:async';
|
||||
import 'settings/settings_controller.dart';
|
||||
|
||||
Function(Int64) addSendingTo = (a) {};
|
||||
Function(Int64) removeSendingTo = (a) {};
|
||||
Function() updateNotifyProvider = () {};
|
||||
// these global function can be called from anywhere to update
|
||||
// the ui when something changed. The callbacks will be set by
|
||||
// MyApp widget.
|
||||
|
||||
// this callback is called by the apiProvider
|
||||
Function(bool) globalCallbackConnectionState = (a) {};
|
||||
|
||||
// these two callbacks are called on updated to the corresponding database
|
||||
Function globalCallBackOnContactChange = () {};
|
||||
Function(int) globalCallBackOnMessageChange = (a) {};
|
||||
|
||||
/// The Widget that configures your application.
|
||||
class MyApp extends StatefulWidget {
|
||||
|
|
@ -28,7 +35,7 @@ class MyApp extends StatefulWidget {
|
|||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
Future<bool> _isUserCreated = isUserCreated();
|
||||
bool _showOnboarding = true;
|
||||
bool _showOnboarding = false;
|
||||
bool _isConnected = false;
|
||||
int redColorOpacity = 0; // Start with dark red
|
||||
bool redColorGoUp = true;
|
||||
|
|
@ -39,31 +46,38 @@ class _MyAppState extends State<MyApp> {
|
|||
super.initState();
|
||||
_startColorAnimation();
|
||||
|
||||
apiProvider.setConnectionStateCallback((isConnected) {
|
||||
// init change provider to load data from the database
|
||||
context.read<ContactChangeProvider>().update();
|
||||
context.read<MessagesChangeProvider>().init();
|
||||
|
||||
// register global callbacks to the widget tree
|
||||
globalCallbackConnectionState = (isConnected) {
|
||||
setState(() {
|
||||
_isConnected = isConnected;
|
||||
});
|
||||
});
|
||||
apiProvider.setUpdatedContacts(() {
|
||||
context.read<NotifyProvider>().update();
|
||||
});
|
||||
|
||||
addSendingTo = (a) {
|
||||
context.read<NotifyProvider>().addSendingTo(a);
|
||||
};
|
||||
|
||||
removeSendingTo = (a) {
|
||||
context.read<NotifyProvider>().removeSendingTo(a);
|
||||
globalCallBackOnContactChange = () {
|
||||
context.read<ContactChangeProvider>().update();
|
||||
};
|
||||
|
||||
updateNotifyProvider = () {
|
||||
context.read<NotifyProvider>().update();
|
||||
globalCallBackOnMessageChange = (userId) {
|
||||
context.read<MessagesChangeProvider>().updateLastMessageFor(userId);
|
||||
};
|
||||
|
||||
context.read<NotifyProvider>().update();
|
||||
// connect async to the backend api
|
||||
apiProvider.connect();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// disable globalCallbacks to the flutter tree
|
||||
globalCallbackConnectionState = (a) {};
|
||||
globalCallBackOnContactChange = () {};
|
||||
globalCallBackOnMessageChange = (a) {};
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startColorAnimation() {
|
||||
// Change the color every second
|
||||
Future.delayed(Duration(milliseconds: 200), () {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
"searchUsernameNotFound": "Username not found",
|
||||
"searchUsernameNewFollowerTitle": "Follow requests",
|
||||
"searchUsernameQrCodeBtn": "Scan QR code",
|
||||
"chatListViewSearchUserNameBtn": "Add user",
|
||||
"chatListViewSendFirstTwonly": "Send your first twonly!",
|
||||
"searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.",
|
||||
"errorUnknown": "An unexpected error has occurred. Please try again later.",
|
||||
"errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:cv/cv.dart';
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/app.dart';
|
||||
|
||||
class Contact {
|
||||
Contact(
|
||||
|
|
@ -98,6 +99,7 @@ class DbContacts extends CvModelBase {
|
|||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
|
||||
static Future acceptUser(int userId) async {
|
||||
|
|
@ -111,6 +113,7 @@ class DbContacts extends CvModelBase {
|
|||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
|
||||
static Future deleteUser(int userId) async {
|
||||
|
|
@ -119,6 +122,7 @@ class DbContacts extends CvModelBase {
|
|||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
|
||||
static Future<bool> insertNewContact(
|
||||
|
|
@ -130,6 +134,7 @@ class DbContacts extends CvModelBase {
|
|||
DbContacts.columnUserId: userId,
|
||||
DbContacts.columnRequested: a
|
||||
});
|
||||
globalCallBackOnContactChange();
|
||||
return true;
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||
import 'package:cv/cv.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/app.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
|
||||
class DbMessage {
|
||||
|
|
@ -13,7 +14,8 @@ class DbMessage {
|
|||
required this.messageMessageKind,
|
||||
required this.messageContent,
|
||||
required this.messageOpenedAt,
|
||||
required this.messageAcknowledge,
|
||||
required this.messageAcknowledgeByUser,
|
||||
required this.messageAcknowledgeByServer,
|
||||
required this.sendOrReceivedAt,
|
||||
});
|
||||
|
||||
|
|
@ -22,9 +24,10 @@ class DbMessage {
|
|||
int? messageOtherId;
|
||||
int otherUserId;
|
||||
MessageKind messageMessageKind;
|
||||
MessageContent messageContent;
|
||||
MessageContent? messageContent;
|
||||
DateTime? messageOpenedAt;
|
||||
bool messageAcknowledge;
|
||||
bool messageAcknowledgeByUser;
|
||||
bool messageAcknowledgeByServer;
|
||||
DateTime sendOrReceivedAt;
|
||||
}
|
||||
|
||||
|
|
@ -38,19 +41,24 @@ class DbMessages extends CvModelBase {
|
|||
final messageOtherId = CvField<int?>(columnMessageOtherId);
|
||||
|
||||
static const columnOtherUserId = "other_user_id";
|
||||
final otherUserId = CvField<int?>(columnOtherUserId);
|
||||
final otherUserId = CvField<int>(columnOtherUserId);
|
||||
|
||||
static const columnMessageKind = "message_kind";
|
||||
final messageMessageKind = CvField<int>(columnMessageKind);
|
||||
|
||||
static const columnMessageContentJson = "message_json";
|
||||
final messageContentJson = CvField<String>(columnMessageContentJson);
|
||||
final messageContentJson = CvField<String?>(columnMessageContentJson);
|
||||
|
||||
static const columnMessageOpenedAt = "message_opened_at";
|
||||
final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt);
|
||||
|
||||
static const columnMessageAcknowledge = "message_acknowledged";
|
||||
final messageAcknowledge = CvField<int>(columnMessageAcknowledge);
|
||||
static const columnMessageAcknowledgeByUser = "message_acknowledged_by_user";
|
||||
final messageAcknowledgeByUser = CvField<int>(columnMessageAcknowledgeByUser);
|
||||
|
||||
static const columnMessageAcknowledgeByServer =
|
||||
"message_acknowledged_by_server";
|
||||
final messageAcknowledgeByServer =
|
||||
CvField<int>(columnMessageAcknowledgeByServer);
|
||||
|
||||
static const columnSendOrReceivedAt = "message_send_or_received_at";
|
||||
final sendOrReceivedAt = CvField<DateTime>(columnSendOrReceivedAt);
|
||||
|
|
@ -63,10 +71,11 @@ class DbMessages extends CvModelBase {
|
|||
CREATE TABLE IF NOT EXISTS $tableName (
|
||||
$columnMessageId INTEGER NOT NULL PRIMARY KEY,
|
||||
$columnMessageOtherId INTEGER DEFAULT NULL,
|
||||
$columnOtherUserId INTEGER DEFAULT NULL,
|
||||
$columnOtherUserId INTEGER NOT NULL,
|
||||
$columnMessageKind INTEGER NOT NULL,
|
||||
$columnMessageAcknowledge INTEGER NOT NULL DEFAULT 0,
|
||||
$columnMessageContentJson TEXT NOT NULL,
|
||||
$columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0,
|
||||
$columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0,
|
||||
$columnMessageContentJson TEXT DEFAULT NULL,
|
||||
$columnMessageOpenedAt DATETIME DEFAULT NULL,
|
||||
$columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
$columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
|
|
@ -74,8 +83,33 @@ class DbMessages extends CvModelBase {
|
|||
""";
|
||||
}
|
||||
|
||||
static Future<int?> insertMyMessage(
|
||||
int userIdFrom, MessageKind kind, String jsonContent) async {
|
||||
static Future deleteMessageById(int messageId) async {
|
||||
await dbProvider.db!.delete(
|
||||
tableName,
|
||||
where: '$columnMessageId = ?',
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
int? fromUserId = await getFromUserIdByMessageId(messageId);
|
||||
if (fromUserId != null) {
|
||||
globalCallBackOnMessageChange(fromUserId);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int?> getFromUserIdByMessageId(int messageId) async {
|
||||
List<Map<String, dynamic>> result = await dbProvider.db!.query(
|
||||
tableName,
|
||||
columns: [columnOtherUserId],
|
||||
where: '$columnMessageId = ?',
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
if (result.isNotEmpty) {
|
||||
return result.first[columnOtherUserId] as int?;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<int?> insertMyMessage(int userIdFrom, MessageKind kind,
|
||||
{String? jsonContent}) async {
|
||||
try {
|
||||
int messageId = await dbProvider.db!.insert(tableName, {
|
||||
columnMessageKind: kind.index,
|
||||
|
|
@ -83,6 +117,7 @@ class DbMessages extends CvModelBase {
|
|||
columnOtherUserId: userIdFrom,
|
||||
columnSendOrReceivedAt: DateTime.now().toIso8601String()
|
||||
});
|
||||
globalCallBackOnMessageChange(userIdFrom);
|
||||
return messageId;
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
|
|
@ -97,8 +132,12 @@ class DbMessages extends CvModelBase {
|
|||
columnMessageOtherId: messageId,
|
||||
columnMessageKind: kind.index,
|
||||
columnMessageContentJson: jsonContent,
|
||||
columnMessageAcknowledgeByServer: 1,
|
||||
columnMessageAcknowledgeByUser:
|
||||
0, // ack in case of sending corresponds to the opened flag
|
||||
columnOtherUserId: userIdFrom
|
||||
});
|
||||
globalCallBackOnMessageChange(userIdFrom);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
|
|
@ -106,56 +145,52 @@ class DbMessages extends CvModelBase {
|
|||
}
|
||||
}
|
||||
|
||||
static List<DbMessage> convertToDbMessage(List<dynamic> fromDb) {
|
||||
try {
|
||||
List<DbMessage> parsedUsers = [];
|
||||
for (int i = 0; i < fromDb.length; i++) {
|
||||
dynamic messageOpenedAt = fromDb[i][columnMessageOpenedAt];
|
||||
if (messageOpenedAt != null) {
|
||||
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
|
||||
}
|
||||
print("Datetime: ${fromDb[i][columnSendOrReceivedAt]}");
|
||||
print(
|
||||
"Datetime parsed: ${DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])}");
|
||||
parsedUsers.add(
|
||||
DbMessage(
|
||||
sendOrReceivedAt:
|
||||
DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])!,
|
||||
messageId: fromDb[i][columnMessageId],
|
||||
messageOtherId: fromDb[i][columnMessageOtherId],
|
||||
otherUserId: fromDb[i][columnOtherUserId],
|
||||
messageMessageKind:
|
||||
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]),
|
||||
messageContent: MessageContent.fromJson(
|
||||
jsonDecode(fromDb[i][columnMessageContentJson])),
|
||||
messageOpenedAt: messageOpenedAt,
|
||||
messageAcknowledge: fromDb[i][columnMessageAcknowledge] == 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
return parsedUsers;
|
||||
} catch (e) {
|
||||
Logger("messages_model/convertToDbMessage").shout("$e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<DbMessage?> getLastMessagesForPreviewForUser(
|
||||
int otherUserId) async {
|
||||
var rows = await dbProvider.db!.query(tableName,
|
||||
var rows = await dbProvider.db!.query(
|
||||
tableName,
|
||||
where: "$columnOtherUserId = ?",
|
||||
whereArgs: [otherUserId],
|
||||
orderBy: "$columnUpdatedAt DESC",
|
||||
limit: 1);
|
||||
limit: 10,
|
||||
);
|
||||
|
||||
List<DbMessage> messages = convertToDbMessage(rows);
|
||||
|
||||
// check if there is a message which was not ack by the server
|
||||
List<DbMessage> notAckByServer =
|
||||
messages.where((c) => !c.messageAcknowledgeByServer).toList();
|
||||
if (notAckByServer.isNotEmpty) return notAckByServer[0];
|
||||
|
||||
// check if there is a message which was not ack by the user
|
||||
List<DbMessage> notAckByUser =
|
||||
messages.where((c) => !c.messageAcknowledgeByUser).toList();
|
||||
if (notAckByUser.isNotEmpty) return notAckByUser[0];
|
||||
|
||||
if (messages.isEmpty) return null;
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
static Future acknowledgeMessage(int fromUserId, int messageId) async {
|
||||
static Future acknowledgeMessageByServer(int messageId) async {
|
||||
Map<String, dynamic> valuesToUpdate = {
|
||||
columnMessageAcknowledge: 1,
|
||||
columnMessageAcknowledgeByServer: 1,
|
||||
};
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
valuesToUpdate,
|
||||
where: "$messageId = ?",
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
int? fromUserId = await getFromUserIdByMessageId(messageId);
|
||||
if (fromUserId != null) {
|
||||
globalCallBackOnMessageChange(fromUserId);
|
||||
}
|
||||
}
|
||||
|
||||
// check fromUserId to prevent spoofing
|
||||
static Future acknowledgeMessageByUser(int fromUserId, int messageId) async {
|
||||
Map<String, dynamic> valuesToUpdate = {
|
||||
columnMessageAcknowledgeByUser: 1,
|
||||
};
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
|
|
@ -163,6 +198,7 @@ class DbMessages extends CvModelBase {
|
|||
where: "$messageId = ? AND $columnOtherUserId = ?",
|
||||
whereArgs: [messageId, fromUserId],
|
||||
);
|
||||
globalCallBackOnMessageChange(fromUserId);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -173,4 +209,42 @@ class DbMessages extends CvModelBase {
|
|||
messageOpenedAt,
|
||||
sendOrReceivedAt
|
||||
];
|
||||
|
||||
static List<DbMessage> convertToDbMessage(List<dynamic> fromDb) {
|
||||
try {
|
||||
List<DbMessage> parsedUsers = [];
|
||||
for (int i = 0; i < fromDb.length; i++) {
|
||||
dynamic messageOpenedAt = fromDb[i][columnMessageOpenedAt];
|
||||
if (messageOpenedAt != null) {
|
||||
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
|
||||
}
|
||||
dynamic content = fromDb[i][columnMessageContentJson];
|
||||
if (content != null) {
|
||||
content = MessageContent.fromJson(
|
||||
jsonDecode(fromDb[i][columnMessageContentJson]));
|
||||
}
|
||||
parsedUsers.add(
|
||||
DbMessage(
|
||||
sendOrReceivedAt:
|
||||
DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])!,
|
||||
messageId: fromDb[i][columnMessageId],
|
||||
messageOtherId: fromDb[i][columnMessageOtherId],
|
||||
otherUserId: fromDb[i][columnOtherUserId],
|
||||
messageMessageKind:
|
||||
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]),
|
||||
messageContent: content,
|
||||
messageOpenedAt: messageOpenedAt,
|
||||
messageAcknowledgeByUser:
|
||||
fromDb[i][columnMessageAcknowledgeByUser] == 1,
|
||||
messageAcknowledgeByServer:
|
||||
fromDb[i][columnMessageAcknowledgeByServer] == 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
return parsedUsers;
|
||||
} catch (e) {
|
||||
Logger("messages_model/convertToDbMessage").shout("$e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -742,21 +742,12 @@ class ApplicationData_GetPrekeysByUserId extends $pb.GeneratedMessage {
|
|||
}
|
||||
|
||||
class ApplicationData_GetUploadToken extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_GetUploadToken({
|
||||
$core.int? len,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (len != null) {
|
||||
$result.len = len;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
factory ApplicationData_GetUploadToken() => create();
|
||||
ApplicationData_GetUploadToken._() : super();
|
||||
factory ApplicationData_GetUploadToken.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ApplicationData_GetUploadToken.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.GetUploadToken', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'len', $pb.PbFieldType.OU3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -780,15 +771,6 @@ class ApplicationData_GetUploadToken extends $pb.GeneratedMessage {
|
|||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_GetUploadToken getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_GetUploadToken>(create);
|
||||
static ApplicationData_GetUploadToken? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get len => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set len($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasLen() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearLen() => clearField(1);
|
||||
}
|
||||
|
||||
class ApplicationData_UploadData extends $pb.GeneratedMessage {
|
||||
|
|
@ -869,6 +851,56 @@ class ApplicationData_UploadData extends $pb.GeneratedMessage {
|
|||
void clearData() => clearField(3);
|
||||
}
|
||||
|
||||
class ApplicationData_DownloadData extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_DownloadData({
|
||||
$core.List<$core.int>? uploadToken,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (uploadToken != null) {
|
||||
$result.uploadToken = uploadToken;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ApplicationData_DownloadData._() : super();
|
||||
factory ApplicationData_DownloadData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ApplicationData_DownloadData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
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)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ApplicationData_DownloadData clone() => ApplicationData_DownloadData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ApplicationData_DownloadData copyWith(void Function(ApplicationData_DownloadData) updates) => super.copyWith((message) => updates(message as ApplicationData_DownloadData)) as ApplicationData_DownloadData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_DownloadData create() => ApplicationData_DownloadData._();
|
||||
ApplicationData_DownloadData createEmptyInstance() => create();
|
||||
static $pb.PbList<ApplicationData_DownloadData> createRepeated() => $pb.PbList<ApplicationData_DownloadData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_DownloadData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_DownloadData>(create);
|
||||
static ApplicationData_DownloadData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get uploadToken => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set uploadToken($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasUploadToken() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearUploadToken() => clearField(1);
|
||||
}
|
||||
|
||||
enum ApplicationData_ApplicationData {
|
||||
textmessage,
|
||||
getuserbyusername,
|
||||
|
|
@ -876,6 +908,7 @@ enum ApplicationData_ApplicationData {
|
|||
getuploadtoken,
|
||||
uploaddata,
|
||||
getuserbyid,
|
||||
downloaddata,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -887,6 +920,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
ApplicationData_GetUploadToken? getuploadtoken,
|
||||
ApplicationData_UploadData? uploaddata,
|
||||
ApplicationData_GetUserById? getuserbyid,
|
||||
ApplicationData_DownloadData? downloaddata,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (textmessage != null) {
|
||||
|
|
@ -907,6 +941,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
if (getuserbyid != null) {
|
||||
$result.getuserbyid = getuserbyid;
|
||||
}
|
||||
if (downloaddata != null) {
|
||||
$result.downloaddata = downloaddata;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ApplicationData._() : super();
|
||||
|
|
@ -920,16 +957,18 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
4 : ApplicationData_ApplicationData.getuploadtoken,
|
||||
5 : ApplicationData_ApplicationData.uploaddata,
|
||||
6 : ApplicationData_ApplicationData.getuserbyid,
|
||||
7 : ApplicationData_ApplicationData.downloaddata,
|
||||
0 : ApplicationData_ApplicationData.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
|
||||
..oo(0, [1, 2, 3, 4, 5, 6])
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7])
|
||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textmessage', subBuilder: ApplicationData_TextMessage.create)
|
||||
..aOM<ApplicationData_GetUserByUsername>(2, _omitFieldNames ? '' : 'getuserbyusername', subBuilder: ApplicationData_GetUserByUsername.create)
|
||||
..aOM<ApplicationData_GetPrekeysByUserId>(3, _omitFieldNames ? '' : 'getprekeysbyuserid', subBuilder: ApplicationData_GetPrekeysByUserId.create)
|
||||
..aOM<ApplicationData_GetUploadToken>(4, _omitFieldNames ? '' : 'getuploadtoken', subBuilder: ApplicationData_GetUploadToken.create)
|
||||
..aOM<ApplicationData_UploadData>(5, _omitFieldNames ? '' : 'uploaddata', subBuilder: ApplicationData_UploadData.create)
|
||||
..aOM<ApplicationData_GetUserById>(6, _omitFieldNames ? '' : 'getuserbyid', subBuilder: ApplicationData_GetUserById.create)
|
||||
..aOM<ApplicationData_DownloadData>(7, _omitFieldNames ? '' : 'downloaddata', subBuilder: ApplicationData_DownloadData.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -1022,6 +1061,17 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
void clearGetuserbyid() => clearField(6);
|
||||
@$pb.TagNumber(6)
|
||||
ApplicationData_GetUserById ensureGetuserbyid() => $_ensure(5);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
ApplicationData_DownloadData get downloaddata => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set downloaddata(ApplicationData_DownloadData v) { setField(7, v); }
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasDownloaddata() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearDownloaddata() => clearField(7);
|
||||
@$pb.TagNumber(7)
|
||||
ApplicationData_DownloadData ensureDownloaddata() => $_ensure(6);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
|
|
|
|||
|
|
@ -117,12 +117,13 @@ const ApplicationData$json = {
|
|||
'2': [
|
||||
{'1': 'textmessage', '3': 1, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.TextMessage', '9': 0, '10': 'textmessage'},
|
||||
{'1': 'getuserbyusername', '3': 2, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUserByUsername', '9': 0, '10': 'getuserbyusername'},
|
||||
{'1': 'getuserbyid', '3': 6, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUserById', '9': 0, '10': 'getuserbyid'},
|
||||
{'1': 'getprekeysbyuserid', '3': 3, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetPrekeysByUserId', '9': 0, '10': 'getprekeysbyuserid'},
|
||||
{'1': 'getuploadtoken', '3': 4, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUploadToken', '9': 0, '10': 'getuploadtoken'},
|
||||
{'1': 'uploaddata', '3': 5, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.UploadData', '9': 0, '10': 'uploaddata'},
|
||||
{'1': 'getuserbyid', '3': 6, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUserById', '9': 0, '10': 'getuserbyid'},
|
||||
{'1': 'downloaddata', '3': 7, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.DownloadData', '9': 0, '10': 'downloaddata'},
|
||||
],
|
||||
'3': [ApplicationData_TextMessage$json, ApplicationData_GetUserByUsername$json, ApplicationData_GetUserById$json, ApplicationData_GetPrekeysByUserId$json, ApplicationData_GetUploadToken$json, ApplicationData_UploadData$json],
|
||||
'3': [ApplicationData_TextMessage$json, ApplicationData_GetUserByUsername$json, ApplicationData_GetUserById$json, ApplicationData_GetPrekeysByUserId$json, ApplicationData_GetUploadToken$json, ApplicationData_UploadData$json, ApplicationData_DownloadData$json],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
],
|
||||
|
|
@ -164,9 +165,6 @@ const ApplicationData_GetPrekeysByUserId$json = {
|
|||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetUploadToken$json = {
|
||||
'1': 'GetUploadToken',
|
||||
'2': [
|
||||
{'1': 'len', '3': 1, '4': 1, '5': 13, '10': 'len'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
|
|
@ -179,25 +177,35 @@ const ApplicationData_UploadData$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_DownloadData$json = {
|
||||
'1': 'DownloadData',
|
||||
'2': [
|
||||
{'1': 'upload_token', '3': 1, '4': 1, '5': 12, '10': 'uploadToken'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dG1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||
'VyLkFwcGxpY2F0aW9uRGF0YS5UZXh0TWVzc2FnZUgAUgt0ZXh0bWVzc2FnZRJjChFnZXR1c2Vy'
|
||||
'Ynl1c2VybmFtZRgCIAEoCzIzLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldF'
|
||||
'VzZXJCeVVzZXJuYW1lSABSEWdldHVzZXJieXVzZXJuYW1lElEKC2dldHVzZXJieWlkGAYgASgL'
|
||||
'Mi0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0VXNlckJ5SWRIAFILZ2V0dX'
|
||||
'NlcmJ5aWQSZgoSZ2V0cHJla2V5c2J5dXNlcmlkGAMgASgLMjQuY2xpZW50X3RvX3NlcnZlci5B'
|
||||
'cHBsaWNhdGlvbkRhdGEuR2V0UHJla2V5c0J5VXNlcklkSABSEmdldHByZWtleXNieXVzZXJpZB'
|
||||
'JaCg5nZXR1cGxvYWR0b2tlbhgEIAEoCzIwLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25E'
|
||||
'YXRhLkdldFVwbG9hZFRva2VuSABSDmdldHVwbG9hZHRva2VuEk4KCnVwbG9hZGRhdGEYBSABKA'
|
||||
'syLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5VcGxvYWREYXRhSABSCnVwbG9h'
|
||||
'ZGRhdGEaOgoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAy'
|
||||
'ABKAxSBGJvZHkaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJu'
|
||||
'YW1lGiYKC0dldFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBotChJHZXRQcmVrZX'
|
||||
'lzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGiIKDkdldFVwbG9hZFRva2VuEhAK'
|
||||
'A2xlbhgBIAEoDVIDbGVuGlsKClVwbG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cG'
|
||||
'xvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRhQhEK'
|
||||
'D0FwcGxpY2F0aW9uRGF0YQ==');
|
||||
'VzZXJCeVVzZXJuYW1lSABSEWdldHVzZXJieXVzZXJuYW1lEmYKEmdldHByZWtleXNieXVzZXJp'
|
||||
'ZBgDIAEoCzI0LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldFByZWtleXNCeV'
|
||||
'VzZXJJZEgAUhJnZXRwcmVrZXlzYnl1c2VyaWQSWgoOZ2V0dXBsb2FkdG9rZW4YBCABKAsyMC5j'
|
||||
'bGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRVcGxvYWRUb2tlbkgAUg5nZXR1cG'
|
||||
'xvYWR0b2tlbhJOCgp1cGxvYWRkYXRhGAUgASgLMiwuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNh'
|
||||
'dGlvbkRhdGEuVXBsb2FkRGF0YUgAUgp1cGxvYWRkYXRhElEKC2dldHVzZXJieWlkGAYgASgLMi'
|
||||
'0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0VXNlckJ5SWRIAFILZ2V0dXNl'
|
||||
'cmJ5aWQSVAoMZG93bmxvYWRkYXRhGAcgASgLMi4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
|
||||
'lvbkRhdGEuRG93bmxvYWREYXRhSABSDGRvd25sb2FkZGF0YRo6CgtUZXh0TWVzc2FnZRIXCgd1'
|
||||
'c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRovChFHZXRVc2VyQnlVc2'
|
||||
'VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaJgoLR2V0VXNlckJ5SWQSFwoHdXNl'
|
||||
'cl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgAS'
|
||||
'gDUgZ1c2VySWQaEAoOR2V0VXBsb2FkVG9rZW4aWwoKVXBsb2FkRGF0YRIhCgx1cGxvYWRfdG9r'
|
||||
'ZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDVIGb2Zmc2V0EhIKBGRhdGEYAy'
|
||||
'ABKAxSBGRhdGEaMQoMRG93bmxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2Fk'
|
||||
'VG9rZW5CEQoPQXBwbGljYXRpb25EYXRh');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ enum V0_Kind {
|
|||
response,
|
||||
newMessage,
|
||||
requestNewPreKeys,
|
||||
downloaddata,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +95,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
Response? response,
|
||||
NewMessage? newMessage,
|
||||
$core.bool? requestNewPreKeys,
|
||||
DownloadData? downloaddata,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (seq != null) {
|
||||
|
|
@ -108,6 +110,9 @@ class V0 extends $pb.GeneratedMessage {
|
|||
if (requestNewPreKeys != null) {
|
||||
$result.requestNewPreKeys = requestNewPreKeys;
|
||||
}
|
||||
if (downloaddata != null) {
|
||||
$result.downloaddata = downloaddata;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
V0._() : super();
|
||||
|
|
@ -118,14 +123,16 @@ class V0 extends $pb.GeneratedMessage {
|
|||
2 : V0_Kind.response,
|
||||
3 : V0_Kind.newMessage,
|
||||
4 : V0_Kind.requestNewPreKeys,
|
||||
5 : V0_Kind.downloaddata,
|
||||
0 : V0_Kind.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'V0', package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create)
|
||||
..oo(0, [2, 3, 4])
|
||||
..oo(0, [2, 3, 4, 5])
|
||||
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..aOM<Response>(2, _omitFieldNames ? '' : 'response', subBuilder: Response.create)
|
||||
..aOM<NewMessage>(3, _omitFieldNames ? '' : 'newMessage', protoName: 'newMessage', subBuilder: NewMessage.create)
|
||||
..aOB(4, _omitFieldNames ? '' : 'RequestNewPreKeys', protoName: 'RequestNewPreKeys')
|
||||
..aOM<DownloadData>(5, _omitFieldNames ? '' : 'downloaddata', subBuilder: DownloadData.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -192,6 +199,17 @@ class V0 extends $pb.GeneratedMessage {
|
|||
$core.bool hasRequestNewPreKeys() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearRequestNewPreKeys() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
DownloadData get downloaddata => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set downloaddata(DownloadData v) { setField(5, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasDownloaddata() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearDownloaddata() => clearField(5);
|
||||
@$pb.TagNumber(5)
|
||||
DownloadData ensureDownloaddata() => $_ensure(4);
|
||||
}
|
||||
|
||||
class NewMessage extends $pb.GeneratedMessage {
|
||||
|
|
@ -258,6 +276,84 @@ class NewMessage extends $pb.GeneratedMessage {
|
|||
void clearFromUserId() => clearField(2);
|
||||
}
|
||||
|
||||
class DownloadData extends $pb.GeneratedMessage {
|
||||
factory DownloadData({
|
||||
$core.List<$core.int>? uploadToken,
|
||||
$core.int? offset,
|
||||
$core.List<$core.int>? data,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (uploadToken != null) {
|
||||
$result.uploadToken = uploadToken;
|
||||
}
|
||||
if (offset != null) {
|
||||
$result.offset = offset;
|
||||
}
|
||||
if (data != null) {
|
||||
$result.data = data;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
DownloadData._() : super();
|
||||
factory DownloadData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DownloadData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DownloadData', package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'uploadToken', $pb.PbFieldType.OY)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'offset', $pb.PbFieldType.OU3)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'data', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DownloadData clone() => DownloadData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DownloadData copyWith(void Function(DownloadData) updates) => super.copyWith((message) => updates(message as DownloadData)) as DownloadData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DownloadData create() => DownloadData._();
|
||||
DownloadData createEmptyInstance() => create();
|
||||
static $pb.PbList<DownloadData> createRepeated() => $pb.PbList<DownloadData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DownloadData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DownloadData>(create);
|
||||
static DownloadData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get uploadToken => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set uploadToken($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasUploadToken() => $_has(0);
|
||||
@$pb.TagNumber(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);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get data => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set data($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasData() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearData() => clearField(3);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
factory Response_PreKey({
|
||||
$fixnum.Int64? id,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const V0$json = {
|
|||
{'1': 'response', '3': 2, '4': 1, '5': 11, '6': '.server_to_client.Response', '9': 0, '10': 'response'},
|
||||
{'1': 'newMessage', '3': 3, '4': 1, '5': 11, '6': '.server_to_client.NewMessage', '9': 0, '10': 'newMessage'},
|
||||
{'1': 'RequestNewPreKeys', '3': 4, '4': 1, '5': 8, '9': 0, '10': 'RequestNewPreKeys'},
|
||||
{'1': 'downloaddata', '3': 5, '4': 1, '5': 11, '6': '.server_to_client.DownloadData', '9': 0, '10': 'downloaddata'},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Kind'},
|
||||
|
|
@ -48,7 +49,8 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
|||
'CgJWMBIQCgNzZXEYASABKARSA3NlcRI4CghyZXNwb25zZRgCIAEoCzIaLnNlcnZlcl90b19jbG'
|
||||
'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90'
|
||||
'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEi4KEVJlcXVlc3ROZXdQcmVLZXlzGA'
|
||||
'QgASgISABSEVJlcXVlc3ROZXdQcmVLZXlzQgYKBEtpbmQ=');
|
||||
'QgASgISABSEVJlcXVlc3ROZXdQcmVLZXlzEkQKDGRvd25sb2FkZGF0YRgFIAEoCzIeLnNlcnZl'
|
||||
'cl90b19jbGllbnQuRG93bmxvYWREYXRhSABSDGRvd25sb2FkZGF0YUIGCgRLaW5k');
|
||||
|
||||
@$core.Deprecated('Use newMessageDescriptor instead')
|
||||
const NewMessage$json = {
|
||||
|
|
@ -64,6 +66,21 @@ final $typed_data.Uint8List newMessageDescriptor = $convert.base64Decode(
|
|||
'CgpOZXdNZXNzYWdlEiAKDGZyb21fdXNlcl9pZBgCIAEoA1IKZnJvbVVzZXJJZBISCgRib2R5GA'
|
||||
'EgASgMUgRib2R5');
|
||||
|
||||
@$core.Deprecated('Use downloadDataDescriptor instead')
|
||||
const DownloadData$json = {
|
||||
'1': 'DownloadData',
|
||||
'2': [
|
||||
{'1': 'upload_token', '3': 1, '4': 1, '5': 12, '10': 'uploadToken'},
|
||||
{'1': 'offset', '3': 2, '4': 1, '5': 13, '10': 'offset'},
|
||||
{'1': 'data', '3': 3, '4': 1, '5': 12, '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DownloadData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List downloadDataDescriptor = $convert.base64Decode(
|
||||
'CgxEb3dubG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhIWCgZvZm'
|
||||
'ZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRh');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
'1': 'Response',
|
||||
|
|
|
|||
107
lib/src/providers/api/api.dart
Normal file
107
lib/src/providers/api/api.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import 'dart:io';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
|
||||
// this functions ensures that the message is received by the server and in case of errors will try again later
|
||||
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
||||
|
||||
if (bytes == null) {
|
||||
Logger("api.dart").shout("Error encryption message!");
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
|
||||
Logger("api.dart").shout(
|
||||
"TODO: store encrypted message and send later again. STORE: userId, bytes and messageId");
|
||||
|
||||
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
||||
|
||||
if (resp.isSuccess) {
|
||||
if (msg.messageId != null) {
|
||||
DbMessages.acknowledgeMessageByServer(msg.messageId!);
|
||||
}
|
||||
// TODO: remove encrypted tmp file
|
||||
} else {
|
||||
// in case of error do nothing. As the message is not removed the app will try again when relaunched
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
||||
int? messageId =
|
||||
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
||||
if (messageId == null) return;
|
||||
|
||||
Result res = await apiProvider.getUploadToken();
|
||||
|
||||
if (res.isError || !res.value.hasUploadtoken()) {
|
||||
Logger("api.dart").shout("Error getting upload token!");
|
||||
|
||||
// TODO store message for later and try again
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
List<int> uploadToken = res.value.uploadtoken;
|
||||
Logger("sendImageToSingleTarget").fine("Got token: $uploadToken");
|
||||
|
||||
MessageContent content =
|
||||
MessageContent(text: null, downloadToken: uploadToken);
|
||||
|
||||
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
|
||||
if (encryptBytes == null) {
|
||||
await DbMessages.deleteMessageById(messageId);
|
||||
Logger("api.dart").shout("Error encrypting image! Deleting image.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<int>? imageToken =
|
||||
await apiProvider.uploadData(uploadToken, encryptBytes);
|
||||
if (imageToken == null) {
|
||||
Logger("api.dart").shout("handle error uploading like saving...");
|
||||
return;
|
||||
}
|
||||
|
||||
print("TODO: insert into DB and then create this MESSAGE");
|
||||
|
||||
Message msg = Message(
|
||||
kind: MessageKind.image,
|
||||
messageId: messageId,
|
||||
content: content,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
await encryptAndSendMessage(target, msg);
|
||||
}
|
||||
|
||||
Future sendImage(List<Int64> userIds, String imagePath) async {
|
||||
// 1. set notifier provider
|
||||
|
||||
File imageFile = File(imagePath);
|
||||
|
||||
Uint8List? imageBytes = await getCompressedImage(imageFile);
|
||||
if (imageBytes == null) {
|
||||
Logger("api.dart").shout("Error compressing image!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < userIds.length; i++) {
|
||||
sendImageToSingleTarget(userIds[i], imageBytes);
|
||||
}
|
||||
}
|
||||
|
||||
Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
||||
print("check if free network connection");
|
||||
|
||||
print("Downloading: " + imageToken.toString());
|
||||
}
|
||||
42
lib/src/providers/api/api_utils.dart
Normal file
42
lib/src/providers/api/api_utils.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
||||
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
||||
|
||||
class Result<T, E> {
|
||||
final T? value;
|
||||
final E? error;
|
||||
|
||||
bool get isSuccess => value != null;
|
||||
bool get isError => error != null;
|
||||
|
||||
Result.success(this.value) : error = null;
|
||||
Result.error(this.error) : value = null;
|
||||
}
|
||||
|
||||
Result asResult(server.ServerToClient? msg) {
|
||||
if (msg == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
if (msg.v0.response.hasOk()) {
|
||||
return Result.success(msg.v0.response.ok);
|
||||
} else {
|
||||
return Result.error(msg.v0.response.error);
|
||||
}
|
||||
}
|
||||
|
||||
ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
||||
var v0 = client.V0()
|
||||
..seq = Int64(0)
|
||||
..handshake = handshake;
|
||||
return ClientToServer()..v0 = v0;
|
||||
}
|
||||
|
||||
ClientToServer createClientToServerFromApplicationData(
|
||||
ApplicationData applicationData) {
|
||||
var v0 = client.V0()
|
||||
..seq = Int64(0)
|
||||
..applicationdata = applicationData;
|
||||
return ClientToServer()..v0 = v0;
|
||||
}
|
||||
99
lib/src/providers/api/server_messages.dart
Normal file
99
lib/src/providers/api/server_messages.dart
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import 'dart:convert';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
||||
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
|
||||
Future handleServerMessage(server.ServerToClient msg) async {
|
||||
client.Response? response;
|
||||
|
||||
if (msg.v0.hasRequestNewPreKeys()) {
|
||||
List<PreKeyRecord> localPreKeys = await SignalHelper.getPreKeys();
|
||||
|
||||
List<client.Response_PreKey> prekeysList = [];
|
||||
for (int i = 0; i < localPreKeys.length; i++) {
|
||||
prekeysList.add(client.Response_PreKey()
|
||||
..id = Int64(localPreKeys[i].id)
|
||||
..prekey = localPreKeys[i].getKeyPair().publicKey.serialize());
|
||||
}
|
||||
var prekeys = client.Response_Prekeys(prekeys: prekeysList);
|
||||
var ok = client.Response_Ok()..prekeys = prekeys;
|
||||
response = client.Response()..ok = ok;
|
||||
} else if (msg.v0.hasNewMessage()) {
|
||||
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
|
||||
Int64 fromUserId = msg.v0.newMessage.fromUserId;
|
||||
Message? message = await SignalHelper.getDecryptedText(fromUserId, body);
|
||||
if (message != null) {
|
||||
switch (message.kind) {
|
||||
case MessageKind.contactRequest:
|
||||
Result username = await apiProvider.getUsername(fromUserId);
|
||||
if (username.isSuccess) {
|
||||
Uint8List name = username.value.userdata.username;
|
||||
DbContacts.insertNewContact(
|
||||
utf8.decode(name), fromUserId.toInt(), true);
|
||||
}
|
||||
break;
|
||||
case MessageKind.rejectRequest:
|
||||
DbContacts.deleteUser(fromUserId.toInt());
|
||||
break;
|
||||
case MessageKind.acceptRequest:
|
||||
DbContacts.acceptUser(fromUserId.toInt());
|
||||
break;
|
||||
case MessageKind.ack:
|
||||
DbMessages.acknowledgeMessageByUser(
|
||||
fromUserId.toInt(), message.messageId!);
|
||||
break;
|
||||
default:
|
||||
if (message.kind != MessageKind.textMessage &&
|
||||
message.kind != MessageKind.video &&
|
||||
message.kind != MessageKind.image) {
|
||||
Logger("handleServerMessages")
|
||||
.shout("Got unknown MessageKind $message");
|
||||
} else {
|
||||
String content = jsonEncode(message.content!.toJson());
|
||||
await DbMessages.insertOtherMessage(
|
||||
fromUserId.toInt(), message.kind, message.messageId!, content);
|
||||
|
||||
encryptAndSendMessage(
|
||||
fromUserId,
|
||||
Message(
|
||||
kind: MessageKind.ack,
|
||||
messageId: message.messageId!,
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
if (message.kind == MessageKind.video ||
|
||||
message.kind == MessageKind.image) {
|
||||
dynamic content = message.content!;
|
||||
List<int> downloadToken = content.downloadToken;
|
||||
tryDownloadMedia(downloadToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var ok = client.Response_Ok()..none = true;
|
||||
response = client.Response()..ok = ok;
|
||||
} else {
|
||||
Logger("handleServerMessage")
|
||||
.shout("Got a new message from the server: $msg");
|
||||
return;
|
||||
}
|
||||
|
||||
var v0 = client.V0()
|
||||
..seq = msg.v0.seq
|
||||
..response = response;
|
||||
|
||||
apiProvider.sendResponse(ClientToServer()..v0 = v0);
|
||||
}
|
||||
|
|
@ -1,18 +1,15 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
||||
import 'package:twonly/src/app.dart';
|
||||
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
||||
import 'package:twonly/src/utils/api.dart';
|
||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||
import 'package:twonly/src/providers/api/server_messages.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
// ignore: library_prefixes
|
||||
|
|
@ -22,30 +19,21 @@ import 'package:web_socket_channel/io.dart';
|
|||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
class Result<T, E> {
|
||||
final T? value;
|
||||
final E? error;
|
||||
|
||||
bool get isSuccess => value != null;
|
||||
bool get isError => error != null;
|
||||
|
||||
Result.success(this.value) : error = null;
|
||||
Result.error(this.error) : value = null;
|
||||
}
|
||||
|
||||
/// The ApiProvider is responsible for communicating with the server.
|
||||
/// It handles errors and does automatically tries to reconnect on
|
||||
/// errors or network changes.
|
||||
class ApiProvider {
|
||||
ApiProvider({required this.apiUrl, required this.backupApiUrl});
|
||||
|
||||
final String apiUrl;
|
||||
final String? backupApiUrl;
|
||||
ApiProvider({required this.apiUrl, required this.backupApiUrl});
|
||||
|
||||
final log = Logger("ApiProvider");
|
||||
|
||||
// reconnection params
|
||||
Timer? reconnectionTimer;
|
||||
int _reconnectionDelay = 5;
|
||||
bool _tryingToConnect = false;
|
||||
final log = Logger("api_provider");
|
||||
Function(bool)? _connectionStateCallback;
|
||||
Function? _updatedContacts;
|
||||
|
||||
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
||||
|
||||
IOWebSocketChannel? _channel;
|
||||
|
||||
Future<bool> _connectTo(String apiUrl) async {
|
||||
|
|
@ -68,19 +56,23 @@ class ApiProvider {
|
|||
if (_channel != null && _channel!.closeCode != null) {
|
||||
return true;
|
||||
}
|
||||
// ensure that the connect function is not called again by the timer.
|
||||
if (reconnectionTimer != null) {
|
||||
reconnectionTimer!.cancel();
|
||||
}
|
||||
|
||||
log.info("Trying to connect to the backend $apiUrl!");
|
||||
if (await _connectTo(apiUrl)) {
|
||||
await authenticate();
|
||||
if (_connectionStateCallback != null) _connectionStateCallback!(true);
|
||||
globalCallbackConnectionState(true);
|
||||
_reconnectionDelay = 5;
|
||||
return true;
|
||||
}
|
||||
if (backupApiUrl != null) {
|
||||
log.info("Trying to connect to the backup backend $backupApiUrl!");
|
||||
if (await _connectTo(backupApiUrl!)) {
|
||||
globalCallbackConnectionState(true);
|
||||
await authenticate();
|
||||
if (_connectionStateCallback != null) _connectionStateCallback!(true);
|
||||
_reconnectionDelay = 5;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -91,42 +83,35 @@ class ApiProvider {
|
|||
bool get isConnected => _channel != null && _channel!.closeCode != null;
|
||||
|
||||
void _onDone() {
|
||||
if (_connectionStateCallback != null) {
|
||||
_connectionStateCallback!(false);
|
||||
}
|
||||
globalCallbackConnectionState(false);
|
||||
_channel = null;
|
||||
tryToReconnect();
|
||||
}
|
||||
|
||||
void _onError(dynamic e) {
|
||||
if (_connectionStateCallback != null) {
|
||||
_connectionStateCallback!(false);
|
||||
}
|
||||
globalCallbackConnectionState(false);
|
||||
_channel = null;
|
||||
tryToReconnect();
|
||||
}
|
||||
|
||||
void setConnectionStateCallback(Function(bool) callBack) {
|
||||
_connectionStateCallback = callBack;
|
||||
}
|
||||
|
||||
void setUpdatedContacts(Function callBack) {
|
||||
_updatedContacts = callBack;
|
||||
}
|
||||
|
||||
void tryToReconnect() {
|
||||
if (_tryingToConnect) return;
|
||||
_tryingToConnect = true;
|
||||
Future.delayed(Duration(seconds: _reconnectionDelay)).then(
|
||||
(value) async {
|
||||
_tryingToConnect = false;
|
||||
_reconnectionDelay = _reconnectionDelay + 2;
|
||||
if (_reconnectionDelay > 20) {
|
||||
_reconnectionDelay = 20;
|
||||
if (reconnectionTimer != null) {
|
||||
reconnectionTimer!.cancel();
|
||||
}
|
||||
|
||||
final int randomDelay = Random().nextInt(20);
|
||||
final int delay = _reconnectionDelay + randomDelay;
|
||||
|
||||
debugPrint("Delay reconnection $delay");
|
||||
|
||||
reconnectionTimer = Timer(Duration(seconds: delay), () async {
|
||||
// increase delay but set a maximum of 60 seconds (including the random delay)
|
||||
_reconnectionDelay = _reconnectionDelay * 2;
|
||||
if (_reconnectionDelay > 40) {
|
||||
_reconnectionDelay = 40;
|
||||
}
|
||||
await connect();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _onData(dynamic msgBuffer) {
|
||||
|
|
@ -135,103 +120,13 @@ class ApiProvider {
|
|||
if (msg.v0.hasResponse()) {
|
||||
messagesV0[msg.v0.seq] = msg;
|
||||
} else {
|
||||
_handleServerMessage(msg);
|
||||
handleServerMessage(msg);
|
||||
}
|
||||
} catch (e) {
|
||||
log.shout("Error parsing the servers message: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future _handleServerMessage(server.ServerToClient msg) async {
|
||||
client.Response? response;
|
||||
|
||||
if (msg.v0.hasRequestNewPreKeys()) {
|
||||
List<PreKeyRecord> localPreKeys = await SignalHelper.getPreKeys();
|
||||
|
||||
List<client.Response_PreKey> prekeysList = [];
|
||||
for (int i = 0; i < localPreKeys.length; i++) {
|
||||
prekeysList.add(client.Response_PreKey()
|
||||
..id = Int64(localPreKeys[i].id)
|
||||
..prekey = localPreKeys[i].getKeyPair().publicKey.serialize());
|
||||
}
|
||||
var prekeys = client.Response_Prekeys(prekeys: prekeysList);
|
||||
var ok = client.Response_Ok()..prekeys = prekeys;
|
||||
response = client.Response()..ok = ok;
|
||||
} else if (msg.v0.hasNewMessage()) {
|
||||
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
|
||||
Int64 fromUserId = msg.v0.newMessage.fromUserId;
|
||||
Message? message = await SignalHelper.getDecryptedText(fromUserId, body);
|
||||
if (message != null) {
|
||||
switch (message.kind) {
|
||||
case MessageKind.contactRequest:
|
||||
Result username = await getUsername(fromUserId);
|
||||
if (username.isSuccess) {
|
||||
Uint8List name = username.value.userdata.username;
|
||||
DbContacts.insertNewContact(
|
||||
utf8.decode(name), fromUserId.toInt(), true);
|
||||
}
|
||||
break;
|
||||
case MessageKind.rejectRequest:
|
||||
DbContacts.deleteUser(fromUserId.toInt());
|
||||
break;
|
||||
case MessageKind.acceptRequest:
|
||||
DbContacts.acceptUser(fromUserId.toInt());
|
||||
break;
|
||||
case MessageKind.ack:
|
||||
DbMessages.acknowledgeMessage(
|
||||
fromUserId.toInt(), message.messageId!);
|
||||
break;
|
||||
default:
|
||||
if (message.kind != MessageKind.textMessage &&
|
||||
message.kind != MessageKind.video &&
|
||||
message.kind != MessageKind.image) {
|
||||
log.shout("Got unknown MessageKind $message");
|
||||
} else {
|
||||
String content = jsonEncode(message.content!.toJson());
|
||||
await DbMessages.insertOtherMessage(fromUserId.toInt(),
|
||||
message.kind, message.messageId!, content);
|
||||
|
||||
encryptAndSendMessage(
|
||||
fromUserId,
|
||||
Message(
|
||||
kind: MessageKind.ack,
|
||||
messageId: message.messageId!,
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
if (message.kind == MessageKind.video ||
|
||||
message.kind == MessageKind.image) {
|
||||
dynamic content = message.content!;
|
||||
List<int> downloadToken = content.downloadToken;
|
||||
tryDownloadMedia(downloadToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateNotifier();
|
||||
var ok = client.Response_Ok()..none = true;
|
||||
response = client.Response()..ok = ok;
|
||||
} else {
|
||||
log.shout("Got a new message from the server: $msg");
|
||||
return;
|
||||
}
|
||||
|
||||
var v0 = client.V0()
|
||||
..seq = msg.v0.seq
|
||||
..response = response;
|
||||
var res = ClientToServer()..v0 = v0;
|
||||
|
||||
final resBytes = res.writeToBuffer();
|
||||
_channel!.sink.add(resBytes);
|
||||
}
|
||||
|
||||
Future updateNotifier() async {
|
||||
if (_updatedContacts != null) {
|
||||
_updatedContacts!();
|
||||
}
|
||||
}
|
||||
|
||||
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
||||
final startTime = DateTime.now();
|
||||
|
||||
|
|
@ -251,9 +146,13 @@ class ApiProvider {
|
|||
}
|
||||
}
|
||||
|
||||
Future<server.ServerToClient?> _sendRequestV0(ClientToServer request) async {
|
||||
Future sendResponse(ClientToServer response) async {
|
||||
_channel!.sink.add(response.writeToBuffer());
|
||||
}
|
||||
|
||||
Future<Result> _sendRequestV0(ClientToServer request) async {
|
||||
if (_channel == null) {
|
||||
return null;
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
var seq = Int64(Random().nextInt(4294967296));
|
||||
while (messagesV0.containsKey(seq)) {
|
||||
|
|
@ -265,30 +164,7 @@ class ApiProvider {
|
|||
|
||||
_channel!.sink.add(requestBytes);
|
||||
|
||||
return await _waitForResponse(seq);
|
||||
}
|
||||
|
||||
ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
||||
var v0 = client.V0()
|
||||
..seq = Int64(0)
|
||||
..handshake = handshake;
|
||||
return ClientToServer()..v0 = v0;
|
||||
}
|
||||
|
||||
ClientToServer createClientToServerFromApplicationData(
|
||||
ApplicationData applicationData) {
|
||||
var v0 = client.V0()
|
||||
..seq = Int64(0)
|
||||
..applicationdata = applicationData;
|
||||
return ClientToServer()..v0 = v0;
|
||||
}
|
||||
|
||||
Result _asResult(server.ServerToClient msg) {
|
||||
if (msg.v0.response.hasOk()) {
|
||||
return Result.success(msg.v0.response.ok);
|
||||
} else {
|
||||
return Result.error(msg.v0.response.error);
|
||||
}
|
||||
return asResult(await _waitForResponse(seq));
|
||||
}
|
||||
|
||||
Future authenticate() async {
|
||||
|
|
@ -299,12 +175,7 @@ class ApiProvider {
|
|||
var handshake = Handshake()..getchallenge = Handshake_GetChallenge();
|
||||
var req = createClientToServerFromHandshake(handshake);
|
||||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
log.shout("Server is not reachable!");
|
||||
return;
|
||||
}
|
||||
final result = _asResult(resp);
|
||||
final result = await _sendRequestV0(req);
|
||||
if (result.isError) {
|
||||
log.shout(result);
|
||||
return;
|
||||
|
|
@ -328,12 +199,7 @@ class ApiProvider {
|
|||
|
||||
var req2 = createClientToServerFromHandshake(opensession);
|
||||
|
||||
final resp2 = await _sendRequestV0(req2);
|
||||
if (resp2 == null) {
|
||||
log.shout("Server is not reachable!");
|
||||
return;
|
||||
}
|
||||
final result2 = _asResult(resp2);
|
||||
final result2 = await _sendRequestV0(req2);
|
||||
if (result2.isError) {
|
||||
log.shout(result2);
|
||||
return;
|
||||
|
|
@ -370,33 +236,21 @@ class ApiProvider {
|
|||
var handshake = Handshake()..register = register;
|
||||
var req = createClientToServerFromHandshake(handshake);
|
||||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
return await _sendRequestV0(req);
|
||||
}
|
||||
|
||||
Future<Result> getUsername(Int64 userId) async {
|
||||
var get = ApplicationData_GetUserById()..userId = userId;
|
||||
var appData = ApplicationData()..getuserbyid = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
return await _sendRequestV0(req);
|
||||
}
|
||||
|
||||
Future<Result> getUploadToken(int size) async {
|
||||
var get = ApplicationData_GetUploadToken()..len = size;
|
||||
Future<Result> getUploadToken() async {
|
||||
var get = ApplicationData_GetUploadToken();
|
||||
var appData = ApplicationData()..getuploadtoken = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
return await _sendRequestV0(req);
|
||||
}
|
||||
|
||||
Future<List<int>?> uploadData(List<int> uploadToken, Uint8List data) async {
|
||||
|
|
@ -409,23 +263,15 @@ class ApiProvider {
|
|||
|
||||
var appData = ApplicationData()..uploaddata = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return null;
|
||||
}
|
||||
return _asResult(resp).isSuccess ? uploadToken : null;
|
||||
final result = await _sendRequestV0(req);
|
||||
return result.isSuccess ? uploadToken : null;
|
||||
}
|
||||
|
||||
Future<Result> getUserData(String username) async {
|
||||
var get = ApplicationData_GetUserByUsername()..username = username;
|
||||
var appData = ApplicationData()..getuserbyusername = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
return await _sendRequestV0(req);
|
||||
}
|
||||
|
||||
Future<Result> sendTextMessage(Int64 target, Uint8List msg) async {
|
||||
|
|
@ -436,10 +282,6 @@ class ApiProvider {
|
|||
var appData = ApplicationData()..textmessage = testMessage;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
return await _sendRequestV0(req);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,26 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
|
||||
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
|
||||
// ignore: prefer_mixin
|
||||
class NotifyProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
// This provider will update the UI on changes in the contact list
|
||||
class ContactChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
// The page index of the HomeView widget
|
||||
int _activePageIdx = 0;
|
||||
// int _activePageIdx = 0;
|
||||
|
||||
List<Contact> _allContacts = [];
|
||||
final Map<int, DbMessage> _lastMessagesGroupedByUser = <int, DbMessage>{};
|
||||
|
||||
final List<Int64> _sendingCurrentlyTo = [];
|
||||
|
||||
int get newContactRequests => _allContacts
|
||||
.where((contact) => !contact.accepted && contact.requested)
|
||||
.length;
|
||||
List<Contact> get allContacts => _allContacts;
|
||||
Map<int, DbMessage> get lastMessagesGroupedByUser =>
|
||||
_lastMessagesGroupedByUser;
|
||||
HashSet<Int64> get sendingCurrentlyTo =>
|
||||
HashSet<Int64>.from(_sendingCurrentlyTo);
|
||||
|
||||
int get activePageIdx => _activePageIdx;
|
||||
// int get activePageIdx => _activePageIdx;
|
||||
|
||||
void setActivePageIdx(int idx) {
|
||||
_activePageIdx = idx;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addSendingTo(Int64 user) {
|
||||
_sendingCurrentlyTo.add(user);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// removes the first occurrence of the user
|
||||
void removeSendingTo(Int64 user) {
|
||||
int index = _sendingCurrentlyTo.indexOf(user);
|
||||
if (index != -1) {
|
||||
_sendingCurrentlyTo.removeAt(index);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
// void setActivePageIdx(int idx) {
|
||||
// _activePageIdx = idx;
|
||||
// notifyListeners();
|
||||
// }
|
||||
|
||||
void update() async {
|
||||
_allContacts = await DbContacts.getUsers();
|
||||
31
lib/src/providers/messages_change_provider.dart
Normal file
31
lib/src/providers/messages_change_provider.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
|
||||
/// This provider does always contains the latest messages send or received
|
||||
/// for every contact.
|
||||
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
||||
|
||||
Map<int, DbMessage> get lastMessage => _lastMessage;
|
||||
|
||||
void updateLastMessageFor(int targetUserId) async {
|
||||
DbMessage? last =
|
||||
await DbMessages.getLastMessagesForPreviewForUser(targetUserId);
|
||||
if (last != null) {
|
||||
_lastMessage[last.otherUserId] = last;
|
||||
}
|
||||
}
|
||||
|
||||
void init() async {
|
||||
// load everything from the database
|
||||
List<Contact> allContacts = await DbContacts.getUsers();
|
||||
for (Contact contact in allContacts) {
|
||||
DbMessage? last = await DbMessages.getLastMessagesForPreviewForUser(
|
||||
contact.userId.toInt());
|
||||
if (last != null) {
|
||||
_lastMessage[last.otherUserId] = last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/app.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/providers/api_provider.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
import 'package:twonly/src/model/json/user_data.dart';
|
||||
|
||||
Future<bool> addNewContact(String username) async {
|
||||
final res = await apiProvider.getUserData(username);
|
||||
|
||||
if (res.isSuccess) {
|
||||
bool added = await DbContacts.insertNewContact(
|
||||
username, res.value.userdata.userId.toInt(), false);
|
||||
|
||||
if (!added) {
|
||||
print("RETURN FALSE HIER!!!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await SignalHelper.addNewContact(res.value.userdata)) {
|
||||
Message msg =
|
||||
Message(kind: MessageKind.contactRequest, timestamp: DateTime.now());
|
||||
|
||||
Uint8List? bytes =
|
||||
await SignalHelper.encryptMessage(msg, res.value.userdata.userId);
|
||||
|
||||
if (bytes == null) {
|
||||
Logger("utils/api").shout("Error encryption message!");
|
||||
return res.error(ErrorCode.InternalError);
|
||||
}
|
||||
|
||||
Result resp =
|
||||
await apiProvider.sendTextMessage(res.value.userdata.userId, bytes);
|
||||
|
||||
return resp.isSuccess;
|
||||
}
|
||||
}
|
||||
return res.isSuccess;
|
||||
}
|
||||
|
||||
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
||||
|
||||
if (bytes == null) {
|
||||
Logger("utils/api").shout("Error encryption message!");
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
|
||||
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
||||
|
||||
if (resp.isError) {
|
||||
// TODO:
|
||||
Logger("encryptAndSendMessage")
|
||||
.shout("handle errors here and store them in the database");
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future<Result> rejectUserRequest(Int64 userId) async {
|
||||
Message msg =
|
||||
Message(kind: MessageKind.rejectRequest, timestamp: DateTime.now());
|
||||
return encryptAndSendMessage(userId, msg);
|
||||
}
|
||||
|
||||
Future<Result> acceptUserRequest(Int64 userId) async {
|
||||
Message msg =
|
||||
Message(kind: MessageKind.acceptRequest, timestamp: DateTime.now());
|
||||
return encryptAndSendMessage(userId, msg);
|
||||
}
|
||||
|
||||
Future<Result> createNewUser(String username, String inviteCode) async {
|
||||
final storage = getSecureStorage();
|
||||
|
||||
await SignalHelper.createIfNotExistsSignalIdentity();
|
||||
|
||||
final res = await apiProvider.register(username, inviteCode);
|
||||
|
||||
if (res.isSuccess) {
|
||||
Logger("create_new_user").info("Got user_id ${res.value} from server");
|
||||
final userData = UserData(
|
||||
userId: res.value.userid, username: username, displayName: username);
|
||||
storage.write(key: "user_data", value: jsonEncode(userData));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Future sendImageToSingleTarget(
|
||||
BuildContext context, Int64 target, Uint8List encryptBytes) async {
|
||||
Result res = await apiProvider.getUploadToken(encryptBytes.length);
|
||||
|
||||
if (res.isError || !res.value.hasUploadtoken()) {
|
||||
Logger("api.dart").shout("Error getting upload token!");
|
||||
return null;
|
||||
}
|
||||
List<int> uploadToken = res.value.uploadtoken;
|
||||
Logger("sendImageToSingleTarget").info("Got token: $uploadToken");
|
||||
|
||||
MessageContent content =
|
||||
MessageContent(text: null, downloadToken: uploadToken);
|
||||
int? messageId = await DbMessages.insertMyMessage(
|
||||
target.toInt(), MessageKind.image, jsonEncode(content.toJson()));
|
||||
if (messageId == null) return;
|
||||
|
||||
updateNotifyProvider();
|
||||
|
||||
List<int>? imageToken =
|
||||
await apiProvider.uploadData(uploadToken, encryptBytes);
|
||||
if (imageToken == null) {
|
||||
Logger("api.dart").shout("handle error uploading like saving...");
|
||||
return;
|
||||
}
|
||||
|
||||
print("TODO: insert into DB and then create this MESSAGE");
|
||||
|
||||
Message msg = Message(
|
||||
kind: MessageKind.image,
|
||||
messageId: messageId,
|
||||
content: content,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
await encryptAndSendMessage(target, msg);
|
||||
removeSendingTo(target);
|
||||
}
|
||||
|
||||
Future sendImage(
|
||||
BuildContext context, List<Contact> users, String imagePath) async {
|
||||
// 1. set notifier provider
|
||||
|
||||
File imageFile = File(imagePath);
|
||||
|
||||
Uint8List? imageBytes = await getCompressedImage(imageFile);
|
||||
if (imageBytes == null) {
|
||||
Logger("api.dart").shout("Error compressing image!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
Int64 target = users[i].userId;
|
||||
Uint8List? encryptedImage =
|
||||
await SignalHelper.encryptBytes(imageBytes, target);
|
||||
if (encryptedImage == null) {
|
||||
Logger("api.dart").shout("Error encrypting image!");
|
||||
continue;
|
||||
}
|
||||
sendImageToSingleTarget(context, target, encryptedImage);
|
||||
}
|
||||
}
|
||||
|
||||
Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
||||
print("check if free network connection");
|
||||
|
||||
print("Downloading: " + imageToken.toString());
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:gal/gal.dart';
|
||||
import 'package:image/image.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
|
@ -127,7 +125,5 @@ Future<Uint8List?> getCompressedImage(File file) async {
|
|||
file.absolute.path,
|
||||
quality: 90,
|
||||
);
|
||||
print(file.lengthSync());
|
||||
print(result!.length);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||
|
|
@ -8,13 +5,14 @@ import 'package:twonly/src/components/notification_badge.dart';
|
|||
import 'package:twonly/src/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/messages_model.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/chat_item_details_view.dart';
|
||||
import 'package:twonly/src/views/home_view.dart';
|
||||
import 'package:twonly/src/views/search_username_view.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class ChatItem {
|
||||
const ChatItem(
|
||||
|
|
@ -42,22 +40,20 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Map<int, DbMessage> lastMessages =
|
||||
context.watch<NotifyProvider>().lastMessagesGroupedByUser;
|
||||
context.watch<MessagesChangeProvider>().lastMessage;
|
||||
|
||||
HashSet<Int64> sendingCurrentlyTo =
|
||||
context.watch<NotifyProvider>().sendingCurrentlyTo;
|
||||
List<Contact> allUsers = context.read<ContactChangeProvider>().allContacts;
|
||||
|
||||
List<Contact> activeUsers = context
|
||||
.read<NotifyProvider>()
|
||||
.allContacts
|
||||
List<Contact> activeUsers = allUsers
|
||||
.where((x) => lastMessages.containsKey(x.userId.toInt()))
|
||||
.toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.chatsTitle),
|
||||
actions: [
|
||||
NotificationBadge(
|
||||
count: context.watch<NotifyProvider>().newContactRequests,
|
||||
count: context.watch<ContactChangeProvider>().newContactRequests,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.person_add),
|
||||
onPressed: () {
|
||||
|
|
@ -72,37 +68,32 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
)
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// if (sendingCurrentlyTo.isNotEmpty)
|
||||
// Container(
|
||||
// padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
// child: ListTile(
|
||||
// leading: Stack(
|
||||
// // child: Stack(
|
||||
// alignment: Alignment.center,
|
||||
// children: [
|
||||
// CircularProgressIndicator(
|
||||
// strokeWidth: 1,
|
||||
// ),
|
||||
// Icon(
|
||||
// Icons.send, // Replace with your desired icon
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// size: 20, // Adjust the size as needed
|
||||
// ),
|
||||
// ],
|
||||
// // ),
|
||||
// ),
|
||||
// title: Text(sendingCurrentlyTo
|
||||
// .map((e) => _activeUsers
|
||||
// .where((c) => c.userId == e)
|
||||
// .map((c) => c.displayName))
|
||||
// .toList()
|
||||
// .join(", ")),
|
||||
// ),
|
||||
// ), //
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
body: (activeUsers.isEmpty)
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: OutlinedButton.icon(
|
||||
icon: Icon((allUsers.isEmpty)
|
||||
? Icons.person_add
|
||||
: Icons.camera_alt),
|
||||
onPressed: () {
|
||||
(allUsers.isEmpty)
|
||||
? Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView(),
|
||||
),
|
||||
)
|
||||
: globalUpdateOfHomeViewPageIndex(1);
|
||||
},
|
||||
label: Text((allUsers.isEmpty)
|
||||
? AppLocalizations.of(context)!
|
||||
.chatListViewSearchUserNameBtn
|
||||
: AppLocalizations.of(context)!
|
||||
.chatListViewSendFirstTwonly)),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
restorationId: 'chat_list_view',
|
||||
itemCount: activeUsers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
|
|
@ -110,25 +101,20 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
return UserListItem(
|
||||
user: user,
|
||||
lastMessage: lastMessages[user.userId.toInt()]!,
|
||||
isSending: sendingCurrentlyTo.contains(user.userId),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserListItem extends StatefulWidget {
|
||||
final Contact user;
|
||||
final DbMessage lastMessage;
|
||||
final bool isSending;
|
||||
|
||||
const UserListItem({
|
||||
super.key,
|
||||
required this.user,
|
||||
required this.isSending,
|
||||
required this.lastMessage,
|
||||
});
|
||||
|
||||
|
|
@ -146,11 +132,11 @@ class _UserListItem extends State<UserListItem> {
|
|||
//_loadAsync();
|
||||
}
|
||||
|
||||
Future _loadAsync() async {
|
||||
// Future _loadAsync() async {
|
||||
// flames = await widget.user.getFlames();
|
||||
// lastMessageInSeconds = await widget.user.getLastMessageInSeconds();
|
||||
setState(() {});
|
||||
}
|
||||
// setState(() {});
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -161,7 +147,7 @@ class _UserListItem extends State<UserListItem> {
|
|||
.difference(widget.lastMessage.sendOrReceivedAt)
|
||||
.inSeconds;
|
||||
|
||||
if (widget.isSending) {
|
||||
if (!widget.lastMessage.messageAcknowledgeByServer) {
|
||||
state = MessageSendState.sending;
|
||||
} else {
|
||||
if (widget.lastMessage.messageOtherId == null) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
|
||||
import 'camera_preview_view.dart';
|
||||
import 'chat_list_view.dart';
|
||||
import 'profile_view.dart';
|
||||
import '../settings/settings_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final PageController homeViewPageController = PageController(initialPage: 0);
|
||||
Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
const HomeView({super.key, required this.settingsController});
|
||||
|
|
@ -19,6 +16,27 @@ class HomeView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class HomeViewState extends State<HomeView> {
|
||||
int activePageIdx = 0;
|
||||
final PageController homeViewPageController = PageController(initialPage: 0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
globalUpdateOfHomeViewPageIndex = (index) {
|
||||
homeViewPageController.jumpToPage(index);
|
||||
setState(() {
|
||||
activePageIdx = index;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// disable globalCallbacks to the flutter tree
|
||||
globalUpdateOfHomeViewPageIndex = (a) {};
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PieCanvas(
|
||||
|
|
@ -47,7 +65,7 @@ class HomeViewState extends State<HomeView> {
|
|||
body: PageView(
|
||||
controller: homeViewPageController,
|
||||
onPageChanged: (index) {
|
||||
context.read<NotifyProvider>().setActivePageIdx(index);
|
||||
activePageIdx = index;
|
||||
},
|
||||
children: [
|
||||
ChatListView(),
|
||||
|
|
@ -69,14 +87,16 @@ class HomeViewState extends State<HomeView> {
|
|||
BottomNavigationBarItem(icon: Icon(Icons.verified_user), label: ""),
|
||||
],
|
||||
onTap: (int index) {
|
||||
context.read<NotifyProvider>().setActivePageIdx(index);
|
||||
activePageIdx = index;
|
||||
setState(() {
|
||||
homeViewPageController.animateToPage(index,
|
||||
homeViewPageController.animateToPage(
|
||||
index,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.bounceIn);
|
||||
curve: Curves.bounceIn,
|
||||
);
|
||||
});
|
||||
},
|
||||
currentIndex: context.watch<NotifyProvider>().activePageIdx,
|
||||
currentIndex: activePageIdx,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import 'dart:convert';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:twonly/src/utils/api.dart';
|
||||
import 'package:twonly/src/model/json/user_data.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/signal.dart';
|
||||
|
||||
class RegisterView extends StatefulWidget {
|
||||
const RegisterView({super.key, required this.callbackOnSuccess});
|
||||
|
|
@ -19,6 +22,41 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
|
||||
bool _isTryingToRegister = false;
|
||||
|
||||
Future createNewUser() async {
|
||||
String username = usernameController.text;
|
||||
String inviteCode = inviteCodeController.text;
|
||||
setState(() {
|
||||
_isTryingToRegister = true;
|
||||
});
|
||||
|
||||
final storage = getSecureStorage();
|
||||
|
||||
await createIfNotExistsSignalIdentity();
|
||||
|
||||
final res = await apiProvider.register(username, inviteCode);
|
||||
|
||||
if (res.isSuccess) {
|
||||
Logger("create_new_user").info("Got user_id ${res.value} from server");
|
||||
final userData = UserData(
|
||||
userId: res.value.userid, username: username, displayName: username);
|
||||
storage.write(key: "user_data", value: jsonEncode(userData));
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isTryingToRegister = false;
|
||||
});
|
||||
|
||||
if (res.isSuccess) {
|
||||
apiProvider.authenticate();
|
||||
widget.callbackOnSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
showAlertDialog(context, "Oh no!", errorCodeToText(context, res.error));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
InputDecoration getInputDecoration(hintText) {
|
||||
|
|
@ -117,23 +155,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
)
|
||||
: Icon(Icons.group),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_isTryingToRegister = true;
|
||||
});
|
||||
final res = await createNewUser(
|
||||
usernameController.text, inviteCodeController.text);
|
||||
setState(() {
|
||||
_isTryingToRegister = false;
|
||||
apiProvider.authenticate();
|
||||
});
|
||||
if (res.isSuccess) {
|
||||
widget.callbackOnSuccess();
|
||||
return;
|
||||
}
|
||||
if (context.mounted) {
|
||||
final errMsg = errorCodeToText(context, res.error);
|
||||
showAlertDialog(context, "Oh no!", errMsg);
|
||||
}
|
||||
createNewUser();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/components/headline.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/utils/api.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/views/register_view.dart';
|
||||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
|
||||
class SearchUsernameView extends StatefulWidget {
|
||||
const SearchUsernameView({super.key});
|
||||
|
|
@ -25,24 +29,30 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
_isLoading = true;
|
||||
});
|
||||
|
||||
final status = await addNewContact(searchUserName.text);
|
||||
final res = await apiProvider.getUserData(searchUserName.text);
|
||||
|
||||
if (res.isSuccess) {
|
||||
bool added = await DbContacts.insertNewContact(
|
||||
searchUserName.text,
|
||||
res.value.userdata.userId.toInt(),
|
||||
false,
|
||||
);
|
||||
|
||||
if (added) {
|
||||
if (await SignalHelper.addNewContact(res.value.userdata)) {
|
||||
encryptAndSendMessage(
|
||||
res.value.userdata.userId,
|
||||
Message(
|
||||
kind: MessageKind.contactRequest,
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
if (context.mounted) {
|
||||
if (status) {
|
||||
context.read<NotifyProvider>().update();
|
||||
} else if (context.mounted) {
|
||||
showAlertDialog(
|
||||
context,
|
||||
AppLocalizations.of(context)!.searchUsernameNotFound,
|
||||
AppLocalizations.of(context)!
|
||||
.searchUsernameNotFoundLong(searchUserName.text));
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -94,7 +104,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
),
|
||||
SizedBox(height: 30),
|
||||
if (context
|
||||
.read<NotifyProvider>()
|
||||
.read<ContactChangeProvider>()
|
||||
.allContacts
|
||||
.where((contact) => !contact.accepted)
|
||||
.isNotEmpty)
|
||||
|
|
@ -122,6 +132,8 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
}
|
||||
|
||||
class ContactsListView extends StatefulWidget {
|
||||
const ContactsListView({super.key});
|
||||
|
||||
@override
|
||||
State<ContactsListView> createState() => _ContactsListViewState();
|
||||
}
|
||||
|
|
@ -130,7 +142,7 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Contact> contacts = context
|
||||
.read<NotifyProvider>()
|
||||
.read<ContactChangeProvider>()
|
||||
.allContacts
|
||||
.where((contact) => !contact.accepted)
|
||||
.toList();
|
||||
|
|
@ -143,9 +155,9 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
leading: InitialsAvatar(displayName: contact.displayName),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: (!contact.requested)
|
||||
? [Text('Pending')]
|
||||
: [
|
||||
children: [
|
||||
if (!contact.requested) Text('Pending'),
|
||||
if (contact.requested) ...[
|
||||
Tooltip(
|
||||
message: "Block the user without informing.",
|
||||
child: IconButton(
|
||||
|
|
@ -153,9 +165,6 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
color: const Color.fromARGB(164, 244, 67, 54)),
|
||||
onPressed: () async {
|
||||
await DbContacts.blockUser(contact.userId.toInt());
|
||||
if (context.mounted) {
|
||||
context.read<NotifyProvider>().update();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -165,10 +174,13 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
icon: Icon(Icons.close, color: Colors.red),
|
||||
onPressed: () async {
|
||||
await DbContacts.deleteUser(contact.userId.toInt());
|
||||
if (context.mounted) {
|
||||
context.read<NotifyProvider>().update();
|
||||
}
|
||||
rejectUserRequest(contact.userId);
|
||||
encryptAndSendMessage(
|
||||
contact.userId,
|
||||
Message(
|
||||
kind: MessageKind.rejectRequest,
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -176,13 +188,17 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
icon: Icon(Icons.check, color: Colors.green),
|
||||
onPressed: () async {
|
||||
await DbContacts.acceptUser(contact.userId.toInt());
|
||||
if (context.mounted) {
|
||||
context.read<NotifyProvider>().update();
|
||||
}
|
||||
acceptUserRequest(contact.userId);
|
||||
encryptAndSendMessage(
|
||||
contact.userId,
|
||||
Message(
|
||||
kind: MessageKind.acceptRequest,
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ import 'dart:collection';
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/app.dart';
|
||||
import 'package:twonly/src/components/best_friends_selector.dart';
|
||||
import 'package:twonly/src/components/headline.dart';
|
||||
import 'package:twonly/src/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/providers/notify_provider.dart';
|
||||
import 'package:twonly/src/utils/api.dart';
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/home_view.dart';
|
||||
|
||||
|
|
@ -112,21 +109,12 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
FilledButton.icon(
|
||||
icon: Icon(Icons.send),
|
||||
onPressed: () async {
|
||||
for (Int64 a in _selectedUserIds) {
|
||||
addSendingTo(a);
|
||||
}
|
||||
|
||||
sendImage(
|
||||
context,
|
||||
_users
|
||||
.where((c) => _selectedUserIds.contains(c.userId))
|
||||
.toList(),
|
||||
widget.image);
|
||||
sendImage(_selectedUserIds.toList(), widget.image);
|
||||
|
||||
// TODO: pop back to the HomeView page popUntil did not work. check later how to improve in case of pushing more then 2
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
context.read<NotifyProvider>().setActivePageIdx(0);
|
||||
homeViewPageController.jumpToPage(0);
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
|
|
|
|||
Loading…
Reference in a new issue