Skip to content

Commit

Permalink
Save image with unit8list (#22)
Browse files Browse the repository at this point in the history
* Add putImageBytes and putVideoBytes to api

* Update GalPlugin.java

* Update example

* Update GalPlugin.java

* Update GalPlugin.swift

* Remove putVideoBytes

* Remove putVideoBytes

* Update doc

* Add test

* Update README.md
  • Loading branch information
natsuk4ze committed Jul 4, 2023
1 parent 70648f1 commit 3dba7ad
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 33 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Add the following key to your _AndroidManifest_ file, located in
```dart
//Save Image
await Gal.putImage('$filePath');
await Gal.putImageBytes('$uint8List');
//Save Video
await Gal.putVideo('$filePath');
Expand Down
44 changes: 32 additions & 12 deletions android/src/main/java/studio/midoridesign/gal/GalPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.util.concurrent.CompletableFuture;

public class GalPlugin
Expand All @@ -55,13 +56,23 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
switch (call.method) {
case "putVideo":
case "putImage": {
String path = call.argument("path");
Uri contentUri = call.method.equals("putVideo") ? MediaStore.Video.Media.EXTERNAL_CONTENT_URI
Uri uri = call.method.contains("Video") ? MediaStore.Video.Media.EXTERNAL_CONTENT_URI
: MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

CompletableFuture.runAsync(() -> {
try {
putMedia(pluginBinding.getApplicationContext(), path, contentUri);
putMedia(pluginBinding.getApplicationContext(), (String) call.argument("path"), uri);
new Handler(Looper.getMainLooper()).post(() -> result.success(null));
} catch (Exception e) {
handleError(e, result);
}
});
break;
}
case "putImageBytes": {
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
CompletableFuture.runAsync(() -> {
try {
putImageBytes(pluginBinding.getApplicationContext(), (byte[]) call.argument("bytes"), uri);
new Handler(Looper.getMainLooper()).post(() -> result.success(null));
} catch (Exception e) {
handleError(e, result);
Expand All @@ -85,23 +96,32 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
}
default:
result.notImplemented();

}
}

private void putMedia(Context context, String path, Uri contentUri)
throws IOException, SecurityException, FileNotFoundException {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
File file = new File(path);
try (InputStream in = new FileInputStream(file)) {
writeContent(context, in, contentUri);
}
}

values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.getName());
values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());
private void putImageBytes(Context context, byte[] bytes, Uri contentUri)
throws IOException, SecurityException {
try (InputStream in = new ByteArrayInputStream(bytes)) {
writeContent(context, in, contentUri);
}
}

private void writeContent(Context context, InputStream in, Uri contentUri)
throws IOException, SecurityException {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());
Uri mediaUri = resolver.insert(contentUri, values);

try (OutputStream out = resolver.openOutputStream(mediaUri);
InputStream in = new FileInputStream(file)) {
try (OutputStream out = resolver.openOutputStream(mediaUri)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
Expand Down Expand Up @@ -129,7 +149,7 @@ private boolean hasAccess() {
return hasAccess == PackageManager.PERMISSION_GRANTED;
}

// If permissions have already been granted by the user,
// If permissions have already been granted by the user,
// returns true immediately without displaying the dialog.
private CompletableFuture<Boolean> requestAccess() {
accessRequestResult = new CompletableFuture<>();
Expand Down
3 changes: 2 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
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" />
<!-- Gal: If supports API <29 add this key :Gal-->
Expand Down
8 changes: 8 additions & 0 deletions example/integration_test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ void main() {
expect(tester.takeException(), isNull);
});

testWidgets('putImageBytes()', (tester) async {
app.main();
await tester.pumpAndSettle();
final button = find.byIcon(Icons.image_rounded);
await tester.tap(button);
expect(tester.takeException(), isNull);
});

testWidgets('open()', (tester) async {
app.main();
await tester.pumpAndSettle();
Expand Down
13 changes: 13 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ class App extends StatelessWidget {
label: 'Save Image from local',
icon: Icons.image,
),
_Button(
onPressed: () async {
final res = await Dio().get(
'https://github.com/natsuk4ze/gal/raw/main/example/assets/done.jpg',
options: Options(responseType: ResponseType.bytes));
await Gal.putImageBytes(Uint8List.fromList(res.data));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
},
label: 'Save Image from bytes',
icon: Icons.image_rounded,
),
_Button(
onPressed: () async {
final path = '${Directory.systemTemp.path}/done.jpg';
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: b99b1d56dc0d5dece70957023af002dbd49614b4a1bf86d3a254af3fe781bdf2
sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8
url: "https://pub.dev"
source: hosted
version: "5.2.0+1"
version: "5.2.1+1"
fake_async:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ environment:
dependencies:
flutter:
sdk: flutter
dio: ^5.2.0+1
dio: ^5.2.1+1
gal:
path: ../

Expand Down
22 changes: 21 additions & 1 deletion ios/Classes/GalPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class GalPlugin: NSObject, FlutterPlugin {

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "putImage", "putVideo":
case "putVideo", "putImage":
let args = call.arguments as! [String: String]
self.putMedia(
path: args["path"]!,
Expand All @@ -23,6 +23,17 @@ public class GalPlugin: NSObject, FlutterPlugin {
result(nil)
}
}
case "putImageBytes":
let args = call.arguments as! [String: FlutterStandardTypedData]
self.putImageBytes(
bytes: args["bytes"]!.data
) { _, error in
if let error = error as NSError? {
result(self.handleError(error: error))
} else {
result(nil)
}
}
case "open":
self.open {
result(nil)
Expand Down Expand Up @@ -50,6 +61,15 @@ public class GalPlugin: NSObject, FlutterPlugin {
completionHandler: completion)
}

private func putImageBytes(
bytes: Data, completion: @escaping (Bool, Error?) -> Void
) {
PHPhotoLibrary.shared().performChanges(
{
PHAssetChangeRequest.creationRequestForAsset(from: UIImage(data: bytes)!)
}, completionHandler: completion)
}

private func open(completion: @escaping () -> Void) {
guard let url = URL(string: "photos-redirect://") else { return }
UIApplication.shared.open(url, options: [:]) { _ in completion() }
Expand Down
27 changes: 13 additions & 14 deletions lib/src/gal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,34 @@ import 'package:gal/src/gal_exception.dart';

import 'gal_platform_interface.dart';

/// Plugin App Facing
/// For detailed please see
/// https://github.com/natsuk4ze/gal/ or
/// https://github.com/natsuk4ze/gal/wiki
/// these functions are first called,
/// [putImage],[putVideo],[putImageBytes],a native dialog is
/// called asking the use for permission. If the user chooses to deny,
/// [GalException] of [GalExceptionType.accessDenied] will be throwed.
/// You should either do error handling or call [requestAccess] once
/// before calling these function.
final class Gal {
Gal._();

/// Save video to standard gallery app
/// [path] is local path.
/// When this function was called was the first access
/// to the gallery app, a native dialog is called asking the user
/// for permission. If the user chooses to deny,
/// [GalException] of [GalExceptionType.accessDenied] will be throwed.
/// You should either do error handling or call [requestAccess] once
/// before calling this function.
static Future<void> putVideo(String path) async =>
_voidOrThrow(() async => GalPlatform.instance.putVideo(path));

/// Save image to standard gallery app
/// [path] is local path.
/// When this function was called was the first access
/// to the gallery app, a native dialog is called asking the user
/// for permission. If the user chooses to deny,
/// [GalException] of [GalExceptionType.accessDenied] will be throwed.
/// You should either do error handling or call [requestAccess] once
/// before calling this function.
static Future<void> putImage(String path) async =>
_voidOrThrow(() async => GalPlatform.instance.putImage(path));

/// Save image to standard gallery app
/// [Uint8List] version of [putImage]
/// It does not require temporary files and saves directly from memory,
/// making it fast.
static Future<void> putImageBytes(Uint8List bytes) async =>
_voidOrThrow(() async => GalPlatform.instance.putImageBytes(bytes));

/// Open OS standard gallery app.
/// Open "iOS Photos" when iOS, "Google Photos" or something when Android.
static Future<void> open() async => GalPlatform.instance.open();
Expand Down
7 changes: 5 additions & 2 deletions lib/src/gal_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ final class MethodChannelGal extends GalPlatform {
@visibleForTesting
final methodChannel = const MethodChannel('gal');

/// argument is Map.
@override
Future<void> putVideo(String path) async =>
methodChannel.invokeMethod<void>('putVideo', {'path': path});

/// argument is Map.
@override
Future<void> putImage(String path) async =>
methodChannel.invokeMethod<void>('putImage', {'path': path});

@override
Future<void> putImageBytes(Uint8List bytes) async =>
methodChannel.invokeMethod<void>('putImageBytes', {'bytes': bytes});

@override
Future<void> open() async => methodChannel.invokeMethod<void>('open');

Expand All @@ -26,6 +28,7 @@ final class MethodChannelGal extends GalPlatform {
final hasAccess = await methodChannel.invokeMethod<bool>('hasAccess');
return hasAccess ?? false;
}

@override
Future<bool> requestAccess() async {
final granted = await methodChannel.invokeMethod<bool>('requestAccess');
Expand Down
7 changes: 7 additions & 0 deletions lib/src/gal_platform_interface.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:typed_data';

import 'package:plugin_platform_interface/plugin_platform_interface.dart';

import 'gal_method_channel.dart';
Expand All @@ -24,6 +26,11 @@ abstract class GalPlatform extends PlatformInterface {
Future<void> putImage(String path) =>
throw UnimplementedError('putImage() has not been implemented.');

/// throw [UnimplementedError] when Plugin [MethodChannelGal] did not
/// define [putImageBytes].
Future<void> putImageBytes(Uint8List bytes) =>
throw UnimplementedError('putImageBytes() has not been implemented.');

/// throw [UnimplementedError] when Plugin [MethodChannelGal] did not
/// define [open].
Future<void> open() =>
Expand Down

0 comments on commit 3dba7ad

Please sign in to comment.