Skip to content

Commit

Permalink
Refactor: Extracted camera overlays into their own widget with visibi…
Browse files Browse the repository at this point in the history
…lity management (#949)

* Refactor: Camera refactoring

* Update ml_kit_scan_page.dart

* Update ml_kit_scan_page.dart

* added documentation and lifecycle management

* Update ml_kit_scan_page.dart
  • Loading branch information
M123-dev authored Jan 15, 2022
1 parent a65b609 commit e30ab6b
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 183 deletions.
73 changes: 23 additions & 50 deletions packages/smooth_app/lib/pages/scan/continuous_scan_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/pages/scan/scan_page_helper.dart';
import 'package:smooth_ui_library/smooth_ui_library.dart';
import 'package:visibility_detector/visibility_detector.dart';
import 'package:smooth_app/pages/scan/scanner_overlay.dart';

class ContinuousScanPage extends StatefulWidget {
const ContinuousScanPage();
Expand All @@ -21,55 +19,30 @@ class _ContinuousScanPageState extends State<ContinuousScanPage> {
@override
Widget build(BuildContext context) {
_model = context.watch<ContinuousScanModel>();
return VisibilityDetector(
key: const Key('VisibilityDetector qr_code_scanner'),
onVisibilityChanged: (VisibilityInfo visibilityInfo) {
if (visibilityInfo.visibleFraction == 0.0) {
_stopLiveFeed();
} else {
_resumeLiveFeed();
}
},
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final double carouselHeight = constraints.maxHeight /
1.81; // roughly 55% of the available height
final double viewFinderBottomOffset = carouselHeight / 2.0;

final List<Widget> children = getScannerWidgets(
context,
constraints,
_model,
);

//Insert scanner at the right position
children.insert(
1,
SmoothRevealAnimation(
delay: 400,
startOffset: Offset.zero,
animationCurve: Curves.easeInOutBack,
child: QRView(
overlay: QrScannerOverlayShape(
// We use [SmoothViewFinder] instead of the overlay.
overlayColor: Colors.transparent,
// This offset adjusts the scanning area on iOS.
cutOutBottomOffset: viewFinderBottomOffset,
),
key: _scannerViewKey,
onQRViewCreated: setupScanner,
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final double carouselHeight =
constraints.maxHeight / 1.81; // roughly 55% of the available height
final double viewFinderBottomOffset = carouselHeight / 2.0;

return Scaffold(
body: ScannerOverlay(
model: _model,
restartCamera: _resumeLiveFeed,
stopCamera: _stopLiveFeed,
scannerWidget: QRView(
overlay: QrScannerOverlayShape(
// We use [SmoothViewFinder] instead of the overlay.
overlayColor: Colors.transparent,
// This offset adjusts the scanning area on iOS.
cutOutBottomOffset: viewFinderBottomOffset,
),
key: _scannerViewKey,
onQRViewCreated: setupScanner,
),
);

return Scaffold(
appBar: AppBar(toolbarHeight: 0.0),
body: Stack(
children: children,
),
);
},
),
),
);
},
);
}

Expand Down
94 changes: 34 additions & 60 deletions packages/smooth_app/lib/pages/scan/ml_kit_scan_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import 'package:google_ml_barcode_scanner/google_ml_barcode_scanner.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/main.dart';
import 'package:smooth_app/pages/scan/scan_page_helper.dart';
import 'package:smooth_ui_library/animations/smooth_reveal_animation.dart';
import 'package:visibility_detector/visibility_detector.dart';
import 'package:smooth_app/pages/scan/scanner_overlay.dart';

class MLKitScannerPage extends StatefulWidget {
const MLKitScannerPage({Key? key}) : super(key: key);
Expand All @@ -26,6 +24,7 @@ class MLKitScannerPageState extends State<MLKitScannerPage> {
int _cameraIndex = 0;
CameraLensDirection cameraLensDirection = CameraLensDirection.back;
bool isBusy = false;
bool imageStreamActive = false;

@override
void initState() {
Expand Down Expand Up @@ -59,31 +58,16 @@ class MLKitScannerPageState extends State<MLKitScannerPage> {

@override
void dispose() {
_stopLiveFeed();
_disposeLiveFeed();
_stopImageStream().then(
(_) => _controller?.dispose(),
);
super.dispose();
}

@override
Widget build(BuildContext context) {
_model = context.watch<ContinuousScanModel>();
return Scaffold(
body: VisibilityDetector(
key: const Key('VisibilityDetector ML Kit'),
onVisibilityChanged: (VisibilityInfo visibilityInfo) {
if (visibilityInfo.visibleFraction == 0.0) {
_stopLiveFeed();
} else {
_startLiveFeed();
}
},
child: _liveFeedBody(),
),
);
}

Widget _liveFeedBody() {
if (_controller?.value.isInitialized == false || _controller == null) {
if (_controller == null || _controller!.value.isInitialized == false) {
return const Center(child: CircularProgressIndicator());
}

Expand All @@ -100,37 +84,20 @@ class MLKitScannerPageState extends State<MLKitScannerPage> {
scale = 1 / scale;
}

return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final List<Widget> children = getScannerWidgets(
context,
constraints,
_model,
);

//Inserting the scanner at the right position
children.insert(
1,
SmoothRevealAnimation(
delay: 400,
startOffset: Offset.zero,
animationCurve: Curves.easeInOutBack,
child: Transform.scale(
scale: scale,
child: Center(
child: CameraPreview(
_controller!,
),
),
return Scaffold(
body: ScannerOverlay(
restartCamera: _resumeImageStream,
stopCamera: _stopImageStream,
model: _model,
scannerWidget: Transform.scale(
scale: scale,
child: Center(
child: CameraPreview(
_controller!,
),
),
);

return Stack(
fit: StackFit.expand,
children: children,
);
},
),
),
);
}

Expand All @@ -141,24 +108,31 @@ class MLKitScannerPageState extends State<MLKitScannerPage> {
ResolutionPreset.high,
enableAudio: false,
);
_controller?.initialize().then((_) {
_controller!.setFocusMode(FocusMode.auto);
_controller!.lockCaptureOrientation(DeviceOrientation.portraitUp);

_controller!.initialize().then((_) {
if (!mounted) {
return;
}
_controller?.setFocusMode(FocusMode.auto);
_controller?.lockCaptureOrientation(DeviceOrientation.portraitUp);
_controller?.startImageStream(_processCameraImage);
_controller!.startImageStream(_processCameraImage);
imageStreamActive = true;
setState(() {});
});
}

Future<void> _stopLiveFeed() async {
await _controller?.stopImageStream();
_controller = null;
void _resumeImageStream() {
if (_controller != null && !imageStreamActive) {
_controller!.startImageStream(_processCameraImage);
imageStreamActive = true;
}
}

Future<void> _disposeLiveFeed() async {
await _controller?.dispose();
Future<void> _stopImageStream() async {
if (_controller != null) {
await _controller!.stopImageStream();
imageStreamActive = false;
}
}

//Convert the [CameraImage] to a [InputImage]
Expand Down
73 changes: 0 additions & 73 deletions packages/smooth_app/lib/pages/scan/scan_page_helper.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/pages/personalized_ranking_page.dart';
import 'package:smooth_app/widgets/ranking_floating_action_button.dart';
import 'package:smooth_app/widgets/smooth_product_carousel.dart';
import 'package:smooth_ui_library/animations/smooth_reveal_animation.dart';
import 'package:smooth_ui_library/util/ui_helpers.dart';
import 'package:smooth_ui_library/widgets/smooth_view_finder.dart';

bool areButtonsRendered(ContinuousScanModel model) =>
model.hasMoreThanOneProduct;
Expand Down Expand Up @@ -68,72 +64,3 @@ Widget buildButtonsRow(BuildContext context, ContinuousScanModel model) {
),
);
}

List<Widget> getScannerWidgets(
BuildContext context,
BoxConstraints constraints,
ContinuousScanModel model,
) {
final Size screenSize = MediaQuery.of(context).size;
final Size scannerSize = Size(
screenSize.width * 0.6,
screenSize.width * 0.33,
);
final double carouselHeight =
constraints.maxHeight / 1.81; // roughly 55% of the available height
final double buttonRowHeight = areButtonsRendered(model) ? 48 : 0;
final double availableScanHeight =
constraints.maxHeight - carouselHeight - buttonRowHeight;
// Padding for the qr code scanner. This ensures the scanner has equal spacing between buttons and carousel.
final EdgeInsets qrScannerPadding = EdgeInsets.only(
top: (availableScanHeight - scannerSize.height) / 2 + buttonRowHeight);

return <Widget>[
Container(
alignment: Alignment.center,
color: Colors.black,
child: Padding(
padding: qrScannerPadding,
child: SvgPicture.asset(
'assets/actions/scanner_alt_2.svg',
width: scannerSize.width,
height: scannerSize.height,
color: Colors.white,
),
),
),
SmoothRevealAnimation(
delay: 400,
startOffset: const Offset(0.0, 0.1),
animationCurve: Curves.easeInOutBack,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: qrScannerPadding,
child: SmoothViewFinder(
boxSize: scannerSize,
lineLength: screenSize.width * 0.8,
),
),
],
),
),
SmoothRevealAnimation(
delay: 400,
startOffset: const Offset(0.0, -0.1),
animationCurve: Curves.easeInOutBack,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
buildButtonsRow(context, model),
const Spacer(),
SmoothProductCarousel(
showSearchCard: true,
height: carouselHeight,
),
],
),
),
];
}
Loading

0 comments on commit e30ab6b

Please sign in to comment.