mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-25 11:14:07 +00:00
better feedback in case a qr code was shown
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
0e377d0ebc
commit
75079a790c
9 changed files with 239 additions and 140 deletions
|
|
@ -2567,7 +2567,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @verifiedPublicKey.
|
/// No description provided for @verifiedPublicKey.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'The public key of {username} has been verified and is valid.'**
|
/// **'The identity of {username} has been successfully verified.'**
|
||||||
String verifiedPublicKey(Object username);
|
String verifiedPublicKey(Object username);
|
||||||
|
|
||||||
/// No description provided for @memoriesAYearAgo.
|
/// No description provided for @memoriesAYearAgo.
|
||||||
|
|
|
||||||
|
|
@ -1429,7 +1429,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String verifiedPublicKey(Object username) {
|
String verifiedPublicKey(Object username) {
|
||||||
return 'Der öffentliche Schlüssel von $username wurde überprüft und ist gültig.';
|
return 'Die Identität von $username wurde erfolgreich überprüft.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -1418,7 +1418,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String verifiedPublicKey(Object username) {
|
String verifiedPublicKey(Object username) {
|
||||||
return 'The public key of $username has been verified and is valid.';
|
return 'The identity of $username has been successfully verified.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 673f6d8c3036d64060b1114912bd5bf5515d5420
|
Subproject commit 2e608f2d5acc26ee001877b673e361884fb3d0ca
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
@ -13,7 +14,7 @@ import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
||||||
|
|
||||||
class KeyVerificationService {
|
class KeyVerificationService {
|
||||||
static Future<List<int>> getNewSecretVerificationToken() async {
|
static Future<List<int>> getNewSecretVerificationToken() async {
|
||||||
|
|
@ -77,12 +78,14 @@ class KeyVerificationService {
|
||||||
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
|
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
|
||||||
final context = rootNavigatorKey.currentContext;
|
final context = rootNavigatorKey.currentContext;
|
||||||
if (context != null && context.mounted && contact != null) {
|
if (context != null && context.mounted && contact != null) {
|
||||||
showSnackbar(
|
unawaited(
|
||||||
|
VerificationSuccessDialog.show(
|
||||||
context,
|
context,
|
||||||
context.lang.secretQrTokenVerifiedSnackbar(
|
contact,
|
||||||
|
message: context.lang.secretQrTokenVerifiedSnackbar(
|
||||||
getContactDisplayName(contact),
|
getContactDisplayName(contact),
|
||||||
),
|
),
|
||||||
level: SnackbarLevel.success,
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
92
lib/src/visual/components/add_contact_dialog.comp.dart
Normal file
92
lib/src/visual/components/add_contact_dialog.comp.dart
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
|
|
||||||
|
/// A premium popup dialog shown when a new user profile is scanned via QR code.
|
||||||
|
/// Allows the user to request connection ("Anfragen") or cancel ("Abbrechen").
|
||||||
|
class AddContactDialog extends StatelessWidget {
|
||||||
|
const AddContactDialog({
|
||||||
|
required this.profile,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PublicProfile profile;
|
||||||
|
|
||||||
|
/// Utility method to easily present this dialog.
|
||||||
|
/// Returns `true` if the user chose to request the contact, `false` otherwise.
|
||||||
|
static Future<bool?> show(BuildContext context, PublicProfile profile) {
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AddContactDialog(profile: profile),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 20,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const AvatarIcon(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
profile.username,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
context.lang.userFoundBody(profile.username),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 28),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: MyButton(
|
||||||
|
variant: MyButtonVariant.secondary,
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: Text(context.lang.cancel),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: MyButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
child: Text(context.lang.friendSuggestionsRequest),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/components/verification_success_animation.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
|
|
||||||
|
/// A premium popup dialog shown when a contact's public key has been successfully verified.
|
||||||
|
class VerificationSuccessDialog extends StatelessWidget {
|
||||||
|
const VerificationSuccessDialog({
|
||||||
|
required this.contact,
|
||||||
|
this.message,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Contact contact;
|
||||||
|
final String? message;
|
||||||
|
|
||||||
|
/// Utility method to easily present this dialog.
|
||||||
|
static Future<void> show(BuildContext context, Contact contact, {String? message}) {
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => VerificationSuccessDialog(contact: contact, message: message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final displayName = getContactDisplayName(contact);
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 28,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
AvatarIcon(
|
||||||
|
contactId: contact.userId,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
displayName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const VerificationSuccessAnimation(
|
||||||
|
size: 160,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
message ?? context.lang.verifiedPublicKey(displayName),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 28),
|
||||||
|
MyButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(context.lang.close),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.utils.dart';
|
|
||||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
|
||||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
|
@ -27,113 +23,14 @@ class CameraScannedOverlay extends StatelessWidget {
|
||||||
width: 150,
|
width: 150,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
...mainController.scannedNewProfiles.values.map(
|
if (mainController.scannedUrl != null)
|
||||||
(c) => _buildScannedProfileTile(context, c),
|
_buildScannedUrlTile(context, mainController.scannedUrl!),
|
||||||
),
|
|
||||||
...mainController.contactsVerified.values.map(
|
|
||||||
(c) => _buildVerifiedContactTile(context, c),
|
|
||||||
),
|
|
||||||
if (mainController.scannedUrl != null) _buildScannedUrlTile(context, mainController.scannedUrl!),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildScannedProfileTile(BuildContext context, ScannedNewProfile c) {
|
|
||||||
if (c.isLoading) return Container();
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
c.isLoading = true;
|
|
||||||
mainController.setState?.call();
|
|
||||||
|
|
||||||
showSnackbar(
|
|
||||||
context,
|
|
||||||
context.lang.requestedUserToastText(c.profile.username),
|
|
||||||
level: SnackbarLevel.success,
|
|
||||||
);
|
|
||||||
if (await addNewContactFromPublicProfile(c.profile) && context.mounted) {
|
|
||||||
// showSnackbar(
|
|
||||||
// context,
|
|
||||||
// context.lang.requestedUserToastText(c.profile.username),
|
|
||||||
// level: SnackbarLevel.success,
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: context.color.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(c.profile.username),
|
|
||||||
Expanded(child: Container()),
|
|
||||||
if (c.isLoading)
|
|
||||||
const SizedBox(
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: FaIcon(
|
|
||||||
FontAwesomeIcons.userPlus,
|
|
||||||
color: isDarkMode(context) ? Colors.white : Colors.black,
|
|
||||||
size: 17,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVerifiedContactTile(
|
|
||||||
BuildContext context,
|
|
||||||
ScannedVerifiedContact c,
|
|
||||||
) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: context.color.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
AvatarIcon(
|
|
||||||
contactId: c.contact.userId,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(
|
|
||||||
getContactDisplayName(c.contact, maxLength: 9),
|
|
||||||
),
|
|
||||||
Expanded(child: Container()),
|
|
||||||
ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 30,
|
|
||||||
child: Lottie.asset(
|
|
||||||
c.verificationOk ? 'assets/animations/success.lottie' : 'assets/animations/failed.lottie',
|
|
||||||
repeat: false,
|
|
||||||
onLoaded: (p0) {
|
|
||||||
Future.delayed(const Duration(seconds: 4), () {
|
|
||||||
mainController.setState?.call();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScannedUrlTile(BuildContext context, String url) {
|
Widget _buildScannedUrlTile(BuildContext context, String url) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,14 @@ import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.utils.dart';
|
import 'package:twonly/src/utils/qr.utils.dart';
|
||||||
|
import 'package:twonly/src/visual/components/add_contact_dialog.comp.dart';
|
||||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||||
|
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
||||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
||||||
|
|
@ -141,7 +142,8 @@ class MainCameraController {
|
||||||
|
|
||||||
if (init) {
|
if (init) {
|
||||||
for (; cameraId < AppEnvironment.cameras.length; cameraId++) {
|
for (; cameraId < AppEnvironment.cameras.length; cameraId++) {
|
||||||
if (AppEnvironment.cameras[cameraId].lensDirection == CameraLensDirection.back) {
|
if (AppEnvironment.cameras[cameraId].lensDirection ==
|
||||||
|
CameraLensDirection.back) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +159,9 @@ class MainCameraController {
|
||||||
AppEnvironment.cameras[cameraId],
|
AppEnvironment.cameras[cameraId],
|
||||||
ResolutionPreset.high,
|
ResolutionPreset.high,
|
||||||
enableAudio: hasMic,
|
enableAudio: hasMic,
|
||||||
imageFormatGroup: Platform.isAndroid ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888,
|
imageFormatGroup: Platform.isAndroid
|
||||||
|
? ImageFormatGroup.nv21
|
||||||
|
: ImageFormatGroup.bgra8888,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
_initializeFuture = cameraController?.initialize();
|
_initializeFuture = cameraController?.initialize();
|
||||||
|
|
@ -210,10 +214,14 @@ class MainCameraController {
|
||||||
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off,
|
selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off,
|
||||||
);
|
);
|
||||||
if (cameraController == null) return;
|
if (cameraController == null) return;
|
||||||
selectedCameraDetails.maxAvailableZoom = await cameraController?.getMaxZoomLevel() ?? 1;
|
selectedCameraDetails.maxAvailableZoom =
|
||||||
selectedCameraDetails.minAvailableZoom = await cameraController?.getMinZoomLevel() ?? 1;
|
await cameraController?.getMaxZoomLevel() ?? 1;
|
||||||
|
selectedCameraDetails.minAvailableZoom =
|
||||||
|
await cameraController?.getMinZoomLevel() ?? 1;
|
||||||
selectedCameraDetails
|
selectedCameraDetails
|
||||||
..isZoomAble = selectedCameraDetails.maxAvailableZoom != selectedCameraDetails.minAvailableZoom
|
..isZoomAble =
|
||||||
|
selectedCameraDetails.maxAvailableZoom !=
|
||||||
|
selectedCameraDetails.minAvailableZoom
|
||||||
..cameraLoaded = true
|
..cameraLoaded = true
|
||||||
..cameraId = cameraId;
|
..cameraId = cameraId;
|
||||||
|
|
||||||
|
|
@ -235,7 +243,8 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onTapDown(TapDownDetails details) async {
|
Future<void> onTapDown(TapDownDetails details) async {
|
||||||
final box = cameraPreviewKey.currentContext?.findRenderObject() as RenderBox?;
|
final box =
|
||||||
|
cameraPreviewKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
if (box == null) return;
|
if (box == null) return;
|
||||||
final localPosition = box.globalToLocal(details.globalPosition);
|
final localPosition = box.globalToLocal(details.globalPosition);
|
||||||
|
|
||||||
|
|
@ -251,7 +260,8 @@ class MainCameraController {
|
||||||
await cameraController?.setFocusPoint(Offset(dx, dy));
|
await cameraController?.setFocusPoint(Offset(dx, dy));
|
||||||
await cameraController?.setFocusMode(FocusMode.auto);
|
await cameraController?.setFocusMode(FocusMode.auto);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is CameraException && (e.code == 'setFocusPointFailed' || e.code == 'setFocusModeFailed')) {
|
if (e is CameraException &&
|
||||||
|
(e.code == 'setFocusPointFailed' || e.code == 'setFocusModeFailed')) {
|
||||||
Log.info('Focus point or mode not supported on this device');
|
Log.info('Focus point or mode not supported on this device');
|
||||||
} else {
|
} else {
|
||||||
Log.warn(e);
|
Log.warn(e);
|
||||||
|
|
@ -292,7 +302,8 @@ class MainCameraController {
|
||||||
if (inputImage == null) return;
|
if (inputImage == null) return;
|
||||||
_processBarcode(inputImage);
|
_processBarcode(inputImage);
|
||||||
// check if front camera is selected
|
// check if front camera is selected
|
||||||
if (cameraController?.description.lensDirection == CameraLensDirection.front) {
|
if (cameraController?.description.lensDirection ==
|
||||||
|
CameraLensDirection.front) {
|
||||||
if (_currentFilterType != FaceFilterType.none) {
|
if (_currentFilterType != FaceFilterType.none) {
|
||||||
_processFaces(inputImage);
|
_processFaces(inputImage);
|
||||||
}
|
}
|
||||||
|
|
@ -311,14 +322,16 @@ class MainCameraController {
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
rotation = InputImageRotationValue.fromRawValue(sensorOrientation);
|
rotation = InputImageRotationValue.fromRawValue(sensorOrientation);
|
||||||
} else if (Platform.isAndroid) {
|
} else if (Platform.isAndroid) {
|
||||||
var rotationCompensation = _orientations[cameraController!.value.deviceOrientation];
|
var rotationCompensation =
|
||||||
|
_orientations[cameraController!.value.deviceOrientation];
|
||||||
if (rotationCompensation == null) return null;
|
if (rotationCompensation == null) return null;
|
||||||
if (camera.lensDirection == CameraLensDirection.front) {
|
if (camera.lensDirection == CameraLensDirection.front) {
|
||||||
// front-facing
|
// front-facing
|
||||||
rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
|
rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
|
||||||
} else {
|
} else {
|
||||||
// back-facing
|
// back-facing
|
||||||
rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
|
rotationCompensation =
|
||||||
|
(sensorOrientation - rotationCompensation + 360) % 360;
|
||||||
}
|
}
|
||||||
rotation = InputImageRotationValue.fromRawValue(rotationCompensation);
|
rotation = InputImageRotationValue.fromRawValue(rotationCompensation);
|
||||||
}
|
}
|
||||||
|
|
@ -360,7 +373,9 @@ class MainCameraController {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
_isBusy = true;
|
_isBusy = true;
|
||||||
final barcodes = await _barcodeScanner.processImage(inputImage);
|
final barcodes = await _barcodeScanner.processImage(inputImage);
|
||||||
if (inputImage.metadata?.size != null && inputImage.metadata?.rotation != null && cameraController != null) {
|
if (inputImage.metadata?.size != null &&
|
||||||
|
inputImage.metadata?.rotation != null &&
|
||||||
|
cameraController != null) {
|
||||||
final painter = BarcodeDetectorPainter(
|
final painter = BarcodeDetectorPainter(
|
||||||
barcodes,
|
barcodes,
|
||||||
inputImage.metadata!.size,
|
inputImage.metadata!.size,
|
||||||
|
|
@ -397,11 +412,21 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact == null || contact.deletedByUser) {
|
if (contact == null || contact.deletedByUser) {
|
||||||
if (scannedNewProfiles[profile.userId.toInt()] == null) {
|
final context = cameraPreviewKey.currentContext;
|
||||||
await HapticFeedback.heavyImpact();
|
if (context != null && context.mounted) {
|
||||||
scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile(
|
unawaited(HapticFeedback.heavyImpact());
|
||||||
profile: profile,
|
final shouldRequest = await AddContactDialog.show(
|
||||||
|
context,
|
||||||
|
profile,
|
||||||
);
|
);
|
||||||
|
if (shouldRequest == true && context.mounted) {
|
||||||
|
showSnackbar(
|
||||||
|
context,
|
||||||
|
context.lang.requestedUserToastText(profile.username),
|
||||||
|
level: SnackbarLevel.success,
|
||||||
|
);
|
||||||
|
await addNewContactFromPublicProfile(profile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -412,16 +437,10 @@ class MainCameraController {
|
||||||
verificationOk: verificationOk,
|
verificationOk: verificationOk,
|
||||||
);
|
);
|
||||||
|
|
||||||
await HapticFeedback.heavyImpact();
|
unawaited(HapticFeedback.heavyImpact());
|
||||||
final context = cameraPreviewKey.currentContext;
|
final context = cameraPreviewKey.currentContext;
|
||||||
if (verificationOk && context != null && context.mounted) {
|
if (verificationOk && context != null && context.mounted) {
|
||||||
showSnackbar(
|
await VerificationSuccessDialog.show(context, contact);
|
||||||
context,
|
|
||||||
context.lang.verifiedPublicKey(
|
|
||||||
getContactDisplayName(contact),
|
|
||||||
),
|
|
||||||
level: SnackbarLevel.success,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -444,7 +463,9 @@ class MainCameraController {
|
||||||
if (_isBusyFaces) return;
|
if (_isBusyFaces) return;
|
||||||
_isBusyFaces = true;
|
_isBusyFaces = true;
|
||||||
final faces = await _faceDetector.processImage(inputImage);
|
final faces = await _faceDetector.processImage(inputImage);
|
||||||
if (inputImage.metadata?.size != null && inputImage.metadata?.rotation != null && cameraController != null) {
|
if (inputImage.metadata?.size != null &&
|
||||||
|
inputImage.metadata?.rotation != null &&
|
||||||
|
cameraController != null) {
|
||||||
if (faces.isNotEmpty) {
|
if (faces.isNotEmpty) {
|
||||||
CustomPainter? painter;
|
CustomPainter? painter;
|
||||||
switch (_currentFilterType) {
|
switch (_currentFilterType) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue