deakjahn / flutter_dropzone

A drag-and-drop Flutter plugin (Web). Only web and only from outside into Flutter.
https://pub.dev/packages/flutter_dropzone
85 stars 36 forks source link

pickFiles not working on mac Safari #45

Closed ManishKiranagi closed 2 years ago

ManishKiranagi commented 2 years ago

I upgraded to Flutter 2.5.3 and am using Dropzone 3.0.3 I was previously on Flutter 1.25.0-8.3.pre and dropzone 1.0.9 After upgrade the controller.pickFiles has stopped working on Mac Safari - it doesnt return anything, no exceptions. This used to work on the previous version. I need urgent help on this as it is blocking my deployments.

deakjahn commented 2 years ago

Could you show some sample code that doesn't work? Also, some help about how to try it on Safari? I don't work on a Mac, although I do have a working virtual machine. Simply running flutter run doesn't start Safari though, it starts Chrome even on a Mac. Maybe you could also check what the browser dev tools console window says?

ManishKiranagi commented 2 years ago

@deakjahn I used the example code on https://pub.dev/packages/flutter_dropzone/example and added a TextButton for the file picking Expanded( child: Stack( children: [ buildZone2(context), Center(child: Text(message2)), TextButton( onPressed: () async { print('before'); var files = await controller1.pickFiles(); print('after'); print(files); }, child: const Text('Add'), ) ], ), )

flutter run --release and then open that link in Safari.. you should be able to see the log in the Safari Console which only prints 'before' Chrome prints 'before', 'after' and the file object

After some investigation - using https://github.com/flutter/flutter/issues/74644 and https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only it appears that input elements need to be part of the dom to have effect. I tested this by changing flutter_dropzone_web.dart to add document.body!.append(picker) and it seems to work consistently on Safari and Chrome

Future<List<dynamic>> pickFiles(bool multiple) { final completer = Completer<List<dynamic>>(); final picker = FileUploadInputElement(); document.body!.append(picker); picker.multiple = multiple; picker.onChange.listen((_) => completer.complete(picker.files)); picker.click(); return completer.future; }

deakjahn commented 2 years ago

Do you use CanvasKit or DomCanvas?

deakjahn commented 2 years ago

Running the same URL while Chrome is running it? Interesting idea, never occurred to me. Yes, it works.

Oops, I get both before and after. Now what? :-) I'll try to include a screenshot but first I have to find out how.

Screenshot 2021-12-07 204917

deakjahn commented 2 years ago

Catalina, Safari 14.0.

deakjahn commented 2 years ago

Shall we try to put that append() with a condition if we can actually test for Safari? What's your opinion? I have a code somewhere else that determines the browser, I'll dig it up but will it be reliable?

ManishKiranagi commented 2 years ago

@deakjahn Yes adding append with a condition will be fantastic if possible. Just one thing - can that element be removed from the DOM in a dispose or something

ManishKiranagi commented 2 years ago

I use CanvasKit, but same problem with html renderer as well.

deakjahn commented 2 years ago

The browser code is on the Dart side in another app of mine, I don't knwo where it came from, probably JS and transposed to Dart.

  String _getBrowser() {
    final userAgent = html.window.navigator.userAgent;
    var browser = html.window.navigator.appName;
    var version = html.window.navigator.appVersion;

    int verOffset, nameOffset;
    // Opera
    if ((verOffset = userAgent.indexOf('Opera')) != -1) {
      browser = 'Opera';
      version = userAgent.substring(verOffset + 6);
      if ((verOffset = userAgent.indexOf('Version')) != -1) {
        version = userAgent.substring(verOffset + 8);
      }
    }
    // Opera Next
    if ((verOffset = userAgent.indexOf('OPR')) != -1) {
      browser = 'Opera';
      version = userAgent.substring(verOffset + 4);
    }
    // Edge
    else if ((verOffset = userAgent.indexOf('Edge')) != -1) {
      browser = 'Microsoft Edge';
      version = userAgent.substring(verOffset + 5);
    }
    // MSIE
    else if ((verOffset = userAgent.indexOf('MSIE')) != -1) {
      browser = 'Microsoft Internet Explorer';
      version = userAgent.substring(verOffset + 5);
    }
    // Chrome
    else if ((verOffset = userAgent.indexOf('Chrome')) != -1) {
      browser = 'Chrome';
      version = userAgent.substring(verOffset + 7);
    }
    // Safari
    else if ((verOffset = userAgent.indexOf('Safari')) != -1) {
      browser = 'Safari';
      version = userAgent.substring(verOffset + 7);
      if ((verOffset = userAgent.indexOf('Version')) != -1) {
        version = userAgent.substring(verOffset + 8);
      }
    }
    // Firefox
    else if ((verOffset = userAgent.indexOf('Firefox')) != -1) {
      browser = 'Firefox';
      version = userAgent.substring(verOffset + 8);
    }
    // MSIE 11+
    else if (userAgent.contains('Trident/')) {
      browser = 'Microsoft Internet Explorer';
      version = userAgent.substring(userAgent.indexOf('rv:') + 3);
    }
    // Other browsers
    else if ((nameOffset = userAgent.lastIndexOf(' ') + 1) < (verOffset = userAgent.lastIndexOf('/'))) {
      browser = userAgent.substring(nameOffset, verOffset);
      version = userAgent.substring(verOffset + 1);
      if (browser.toLowerCase() == browser.toUpperCase()) {
        browser = html.window.navigator.appName;
      }
    }

    return '$browser ($version)';
  }

The Safari part specifically extracted?

deakjahn commented 2 years ago

That's just looking for "Safari" in the UA string, nothing else.

deakjahn commented 2 years ago

But why do I receive an "after" in my Safari? :-)

deakjahn commented 2 years ago

I have this but I can't really tell because it works for me both with and without:

Future<List<dynamic>> pickFiles(bool multiple) {
  final completer = Completer<List<dynamic>>();
  final picker = FileUploadInputElement();
  final isSafari = window.navigator.userAgent.contains('Safari');
  if (isSafari) document.body!.append(picker);
  picker.multiple = multiple;
  picker.onChange.listen((_) {
    completer.complete(picker.files);
    if (isSafari) picker.remove();
  });
  picker.click();
  return completer.future;
}

Could you check if this works for you?

ManishKiranagi commented 2 years ago

@deakjahn I have tested and it works perfectly. Could you do lowercase check for Safari instead window.navigator.userAgent.toLowerCase().contains('safari') I have tested that and it works as well

Could you tell me if you are going to publish a pre-release version?

deakjahn commented 2 years ago

If you say it works I can't see any reason why not to push it.

deakjahn commented 2 years ago

3.0.5 it is.

ManishKiranagi commented 2 years ago

Thanks.. will try now

ManishKiranagi commented 2 years ago

@deakjahn Getting this.. on flutter pub get depends on flutter_dropzone ^3.0.5 which doesn't match any versions, version solving failed.

deakjahn commented 2 years ago

Did you happen to change the version manually? You don't have to... The main plugin doesn't have 3.0.5 yet, only the web sub-plugin has. And you don't need to change anything anywhere, just update the current one and you'll get it automatically.

deakjahn commented 2 years ago

Leave

flutter_dropzone: ^3.0.3

in your pubspec, let the system sort out the rest.

ManishKiranagi commented 2 years ago

OK. I had to delete flutter_dropzone: ^3.0.3 and flutter pub get, then add again flutter_dropzone: ^3.0.3 and flutter pub get and then in the pub cache I see dropzoneweb 3.0.5!

deakjahn commented 2 years ago

Yes. This is a so-called federated plugin, one that has a main one and two additional ones (could even have more than two, of course). You only need to bother with the main plugin, the rest is upgraded automatically. Unless there is a major, breaking change (eg. 3.1.0 or even 4.0.0), you always get the latest versions of both the main plugin and the dependent ones, even if, for instance, the main plugin still technically refers to web 3.0.4 and not 3.0.5 (I'll change that with the next coming push of the main plugin but there's no rush because it works as is, automatically).