Toggle navigation
Toggle navigation
This project
Loading...
Sign in
flutter_package
/
mobile_scanner
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Navaron Bracke
2023-11-08 21:00:42 +0100
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
4c7ed61a96a41290be02e1fd19ee401df8be7039
4c7ed61a
1 parent
19fa6a19
refactor the MobileScanner widget to remove the lifecycle handling in the widget
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
93 additions
and
215 deletions
lib/src/mobile_scanner.dart
lib/src/mobile_scanner.dart
View file @
4c7ed61
import
'dart:async'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_controller.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_exception.dart'
;
import
'package:mobile_scanner/src/objects/barcode_capture.dart'
;
import
'package:mobile_scanner/src/objects/mobile_scanner_arguments.dart'
;
import
'package:mobile_scanner/src/mobile_scanner_platform_interface.dart'
;
import
'package:mobile_scanner/src/objects/mobile_scanner_state.dart'
;
import
'package:mobile_scanner/src/scan_window_calculation.dart'
;
/// The function signature for the error builder.
...
...
@@ -17,18 +14,26 @@ typedef MobileScannerErrorBuilder = Widget Function(
Widget
?,
);
/// Th
e [MobileScanner] widget displays a live camera preview
.
/// Th
is widget displays a live camera preview for the barcode scanner
.
class
MobileScanner
extends
StatefulWidget
{
/// The controller that manages the barcode scanner.
///
/// If this is null, the scanner will manage its own controller.
final
MobileScannerController
?
controller
;
/// Create a new [MobileScanner] using the provided [controller].
const
MobileScanner
({
required
this
.
controller
,
this
.
fit
=
BoxFit
.
cover
,
this
.
errorBuilder
,
this
.
overlayBuilder
,
this
.
placeholderBuilder
,
this
.
scanWindow
,
super
.
key
,
});
/// The controller for the camera preview.
final
MobileScannerController
controller
;
/// The function that builds an error widget when the scanner
/// could not be started.
/// The error builder for the camera preview.
///
/// If this is null, defaults to a black [ColoredBox]
/// with a centered white [Icons.error] icon.
/// If this is null, a black [ColoredBox],
/// with a centered white [Icons.error] icon is used as error widget.
final
MobileScannerErrorBuilder
?
errorBuilder
;
/// The [BoxFit] for the camera preview.
...
...
@@ -36,250 +41,123 @@ class MobileScanner extends StatefulWidget {
/// Defaults to [BoxFit.cover].
final
BoxFit
fit
;
/// The function that signals when new codes were detected by the [controller].
final
void
Function
(
BarcodeCapture
barcodes
)
onDetect
;
/// The function that signals when the barcode scanner is started.
@Deprecated
(
'Use onScannerStarted() instead.'
)
final
void
Function
(
MobileScannerArguments
?
arguments
)?
onStart
;
/// The function that signals when the barcode scanner is started.
final
void
Function
(
MobileScannerArguments
?
arguments
)?
onScannerStarted
;
/// The builder for the overlay above the camera preview.
///
/// The resulting widget can be combined with the [scanWindow] rectangle
/// to create a cutout for the camera preview.
///
/// The [BoxConstraints] for this builder
/// are the same constraints that are used to compute the effective [scanWindow].
///
/// The overlay is only displayed when the camera preview is visible.
final
LayoutWidgetBuilder
?
overlayBuilder
;
/// The function that builds a placeholder widget when the scanner
/// is not yet displaying its camera preview.
/// The placeholder builder for the camera preview.
///
/// If this is null, a black [ColoredBox] is used as placeholder.
///
/// The placeholder is displayed when the camera preview is being initialized.
final
Widget
Function
(
BuildContext
,
Widget
?)?
placeholderBuilder
;
/// if set barcodes will only be scanned if they fall within this [Rect]
/// useful for having a cut-out overlay for example. these [Rect]
/// coordinates are relative to the widget size, so by how much your
/// rectangle overlays the actual image can depend on things like the
/// [BoxFit]
final
Rect
?
scanWindow
;
/// Only set this to true if you are starting another instance of mobile_scanner
/// right after disposing the first one, like in a PageView.
/// The scan window rectangle for the barcode scanner.
///
/// Default: false
final
bool
startDelay
;
/// The overlay which will be painted above the scanner when has started successful.
/// Will no be pointed when an error occurs or the scanner hasn't been started yet.
final
Widget
?
overlay
;
/// Create a new [MobileScanner] using the provided [controller]
/// and [onBarcodeDetected] callback.
const
MobileScanner
({
this
.
controller
,
this
.
errorBuilder
,
this
.
fit
=
BoxFit
.
cover
,
required
this
.
onDetect
,
@Deprecated
(
'Use onScannerStarted() instead.'
)
this
.
onStart
,
this
.
onScannerStarted
,
this
.
placeholderBuilder
,
this
.
scanWindow
,
this
.
startDelay
=
false
,
this
.
overlay
,
super
.
key
,
});
/// If this is not null, the barcode scanner will only scan barcodes
/// which intersect this rectangle.
///
/// The rectangle is relative to the layout size of the *camera preview widget*,
/// rather than the actual camera preview size,
/// since the actual widget size might not be the same as the camera preview size.
///
/// For example, the applied [fit] has an effect on the size of the camera preview widget,
/// while the camera preview size remains the same.
final
Rect
?
scanWindow
;
@override
State
<
MobileScanner
>
createState
()
=>
_MobileScannerState
();
}
class
_MobileScannerState
extends
State
<
MobileScanner
>
with
WidgetsBindingObserver
{
/// The subscription that listens to barcode detection.
StreamSubscription
<
BarcodeCapture
>?
_barcodesSubscription
;
/// The internally managed controller.
late
MobileScannerController
_controller
;
/// Whether the controller should resume
/// when the application comes back to the foreground.
bool
_resumeFromBackground
=
false
;
MobileScannerException
?
_startException
;
Widget
_buildPlaceholderOrError
(
BuildContext
context
,
Widget
?
child
)
{
final
error
=
_startException
;
class
_MobileScannerState
extends
State
<
MobileScanner
>
{
/// The current scan window.
Rect
?
scanWindow
;
if
(
error
!=
null
)
{
return
widget
.
errorBuilder
?.
call
(
context
,
error
,
child
)
??
const
ColoredBox
(
color:
Colors
.
black
,
child:
Center
(
child:
Icon
(
Icons
.
error
,
color:
Colors
.
white
)),
/// Recalculate the scan window based on the updated [constraints].
void
_maybeUpdateScanWindow
(
MobileScannerState
scannerState
,
BoxConstraints
constraints
)
{
if
(
widget
.
scanWindow
!=
null
&&
scanWindow
==
null
)
{
scanWindow
=
calculateScanWindowRelativeToTextureInPercentage
(
widget
.
fit
,
widget
.
scanWindow
!,
textureSize:
scannerState
.
size
,
widgetSize:
constraints
.
biggest
,
);
}
return
widget
.
placeholderBuilder
?.
call
(
context
,
child
)
??
const
ColoredBox
(
color:
Colors
.
black
);
unawaited
(
widget
.
controller
.
updateScanWindow
(
scanWindow
));
}
/// Start the given [scanner].
Future
<
void
>
_startScanner
()
async
{
if
(
widget
.
startDelay
)
{
await
Future
.
delayed
(
const
Duration
(
seconds:
1
,
milliseconds:
500
));
}
_barcodesSubscription
??=
_controller
.
barcodes
.
listen
(
widget
.
onDetect
,
);
@override
Widget
build
(
BuildContext
context
)
{
return
ValueListenableBuilder
<
MobileScannerState
>(
valueListenable:
widget
.
controller
,
builder:
(
BuildContext
context
,
MobileScannerState
value
,
Widget
?
child
)
{
if
(!
value
.
isInitialized
)
{
const
Widget
defaultPlaceholder
=
ColoredBox
(
color:
Colors
.
black
);
if
(!
_controller
.
autoStart
)
{
debugPrint
(
'mobile_scanner: not starting automatically because autoStart is set to false in the controller.'
,
);
return
;
return
widget
.
placeholderBuilder
?.
call
(
context
,
child
)
??
defaultPlaceholder
;
}
_controller
.
start
().
then
((
arguments
)
{
// ignore: deprecated_member_use_from_same_package
widget
.
onStart
?.
call
(
arguments
);
widget
.
onScannerStarted
?.
call
(
arguments
);
}).
catchError
((
error
)
{
if
(!
mounted
)
{
return
;
}
final
MobileScannerException
?
error
=
value
.
error
;
if
(
error
is
MobileScannerException
)
{
_startException
=
error
;
}
else
if
(
error
is
PlatformException
)
{
_startException
=
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
errorDetails:
MobileScannerErrorDetails
(
code:
error
.
code
,
message:
error
.
message
,
details:
error
.
details
,
),
);
}
else
{
_startException
=
MobileScannerException
(
errorCode:
MobileScannerErrorCode
.
genericError
,
errorDetails:
MobileScannerErrorDetails
(
details:
error
,
),
if
(
error
!=
null
)
{
const
Widget
defaultError
=
ColoredBox
(
color:
Colors
.
black
,
child:
Center
(
child:
Icon
(
Icons
.
error
,
color:
Colors
.
white
)),
);
}
setState
(()
{});
});
return
widget
.
errorBuilder
?.
call
(
context
,
error
,
child
)
??
defaultError
;
}
@override
void
initState
()
{
super
.
initState
();
WidgetsBinding
.
instance
.
addObserver
(
this
);
_controller
=
widget
.
controller
??
MobileScannerController
();
_startScanner
();
}
@override
void
didChangeAppLifecycleState
(
AppLifecycleState
state
)
{
// App state changed before the controller was initialized.
if
(
_controller
.
isStarting
)
{
return
;
}
switch
(
state
)
{
case
AppLifecycleState
.
resumed
:
if
(
_resumeFromBackground
)
{
_startScanner
();
}
case
AppLifecycleState
.
inactive
:
_resumeFromBackground
=
true
;
_controller
.
stop
();
default
:
break
;
}
}
Rect
?
scanWindow
;
@override
Widget
build
(
BuildContext
context
)
{
return
LayoutBuilder
(
builder:
(
context
,
constraints
)
{
return
ValueListenableBuilder
<
MobileScannerArguments
?>(
valueListenable:
_controller
.
startArguments
,
builder:
(
context
,
value
,
child
)
{
if
(
value
==
null
)
{
return
_buildPlaceholderOrError
(
context
,
child
);
}
_maybeUpdateScanWindow
(
value
,
constraints
);
if
(
widget
.
scanWindow
!=
null
&&
scanWindow
==
null
)
{
scanWindow
=
calculateScanWindowRelativeToTextureInPercentage
(
widget
.
fit
,
widget
.
scanWindow
!,
textureSize:
value
.
size
,
widgetSize:
constraints
.
biggest
,
final
Widget
?
overlay
=
widget
.
overlayBuilder
?.
call
(
context
,
constraints
);
final
Size
cameraPreviewSize
=
value
.
size
;
final
Widget
scannerWidget
=
ClipRect
(
child:
SizedBox
.
fromSize
(
size:
constraints
.
biggest
,
child:
FittedBox
(
fit:
widget
.
fit
,
child:
SizedBox
(
width:
cameraPreviewSize
.
width
,
height:
cameraPreviewSize
.
height
,
child:
MobileScannerPlatform
.
instance
.
buildCameraView
(),
),
),
),
);
_controller
.
updateScanWindow
(
scanWindow
);
if
(
overlay
==
null
)
{
return
scannerWidget
;
}
if
(
widget
.
overlay
!=
null
)
{
return
Stack
(
alignment:
Alignment
.
center
,
children:
[
_scanner
(
value
.
size
,
value
.
webId
,
value
.
textureId
,
value
.
numberOfCameras
,
),
widget
.
overlay
!,
children:
<
Widget
>[
scannerWidget
,
overlay
,
],
);
}
else
{
return
_scanner
(
value
.
size
,
value
.
webId
,
value
.
textureId
,
value
.
numberOfCameras
,
);
}
},
);
},
);
}
Widget
_scanner
(
Size
size
,
String
?
webId
,
int
?
textureId
,
int
?
numberOfCameras
,
)
{
return
ClipRect
(
child:
LayoutBuilder
(
builder:
(
_
,
constraints
)
{
return
SizedBox
.
fromSize
(
size:
constraints
.
biggest
,
child:
FittedBox
(
fit:
widget
.
fit
,
child:
SizedBox
(
width:
size
.
width
,
height:
size
.
height
,
child:
kIsWeb
?
HtmlElementView
(
viewType:
webId
!)
:
Texture
(
textureId:
textureId
!),
),
),
);
},
),
);
}
@override
void
dispose
()
{
_controller
.
updateScanWindow
(
null
);
WidgetsBinding
.
instance
.
removeObserver
(
this
);
_barcodesSubscription
?.
cancel
();
_barcodesSubscription
=
null
;
_controller
.
dispose
();
// When this widget is unmounted, reset the scan window.
widget
.
controller
.
updateScanWindow
(
null
);
super
.
dispose
();
}
}
...
...
Please
register
or
login
to post a comment