Skip to content

Commit

Permalink
Merge pull request #71 from natsuk4ze/saving-to-album
Browse files Browse the repository at this point in the history
  • Loading branch information
natsuk4ze authored Aug 9, 2023
2 parents 69f6540 + 9de1ccb commit 5b6409d
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 169 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ jobs:
sdcard-path-or-size: ${{ matrix.api-level < 29 && '10M' || null }}
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none
script: |
if [ ${{ matrix.api-level }} -le 29 ]; then flutter build apk --debug; adb install -r build/app/outputs/flutter-apk/app-debug.apk; adb shell pm grant studio.midoridesign.gal_example android.permission.WRITE_EXTERNAL_STORAGE; fi
if [ ${{ matrix.api-level }} -le 29 ]; then flutter build apk --debug; adb install -r build/app/outputs/flutter-apk/app-debug.apk; adb shell pm grant studio.midoridesign.gal_example android.permission.WRITE_EXTERNAL_STORAGE; adb shell pm grant studio.midoridesign.gal_example android.permission.READ_EXTERNAL_STORAGE; fi
flutter test integration_test/integration_test.dart
- name: Retry integration tests
Expand All @@ -153,7 +153,7 @@ jobs:
adb start-server
script: |
flutter clean && flutter pub get
if [ ${{ matrix.api-level }} -le 29 ]; then flutter build apk --debug; adb install -r build/app/outputs/flutter-apk/app-debug.apk; adb shell pm grant studio.midoridesign.gal_example android.permission.WRITE_EXTERNAL_STORAGE; fi
if [ ${{ matrix.api-level }} -le 29 ]; then flutter build apk --debug; adb install -r build/app/outputs/flutter-apk/app-debug.apk; adb shell pm grant studio.midoridesign.gal_example android.permission.WRITE_EXTERNAL_STORAGE; adb shell pm grant studio.midoridesign.gal_example android.permission.READ_EXTERNAL_STORAGE; fi
flutter test integration_test/integration_test.dart
- name: Re:Retry integration tests
Expand All @@ -173,5 +173,5 @@ jobs:
adb start-server
script: |
flutter clean && flutter pub get
if [ ${{ matrix.api-level }} -le 29 ]; then flutter build apk --debug; adb install -r build/app/outputs/flutter-apk/app-debug.apk; adb shell pm grant studio.midoridesign.gal_example android.permission.WRITE_EXTERNAL_STORAGE; fi
if [ ${{ matrix.api-level }} -le 29 ]; then flutter build apk --debug; adb install -r build/app/outputs/flutter-apk/app-debug.apk; adb shell pm grant studio.midoridesign.gal_example android.permission.WRITE_EXTERNAL_STORAGE; adb shell pm grant studio.midoridesign.gal_example android.permission.READ_EXTERNAL_STORAGE; fi
flutter test integration_test/integration_test.dart
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ ios/Flutter/ephemeral/
profile
xcuserdata
**/.generated/

App.framework
Flutter.framework
Flutter.podspec
Expand Down
41 changes: 28 additions & 13 deletions android/src/main/java/studio/midoridesign/gal/GalPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class GalPlugin implements FlutterPlugin, MethodCallHandler, ActivityAwar
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_EXTERNAL_STORAGE = Build.VERSION.SDK_INT <= 23;
private static final boolean USE_EXTERNAL_STORAGE = Build.VERSION.SDK_INT <= 29;
private static final boolean HAS_ACCESS_BY_DEFAULT =
Build.VERSION.SDK_INT < 23 || Build.VERSION.SDK_INT >= 29;

Expand All @@ -66,7 +66,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
new Thread(() -> {
try {
putMedia(pluginBinding.getApplicationContext(),
call.argument("path"), call.method.contains("Image"));
call.argument("path"), call.argument("album"),
call.method.contains("Image"));

new Handler(Looper.getMainLooper()).post(() -> result.success(null));
} catch (Exception e) {
handleError(e, result);
Expand All @@ -77,7 +79,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
case "putImageBytes": {
new Thread(() -> {
try {
putMediaBytes(pluginBinding.getApplicationContext(), call.argument("bytes"));
putMediaBytes(pluginBinding.getApplicationContext(),
call.argument("bytes"), call.argument("album"));

new Handler(Looper.getMainLooper()).post(() -> result.success(null));
} catch (Exception e) {
handleError(e, result);
Expand Down Expand Up @@ -113,34 +117,45 @@ public void run() {
}
}

private void putMedia(Context context, String path, boolean isImage)
private void putMedia(Context context, String path, String album, boolean isImage)
throws IOException, SecurityException, FileNotFoundException {
File file = new File(path);
String name = file.getName();
int dotIndex = name.lastIndexOf('.');
if (dotIndex == -1) throw new FileNotFoundException("Extension not found.");

try (InputStream in = new FileInputStream(file)) {
writeData(context, in, isImage, name.substring(dotIndex));
writeData(context, in, isImage, name.substring(dotIndex + 1), album);
}
}

private void putMediaBytes(Context context, byte[] bytes)
private void putMediaBytes(Context context, byte[] bytes, String album)
throws IOException, SecurityException {
try (InputStream in = new ByteArrayInputStream(bytes)) {
writeData(context, in, true, ".jpg");
writeData(context, in, true, "jpg", album);
}
}

private void writeData(Context context, InputStream in, boolean isImage,
String extension) throws IOException, SecurityException, FileNotFoundException {
private void writeData(Context context, InputStream in, boolean isImage, String extension,
String album) throws IOException, SecurityException, FileNotFoundException {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
String dirPath = isImage || album != null ? Environment.DIRECTORY_PICTURES
: Environment.DIRECTORY_MOVIES;

if (USE_EXTERNAL_STORAGE) {
String path = Environment.getExternalStoragePublicDirectory(
isImage ? Environment.DIRECTORY_PICTURES : Environment.DIRECTORY_MOVIES)
+ File.separator + UUID.randomUUID().toString() + extension;
File dir = new File(Environment.getExternalStoragePublicDirectory(dirPath),
album != null ? album : "");
if (!dir.exists()) dir.mkdirs();
String path =
dir.getPath() + File.separator + UUID.randomUUID().toString() + "." + extension;
values.put(MediaStore.MediaColumns.DATA, path);
} else {
String path = dirPath + (album != null ? File.separator + album : "");
values.put(isImage ? MediaStore.Images.Media.RELATIVE_PATH
: MediaStore.Video.Media.RELATIVE_PATH, path);
}

Uri uri = resolver.insert(isImage ? IMAGE_URI : VIDEO_URI, values);
try (OutputStream out = resolver.openOutputStream(uri)) {
byte[] buffer = new byte[8192];
Expand All @@ -155,7 +170,7 @@ private void open() {
Context context = pluginBinding.getApplicationContext();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
if (USE_EXTERNAL_STORAGE) {
if (Build.VERSION.SDK_INT <= 23) {
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"});
} else {
Expand Down
2 changes: 2 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
android:value="2" />
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />

<!-- Gal: If supports API <29 add this key :Gal-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
Expand Down
27 changes: 21 additions & 6 deletions example/integration_test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,30 @@ void main() {

Platform.isAndroid
? group('Android Test', () {
execute('hasAccess()');
execute('requestAccess()');
execute('putImage()');
execute('putImageBytes()');
execute('putVideo()');
bool toAlbum = false;
for (var i = 0; i < 2; i++) {
if (i == 1) {
execute('Toggle toAlbum');
toAlbum = true;
}
execute('hasAccess(toAlbum: $toAlbum)');
execute('requestAccess(toAlbum: $toAlbum)');
execute('putImage(toAlbum: $toAlbum)');
execute('putImageBytes(toAlbum: $toAlbum)');
execute('putVideo(toAlbum: $toAlbum)');
}
execute('open()');
})
: group('iOS Test', () {
execute('hasAccess()');
bool toAlbum = false;
for (var i = 0; i < 2; i++) {
if (i == 1) {
execute('Toggle toAlbum');
toAlbum = true;
}
execute('hasAccess(toAlbum: $toAlbum)');
}

execute('open()');

/// Other functions take longer to implement
Expand Down
106 changes: 51 additions & 55 deletions example/integration_test/test_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,72 @@ import 'package:flutter/services.dart';
import 'package:gal/gal.dart';

var logger = Logger();
bool toAlbum = false;

void main() => runApp(const App());

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

@override
State<App> createState() => _AppState();
}

class _AppState extends State<App> {
bool isTesting = false;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: isTesting
? const CircularProgressIndicator()
: SingleChildScrollView(
child: Column(
children: [
buildButton(
onPressed: () async => Gal.hasAccess(),
label: 'hasAccess()',
),
buildButton(
onPressed: () async => Gal.requestAccess(),
label: 'requestAccess()',
),
buildButton(
onPressed: () async {
final path = await getFilePath('assets/done.jpg');
await Gal.putImage(path);
},
label: 'putImage()',
),
buildButton(
onPressed: () async {
final byteData =
await rootBundle.load('assets/done.jpg');
final uint8List = byteData.buffer.asUint8List(
byteData.offsetInBytes, byteData.lengthInBytes);
await Gal.putImageBytes(
Uint8List.fromList(uint8List));
},
label: 'putImageBytes()',
),
buildButton(
onPressed: () async {
final path = await getFilePath('assets/done.mp4');
await Gal.putVideo(path);
},
label: 'putVideo()',
),
buildButton(
onPressed: () async => Gal.open(),
label: 'open()',
),
],
),
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildButton(
onPressed: () async => toAlbum = !toAlbum,
label: 'Toggle toAlbum',
),
buildButton(
onPressed: () async => Gal.hasAccess(),
label: 'hasAccess(toAlbum: $toAlbum)',
),
buildButton(
onPressed: () async => Gal.requestAccess(),
label: 'requestAccess(toAlbum: $toAlbum)',
),
buildButton(
onPressed: () async {
final path = await getFilePath('assets/done.jpg');
await Gal.putImage(path, album: album);
},
label: 'putImage(toAlbum: $toAlbum)',
),
buildButton(
onPressed: () async {
final byteData = await rootBundle.load('assets/done.jpg');
final uint8List = byteData.buffer.asUint8List(
byteData.offsetInBytes, byteData.lengthInBytes);
await Gal.putImageBytes(Uint8List.fromList(uint8List),
album: album);
},
label: 'putImageBytes(toAlbum: $toAlbum)',
),
buildButton(
onPressed: () async {
final path = await getFilePath('assets/done.mp4');
await Gal.putVideo(path, album: album);
},
label: 'putVideo(toAlbum: $toAlbum)',
),
buildButton(
onPressed: () async => Gal.open(),
label: 'open()',
),
],
),
),
),
),
);
}

String? get album => toAlbum ? 'Album' : null;

Widget buildButton({
required String label,
required Future Function() onPressed,
Expand All @@ -80,7 +79,6 @@ class _AppState extends State<App> {
key: Key(label),
onPressed: () async {
logger = Logger();
setState(() => isTesting = true);
try {
logger.value = await onPressed();
} catch (e, st) {
Expand All @@ -89,8 +87,6 @@ class _AppState extends State<App> {
if (e is GalException) {
logger.platformException = e.error as PlatformException;
}
} finally {
setState(() => isTesting = false);
}
},
child: Text(label),
Expand Down
2 changes: 2 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<!-- gal -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need add only access to save media files</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need add only access to save media files</string>
<!-- gal -->

<key>CFBundleDevelopmentRegion</key>
Expand Down
Loading

0 comments on commit 5b6409d

Please sign in to comment.