Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
deckerst committed Apr 1, 2024
2 parents 42a8699 + 4b458e0 commit 82f070f
Show file tree
Hide file tree
Showing 141 changed files with 1,616 additions and 705 deletions.
2 changes: 1 addition & 1 deletion .flutter
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.

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

## <a id="v1.10.8"></a>[v1.10.8] - 2024-04-01

### Added

- Collection: support for Fairphone burst pattern
- Collection: allow using tags/make/model when bulk renaming
- Video: A-B repeat
- Settings: hidden items can be toggled

### Changed

- opening app from launcher always show home page
- use dates with western arabic numerals for maghreb arabic locales
- album unique names are case insensitive
- upgraded Flutter to stable v3.19.5

### Fixed

- crash when decoding large region
- viewer position drift during scale
- viewer side gesture precedence (next entry by single tap vs zoom by double tap)

## <a id="v1.10.7"></a>[v1.10.7] - 2024-03-12

### Added
Expand Down
24 changes: 10 additions & 14 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id 'com.android.application'
id 'com.google.devtools.ksp' version "$ksp_version"
id 'com.google.devtools.ksp'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dev.flutter.flutter-gradle-plugin'
}

def packageName = "deckers.thibault.aves"
Expand All @@ -20,11 +21,6 @@ if (localPropertiesFile.exists()) {
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionName = localProperties.getProperty('flutter.versionName')
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

// Keys

Expand Down Expand Up @@ -54,8 +50,8 @@ android {
ndkVersion '25.1.8937393'

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

lint {
Expand Down Expand Up @@ -181,12 +177,12 @@ android {
}

tasks.withType(KotlinCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlin {
jvmToolchain(8)
jvmToolchain(17)
}

flutter {
Expand All @@ -210,7 +206,7 @@ repositories {
}

dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'

implementation "androidx.appcompat:appcompat:1.6.1"
implementation 'androidx.core:core-ktx:1.12.0'
Expand All @@ -226,7 +222,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glide_version"
implementation 'com.google.android.material:material:1.11.0'
// SLF4J implementation for `mp4parser`
implementation 'org.slf4j:slf4j-simple:2.0.11'
implementation 'org.slf4j:slf4j-simple:2.0.12'

// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
Expand All @@ -240,7 +236,7 @@ dependencies {
// huawei flavor only
huaweiImplementation "com.huawei.agconnect:agconnect-core:$huawei_agconnect_version"

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

kapt 'androidx.annotation:annotation:1.7.1'
ksp "com.github.bumptech.glide:ksp:$glide_version"
Expand Down
6 changes: 4 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<!-- 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 [Android 15 (API 35)] use `FOREGROUND_SERVICE_MEDIA_PROCESSING` -->
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
tools:ignore="SystemPermissionTypo" />
Expand Down Expand Up @@ -72,11 +73,11 @@

<!--
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)
- Google Maps is from API 20
-->
<uses-sdk tools:overrideLibrary="io.flutter.plugins.googlemaps, androidx.security:security-crypto, com.arthenica.ffmpegkit.flutter" />
<uses-sdk tools:overrideLibrary="androidx.security, com.arthenica.ffmpegkit.flutter, io.flutter.plugins.googlemaps" />

<!-- from Android 11, we should define <queries> to make other apps visible to this app -->
<queries>
Expand Down Expand Up @@ -258,6 +259,7 @@
</receiver>

<!-- anonymous service for analysis worker is specified here to provide service type -->
<!-- TODO TLAD [Android 15 (API 35)] use `mediaProcessing` -->
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
// 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
// TODO TLAD [Android 15 (API 35)] use `FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING`
val type = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
ForegroundInfo(NOTIFICATION_ID, notification, type)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package deckers.thibault.aves
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import io.flutter.embedding.engine.FlutterEngine
Expand Down Expand Up @@ -56,7 +57,7 @@ class HomeWidgetSettingsActivity : MainActivity() {
finish()
}

override fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
override fun extractIntentData(intent: Intent?): FieldMap {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_WIDGET_SETTINGS,
INTENT_DATA_KEY_WIDGET_ID to appWidgetId,
Expand Down
33 changes: 10 additions & 23 deletions android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,6 @@ open class MainActivity : FlutterFragmentActivity() {
}
}

override fun onResume() {
super.onResume()
mediaStoreChangeStreamHandler.onAppResume()
settingsChangeStreamHandler.onAppResume()
}

override fun onPause() {
mediaStoreChangeStreamHandler.onAppPause()
settingsChangeStreamHandler.onAppPause()
super.onPause()
}

override fun onStop() {
Log.i(LOG_TAG, "onStop")
super.onStop()
Expand Down Expand Up @@ -242,7 +230,7 @@ open class MainActivity : FlutterFragmentActivity() {

private fun onEditResult(resultCode: Int, intent: Intent?) {
val fields: FieldMap? = if (resultCode == RESULT_OK) hashMapOf(
"uri" to intent?.data.toString(),
"uri" to intent?.data?.toString(),
"mimeType" to intent?.type,
) else null
pendingEditIntentHandler?.let { it(fields) }
Expand Down Expand Up @@ -279,21 +267,19 @@ open class MainActivity : FlutterFragmentActivity() {
}
}

open fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
open fun extractIntentData(intent: Intent?): FieldMap {
when (val action = intent?.action) {
Intent.ACTION_MAIN -> {
if (intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false)) {
return hashMapOf(
INTENT_DATA_KEY_SAFE_MODE to true,
)
}
val fields = hashMapOf<String, Any?>(
INTENT_DATA_KEY_LAUNCHER to intent.hasCategory(Intent.CATEGORY_LAUNCHER),
INTENT_DATA_KEY_SAFE_MODE to intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false),
)
intent.getStringExtra(EXTRA_KEY_PAGE)?.let { page ->
val filters = extractFiltersFromIntent(intent)
return hashMapOf(
INTENT_DATA_KEY_PAGE to page,
INTENT_DATA_KEY_FILTERS to filters,
)
fields[INTENT_DATA_KEY_PAGE] = page
fields[INTENT_DATA_KEY_FILTERS] = filters
}
return fields
}

Intent.ACTION_VIEW,
Expand Down Expand Up @@ -496,6 +482,7 @@ open class MainActivity : FlutterFragmentActivity() {
const val INTENT_DATA_KEY_ACTION = "action"
const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple"
const val INTENT_DATA_KEY_FILTERS = "filters"
const val INTENT_DATA_KEY_LAUNCHER = "launcher"
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
const val INTENT_DATA_KEY_PAGE = "page"
const val INTENT_DATA_KEY_QUERY = "query"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package deckers.thibault.aves

import android.content.Intent
import deckers.thibault.aves.model.FieldMap

class ScreenSaverSettingsActivity : MainActivity() {
override fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
override fun extractIntentData(intent: Intent?): FieldMap {
return hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_SCREEN_SAVER_SETTINGS,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.FlutterUtils
import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering
import deckers.thibault.aves.utils.LogUtils
Expand All @@ -25,7 +26,7 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class WallpaperActivity : FlutterFragmentActivity() {
private lateinit var intentDataMap: MutableMap<String, Any?>
private lateinit var intentDataMap: FieldMap
private lateinit var mediaSessionHandler: MediaSessionHandler

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -103,7 +104,7 @@ class WallpaperActivity : FlutterFragmentActivity() {
}
}

private fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
private fun extractIntentData(intent: Intent?): FieldMap {
when (intent?.action) {
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler

val provider = getProvider(context, uri)
if (provider == null) {
result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
result.error("getEntry-provider", "failed to find provider for uri=$uri mimeType=$mimeType", null)
return
}

provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = result.success(fields)
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri", throwable.message)
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri mimeType=$mimeType", throwable.message)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.drew.metadata.webp.WebpDirectory
import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.ExifGeoTiffTags
import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.ExifInterfaceHelper.describeAll
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDouble
Expand Down Expand Up @@ -110,7 +111,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"getAllMetadata" -> ioScope.launch { safe(call, result, ::getAllMetadata) }
"getCatalogMetadata" -> ioScope.launch { safe(call, result, ::getCatalogMetadata) }
"getFields" -> ioScope.launch { safe(call, result, ::getFields) }
"getOverlayMetadata" -> ioScope.launch { safe(call, result, ::getOverlayMetadata) }
"getGeoTiffInfo" -> ioScope.launch { safe(call, result, ::getGeoTiffInfo) }
"getMultiPageInfo" -> ioScope.launch { safe(call, result, ::getMultiPageInfo) }
"getPanoramaInfo" -> ioScope.launch { safe(call, result, ::getPanoramaInfo) }
Expand All @@ -119,6 +120,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
"hasContentResolverProp" -> ioScope.launch { safe(call, result, ::hasContentProp) }
"getContentResolverProp" -> ioScope.launch { safe(call, result, ::getContentPropValue) }
"getDate" -> ioScope.launch { safe(call, result, ::getDate) }
"getFields" -> ioScope.launch { safe(call, result, ::getFields) }
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -815,7 +817,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}

private fun getFields(call: MethodCall, result: MethodChannel.Result) {
private fun getOverlayMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
Expand Down Expand Up @@ -1250,6 +1252,71 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
result.success(dateMillis)
}

private fun getFields(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
val fields = call.argument<List<String>>("fields")
if (mimeType == null || uri == null || fields == null) {
result.error("getFields-args", "missing arguments", null)
return
}

val metadataMap = HashMap<String, Any>()
if (fields.isEmpty() || isVideo(mimeType)) {
result.success(metadataMap)
return
}

var foundExif = false
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = Helper.safeRead(input)
for (dir in metadata.getDirectoriesOfType(ExifDirectoryBase::class.java)) {
foundExif = true
val allTags = ExifInterfaceHelper.allTags
fields.forEach { tag ->
allTags[tag]?.let { mapper ->
val tagType = mapper.type
dir.getDescription(tagType)?.let { value -> metadataMap[tag] = value }
}
}
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: AssertionError) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}

if (!foundExif && canReadWithExifInterface(mimeType)) {
// fallback to read EXIF via ExifInterface
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val exif = ExifInterface(input)
fields.forEach { tag ->
if (exif.hasAttribute(tag)) {
val value = exif.getAttribute(tag)
if (value != null) {
metadataMap[tag] = value
}
}
}
}
} catch (e: Exception) {
// ExifInterface initialization can fail with a RuntimeException
// caused by an internal MediaMetadataRetriever failure
Log.w(LOG_TAG, "failed to get metadata by ExifInterface for mimeType=$mimeType uri=$uri", e)
}
}

result.success(metadataMap)
}

companion object {
private val LOG_TAG = LogUtils.createTag<MetadataFetchHandler>()
const val CHANNEL = "deckers.thibault/aves/metadata_fetch"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
}

val trashDirs = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }
val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.mapNotNull { file -> file?.path } ?: listOf() }
val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.filterNotNull()?.mapNotNull { file -> file.path } ?: listOf() }
val untrackedPaths = trashItemPaths.filterNot(knownPaths::contains).toList()

result.success(untrackedPaths)
Expand Down
Loading

0 comments on commit 82f070f

Please sign in to comment.