달력

5

« 2024/5 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2021. 11. 11. 11:58

webview_flutter로 alert/confirm 띄우기 (iOS) 개발/Flutter2021. 11. 11. 11:58

반응형

이전에도 비슷한 주제로  글을 작성한 적이 있었는데 이번엔 해결법이 약간 다르다.

(이전 글 : https://risha-lee.tistory.com/40)

 

이번에는 상황이 Confirm 종류도 많았고,

그냥 웹뷰를 보여주는 앱이었으며
웹은 이미 작성된지 오래된 상태였고

Confirm 이후 분기 처리도 꽤 복잡한 편이어서 꼭 웹의 Confirm 기능을 써야만 했다.

 

일단 Android는 webview_flutter를 2.0.13으로 버전 업하니 alert가 기본으로 작동하게 되었는데 

iOS에서는 여전히 alert/confirm이 안뜨는 문제가 있었다.

찾아보니 역시나 webview_flutter 자체 지원은 안되고 파일 업로드 때 처럼 직접 소스 코드를 수정하기로 하였다.

 

1. 먼저 webview_flutter가 저장되는 위치를 찾는다.

나는 mac 환경이고 webview_flutter 버전은 2.0.13을 사용하였기 때문에

/Users/${userId}/.pub-cache/hosted/pub.dartlang.org/webview_flutter-2.0.13

여기에 저장되어 있었다.

 

2. webview_flutter-2.0.13/ios/Classes/FlutterWebView.m 의 맨 하단 @end 이전에

다음을 추가한다.

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
  [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  completionHandler();
  }])];

    UIViewController *_viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  [_viewController presentViewController:alertController animated:YES completion:nil];
}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
  //    DLOG(@"msg = %@ frmae = %@",message,frame);
  UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
  [alertController addAction:([UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    completionHandler(NO);
  }])];
  [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    completionHandler(YES);
  }])];

    UIViewController *_viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  [_viewController presentViewController:alertController animated:YES completion:nil];
}

 

 

runJavaScriptTextInputPanelWithPrompt라고 prompt 처리하는 것으로 보이는 것도 있긴한데

내가 작업하는 웹뷰에서 사용하지 않아서 확인하진 못하였으나 비슷한 방식으로 변환하면 다음과 같다.

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
  UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
  [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
    textField.text = defaultText;
  }];
  [alertController addAction:([UIAlertAction actionWithTitle:@"" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    completionHandler(alertController.textFields[0].text?:@"");
  }])];
  UIViewController *_viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  [_viewController presentViewController:alertController animated:YES completion:nil];
}

 

 

 

참고 출처 : https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/695084/

https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios

반응형
:
Posted by 리샤씨
2021. 11. 9. 12:52

webview_flutter로 file upload하기 (Android) 개발/Flutter2021. 11. 9. 12:52

반응형

최근 flutter로 웹뷰에서 파일 업로드해야 하는 일이 생겼는데 

Android webview_flutter(https://pub.dev/packages/webview_flutter)에서

그냥 input file 은 작동하지 않는 것을 확인하였다.

 

검색해보니 flutter_webview_plugin(https://pub.dev/packages/flutter_webview_plugin)를

이용하면 별도 작업없이 바로 되는 것 같아 보였다.

하지만 flutter_webview_plugin로는 이미 개발해둔 javasrcipt와 flutter의 통신

페이지 네비게이션 등 처리가 될 것 같지 않아보여서 다른 방법을 더 찾아보았다.

 

결국 직접적으로 webview_flutter의 소스를 건드는 방법을 알게되어 그 소스를 공유하도록 하겠다.

 

1. 먼저 webview_flutter가 저장되는 위치를 찾는다.

나는 mac 환경이고 webview_flutter 버전은 2.0.13을 사용하였기 때문에

/Users/${userId}/.pub-cache/hosted/pub.dartlang.org/webview_flutter-2.0.13

여기에 저장되어 있었다.

 

2. webview_flutter-2.0.13/android/src/main/AndroidManifest.xml를 아래 소스로 수정한다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="io.flutter.plugins.webviewflutter">
<application>
 <provider
     android:name="io.flutter.plugins.webviewflutter.GenericFileProvider"
     android:authorities="${applicationId}.generic.provider"
     android:exported="false"
     android:grantUriPermissions="true"
     >
     <meta-data
         android:name="android.support.FILE_PROVIDER_PATHS"
         android:resource="@xml/provider_paths"
         />
 </provider>
 <activity android:name="io.flutter.plugins.webviewflutter.FileChooserActivity">
 </activity>
 <activity android:name="io.flutter.plugins.webviewflutter.RequestCameraPermissionActivity">
 </activity>

 </application>
 <queries>
     <package android:name="com.google.android.apps.photos" />
     <package android:name="com.google.android.apps.docs" />
     <package android:name="com.google.android.documentsui" />
 </queries>
</manifest>

 

3. webview_flutter-2.0.13/android/src/main/res/values/strings.xml를 추가한다.

경로에 폴더가 없다면 폴더도 추가한다.

<resources>
    <string name="webview_file_chooser_title">Choose a file</string>
    <string name="webview_image_chooser_title">Choose an image</string>
    <string name="webview_video_chooser_title">Choose a video</string>
</resources>

 

4. webview_flutter-2.0.13/android/src/main/res/xml/provider_paths.xml를 추가한다.

경로에 폴더가 없다면 폴더도 추가한다.

 <paths xmlns:android="http://schemas.android.com/apk/res/android">
     <external-files-path name="safetyapp_images" path="images" />
 </paths>

 

5. webview_flutter-2.0.13/android/src/main/java/io/flutter/plugins/webviewflutter/Constants.java를 추가합니다.

package io.flutter.plugins.webviewflutter;

public class Constants {
    static final String ACTION_REQUEST_CAMERA_PERMISSION_FINISHED =
            "action_request_camera_permission_denied";
    static final String ACTION_FILE_CHOOSER_FINISHED = "action_file_chooser_completed";

    static final String EXTRA_TITLE = "extra_title";
    static final String EXTRA_ACCEPT_TYPES = "extra_types";
    static final String EXTRA_SHOW_VIDEO_OPTION = "extra_show_video_option";
    static final String EXTRA_SHOW_IMAGE_OPTION = "extra_show_image_option";
    static final String EXTRA_FILE_URIS = "extra_file_uris";
    static final String EXTRA_ALLOW_MULTIPLE_FILES = "extra_allow_multiple_files";


    static final String WEBVIEW_STORAGE_DIRECTORY = "images";
}

 

6. webview_flutter-2.0.13/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserActivity.java를 추가한다.

package io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;
import static io.flutter.plugins.webviewflutter.Constants.WEBVIEW_STORAGE_DIRECTORY;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

public class FileChooserActivity extends Activity {

private static final int FILE_CHOOSER_REQUEST_CODE = 12322;
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");

// List of Uris that point to files where there MIGHT be the output of the capture. At most one of these can be valid
private final ArrayList<Uri> potentialCaptureOutputUris = new ArrayList<>();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    showFileChooser(
            getIntent().getBooleanExtra(EXTRA_SHOW_IMAGE_OPTION, false),
            getIntent().getBooleanExtra(EXTRA_SHOW_VIDEO_OPTION, false));
}

private void showFileChooser(boolean showImageIntent, boolean showVideoIntent) {
    Intent getContentIntent = createGetContentIntent();
    Intent captureImageIntent =
            showImageIntent ? createCaptureIntent(MediaStore.ACTION_IMAGE_CAPTURE, "jpg") : null;
    Intent captureVideoIntent =
            showVideoIntent ? createCaptureIntent(MediaStore.ACTION_VIDEO_CAPTURE, "mp4") : null;

    if (getContentIntent == null && captureImageIntent == null && captureVideoIntent == null) {
        // cannot open anything: cancel file chooser
        sendBroadcast(new Intent(ACTION_FILE_CHOOSER_FINISHED));
        finish();
    } else {
        ArrayList<Intent> intentList = new ArrayList<>();

        if (getContentIntent != null) {
            intentList.add(getContentIntent);
        }

        if (captureImageIntent != null) {
            intentList.add(captureImageIntent);
        }
        if (captureVideoIntent != null) {
            intentList.add(captureVideoIntent);
        }

        Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
        chooserIntent.putExtra(Intent.EXTRA_TITLE, getIntent().getStringExtra(EXTRA_TITLE));

        chooserIntent.putExtra(Intent.EXTRA_INTENT, intentList.get(0));
        intentList.remove(0);
        if (intentList.size() > 0) {
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new Intent[0]));
        }

        startActivityForResult(chooserIntent, FILE_CHOOSER_REQUEST_CODE);
    }
}

private Intent createGetContentIntent() {
    Intent filesIntent = new Intent(Intent.ACTION_GET_CONTENT);

    if (getIntent().getBooleanExtra(EXTRA_ALLOW_MULTIPLE_FILES, false)) {
        filesIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    }

    String[] acceptTypes = getIntent().getStringArrayExtra(EXTRA_ACCEPT_TYPES);

    if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
        // empty array or only 1 empty string? -> accept all types
        filesIntent.setType("*/*");
    } else if (acceptTypes.length == 1) {
        filesIntent.setType(acceptTypes[0]);
    } else {
        // acceptTypes.length > 1
        filesIntent.setType("*/*");
        filesIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
    }

    return (filesIntent.resolveActivity(getPackageManager()) != null) ? filesIntent : null;
}

private Intent createCaptureIntent(String type, String fileFormat) {
    Intent captureIntent = new Intent(type);
    if (captureIntent.resolveActivity(getPackageManager()) == null) {
        return null;
    }

    // Create the File where the output should go
    Uri captureOutputUri = getTempUri(fileFormat);
    potentialCaptureOutputUris.add(captureOutputUri);

    captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureOutputUri);

    return captureIntent;
}
private File getStorageDirectory() {
    File imageDirectory = new File(this.getExternalFilesDir(null), WEBVIEW_STORAGE_DIRECTORY);
    if (!imageDirectory.isDirectory()) {
        imageDirectory.mkdir();
    }
    return imageDirectory;
}


private Uri getTempUri(String format) {
    String fileName = "CAPTURE-" + simpleDateFormat.format(new Date()) + "." + format;
    File file = new File(getStorageDirectory(), fileName);
    return FileProvider.getUriForFile(
            this, getApplicationContext().getPackageName() + ".generic.provider", file);
}

private String getFileNameFromUri(Uri uri) {
    Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
    assert returnCursor != null;
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    returnCursor.moveToFirst();
    String name = returnCursor.getString(nameIndex);
    returnCursor.close();
    return name;
}

private Uri copyToLocalUri(Uri uri) {
    File destination = new File(getStorageDirectory(), getFileNameFromUri(uri));

    try (InputStream in = getContentResolver().openInputStream(uri);
         OutputStream out = new FileOutputStream(destination)) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        return FileProvider.getUriForFile(
                this, getApplicationContext().getPackageName() + ".generic.provider", destination);
    } catch (IOException e) {
        Log.e("WEBVIEW", "Unable to copy selected image", e);
        e.printStackTrace();
        return null;
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FILE_CHOOSER_REQUEST_CODE) {
        Intent fileChooserFinishedIntent = new Intent(ACTION_FILE_CHOOSER_FINISHED);
        if (resultCode == Activity.RESULT_OK) {
            if (data != null && (data.getDataString() != null || data.getClipData() != null)) {
                if (data.getDataString() != null) {
                    // single result from file browser OR video from camera
                    Uri localUri = copyToLocalUri(data.getData());
                    if (localUri != null) {
                        fileChooserFinishedIntent.putExtra(
                                EXTRA_FILE_URIS, new String[] {localUri.toString()});
                    }
                } else if (data.getClipData() != null) {
                    // multiple results from file browser
                    int uriCount = data.getClipData().getItemCount();
                    String[] uriStrings = new String[uriCount];

                    for (int i = 0; i < uriCount; i++) {
                        Uri localUri = copyToLocalUri(data.getClipData().getItemAt(i).getUri());
                        if (localUri != null) {
                            uriStrings[i] = localUri.toString();
                        }
                    }
                    fileChooserFinishedIntent.putExtra(EXTRA_FILE_URIS, uriStrings);
                }
            } else {
                // image result from camera (videos from the camera are handled above, but this if-branch could handle them too if this varies from device to device)
                for (Uri captureOutputUri : potentialCaptureOutputUris) {
                    try {
                        // just opening an input stream (and closing immediately) to test if the Uri points to a valid file
                        // if it's not a real file, the below catch-clause gets executed and we continue with the next Uri in the loop.
                        getContentResolver().openInputStream(captureOutputUri).close();
                        fileChooserFinishedIntent.putExtra(
                                EXTRA_FILE_URIS, new String[] {captureOutputUri.toString()});
                        // leave the loop, as only one of the potentialCaptureOutputUris is valid and we just found it
                        break;
                    } catch (IOException ignored) {
                    }
                }
            }
        }
        sendBroadcast(fileChooserFinishedIntent);
        finish();
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
   }
 }

 

7. webview_flutter-2.0.13/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserLauncher.java를 추가한다.

package io.flutter.plugins.webviewflutter;

import static io.flutter.plugins.webviewflutter.Constants.ACTION_FILE_CHOOSER_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ACCEPT_TYPES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_ALLOW_MULTIPLE_FILES;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_FILE_URIS;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_IMAGE_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_SHOW_VIDEO_OPTION;
import static io.flutter.plugins.webviewflutter.Constants.EXTRA_TITLE;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.webkit.ValueCallback;
import androidx.core.content.ContextCompat;

public class FileChooserLauncher extends BroadcastReceiver {

private Context context;
private String title;
private boolean allowMultipleFiles;
private boolean videoAcceptable;
private boolean imageAcceptable;
private ValueCallback<Uri[]> filePathCallback;
private String[] acceptTypes;

public FileChooserLauncher(
        Context context,
        boolean allowMultipleFiles,
        ValueCallback<Uri[]> filePathCallback,
        String[] acceptTypes) {
    this.context = context;
    this.allowMultipleFiles = allowMultipleFiles;
    this.filePathCallback = filePathCallback;
    this.acceptTypes = acceptTypes;

    if (acceptTypes.length == 0 || (acceptTypes.length == 1 && acceptTypes[0].length() == 0)) {
        // acceptTypes empty -> accept anything
        imageAcceptable = true;
        videoAcceptable = true;
    } else {
        for (String acceptType : acceptTypes) {
            if (acceptType.startsWith("image/")) {
                imageAcceptable = true;
            } else if (acceptType.startsWith("video/")) {
                videoAcceptable = true;
            }
        }
    }

    if (imageAcceptable && !videoAcceptable) {
        title = context.getResources().getString(R.string.webview_image_chooser_title);
    } else if (videoAcceptable && !imageAcceptable) {
        title = context.getResources().getString(R.string.webview_video_chooser_title);
    } else {
        title = context.getResources().getString(R.string.webview_file_chooser_title);
    }
}

private boolean canCameraProduceAcceptableType() {
    return imageAcceptable || videoAcceptable;
}

private boolean hasCameraPermission() {
    return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
            == PackageManager.PERMISSION_GRANTED;
}

public void start() {
    if (!canCameraProduceAcceptableType() || hasCameraPermission()) {
        showFileChooser();
    } else {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED);
        context.registerReceiver(this, intentFilter);

        Intent intent = new Intent(context, RequestCameraPermissionActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

private void showFileChooser() {
    IntentFilter intentFilter = new IntentFilter(ACTION_FILE_CHOOSER_FINISHED);
    context.registerReceiver(this, intentFilter);

    Intent intent = new Intent(context, FileChooserActivity.class);
    intent.putExtra(EXTRA_TITLE, title);
    intent.putExtra(EXTRA_ACCEPT_TYPES, acceptTypes);
    intent.putExtra(EXTRA_SHOW_IMAGE_OPTION, imageAcceptable && hasCameraPermission());
    intent.putExtra(EXTRA_SHOW_VIDEO_OPTION, videoAcceptable && hasCameraPermission());
    intent.putExtra(EXTRA_ALLOW_MULTIPLE_FILES, allowMultipleFiles);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

@Override
public void onReceive(Context context, Intent intent) {
    if (intent.getAction().equals(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED)) {
        context.unregisterReceiver(this);
        showFileChooser();
    } else if (intent.getAction().equals(ACTION_FILE_CHOOSER_FINISHED)) {
        String[] uriStrings = intent.getStringArrayExtra(EXTRA_FILE_URIS);
        Uri[] result = null;

        if (uriStrings != null) {
            int uriStringCount = uriStrings.length;
            result = new Uri[uriStringCount];

            for (int i = 0; i < uriStringCount; i++) {
                result[i] = Uri.parse(uriStrings[i]);
            }
        }

        filePathCallback.onReceiveValue(result);
        context.unregisterReceiver(this);
        filePathCallback = null;
    }
  }
 }

 

8. webview_flutter-2.0.13/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java

class FlutterWebChromeClient안에 메소드를 추가한다.

import android.net.Uri;
import android.webkit.ValueCallback;


    @Override
    public boolean onShowFileChooser(
            WebView webView,
            ValueCallback<Uri[]> filePathCallback,
            FileChooserParams fileChooserParams) {
      // info as of 2021-03-08:
      // don't use fileChooserParams.getTitle() as it is (always? on                Mi 9T Pro Android 10 at least) null
      // don't use fileChooserParams.isCaptureEnabled() as it is (always? on Mi 9T Pro Android 10 at least) false, even when the file upload allows images or any file
      final Context context = webView.getContext();
      final boolean allowMultipleFiles = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
              && fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
      final String[] acceptTypes = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
              ? fileChooserParams.getAcceptTypes() : new String[0];
      new FileChooserLauncher(context, allowMultipleFiles, filePathCallback, acceptTypes)
              .start();
      return true;
    }

 

9. webview_flutter-2.0.13/android/src/main/java/io/flutter/plugins/webviewflutter/RequestCameraPermissionActivity.java를 추가합니다.

package io.flutter.plugins.webviewflutter;

import android.Manifest; 
import android.app.Activity; 
import android.content.Intent; 
import android.os.Bundle; 
import androidx.annotation.NonNull; 
import androidx.annotation.Nullable; 
import androidx.core.app.ActivityCompat;
import static io.flutter.plugins.webviewflutter.Constants.ACTION_REQUEST_CAMERA_PERMISSION_FINISHED;

public class RequestCameraPermissionActivity extends Activity {

    private static final int CAMERA_PERMISSION_REQUEST_CODE = 12321;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityCompat.requestPermissions(
                this, new String[] {Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            sendBroadcast(new Intent(ACTION_REQUEST_CAMERA_PERMISSION_FINISHED));
            finish();
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

 

10. webview_flutter-2.0.13/android/src/main/java/io/flutter/plugins/webviewflutter/GenericFileProvider.java를 추가한다.

package io.flutter.plugins.webviewflutter;

import androidx.core.content.FileProvider;

public class GenericFileProvider extends FileProvider {

    public static final String PROVIDER_NAME = "io.flutter.plugins.webviewflutter.GenericFileProvider";

}

 

11. input file 클릭시 카메라로 직접 사진을 찍어 파일을 올리게 하려면 원래 flutter project에서 android/app/src/main/AndroidManifest.xml 에 <uses-permission android:name="android.permission.CAMERA"/> 권한을 추가한다. (선택)

 

카메라 권한 확인 팝업 / 파일 선택 팝업

 

 

 

출처 :

https://stackoverflow.com/questions/60289932/is-there-a-way-of-making-a-flutter-webview-use-android-camera-for-file-upload-h
https://github.com/pubnative/easy-files-android/blob/master/app/src/main/java/net/easynaps/easyfiles/utils/GenericFileProvider.java

https://m.blog.naver.com/websearch/221738662288

 

 

 

 

반응형
:
Posted by 리샤씨
반응형

며칠 전, Flutter에서 webview_flutter(https://pub.dev/packages/webview_flutter)로 띄운 소스에서

alert가 보이지 않으니 해결해달라는 요청을 받아 처리하게 되었습니다.

찾아보니 webview_flutter는 alert&confirm을 지원하지 않는다고 합니다.

flutter와 html에서 약속을 해서 새로운 function을 만들고 호출하는 방법도 있지만,

(참조: https://cording-cossk3.tistory.com/103)

저는 html 개발하시는 분과 직접 소통하는 것도 아니고, 최대한 네이티브 소스는 건드리지 않기 위하여

그냥 기존에 있는 alert를 flutter의 toast로 작업하는 방식으로 처리하였습니다.

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:toast/toast.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebviewPage extends StatelessWidget {
  WebViewController _webViewController;

  @override
  void initState() {
    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
  }

  @override
  Widget build(BuildContext context) {
    return WebView(
      initialUrl: '${yourUrl}',
      javascriptMode: JavascriptMode.unrestricted,
      javascriptChannels: <JavascriptChannel>{
        _alertJavascriptChannel(context),
      },
      onWebViewCreated: (WebViewController webViewController) {
        _webViewController = webViewController;
      },
      onPageFinished: (url) async {
        try {
          var javascript = '''
                             window.alert = function (e){
                               Alert.postMessage(e);
                             }
                           ''';
          await _webViewController?.evaluateJavascript(javascript);
        } catch (_) {}
      },
    );
  }

  JavascriptChannel _alertJavascriptChannel(BuildContext context) {
    return JavascriptChannel(
        name: 'Alert',
        onMessageReceived: (JavascriptMessage message) {
          Toast.show(message.message, context,
              duration: Toast.LENGTH_LONG,
              gravity: Toast.CENTER,
              backgroundColor: Colors.black38,
              backgroundRadius: 5);
        });
  }
}

이 방법으로 하면 confirm도 처리되긴 합니다.

그런데 confirm이 하나일땐 크게 문제 없겠지만 (위와 같이 confirm을 정의해서 flutter 내에서 dialog 호출해서 처리)

여러 개일때는 confirm마다 기능이 다르다면 결국 html 개발자와 같이 상의해서 개발해야 할 것입니다.

confirm도 처리해야 할 때는 android는 webview_flutter를 2.0.13 버전 이상으로 업 하면 해결됩니다.

ios는 다음 글을 참고하여 처리하여 주세요. (다음 글 : https://risha-lee.tistory.com/42)

 

 

참고 출처 : https://github.com/flutter/flutter/issues/30358

 

 

 

 

 

 

 

반응형
:
Posted by 리샤씨


반응형
반응형