freedelity / flutter_native_barcode_scanner

Fast barcode/QR scanner flutter plugin using PlatformView and processing on native side
https://pub.dev/packages/native_barcode_scanner
BSD 3-Clause "New" or "Revised" License
6 stars 8 forks source link

camera opening with bug for iOS 16 and 17 #11

Closed renehw closed 10 months ago

renehw commented 1 year ago

I found a bug when opening the code reader on iPhone, specifically iOS 16.6.1 and 17.0.3. When opening, the screen is divided into half with the camera open and the other half with a black stripe.

[✓] Flutter (Channel stable, 3.13.6, on Ubuntu 22.04.3 LTS 6.2.0-35-generic, locale pt_BR.UTF-8)
    • Flutter version 3.13.6 on channel stable at /home/rene/snap/flutter/common/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ead455963c (4 weeks ago), 2023-09-26 18:28:17 -0700
    • Engine revision a794cf2681
    • Dart version 3.1.3
    • DevTools version 2.25.0

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /home/rene/Android/Sdk
    • Platform android-34, build-tools 34.0.0
    • Java binary at: /snap/android-studio/128/android-studio/jbr/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)
    • All Android licenses accepted.

[✓] Chrome - develop for the web
    • Chrome at google-chrome

[✓] Linux toolchain - develop for Linux desktop
    • clang version 10.0.0-4ubuntu1
    • cmake version 3.16.3
    • ninja version 1.10.0
    • pkg-config version 0.29.1

[✓] Android Studio (version 2022.3)
    • Android Studio at /snap/android-studio/128/android-studio
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b829.9-10027231)

[✓] VS Code (version unknown)
    • VS Code at /snap/code/current
    • Flutter extension version 3.74.0
    ✗ Unable to determine VS Code version.

[✓] Connected device (3 available)
    • sdk gphone x86 64 (mobile) • emulator-5554 • android-x64    • Android 13 (API 33) (emulator)
    • Linux (desktop)            • linux         • linux-x64      • Ubuntu 22.04.3 LTS 6.2.0-35-generic
    • Chrome (web)               • chrome        • web-javascript • Google Chrome 118.0.5993.70

[✓] Network resources
    • All expected network resources are available.

• No issues found!

image

Verbruik commented 1 year ago

Hi @renehw ! Can you check that the last modifications fix your bug ? It actually can't dynamically set the orientation, but you can manage this directly from your widget which implements BarcodeScannerWidget by setting the orientation parameter.

renehw commented 1 year ago

Hello!

I will update and test. I'll get back to you as soon as I finish the tests, but I believe everything is ok. Thanks!

renehw commented 1 year ago

Hello @Verbruik !

I ran the tests again and the problem still persists.

renehw commented 1 year ago

I'm sorry, I hadn't paid attention to the message, so I ended up not implementing the new parameter. I'll make the adjustment and test again.

Verbruik commented 1 year ago

Hello @renehw ! Did you test with the new parameter ?

renehw commented 1 year ago

Hello @Verbruik , I used the parameter you mentioned "orientation", but the problem still occurs.

Verbruik commented 1 year ago

Ok, just to precise, the new parameter "orientation" doesn't manage the orientation dynamically, it's an initialization param which set the orientation of the camera when the view is created. I'll certainly add the possibility to track the orientation and set it dynamically later but right now i'm out of time for that :sweat_smile:

Can you ensure me that the orientation is not set when the view is created ?

renehw commented 1 year ago

I was using the following code in the initState of the screen that renders the camera:

@override
   void initState() {
     SystemChrome.setPreferredOrientations([
       DeviceOrientation.landscapeLeft,
       DeviceOrientation.landscapeRight,
     ]);

     super.initState();
   }
renehw commented 1 year ago

Any news?

Verbruik commented 1 year ago

Hi ! I'm currently working on the plugin and will check this asap. Sorry for the waiting :sweat_smile:

renehw commented 1 year ago

No problem, if you need more details you can ask me.

Perhaps information that was not clear in my last message, but the code snippet I sent causes the screen to be automatically rotated when starting the screen where the camera widget is called. More precisely, before the camera widget is initialized.

Verbruik commented 1 year ago

Hello !

I ran the example app and test with a forced orientation as you suggested and everything run fine with my two devices for testing, can you send me an example app which reproduce the bug ?

[✓] Connected device (3 available) • Mathieu's iPhone (mobile) • 0f2eb882713fb79a7a0cd535e5f62f586d7468af • ios • iOS 15.4.1 19E258

[✓] Connected device (3 available) • Mathieu’s iPhone (mobile) • 00008101-0015292922FA001E • ios • iOS 16.6.1 20G81

renehw commented 1 year ago

Hi!

This is the code I use in my app

class LeitorCodigo extends StatefulWidget {
  const LeitorCodigo({super.key});

  @override
  State<LeitorCodigo> createState() => _LeitorCodigoState();
}

class _LeitorCodigoState extends State<LeitorCodigo> {
  bool withOverlay = true;
  bool toggleFlash = false;

  @override
  void initState() {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
    ]);

    super.initState();
  }

  @override
  void dispose() {
    SystemChrome.setPreferredOrientations([
       DeviceOrientation.portraitUp,
       DeviceOrientation.portraitDown,
    ]);
    BarcodeScanner.stopScanner();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0.0,
        leading: IconButton(
          icon: const Icon(
            Icons.close,
            color: Colors.white,
          ),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
        actions: [
          IconButton(
            icon: Icon(
              toggleFlash == true ? Icons.flash_on : Icons.flash_off,
              color: Colors.white,
            ),
            tooltip: 'Flash',
            onPressed: () async {
              setState(() {
                toggleFlash = !toggleFlash;
              });
              await BarcodeScanner.toggleFlashlight();
            },
          ),
        ],
      ),
      body: Builder(
        builder: (context) {
          return SmartlookTrackingWidget(
            isSensitive: true,
            child: Stack(
              children: [
                BarcodeScannerWidget(
                  orientation: CameraOrientation.landscapeLeft,
                  startScanning: true,
                  stopScanOnBarcodeDetected: true,
                  onBarcodeDetected: (barcode) {
                    log('leitura realizada');
                    Navigator.pop(context, barcode.value);
                  },
                  onError: (error) {
                    log('Erro na leitura');
                  },
                ),
                Align(alignment: Alignment.center, child: Divider(color: Colors.red[400], thickness: 0.8)),
                ColorFiltered(
                  colorFilter: ColorFilter.mode(
                    Colors.black.withOpacity(0.4),
                    BlendMode.srcOut,
                  ),
                  child: Stack(
                    children: [
                      Container(
                        decoration: const BoxDecoration(
                          color: Colors.black,
                          backgroundBlendMode: BlendMode.dstOut,
                        ),
                      ),
                      Center(
                        child: AnimatedContainer(
                          width: MediaQuery.sizeOf(context).width * 1,
                          height: MediaQuery.sizeOf(context).height * 0.55,
                          color: Colors.white,
                          margin: const EdgeInsets.only(top: 10.0),
                          duration: const Duration(milliseconds: 150),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}
Verbruik commented 1 year ago

Hi ! Thanks to your code I found where is the problem and I'm happy to say that the plugin works as it should :-)

Because you set the SystemChrome.orientation value before this screen, the async call to setPreferredOrientations to landscape is not over when the BarcodeScannerPlugin is intialized and it takes the value of the screen in portrait mode, you can check that the switch is over before initialize the Scanner and it works well !

Add a startScanner variable and set it when the orientation is set, I'm not 100% sure that's the proper way to do it, but it still work anyway... 😅


class _LeitorCodigoState extends State<LeitorCodigo> {
  bool withOverlay = true;
  bool toggleFlash = false;

  bool startScanner = false;

  @override
  void initState() {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
    ]);

    super.initState();
  }

  @override
  void dispose() {
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
    BarcodeScanner.stopScanner();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint('################## ${MediaQuery.sizeOf(context).width}');
    if (!startScanner && MediaQuery.sizeOf(context).width > MediaQuery.sizeOf(context).height) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        debugPrint('####################### start scanner !!!!!!');
        if (context.mounted) setState(() => startScanner = true);
      });
    }
    return Scaffold(
      backgroundColor: Colors.black,
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0.0,
        leading: IconButton(
          icon: const Icon(
            Icons.close,
            color: Colors.white,
          ),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
        actions: [
          IconButton(
            icon: Icon(
              toggleFlash == true ? Icons.flash_on : Icons.flash_off,
              color: Colors.white,
            ),
            tooltip: 'Flash',
            onPressed: () async {
              setState(() {
                toggleFlash = !toggleFlash;
              });
              await BarcodeScanner.toggleFlashlight();
            },
          ),
        ],
      ),
      body: Builder(
        builder: (context) {
          return Stack(
            children: [
              !startScanner ? Container() : BarcodeScannerWidget(
                orientation: CameraOrientation.landscapeLeft,
                startScanning: true,
                stopScanOnBarcodeDetected: true,
                onBarcodeDetected: (barcode) {
                  print('leitura realizada');
                  Navigator.pop(context, barcode.value);
                },
                onError: (error) {
                  print('Erro na leitura');
                },
              ),
              Align(alignment: Alignment.center, child: Divider(color: Colors.red[400], thickness: 0.8)),
              ColorFiltered(
                colorFilter: ColorFilter.mode(
                  Colors.black.withOpacity(0.4),
                  BlendMode.srcOut,
                ),
                child: Stack(
                  children: [
                    Container(
                      decoration: const BoxDecoration(
                        color: Colors.black,
                        backgroundBlendMode: BlendMode.dstOut,
                      ),
                    ),
                    Center(
                      child: AnimatedContainer(
                        width: MediaQuery.sizeOf(context).width * 1,
                        height: MediaQuery.sizeOf(context).height * 0.55,
                        color: Colors.white,
                        margin: const EdgeInsets.only(top: 10.0),
                        duration: const Duration(milliseconds: 150),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
renehw commented 1 year ago

Hello @Verbruik , thank you for your help, I suspected it could be this, but as it was working fine on Android I expected it to be something from the package, and unfortunately as I don't have a physical iOS device, I wasn't able to test whether it could be this delay situation, thanks for the clarification, I will try to provide a device to perform the adjustment.

Thanks again, and I appreciate the package, I noticed that it provides better performance than others due to the use of recent SDKs, thank you and great work!!!

ndusart commented 1 year ago

Hello guys,

For a side-note, if you depend on a future to build your widget, you should consider the FutureBuilder which will handle this specific case. :wink:

class _LeitorCodigoState extends State<LeitorCodigo> {
  // Calls the future (either in the field initialization as here or in initState)
  final Future<void> _orientationFuture =  SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
    ]);

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<void>(
      future: _orientationFuture,
      builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          // The future has completed.
        } else {
          // Future not complete yet
        }
    },
    );
  }
}
renehw commented 12 months ago

A new problem has been identified, it appears that iOS now does not respect the guidance.

The following image shows the phone in portrait mode, but the camera is in landscape mode, the image being captured is also in portrait mode, and not as in the capture. Strangely what I did was remove the code orientation: CameraOrientation.landscapeLeft, from the BarcodeScannerWidget.

Some help?

my initState and dispose are like this:

@override
   void initState() {
     SystemChrome.setPreferredOrientations([
       DeviceOrientation.landscapeLeft,
       DeviceOrientation.landscapeRight,
     ]);

     super.initState();
   }

   @override
   void dispose() {
     SystemChrome.setPreferredOrientations([
       DeviceOrientation.portraitUp,
       DeviceOrientation.portraitDown,
     ]);
     BarcodeScanner.stopScanner();
     super.dispose();
   }

image

renehw commented 11 months ago

Hello!

Any solution?

Verbruik commented 11 months ago

Hello !

I think what you pointed as a new problem is the desired behavior. I'm agree that it's not the best solution, and it will be fixed later, but ATM the orientation is not set dynamically on iOS native side, so you must set the orientation for BarcodeScannerWidget if you want to change the camera orientation.

Does this answer your question ? :sweat_smile:

renehw commented 11 months ago

Hello

This BarcodeScannerWidget orientation would be orientation: CameraOrientation.landscapeLeft,. The parameter you added some time ago?

If this is the case, I've already added it, and this causes another bug where when the camera is opened, the image is rendered upside down.

renehw commented 11 months ago

@Verbruik

Hello!

Has there been any progress on the problem?

Verbruik commented 11 months ago

Hi !

I'm sorry that it's taking so long but I'm quite busy for the moment :sweat:

Be sure that I don't forget you and will fix this asap !

Verbruik commented 10 months ago

Hi @renehw !

This BarcodeScannerWidget orientation would be orientation: CameraOrientation.landscapeLeft,. The parameter you added some time ago?

If this is the case, I've already added it, and this causes another bug where when the camera is opened, the image is rendered upside down.

I think there is a confusion between landscapeLeft and landscapeRight, if the screen is upside down it's probably because you try to set the orientation to the wrong one. The problem here is that Flutter doesn't given a proper way to get the device orientation (left or right), so there is a couple of solutions for you :

I surely implement a detection of the orientation later on the plugin but right now I don't have time for that. If you are determinate and have a little bit of time to share with us, feel free to create a new MR with the code which detect the screen orientation 😄

Thanks for the report, if you have and other issue don't hesitate to create a new issue or reopen this one if the problem still with those suggestions 😉