From 4c4475ba3bff79ee54c64abeec734fc0323ff3b1 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Mon, 23 Mar 2020 10:37:27 +0800 Subject: [PATCH 1/3] Add Date filter and sort --- CHANGELOG.md | 5 + .../imagescanner/core/entity/FilterOption.kt | 30 +-- .../core/utils/AndroidQDBUtils.kt | 19 +- .../imagescanner/core/utils/ConvertUtils.kt | 11 +- .../kikt/imagescanner/core/utils/DBUtils.kt | 208 +++++++++--------- .../kikt/imagescanner/core/utils/IDBUtils.kt | 200 +++++++++-------- example/ios/Podfile.lock | 16 +- example/lib/model/photo_provider.dart | 52 +++-- example/lib/page/filter_option_page.dart | 60 ++++- example/lib/page/home_page.dart | 30 --- ios/Classes/core/ConvertUtils.m | 19 +- ios/Classes/core/PMFilterOption.h | 17 +- ios/Classes/core/PMFilterOption.m | 27 ++- ios/Classes/core/PMManager.m | 10 +- lib/src/entity.dart | 14 +- lib/src/filter/filter_options.dart | 48 ++++ lib/src/manager.dart | 17 +- lib/src/plugin.dart | 7 +- lib/src/utils/convert_utils.dart | 1 - 19 files changed, 476 insertions(+), 315 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 338329c5..2ee42150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Feature: - Add `orientation` for `AssetEntity`. - Add `onlyAll` for `getAssetPathList`. - Support audio type(Only android, iOS Photos have no audio) +- **Breaking change**, Add date condition to filter datetime + - Add class `DateTimeCond` + - Add `dateTimeCond` to `FilterOptionGroup` + - Remove `fetchDateTime` from `getAssetPathList` + - Remove param `dt` from `AssetPathEntity.refreshPathProperties`, and add `refreshPathProperties` params to the method. Update diff --git a/android/src/main/kotlin/top/kikt/imagescanner/core/entity/FilterOption.kt b/android/src/main/kotlin/top/kikt/imagescanner/core/entity/FilterOption.kt index 398034cd..ba7b992b 100644 --- a/android/src/main/kotlin/top/kikt/imagescanner/core/entity/FilterOption.kt +++ b/android/src/main/kotlin/top/kikt/imagescanner/core/entity/FilterOption.kt @@ -6,57 +6,61 @@ import top.kikt.imagescanner.AssetType import top.kikt.imagescanner.core.utils.ConvertUtils class FilterOption(map: Map<*, *>) { - + val videoOption = ConvertUtils.getOptionFromType(map, AssetType.Video) val imageOption = ConvertUtils.getOptionFromType(map, AssetType.Image) val audioOption = ConvertUtils.getOptionFromType(map, AssetType.Audio) - + val dateCond = ConvertUtils.convertToDateCond(map["date"] as Map<*, *>) } class FilterCond { var isShowTitle = false lateinit var sizeConstraint: SizeConstraint lateinit var durationConstraint: DurationConstraint - + companion object { private const val widthKey = MediaStore.Files.FileColumns.WIDTH private const val heightKey = MediaStore.Files.FileColumns.HEIGHT @SuppressLint("InlinedApi") private const val durationKey = MediaStore.Video.VideoColumns.DURATION } - + fun sizeCond(): String { - + return "$widthKey >= ? AND $widthKey <= ? AND $heightKey >= ? AND $heightKey <=?" } - + fun sizeArgs(): Array { return arrayOf(sizeConstraint.minWidth, sizeConstraint.maxWidth, sizeConstraint.minHeight, sizeConstraint.maxHeight).toList().map { it.toString() }.toTypedArray() } - + fun durationCond(): String { return "$durationKey >=? AND $durationKey <=?" } - + fun durationArgs(): Array { return arrayOf(durationConstraint.min, durationConstraint.max).toList().map { it.toString() }.toTypedArray() } - + class SizeConstraint { var minWidth = 0 var maxWidth = 0 var minHeight = 0 var maxHeight = 0 - + } - + class DurationConstraint { var min: Long = 0 var max: Long = 0 - + } -} \ No newline at end of file +} + +data class DateCond( + val minMs: Long, val maxMs: Long, val asc: Boolean +) \ No newline at end of file diff --git a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/AndroidQDBUtils.kt b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/AndroidQDBUtils.kt index 45d3ad01..d9543cf5 100644 --- a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/AndroidQDBUtils.kt +++ b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/AndroidQDBUtils.kt @@ -41,8 +41,7 @@ object AndroidQDBUtils : IDBUtils { val args = ArrayList() val typeSelection: String = getCondFromType(requestType, option, args) - val dateSelection = "AND ${MediaStore.MediaColumns.DATE_ADDED} <= ?" - args.add(timeStamp.toString()) + val dateSelection = getDateCond(args, timeStamp, option) val sizeWhere = sizeWhere(requestType) @@ -87,8 +86,7 @@ object AndroidQDBUtils : IDBUtils { val args = ArrayList() val typeSelection: String = getCondFromType(requestType, option, args) - val dateSelection = "AND ${MediaStore.MediaColumns.DATE_ADDED} <= ?" - args.add(timeStamp.toString()) + val dateSelection = getDateCond(args, timeStamp, option) val sizeWhere = sizeWhere(requestType) @@ -123,8 +121,7 @@ object AndroidQDBUtils : IDBUtils { val sizeWhere = sizeWhere(requestType) - val dateSelection = "AND ${MediaStore.Images.Media.DATE_ADDED} <= ?" - args.add(timeStamp.toString()) + val dateSelection = getDateCond(args, timeStamp, option) val keys = (IDBUtils.storeImageKeys + IDBUtils.storeVideoKeys + IDBUtils.typeKeys).distinct().toTypedArray() val selection = if (isAll) { @@ -133,7 +130,7 @@ object AndroidQDBUtils : IDBUtils { "${MediaStore.Images.ImageColumns.BUCKET_ID} = ? $typeSelection $dateSelection $sizeWhere" } - val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC LIMIT $pageSize OFFSET ${page * pageSize}" + val sortOrder = getSortOrder(page * pageSize, pageSize, option) val cursor = context.contentResolver.query(uri, keys, selection, args.toTypedArray(), sortOrder) ?: return emptyList() @@ -165,8 +162,7 @@ object AndroidQDBUtils : IDBUtils { val sizeWhere = sizeWhere(requestType) - val dateSelection = "AND ${MediaStore.Images.Media.DATE_ADDED} <= ?" - args.add(timestamp.toString()) + val dateSelection = getDateCond(args, timestamp, option) val keys = (IDBUtils.storeImageKeys + IDBUtils.storeVideoKeys + IDBUtils.typeKeys).distinct().toTypedArray() val selection = if (isAll) { @@ -177,7 +173,7 @@ object AndroidQDBUtils : IDBUtils { val pageSize = end - start - val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC LIMIT $pageSize OFFSET $start" + val sortOrder = getSortOrder(start, pageSize, option) val cursor = context.contentResolver.query(uri, keys, selection, args.toTypedArray(), sortOrder) ?: return emptyList() @@ -246,8 +242,7 @@ object AndroidQDBUtils : IDBUtils { val args = ArrayList() val typeSelection: String = getCondFromType(type, option, args) - val dateSelection = "AND ${MediaStore.MediaColumns.DATE_ADDED} <= ?" - args.add(timeStamp.toString()) + val dateSelection = getDateCond(args, timeStamp, option) val idSelection: String if (isAll) { diff --git a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/ConvertUtils.kt b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/ConvertUtils.kt index 03cead70..a9e53b9b 100644 --- a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/ConvertUtils.kt +++ b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/ConvertUtils.kt @@ -1,10 +1,7 @@ package top.kikt.imagescanner.core.utils import top.kikt.imagescanner.AssetType -import top.kikt.imagescanner.core.entity.AssetEntity -import top.kikt.imagescanner.core.entity.FilterCond -import top.kikt.imagescanner.core.entity.FilterOption -import top.kikt.imagescanner.core.entity.GalleryEntity +import top.kikt.imagescanner.core.entity.* /// create 2019-09-05 by cai @@ -118,6 +115,12 @@ object ConvertUtils { return filterOptions } + fun convertToDateCond(map: Map<*, *>): DateCond { + val min = map["min"].toString().toLong() + val max = map["max"].toString().toLong() + val asc = map["asc"].toString().toBoolean() + return DateCond(min, max, asc) + } fun convertFilterOptionsFromMap(map: Map<*, *>): FilterOption { return FilterOption(map) diff --git a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/DBUtils.kt b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/DBUtils.kt index 8ff6bc53..365af535 100644 --- a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/DBUtils.kt +++ b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/DBUtils.kt @@ -27,19 +27,19 @@ import java.net.URLConnection /// Call the MediaStore API and get entity for the data. @Suppress("DEPRECATION") object DBUtils : IDBUtils { - + private const val TAG = "DBUtils" - + private val imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI private val videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - + private val cacheContainer = CacheContainer() - + private val locationKeys = arrayOf( - MediaStore.Images.ImageColumns.LONGITUDE, - MediaStore.Images.ImageColumns.LATITUDE + MediaStore.Images.ImageColumns.LONGITUDE, + MediaStore.Images.ImageColumns.LATITUDE ) - + private fun convertTypeToUri(type: Int): Uri { return when (type) { 1 -> imageUri @@ -47,68 +47,68 @@ object DBUtils : IDBUtils { else -> allUri } } - + @SuppressLint("Recycle") override fun getGalleryList(context: Context, requestType: Int, timeStamp: Long, option: FilterOption): List { val list = ArrayList() val uri = allUri val projection = storeBucketKeys + arrayOf("count(1)") - + val args = ArrayList() val typeSelection: String = getCondFromType(requestType, option, args) - - val dateSelection = getDateCond(args, timeStamp) - + + val dateSelection = getDateCond(args, timeStamp, option) + val sizeWhere = AndroidQDBUtils.sizeWhere(requestType) - + val selection = "${MediaStore.Images.Media.BUCKET_ID} IS NOT NULL $typeSelection $dateSelection $sizeWhere) GROUP BY (${MediaStore.Images.Media.BUCKET_ID}" val cursor = context.contentResolver.query(uri, projection, selection, args.toTypedArray(), null) - ?: return emptyList() + ?: return emptyList() while (cursor.moveToNext()) { val id = cursor.getString(0) val name = cursor.getString(1) ?: "" val count = cursor.getInt(2) list.add(GalleryEntity(id, name, count, 0)) } - + cursor.close() return list } - + override fun getOnlyGalleryList(context: Context, requestType: Int, timeStamp: Long, option: FilterOption): List { val list = ArrayList() - + val args = ArrayList() val typeSelection: String = AndroidQDBUtils.getCondFromType(requestType, option, args) val projection = storeBucketKeys + arrayOf("count(1)") - - val dateSelection = getDateCond(args, timeStamp) - + + val dateSelection = getDateCond(args, timeStamp, option) + val sizeWhere = AndroidQDBUtils.sizeWhere(requestType) - + val selections = "${MediaStore.Images.Media.BUCKET_ID} IS NOT NULL $typeSelection $dateSelection $sizeWhere" - + val cursor = context.contentResolver.query(allUri, projection, selections, args.toTypedArray(), null) - ?: return list - + ?: return list + cursor.use { val count = cursor.count val galleryEntity = GalleryEntity(PhotoManager.ALL_ID, "Recent", count, requestType, true) list.add(galleryEntity) } - + return list } - + override fun getGalleryEntity(context: Context, galleryId: String, type: Int, timeStamp: Long, option: FilterOption): GalleryEntity? { val uri = allUri val projection = storeBucketKeys + arrayOf("count(1)") - + val args = ArrayList() val typeSelection: String = getCondFromType(type, option, args) - - val dateSelection = getDateCond(args, timeStamp) - + + val dateSelection = getDateCond(args, timeStamp, option) + val idSelection: String if (galleryId == "") { idSelection = "" @@ -116,12 +116,12 @@ object DBUtils : IDBUtils { idSelection = "AND ${MediaStore.Images.Media.BUCKET_ID} = ?" args.add(galleryId) } - + val sizeWhere = AndroidQDBUtils.sizeWhere(null) - + val selection = "${MediaStore.Images.Media.BUCKET_ID} IS NOT NULL $typeSelection $dateSelection $idSelection $sizeWhere) GROUP BY (${MediaStore.Images.Media.BUCKET_ID}" val cursor = context.contentResolver.query(uri, projection, selection, args.toTypedArray(), null) - ?: return null + ?: return null return if (cursor.moveToNext()) { val id = cursor.getString(0) val name = cursor.getString(1) ?: "" @@ -133,110 +133,104 @@ object DBUtils : IDBUtils { null } } - + override fun getThumbUri(context: Context, id: String, width: Int, height: Int, type: Int?): Uri? { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - + @SuppressLint("Recycle") override fun getAssetFromGalleryId( - context: Context, - galleryId: String, - page: Int, - pageSize: Int, - requestType: Int, - timeStamp: Long, - option: FilterOption, - cacheContainer: CacheContainer? + context: Context, + galleryId: String, + page: Int, + pageSize: Int, + requestType: Int, + timeStamp: Long, + option: FilterOption, + cacheContainer: CacheContainer? ): List { val cache = cacheContainer ?: this.cacheContainer - + val isAll = galleryId.isEmpty() - + val list = ArrayList() val uri = allUri - + val args = ArrayList() if (!isAll) { args.add(galleryId) } val typeSelection = getCondFromType(requestType, option, args) - - val dateSelection = getDateCond(args, timeStamp) - + + val dateSelection = getDateCond(args, timeStamp, option) + val sizeWhere = AndroidQDBUtils.sizeWhere(requestType) - + val keys = (storeImageKeys + storeVideoKeys + typeKeys + locationKeys).distinct().toTypedArray() val selection = if (isAll) { "${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL $typeSelection $dateSelection $sizeWhere" } else { "${MediaStore.Images.ImageColumns.BUCKET_ID} = ? $typeSelection $dateSelection $sizeWhere" } - - val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC LIMIT $pageSize OFFSET ${page * pageSize}" + + val sortOrder = getSortOrder(page * pageSize, pageSize, option) val cursor = context.contentResolver.query(uri, keys, selection, args.toTypedArray(), sortOrder) - ?: return emptyList() - + ?: return emptyList() + while (cursor.moveToNext()) { val asset = convertCursorToAsset(cursor, requestType) list.add(asset) cache.putAsset(asset) } - + cursor.close() - + return list } - + override fun getAssetFromGalleryIdRange(context: Context, gId: String, start: Int, end: Int, requestType: Int, timestamp: Long, option: FilterOption): List { val cache = cacheContainer - + val isAll = gId.isEmpty() - + val list = ArrayList() val uri = allUri - + val args = ArrayList() if (!isAll) { args.add(gId) } val typeSelection = getCondFromType(requestType, option, args) - - val dateSelection = getDateCond(args, timestamp) - + + val dateSelection = getDateCond(args, timestamp, option) + val sizeWhere = AndroidQDBUtils.sizeWhere(requestType) - + val keys = (storeImageKeys + storeVideoKeys + typeKeys + locationKeys).distinct().toTypedArray() val selection = if (isAll) { "${MediaStore.Images.ImageColumns.BUCKET_ID} IS NOT NULL $typeSelection $dateSelection $sizeWhere" } else { "${MediaStore.Images.ImageColumns.BUCKET_ID} = ? $typeSelection $dateSelection $sizeWhere" } - + val pageSize = end - start - - val sortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC LIMIT $pageSize OFFSET $start" + + val sortOrder = getSortOrder(start, pageSize, option) val cursor = context.contentResolver.query(uri, keys, selection, args.toTypedArray(), sortOrder) - ?: return emptyList() - + ?: return emptyList() + while (cursor.moveToNext()) { val asset = convertCursorToAsset(cursor, requestType) - + list.add(asset) cache.putAsset(asset) } - + cursor.close() - + return list } - - private fun getDateCond(args: ArrayList, timestamp: Long): String { - val dateSelection = "AND ( ${MediaStore.Images.Media.DATE_ADDED} <= ? )" - args.add((timestamp / 1000).toString()) - return dateSelection - } - + private fun convertCursorToAsset(cursor: Cursor, requestType: Int): AssetEntity { val id = cursor.getString(MediaStore.MediaColumns._ID) val path = cursor.getString(MediaStore.MediaColumns.DATA) @@ -247,36 +241,36 @@ object DBUtils : IDBUtils { val height = cursor.getInt(MediaStore.MediaColumns.HEIGHT) val displayName = File(path).name val modifiedDate = cursor.getLong(MediaStore.MediaColumns.DATE_MODIFIED) - + val lat = cursor.getDouble(MediaStore.Images.ImageColumns.LATITUDE) val lng = cursor.getDouble(MediaStore.Images.ImageColumns.LONGITUDE) val orientation: Int = cursor.getInt(MediaStore.MediaColumns.ORIENTATION) - + return AssetEntity(id, path, duration, date, width, height, getMediaType(type), displayName, modifiedDate, orientation, lat, lng) } - + @SuppressLint("Recycle") override fun getAssetEntity(context: Context, id: String): AssetEntity? { val asset = cacheContainer.getAsset(id) if (asset != null) { return asset } - + val keys = (storeImageKeys + storeVideoKeys + locationKeys + typeKeys).distinct().toTypedArray() - + val selection = "${MediaStore.Files.FileColumns._ID} = ?" - + val args = arrayOf(id) - + val cursor = context.contentResolver.query(allUri, keys, selection, args, null) - ?: return null - + ?: return null + if (cursor.moveToNext()) { val type = cursor.getInt(MediaStore.Files.FileColumns.MEDIA_TYPE) val dbAsset = convertCursorToAsset(cursor, type) - + cacheContainer.putAsset(dbAsset) - + cursor.close() return dbAsset } else { @@ -284,48 +278,48 @@ object DBUtils : IDBUtils { return null } } - + override fun getOriginBytes(context: Context, asset: AssetEntity, haveLocationPermission: Boolean): ByteArray { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - + override fun cacheOriginFile(context: Context, asset: AssetEntity, byteArray: ByteArray) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - + override fun getExif(context: Context, id: String): ExifInterface? { val asset = getAssetEntity(context, id) ?: return null return ExifInterface(asset.path) } - + override fun getFilePath(context: Context, id: String, origin: Boolean): String? { val assetEntity = getAssetEntity(context, id) ?: return null return assetEntity.path } - + override fun clearCache() { cacheContainer.clearCache() } - + override fun saveImage(context: Context, image: ByteArray, title: String, desc: String): AssetEntity? { val bmp = BitmapFactory.decodeByteArray(image, 0, image.count()) val insertImage = MediaStore.Images.Media.insertImage(context.contentResolver, bmp, title, desc) val id = ContentUris.parseId(Uri.parse(insertImage)) return getAssetEntity(context, id.toString()) } - + override fun saveVideo(context: Context, inputStream: InputStream, title: String, desc: String): AssetEntity? { val cr = context.contentResolver val timestamp = System.currentTimeMillis() / 1000 - + var typeFromStream: String? = URLConnection.guessContentTypeFromStream(inputStream) - + if (typeFromStream == null) { typeFromStream = "video/${File(title).extension}" } - + val uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI - + val values = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, title) put(MediaStore.Video.VideoColumns.MIME_TYPE, typeFromStream) @@ -334,25 +328,25 @@ object DBUtils : IDBUtils { put(MediaStore.Video.VideoColumns.DATE_ADDED, timestamp) put(MediaStore.Video.VideoColumns.DATE_MODIFIED, timestamp) } - + val contentUri = cr.insert(uri, values) ?: return null val outputStream = cr.openOutputStream(contentUri) - + outputStream?.use { inputStream.use { inputStream.copyTo(outputStream) } } - + val id = ContentUris.parseId(contentUri) - + cr.notifyChange(contentUri, null) return getAssetEntity(context, id.toString()) } - + override fun getMediaUri(context: Context, id: String, type: Int): String { val asset = getAssetEntity(context, id) ?: return "" return File(asset.path).toURI().toString() } - + } \ No newline at end of file diff --git a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/IDBUtils.kt b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/IDBUtils.kt index d5b560c9..c4a7880f 100644 --- a/android/src/main/kotlin/top/kikt/imagescanner/core/utils/IDBUtils.kt +++ b/android/src/main/kotlin/top/kikt/imagescanner/core/utils/IDBUtils.kt @@ -20,68 +20,68 @@ import java.io.InputStream interface IDBUtils { - + @IntDef(value = [MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO, MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO, MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE]) annotation class MediaTypeDef { - + } - + companion object { val isAndroidQ = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - + val storeImageKeys = arrayOf( - MediaStore.MediaColumns.DISPLAY_NAME, // 显示的名字 - MediaStore.MediaColumns.DATA, // 数据 - MediaStore.MediaColumns._ID, // id - MediaStore.MediaColumns.TITLE, // id - MediaStore.MediaColumns.BUCKET_ID, // dir id 目录 - MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, // dir name 目录名字 - MediaStore.MediaColumns.WIDTH, // 宽 - MediaStore.MediaColumns.HEIGHT, // 高 - MediaStore.MediaColumns.ORIENTATION, // 角度 - MediaStore.MediaColumns.DATE_MODIFIED, // 修改时间 - MediaStore.MediaColumns.MIME_TYPE, // 高 - MediaStore.MediaColumns.DATE_TAKEN //日期 + MediaStore.MediaColumns.DISPLAY_NAME, // 显示的名字 + MediaStore.MediaColumns.DATA, // 数据 + MediaStore.MediaColumns._ID, // id + MediaStore.MediaColumns.TITLE, // id + MediaStore.MediaColumns.BUCKET_ID, // dir id 目录 + MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, // dir name 目录名字 + MediaStore.MediaColumns.WIDTH, // 宽 + MediaStore.MediaColumns.HEIGHT, // 高 + MediaStore.MediaColumns.ORIENTATION, // 角度 + MediaStore.MediaColumns.DATE_MODIFIED, // 修改时间 + MediaStore.MediaColumns.MIME_TYPE, // 高 + MediaStore.MediaColumns.DATE_TAKEN //日期 ) - + val storeVideoKeys = arrayOf( - MediaStore.MediaColumns.DISPLAY_NAME, // 显示的名字 - MediaStore.MediaColumns.DATA, // 数据 - MediaStore.MediaColumns._ID, // id - MediaStore.MediaColumns.TITLE, // id - MediaStore.MediaColumns.BUCKET_ID, // dir id 目录 - MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, // dir name 目录名字 - MediaStore.MediaColumns.DATE_TAKEN, //日期 - MediaStore.MediaColumns.WIDTH, // 宽 - MediaStore.MediaColumns.HEIGHT, // 高 - MediaStore.MediaColumns.ORIENTATION, // 角度 - MediaStore.MediaColumns.DATE_MODIFIED, // 修改时间 - MediaStore.MediaColumns.MIME_TYPE, // 高 - MediaStore.MediaColumns.DURATION //时长 + MediaStore.MediaColumns.DISPLAY_NAME, // 显示的名字 + MediaStore.MediaColumns.DATA, // 数据 + MediaStore.MediaColumns._ID, // id + MediaStore.MediaColumns.TITLE, // id + MediaStore.MediaColumns.BUCKET_ID, // dir id 目录 + MediaStore.MediaColumns.BUCKET_DISPLAY_NAME, // dir name 目录名字 + MediaStore.MediaColumns.DATE_TAKEN, //日期 + MediaStore.MediaColumns.WIDTH, // 宽 + MediaStore.MediaColumns.HEIGHT, // 高 + MediaStore.MediaColumns.ORIENTATION, // 角度 + MediaStore.MediaColumns.DATE_MODIFIED, // 修改时间 + MediaStore.MediaColumns.MIME_TYPE, // 高 + MediaStore.MediaColumns.DURATION //时长 ) - + val typeKeys = arrayOf( - MediaStore.Files.FileColumns.MEDIA_TYPE, - MediaStore.Images.Media.DISPLAY_NAME + MediaStore.Files.FileColumns.MEDIA_TYPE, + MediaStore.Images.Media.DISPLAY_NAME ) - + val storeBucketKeys = arrayOf( - MediaStore.Images.Media.BUCKET_ID, - MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME + MediaStore.Images.Media.BUCKET_ID, + MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME ) - + // fun galleryIdKey(@MediaTypeDef mediaType: Int) :String{ // if(mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO) // return MediaStore.Audio.AudioColumns.ALBUM // } val allUri: Uri get() = MediaStore.Files.getContentUri(VOLUME_EXTERNAL) - + } - + val allUri: Uri get() = IDBUtils.allUri - + /** * Just filter [MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE] */ @@ -90,36 +90,36 @@ interface IDBUtils { return "" } val mediaType = MediaStore.Files.FileColumns.MEDIA_TYPE - - + + var result = "" - + if (typeUtils.containsVideo(requestType)) { result = "OR ( $mediaType = ${MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO} )" } - + if (typeUtils.containsAudio(requestType)) { result = "$result OR ( $mediaType = ${MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO} )" } - + val size = "${MediaStore.MediaColumns.WIDTH} > 0 AND ${MediaStore.MediaColumns.HEIGHT} > 0" - + val imageCondString = "( $mediaType = ${MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE} AND $size )" - + result = "AND ($imageCondString $result)" - + return result } - + private val typeUtils: RequestTypeUtils get() = RequestTypeUtils - + fun getGalleryList(context: Context, requestType: Int = 0, timeStamp: Long, option: FilterOption): List - + fun getAssetFromGalleryId(context: Context, galleryId: String, page: Int, pageSize: Int, requestType: Int = 0, timeStamp: Long, option: FilterOption, cacheContainer: CacheContainer? = null): List - + fun getAssetEntity(context: Context, id: String): AssetEntity? - + fun getMediaType(type: Int): Int { return when (type) { MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1 @@ -128,33 +128,33 @@ interface IDBUtils { else -> 0 } } - + fun Cursor.getInt(columnName: String): Int { return getInt(getColumnIndex(columnName)) } - + fun Cursor.getString(columnName: String): String { - return getString(getColumnIndex(columnName)) + return getString(getColumnIndex(columnName)) ?: "" } - + fun Cursor.getLong(columnName: String): Long { return getLong(getColumnIndex(columnName)) } - + fun Cursor.getDouble(columnName: String): Double { return getDouble(getColumnIndex(columnName)) } - + fun getGalleryEntity(context: Context, galleryId: String, type: Int, timeStamp: Long, option: FilterOption): GalleryEntity? - + fun clearCache() - + fun getFilePath(context: Context, id: String, origin: Boolean): String? - + fun getThumbUri(context: Context, id: String, width: Int, height: Int, type: Int?): Uri? - + fun getAssetFromGalleryIdRange(context: Context, gId: String, start: Int, end: Int, requestType: Int, timestamp: Long, option: FilterOption): List - + fun deleteWithIds(context: Context, ids: List): List { val where = "${MediaStore.MediaColumns._ID} in (?)" val idsArgs = ids.joinToString() @@ -169,11 +169,11 @@ interface IDBUtils { emptyList() } } - + fun saveImage(context: Context, image: ByteArray, title: String, desc: String): AssetEntity? - + fun saveVideo(context: Context, inputStream: InputStream, title: String, desc: String): AssetEntity? - + fun exists(context: Context, id: String): Boolean { val columns = arrayOf(MediaStore.Files.FileColumns._ID) context.contentResolver.query(allUri, columns, "${MediaStore.Files.FileColumns._ID} = ?", arrayOf(id), null).use { @@ -183,25 +183,25 @@ interface IDBUtils { return it.count >= 1 } } - + fun getExif(context: Context, id: String): ExifInterface? - + fun getOriginBytes(context: Context, asset: AssetEntity, haveLocationPermission: Boolean): ByteArray - + fun cacheOriginFile(context: Context, asset: AssetEntity, byteArray: ByteArray) - + fun getCondFromType(type: Int, filterOption: FilterOption, args: ArrayList): String { val cond = StringBuilder(); val typeKey = MediaStore.Files.FileColumns.MEDIA_TYPE - + val haveImage = RequestTypeUtils.containsImage(type) val haveVideo = RequestTypeUtils.containsVideo(type) val haveAudio = RequestTypeUtils.containsAudio(type) - + var imageCondString = "" var videoCondString = "" var audioCondString = "" - + if (haveImage) { val imageCond = filterOption.imageOption val sizeCond = imageCond.sizeCond() @@ -210,7 +210,7 @@ interface IDBUtils { args.add(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString()) args.addAll(sizeArgs) } - + if (haveVideo) { val videoCond = filterOption.videoOption val durationCond = videoCond.durationCond() @@ -219,7 +219,7 @@ interface IDBUtils { args.add(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()) args.addAll(durationArgs) } - + if (haveAudio) { val audioCond = filterOption.audioOption val durationCond = audioCond.durationCond() @@ -228,33 +228,33 @@ interface IDBUtils { args.add(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO.toString()) args.addAll(durationArgs) } - + if (haveImage) { cond.append("( $imageCondString )") } - + if (haveVideo) { if (cond.isNotEmpty()) { cond.append("OR ") } - + cond.append("( $videoCondString )") } - - + + if (haveAudio) { if (cond.isNotEmpty()) { cond.append("OR ") } - + cond.append("( $audioCondString )") } - + val condString = "AND ( $cond )" - + return condString } - + private fun getCond(cond: Boolean, condString: String): String { if (cond) { return condString @@ -262,7 +262,7 @@ interface IDBUtils { return "1 == 0" } } - + fun logRowWithId(context: Context, id: String) { if (LogUtils.isLog) { val splitter = "".padStart(40, '-') @@ -279,9 +279,31 @@ interface IDBUtils { LogUtils.info("log error row $id end $splitter") } } - + fun getMediaUri(context: Context, id: String, type: Int): String - + fun getOnlyGalleryList(context: Context, requestType: Int, timeStamp: Long, option: FilterOption): List - + + fun getDateCond(args: ArrayList, timestamp: Long, option: FilterOption): String { + val minMs = option.dateCond.minMs + val maxMs = option.dateCond.maxMs + + val dateSelection = "AND ( ${MediaStore.Images.Media.DATE_ADDED} >= ? AND ${MediaStore.Images.Media.DATE_ADDED} <= ? )" + args.add((minMs / 1000).toString()) + args.add((maxMs / 1000).toString()) + return dateSelection + } + + + fun getSortOrder(start: Int, pageSize: Int, filterOption: FilterOption): String { + val asc = + if (filterOption.dateCond.asc) { + "ASC" + } else { + "DESC" + } + return "${MediaStore.Files.FileColumns.DATE_ADDED} $asc LIMIT $pageSize OFFSET $start" + } + + } \ No newline at end of file diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index bdb2425c..35034773 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -4,31 +4,25 @@ PODS: - Flutter - video_player (0.0.1): - Flutter - - video_player_web (0.0.1): - - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - - photo_manager (from `/Users/caijinglong/code/flutter/plugin/flutter_photo_manager/ios/photo_manager.podspec`) - - video_player (from `/Users/caijinglong/.pub-cache/hosted/pub.flutter-io.cn/video_player-0.10.7/ios/video_player.podspec`) - - video_player_web (from `/Users/caijinglong/.pub-cache/hosted/pub.flutter-io.cn/video_player_web-0.1.2/ios/video_player_web.podspec`) + - photo_manager (from `/Users/cai/Documents/GitHub/flutter_photo_manager/ios/photo_manager.podspec`) + - "video_player (from `/Users/cai/.pub-cache/hosted/pub.flutter-io.cn/video_player-0.10.2+5/ios/video_player.podspec`)" EXTERNAL SOURCES: Flutter: :path: Flutter photo_manager: - :path: "/Users/caijinglong/code/flutter/plugin/flutter_photo_manager/ios/photo_manager.podspec" + :path: "/Users/cai/Documents/GitHub/flutter_photo_manager/ios/photo_manager.podspec" video_player: - :path: "/Users/caijinglong/.pub-cache/hosted/pub.flutter-io.cn/video_player-0.10.7/ios/video_player.podspec" - video_player_web: - :path: "/Users/caijinglong/.pub-cache/hosted/pub.flutter-io.cn/video_player_web-0.1.2/ios/video_player_web.podspec" + :path: "/Users/cai/.pub-cache/hosted/pub.flutter-io.cn/video_player-0.10.2+5/ios/video_player.podspec" SPEC CHECKSUMS: Flutter: 0e3d915762c693b495b44d77113d4970485de6ec photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67 video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 - video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 PODFILE CHECKSUM: 4d8d59437a9bee20e84f185bba22b518812142d3 -COCOAPODS: 1.9.1 +COCOAPODS: 1.8.4 diff --git a/example/lib/model/photo_provider.dart b/example/lib/model/photo_provider.dart index 9524157c..88f759e4 100644 --- a/example/lib/model/photo_provider.dart +++ b/example/lib/model/photo_provider.dart @@ -8,8 +8,6 @@ class PhotoProvider extends ChangeNotifier { RequestType type = RequestType.common; - DateTime dt = DateTime.now(); - var hasAll = true; var onlyAll = false; @@ -27,6 +25,34 @@ class PhotoProvider extends ChangeNotifier { notifyListeners(); } + DateTime _startDt = DateTime.now() + .subtract(Duration(days: 365 * 8)); // Default Before 8 years + + DateTime get startDt => _startDt; + + set startDt(DateTime startDt) { + _startDt = startDt; + notifyListeners(); + } + + DateTime _endDt = DateTime.now(); + + DateTime get endDt => _endDt; + + set endDt(DateTime endDt) { + _endDt = endDt; + notifyListeners(); + } + + bool _asc = false; + + bool get asc => _asc; + + set asc(bool asc) { + _asc = asc; + notifyListeners(); + } + var _thumbFormat = ThumbFormat.png; ThumbFormat get thumbFormat => _thumbFormat; @@ -81,16 +107,6 @@ class PhotoProvider extends ChangeNotifier { notifyListeners(); } - void changeDateToNow() { - this.dt = DateTime.now(); - notifyListeners(); - } - - void changeDate(DateTime pickDt) { - this.dt = pickDt; - notifyListeners(); - } - void reset() { this.list.clear(); pathProviderMap.clear(); @@ -106,7 +122,6 @@ class PhotoProvider extends ChangeNotifier { reset(); var galleryList = await PhotoManager.getAssetPathList( - fetchDateTime: dt, type: type, hasAll: hasAll, onlyAll: onlyAll, @@ -155,15 +170,22 @@ class PhotoProvider extends ChangeNotifier { needTitle: needTitle, ); + final dtCond = DateTimeCond( + min: startDt, + max: endDt, + asc: asc, + ); + return FilterOptionGroup() ..setOption(AssetType.video, option) ..setOption(AssetType.image, option) - ..setOption(AssetType.audio, option); + ..setOption(AssetType.audio, option) + ..dateTimeCond = dtCond; } Future refreshAllGalleryProperties() async { for (var gallery in list) { - await gallery.refreshPathProperties(dt: DateTime.now()); + await gallery.refreshPathProperties(); } notifyListeners(); } diff --git a/example/lib/page/filter_option_page.dart b/example/lib/page/filter_option_page.dart index a824035f..f034723d 100644 --- a/example/lib/page/filter_option_page.dart +++ b/example/lib/page/filter_option_page.dart @@ -46,6 +46,25 @@ class _FilterOptionPageState extends State { provider.maxDuration = duration; }, ), + buildDateTimeWidget( + provider, + "Start DateTime", + provider.startDt, + (DateTime dateTime) { + provider.startDt = dateTime; + }, + ), + buildDateTimeWidget( + provider, + "End DateTime", + provider.endDt, + (DateTime dateTime) { + if (provider.startDt.difference(dateTime) < Duration.zero) { + provider.endDt = dateTime; + } + }, + ), + buildDateAscCheck(provider), ], ), ); @@ -89,7 +108,8 @@ class _FilterOptionPageState extends State { animation: listenable, builder: (context, snapshot) { return ListTile( - title: Text( + title: Text(title), + subtitle: Text( "${value.inHours.toString().padLeft(2, '0')}h : ${(value.inMinutes % 60).toString().padLeft(2, '0')}m"), onTap: () async { // final duration = await showDurationPicker( @@ -113,4 +133,42 @@ class _FilterOptionPageState extends State { }, ); } + + Widget buildDateTimeWidget( + PhotoProvider provider, + String title, + DateTime startDt, + void Function(DateTime dateTime) onChange, + ) { + return ListTile( + title: Text(title), + subtitle: Text("$startDt"), + onTap: () async { + final result = await showDatePicker( + context: context, + initialDate: startDt, + firstDate: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true), + lastDate: DateTime.now().add(Duration(days: 1)), + ); + + if (result != null) { + onChange(result); + } + }, + trailing: RaisedButton( + child: Text("Today"), + onPressed: () {}, + ), + ); + } + + Widget buildDateAscCheck(PhotoProvider provider) { + return CheckboxListTile( + title: Text("Date sort asc"), + value: provider.asc, + onChanged: (bool value) { + provider.asc = value; + }, + ); + } } diff --git a/example/lib/page/home_page.dart b/example/lib/page/home_page.dart index 7db5c44b..8aa8f172 100644 --- a/example/lib/page/home_page.dart +++ b/example/lib/page/home_page.dart @@ -43,13 +43,6 @@ class _NewHomePageState extends State { ], ), _buildTypeChecks(provider), - Row( - children: [ - _buildFecthDtPicker(), - _buildDateToNow(), - ], - mainAxisSize: MainAxisSize.min, - ), _buildHasAllCheck(), _buildOnlyAllCheck(), _buildPngCheck(), @@ -112,23 +105,6 @@ class _NewHomePageState extends State { )); } - Widget _buildFecthDtPicker() { - final dt = provider.dt; - return buildButton( - "${dt.year}-${dt.month}-${dt.day} ${dt.hour}:${dt.minute}:${dt.second}", - () async { - final pickDt = await showDatePicker( - context: context, - firstDate: DateTime(2018, 1, 1), - initialDate: dt, - lastDate: DateTime.now(), - ); - if (pickDt != null) { - provider.changeDate(pickDt); - } - }); - } - Widget _buildHasAllCheck() { return CheckboxListTile( value: provider.hasAll, @@ -159,12 +135,6 @@ class _NewHomePageState extends State { ); } - Widget _buildDateToNow() { - return buildButton("Date to now", () { - provider.changeDateToNow(); - }); - } - Widget _buildNotifyCheck() { return CheckboxListTile( value: provider.notifying, diff --git a/ios/Classes/core/ConvertUtils.m b/ios/Classes/core/ConvertUtils.m index 4d9a9e40..3a118cb7 100644 --- a/ios/Classes/core/ConvertUtils.m +++ b/ios/Classes/core/ConvertUtils.m @@ -106,6 +106,7 @@ + (PMFilterOptionGroup *)convertMapToOptionContainer:(NSDictionary *)map { container.imageOption = [self convertMapToPMFilterOption:image]; container.videoOption = [self convertMapToPMFilterOption:video]; container.audioOption = [self convertMapToPMFilterOption:audio]; + container.dateOption = [self convertMapToPMDateOption:map[@"date"]]; return container; } @@ -126,14 +127,28 @@ + (PMFilterOption *)convertMapToPMFilterOption:(NSDictionary *)map { PMDurationConstraint durationConstraint; durationConstraint.minDuration = - [ConvertUtils convertNSNumberToSecond:durationMap[@"min"]]; + [ConvertUtils convertNSNumberToSecond:durationMap[@"min"]]; durationConstraint.maxDuration = - [ConvertUtils convertNSNumberToSecond:durationMap[@"max"]]; + [ConvertUtils convertNSNumberToSecond:durationMap[@"max"]]; option.durationConstraint = durationConstraint; return option; } ++ (PMDateOption *)convertMapToPMDateOption:(NSDictionary *)map { + PMDateOption *option = [PMDateOption new]; + + long min = [map[@"min"] longValue]; + long max = [map[@"max"] longValue]; + BOOL asc = [map[@"asc"] boolValue]; + + option.min = [NSDate dateWithTimeIntervalSince1970:(min / 1000.0)]; + option.max = [NSDate dateWithTimeIntervalSince1970:(max / 1000.0)]; + option.asc = asc; + + return option; +} + + (double)convertNSNumberToSecond:(NSNumber *)number { unsigned int i = number.unsignedIntValue; return (double) i / 1000.0; diff --git a/ios/Classes/core/PMFilterOption.h b/ios/Classes/core/PMFilterOption.h index 39747281..15ce1831 100644 --- a/ios/Classes/core/PMFilterOption.h +++ b/ios/Classes/core/PMFilterOption.h @@ -4,6 +4,19 @@ #import +@interface PMDateOption : NSObject + +@property(nonatomic, strong) NSDate *min; +@property(nonatomic, strong) NSDate *max; +@property(nonatomic, assign) BOOL asc; + +- (NSString *)dateCond; + +- (NSArray *)dateArgs; + +- (NSSortDescriptor *)sortCond; +@end + typedef struct PMSizeConstraint { unsigned int minWidth; @@ -41,5 +54,7 @@ typedef struct PMDurationConstraint { @property(nonatomic, strong) PMFilterOption *imageOption; @property(nonatomic, strong) PMFilterOption *videoOption; @property(nonatomic, strong) PMFilterOption *audioOption; +@property(nonatomic, strong) PMDateOption *dateOption; -@end \ No newline at end of file +- (NSArray *)sortCond; +@end diff --git a/ios/Classes/core/PMFilterOption.m b/ios/Classes/core/PMFilterOption.m index f6941e63..86294b85 100644 --- a/ios/Classes/core/PMFilterOption.m +++ b/ios/Classes/core/PMFilterOption.m @@ -7,6 +7,13 @@ @implementation PMFilterOptionGroup { } +- (NSArray *)sortCond { + PMDateOption *dateOption = self.dateOption; + return @[ + [dateOption sortCond] + ]; +} + @end @implementation PMFilterOption { @@ -23,7 +30,6 @@ - (NSArray *)sizeArgs { - (NSString *)durationCond { - return @"duration >= %f AND duration <= %f"; } @@ -32,4 +38,23 @@ - (NSArray *)durationArgs { return @[@(constraint.minDuration), @(constraint.maxDuration)]; } +@end + + +@implementation PMDateOption { + +} + +- (NSString *)dateCond { + return @"AND ( creationDate >= %@ AND creationDate <= %@ )"; +} + +- (NSArray *)dateArgs { + return @[self.min, self.max]; +} + +- (NSSortDescriptor *)sortCond { + return [NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:self.asc]; +} + @end \ No newline at end of file diff --git a/ios/Classes/core/PMManager.m b/ios/Classes/core/PMManager.m index fb74f8a2..1d0c265d 100644 --- a/ios/Classes/core/PMManager.m +++ b/ios/Classes/core/PMManager.m @@ -635,7 +635,7 @@ - (PHFetchOptions *)getAssetOptions:(int)type date:(NSDate *)date filterOption:(PMFilterOptionGroup *)optionGroup { PHFetchOptions *options = [PHFetchOptions new]; - options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; + options.sortDescriptors = [optionGroup sortCond]; NSMutableString *cond = [NSMutableString new]; NSMutableArray *args = [NSMutableArray new]; @@ -709,8 +709,12 @@ - (PHFetchOptions *)getAssetOptions:(int)type [cond insertString:@"(" atIndex:0]; [cond appendString:@")"]; - [cond appendString:@" AND ( creationDate <= %@ )"]; - [args addObject:date]; +// [cond appendString:@" AND ( creationDate <= %@ )"]; +// [args addObject:date]; + + PMDateOption *dateOption = optionGroup.dateOption; + [cond appendString:[dateOption dateCond]]; + [args addObjectsFromArray:[dateOption dateArgs]]; options.predicate = [NSPredicate predicateWithFormat:cond argumentArray:args]; diff --git a/lib/src/entity.dart b/lib/src/entity.dart index 3592dbac..ad824962 100644 --- a/lib/src/entity.dart +++ b/lib/src/entity.dart @@ -41,18 +41,16 @@ class AssetPathEntity { /// This path is the path that contains all the assets. bool isAll = false; - /// The timestamp of the path, when the request page number is 0, reset it to the current time. When other page numbers are passed directly. - DateTime fetchDatetime; - AssetPathEntity({this.id, this.name, this.filterOption}); - Future refreshPathProperties({DateTime dt}) async { - dt ??= DateTime.now(); - final result = await PhotoManager.fetchPathProperties(this, dt); + Future refreshPathProperties({DateTimeCond dateTimeCond}) async { + dateTimeCond ??= + this.filterOption.dateTimeCond.copyWith(max: DateTime.now()); + final result = await PhotoManager.fetchPathProperties(this, dateTimeCond); if (result != null) { this.assetCount = result.assetCount; - this.fetchDatetime = result.fetchDatetime; this.name = result.name; + this.filterOption.dateTimeCond = dateTimeCond; } } @@ -239,7 +237,7 @@ class AssetEntity { width: width, height: height, format: format, - quality:quality, + quality: quality, ); } diff --git a/lib/src/filter/filter_options.dart b/lib/src/filter/filter_options.dart index 6730e875..c0e33e77 100644 --- a/lib/src/filter/filter_options.dart +++ b/lib/src/filter/filter_options.dart @@ -17,6 +17,8 @@ class FilterOptionGroup { final Map _map = {}; + DateTimeCond dateTimeCond = DateTimeCond.def(); + void setOption(AssetType type, FilterOption option) { _map[type] = option; } @@ -33,6 +35,8 @@ class FilterOptionGroup { result["audio"] = _map[AssetType.audio].toMap(); } + result["date"] = dateTimeCond.toMap(); + return result; } } @@ -109,3 +113,47 @@ class DurationConstraint { }; } } + +class DateTimeCond { + static final DateTime zero = DateTime.utc(0); + + final DateTime min; + final DateTime max; + final bool asc; + + const DateTimeCond({ + this.min, + this.max, + this.asc = false, // default desc + }) : assert(min != null), + assert(max != null), + assert(asc != null); + + factory DateTimeCond.def() { + return DateTimeCond( + min: zero, + max: DateTime.now(), + asc: false, + ); + } + + DateTimeCond copyWith({ + final DateTime min, + final DateTime max, + final bool asc, + }) { + return DateTimeCond( + min: min ?? this.min, + max: max ?? this.max, + asc: asc ?? this.asc, + ); + } + + Map toMap() { + return { + "min": min.millisecondsSinceEpoch, + "max": max.millisecondsSinceEpoch, + "asc": asc, + }; + } +} diff --git a/lib/src/manager.dart b/lib/src/manager.dart index 63d6bf70..f829d0e2 100644 --- a/lib/src/manager.dart +++ b/lib/src/manager.dart @@ -28,7 +28,6 @@ class PhotoManager { bool hasAll = true, bool onlyAll = false, RequestType type = RequestType.common, - DateTime fetchDateTime, FilterOptionGroup filterOption, }) async { assert(hasAll != null); @@ -39,7 +38,6 @@ class PhotoManager { filterOption ??= FilterOptionGroup(); return _plugin.getAllGalleryList( type: type.index, - dt: fetchDateTime, hasAll: hasAll, onlyAll: onlyAll, optionGroup: filterOption, @@ -79,7 +77,6 @@ class PhotoManager { page: page, pageCount: pageCount, type: entity.typeInt, - pagedDt: entity.fetchDatetime, optionGroup: entity.filterOption, ); } @@ -98,7 +95,6 @@ class PhotoManager { typeInt: entity.typeInt, start: start, end: end, - fetchDt: entity.fetchDatetime, optionGroup: entity.filterOption, ); } @@ -183,29 +179,24 @@ class PhotoManager { return _plugin.assetExistsWithId(id); } - // static Future _createAssetEntityWithId(String id) async { - // return AssetEntity(id: id); - // } - static Future fetchPathProperties( AssetPathEntity entity, - DateTime time, + DateTimeCond dateTimeCond, ) async { assert(entity != null); - var result = await _plugin.fetchPathProperties( + assert(dateTimeCond != null); + final result = await _plugin.fetchPathProperties( entity.id, entity.typeInt, - time, entity.filterOption, ); if (result == null) { return null; } - var list = result["data"]; + final list = result["data"]; if (list is List && list.isNotEmpty) { return ConvertUtils.convertPath( result, - dt: time, type: entity.typeInt, optionGroup: entity.filterOption, )[0]; diff --git a/lib/src/plugin.dart b/lib/src/plugin.dart index a651f430..5f4d6ae6 100644 --- a/lib/src/plugin.dart +++ b/lib/src/plugin.dart @@ -137,14 +137,13 @@ class Plugin { _channel.invokeMethod("openSetting"); } - Future fetchPathProperties(String id, int type, DateTime datetime, - FilterOptionGroup optionGroup) async { - datetime ??= _createDefaultFetchDatetime(); + Future fetchPathProperties( + String id, int type, FilterOptionGroup optionGroup) async { return _channel.invokeMethod( "fetchPathProperties", { "id": id, - "timestamp": datetime.millisecondsSinceEpoch, + "timestamp": 0, "type": type, "option": optionGroup.toMap(), }, diff --git a/lib/src/utils/convert_utils.dart b/lib/src/utils/convert_utils.dart index 47182d51..fd3a89ec 100644 --- a/lib/src/utils/convert_utils.dart +++ b/lib/src/utils/convert_utils.dart @@ -17,7 +17,6 @@ class ConvertUtils { ..name = item["name"] ..typeInt = type ..isAll = item["isAll"] - ..fetchDatetime = dt ..assetCount = item["length"]; result.add(entity); From 6221d09334afe8f1d0620fefaea472ca0f613efa Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Mon, 23 Mar 2020 10:38:48 +0800 Subject: [PATCH 2/3] Update readme. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3cdaedcb..5170230f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A flutter api for photo, you can get image/video from ios or android. If you just need a picture selector, you can choose to use [photo](https://pub.dartlang.org/packages/photo) library , a multi image picker. All UI create by flutter. -- [photo_manager](#photo_manager) +- [photo_manager](#photomanager) - [install](#install) - [Add to pubspec](#add-to-pubspec) - [import in dart code](#import-in-dart-code) @@ -78,12 +78,11 @@ if (result) { List list = await PhotoManager.getAssetPathList(); ``` -| name | description | -| ------------- | --------------------------------------------------------------------------------------------- | -| hasAll | Is there an album containing "all" | -| type | image/video/all , default all. | -| fetchDateTime | Only include resources older than that time.(Will be included in filterOption in the future.) | -| fliterOption | See FilterOption. | +| name | description | +| ------------ | ---------------------------------- | +| hasAll | Is there an album containing "all" | +| type | image/video/all , default all. | +| fliterOption | See FilterOption. | #### FilterOption @@ -92,6 +91,7 @@ List list = await PhotoManager.getAssetPathList(); | needTitle | The title attribute of the picture must be included in android (even if it is false), it is more performance-consuming in iOS, please consider whether you need it. The default is false. | | sizeConstraint | Constraints on resource size. | | durationConstraint | Constraints of time, pictures will ignore this constraint. | +| dateTimeCond | Includes date filtering and date sorting | Example see [filter_option_page.dart](https://github.com/CaiJingLong/flutter_photo_manager/blob/filter-option/example/lib/page/filter_option_page.dart). From b1a749eb2c92f0d5943f31b6ec1959100fa6e873 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Mon, 23 Mar 2020 10:42:28 +0800 Subject: [PATCH 3/3] Update example. --- example/lib/page/filter_option_page.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/lib/page/filter_option_page.dart b/example/lib/page/filter_option_page.dart index f034723d..f7ca0dc3 100644 --- a/example/lib/page/filter_option_page.dart +++ b/example/lib/page/filter_option_page.dart @@ -157,7 +157,9 @@ class _FilterOptionPageState extends State { }, trailing: RaisedButton( child: Text("Today"), - onPressed: () {}, + onPressed: () { + onChange(DateTime.now()); + }, ), ); }