mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +00:00
clean code
This commit is contained in:
parent
2f3af42771
commit
5cea69c224
22 changed files with 941 additions and 717 deletions
|
|
@ -4,7 +4,8 @@ import 'package:twonly/src/providers/api_provider.dart';
|
||||||
import 'package:twonly/src/providers/db_provider.dart';
|
import 'package: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),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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), () {
|
||||||
|
|
|
||||||
|
|
@ -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.",
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
107
lib/src/providers/api/api.dart
Normal file
107
lib/src/providers/api/api.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:twonly/main.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
|
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
// ignore: library_prefixes
|
||||||
|
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||||
|
|
||||||
|
// this functions ensures that the message is received by the server and in case of errors will try again later
|
||||||
|
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||||
|
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
||||||
|
|
||||||
|
if (bytes == null) {
|
||||||
|
Logger("api.dart").shout("Error encryption message!");
|
||||||
|
return Result.error(ErrorCode.InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger("api.dart").shout(
|
||||||
|
"TODO: store encrypted message and send later again. STORE: userId, bytes and messageId");
|
||||||
|
|
||||||
|
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
||||||
|
|
||||||
|
if (resp.isSuccess) {
|
||||||
|
if (msg.messageId != null) {
|
||||||
|
DbMessages.acknowledgeMessageByServer(msg.messageId!);
|
||||||
|
}
|
||||||
|
// TODO: remove encrypted tmp file
|
||||||
|
} else {
|
||||||
|
// in case of error do nothing. As the message is not removed the app will try again when relaunched
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
||||||
|
int? messageId =
|
||||||
|
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
||||||
|
if (messageId == null) return;
|
||||||
|
|
||||||
|
Result res = await apiProvider.getUploadToken();
|
||||||
|
|
||||||
|
if (res.isError || !res.value.hasUploadtoken()) {
|
||||||
|
Logger("api.dart").shout("Error getting upload token!");
|
||||||
|
|
||||||
|
// TODO store message for later and try again
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> uploadToken = res.value.uploadtoken;
|
||||||
|
Logger("sendImageToSingleTarget").fine("Got token: $uploadToken");
|
||||||
|
|
||||||
|
MessageContent content =
|
||||||
|
MessageContent(text: null, downloadToken: uploadToken);
|
||||||
|
|
||||||
|
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
|
||||||
|
if (encryptBytes == null) {
|
||||||
|
await DbMessages.deleteMessageById(messageId);
|
||||||
|
Logger("api.dart").shout("Error encrypting image! Deleting image.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int>? imageToken =
|
||||||
|
await apiProvider.uploadData(uploadToken, encryptBytes);
|
||||||
|
if (imageToken == null) {
|
||||||
|
Logger("api.dart").shout("handle error uploading like saving...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("TODO: insert into DB and then create this MESSAGE");
|
||||||
|
|
||||||
|
Message msg = Message(
|
||||||
|
kind: MessageKind.image,
|
||||||
|
messageId: messageId,
|
||||||
|
content: content,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await encryptAndSendMessage(target, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future sendImage(List<Int64> userIds, String imagePath) async {
|
||||||
|
// 1. set notifier provider
|
||||||
|
|
||||||
|
File imageFile = File(imagePath);
|
||||||
|
|
||||||
|
Uint8List? imageBytes = await getCompressedImage(imageFile);
|
||||||
|
if (imageBytes == null) {
|
||||||
|
Logger("api.dart").shout("Error compressing image!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < userIds.length; i++) {
|
||||||
|
sendImageToSingleTarget(userIds[i], imageBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
||||||
|
print("check if free network connection");
|
||||||
|
|
||||||
|
print("Downloading: " + imageToken.toString());
|
||||||
|
}
|
||||||
42
lib/src/providers/api/api_utils.dart
Normal file
42
lib/src/providers/api/api_utils.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
||||||
|
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||||
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
|
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
||||||
|
|
||||||
|
class Result<T, E> {
|
||||||
|
final T? value;
|
||||||
|
final E? error;
|
||||||
|
|
||||||
|
bool get isSuccess => value != null;
|
||||||
|
bool get isError => error != null;
|
||||||
|
|
||||||
|
Result.success(this.value) : error = null;
|
||||||
|
Result.error(this.error) : value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result asResult(server.ServerToClient? msg) {
|
||||||
|
if (msg == null) {
|
||||||
|
return Result.error(ErrorCode.InternalError);
|
||||||
|
}
|
||||||
|
if (msg.v0.response.hasOk()) {
|
||||||
|
return Result.success(msg.v0.response.ok);
|
||||||
|
} else {
|
||||||
|
return Result.error(msg.v0.response.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
||||||
|
var v0 = client.V0()
|
||||||
|
..seq = Int64(0)
|
||||||
|
..handshake = handshake;
|
||||||
|
return ClientToServer()..v0 = v0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientToServer createClientToServerFromApplicationData(
|
||||||
|
ApplicationData applicationData) {
|
||||||
|
var v0 = client.V0()
|
||||||
|
..seq = Int64(0)
|
||||||
|
..applicationdata = applicationData;
|
||||||
|
return ClientToServer()..v0 = v0;
|
||||||
|
}
|
||||||
99
lib/src/providers/api/server_messages.dart
Normal file
99
lib/src/providers/api/server_messages.dart
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:twonly/main.dart';
|
||||||
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
|
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
|
||||||
|
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||||
|
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
||||||
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
|
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
|
// ignore: library_prefixes
|
||||||
|
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||||
|
|
||||||
|
Future handleServerMessage(server.ServerToClient msg) async {
|
||||||
|
client.Response? response;
|
||||||
|
|
||||||
|
if (msg.v0.hasRequestNewPreKeys()) {
|
||||||
|
List<PreKeyRecord> localPreKeys = await SignalHelper.getPreKeys();
|
||||||
|
|
||||||
|
List<client.Response_PreKey> prekeysList = [];
|
||||||
|
for (int i = 0; i < localPreKeys.length; i++) {
|
||||||
|
prekeysList.add(client.Response_PreKey()
|
||||||
|
..id = Int64(localPreKeys[i].id)
|
||||||
|
..prekey = localPreKeys[i].getKeyPair().publicKey.serialize());
|
||||||
|
}
|
||||||
|
var prekeys = client.Response_Prekeys(prekeys: prekeysList);
|
||||||
|
var ok = client.Response_Ok()..prekeys = prekeys;
|
||||||
|
response = client.Response()..ok = ok;
|
||||||
|
} else if (msg.v0.hasNewMessage()) {
|
||||||
|
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
|
||||||
|
Int64 fromUserId = msg.v0.newMessage.fromUserId;
|
||||||
|
Message? message = await SignalHelper.getDecryptedText(fromUserId, body);
|
||||||
|
if (message != null) {
|
||||||
|
switch (message.kind) {
|
||||||
|
case MessageKind.contactRequest:
|
||||||
|
Result username = await apiProvider.getUsername(fromUserId);
|
||||||
|
if (username.isSuccess) {
|
||||||
|
Uint8List name = username.value.userdata.username;
|
||||||
|
DbContacts.insertNewContact(
|
||||||
|
utf8.decode(name), fromUserId.toInt(), true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageKind.rejectRequest:
|
||||||
|
DbContacts.deleteUser(fromUserId.toInt());
|
||||||
|
break;
|
||||||
|
case MessageKind.acceptRequest:
|
||||||
|
DbContacts.acceptUser(fromUserId.toInt());
|
||||||
|
break;
|
||||||
|
case MessageKind.ack:
|
||||||
|
DbMessages.acknowledgeMessageByUser(
|
||||||
|
fromUserId.toInt(), message.messageId!);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (message.kind != MessageKind.textMessage &&
|
||||||
|
message.kind != MessageKind.video &&
|
||||||
|
message.kind != MessageKind.image) {
|
||||||
|
Logger("handleServerMessages")
|
||||||
|
.shout("Got unknown MessageKind $message");
|
||||||
|
} else {
|
||||||
|
String content = jsonEncode(message.content!.toJson());
|
||||||
|
await DbMessages.insertOtherMessage(
|
||||||
|
fromUserId.toInt(), message.kind, message.messageId!, content);
|
||||||
|
|
||||||
|
encryptAndSendMessage(
|
||||||
|
fromUserId,
|
||||||
|
Message(
|
||||||
|
kind: MessageKind.ack,
|
||||||
|
messageId: message.messageId!,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (message.kind == MessageKind.video ||
|
||||||
|
message.kind == MessageKind.image) {
|
||||||
|
dynamic content = message.content!;
|
||||||
|
List<int> downloadToken = content.downloadToken;
|
||||||
|
tryDownloadMedia(downloadToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ok = client.Response_Ok()..none = true;
|
||||||
|
response = client.Response()..ok = ok;
|
||||||
|
} else {
|
||||||
|
Logger("handleServerMessage")
|
||||||
|
.shout("Got a new message from the server: $msg");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var v0 = client.V0()
|
||||||
|
..seq = msg.v0.seq
|
||||||
|
..response = response;
|
||||||
|
|
||||||
|
apiProvider.sendResponse(ClientToServer()..v0 = v0);
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart: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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
31
lib/src/providers/messages_change_provider.dart
Normal file
31
lib/src/providers/messages_change_provider.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
|
|
||||||
|
/// This provider does always contains the latest messages send or received
|
||||||
|
/// for every contact.
|
||||||
|
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
|
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
||||||
|
|
||||||
|
Map<int, DbMessage> get lastMessage => _lastMessage;
|
||||||
|
|
||||||
|
void updateLastMessageFor(int targetUserId) async {
|
||||||
|
DbMessage? last =
|
||||||
|
await DbMessages.getLastMessagesForPreviewForUser(targetUserId);
|
||||||
|
if (last != null) {
|
||||||
|
_lastMessage[last.otherUserId] = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() async {
|
||||||
|
// load everything from the database
|
||||||
|
List<Contact> allContacts = await DbContacts.getUsers();
|
||||||
|
for (Contact contact in allContacts) {
|
||||||
|
DbMessage? last = await DbMessages.getLastMessagesForPreviewForUser(
|
||||||
|
contact.userId.toInt());
|
||||||
|
if (last != null) {
|
||||||
|
_lastMessage[last.otherUserId] = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:fixnum/fixnum.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:twonly/main.dart';
|
|
||||||
import 'package:twonly/src/app.dart';
|
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
|
||||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
|
||||||
import 'package:twonly/src/providers/notify_provider.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
// ignore: library_prefixes
|
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
|
||||||
import 'package:twonly/src/model/json/user_data.dart';
|
|
||||||
|
|
||||||
Future<bool> addNewContact(String username) async {
|
|
||||||
final res = await apiProvider.getUserData(username);
|
|
||||||
|
|
||||||
if (res.isSuccess) {
|
|
||||||
bool added = await DbContacts.insertNewContact(
|
|
||||||
username, res.value.userdata.userId.toInt(), false);
|
|
||||||
|
|
||||||
if (!added) {
|
|
||||||
print("RETURN FALSE HIER!!!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await SignalHelper.addNewContact(res.value.userdata)) {
|
|
||||||
Message msg =
|
|
||||||
Message(kind: MessageKind.contactRequest, timestamp: DateTime.now());
|
|
||||||
|
|
||||||
Uint8List? bytes =
|
|
||||||
await SignalHelper.encryptMessage(msg, res.value.userdata.userId);
|
|
||||||
|
|
||||||
if (bytes == null) {
|
|
||||||
Logger("utils/api").shout("Error encryption message!");
|
|
||||||
return res.error(ErrorCode.InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result resp =
|
|
||||||
await apiProvider.sendTextMessage(res.value.userdata.userId, bytes);
|
|
||||||
|
|
||||||
return resp.isSuccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.isSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
|
||||||
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
|
||||||
|
|
||||||
if (bytes == null) {
|
|
||||||
Logger("utils/api").shout("Error encryption message!");
|
|
||||||
return Result.error(ErrorCode.InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
|
||||||
|
|
||||||
if (resp.isError) {
|
|
||||||
// TODO:
|
|
||||||
Logger("encryptAndSendMessage")
|
|
||||||
.shout("handle errors here and store them in the database");
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> rejectUserRequest(Int64 userId) async {
|
|
||||||
Message msg =
|
|
||||||
Message(kind: MessageKind.rejectRequest, timestamp: DateTime.now());
|
|
||||||
return encryptAndSendMessage(userId, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> acceptUserRequest(Int64 userId) async {
|
|
||||||
Message msg =
|
|
||||||
Message(kind: MessageKind.acceptRequest, timestamp: DateTime.now());
|
|
||||||
return encryptAndSendMessage(userId, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> createNewUser(String username, String inviteCode) async {
|
|
||||||
final storage = getSecureStorage();
|
|
||||||
|
|
||||||
await SignalHelper.createIfNotExistsSignalIdentity();
|
|
||||||
|
|
||||||
final res = await apiProvider.register(username, inviteCode);
|
|
||||||
|
|
||||||
if (res.isSuccess) {
|
|
||||||
Logger("create_new_user").info("Got user_id ${res.value} from server");
|
|
||||||
final userData = UserData(
|
|
||||||
userId: res.value.userid, username: username, displayName: username);
|
|
||||||
storage.write(key: "user_data", value: jsonEncode(userData));
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendImageToSingleTarget(
|
|
||||||
BuildContext context, Int64 target, Uint8List encryptBytes) async {
|
|
||||||
Result res = await apiProvider.getUploadToken(encryptBytes.length);
|
|
||||||
|
|
||||||
if (res.isError || !res.value.hasUploadtoken()) {
|
|
||||||
Logger("api.dart").shout("Error getting upload token!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
List<int> uploadToken = res.value.uploadtoken;
|
|
||||||
Logger("sendImageToSingleTarget").info("Got token: $uploadToken");
|
|
||||||
|
|
||||||
MessageContent content =
|
|
||||||
MessageContent(text: null, downloadToken: uploadToken);
|
|
||||||
int? messageId = await DbMessages.insertMyMessage(
|
|
||||||
target.toInt(), MessageKind.image, jsonEncode(content.toJson()));
|
|
||||||
if (messageId == null) return;
|
|
||||||
|
|
||||||
updateNotifyProvider();
|
|
||||||
|
|
||||||
List<int>? imageToken =
|
|
||||||
await apiProvider.uploadData(uploadToken, encryptBytes);
|
|
||||||
if (imageToken == null) {
|
|
||||||
Logger("api.dart").shout("handle error uploading like saving...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("TODO: insert into DB and then create this MESSAGE");
|
|
||||||
|
|
||||||
Message msg = Message(
|
|
||||||
kind: MessageKind.image,
|
|
||||||
messageId: messageId,
|
|
||||||
content: content,
|
|
||||||
timestamp: DateTime.now(),
|
|
||||||
);
|
|
||||||
|
|
||||||
await encryptAndSendMessage(target, msg);
|
|
||||||
removeSendingTo(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future sendImage(
|
|
||||||
BuildContext context, List<Contact> users, String imagePath) async {
|
|
||||||
// 1. set notifier provider
|
|
||||||
|
|
||||||
File imageFile = File(imagePath);
|
|
||||||
|
|
||||||
Uint8List? imageBytes = await getCompressedImage(imageFile);
|
|
||||||
if (imageBytes == null) {
|
|
||||||
Logger("api.dart").shout("Error compressing image!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < users.length; i++) {
|
|
||||||
Int64 target = users[i].userId;
|
|
||||||
Uint8List? encryptedImage =
|
|
||||||
await SignalHelper.encryptBytes(imageBytes, target);
|
|
||||||
if (encryptedImage == null) {
|
|
||||||
Logger("api.dart").shout("Error encrypting image!");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sendImageToSingleTarget(context, target, encryptedImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
|
||||||
print("check if free network connection");
|
|
||||||
|
|
||||||
print("Downloading: " + imageToken.toString());
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart: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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue