diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4a52e3c4..ecf26c8f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,8 +10,13 @@ All notable changes to this project will be documented in this file.
### Changed
+- request notification permission when launching scanning service
- upgraded Flutter to stable v3.24.1
+### Fixed
+
+- duplicates from new item loading/refreshing
+
## [v1.11.9] - 2024-08-07
### Added
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index a83c2ec03..434def855 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -76,7 +76,7 @@
-->
-
+
@@ -92,7 +92,7 @@
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt
index 4ae662b5f..1636543a9 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt
@@ -96,7 +96,7 @@ object PermissionManager {
segments.volumePath?.let { volumePath ->
val dirSet = dirsPerVolume[volumePath] ?: HashSet()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- // request primary directory on volume from Android 11
+ // request primary directory on volume from Android 11 (API 30)
val relativeDir = segments.relativeDir
if (relativeDir != null) {
val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
@@ -172,7 +172,6 @@ object PermissionManager {
val accessibleDirs = HashSet(getGrantedDirs(context))
accessibleDirs.addAll(context.getExternalFilesDirs(null).filterNotNull().map { it.path })
- // from API 19 / Android 4.4 / KitKat, removable storage requires access permission, at the file level
// from API 21 / Android 5.0 / Lollipop, removable storage requires access permission, but directory access grant is possible
// from API 30 / Android 11 / R, any storage requires access permission
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
index ff888a95f..d7d4a6011 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt
@@ -565,7 +565,7 @@ object StorageUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isMediaStoreContentUri(uri)) {
val path = uri.path
path ?: return uri
- // from Android 11, accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
+ // from Android 11 (API 30), accessing the original URI for a `file` or `downloads` media content yields a `SecurityException`
if (path.startsWith(IMAGE_PATH_ROOT) || path.startsWith(VIDEO_PATH_ROOT)) {
// "Caller must hold ACCESS_MEDIA_LOCATION permission to access original"
if (context.checkSelfPermission(Manifest.permission.ACCESS_MEDIA_LOCATION) == PackageManager.PERMISSION_GRANTED) {
diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart
index e66b49601..0b318dc15 100644
--- a/lib/model/source/collection_source.dart
+++ b/lib/model/source/collection_source.dart
@@ -497,7 +497,6 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
}
}
if (startAnalysisService) {
- // TODO TLAD [tiramisu] explain foreground service and request POST_NOTIFICATIONS permission
await AnalysisService.startService(
force: force,
entryIds: entries?.map((entry) => entry.id).toList(),
diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart
index 3460d890b..f06263480 100644
--- a/lib/services/analysis_service.dart
+++ b/lib/services/analysis_service.dart
@@ -13,6 +13,7 @@ import 'package:aves_model/aves_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
+import 'package:permission_handler/permission_handler.dart';
class AnalysisService {
static const _platform = MethodChannel('deckers.thibault/aves/analysis');
@@ -29,6 +30,10 @@ class AnalysisService {
}
static Future startService({required bool force, List? entryIds}) async {
+ // from Android 13 (API 33), notifications are off by default,
+ // so the user needs to grant the permission to see the service notification
+ unawaited(Permission.notification.request());
+
await reportService.log('Start analysis service${entryIds != null ? ' for ${entryIds.length} items' : ''}');
try {
await _platform.invokeMethod('startAnalysis', {