Skip to content

Commit

Permalink
✨ Expose orientation to saving methods (#1159)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexV525 authored Aug 31, 2024
1 parent 46c2aef commit 9fe3dba
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 115 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ To know more about breaking changes, see the [Migration Guide][].

## Unreleased

*None.*
### Improvements

- Allows saving assets with a given orientation value.
- Reads image size from EXIF rather than decoding from the bitmap factory.
- Upgrades Android EXIF library.

### Developer

Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,5 @@ android {

dependencies {
implementation 'com.github.bumptech.glide:glide:4.15.1'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.exifinterface:exifinterface:1.3.7'
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,25 +170,33 @@ class PhotoManager(private val context: Context) {
image: ByteArray,
title: String,
description: String,
relativePath: String?
relativePath: String,
orientation: Int?
): AssetEntity? {
return dbUtils.saveImage(context, image, title, description, relativePath)
return dbUtils.saveImage(context, image, title, description, relativePath, orientation)
}

fun saveImage(
path: String,
title: String,
description: String,
relativePath: String?
relativePath: String,
orientation: Int?
): AssetEntity? {
return dbUtils.saveImage(context, path, title, description, relativePath)
return dbUtils.saveImage(context, path, title, description, relativePath, orientation)
}

fun saveVideo(path: String, title: String, desc: String, relativePath: String?): AssetEntity? {
fun saveVideo(
path: String,
title: String,
desc: String,
relativePath: String,
orientation: Int?
): AssetEntity? {
if (!File(path).exists()) {
return null
}
return dbUtils.saveVideo(context, path, title, desc, relativePath)
return dbUtils.saveVideo(context, path, title, desc, relativePath, orientation)
}

fun assetExists(id: String, resultHandler: ResultHandler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class PhotoManagerDeleteManager(val context: Context, private var activity: Acti
private fun handleAndroidRDelete(resultCode: Int) {
if (resultCode == Activity.RESULT_OK) {
androidRHandler?.apply {
val ids = call?.argument<List<String>>("ids") ?: return@apply
val ids = call.argument<List<String>>("ids") ?: return@apply
androidRHandler?.reply(ids)
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class PhotoManagerPlugin(
private val permissionsUtils: PermissionsUtils
) : MethodChannel.MethodCallHandler {
companion object {
private const val poolSize = 8
private const val POOL_SIZE = 8
private val threadPool: ThreadPoolExecutor = ThreadPoolExecutor(
poolSize,
POOL_SIZE,
Int.MAX_VALUE,
1,
TimeUnit.MINUTES,
Expand Down Expand Up @@ -227,7 +227,9 @@ class PhotoManagerPlugin(
permissionsUtils.withActivity(activity)
.setListener(object : PermissionsListener {
override fun onGranted(needPermissions: MutableList<String>) {
resultHandler.reply(permissionsUtils.getAuthValue(requestType, mediaLocation).value)
resultHandler.reply(
permissionsUtils.getAuthValue(requestType, mediaLocation).value
)
}

override fun onDenied(
Expand Down Expand Up @@ -467,7 +469,14 @@ class PhotoManagerPlugin(
val title = call.argument<String>("title") ?: ""
val desc = call.argument<String>("desc") ?: ""
val relativePath = call.argument<String>("relativePath") ?: ""
val entity = photoManager.saveImage(image, title, desc, relativePath)
val orientation = call.argument<Int?>("orientation")
val entity = photoManager.saveImage(
image,
title,
desc,
relativePath,
orientation,
)
if (entity == null) {
resultHandler.reply(null)
return
Expand All @@ -486,8 +495,14 @@ class PhotoManagerPlugin(
val title = call.argument<String>("title") ?: ""
val desc = call.argument<String>("desc") ?: ""
val relativePath = call.argument<String>("relativePath") ?: ""
val entity =
photoManager.saveImage(imagePath, title, desc, relativePath)
val orientation = call.argument<Int?>("orientation")
val entity = photoManager.saveImage(
imagePath,
title,
desc,
relativePath,
orientation,
)
if (entity == null) {
resultHandler.reply(null)
return
Expand All @@ -506,8 +521,14 @@ class PhotoManagerPlugin(
val title = call.argument<String>("title")!!
val desc = call.argument<String>("desc") ?: ""
val relativePath = call.argument<String>("relativePath") ?: ""
val entity =
photoManager.saveVideo(videoPath, title, desc, relativePath)
val orientation = call.argument<Int?>("orientation")
val entity = photoManager.saveVideo(
videoPath,
title,
desc,
relativePath,
orientation,
)
if (entity == null) {
resultHandler.reply(null)
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ data class AssetEntity(
val type: Int,
val displayName: String,
val modifiedDate: Long,
val orientation: Int,
var orientation: Int,
var lat: Double? = null,
var lng: Double? = null,
val androidQRelativePath: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,36 +253,25 @@ interface IDBUtils {
bytes: ByteArray,
title: String,
desc: String,
relativePath: String?
relativePath: String,
orientation: Int?
): AssetEntity? {
var inputStream = ByteArrayInputStream(bytes)
fun refreshInputStream() {
inputStream = ByteArrayInputStream(bytes)
}

val timestamp = System.currentTimeMillis() / 1000
val (width, height) = try {
val bmp = BitmapFactory.decodeStream(inputStream)
Pair(bmp.width, bmp.height)
} catch (e: Exception) {
Pair(0, 0)
}
val typeFromStream: String = URLConnection.guessContentTypeFromName(title)
?: URLConnection.guessContentTypeFromStream(inputStream)
?: "image/*"
val (rotationDegrees, latLong) = kotlin.run {
try {
val exif = ExifInterface(inputStream)
Pair(
if (isAboveAndroidQ) exif.rotationDegrees else 0,
if (isAboveAndroidQ) null else exif.latLong
)
} catch (e: Exception) {
Pair(0, null)
}
}
refreshInputStream()

val exif = ExifInterface(inputStream)
val (width, height) = Pair(
exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0),
exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0)
)
val (rotationDegrees, latLong) = Pair(
orientation ?: if (isAboveAndroidQ) exif.rotationDegrees else 0,
if (isAboveAndroidQ) null else exif.latLong
)

val timestamp = System.currentTimeMillis() / 1000
val values = ContentValues().apply {
put(
MediaStore.Files.FileColumns.MEDIA_TYPE,
Expand All @@ -299,7 +288,7 @@ interface IDBUtils {
if (isAboveAndroidQ) {
put(DATE_TAKEN, timestamp * 1000)
put(ORIENTATION, rotationDegrees)
if (relativePath != null) {
if (relativePath.isNotBlank()) {
put(RELATIVE_PATH, relativePath)
}
}
Expand All @@ -309,55 +298,51 @@ interface IDBUtils {
}
}

return insertUri(
inputStream = ByteArrayInputStream(bytes)
val entity = insertUri(
context,
inputStream,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values,
)
entity?.let {
it.orientation = orientation ?: it.orientation
}
return entity
}

fun saveImage(
context: Context,
fromPath: String,
title: String,
desc: String,
relativePath: String?
relativePath: String,
orientation: Int?
): AssetEntity? {
fromPath.checkDirs()
val file = File(fromPath)
var inputStream = FileInputStream(file)
fun refreshInputStream() {
inputStream = FileInputStream(file)
}

val timestamp = System.currentTimeMillis() / 1000
val (width, height) = try {
val bmp = BitmapFactory.decodeStream(inputStream)
Pair(bmp.width, bmp.height)
} catch (e: Exception) {
Pair(0, 0)
}
val typeFromStream: String = URLConnection.guessContentTypeFromName(title)
?: URLConnection.guessContentTypeFromName(fromPath)
?: URLConnection.guessContentTypeFromStream(inputStream)
?: "image/*"
val (rotationDegrees, latLong) = try {
val exif = ExifInterface(inputStream)
Pair(
if (isAboveAndroidQ) exif.rotationDegrees else 0,
if (isAboveAndroidQ) null else exif.latLong
)
} catch (e: Exception) {
Pair(0, null)
}
refreshInputStream()

val exif = ExifInterface(inputStream)
val (width, height) = Pair(
exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0),
exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0)
)
val (rotationDegrees, latLong) = Pair(
orientation ?: if (isAboveAndroidQ) exif.rotationDegrees else 0,
if (isAboveAndroidQ) null else exif.latLong
)

val shouldKeepPath = if (!isAboveAndroidQ) {
val dir = Environment.getExternalStorageDirectory()
file.absolutePath.startsWith(dir.path)
} else false

val timestamp = System.currentTimeMillis() / 1000
val values = ContentValues().apply {
put(
MediaStore.Files.FileColumns.MEDIA_TYPE,
Expand All @@ -374,7 +359,7 @@ interface IDBUtils {
if (isAboveAndroidQ) {
put(DATE_TAKEN, timestamp * 1000)
put(ORIENTATION, rotationDegrees)
if (relativePath != null) {
if (relativePath.isNotBlank()) {
put(RELATIVE_PATH, relativePath)
}
}
Expand All @@ -387,21 +372,27 @@ interface IDBUtils {
}
}

return insertUri(
inputStream = FileInputStream(file)
val entity = insertUri(
context,
inputStream,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values,
shouldKeepPath
)
entity?.let {
it.orientation = orientation ?: it.orientation
}
return entity
}

fun saveVideo(
context: Context,
fromPath: String,
title: String,
desc: String,
relativePath: String?
relativePath: String,
orientation: Int?
): AssetEntity? {
fromPath.checkDirs()
val file = File(fromPath)
Expand All @@ -418,11 +409,11 @@ interface IDBUtils {
val (rotationDegrees, latLong) = try {
val exif = ExifInterface(inputStream)
Pair(
if (isAboveAndroidQ) exif.rotationDegrees else 0,
orientation ?: if (isAboveAndroidQ) exif.rotationDegrees else 0,
if (isAboveAndroidQ) null else exif.latLong
)
} catch (e: Exception) {
Pair(0, null)
Pair(orientation ?: 0, null)
}
refreshInputStream()

Expand All @@ -448,7 +439,7 @@ interface IDBUtils {
if (isAboveAndroidQ) {
put(DATE_TAKEN, timestamp * 1000)
put(ORIENTATION, rotationDegrees)
if (relativePath != null) {
if (relativePath.isNotBlank()) {
put(RELATIVE_PATH, relativePath)
}
}
Expand All @@ -461,13 +452,17 @@ interface IDBUtils {
}
}

return insertUri(
val entity = insertUri(
context,
inputStream,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
values,
shouldKeepPath
)
entity?.let {
it.orientation = orientation ?: it.orientation
}
return entity
}

private fun insertUri(
Expand Down
6 changes: 5 additions & 1 deletion example/lib/util/common_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ class CommonUtil {
),
_buildInfoItem('create', entity.createDateTime.toString()),
_buildInfoItem('modified', entity.modifiedDateTime.toString()),
_buildInfoItem('size', entity.size.toString()),
_buildInfoItem('orientation', entity.orientation.toString()),
_buildInfoItem('size', entity.size.toString()),
_buildInfoItem(
'orientatedSize',
entity.orientatedSize.toString(),
),
_buildInfoItem('duration', entity.videoDuration.toString()),
_buildInfoItemAsync('title', entity.titleAsync),
_buildInfoItem('lat', lat.toString()),
Expand Down
2 changes: 1 addition & 1 deletion lib/photo_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export 'src/filter/custom/order_by_item.dart';
export 'src/filter/path_filter.dart';

export 'src/internal/enums.dart';
export 'src/internal/extensions.dart';
export 'src/internal/extensions.dart' show PermissionStateExt;
export 'src/internal/plugin.dart' show PhotoManagerPlugin;
export 'src/internal/progress_handler.dart';

Expand Down
Loading

0 comments on commit 9fe3dba

Please sign in to comment.