Skip to content

Commit 17a79b4

Browse files
committed
add is_favorite support for Android R and above using MediaColumns.IS_FAVORITE
[1] added a `PhotoManagerFavoriteManager` class which implements `is_favorite` for Android R and above where MediaColumns.IS_FAVORITE is introduced [2] updated `Toggle isFavorite` button for `example` project
1 parent e27d1f1 commit 17a79b4

File tree

12 files changed

+84
-53
lines changed

12 files changed

+84
-53
lines changed

android/src/main/kotlin/com/fluttercandies/photo_manager/PhotoManagerPlugin.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class PhotoManagerPlugin : FlutterPlugin, ActivityAware {
8484
binding.addRequestPermissionsResultListener(listener)
8585
plugin?.let {
8686
binding.addActivityResultListener(it.deleteManager)
87+
binding.addActivityResultListener(it.favoriteManager)
8788
}
8889
}
8990

@@ -93,6 +94,7 @@ class PhotoManagerPlugin : FlutterPlugin, ActivityAware {
9394
}
9495
plugin?.let { p ->
9596
oldBinding.removeActivityResultListener(p.deleteManager)
97+
oldBinding.removeActivityResultListener(p.favoriteManager)
9698
}
9799
}
98100
}

android/src/main/kotlin/com/fluttercandies/photo_manager/constant/Methods.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class Methods {
8181
const val saveImage = "saveImage"
8282
const val saveImageWithPath = "saveImageWithPath"
8383
const val saveVideo = "saveVideo"
84+
const val favoriteAsset = "favoriteAsset"
8485
const val copyAsset = "copyAsset"
8586
const val moveAssetToPath = "moveAssetToPath"
8687
const val removeNoExistsAssets = "removeNoExistsAssets"

android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.fluttercandies.photo_manager.core
22

3+
import android.app.Activity
4+
import android.app.RecoverableSecurityException
35
import android.content.Context
46
import android.graphics.Bitmap
57
import android.net.Uri
68
import android.os.Build
9+
import android.provider.MediaStore
710
import android.util.Log
811
import com.bumptech.glide.Glide
912
import com.bumptech.glide.request.FutureTarget

android/src/main/kotlin/com/fluttercandies/photo_manager/core/PhotoManagerPlugin.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.fluttercandies.photo_manager.core
22

33
import android.app.Activity
4+
import android.app.RecoverableSecurityException
45
import android.content.Context
56
import android.net.Uri
67
import android.os.Build
@@ -58,11 +59,13 @@ class PhotoManagerPlugin(
5859
}
5960

6061
val deleteManager = PhotoManagerDeleteManager(applicationContext, activity)
62+
val favoriteManager = PhotoManagerFavoriteManager(applicationContext)
6163

6264
fun bindActivity(activity: Activity?) {
6365
this.activity = activity
6466
permissionsUtils.withActivity(activity)
6567
deleteManager.bindActivity(activity)
68+
favoriteManager.bindActivity(activity)
6669
}
6770

6871
private val notifyChannel = PhotoManagerNotifyChannel(
@@ -541,6 +544,17 @@ class PhotoManagerPlugin(
541544
}
542545
}
543546

547+
Methods.favoriteAsset -> {
548+
val assetId = call.argument<String>("id")!!
549+
val isFavorite = call.argument<Boolean>("favorite")!!
550+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
551+
LogUtils.error("The API 30 or lower have no IS_FAVORITE row in MediaStore.")
552+
resultHandler.reply(false)
553+
return
554+
}
555+
favoriteManager.favoriteAsset(photoManager.getUri(assetId), isFavorite, resultHandler)
556+
}
557+
544558
Methods.copyAsset -> {
545559
val assetId = call.argument<String>("assetId")!!
546560
val galleryId = call.argument<String>("galleryId")!!

android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/AssetEntity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ data class AssetEntity(
1616
val displayName: String,
1717
val modifiedDate: Long,
1818
val orientation: Int,
19+
val isFavorite: Boolean = false,
1920
val lat: Double? = null,
2021
val lng: Double? = null,
2122
val androidQRelativePath: String? = null,

android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/ConvertUtils.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ object ConvertUtils {
5050
"width" to entity.width,
5151
"height" to entity.height,
5252
"orientation" to entity.orientation,
53+
"is_favorite" to entity.isFavorite,
5354
"modifiedDt" to entity.modifiedDate,
5455
"lat" to entity.lat,
5556
"lng" to entity.lng,

android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/IDBUtils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import android.provider.MediaStore.MediaColumns.DATE_TAKEN
1919
import android.provider.MediaStore.MediaColumns.DISPLAY_NAME
2020
import android.provider.MediaStore.MediaColumns.DURATION
2121
import android.provider.MediaStore.MediaColumns.HEIGHT
22+
import android.provider.MediaStore.MediaColumns.IS_FAVORITE
2223
import android.provider.MediaStore.MediaColumns.MIME_TYPE
2324
import android.provider.MediaStore.MediaColumns.ORIENTATION
2425
import android.provider.MediaStore.MediaColumns.RELATIVE_PATH
@@ -27,6 +28,7 @@ import android.provider.MediaStore.MediaColumns.WIDTH
2728
import android.provider.MediaStore.MediaColumns._ID
2829
import android.provider.MediaStore.VOLUME_EXTERNAL
2930
import androidx.annotation.ChecksSdkIntAtLeast
31+
import androidx.annotation.RequiresApi
3032
import androidx.exifinterface.media.ExifInterface
3133
import com.fluttercandies.photo_manager.core.PhotoManager
3234
import com.fluttercandies.photo_manager.core.entity.AssetEntity
@@ -61,6 +63,7 @@ interface IDBUtils {
6163
DATE_TAKEN //日期
6264
).apply {
6365
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) add(DATE_TAKEN) // 拍摄时间
66+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) add(IS_FAVORITE)
6467
}
6568

6669
val storeVideoKeys = mutableListOf(
@@ -79,6 +82,7 @@ interface IDBUtils {
7982
DURATION //时长
8083
).apply {
8184
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) add(DATE_TAKEN) // 拍摄时间
85+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) add(IS_FAVORITE)
8286
}
8387

8488
val typeKeys = arrayOf(
@@ -199,6 +203,7 @@ interface IDBUtils {
199203
val displayName = getString(DISPLAY_NAME)
200204
val modifiedDate = getLong(DATE_MODIFIED)
201205
var orientation: Int = getInt(ORIENTATION)
206+
val isFavorite = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && getInt(IS_FAVORITE) == 1
202207
val relativePath: String? = if (isAboveAndroidQ) {
203208
getString(RELATIVE_PATH)
204209
} else null
@@ -240,6 +245,7 @@ interface IDBUtils {
240245
displayName,
241246
modifiedDate,
242247
orientation,
248+
isFavorite,
243249
androidQRelativePath = relativePath,
244250
mimeType = mimeType
245251
)

example/lib/page/image_list_page.dart

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import '../util/log.dart';
1515
import '../widget/dialog/list_dialog.dart';
1616
import '../widget/image_item_widget.dart';
1717
import '../widget/loading_widget.dart';
18-
1918
import 'copy_to_another_gallery_example.dart';
2019
import 'detail_page.dart';
2120
import 'move_to_another_gallery_example.dart';
@@ -207,36 +206,40 @@ class _GalleryContentListPageState extends State<GalleryContentListPage> {
207206
<int>[500, 600, 700, 1000, 1500, 2000],
208207
),
209208
),
210-
if (Platform.isIOS || Platform.isMacOS || PlatformUtils.isOhos)
211-
ElevatedButton(
212-
child: const Text('Toggle isFavorite'),
213-
onPressed: () async {
214-
final bool isFavorite = entity.isFavorite;
215-
print('Current isFavorite: $isFavorite');
216-
if (PlatformUtils.isOhos) {
217-
await PhotoManager.editor.ohos.favoriteAsset(
218-
entity: entity,
219-
favorite: !isFavorite,
220-
);
221-
} else {
222-
await PhotoManager.editor.darwin.favoriteAsset(
223-
entity: entity,
224-
favorite: !isFavorite,
225-
);
226-
}
227-
final AssetEntity? newEntity =
228-
await entity.obtainForNewProperties();
229-
print('New isFavorite: ${newEntity?.isFavorite}');
230-
if (!mounted) {
231-
return;
232-
}
233-
if (newEntity != null) {
234-
entity = newEntity;
235-
readPathProvider(context).list[index] = newEntity;
236-
setState(() {});
237-
}
238-
},
239-
),
209+
ElevatedButton(
210+
child: const Text('Toggle isFavorite'),
211+
onPressed: () async {
212+
final bool isFavorite = entity.isFavorite;
213+
print('Current isFavorite: $isFavorite');
214+
if (PlatformUtils.isOhos) {
215+
await PhotoManager.editor.ohos.favoriteAsset(
216+
entity: entity,
217+
favorite: !isFavorite,
218+
);
219+
} else if (Platform.isAndroid) {
220+
await PhotoManager.editor.android.favoriteAsset(
221+
entity: entity,
222+
favorite: !isFavorite,
223+
);
224+
} else {
225+
await PhotoManager.editor.darwin.favoriteAsset(
226+
entity: entity,
227+
favorite: !isFavorite,
228+
);
229+
}
230+
final AssetEntity? newEntity =
231+
await entity.obtainForNewProperties();
232+
print('New isFavorite: ${newEntity?.isFavorite}');
233+
if (!mounted) {
234+
return;
235+
}
236+
if (newEntity != null) {
237+
entity = newEntity;
238+
readPathProvider(context).list[index] = newEntity;
239+
setState(() {});
240+
}
241+
},
242+
),
240243
if ((Platform.isIOS || Platform.isMacOS) && entity.isLivePhoto)
241244
ElevatedButton(
242245
onPressed: () {

example/lib/util/common_util.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class CommonUtil {
4949
_buildInfoItemAsync('title', entity.titleAsync),
5050
_buildInfoItem('lat', lat.toString()),
5151
_buildInfoItem('lng', lng.toString()),
52+
_buildInfoItem('is favorite', entity.isFavorite.toString()),
5253
_buildInfoItem('relative path', entity.relativePath ?? 'null'),
5354
_buildInfoItemAsync('mimeType', entity.mimeTypeAsync),
5455
],

lib/src/internal/editor.dart

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -259,19 +259,12 @@ class DarwinEditor {
259259

260260
/// Sets the favorite status of the given [entity].
261261
///
262-
/// Returns the updated [AssetEntity] if the operation was successful; otherwise, `null`.
263-
Future<AssetEntity> favoriteAsset({
262+
/// Returns `true` if the operation was successful; otherwise, `false`.
263+
Future<bool> favoriteAsset({
264264
required AssetEntity entity,
265265
required bool favorite,
266266
}) async {
267-
final bool result = await plugin.favoriteAsset(entity.id, favorite);
268-
if (result) {
269-
return entity.copyWith(isFavorite: favorite);
270-
}
271-
throw StateError(
272-
'Failed to favorite the asset '
273-
'${entity.id} for unknown reason',
274-
);
267+
return plugin.favoriteAsset(entity.id, favorite);
275268
}
276269

277270
/// Save Live Photo to the gallery from the given [imageFile] and [videoFile].
@@ -305,6 +298,16 @@ class AndroidEditor {
305298
/// Creates a new [AndroidEditor] object.
306299
const AndroidEditor();
307300

301+
/// Sets the favorite status of the given [entity].
302+
///
303+
/// Returns `true` if the operation was successful; otherwise, `false`.
304+
Future<bool> favoriteAsset({
305+
required AssetEntity entity,
306+
required bool favorite,
307+
}) async {
308+
return plugin.favoriteAsset(entity.id, favorite);
309+
}
310+
308311
/// Moves the given [entity] to the specified [target] path.
309312
///
310313
/// Returns `true` if the move was successful; otherwise, `false`.
@@ -341,18 +344,11 @@ class OhosEditor {
341344

342345
/// Sets the favorite status of the given [entity].
343346
///
344-
/// Returns the updated [AssetEntity] if the operation was successful; otherwise, `null`.
345-
Future<AssetEntity> favoriteAsset({
347+
/// Returns `true` if the operation was successful; otherwise, `false`.
348+
Future<bool> favoriteAsset({
346349
required AssetEntity entity,
347350
required bool favorite,
348351
}) async {
349-
final bool result = await plugin.favoriteAsset(entity.id, favorite);
350-
if (result) {
351-
return entity.copyWith(isFavorite: favorite);
352-
}
353-
throw StateError(
354-
'Failed to favorite the asset '
355-
'${entity.id} for unknown reason',
356-
);
352+
return plugin.favoriteAsset(entity.id, favorite);
357353
}
358354
}

0 commit comments

Comments
 (0)