Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add qr scanner #62

Merged
merged 26 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e167a3a
add key to `Info.plist`
ethan-tbd Feb 12, 2024
3f61853
add `mobile_scanner` package
ethan-tbd Feb 12, 2024
90e651e
add `ScanQrPage`
ethan-tbd Feb 12, 2024
a4a17ba
launch `ScanQrPage` on tap of `ListTile`
ethan-tbd Feb 12, 2024
f8752d3
update localizations for qr scanning
ethan-tbd Feb 13, 2024
d238148
use flutter hooks `useOnAppLifecycleStateChange()` and add `maxSize` …
ethan-tbd Feb 13, 2024
ca9ed9d
validate qr code scan using `DidJwk.resolve()`
ethan-tbd Feb 13, 2024
24102e6
add `qr_flutter` to generate qr code
ethan-tbd Feb 13, 2024
94aeac0
add localizations for qr generation
ethan-tbd Feb 13, 2024
45a47dc
add qr code in `AccountDidPage`
ethan-tbd Feb 13, 2024
a6a9c0d
add localization for simulated qr scan
ethan-tbd Feb 13, 2024
1d1427c
add `device_info_plus` to check for simulator
ethan-tbd Feb 13, 2024
31388e3
check for physical device before qr scan
ethan-tbd Feb 13, 2024
4df08c5
memoize `WebViewController`
ethan-tbd Feb 13, 2024
b271742
remove test that requires override
ethan-tbd Feb 14, 2024
daa34d1
override `didProvider` to fix tests
ethan-tbd Feb 14, 2024
9cd3ef1
remove did tag localizations
ethan-tbd Feb 14, 2024
ecff5d5
replace removed test with `didProvider` override
ethan-tbd Feb 14, 2024
4f7ed45
make `scanWindow` a square
ethan-tbd Feb 14, 2024
32ae7a3
update localization for no did qr code
ethan-tbd Feb 14, 2024
dcb9cb4
add `Grid.side` padding to `listTileTheme`
ethan-tbd Feb 14, 2024
a99b5df
use `ListTile` theme `contentPadding`
ethan-tbd Feb 14, 2024
a79803b
make custom painter private and refactor `useOnAppLifecycleStateChange`
ethan-tbd Feb 14, 2024
50db92e
add provider for `DeviceInfoService`
ethan-tbd Feb 14, 2024
18f2675
use `deviceInfoServiceProvider`
ethan-tbd Feb 14, 2024
4d5bd34
add `_simulateScanQrCode`
ethan-tbd Feb 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes</string>
ethan-tbd marked this conversation as resolved.
Show resolved Hide resolved
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
50 changes: 46 additions & 4 deletions lib/features/account/account_did_page.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import 'dart:math';

import 'package:didpay/l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:didpay/features/account/account_providers.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';

class AccountDidPage extends HookConsumerWidget {
const AccountDidPage({super.key});
Expand All @@ -10,11 +15,48 @@ class AccountDidPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final did = ref.watch(didProvider);

const maxSize = 250.0;
final screenSize = MediaQuery.of(context).size;
final qrSize = min(screenSize.width * 0.5, maxSize);

return Scaffold(
appBar: AppBar(title: const Text('My DID')),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: Center(child: SelectableText(did.uri))),
appBar: AppBar(title: Text(Loc.of(context).myDid)),
body: Column(
children: [
const SizedBox(height: Grid.md),
QrImageView(
data: did.uri,
size: qrSize,
eyeStyle: QrEyeStyle(
eyeShape: QrEyeShape.square,
color: Theme.of(context).colorScheme.onBackground,
),
dataModuleStyle: QrDataModuleStyle(
color: Theme.of(context).colorScheme.onBackground,
dataModuleShape: QrDataModuleShape.square,
),
),
const SizedBox(height: Grid.xxl),
ListTile(
title: Text(did.uri),
trailing: const Icon(Icons.content_copy),
onTap: () {
Clipboard.setData(ClipboardData(text: did.uri));
final snackBar = SnackBar(
content: Text(
Loc.of(context).copiedDid,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
);

ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
),
],
),
);
}
}
1 change: 0 additions & 1 deletion lib/features/payments/payment_details_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ class PaymentDetailsPage extends HookConsumerWidget {
: Loc.of(context).serviceFeeAmount(fee, 'USD'),
style: Theme.of(context).textTheme.bodySmall,
),
contentPadding: const EdgeInsets.symmetric(horizontal: Grid.side),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
Expand Down
32 changes: 16 additions & 16 deletions lib/features/payments/search_payment_methods_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ class SearchPaymentMethodsPage extends HookWidget {
return Scaffold(
appBar: AppBar(scrolledUnderElevation: 0),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.side),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
Expand All @@ -49,17 +49,17 @@ class SearchPaymentMethodsPage extends HookWidget {
],
),
),
const SizedBox(height: Grid.xs),
Expanded(
child: _buildList(
context,
selectedPaymentMethod,
searchText,
paymentMethods,
),
),
const SizedBox(height: Grid.xs),
Expanded(
child: _buildList(
context,
selectedPaymentMethod,
searchText,
paymentMethods,
),
],
),
),
],
),
),
);
Expand Down
2 changes: 1 addition & 1 deletion lib/features/pfis/pfi_verification_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class PfiVerificationPage extends HookConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = WebViewController()
final controller = useMemoized(() => WebViewController())
ethan-tbd marked this conversation as resolved.
Show resolved Hide resolved
..setBackgroundColor(Theme.of(context).colorScheme.background)
..setNavigationDelegate(
NavigationDelegate(
Expand Down
117 changes: 117 additions & 0 deletions lib/features/send/scan_qr_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:didpay/shared/theme/grid.dart';

class ScanQrPage extends HookWidget {
const ScanQrPage({super.key});

@override
Widget build(BuildContext context) {
final isProcessing = useState(false);
final controller = useMemoized(() => MobileScannerController());

useOnAppLifecycleStateChange((_, current) =>
current == AppLifecycleState.resumed
? controller.start()
: controller.stop());

const maxSize = 400.0;
final screenSize = MediaQuery.of(context).size;
final scanSize = screenSize.width * 0.8;

final scanWindow = Rect.fromCenter(
center: screenSize.center(Offset.zero),
width: min(scanSize, maxSize),
height: min(scanSize, maxSize),
);

return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
IconButton(
icon: const Icon(Icons.flash_on),
onPressed: () => controller.toggleTorch(),
),
],
),
body: SafeArea(
top: false,
bottom: false,
child: Stack(
children: [
MobileScanner(
controller: controller,
scanWindow: scanWindow,
onDetect: (barcode) {
if (isProcessing.value) return;
isProcessing.value = true;

Navigator.of(context).pop(
barcode.barcodes.map((e) => e.rawValue).join(),
);
},
),
CustomPaint(painter: _ScannerOverlay(scanWindow)),
],
),
),
);
}
}

class _ScannerOverlay extends CustomPainter {
final Rect scanWindow;

_ScannerOverlay(this.scanWindow);

@override
void paint(Canvas canvas, Size size) {
final backgroundPath = Path()..addRect(Rect.largest);
final cutoutPath = Path()
..addRRect(
RRect.fromRectAndCorners(
scanWindow,
topLeft: const Radius.circular(Grid.radius),
topRight: const Radius.circular(Grid.radius),
bottomLeft: const Radius.circular(Grid.radius),
bottomRight: const Radius.circular(Grid.radius),
),
);

final backgroundPaint = Paint()
..color = Colors.black.withOpacity(0.5)
..style = PaintingStyle.fill
..blendMode = BlendMode.dstOut;

final backgroundWithCutout = Path.combine(
PathOperation.difference,
backgroundPath,
cutoutPath,
);

final borderRect = RRect.fromRectAndCorners(
scanWindow,
topLeft: const Radius.circular(Grid.radius),
topRight: const Radius.circular(Grid.radius),
bottomLeft: const Radius.circular(Grid.radius),
bottomRight: const Radius.circular(Grid.radius),
);

final borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = Grid.quarter;

canvas.drawPath(backgroundWithCutout, backgroundPaint);
canvas.drawRRect(borderRect, borderPaint);
}

@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Loading
Loading