clean code

This commit is contained in:
otsmr 2025-01-27 20:30:41 +01:00
parent 2f3af42771
commit 5cea69c224
22 changed files with 941 additions and 717 deletions

View file

@ -4,7 +4,8 @@ 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/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 'package:twonly/src/utils/misc.dart';
import 'src/app.dart'; import 'src/app.dart';
import 'src/settings/settings_controller.dart'; import 'src/settings/settings_controller.dart';
@ -54,7 +55,8 @@ void main() async {
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (_) => NotifyProvider()), ChangeNotifierProvider(create: (_) => MessagesChangeProvider()),
ChangeNotifierProvider(create: (_) => ContactChangeProvider()),
], ],
child: MyApp(settingsController: settingsController), child: MyApp(settingsController: settingsController),
), ),

View file

@ -1,7 +1,7 @@
import 'package:fixnum/fixnum.dart';
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/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/utils/storage.dart';
import 'package:twonly/src/views/onboarding_view.dart'; import 'package:twonly/src/views/onboarding_view.dart';
import 'package:twonly/src/views/home_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 'dart:async';
import 'settings/settings_controller.dart'; import 'settings/settings_controller.dart';
Function(Int64) addSendingTo = (a) {}; // these global function can be called from anywhere to update
Function(Int64) removeSendingTo = (a) {}; // the ui when something changed. The callbacks will be set by
Function() updateNotifyProvider = () {}; // 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. /// The Widget that configures your application.
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@ -28,7 +35,7 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
Future<bool> _isUserCreated = isUserCreated(); Future<bool> _isUserCreated = isUserCreated();
bool _showOnboarding = true; bool _showOnboarding = false;
bool _isConnected = false; bool _isConnected = false;
int redColorOpacity = 0; // Start with dark red int redColorOpacity = 0; // Start with dark red
bool redColorGoUp = true; bool redColorGoUp = true;
@ -39,31 +46,38 @@ class _MyAppState extends State<MyApp> {
super.initState(); super.initState();
_startColorAnimation(); _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(() { setState(() {
_isConnected = isConnected; _isConnected = isConnected;
}); });
});
apiProvider.setUpdatedContacts(() {
context.read<NotifyProvider>().update();
});
addSendingTo = (a) {
context.read<NotifyProvider>().addSendingTo(a);
}; };
removeSendingTo = (a) { globalCallBackOnContactChange = () {
context.read<NotifyProvider>().removeSendingTo(a); context.read<ContactChangeProvider>().update();
}; };
updateNotifyProvider = () { globalCallBackOnMessageChange = (userId) {
context.read<NotifyProvider>().update(); context.read<MessagesChangeProvider>().updateLastMessageFor(userId);
}; };
context.read<NotifyProvider>().update(); // connect async to the backend api
apiProvider.connect(); apiProvider.connect();
} }
@override
void dispose() {
// disable globalCallbacks to the flutter tree
globalCallbackConnectionState = (a) {};
globalCallBackOnContactChange = () {};
globalCallBackOnMessageChange = (a) {};
super.dispose();
}
void _startColorAnimation() { void _startColorAnimation() {
// Change the color every second // Change the color every second
Future.delayed(Duration(milliseconds: 200), () { Future.delayed(Duration(milliseconds: 200), () {

View file

@ -20,6 +20,8 @@
"searchUsernameNotFound": "Username not found", "searchUsernameNotFound": "Username not found",
"searchUsernameNewFollowerTitle": "Follow requests", "searchUsernameNewFollowerTitle": "Follow requests",
"searchUsernameQrCodeBtn": "Scan QR code", "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.", "searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.",
"errorUnknown": "An unexpected error has occurred. Please try again later.", "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.", "errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.",

View file

@ -2,6 +2,7 @@ import 'package:cv/cv.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
import 'package:twonly/src/app.dart';
class Contact { class Contact {
Contact( Contact(
@ -98,6 +99,7 @@ class DbContacts extends CvModelBase {
where: "$columnUserId = ?", where: "$columnUserId = ?",
whereArgs: [userId], whereArgs: [userId],
); );
globalCallBackOnContactChange();
} }
static Future acceptUser(int userId) async { static Future acceptUser(int userId) async {
@ -111,6 +113,7 @@ class DbContacts extends CvModelBase {
where: "$columnUserId = ?", where: "$columnUserId = ?",
whereArgs: [userId], whereArgs: [userId],
); );
globalCallBackOnContactChange();
} }
static Future deleteUser(int userId) async { static Future deleteUser(int userId) async {
@ -119,6 +122,7 @@ class DbContacts extends CvModelBase {
where: "$columnUserId = ?", where: "$columnUserId = ?",
whereArgs: [userId], whereArgs: [userId],
); );
globalCallBackOnContactChange();
} }
static Future<bool> insertNewContact( static Future<bool> insertNewContact(
@ -130,6 +134,7 @@ class DbContacts extends CvModelBase {
DbContacts.columnUserId: userId, DbContacts.columnUserId: userId,
DbContacts.columnRequested: a DbContacts.columnRequested: a
}); });
globalCallBackOnContactChange();
return true; return true;
} catch (e) { } catch (e) {
Logger("contacts_model/getUsers").shout("$e"); Logger("contacts_model/getUsers").shout("$e");

View file

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:cv/cv.dart'; import 'package:cv/cv.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.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';
class DbMessage { class DbMessage {
@ -13,7 +14,8 @@ class DbMessage {
required this.messageMessageKind, required this.messageMessageKind,
required this.messageContent, required this.messageContent,
required this.messageOpenedAt, required this.messageOpenedAt,
required this.messageAcknowledge, required this.messageAcknowledgeByUser,
required this.messageAcknowledgeByServer,
required this.sendOrReceivedAt, required this.sendOrReceivedAt,
}); });
@ -22,9 +24,10 @@ class DbMessage {
int? messageOtherId; int? messageOtherId;
int otherUserId; int otherUserId;
MessageKind messageMessageKind; MessageKind messageMessageKind;
MessageContent messageContent; MessageContent? messageContent;
DateTime? messageOpenedAt; DateTime? messageOpenedAt;
bool messageAcknowledge; bool messageAcknowledgeByUser;
bool messageAcknowledgeByServer;
DateTime sendOrReceivedAt; DateTime sendOrReceivedAt;
} }
@ -38,19 +41,24 @@ class DbMessages extends CvModelBase {
final messageOtherId = CvField<int?>(columnMessageOtherId); final messageOtherId = CvField<int?>(columnMessageOtherId);
static const columnOtherUserId = "other_user_id"; static const columnOtherUserId = "other_user_id";
final otherUserId = CvField<int?>(columnOtherUserId); final otherUserId = CvField<int>(columnOtherUserId);
static const columnMessageKind = "message_kind"; static const columnMessageKind = "message_kind";
final messageMessageKind = CvField<int>(columnMessageKind); final messageMessageKind = CvField<int>(columnMessageKind);
static const columnMessageContentJson = "message_json"; static const columnMessageContentJson = "message_json";
final messageContentJson = CvField<String>(columnMessageContentJson); final messageContentJson = CvField<String?>(columnMessageContentJson);
static const columnMessageOpenedAt = "message_opened_at"; static const columnMessageOpenedAt = "message_opened_at";
final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt); final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt);
static const columnMessageAcknowledge = "message_acknowledged"; static const columnMessageAcknowledgeByUser = "message_acknowledged_by_user";
final messageAcknowledge = CvField<int>(columnMessageAcknowledge); 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"; static const columnSendOrReceivedAt = "message_send_or_received_at";
final sendOrReceivedAt = CvField<DateTime>(columnSendOrReceivedAt); final sendOrReceivedAt = CvField<DateTime>(columnSendOrReceivedAt);
@ -63,10 +71,11 @@ class DbMessages extends CvModelBase {
CREATE TABLE IF NOT EXISTS $tableName ( CREATE TABLE IF NOT EXISTS $tableName (
$columnMessageId INTEGER NOT NULL PRIMARY KEY, $columnMessageId INTEGER NOT NULL PRIMARY KEY,
$columnMessageOtherId INTEGER DEFAULT NULL, $columnMessageOtherId INTEGER DEFAULT NULL,
$columnOtherUserId INTEGER DEFAULT NULL, $columnOtherUserId INTEGER NOT NULL,
$columnMessageKind INTEGER NOT NULL, $columnMessageKind INTEGER NOT NULL,
$columnMessageAcknowledge INTEGER NOT NULL DEFAULT 0, $columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0,
$columnMessageContentJson TEXT NOT NULL, $columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0,
$columnMessageContentJson TEXT DEFAULT NULL,
$columnMessageOpenedAt DATETIME DEFAULT NULL, $columnMessageOpenedAt DATETIME DEFAULT NULL,
$columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP, $columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
$columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP $columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
@ -74,8 +83,33 @@ class DbMessages extends CvModelBase {
"""; """;
} }
static Future<int?> insertMyMessage( static Future deleteMessageById(int messageId) async {
int userIdFrom, MessageKind kind, String jsonContent) 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 { try {
int messageId = await dbProvider.db!.insert(tableName, { int messageId = await dbProvider.db!.insert(tableName, {
columnMessageKind: kind.index, columnMessageKind: kind.index,
@ -83,6 +117,7 @@ class DbMessages extends CvModelBase {
columnOtherUserId: userIdFrom, columnOtherUserId: userIdFrom,
columnSendOrReceivedAt: DateTime.now().toIso8601String() columnSendOrReceivedAt: DateTime.now().toIso8601String()
}); });
globalCallBackOnMessageChange(userIdFrom);
return messageId; return messageId;
} catch (e) { } catch (e) {
Logger("contacts_model/getUsers").shout("$e"); Logger("contacts_model/getUsers").shout("$e");
@ -97,8 +132,12 @@ class DbMessages extends CvModelBase {
columnMessageOtherId: messageId, columnMessageOtherId: messageId,
columnMessageKind: kind.index, columnMessageKind: kind.index,
columnMessageContentJson: jsonContent, columnMessageContentJson: jsonContent,
columnMessageAcknowledgeByServer: 1,
columnMessageAcknowledgeByUser:
0, // ack in case of sending corresponds to the opened flag
columnOtherUserId: userIdFrom columnOtherUserId: userIdFrom
}); });
globalCallBackOnMessageChange(userIdFrom);
return true; return true;
} catch (e) { } catch (e) {
Logger("contacts_model/getUsers").shout("$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( static Future<DbMessage?> getLastMessagesForPreviewForUser(
int otherUserId) async { int otherUserId) async {
var rows = await dbProvider.db!.query(tableName, var rows = await dbProvider.db!.query(
tableName,
where: "$columnOtherUserId = ?", where: "$columnOtherUserId = ?",
whereArgs: [otherUserId], whereArgs: [otherUserId],
orderBy: "$columnUpdatedAt DESC", orderBy: "$columnUpdatedAt DESC",
limit: 1); limit: 10,
);
List<DbMessage> messages = convertToDbMessage(rows); 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; if (messages.isEmpty) return null;
return messages[0]; return messages[0];
} }
static Future acknowledgeMessage(int fromUserId, int messageId) async { static Future acknowledgeMessageByServer(int messageId) async {
Map<String, dynamic> valuesToUpdate = { 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( await dbProvider.db!.update(
tableName, tableName,
@ -163,6 +198,7 @@ class DbMessages extends CvModelBase {
where: "$messageId = ? AND $columnOtherUserId = ?", where: "$messageId = ? AND $columnOtherUserId = ?",
whereArgs: [messageId, fromUserId], whereArgs: [messageId, fromUserId],
); );
globalCallBackOnMessageChange(fromUserId);
} }
@override @override
@ -173,4 +209,42 @@ class DbMessages extends CvModelBase {
messageOpenedAt, messageOpenedAt,
sendOrReceivedAt 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 [];
}
}
} }

View file

@ -742,21 +742,12 @@ class ApplicationData_GetPrekeysByUserId extends $pb.GeneratedMessage {
} }
class ApplicationData_GetUploadToken extends $pb.GeneratedMessage { class ApplicationData_GetUploadToken extends $pb.GeneratedMessage {
factory ApplicationData_GetUploadToken({ factory ApplicationData_GetUploadToken() => create();
$core.int? len,
}) {
final $result = create();
if (len != null) {
$result.len = len;
}
return $result;
}
ApplicationData_GetUploadToken._() : super(); 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.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); 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) 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 ..hasRequiredFields = false
; ;
@ -780,15 +771,6 @@ class ApplicationData_GetUploadToken extends $pb.GeneratedMessage {
@$core.pragma('dart2js:noInline') @$core.pragma('dart2js:noInline')
static ApplicationData_GetUploadToken getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_GetUploadToken>(create); static ApplicationData_GetUploadToken getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_GetUploadToken>(create);
static ApplicationData_GetUploadToken? _defaultInstance; 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 { class ApplicationData_UploadData extends $pb.GeneratedMessage {
@ -869,6 +851,56 @@ class ApplicationData_UploadData extends $pb.GeneratedMessage {
void clearData() => clearField(3); 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 { enum ApplicationData_ApplicationData {
textmessage, textmessage,
getuserbyusername, getuserbyusername,
@ -876,6 +908,7 @@ enum ApplicationData_ApplicationData {
getuploadtoken, getuploadtoken,
uploaddata, uploaddata,
getuserbyid, getuserbyid,
downloaddata,
notSet notSet
} }
@ -887,6 +920,7 @@ class ApplicationData extends $pb.GeneratedMessage {
ApplicationData_GetUploadToken? getuploadtoken, ApplicationData_GetUploadToken? getuploadtoken,
ApplicationData_UploadData? uploaddata, ApplicationData_UploadData? uploaddata,
ApplicationData_GetUserById? getuserbyid, ApplicationData_GetUserById? getuserbyid,
ApplicationData_DownloadData? downloaddata,
}) { }) {
final $result = create(); final $result = create();
if (textmessage != null) { if (textmessage != null) {
@ -907,6 +941,9 @@ class ApplicationData extends $pb.GeneratedMessage {
if (getuserbyid != null) { if (getuserbyid != null) {
$result.getuserbyid = getuserbyid; $result.getuserbyid = getuserbyid;
} }
if (downloaddata != null) {
$result.downloaddata = downloaddata;
}
return $result; return $result;
} }
ApplicationData._() : super(); ApplicationData._() : super();
@ -920,16 +957,18 @@ class ApplicationData extends $pb.GeneratedMessage {
4 : ApplicationData_ApplicationData.getuploadtoken, 4 : ApplicationData_ApplicationData.getuploadtoken,
5 : ApplicationData_ApplicationData.uploaddata, 5 : ApplicationData_ApplicationData.uploaddata,
6 : ApplicationData_ApplicationData.getuserbyid, 6 : ApplicationData_ApplicationData.getuserbyid,
7 : ApplicationData_ApplicationData.downloaddata,
0 : ApplicationData_ApplicationData.notSet 0 : ApplicationData_ApplicationData.notSet
}; };
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create) 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_TextMessage>(1, _omitFieldNames ? '' : 'textmessage', subBuilder: ApplicationData_TextMessage.create)
..aOM<ApplicationData_GetUserByUsername>(2, _omitFieldNames ? '' : 'getuserbyusername', subBuilder: ApplicationData_GetUserByUsername.create) ..aOM<ApplicationData_GetUserByUsername>(2, _omitFieldNames ? '' : 'getuserbyusername', subBuilder: ApplicationData_GetUserByUsername.create)
..aOM<ApplicationData_GetPrekeysByUserId>(3, _omitFieldNames ? '' : 'getprekeysbyuserid', subBuilder: ApplicationData_GetPrekeysByUserId.create) ..aOM<ApplicationData_GetPrekeysByUserId>(3, _omitFieldNames ? '' : 'getprekeysbyuserid', subBuilder: ApplicationData_GetPrekeysByUserId.create)
..aOM<ApplicationData_GetUploadToken>(4, _omitFieldNames ? '' : 'getuploadtoken', subBuilder: ApplicationData_GetUploadToken.create) ..aOM<ApplicationData_GetUploadToken>(4, _omitFieldNames ? '' : 'getuploadtoken', subBuilder: ApplicationData_GetUploadToken.create)
..aOM<ApplicationData_UploadData>(5, _omitFieldNames ? '' : 'uploaddata', subBuilder: ApplicationData_UploadData.create) ..aOM<ApplicationData_UploadData>(5, _omitFieldNames ? '' : 'uploaddata', subBuilder: ApplicationData_UploadData.create)
..aOM<ApplicationData_GetUserById>(6, _omitFieldNames ? '' : 'getuserbyid', subBuilder: ApplicationData_GetUserById.create) ..aOM<ApplicationData_GetUserById>(6, _omitFieldNames ? '' : 'getuserbyid', subBuilder: ApplicationData_GetUserById.create)
..aOM<ApplicationData_DownloadData>(7, _omitFieldNames ? '' : 'downloaddata', subBuilder: ApplicationData_DownloadData.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -1022,6 +1061,17 @@ class ApplicationData extends $pb.GeneratedMessage {
void clearGetuserbyid() => clearField(6); void clearGetuserbyid() => clearField(6);
@$pb.TagNumber(6) @$pb.TagNumber(6)
ApplicationData_GetUserById ensureGetuserbyid() => $_ensure(5); 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 { class Response_PreKey extends $pb.GeneratedMessage {

View file

@ -117,12 +117,13 @@ const ApplicationData$json = {
'2': [ '2': [
{'1': 'textmessage', '3': 1, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.TextMessage', '9': 0, '10': 'textmessage'}, {'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': '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': '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': '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': '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': [ '8': [
{'1': 'ApplicationData'}, {'1': 'ApplicationData'},
], ],
@ -164,9 +165,6 @@ const ApplicationData_GetPrekeysByUserId$json = {
@$core.Deprecated('Use applicationDataDescriptor instead') @$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_GetUploadToken$json = { const ApplicationData_GetUploadToken$json = {
'1': 'GetUploadToken', '1': 'GetUploadToken',
'2': [
{'1': 'len', '3': 1, '4': 1, '5': 13, '10': 'len'},
],
}; };
@$core.Deprecated('Use applicationDataDescriptor instead') @$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`. /// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dG1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm' 'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dG1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
'VyLkFwcGxpY2F0aW9uRGF0YS5UZXh0TWVzc2FnZUgAUgt0ZXh0bWVzc2FnZRJjChFnZXR1c2Vy' 'VyLkFwcGxpY2F0aW9uRGF0YS5UZXh0TWVzc2FnZUgAUgt0ZXh0bWVzc2FnZRJjChFnZXR1c2Vy'
'Ynl1c2VybmFtZRgCIAEoCzIzLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldF' 'Ynl1c2VybmFtZRgCIAEoCzIzLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldF'
'VzZXJCeVVzZXJuYW1lSABSEWdldHVzZXJieXVzZXJuYW1lElEKC2dldHVzZXJieWlkGAYgASgL' 'VzZXJCeVVzZXJuYW1lSABSEWdldHVzZXJieXVzZXJuYW1lEmYKEmdldHByZWtleXNieXVzZXJp'
'Mi0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0VXNlckJ5SWRIAFILZ2V0dX' 'ZBgDIAEoCzI0LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldFByZWtleXNCeV'
'NlcmJ5aWQSZgoSZ2V0cHJla2V5c2J5dXNlcmlkGAMgASgLMjQuY2xpZW50X3RvX3NlcnZlci5B' 'VzZXJJZEgAUhJnZXRwcmVrZXlzYnl1c2VyaWQSWgoOZ2V0dXBsb2FkdG9rZW4YBCABKAsyMC5j'
'cHBsaWNhdGlvbkRhdGEuR2V0UHJla2V5c0J5VXNlcklkSABSEmdldHByZWtleXNieXVzZXJpZB' 'bGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRVcGxvYWRUb2tlbkgAUg5nZXR1cG'
'JaCg5nZXR1cGxvYWR0b2tlbhgEIAEoCzIwLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25E' 'xvYWR0b2tlbhJOCgp1cGxvYWRkYXRhGAUgASgLMiwuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNh'
'YXRhLkdldFVwbG9hZFRva2VuSABSDmdldHVwbG9hZHRva2VuEk4KCnVwbG9hZGRhdGEYBSABKA' 'dGlvbkRhdGEuVXBsb2FkRGF0YUgAUgp1cGxvYWRkYXRhElEKC2dldHVzZXJieWlkGAYgASgLMi'
'syLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5VcGxvYWREYXRhSABSCnVwbG9h' '0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0VXNlckJ5SWRIAFILZ2V0dXNl'
'ZGRhdGEaOgoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAy' 'cmJ5aWQSVAoMZG93bmxvYWRkYXRhGAcgASgLMi4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
'ABKAxSBGJvZHkaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJu' 'lvbkRhdGEuRG93bmxvYWREYXRhSABSDGRvd25sb2FkZGF0YRo6CgtUZXh0TWVzc2FnZRIXCgd1'
'YW1lGiYKC0dldFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBotChJHZXRQcmVrZX' 'c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRovChFHZXRVc2VyQnlVc2'
'lzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGiIKDkdldFVwbG9hZFRva2VuEhAK' 'VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaJgoLR2V0VXNlckJ5SWQSFwoHdXNl'
'A2xlbhgBIAEoDVIDbGVuGlsKClVwbG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cG' 'cl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgAS'
'xvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRhQhEK' 'gDUgZ1c2VySWQaEAoOR2V0VXBsb2FkVG9rZW4aWwoKVXBsb2FkRGF0YRIhCgx1cGxvYWRfdG9r'
'D0FwcGxpY2F0aW9uRGF0YQ=='); 'ZW4YASABKAxSC3VwbG9hZFRva2VuEhYKBm9mZnNldBgCIAEoDVIGb2Zmc2V0EhIKBGRhdGEYAy'
'ABKAxSBGRhdGEaMQoMRG93bmxvYWREYXRhEiEKDHVwbG9hZF90b2tlbhgBIAEoDFILdXBsb2Fk'
'VG9rZW5CEQoPQXBwbGljYXRpb25EYXRh');
@$core.Deprecated('Use responseDescriptor instead') @$core.Deprecated('Use responseDescriptor instead')
const Response$json = { const Response$json = {

View file

@ -85,6 +85,7 @@ enum V0_Kind {
response, response,
newMessage, newMessage,
requestNewPreKeys, requestNewPreKeys,
downloaddata,
notSet notSet
} }
@ -94,6 +95,7 @@ class V0 extends $pb.GeneratedMessage {
Response? response, Response? response,
NewMessage? newMessage, NewMessage? newMessage,
$core.bool? requestNewPreKeys, $core.bool? requestNewPreKeys,
DownloadData? downloaddata,
}) { }) {
final $result = create(); final $result = create();
if (seq != null) { if (seq != null) {
@ -108,6 +110,9 @@ class V0 extends $pb.GeneratedMessage {
if (requestNewPreKeys != null) { if (requestNewPreKeys != null) {
$result.requestNewPreKeys = requestNewPreKeys; $result.requestNewPreKeys = requestNewPreKeys;
} }
if (downloaddata != null) {
$result.downloaddata = downloaddata;
}
return $result; return $result;
} }
V0._() : super(); V0._() : super();
@ -118,14 +123,16 @@ class V0 extends $pb.GeneratedMessage {
2 : V0_Kind.response, 2 : V0_Kind.response,
3 : V0_Kind.newMessage, 3 : V0_Kind.newMessage,
4 : V0_Kind.requestNewPreKeys, 4 : V0_Kind.requestNewPreKeys,
5 : V0_Kind.downloaddata,
0 : V0_Kind.notSet 0 : V0_Kind.notSet
}; };
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'V0', package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create) 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) ..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
..aOM<Response>(2, _omitFieldNames ? '' : 'response', subBuilder: Response.create) ..aOM<Response>(2, _omitFieldNames ? '' : 'response', subBuilder: Response.create)
..aOM<NewMessage>(3, _omitFieldNames ? '' : 'newMessage', protoName: 'newMessage', subBuilder: NewMessage.create) ..aOM<NewMessage>(3, _omitFieldNames ? '' : 'newMessage', protoName: 'newMessage', subBuilder: NewMessage.create)
..aOB(4, _omitFieldNames ? '' : 'RequestNewPreKeys', protoName: 'RequestNewPreKeys') ..aOB(4, _omitFieldNames ? '' : 'RequestNewPreKeys', protoName: 'RequestNewPreKeys')
..aOM<DownloadData>(5, _omitFieldNames ? '' : 'downloaddata', subBuilder: DownloadData.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -192,6 +199,17 @@ class V0 extends $pb.GeneratedMessage {
$core.bool hasRequestNewPreKeys() => $_has(3); $core.bool hasRequestNewPreKeys() => $_has(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
void clearRequestNewPreKeys() => clearField(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 { class NewMessage extends $pb.GeneratedMessage {
@ -258,6 +276,84 @@ class NewMessage extends $pb.GeneratedMessage {
void clearFromUserId() => clearField(2); 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 { class Response_PreKey extends $pb.GeneratedMessage {
factory Response_PreKey({ factory Response_PreKey({
$fixnum.Int64? id, $fixnum.Int64? id,

View file

@ -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': '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': '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': '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': [ '8': [
{'1': 'Kind'}, {'1': 'Kind'},
@ -48,7 +49,8 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
'CgJWMBIQCgNzZXEYASABKARSA3NlcRI4CghyZXNwb25zZRgCIAEoCzIaLnNlcnZlcl90b19jbG' 'CgJWMBIQCgNzZXEYASABKARSA3NlcRI4CghyZXNwb25zZRgCIAEoCzIaLnNlcnZlcl90b19jbG'
'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90' 'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90'
'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEi4KEVJlcXVlc3ROZXdQcmVLZXlzGA' 'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEi4KEVJlcXVlc3ROZXdQcmVLZXlzGA'
'QgASgISABSEVJlcXVlc3ROZXdQcmVLZXlzQgYKBEtpbmQ='); 'QgASgISABSEVJlcXVlc3ROZXdQcmVLZXlzEkQKDGRvd25sb2FkZGF0YRgFIAEoCzIeLnNlcnZl'
'cl90b19jbGllbnQuRG93bmxvYWREYXRhSABSDGRvd25sb2FkZGF0YUIGCgRLaW5k');
@$core.Deprecated('Use newMessageDescriptor instead') @$core.Deprecated('Use newMessageDescriptor instead')
const NewMessage$json = { const NewMessage$json = {
@ -64,6 +66,21 @@ final $typed_data.Uint8List newMessageDescriptor = $convert.base64Decode(
'CgpOZXdNZXNzYWdlEiAKDGZyb21fdXNlcl9pZBgCIAEoA1IKZnJvbVVzZXJJZBISCgRib2R5GA' 'CgpOZXdNZXNzYWdlEiAKDGZyb21fdXNlcl9pZBgCIAEoA1IKZnJvbVVzZXJJZBISCgRib2R5GA'
'EgASgMUgRib2R5'); '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') @$core.Deprecated('Use responseDescriptor instead')
const Response$json = { const Response$json = {
'1': 'Response', '1': 'Response',

View 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());
}

View 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;
}

View 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);
}

View file

@ -1,18 +1,15 @@
import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/app.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/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.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/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/misc.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
// ignore: library_prefixes // 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:libsignal_protocol_dart/src/ecc/ed25519.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
class Result<T, E> { /// The ApiProvider is responsible for communicating with the server.
final T? value; /// It handles errors and does automatically tries to reconnect on
final E? error; /// errors or network changes.
bool get isSuccess => value != null;
bool get isError => error != null;
Result.success(this.value) : error = null;
Result.error(this.error) : value = null;
}
class ApiProvider { class ApiProvider {
ApiProvider({required this.apiUrl, required this.backupApiUrl});
final String apiUrl; final String apiUrl;
final String? backupApiUrl; final String? backupApiUrl;
ApiProvider({required this.apiUrl, required this.backupApiUrl});
final log = Logger("ApiProvider");
// reconnection params
Timer? reconnectionTimer;
int _reconnectionDelay = 5; int _reconnectionDelay = 5;
bool _tryingToConnect = false;
final log = Logger("api_provider");
Function(bool)? _connectionStateCallback;
Function? _updatedContacts;
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap(); final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
IOWebSocketChannel? _channel; IOWebSocketChannel? _channel;
Future<bool> _connectTo(String apiUrl) async { Future<bool> _connectTo(String apiUrl) async {
@ -68,19 +56,23 @@ class ApiProvider {
if (_channel != null && _channel!.closeCode != null) { if (_channel != null && _channel!.closeCode != null) {
return true; 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!"); log.info("Trying to connect to the backend $apiUrl!");
if (await _connectTo(apiUrl)) { if (await _connectTo(apiUrl)) {
await authenticate(); await authenticate();
if (_connectionStateCallback != null) _connectionStateCallback!(true); globalCallbackConnectionState(true);
_reconnectionDelay = 5; _reconnectionDelay = 5;
return true; return true;
} }
if (backupApiUrl != null) { if (backupApiUrl != null) {
log.info("Trying to connect to the backup backend $backupApiUrl!"); log.info("Trying to connect to the backup backend $backupApiUrl!");
if (await _connectTo(backupApiUrl!)) { if (await _connectTo(backupApiUrl!)) {
globalCallbackConnectionState(true);
await authenticate(); await authenticate();
if (_connectionStateCallback != null) _connectionStateCallback!(true);
_reconnectionDelay = 5; _reconnectionDelay = 5;
return true; return true;
} }
@ -91,42 +83,35 @@ class ApiProvider {
bool get isConnected => _channel != null && _channel!.closeCode != null; bool get isConnected => _channel != null && _channel!.closeCode != null;
void _onDone() { void _onDone() {
if (_connectionStateCallback != null) { globalCallbackConnectionState(false);
_connectionStateCallback!(false);
}
_channel = null; _channel = null;
tryToReconnect(); tryToReconnect();
} }
void _onError(dynamic e) { void _onError(dynamic e) {
if (_connectionStateCallback != null) { globalCallbackConnectionState(false);
_connectionStateCallback!(false);
}
_channel = null; _channel = null;
tryToReconnect(); tryToReconnect();
} }
void setConnectionStateCallback(Function(bool) callBack) {
_connectionStateCallback = callBack;
}
void setUpdatedContacts(Function callBack) {
_updatedContacts = callBack;
}
void tryToReconnect() { void tryToReconnect() {
if (_tryingToConnect) return; if (reconnectionTimer != null) {
_tryingToConnect = true; reconnectionTimer!.cancel();
Future.delayed(Duration(seconds: _reconnectionDelay)).then( }
(value) async {
_tryingToConnect = false; final int randomDelay = Random().nextInt(20);
_reconnectionDelay = _reconnectionDelay + 2; final int delay = _reconnectionDelay + randomDelay;
if (_reconnectionDelay > 20) {
_reconnectionDelay = 20; 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(); await connect();
}, });
);
} }
void _onData(dynamic msgBuffer) { void _onData(dynamic msgBuffer) {
@ -135,103 +120,13 @@ class ApiProvider {
if (msg.v0.hasResponse()) { if (msg.v0.hasResponse()) {
messagesV0[msg.v0.seq] = msg; messagesV0[msg.v0.seq] = msg;
} else { } else {
_handleServerMessage(msg); handleServerMessage(msg);
} }
} catch (e) { } catch (e) {
log.shout("Error parsing the servers message: $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 { Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
final startTime = DateTime.now(); 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) { if (_channel == null) {
return null; return Result.error(ErrorCode.InternalError);
} }
var seq = Int64(Random().nextInt(4294967296)); var seq = Int64(Random().nextInt(4294967296));
while (messagesV0.containsKey(seq)) { while (messagesV0.containsKey(seq)) {
@ -265,30 +164,7 @@ class ApiProvider {
_channel!.sink.add(requestBytes); _channel!.sink.add(requestBytes);
return await _waitForResponse(seq); return asResult(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);
}
} }
Future authenticate() async { Future authenticate() async {
@ -299,12 +175,7 @@ class ApiProvider {
var handshake = Handshake()..getchallenge = Handshake_GetChallenge(); var handshake = Handshake()..getchallenge = Handshake_GetChallenge();
var req = createClientToServerFromHandshake(handshake); var req = createClientToServerFromHandshake(handshake);
final resp = await _sendRequestV0(req); final result = await _sendRequestV0(req);
if (resp == null) {
log.shout("Server is not reachable!");
return;
}
final result = _asResult(resp);
if (result.isError) { if (result.isError) {
log.shout(result); log.shout(result);
return; return;
@ -328,12 +199,7 @@ class ApiProvider {
var req2 = createClientToServerFromHandshake(opensession); var req2 = createClientToServerFromHandshake(opensession);
final resp2 = await _sendRequestV0(req2); final result2 = await _sendRequestV0(req2);
if (resp2 == null) {
log.shout("Server is not reachable!");
return;
}
final result2 = _asResult(resp2);
if (result2.isError) { if (result2.isError) {
log.shout(result2); log.shout(result2);
return; return;
@ -370,33 +236,21 @@ class ApiProvider {
var handshake = Handshake()..register = register; var handshake = Handshake()..register = register;
var req = createClientToServerFromHandshake(handshake); var req = createClientToServerFromHandshake(handshake);
final resp = await _sendRequestV0(req); return await _sendRequestV0(req);
if (resp == null) {
return Result.error(ErrorCode.InternalError);
}
return _asResult(resp);
} }
Future<Result> getUsername(Int64 userId) async { Future<Result> getUsername(Int64 userId) async {
var get = ApplicationData_GetUserById()..userId = userId; var get = ApplicationData_GetUserById()..userId = userId;
var appData = ApplicationData()..getuserbyid = get; var appData = ApplicationData()..getuserbyid = get;
var req = createClientToServerFromApplicationData(appData); var req = createClientToServerFromApplicationData(appData);
final resp = await _sendRequestV0(req); return await _sendRequestV0(req);
if (resp == null) {
return Result.error(ErrorCode.InternalError);
}
return _asResult(resp);
} }
Future<Result> getUploadToken(int size) async { Future<Result> getUploadToken() async {
var get = ApplicationData_GetUploadToken()..len = size; var get = ApplicationData_GetUploadToken();
var appData = ApplicationData()..getuploadtoken = get; var appData = ApplicationData()..getuploadtoken = get;
var req = createClientToServerFromApplicationData(appData); var req = createClientToServerFromApplicationData(appData);
final resp = await _sendRequestV0(req); return await _sendRequestV0(req);
if (resp == null) {
return Result.error(ErrorCode.InternalError);
}
return _asResult(resp);
} }
Future<List<int>?> uploadData(List<int> uploadToken, Uint8List data) async { Future<List<int>?> uploadData(List<int> uploadToken, Uint8List data) async {
@ -409,23 +263,15 @@ class ApiProvider {
var appData = ApplicationData()..uploaddata = get; var appData = ApplicationData()..uploaddata = get;
var req = createClientToServerFromApplicationData(appData); var req = createClientToServerFromApplicationData(appData);
final resp = await _sendRequestV0(req); final result = await _sendRequestV0(req);
if (resp == null) { return result.isSuccess ? uploadToken : null;
return null;
}
return _asResult(resp).isSuccess ? uploadToken : null;
} }
Future<Result> getUserData(String username) async { Future<Result> getUserData(String username) async {
var get = ApplicationData_GetUserByUsername()..username = username; var get = ApplicationData_GetUserByUsername()..username = username;
var appData = ApplicationData()..getuserbyusername = get; var appData = ApplicationData()..getuserbyusername = get;
var req = createClientToServerFromApplicationData(appData); var req = createClientToServerFromApplicationData(appData);
return await _sendRequestV0(req);
final resp = await _sendRequestV0(req);
if (resp == null) {
return Result.error(ErrorCode.InternalError);
}
return _asResult(resp);
} }
Future<Result> sendTextMessage(Int64 target, Uint8List msg) async { Future<Result> sendTextMessage(Int64 target, Uint8List msg) async {
@ -436,10 +282,6 @@ class ApiProvider {
var appData = ApplicationData()..textmessage = testMessage; var appData = ApplicationData()..textmessage = testMessage;
var req = createClientToServerFromApplicationData(appData); var req = createClientToServerFromApplicationData(appData);
final resp = await _sendRequestV0(req); return await _sendRequestV0(req);
if (resp == null) {
return Result.error(ErrorCode.InternalError);
}
return _asResult(resp);
} }
} }

View file

@ -1,50 +1,26 @@
import 'dart:collection';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/model/messages_model.dart'; import 'package:twonly/src/model/messages_model.dart';
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool // This provider will update the UI on changes in the contact list
// ignore: prefer_mixin class ContactChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
class NotifyProvider with ChangeNotifier, DiagnosticableTreeMixin {
// The page index of the HomeView widget // The page index of the HomeView widget
int _activePageIdx = 0; // int _activePageIdx = 0;
List<Contact> _allContacts = []; List<Contact> _allContacts = [];
final Map<int, DbMessage> _lastMessagesGroupedByUser = <int, DbMessage>{}; final Map<int, DbMessage> _lastMessagesGroupedByUser = <int, DbMessage>{};
final List<Int64> _sendingCurrentlyTo = [];
int get newContactRequests => _allContacts int get newContactRequests => _allContacts
.where((contact) => !contact.accepted && contact.requested) .where((contact) => !contact.accepted && contact.requested)
.length; .length;
List<Contact> get allContacts => _allContacts; 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) { // void setActivePageIdx(int idx) {
_activePageIdx = idx; // _activePageIdx = idx;
notifyListeners(); // 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 update() async { void update() async {
_allContacts = await DbContacts.getUsers(); _allContacts = await DbContacts.getUsers();

View 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;
}
}
}
}

View file

@ -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());
}

View file

@ -1,11 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:gal/gal.dart'; import 'package:gal/gal.dart';
import 'package:image/image.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:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -127,7 +125,5 @@ Future<Uint8List?> getCompressedImage(File file) async {
file.absolute.path, file.absolute.path,
quality: 90, quality: 90,
); );
print(file.lengthSync());
print(result!.length);
return result; return result;
} }

View file

@ -1,6 +1,3 @@
import 'dart:collection';
import 'package:fixnum/fixnum.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.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/components/user_context_menu.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/model/messages_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/utils/misc.dart';
import 'package:twonly/src/views/chat_item_details_view.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:twonly/src/views/search_username_view.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async';
class ChatItem { class ChatItem {
const ChatItem( const ChatItem(
@ -42,22 +40,20 @@ class _ChatListViewState extends State<ChatListView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Map<int, DbMessage> lastMessages = Map<int, DbMessage> lastMessages =
context.watch<NotifyProvider>().lastMessagesGroupedByUser; context.watch<MessagesChangeProvider>().lastMessage;
HashSet<Int64> sendingCurrentlyTo = List<Contact> allUsers = context.read<ContactChangeProvider>().allContacts;
context.watch<NotifyProvider>().sendingCurrentlyTo;
List<Contact> activeUsers = context List<Contact> activeUsers = allUsers
.read<NotifyProvider>()
.allContacts
.where((x) => lastMessages.containsKey(x.userId.toInt())) .where((x) => lastMessages.containsKey(x.userId.toInt()))
.toList(); .toList();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.chatsTitle), title: Text(AppLocalizations.of(context)!.chatsTitle),
actions: [ actions: [
NotificationBadge( NotificationBadge(
count: context.watch<NotifyProvider>().newContactRequests, count: context.watch<ContactChangeProvider>().newContactRequests,
child: IconButton( child: IconButton(
icon: Icon(Icons.person_add), icon: Icon(Icons.person_add),
onPressed: () { onPressed: () {
@ -72,37 +68,32 @@ class _ChatListViewState extends State<ChatListView> {
) )
], ],
), ),
body: Column( body: (activeUsers.isEmpty)
children: [ ? Center(
// if (sendingCurrentlyTo.isNotEmpty) child: Padding(
// Container( padding: const EdgeInsets.all(10),
// padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10), child: OutlinedButton.icon(
// child: ListTile( icon: Icon((allUsers.isEmpty)
// leading: Stack( ? Icons.person_add
// // child: Stack( : Icons.camera_alt),
// alignment: Alignment.center, onPressed: () {
// children: [ (allUsers.isEmpty)
// CircularProgressIndicator( ? Navigator.push(
// strokeWidth: 1, context,
// ), MaterialPageRoute(
// Icon( builder: (context) => SearchUsernameView(),
// Icons.send, // Replace with your desired icon ),
// color: Theme.of(context).colorScheme.primary, )
// size: 20, // Adjust the size as needed : globalUpdateOfHomeViewPageIndex(1);
// ), },
// ], label: Text((allUsers.isEmpty)
// // ), ? AppLocalizations.of(context)!
// ), .chatListViewSearchUserNameBtn
// title: Text(sendingCurrentlyTo : AppLocalizations.of(context)!
// .map((e) => _activeUsers .chatListViewSendFirstTwonly)),
// .where((c) => c.userId == e) ),
// .map((c) => c.displayName)) )
// .toList() : ListView.builder(
// .join(", ")),
// ),
// ), //
Expanded(
child: ListView.builder(
restorationId: 'chat_list_view', restorationId: 'chat_list_view',
itemCount: activeUsers.length, itemCount: activeUsers.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
@ -110,25 +101,20 @@ class _ChatListViewState extends State<ChatListView> {
return UserListItem( return UserListItem(
user: user, user: user,
lastMessage: lastMessages[user.userId.toInt()]!, lastMessage: lastMessages[user.userId.toInt()]!,
isSending: sendingCurrentlyTo.contains(user.userId),
); );
}, },
), ),
) );
],
));
} }
} }
class UserListItem extends StatefulWidget { class UserListItem extends StatefulWidget {
final Contact user; final Contact user;
final DbMessage lastMessage; final DbMessage lastMessage;
final bool isSending;
const UserListItem({ const UserListItem({
super.key, super.key,
required this.user, required this.user,
required this.isSending,
required this.lastMessage, required this.lastMessage,
}); });
@ -146,11 +132,11 @@ class _UserListItem extends State<UserListItem> {
//_loadAsync(); //_loadAsync();
} }
Future _loadAsync() async { // Future _loadAsync() async {
// flames = await widget.user.getFlames(); // flames = await widget.user.getFlames();
// lastMessageInSeconds = await widget.user.getLastMessageInSeconds(); // lastMessageInSeconds = await widget.user.getLastMessageInSeconds();
setState(() {}); // setState(() {});
} // }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -161,7 +147,7 @@ class _UserListItem extends State<UserListItem> {
.difference(widget.lastMessage.sendOrReceivedAt) .difference(widget.lastMessage.sendOrReceivedAt)
.inSeconds; .inSeconds;
if (widget.isSending) { if (!widget.lastMessage.messageAcknowledgeByServer) {
state = MessageSendState.sending; state = MessageSendState.sending;
} else { } else {
if (widget.lastMessage.messageOtherId == null) { if (widget.lastMessage.messageOtherId == null) {

View file

@ -1,14 +1,11 @@
import 'package:pie_menu/pie_menu.dart'; 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 'camera_preview_view.dart';
import 'chat_list_view.dart'; import 'chat_list_view.dart';
import 'profile_view.dart'; import 'profile_view.dart';
import '../settings/settings_controller.dart'; import '../settings/settings_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
final PageController homeViewPageController = PageController(initialPage: 0); Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
class HomeView extends StatefulWidget { class HomeView extends StatefulWidget {
const HomeView({super.key, required this.settingsController}); const HomeView({super.key, required this.settingsController});
@ -19,6 +16,27 @@ class HomeView extends StatefulWidget {
} }
class HomeViewState extends State<HomeView> { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PieCanvas( return PieCanvas(
@ -47,7 +65,7 @@ class HomeViewState extends State<HomeView> {
body: PageView( body: PageView(
controller: homeViewPageController, controller: homeViewPageController,
onPageChanged: (index) { onPageChanged: (index) {
context.read<NotifyProvider>().setActivePageIdx(index); activePageIdx = index;
}, },
children: [ children: [
ChatListView(), ChatListView(),
@ -69,14 +87,16 @@ class HomeViewState extends State<HomeView> {
BottomNavigationBarItem(icon: Icon(Icons.verified_user), label: ""), BottomNavigationBarItem(icon: Icon(Icons.verified_user), label: ""),
], ],
onTap: (int index) { onTap: (int index) {
context.read<NotifyProvider>().setActivePageIdx(index); activePageIdx = index;
setState(() { setState(() {
homeViewPageController.animateToPage(index, homeViewPageController.animateToPage(
index,
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
curve: Curves.bounceIn); curve: Curves.bounceIn,
);
}); });
}, },
currentIndex: context.watch<NotifyProvider>().activePageIdx, currentIndex: activePageIdx,
), ),
), ),
); );

View file

@ -1,9 +1,12 @@
import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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/misc.dart';
import 'package:twonly/src/utils/signal.dart';
class RegisterView extends StatefulWidget { class RegisterView extends StatefulWidget {
const RegisterView({super.key, required this.callbackOnSuccess}); const RegisterView({super.key, required this.callbackOnSuccess});
@ -19,6 +22,41 @@ class _RegisterViewState extends State<RegisterView> {
bool _isTryingToRegister = false; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
InputDecoration getInputDecoration(hintText) { InputDecoration getInputDecoration(hintText) {
@ -117,23 +155,7 @@ class _RegisterViewState extends State<RegisterView> {
) )
: Icon(Icons.group), : Icon(Icons.group),
onPressed: () async { onPressed: () async {
setState(() { createNewUser();
_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);
}
}, },
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>( padding: WidgetStateProperty.all<EdgeInsets>(

View file

@ -2,12 +2,16 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/main.dart';
import 'package:twonly/src/components/headline.dart'; import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/providers/notify_provider.dart'; import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/utils/api.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'; import 'package:twonly/src/views/register_view.dart';
// ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
class SearchUsernameView extends StatefulWidget { class SearchUsernameView extends StatefulWidget {
const SearchUsernameView({super.key}); const SearchUsernameView({super.key});
@ -25,24 +29,30 @@ class _SearchUsernameView extends State<SearchUsernameView> {
_isLoading = true; _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(() { setState(() {
_isLoading = false; _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 @override
@ -94,7 +104,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
), ),
SizedBox(height: 30), SizedBox(height: 30),
if (context if (context
.read<NotifyProvider>() .read<ContactChangeProvider>()
.allContacts .allContacts
.where((contact) => !contact.accepted) .where((contact) => !contact.accepted)
.isNotEmpty) .isNotEmpty)
@ -122,6 +132,8 @@ class _SearchUsernameView extends State<SearchUsernameView> {
} }
class ContactsListView extends StatefulWidget { class ContactsListView extends StatefulWidget {
const ContactsListView({super.key});
@override @override
State<ContactsListView> createState() => _ContactsListViewState(); State<ContactsListView> createState() => _ContactsListViewState();
} }
@ -130,7 +142,7 @@ class _ContactsListViewState extends State<ContactsListView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Contact> contacts = context List<Contact> contacts = context
.read<NotifyProvider>() .read<ContactChangeProvider>()
.allContacts .allContacts
.where((contact) => !contact.accepted) .where((contact) => !contact.accepted)
.toList(); .toList();
@ -143,9 +155,9 @@ class _ContactsListViewState extends State<ContactsListView> {
leading: InitialsAvatar(displayName: contact.displayName), leading: InitialsAvatar(displayName: contact.displayName),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: (!contact.requested) children: [
? [Text('Pending')] if (!contact.requested) Text('Pending'),
: [ if (contact.requested) ...[
Tooltip( Tooltip(
message: "Block the user without informing.", message: "Block the user without informing.",
child: IconButton( child: IconButton(
@ -153,9 +165,6 @@ class _ContactsListViewState extends State<ContactsListView> {
color: const Color.fromARGB(164, 244, 67, 54)), color: const Color.fromARGB(164, 244, 67, 54)),
onPressed: () async { onPressed: () async {
await DbContacts.blockUser(contact.userId.toInt()); 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), icon: Icon(Icons.close, color: Colors.red),
onPressed: () async { onPressed: () async {
await DbContacts.deleteUser(contact.userId.toInt()); await DbContacts.deleteUser(contact.userId.toInt());
if (context.mounted) { encryptAndSendMessage(
context.read<NotifyProvider>().update(); contact.userId,
} Message(
rejectUserRequest(contact.userId); kind: MessageKind.rejectRequest,
timestamp: DateTime.now(),
),
);
}, },
), ),
), ),
@ -176,13 +188,17 @@ class _ContactsListViewState extends State<ContactsListView> {
icon: Icon(Icons.check, color: Colors.green), icon: Icon(Icons.check, color: Colors.green),
onPressed: () async { onPressed: () async {
await DbContacts.acceptUser(contact.userId.toInt()); await DbContacts.acceptUser(contact.userId.toInt());
if (context.mounted) { encryptAndSendMessage(
context.read<NotifyProvider>().update(); contact.userId,
} Message(
acceptUserRequest(contact.userId); kind: MessageKind.acceptRequest,
timestamp: DateTime.now(),
),
);
}, },
), ),
], ],
],
), ),
); );
}, },

View file

@ -2,14 +2,11 @@ import 'dart:collection';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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/best_friends_selector.dart';
import 'package:twonly/src/components/headline.dart'; import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart'; import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
import 'package:twonly/src/providers/notify_provider.dart'; import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/utils/api.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/home_view.dart'; import 'package:twonly/src/views/home_view.dart';
@ -112,21 +109,12 @@ class _ShareImageView extends State<ShareImageView> {
FilledButton.icon( FilledButton.icon(
icon: Icon(Icons.send), icon: Icon(Icons.send),
onPressed: () async { onPressed: () async {
for (Int64 a in _selectedUserIds) { sendImage(_selectedUserIds.toList(), widget.image);
addSendingTo(a);
}
sendImage(
context,
_users
.where((c) => _selectedUserIds.contains(c.userId))
.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);
Navigator.pop(context); Navigator.pop(context);
context.read<NotifyProvider>().setActivePageIdx(0); globalUpdateOfHomeViewPageIndex(0);
homeViewPageController.jumpToPage(0);
}, },
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>( padding: WidgetStateProperty.all<EdgeInsets>(