Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
deckerst committed Dec 2, 2023
2 parents aa2b4c1 + 9eefea0 commit 5b6d7af
Show file tree
Hide file tree
Showing 522 changed files with 5,661 additions and 2,072 deletions.
2 changes: 1 addition & 1 deletion .flutter
Submodule .flutter updated 2238 files
23 changes: 21 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.

## <a id="unreleased"></a>[Unreleased]

## <a id="v1.10.0"></a>[v1.10.0] - 2023-12-02

### Added

- Viewer / Slideshow: cast images via DLNA/UPnP
- Icelandic translation (thanks Sveinn í Felli)

### Changed

- long press actions trigger haptic feedback according to OS settings
- target Android 14 (API 34)
- upgraded Flutter to stable v3.16.2

### Fixed

- temporary files remaining in the cache directory forever
- detecting motion photos with more items in the XMP Container directory
- parsing EXIF date written as epoch time

## <a id="v1.9.7"></a>[v1.9.7] - 2023-10-17

### Added
Expand All @@ -15,12 +34,12 @@ All notable changes to this project will be documented in this file.

- mosaic layout: clamp ratio to 32/9
- Video: disable subtitles by default
- Map: Stamen Watercolor layer (no longer served for free by Stamen) now served by Smithsonian Institution
- Map: Stamen Watercolor layer (no longer hosted for free by Stamen) now hosted by Smithsonian Institution
- upgraded Flutter to stable v3.13.7

### Removed

- Map: Stamen Toner layer (no longer served for free by Stamen)
- Map: Stamen Toner layer (no longer hosted for free by Stamen)

### Fixed

Expand Down
14 changes: 7 additions & 7 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ android {

defaultConfig {
applicationId packageName
// minSdkVersion constraints:
// minSdk constraints:
// - Flutter & other plugins: 16
// - google_maps_flutter v2.1.1: 20
// - to build XML documents from XMP data, `metadata-extractor` and `PixyMeta` rely on `DocumentBuilder`,
// which implementation `DocumentBuilderImpl` is provided by the OS and is not customizable on Android,
// but the implementation on API <19 is not robust enough and fails to build XMP documents
minSdkVersion 19
targetSdkVersion 33
minSdk 19
targetSdk 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders = [googleApiKey: keystoreProperties["googleApiKey"] ?: "<NONE>",
Expand Down Expand Up @@ -216,14 +216,14 @@ dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
implementation 'androidx.media:media:1.6.0'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'androidx.work:work-runtime-ktx:2.8.1'
implementation 'androidx.work:work-runtime-ktx:2.9.0'

implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.commonsware.cwac:document:0.5.0'
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
implementation 'com.drewnoakes:metadata-extractor:2.19.0'
implementation "com.github.bumptech.glide:glide:$glide_version"
// SLF4J implementation for `mp4parser`
implementation 'org.slf4j:slf4j-simple:2.0.9'
Expand All @@ -240,7 +240,7 @@ dependencies {
// huawei flavor only
huaweiImplementation "com.huawei.agconnect:agconnect-core:$huawei_agconnect_version"

testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.1'

kapt 'androidx.annotation:annotation:1.7.0'
ksp "com.github.bumptech.glide:ksp:$glide_version"
Expand Down
27 changes: 19 additions & 8 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@
<!-- to access media with original metadata with scoped storage (API >=29) -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- to provide a foreground service type, as required by Android 14 (API 34) -->
<!-- TODO TLAD revisit with Android 14 >beta5 -->
<!-- <uses-permission-->
<!-- android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"-->
<!-- tools:ignore="SystemPermissionTypo" />-->
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
tools:ignore="SystemPermissionTypo" />
<!-- TODO TLAD still needed to fetch map tiles / reverse geocoding / else ? check in release mode -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- from Android 12 (API 31), users can optionally grant access to the media management special permission -->
Expand Down Expand Up @@ -69,7 +68,7 @@
-->

<!--
allow install on API 19, despite the `minSdkVersion` declared in dependencies:
allow install on API 19, despite the `minSdk` declared in dependencies:
- Google Maps is from API 20
- the Security library is from API 21
- FFmpegKit for Flutter is from API 24 (when not LTS)
Expand Down Expand Up @@ -102,18 +101,24 @@
</intent>
</queries>

<!--
as of Flutter v3.16.0, predictive back gesture does not work
as expected when extending `FlutterFragmentActivity`
so we disable `enableOnBackInvokedCallback`
-->
<application
android:allowBackup="true"
android:appCategory="image"
android:banner="@drawable/banner"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="false"
android:fullBackupContent="@xml/full_backup_content"
android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
tools:targetApi="s">
tools:targetApi="tiramisu">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
Expand Down Expand Up @@ -248,6 +253,12 @@
</intent-filter>
</receiver>

<!-- anonymous service for analysis worker is specified here to provide service type -->
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
tools:node="merge" />

<service
android:name=".MediaPlaybackService"
android:exported="false"
Expand Down Expand Up @@ -304,8 +315,8 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- as of Flutter v3.10.1 (stable) / v3.12.0-15.0.pre.105 (master),
Impeller badly renders text, fails to render videos, and crashes with Google Maps -->
<!-- as of Flutter v3.16.0 (stable),
Impeller fails to render videos & platform views, has poor performance -->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,15 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
.setContentIntent(openAppIntent)
.addAction(stopAction)
.build()
// TODO TLAD revisit with Android 14 >beta5
return ForegroundInfo(NOTIFICATION_ID, notification);
// return if (Build.VERSION.SDK_INT >= 34) {
// // as of Android 14 beta 5, foreground service type is mandatory
// // despite the sample code omitting it at:
// // https://developer.android.com/guide/background/persistent/how-to/long-running
// val type = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
// ForegroundInfo(NOTIFICATION_ID, notification, type)
// } else {
// ForegroundInfo(NOTIFICATION_ID, notification)
// }
return if (Build.VERSION.SDK_INT >= 34) {
// from Android 14 (API 34), foreground service type is mandatory
// despite the sample code omitting it at:
// https://developer.android.com/guide/background/persistent/how-to/long-running
val type = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
ForegroundInfo(NOTIFICATION_ID, notification, type)
} else {
ForegroundInfo(NOTIFICATION_ID, notification)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.File
import java.io.InputStream

class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
Expand Down Expand Up @@ -279,8 +278,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
embeddedByteLength: Long,
) {
val extension = extensionFor(mimeType)
val targetFile = File.createTempFile("aves", extension, context.cacheDir).apply {
deleteOnExit()
val targetFile = StorageUtils.createTempFile(context, extension).apply {
transferFrom(embeddedByteStream, embeddedByteLength)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
"getRestrictedDirectories" -> ioScope.launch { safe(call, result, ::getRestrictedDirectories) }
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
"deleteEmptyDirectories" -> ioScope.launch { safe(call, result, ::deleteEmptyDirectories) }
"deleteTempDirectory" -> ioScope.launch { safe(call, result, ::deleteTempDirectory) }
"canRequestMediaFileBulkAccess" -> safe(call, result, ::canRequestMediaFileBulkAccess)
"canInsertMedia" -> safe(call, result, ::canInsertMedia)
else -> result.notImplemented()
Expand Down Expand Up @@ -200,6 +201,10 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
result.success(deleted)
}

private fun deleteTempDirectory(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(StorageUtils.deleteTempDirectory(context))
}

private fun canRequestMediaFileBulkAccess(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel
import java.io.File
import kotlin.math.roundToInt

class RegionFetcher internal constructor(
Expand Down Expand Up @@ -113,8 +112,7 @@ class RegionFetcher internal constructor(
.submit()
try {
val bitmap = target.get()
val tempFile = File.createTempFile("aves", null, context.cacheDir).apply {
deleteOnExit()
val tempFile = StorageUtils.createTempFile(context).apply {
outputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ enum class DirType {
override fun createDirectory() = ExifIFD0Directory()
},
EXIF_THUMBNAIL {
override fun createDirectory() = ExifThumbnailDirectory()
override fun createDirectory() = ExifThumbnailDirectory(0)
},
GPS {
override fun createDirectory() = GpsDirectory()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ object ExifTags {
private const val SAMPLE_FORMAT = 0x0153
private const val SMIN_SAMPLE_VALUE = 0x0154
private const val SMAX_SAMPLE_VALUE = 0x0155
private const val RATING_PERCENT = 0x4749
private const val SONY_RAW_FILE_TYPE = 0x7000
private const val SONY_TONE_CURVE = 0x7010
private const val MATTEING = 0x80e3
Expand All @@ -40,7 +39,6 @@ object ExifTags {
SAMPLE_FORMAT to "Sample Format",
SMIN_SAMPLE_VALUE to "S Min Sample Value",
SMAX_SAMPLE_VALUE to "S Max Sample Value",
RATING_PERCENT to "Rating Percent",
SONY_RAW_FILE_TYPE to "Sony Raw File Type",
SONY_TONE_CURVE to "Sony Tone Curve",
MATTEING to "Matteing",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ object Metadata {
}

fun createPreviewFile(context: Context, uri: Uri): File {
return File.createTempFile("aves", null, context.cacheDir).apply {
deleteOnExit()
return StorageUtils.createTempFile(context).apply {
transferFrom(StorageUtils.openInputStream(context, uri), PREVIEW_SIZE)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,7 @@ object MultiPage {
} else if (xmpMeta.doesPropExist(XMP.GCONTAINER_DIRECTORY_PROP_NAME)) {
// `Container` motion photo
val count = xmpMeta.countPropArrayItems(XMP.GCONTAINER_DIRECTORY_PROP_NAME)
if (count == 2) {
// expect the video to be the second item
val i = 2
for (i in 1 until count + 1) {
val mime = xmpMeta.getSafeStructField(listOf(XMP.GCONTAINER_DIRECTORY_PROP_NAME, i, XMP.GCONTAINER_ITEM_PROP_NAME, XMP.GCONTAINER_ITEM_MIME_PROP_NAME))?.value
val length = xmpMeta.getSafeStructField(listOf(XMP.GCONTAINER_DIRECTORY_PROP_NAME, i, XMP.GCONTAINER_ITEM_PROP_NAME, XMP.GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value
if (MimeTypes.isVideo(mime) && length != null) {
Expand Down
18 changes: 8 additions & 10 deletions android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,15 @@ object XMP {
// Container motion photo
if (doesPropExist(GCONTAINER_DIRECTORY_PROP_NAME)) {
val count = countPropArrayItems(GCONTAINER_DIRECTORY_PROP_NAME)
if (count == 2) {
var hasImage = false
var hasVideo = false
for (i in 1 until count + 1) {
val mime = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_MIME_PROP_NAME))?.value
val length = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value
hasImage = hasImage || MimeTypes.isImage(mime) && length != null
hasVideo = hasVideo || MimeTypes.isVideo(mime) && length != null
}
if (hasImage && hasVideo) return true
var hasImage = false
var hasVideo = false
for (i in 1 until count + 1) {
val mime = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_MIME_PROP_NAME))?.value
val length = getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value
hasImage = hasImage || MimeTypes.isImage(mime) && length != null
hasVideo = hasVideo || MimeTypes.isVideo(mime) && length != null
}
if (hasImage && hasVideo) return true
}

return false
Expand Down
Loading

0 comments on commit 5b6d7af

Please sign in to comment.