import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:pointycastle/impl.dart';
|
|
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
|
import 'package:sqflite/sqflite.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '/models/friends.dart';
|
|
import '/models/my_profile.dart';
|
|
import '/utils/encryption/aes_helper.dart';
|
|
import '/utils/encryption/crypto_utils.dart';
|
|
import '/utils/storage/database.dart';
|
|
import '/utils/strings.dart';
|
|
import '/utils/storage/session_cookie.dart';
|
|
import 'flash_message.dart';
|
|
|
|
class QrReader extends StatefulWidget {
|
|
const QrReader({
|
|
Key? key,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<QrReader> createState() => _QrReaderState();
|
|
}
|
|
|
|
class _QrReaderState extends State<QrReader> {
|
|
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
|
Barcode? result;
|
|
QRViewController? controller;
|
|
|
|
// In order to get hot reload to work we need to pause the camera if the platform
|
|
// is android, or resume the camera if the platform is iOS.
|
|
@override
|
|
void reassemble() {
|
|
super.reassemble();
|
|
if (Platform.isAndroid) {
|
|
controller!.pauseCamera();
|
|
} else if (Platform.isIOS) {
|
|
controller!.resumeCamera();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Column(
|
|
children: <Widget>[
|
|
Expanded(
|
|
flex: 5,
|
|
child: QRView(
|
|
key: qrKey,
|
|
onQRViewCreated: _onQRViewCreated,
|
|
formatsAllowed: const [BarcodeFormat.qrcode],
|
|
overlay: QrScannerOverlayShape(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _onQRViewCreated(QRViewController controller) {
|
|
this.controller = controller;
|
|
controller.scannedDataStream.listen((scanData) {
|
|
addFriend(scanData)
|
|
.then((dynamic ret) {
|
|
if (ret) {
|
|
// Delay exit to prevent exit mid way through rendering
|
|
Future.delayed(Duration.zero, () {
|
|
Navigator.of(context).pop();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<bool> addFriend(Barcode scanData) async {
|
|
Map<String, dynamic> friendJson = jsonDecode(scanData.code!);
|
|
|
|
RSAPublicKey publicKey = CryptoUtils.rsaPublicKeyFromPem(
|
|
String.fromCharCodes(
|
|
base64.decode(
|
|
friendJson['k']
|
|
)
|
|
)
|
|
);
|
|
|
|
MyProfile profile = await MyProfile.getProfile();
|
|
|
|
var uuid = const Uuid();
|
|
|
|
final symmetricKey1 = AesHelper.deriveKey(generateRandomString(32));
|
|
final symmetricKey2 = AesHelper.deriveKey(generateRandomString(32));
|
|
|
|
Friend request1 = Friend(
|
|
id: uuid.v4(),
|
|
userId: friendJson['i'],
|
|
username: profile.username,
|
|
friendId: profile.id,
|
|
friendSymmetricKey: base64.encode(symmetricKey1),
|
|
publicKey: profile.publicKey!,
|
|
acceptedAt: DateTime.now(),
|
|
);
|
|
|
|
Friend request2 = Friend(
|
|
id: uuid.v4(),
|
|
userId: profile.id,
|
|
friendId: friendJson['i'],
|
|
username: friendJson['u'],
|
|
friendSymmetricKey: base64.encode(symmetricKey2),
|
|
publicKey: publicKey,
|
|
acceptedAt: DateTime.now(),
|
|
);
|
|
|
|
String payload = jsonEncode([
|
|
request1.payloadJson(),
|
|
request2.payloadJson(),
|
|
]);
|
|
|
|
var resp = await http.post(
|
|
await MyProfile.getServerUrl('api/v1/auth/friend_request/qr_code'),
|
|
headers: <String, String>{
|
|
'Content-Type': 'application/json; charset=UTF-8',
|
|
'cookie': await getSessionCookie(),
|
|
},
|
|
body: payload,
|
|
);
|
|
|
|
if (resp.statusCode != 200) {
|
|
showMessage(
|
|
'Failed to add friend, please try again later',
|
|
context
|
|
);
|
|
return false;
|
|
}
|
|
|
|
final db = await getDatabaseConnection();
|
|
|
|
await db.insert(
|
|
'friends',
|
|
request1.toMap(),
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
|
|
await db.insert(
|
|
'friends',
|
|
request2.toMap(),
|
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
);
|
|
|
|
return true;
|
|
}
|
|
}
|