mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
follow request
This commit is contained in:
parent
b06c2e1cc8
commit
20c20eb1e1
19 changed files with 533 additions and 197 deletions
|
|
@ -36,10 +36,10 @@ void main() async {
|
|||
|
||||
var apiUrl = "ws://api.twonly.eu/api/client";
|
||||
var backupApiUrl = "ws://api2.twonly.eu/api/client";
|
||||
if (!kReleaseMode) {
|
||||
// Overwrite the domain in your local network so you can test the app locally
|
||||
apiUrl = "ws://10.99.0.6:3030/api/client";
|
||||
}
|
||||
// if (!kReleaseMode) {
|
||||
// Overwrite the domain in your local network so you can test the app locally
|
||||
apiUrl = "ws://10.99.0.6:3030/api/client";
|
||||
// }
|
||||
|
||||
apiProvider = ApiProvider(apiUrl: apiUrl, backupApiUrl: backupApiUrl);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"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.",
|
||||
"errorTooManyRequests": "You have made too many requests in a short period. Please wait a moment before trying again.",
|
||||
"errorInternalError": "The server encountered an internal error. Please try again later.",
|
||||
"errorInternalError": "The server is currently not available. Please try again later.",
|
||||
"errorInvalidInvitationCode": "The invitation code you provided is invalid. Please check the code and try again.",
|
||||
"errorUsernameAlreadyTaken": "The username you want to use is already taken. Please choose a different username.",
|
||||
"errorSignatureNotValid": "The provided signature is not valid. Please check your credentials and try again.",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
import 'package:cv/cv.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/main.dart';
|
||||
|
||||
class Contact {
|
||||
Contact({required this.userId, required this.displayName});
|
||||
Contact(
|
||||
{required this.userId,
|
||||
required this.displayName,
|
||||
required this.accepted,
|
||||
required this.requested});
|
||||
final Int64 userId;
|
||||
final String displayName;
|
||||
final bool accepted;
|
||||
final bool requested;
|
||||
}
|
||||
|
||||
class DbContacts extends CvModelBase {
|
||||
|
|
@ -17,6 +24,12 @@ class DbContacts extends CvModelBase {
|
|||
static const columnDisplayName = "display_name";
|
||||
final displayName = CvField<String>(columnDisplayName);
|
||||
|
||||
static const columnAccepted = "accepted";
|
||||
final accepted = CvField<int>(columnAccepted);
|
||||
|
||||
static const columnRequested = "requested";
|
||||
final requested = CvField<int>(columnRequested);
|
||||
|
||||
static const columnCreatedAt = "created_at";
|
||||
final createdAt = CvField<DateTime>(columnCreatedAt);
|
||||
|
||||
|
|
@ -25,25 +38,60 @@ class DbContacts extends CvModelBase {
|
|||
CREATE TABLE $tableName (
|
||||
$columnUserId INTEGER NOT NULL PRIMARY KEY,
|
||||
$columnDisplayName TEXT,
|
||||
$columnAccepted INT NOT NULL DEFAULT 0,
|
||||
$columnRequested INT NOT NULL DEFAULT 0,
|
||||
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
static Future<List<Contact>> getUsers() async {
|
||||
var users = await dbProvider.db!.query(tableName,
|
||||
columns: [columnUserId, columnDisplayName, columnCreatedAt]);
|
||||
if (users.isEmpty) return [];
|
||||
@override
|
||||
List<CvField> get fields =>
|
||||
[userId, displayName, accepted, requested, createdAt];
|
||||
|
||||
List<Contact> parsedUsers = [];
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
parsedUsers.add(Contact(
|
||||
userId: Int64(users.cast()[i][columnUserId]),
|
||||
displayName: users.cast()[i][columnDisplayName]));
|
||||
static Future<List<Contact>> getUsers() async {
|
||||
try {
|
||||
var users = await dbProvider.db!.query(tableName, columns: [
|
||||
columnUserId,
|
||||
columnDisplayName,
|
||||
columnAccepted,
|
||||
columnRequested,
|
||||
columnCreatedAt
|
||||
]);
|
||||
if (users.isEmpty) return [];
|
||||
|
||||
List<Contact> parsedUsers = [];
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
print(users[i]);
|
||||
parsedUsers.add(
|
||||
Contact(
|
||||
userId: Int64(users.cast()[i][columnUserId]),
|
||||
displayName: users.cast()[i][columnDisplayName],
|
||||
accepted: users[i][columnAccepted] == 1,
|
||||
requested: users[i][columnRequested] == 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
return parsedUsers;
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
return [];
|
||||
}
|
||||
return parsedUsers;
|
||||
}
|
||||
|
||||
@override
|
||||
List<CvField> get fields => [userId, createdAt];
|
||||
static Future<bool> insertNewContact(
|
||||
String username, int userId, bool requested) async {
|
||||
try {
|
||||
int a = requested ? 1 : 0;
|
||||
await dbProvider.db!.insert(DbContacts.tableName, {
|
||||
DbContacts.columnDisplayName: username,
|
||||
DbContacts.columnUserId: userId,
|
||||
DbContacts.columnRequested: a
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:twonly/src/utils/json.dart';
|
||||
part 'message.g.dart';
|
||||
|
||||
|
|
@ -14,23 +14,19 @@ class _MessageKind {
|
|||
@JsonSerializable()
|
||||
class Message {
|
||||
@Int64Converter()
|
||||
final Int64 fromUserId;
|
||||
final MessageKind kind;
|
||||
final MessageContent? content;
|
||||
DateTime timestamp;
|
||||
|
||||
Message(
|
||||
{required this.fromUserId,
|
||||
required this.kind,
|
||||
this.content,
|
||||
required this.timestamp});
|
||||
Message({required this.kind, this.content, required this.timestamp});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
|
||||
}
|
||||
|
||||
Message fromJson(Map<String, dynamic> json) {
|
||||
static Message fromJson(String jsonString) {
|
||||
Map<String, dynamic> json = jsonDecode(jsonString);
|
||||
dynamic content;
|
||||
MessageKind kind = $enumDecode(_$MessageKindEnumMap, json['kind']);
|
||||
switch (kind) {
|
||||
|
|
@ -44,22 +40,19 @@ class Message {
|
|||
}
|
||||
|
||||
return Message(
|
||||
fromUserId: const Int64Converter().fromJson(json['fromUserId'] as String),
|
||||
kind: kind,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson(Message instance) {
|
||||
String toJson() {
|
||||
var json = <String, dynamic>{
|
||||
'fromUserId': const Int64Converter().toJson(instance.fromUserId),
|
||||
'kind': _$MessageKindEnumMap[instance.kind]!,
|
||||
'timestamp': instance.timestamp.toIso8601String(),
|
||||
'content': instance.content
|
||||
'kind': _$MessageKindEnumMap[kind]!,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'content': content
|
||||
};
|
||||
|
||||
return json;
|
||||
return jsonEncode(json);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ const _$MessageKindEnumMap = {
|
|||
};
|
||||
|
||||
Message _$MessageFromJson(Map<String, dynamic> json) => Message(
|
||||
fromUserId: const Int64Converter().fromJson(json['fromUserId'] as String),
|
||||
kind: $enumDecode(_$MessageKindEnumMap, json['kind']),
|
||||
content: json['content'] == null
|
||||
? null
|
||||
|
|
@ -31,7 +30,6 @@ Message _$MessageFromJson(Map<String, dynamic> json) => Message(
|
|||
);
|
||||
|
||||
Map<String, dynamic> _$MessageToJson(Message instance) => <String, dynamic>{
|
||||
'fromUserId': const Int64Converter().toJson(instance.fromUserId),
|
||||
'kind': _$MessageKindEnumMap[instance.kind]!,
|
||||
'content': instance.content,
|
||||
'timestamp': instance.timestamp.toIso8601String(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const String dbName = 'twonly.db';
|
||||
|
||||
const int kVersion1 = 1;
|
||||
const int kVersion1 = 3;
|
||||
|
||||
String tableLibSignal = 'LibSignal';
|
||||
|
|
|
|||
|
|
@ -641,6 +641,56 @@ class ApplicationData_GetUserByUsername extends $pb.GeneratedMessage {
|
|||
void clearUsername() => clearField(1);
|
||||
}
|
||||
|
||||
class ApplicationData_GetUserById extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_GetUserById({
|
||||
$fixnum.Int64? userId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (userId != null) {
|
||||
$result.userId = userId;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ApplicationData_GetUserById._() : super();
|
||||
factory ApplicationData_GetUserById.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ApplicationData_GetUserById.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData.GetUserById', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'userId')
|
||||
..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_GetUserById clone() => ApplicationData_GetUserById()..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_GetUserById copyWith(void Function(ApplicationData_GetUserById) updates) => super.copyWith((message) => updates(message as ApplicationData_GetUserById)) as ApplicationData_GetUserById;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_GetUserById create() => ApplicationData_GetUserById._();
|
||||
ApplicationData_GetUserById createEmptyInstance() => create();
|
||||
static $pb.PbList<ApplicationData_GetUserById> createRepeated() => $pb.PbList<ApplicationData_GetUserById>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_GetUserById getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ApplicationData_GetUserById>(create);
|
||||
static ApplicationData_GetUserById? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get userId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set userId($fixnum.Int64 v) { $_setInt64(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasUserId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearUserId() => clearField(1);
|
||||
}
|
||||
|
||||
class ApplicationData_GetPrekeysByUserId extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_GetPrekeysByUserId({
|
||||
$fixnum.Int64? userId,
|
||||
|
|
@ -825,6 +875,7 @@ enum ApplicationData_ApplicationData {
|
|||
getprekeysbyuserid,
|
||||
getuploadtoken,
|
||||
uploaddata,
|
||||
getuserbyid,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -835,6 +886,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
ApplicationData_GetPrekeysByUserId? getprekeysbyuserid,
|
||||
ApplicationData_GetUploadToken? getuploadtoken,
|
||||
ApplicationData_UploadData? uploaddata,
|
||||
ApplicationData_GetUserById? getuserbyid,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (textmessage != null) {
|
||||
|
|
@ -852,6 +904,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
if (uploaddata != null) {
|
||||
$result.uploaddata = uploaddata;
|
||||
}
|
||||
if (getuserbyid != null) {
|
||||
$result.getuserbyid = getuserbyid;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ApplicationData._() : super();
|
||||
|
|
@ -864,15 +919,17 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
3 : ApplicationData_ApplicationData.getprekeysbyuserid,
|
||||
4 : ApplicationData_ApplicationData.getuploadtoken,
|
||||
5 : ApplicationData_ApplicationData.uploaddata,
|
||||
6 : ApplicationData_ApplicationData.getuserbyid,
|
||||
0 : ApplicationData_ApplicationData.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ApplicationData', package: const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), createEmptyInstance: create)
|
||||
..oo(0, [1, 2, 3, 4, 5])
|
||||
..oo(0, [1, 2, 3, 4, 5, 6])
|
||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textmessage', subBuilder: ApplicationData_TextMessage.create)
|
||||
..aOM<ApplicationData_GetUserByUsername>(2, _omitFieldNames ? '' : 'getuserbyusername', subBuilder: ApplicationData_GetUserByUsername.create)
|
||||
..aOM<ApplicationData_GetPrekeysByUserId>(3, _omitFieldNames ? '' : 'getprekeysbyuserid', subBuilder: ApplicationData_GetPrekeysByUserId.create)
|
||||
..aOM<ApplicationData_GetUploadToken>(4, _omitFieldNames ? '' : 'getuploadtoken', subBuilder: ApplicationData_GetUploadToken.create)
|
||||
..aOM<ApplicationData_UploadData>(5, _omitFieldNames ? '' : 'uploaddata', subBuilder: ApplicationData_UploadData.create)
|
||||
..aOM<ApplicationData_GetUserById>(6, _omitFieldNames ? '' : 'getuserbyid', subBuilder: ApplicationData_GetUserById.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -954,6 +1011,17 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
void clearUploaddata() => clearField(5);
|
||||
@$pb.TagNumber(5)
|
||||
ApplicationData_UploadData ensureUploaddata() => $_ensure(4);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
ApplicationData_GetUserById get getuserbyid => $_getN(5);
|
||||
@$pb.TagNumber(6)
|
||||
set getuserbyid(ApplicationData_GetUserById v) { setField(6, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasGetuserbyid() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearGetuserbyid() => clearField(6);
|
||||
@$pb.TagNumber(6)
|
||||
ApplicationData_GetUserById ensureGetuserbyid() => $_ensure(5);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
|
|
|
|||
|
|
@ -117,11 +117,12 @@ const ApplicationData$json = {
|
|||
'2': [
|
||||
{'1': 'textmessage', '3': 1, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.TextMessage', '9': 0, '10': 'textmessage'},
|
||||
{'1': 'getuserbyusername', '3': 2, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUserByUsername', '9': 0, '10': 'getuserbyusername'},
|
||||
{'1': 'getuserbyid', '3': 6, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUserById', '9': 0, '10': 'getuserbyid'},
|
||||
{'1': 'getprekeysbyuserid', '3': 3, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetPrekeysByUserId', '9': 0, '10': 'getprekeysbyuserid'},
|
||||
{'1': 'getuploadtoken', '3': 4, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.GetUploadToken', '9': 0, '10': 'getuploadtoken'},
|
||||
{'1': 'uploaddata', '3': 5, '4': 1, '5': 11, '6': '.client_to_server.ApplicationData.UploadData', '9': 0, '10': 'uploaddata'},
|
||||
],
|
||||
'3': [ApplicationData_TextMessage$json, ApplicationData_GetUserByUsername$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],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
],
|
||||
|
|
@ -144,6 +145,14 @@ const ApplicationData_GetUserByUsername$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetUserById$json = {
|
||||
'1': 'GetUserById',
|
||||
'2': [
|
||||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetPrekeysByUserId$json = {
|
||||
'1': 'GetPrekeysByUserId',
|
||||
|
|
@ -175,18 +184,20 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dG1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||
'VyLkFwcGxpY2F0aW9uRGF0YS5UZXh0TWVzc2FnZUgAUgt0ZXh0bWVzc2FnZRJjChFnZXR1c2Vy'
|
||||
'Ynl1c2VybmFtZRgCIAEoCzIzLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldF'
|
||||
'VzZXJCeVVzZXJuYW1lSABSEWdldHVzZXJieXVzZXJuYW1lEmYKEmdldHByZWtleXNieXVzZXJp'
|
||||
'ZBgDIAEoCzI0LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldFByZWtleXNCeV'
|
||||
'VzZXJJZEgAUhJnZXRwcmVrZXlzYnl1c2VyaWQSWgoOZ2V0dXBsb2FkdG9rZW4YBCABKAsyMC5j'
|
||||
'bGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRVcGxvYWRUb2tlbkgAUg5nZXR1cG'
|
||||
'xvYWR0b2tlbhJOCgp1cGxvYWRkYXRhGAUgASgLMiwuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNh'
|
||||
'dGlvbkRhdGEuVXBsb2FkRGF0YUgAUgp1cGxvYWRkYXRhGjoKC1RleHRNZXNzYWdlEhcKB3VzZX'
|
||||
'JfaWQYASABKANSBnVzZXJJZBISCgRib2R5GAMgASgMUgRib2R5Gi8KEUdldFVzZXJCeVVzZXJu'
|
||||
'YW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2VybmFtZRotChJHZXRQcmVrZXlzQnlVc2VySWQSFw'
|
||||
'oHdXNlcl9pZBgBIAEoA1IGdXNlcklkGiIKDkdldFVwbG9hZFRva2VuEhAKA2xlbhgBIAEoDVID'
|
||||
'bGVuGlsKClVwbG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhIWCg'
|
||||
'ZvZmZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRhQhEKD0FwcGxpY2F0aW9u'
|
||||
'RGF0YQ==');
|
||||
'VzZXJCeVVzZXJuYW1lSABSEWdldHVzZXJieXVzZXJuYW1lElEKC2dldHVzZXJieWlkGAYgASgL'
|
||||
'Mi0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0VXNlckJ5SWRIAFILZ2V0dX'
|
||||
'NlcmJ5aWQSZgoSZ2V0cHJla2V5c2J5dXNlcmlkGAMgASgLMjQuY2xpZW50X3RvX3NlcnZlci5B'
|
||||
'cHBsaWNhdGlvbkRhdGEuR2V0UHJla2V5c0J5VXNlcklkSABSEmdldHByZWtleXNieXVzZXJpZB'
|
||||
'JaCg5nZXR1cGxvYWR0b2tlbhgEIAEoCzIwLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25E'
|
||||
'YXRhLkdldFVwbG9hZFRva2VuSABSDmdldHVwbG9hZHRva2VuEk4KCnVwbG9hZGRhdGEYBSABKA'
|
||||
'syLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5VcGxvYWREYXRhSABSCnVwbG9h'
|
||||
'ZGRhdGEaOgoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAy'
|
||||
'ABKAxSBGJvZHkaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJu'
|
||||
'YW1lGiYKC0dldFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBotChJHZXRQcmVrZX'
|
||||
'lzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGiIKDkdldFVwbG9hZFRva2VuEhAK'
|
||||
'A2xlbhgBIAEoDVIDbGVuGlsKClVwbG9hZERhdGESIQoMdXBsb2FkX3Rva2VuGAEgASgMUgt1cG'
|
||||
'xvYWRUb2tlbhIWCgZvZmZzZXQYAiABKA1SBm9mZnNldBISCgRkYXRhGAMgASgMUgRkYXRhQhEK'
|
||||
'D0FwcGxpY2F0aW9uRGF0YQ==');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
|
|||
|
|
@ -197,11 +197,15 @@ class V0 extends $pb.GeneratedMessage {
|
|||
class NewMessage extends $pb.GeneratedMessage {
|
||||
factory NewMessage({
|
||||
$core.List<$core.int>? body,
|
||||
$fixnum.Int64? fromUserId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (body != null) {
|
||||
$result.body = body;
|
||||
}
|
||||
if (fromUserId != null) {
|
||||
$result.fromUserId = fromUserId;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
NewMessage._() : super();
|
||||
|
|
@ -210,6 +214,7 @@ class NewMessage extends $pb.GeneratedMessage {
|
|||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'NewMessage', package: const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'), createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'body', $pb.PbFieldType.OY)
|
||||
..aInt64(2, _omitFieldNames ? '' : 'fromUserId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -242,6 +247,15 @@ class NewMessage extends $pb.GeneratedMessage {
|
|||
$core.bool hasBody() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearBody() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$fixnum.Int64 get fromUserId => $_getI64(1);
|
||||
@$pb.TagNumber(2)
|
||||
set fromUserId($fixnum.Int64 v) { $_setInt64(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasFromUserId() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearFromUserId() => clearField(2);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
|
|
@ -316,6 +330,7 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
$core.List<$core.int>? signedPrekey,
|
||||
$core.List<$core.int>? signedPrekeySignature,
|
||||
$fixnum.Int64? signedPrekeyId,
|
||||
$core.List<$core.int>? username,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (userId != null) {
|
||||
|
|
@ -336,6 +351,9 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
if (signedPrekeyId != null) {
|
||||
$result.signedPrekeyId = signedPrekeyId;
|
||||
}
|
||||
if (username != null) {
|
||||
$result.username = username;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
Response_UserData._() : super();
|
||||
|
|
@ -349,6 +367,7 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'signedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'signedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..aInt64(6, _omitFieldNames ? '' : 'signedPrekeyId')
|
||||
..a<$core.List<$core.int>>(7, _omitFieldNames ? '' : 'username', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -420,6 +439,15 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
$core.bool hasSignedPrekeyId() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearSignedPrekeyId() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.List<$core.int> get username => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set username($core.List<$core.int> v) { $_setBytes(6, v); }
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasUsername() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearUsername() => clearField(7);
|
||||
}
|
||||
|
||||
enum Response_Ok_Ok {
|
||||
|
|
|
|||
|
|
@ -54,13 +54,15 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
|||
const NewMessage$json = {
|
||||
'1': 'NewMessage',
|
||||
'2': [
|
||||
{'1': 'from_user_id', '3': 2, '4': 1, '5': 3, '10': 'fromUserId'},
|
||||
{'1': 'body', '3': 1, '4': 1, '5': 12, '10': 'body'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `NewMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List newMessageDescriptor = $convert.base64Decode(
|
||||
'CgpOZXdNZXNzYWdlEhIKBGJvZHkYASABKAxSBGJvZHk=');
|
||||
'CgpOZXdNZXNzYWdlEiAKDGZyb21fdXNlcl9pZBgCIAEoA1IKZnJvbVVzZXJJZBISCgRib2R5GA'
|
||||
'EgASgMUgRib2R5');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
@ -90,12 +92,14 @@ const Response_UserData$json = {
|
|||
'2': [
|
||||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{'1': 'prekeys', '3': 2, '4': 3, '5': 11, '6': '.server_to_client.Response.PreKey', '10': 'prekeys'},
|
||||
{'1': 'public_identity_key', '3': 3, '4': 1, '5': 12, '9': 0, '10': 'publicIdentityKey', '17': true},
|
||||
{'1': 'signed_prekey', '3': 4, '4': 1, '5': 12, '9': 1, '10': 'signedPrekey', '17': true},
|
||||
{'1': 'signed_prekey_signature', '3': 5, '4': 1, '5': 12, '9': 2, '10': 'signedPrekeySignature', '17': true},
|
||||
{'1': 'signed_prekey_id', '3': 6, '4': 1, '5': 3, '9': 3, '10': 'signedPrekeyId', '17': true},
|
||||
{'1': 'username', '3': 7, '4': 1, '5': 12, '9': 0, '10': 'username', '17': true},
|
||||
{'1': 'public_identity_key', '3': 3, '4': 1, '5': 12, '9': 1, '10': 'publicIdentityKey', '17': true},
|
||||
{'1': 'signed_prekey', '3': 4, '4': 1, '5': 12, '9': 2, '10': 'signedPrekey', '17': true},
|
||||
{'1': 'signed_prekey_signature', '3': 5, '4': 1, '5': 12, '9': 3, '10': 'signedPrekeySignature', '17': true},
|
||||
{'1': 'signed_prekey_id', '3': 6, '4': 1, '5': 3, '9': 4, '10': 'signedPrekeyId', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_username'},
|
||||
{'1': '_public_identity_key'},
|
||||
{'1': '_signed_prekey'},
|
||||
{'1': '_signed_prekey_signature'},
|
||||
|
|
@ -122,16 +126,17 @@ const Response_Ok$json = {
|
|||
final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
||||
'CghSZXNwb25zZRIvCgJvaxgBIAEoCzIdLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuT2tIAF'
|
||||
'ICb2sSKAoFZXJyb3IYAiABKA4yEC5lcnJvci5FcnJvckNvZGVIAFIFZXJyb3IaMAoGUHJlS2V5'
|
||||
'Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkYAiABKAxSBnByZWtleRqGAwoIVXNlckRhdGESFw'
|
||||
'Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkYAiABKAxSBnByZWtleRq0AwoIVXNlckRhdGESFw'
|
||||
'oHdXNlcl9pZBgBIAEoA1IGdXNlcklkEjsKB3ByZWtleXMYAiADKAsyIS5zZXJ2ZXJfdG9fY2xp'
|
||||
'ZW50LlJlc3BvbnNlLlByZUtleVIHcHJla2V5cxIzChNwdWJsaWNfaWRlbnRpdHlfa2V5GAMgAS'
|
||||
'gMSABSEXB1YmxpY0lkZW50aXR5S2V5iAEBEigKDXNpZ25lZF9wcmVrZXkYBCABKAxIAVIMc2ln'
|
||||
'bmVkUHJla2V5iAEBEjsKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgASgMSAJSFXNpZ25lZF'
|
||||
'ByZWtleVNpZ25hdHVyZYgBARItChBzaWduZWRfcHJla2V5X2lkGAYgASgDSANSDnNpZ25lZFBy'
|
||||
'ZWtleUlkiAEBQhYKFF9wdWJsaWNfaWRlbnRpdHlfa2V5QhAKDl9zaWduZWRfcHJla2V5QhoKGF'
|
||||
'9zaWduZWRfcHJla2V5X3NpZ25hdHVyZUITChFfc2lnbmVkX3ByZWtleV9pZBrBAQoCT2sSFAoE'
|
||||
'Tm9uZRgBIAEoCEgAUgROb25lEhgKBnVzZXJpZBgCIAEoA0gAUgZ1c2VyaWQSHgoJY2hhbGxlbm'
|
||||
'dlGAMgASgMSABSCWNoYWxsZW5nZRIiCgt1cGxvYWR0b2tlbhgEIAEoDEgAUgt1cGxvYWR0b2tl'
|
||||
'bhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuVXNlckRhdG'
|
||||
'FIAFIIdXNlcmRhdGFCBAoCT2tCCgoIUmVzcG9uc2U=');
|
||||
'ZW50LlJlc3BvbnNlLlByZUtleVIHcHJla2V5cxIfCgh1c2VybmFtZRgHIAEoDEgAUgh1c2Vybm'
|
||||
'FtZYgBARIzChNwdWJsaWNfaWRlbnRpdHlfa2V5GAMgASgMSAFSEXB1YmxpY0lkZW50aXR5S2V5'
|
||||
'iAEBEigKDXNpZ25lZF9wcmVrZXkYBCABKAxIAlIMc2lnbmVkUHJla2V5iAEBEjsKF3NpZ25lZF'
|
||||
'9wcmVrZXlfc2lnbmF0dXJlGAUgASgMSANSFXNpZ25lZFByZWtleVNpZ25hdHVyZYgBARItChBz'
|
||||
'aWduZWRfcHJla2V5X2lkGAYgASgDSARSDnNpZ25lZFByZWtleUlkiAEBQgsKCV91c2VybmFtZU'
|
||||
'IWChRfcHVibGljX2lkZW50aXR5X2tleUIQCg5fc2lnbmVkX3ByZWtleUIaChhfc2lnbmVkX3By'
|
||||
'ZWtleV9zaWduYXR1cmVCEwoRX3NpZ25lZF9wcmVrZXlfaWQawQEKAk9rEhQKBE5vbmUYASABKA'
|
||||
'hIAFIETm9uZRIYCgZ1c2VyaWQYAiABKANIAFIGdXNlcmlkEh4KCWNoYWxsZW5nZRgDIAEoDEgA'
|
||||
'UgljaGFsbGVuZ2USIgoLdXBsb2FkdG9rZW4YBCABKAxIAFILdXBsb2FkdG9rZW4SQQoIdXNlcm'
|
||||
'RhdGEYBSABKAsyIy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVzZXJEYXRhSABSCHVzZXJk'
|
||||
'YXRhQgQKAk9rQgoKCFJlc3BvbnNl');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/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';
|
||||
|
|
@ -127,7 +131,6 @@ class ApiProvider {
|
|||
messagesV0[msg.v0.seq] = msg;
|
||||
} else {
|
||||
_handleServerMessage(msg);
|
||||
log.shout("Got a new message from the server: $msg");
|
||||
}
|
||||
} catch (e) {
|
||||
log.shout("Error parsing the servers message: $e");
|
||||
|
|
@ -137,7 +140,7 @@ class ApiProvider {
|
|||
Future _handleServerMessage(server.ServerToClient msg) async {
|
||||
client.Response? response;
|
||||
|
||||
if (msg.v0.requestNewPreKeys) {
|
||||
if (msg.v0.hasRequestNewPreKeys()) {
|
||||
List<PreKeyRecord> localPreKeys = await SignalHelper.getPreKeys();
|
||||
|
||||
List<client.Response_PreKey> prekeysList = [];
|
||||
|
|
@ -149,10 +152,27 @@ class ApiProvider {
|
|||
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) {
|
||||
Result username = await getUsername(fromUserId);
|
||||
if (username.isSuccess) {
|
||||
print(username.value);
|
||||
Uint8List name = username.value.userdata.username;
|
||||
DbContacts.insertNewContact(
|
||||
utf8.decode(name), fromUserId.toInt(), true);
|
||||
print(message);
|
||||
}
|
||||
}
|
||||
var ok = client.Response_Ok()..none = true;
|
||||
response = client.Response()..ok = ok;
|
||||
} else {
|
||||
log.shout("Got a new message from the server: $msg");
|
||||
return;
|
||||
}
|
||||
|
||||
if (response == null) return;
|
||||
|
||||
var v0 = client.V0()
|
||||
..seq = msg.v0.seq
|
||||
..response = response;
|
||||
|
|
@ -308,8 +328,7 @@ class ApiProvider {
|
|||
Future<Result> register(String username, String? inviteCode) async {
|
||||
final signalIdentity = await SignalHelper.getSignalIdentity();
|
||||
if (signalIdentity == null) {
|
||||
return Result.error(
|
||||
"There was an fatal error. Try reinstalling the app.");
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
|
||||
final signalStore =
|
||||
|
|
@ -336,7 +355,18 @@ class ApiProvider {
|
|||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error("Server is not reachable!");
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
}
|
||||
|
||||
Future<Result> getUsername(Int64 userId) async {
|
||||
var get = ApplicationData_GetUserById()..userId = userId;
|
||||
var appData = ApplicationData()..getuserbyid = get;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
}
|
||||
|
|
@ -348,7 +378,22 @@ class ApiProvider {
|
|||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error("Server is not reachable!");
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
}
|
||||
|
||||
Future<Result> sendTextMessage(Int64 target, Uint8List msg) async {
|
||||
var testMessage = ApplicationData_TextMessage()
|
||||
..userId = target
|
||||
..body = msg;
|
||||
|
||||
var appData = ApplicationData()..textmessage = testMessage;
|
||||
var req = createClientToServerFromApplicationData(appData);
|
||||
|
||||
final resp = await _sendRequestV0(req);
|
||||
if (resp == null) {
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
return _asResult(resp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class DbProvider {
|
|||
await _createDb(db);
|
||||
}, onUpgrade: (db, oldVersion, newVersion) async {
|
||||
if (oldVersion < kVersion1) {
|
||||
await _createDb(db);
|
||||
//await _createDb(db);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class ConnectPreKeyStore extends PreKeyStore {
|
|||
|
||||
@override
|
||||
Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
|
||||
if (await containsPreKey(preKeyId)) {
|
||||
if (!await containsPreKey(preKeyId)) {
|
||||
print(preKeyId);
|
||||
await dbProvider.db!.insert(DB.tableName,
|
||||
{DB.columnPreKeyId: preKeyId, DB.columnPreKey: record.serialize()});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class ConnectSessionStore extends SessionStore {
|
|||
@override
|
||||
Future<void> storeSession(
|
||||
SignalProtocolAddress address, SessionRecord record) async {
|
||||
if (await containsSession(address)) {
|
||||
if (!await containsSession(address)) {
|
||||
await dbProvider.db!.insert(DB.tableName, {
|
||||
DB.columnDeviceId: address.getDeviceId(),
|
||||
DB.columnName: address.getName(),
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/src/utils/signal.dart';
|
||||
import 'connect_sender_key_store.dart';
|
||||
import 'connect_signal_protocol_store.dart';
|
||||
|
||||
class SignalDataModel {
|
||||
Uint8List userId;
|
||||
ConnectSignalProtocolStore signalStore;
|
||||
ConnectSenderKeyStore senderKeyStore;
|
||||
SignalDataModel({
|
||||
required this.userId,
|
||||
required this.senderKeyStore,
|
||||
required this.signalStore,
|
||||
});
|
||||
// Session validation
|
||||
Future<Fingerprint?> generateSessionFingerPrint(String target) async {
|
||||
try {
|
||||
IdentityKey? targetIdentity = await signalStore
|
||||
.getIdentity(SignalProtocolAddress(target, defaultDeviceId));
|
||||
if (targetIdentity != null) {
|
||||
final generator = NumericFingerprintGenerator(5200);
|
||||
final localFingerprint = generator.createFor(
|
||||
1,
|
||||
userId,
|
||||
(await signalStore.getIdentityKeyPair()).getPublicKey(),
|
||||
Uint8List.fromList(utf8.encode(target)),
|
||||
targetIdentity,
|
||||
);
|
||||
return localFingerprint;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// PreKeyBundle preKeyBundleFromJson(Map<String, dynamic> remoteBundle) {
|
||||
|
||||
// }
|
||||
|
||||
Future<String?> getEncryptedText(String text, String target) async {
|
||||
try {
|
||||
SessionCipher session = SessionCipher.fromStore(
|
||||
signalStore, SignalProtocolAddress(target, defaultDeviceId));
|
||||
final ciphertext =
|
||||
await session.encrypt(Uint8List.fromList(utf8.encode(text)));
|
||||
Map<String, dynamic> data = {
|
||||
"msg": base64Encode(ciphertext.serialize()),
|
||||
"type": ciphertext.getType(),
|
||||
};
|
||||
return jsonEncode(data);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getDecryptedText(String source, String msg) async {
|
||||
try {
|
||||
SessionCipher session = SessionCipher.fromStore(
|
||||
signalStore, SignalProtocolAddress(source, defaultDeviceId));
|
||||
Map data = jsonDecode(msg);
|
||||
if (data["type"] == CiphertextMessage.prekeyType) {
|
||||
PreKeySignalMessage pre =
|
||||
PreKeySignalMessage(base64Decode(data["msg"]));
|
||||
Uint8List plaintext = await session.decrypt(pre);
|
||||
String dectext = utf8.decode(plaintext);
|
||||
return dectext;
|
||||
} else if (data["type"] == CiphertextMessage.whisperType) {
|
||||
SignalMessage signalMsg =
|
||||
SignalMessage.fromSerialized(base64Decode(data["msg"]));
|
||||
Uint8List plaintext = await session.decryptFromSignal(signalMsg);
|
||||
String dectext = utf8.decode(plaintext);
|
||||
return dectext;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.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/proto/api/error.pb.dart';
|
||||
import 'package:twonly/src/providers/api_provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
// ignore: library_prefixes
|
||||
|
|
@ -12,11 +15,30 @@ 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)) {
|
||||
await dbProvider.db!.insert(DbContacts.tableName, {
|
||||
DbContacts.columnDisplayName: username,
|
||||
DbContacts.columnUserId: res.value.userdata.userId.toInt()
|
||||
});
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.dart';
|
||||
import 'package:twonly/src/proto/api/server_to_client.pb.dart';
|
||||
import 'package:twonly/src/signal/connect_signal_protocol_store.dart';
|
||||
|
|
@ -86,13 +89,17 @@ Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
|||
}
|
||||
|
||||
Future<SignalIdentity?> getSignalIdentity() async {
|
||||
final storage = getSecureStorage();
|
||||
final signalIdentityJson = await storage.read(key: "signal_identity");
|
||||
if (signalIdentityJson == null) {
|
||||
try {
|
||||
final storage = getSecureStorage();
|
||||
final signalIdentityJson = await storage.read(key: "signal_identity");
|
||||
if (signalIdentityJson == null) {
|
||||
return null;
|
||||
}
|
||||
return SignalIdentity.fromJson(jsonDecode(signalIdentityJson));
|
||||
} catch (e) {
|
||||
Logger("signal.dart/getSignalIdentity").shout(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalIdentity.fromJson(jsonDecode(signalIdentityJson));
|
||||
}
|
||||
|
||||
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
||||
|
|
@ -140,3 +147,99 @@ Future createIfNotExistsSignalIdentity() async {
|
|||
await storage.write(
|
||||
key: "signal_identity", value: jsonEncode(storedSignalIdentity));
|
||||
}
|
||||
|
||||
// Future<Fingerprint?> generateSessionFingerPrint(String target) async {
|
||||
// try {
|
||||
// IdentityKey? targetIdentity = await signalStore
|
||||
// .getIdentity(SignalProtocolAddress(target, defaultDeviceId));
|
||||
// if (targetIdentity != null) {
|
||||
// final generator = NumericFingerprintGenerator(5200);
|
||||
// final localFingerprint = generator.createFor(
|
||||
// 1,
|
||||
// userId,
|
||||
// (await signalStore.getIdentityKeyPair()).getPublicKey(),
|
||||
// Uint8List.fromList(utf8.encode(target)),
|
||||
// targetIdentity,
|
||||
// );
|
||||
// return localFingerprint;
|
||||
// }
|
||||
// return null;
|
||||
// } catch (e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
Uint8List intToBytes(int value) {
|
||||
final byteData = ByteData(4);
|
||||
byteData.setInt32(0, value, Endian.big);
|
||||
return byteData.buffer.asUint8List();
|
||||
}
|
||||
|
||||
int bytesToInt(Uint8List bytes) {
|
||||
final byteData = ByteData.sublistView(bytes);
|
||||
return byteData.getInt32(0, Endian.big);
|
||||
}
|
||||
|
||||
List<Uint8List>? removeLastFourBytes(Uint8List original) {
|
||||
if (original.length < 4) {
|
||||
return null;
|
||||
}
|
||||
final newList = Uint8List(original.length - 4);
|
||||
newList.setAll(0, original.sublist(0, original.length - 4));
|
||||
|
||||
final lastFourBytes = original.sublist(original.length - 4);
|
||||
return [newList, lastFourBytes];
|
||||
}
|
||||
|
||||
Future<Uint8List?> encryptMessage(Message msg, Int64 target) async {
|
||||
try {
|
||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||
|
||||
SessionCipher session = SessionCipher.fromStore(
|
||||
signalStore, SignalProtocolAddress(target.toString(), defaultDeviceId));
|
||||
|
||||
final ciphertext = await session
|
||||
.encrypt(Uint8List.fromList(gzip.encode(utf8.encode(msg.toJson()))));
|
||||
|
||||
var b = BytesBuilder();
|
||||
b.add(ciphertext.serialize());
|
||||
b.add(intToBytes(ciphertext.getType()));
|
||||
|
||||
return b.takeBytes();
|
||||
} catch (e) {
|
||||
Logger("utils/signal").shout(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Message?> getDecryptedText(Int64 source, Uint8List msg) async {
|
||||
try {
|
||||
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
|
||||
|
||||
SessionCipher session = SessionCipher.fromStore(
|
||||
signalStore, SignalProtocolAddress(source.toString(), defaultDeviceId));
|
||||
|
||||
List<Uint8List>? msgs = removeLastFourBytes(msg);
|
||||
if (msgs == null) return null;
|
||||
Uint8List body = msgs[0];
|
||||
int type = bytesToInt(msgs[1]);
|
||||
|
||||
// gzip.decode(body);
|
||||
|
||||
Uint8List plaintext;
|
||||
if (type == CiphertextMessage.prekeyType) {
|
||||
PreKeySignalMessage pre = PreKeySignalMessage(body);
|
||||
plaintext = await session.decrypt(pre);
|
||||
} else if (type == CiphertextMessage.whisperType) {
|
||||
SignalMessage signalMsg = SignalMessage.fromSerialized(body);
|
||||
plaintext = await session.decryptFromSignal(signalMsg);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
Message dectext = Message.fromJson(utf8.decode(gzip.decode(plaintext)));
|
||||
return dectext;
|
||||
} catch (e) {
|
||||
Logger("utils/signal").shout(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:twonly/src/components/initialsavatar_component.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/views/search_username_view.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'new_message_view.dart';
|
||||
|
|
@ -56,12 +57,19 @@ class ChatListView extends StatefulWidget {
|
|||
|
||||
class _ChatListViewState extends State<ChatListView> {
|
||||
int _secondsSinceOpen = 0;
|
||||
int _newContactRequests = 0;
|
||||
late Timer _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startTimer();
|
||||
_checkNewContactRequests();
|
||||
}
|
||||
|
||||
Future _checkNewContactRequests() async {
|
||||
_newContactRequests = (await DbContacts.getUsers()).length;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
|
|
@ -172,17 +180,41 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.chatsTitle),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.person_add), // User with add icon
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView(),
|
||||
Stack(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.person_add), // User with add icon
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_newContactRequests > 0)
|
||||
Positioned(
|
||||
right: 5,
|
||||
top: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.0), // Add some padding
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red, // Background color
|
||||
shape: BoxShape.circle, // Make it circular
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
_newContactRequests.toString(),
|
||||
style: TextStyle(
|
||||
color: Colors.white, // Text color
|
||||
fontSize: 10),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:twonly/src/model/contacts_model.dart';
|
||||
import 'package:twonly/src/utils/api.dart';
|
||||
import 'package:twonly/src/views/register_view.dart';
|
||||
|
||||
|
|
@ -14,28 +15,24 @@ class SearchUsernameView extends StatefulWidget {
|
|||
|
||||
class _SearchUsernameView extends State<SearchUsernameView> {
|
||||
final TextEditingController searchUserName = TextEditingController();
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
Future _addNewUser(BuildContext context) async {
|
||||
Timer timer = Timer(Duration(milliseconds: 500), () {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
final status = await addNewContact(searchUserName.text);
|
||||
|
||||
timer.cancel();
|
||||
// loaderDelay.timeout(Duration(microseconds: 0));
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
Logger("search_user_name").warning("Replace instead of pop");
|
||||
|
||||
if (context.mounted) {
|
||||
if (status) {
|
||||
Navigator.pop(context);
|
||||
// Navigator.pop(context);
|
||||
} else if (context.mounted) {
|
||||
showAlertDialog(
|
||||
context,
|
||||
|
|
@ -84,7 +81,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
controller: searchUserName,
|
||||
decoration: getInputDecoration(
|
||||
AppLocalizations.of(context)!.searchUsernameInput))),
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: 20),
|
||||
OutlinedButton.icon(
|
||||
icon: Icon(Icons.qr_code),
|
||||
onPressed: () {
|
||||
|
|
@ -93,9 +90,18 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
},
|
||||
label: Text("QR-Code scannen"),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
const SizedBox(height: 40),
|
||||
if (_isLoading) const Center(child: CircularProgressIndicator())
|
||||
SizedBox(height: 30),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 10),
|
||||
child: Text(
|
||||
"Neue Followanfragen",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ContactsListView(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -103,11 +109,73 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
|||
padding: const EdgeInsets.only(bottom: 30.0),
|
||||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
_addNewUser(context);
|
||||
if (!_isLoading) _addNewUser(context);
|
||||
},
|
||||
child: const Icon(Icons.arrow_right_rounded),
|
||||
child: (_isLoading)
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Icon(Icons.arrow_right_rounded),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContactsListView extends StatefulWidget {
|
||||
@override
|
||||
State<ContactsListView> createState() => _ContactsListViewState();
|
||||
}
|
||||
|
||||
class _ContactsListViewState extends State<ContactsListView> {
|
||||
List<Contact> _allContacts = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadContacts();
|
||||
}
|
||||
|
||||
Future _loadContacts() async {
|
||||
List<Contact> allContacts = await DbContacts.getUsers();
|
||||
_allContacts = allContacts.where((contact) => !contact.accepted).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: _allContacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = _allContacts[index];
|
||||
|
||||
if (!contact.requested) {
|
||||
return ListTile(
|
||||
title: Text(contact.displayName),
|
||||
subtitle: Text('Pending'),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: Text(contact.displayName),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.close, color: Colors.red),
|
||||
onPressed: () {
|
||||
// Handle reject action
|
||||
print('Rejected ${contact.displayName}');
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.check, color: Colors.green),
|
||||
onPressed: () {
|
||||
// Handle accept action
|
||||
print('Accepted ${contact.displayName}');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue