Skip to content

Commit

Permalink
Refactor (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
natsuk4ze authored Aug 1, 2023
1 parent af386ee commit bdcb58e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 69 deletions.
130 changes: 66 additions & 64 deletions android/src/main/java/studio/midoridesign/gal/GalPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;

import java.util.UUID;

public class GalPlugin
implements FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener {
public class GalPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
private static final String PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
private static final Uri IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
private static final Uri VIDEO_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
private static final int PERMISSION_REQUEST_CODE = 1317298; // Anything unique in the app.
private static final boolean USE_MEDIA_STORE = Build.VERSION.SDK_INT > 23;
private static final boolean HAS_ACCESS_BY_DEFAULT =
Build.VERSION.SDK_INT < 23 || Build.VERSION.SDK_INT >= 29;

private MethodChannel channel;
private FlutterPluginBinding pluginBinding;
Expand All @@ -64,8 +68,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
case "putImage": {
new Thread(() -> {
try {
putMedia(pluginBinding.getApplicationContext(), (String) call.argument("path"),
call.method.contains("Image"));
putMedia(pluginBinding.getApplicationContext(),
(String) call.argument("path"), call.method.contains("Image"));
new Handler(Looper.getMainLooper()).post(() -> result.success(null));
} catch (Exception e) {
handleError(e, result);
Expand All @@ -76,7 +80,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
case "putImageBytes": {
new Thread(() -> {
try {
putImageBytes(pluginBinding.getApplicationContext(), (byte[]) call.argument("bytes"));
putMediaBytes(pluginBinding.getApplicationContext(),
(byte[]) call.argument("bytes"));
new Handler(Looper.getMainLooper()).post(() -> result.success(null));
} catch (Exception e) {
handleError(e, result);
Expand All @@ -86,8 +91,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
}
case "open": {
open();
new Handler(Looper.getMainLooper())
.post(() -> result.success(null));
new Handler(Looper.getMainLooper()).post(() -> result.success(null));
break;
}
case "hasAccess": {
Expand Down Expand Up @@ -117,59 +121,58 @@ private void putMedia(Context context, String path, boolean isImage)
throws IOException, SecurityException, FileNotFoundException {
File file = new File(path);
try (InputStream in = new FileInputStream(file)) {
writeContent(context, in, isImage, file.getName());
if (USE_MEDIA_STORE) {
putMediaToMediaStore(context, in, isImage);
} else {
putMediaToExternalStorage(context, in, isImage, file.getName());
}
}
}

private void putImageBytes(Context context, byte[] bytes)
private void putMediaBytes(Context context, byte[] bytes)
throws IOException, SecurityException {
try (InputStream in = new ByteArrayInputStream(bytes)) {
writeContent(context, in, true, UUID.randomUUID().toString() + ".jpg");
if (USE_MEDIA_STORE) {
putMediaToMediaStore(context, in, true);
} else {
putMediaToExternalStorage(context, in, true, "image.jpg");
}
}
}

private void writeContent(Context context, InputStream in, boolean isImage, String name)
throws IOException, SecurityException {
if (Build.VERSION.SDK_INT > 23) {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());
Uri mediaUri = resolver.insert(isImage ? IMAGE_URI : VIDEO_URI, values);
private void putMediaToMediaStore(Context context, InputStream in, boolean isImage)
throws IOException, SecurityException, FileNotFoundException {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
Uri uri = resolver.insert(isImage ? IMAGE_URI : VIDEO_URI, values);

try (OutputStream out = resolver.openOutputStream(mediaUri)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
} else {
File directory = Environment.getExternalStoragePublicDirectory(
isImage ? Environment.DIRECTORY_PICTURES : Environment.DIRECTORY_MOVIES);
if (!directory.exists()) {
directory.mkdirs();
}
String baseName = name;
String extension = "";
int dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) {
baseName = name.substring(0, dotIndex);
extension = name.substring(dotIndex);
}
String newName = name;
File file = new File(directory, newName);
for (int counter = 1; file.exists(); counter++) {
newName = baseName + "(" + counter + ")" + extension;
file = new File(directory, newName);
}
try (OutputStream out = new FileOutputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
MediaScannerConnection.scanFile(context, new String[] { file.getAbsolutePath() }, null, null);
try (OutputStream out = resolver.openOutputStream(uri)) {
writeData(in, out);
}
}

private void putMediaToExternalStorage(Context context, InputStream in, boolean isImage,
String name) throws IOException, SecurityException, FileNotFoundException {
File directory = Environment.getExternalStoragePublicDirectory(
isImage ? Environment.DIRECTORY_PICTURES : Environment.DIRECTORY_MOVIES);
if (!directory.exists()) directory.mkdirs();

int dotIndex = name.lastIndexOf('.');
if (dotIndex == -1) throw new FileNotFoundException("Extension not found.");
String extension = name.substring(dotIndex);
File file = new File(directory, UUID.randomUUID().toString() + extension);

try (OutputStream out = new FileOutputStream(file)) {
writeData(in, out);
}
MediaScannerConnection.scanFile(context, new String[] {file.getAbsolutePath()}, null, null);
}

private void writeData(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}

Expand All @@ -183,19 +186,19 @@ private void open() {
}

private boolean hasAccess() {
if (Build.VERSION.SDK_INT < 23 || Build.VERSION.SDK_INT >= 29) {
return true;
}
if (HAS_ACCESS_BY_DEFAULT) return true;
Context context = pluginBinding.getApplicationContext();
int status = ContextCompat.checkSelfPermission(context, PERMISSION);
return status == PackageManager.PERMISSION_GRANTED;
}

private void requestAccess() {
ActivityCompat.requestPermissions(activity, new String[] { PERMISSION }, PERMISSION_REQUEST_CODE);
ActivityCompat.requestPermissions(activity, new String[] {PERMISSION},
PERMISSION_REQUEST_CODE);
}

private void sendError(String errorCode, String message, StackTraceElement[] stackTrace, Result result) {
private void sendError(String errorCode, String message, StackTraceElement[] stackTrace,
Result result) {
StringBuilder trace = new StringBuilder();
for (StackTraceElement st : stackTrace) {
trace.append(st.toString());
Expand All @@ -207,8 +210,7 @@ private void sendError(String errorCode, String message, StackTraceElement[] sta

private void handleError(Exception e, Result result) {
String errorCode;
if (e instanceof SecurityException
|| (e instanceof FileNotFoundException && e.toString().contains("Permission denied"))) {
if (e instanceof SecurityException || e.toString().contains("Permission denied")) {
errorCode = "ACCESS_DENIED";
} else if (e instanceof FileNotFoundException) {
errorCode = "NOT_SUPPORTED_FORMAT";
Expand All @@ -232,7 +234,8 @@ public void onDetachedFromActivity() {
}

@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding activityPluginBinding) {
public void onReattachedToActivityForConfigChanges(
@NonNull ActivityPluginBinding activityPluginBinding) {
activity = activityPluginBinding.getActivity();
activityPluginBinding.addRequestPermissionsResultListener(this);
}
Expand All @@ -243,10 +246,9 @@ public void onDetachedFromActivityForConfigChanges() {
}

@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode != PERMISSION_REQUEST_CODE || grantResults.length == 0) {
return false;
}
public boolean onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode != PERMISSION_REQUEST_CODE || grantResults.length == 0) return false;
new Handler(Looper.getMainLooper()).post(requestAccessCallback);
requestAccessCallback = null;
return true;
Expand All @@ -257,4 +259,4 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
pluginBinding = null;
}
}
}
9 changes: 6 additions & 3 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<uses-permission android:name="android.permission.INTERNET"/>


<!-- Gal: If supports API <29 add this key :Gal-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

<!-- Gal: This is only required in a CI. Ignore it :Gal-->
<!--Package users can ignore this key: For example-->
<uses-permission android:name="android.permission.INTERNET"/>

<!--Package users can ignore this key: For integration test-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="23" />

</manifest>
4 changes: 2 additions & 2 deletions ios/Classes/GalPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public class GalPlugin: NSObject, FlutterPlugin {
PHErrorCode.multipleIdentifiersFound.rawValue,
PHErrorCode.requestNotSupportedForAsset.rawValue,
PHErrorCode.videoConversionFailed.rawValue,
PHErrorCode.unsupportedVideoCodecs.rawValue:
PHErrorCode.unsupportedVideoCodec.rawValue:
return FlutterError(code: "NOT_SUPPORTED_FORMAT", message: message, details: details)

case PHErrorCode.notEnoughSpace.rawValue:
Expand Down Expand Up @@ -136,7 +136,7 @@ enum PHErrorCode: Int {
case videoConversionFailed = 3300

// Apple has not released documentation.
case unsupportedVideoCodecs = 3302
case unsupportedVideoCodec = 3302

// [PHPhotosError.notEnoughSpace]
case notEnoughSpace = 3305
Expand Down
2 changes: 2 additions & 0 deletions lib/src/gal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ final class Gal {
_voidOrThrow(() async => GalPlatform.instance.putImageBytes(bytes));

/// Open the default gallery app.
///
/// In Android API -23, open gallery/pictures. Otherwise, gallery/
static Future<void> open() async => GalPlatform.instance.open();

/// Check if the app has access permissions.
Expand Down

0 comments on commit bdcb58e

Please sign in to comment.