Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
deckerst committed Mar 11, 2024
2 parents 579e7a2 + 895c495 commit a02131c
Show file tree
Hide file tree
Showing 89 changed files with 747 additions and 391 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
name: Build and release artifacts.
runs-on: ubuntu-latest
steps:
- uses: actions/setup-java@v3
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
Expand Down Expand Up @@ -75,7 +75,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Upload app bundle
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: appbundle
path: outputs/app-play-release.aab
Expand All @@ -85,15 +85,15 @@ jobs:
needs: [ build ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Get appbundle from artifacts.
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: appbundle

- name: Release app to beta channel.
uses: r0adkll/[email protected].1
uses: r0adkll/[email protected].3
with:
serviceAccountJsonPlainText: ${{ secrets.PLAYSTORE_ACCOUNT_KEY }}
packageName: deckers.thibault.aves
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.

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

## <a id="v1.10.6"></a>[v1.10.6] - 2024-03-11

### Added

- Cataloguing: detect/filter HDR videos

### Changed

- check Media Store changes when resuming app
- disabling animations also applies to pop up menus
- upgraded Flutter to stable v3.19.3

### Fixed

- engine leak from analysis worker

## <a id="v1.10.5"></a>[v1.10.5] - 2024-02-22

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,22 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
workCont = cont
onStart()
}
dispose()
return Result.success()
}

private suspend fun dispose() {
Log.i(LOG_TAG, "Clean analysis worker $id")
flutterEngine?.let {
FlutterUtils.runOnUiThread {
it.destroy()
}
flutterEngine = null
}
}

private fun onStart() {
Log.i(LOG_TAG, "Start analysis worker")
Log.i(LOG_TAG, "Start analysis worker $id")
runBlocking {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) {
flutterEngine = it
Expand Down
12 changes: 12 additions & 0 deletions android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.drew.metadata.file.FileTypeDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.*
import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper
import deckers.thibault.aves.metadata.Metadata
import deckers.thibault.aves.metadata.Mp4ParserHelper
import deckers.thibault.aves.metadata.Mp4ParserHelper.dumpBoxes
import deckers.thibault.aves.metadata.PixyMetaHelper
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.LogUtils
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import androidx.core.content.pm.ShortcutManagerCompat
import com.google.android.material.color.DynamicColors
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MemoryUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
Expand All @@ -35,6 +35,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
"requestGarbageCollection" -> safe(call, result, ::requestGarbageCollection)
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -123,6 +125,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
result.success(true)
}

private fun getAvailableHeapSize(@Suppress("unused_parameter") methodCall: MethodCall, result: MethodChannel.Result) {
result.success(MemoryUtils.getAvailableHeapSize())
}

private fun requestGarbageCollection(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Runtime.getRuntime().gc()
result.success(true)
}

companion object {
const val CHANNEL = "deckers.thibault/aves/device"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package deckers.thibault.aves.channel.calls
import android.content.Context
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.provider.MediaStoreImageProvider
import io.flutter.plugin.common.MethodCall
Expand All @@ -20,13 +22,15 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
when (call.method) {
"checkObsoleteContentIds" -> ioScope.launch { safe(call, result, ::checkObsoleteContentIds) }
"checkObsoletePaths" -> ioScope.launch { safe(call, result, ::checkObsoletePaths) }
"getChangedUris" -> ioScope.launch { safe(call, result, ::getChangedUris) }
"getGeneration" -> ioScope.launch { safe(call, result, ::getGeneration) }
"scanFile" -> ioScope.launch { safe(call, result, ::scanFile) }
else -> result.notImplemented()
}
}

private fun checkObsoleteContentIds(call: MethodCall, result: MethodChannel.Result) {
val knownContentIds = call.argument<List<Int?>>("knownContentIds")
val knownContentIds = call.argument<List<Number?>>("knownContentIds")?.map { it?.toLong() }
if (knownContentIds == null) {
result.error("checkObsoleteContentIds-args", "missing arguments", null)
return
Expand All @@ -35,14 +39,33 @@ class MediaStoreHandler(private val context: Context) : MethodCallHandler {
}

private fun checkObsoletePaths(call: MethodCall, result: MethodChannel.Result) {
val knownPathById = call.argument<Map<Int?, String?>>("knownPathById")
val knownPathById = call.argument<Map<Number?, String?>>("knownPathById")?.mapKeys { it.key?.toLong() }
if (knownPathById == null) {
result.error("checkObsoletePaths-args", "missing arguments", null)
return
}
result.success(MediaStoreImageProvider().checkObsoletePaths(context, knownPathById))
}

private fun getChangedUris(call: MethodCall, result: MethodChannel.Result) {
val sinceGeneration = call.argument<Int>("sinceGeneration")
if (sinceGeneration == null) {
result.error("getChangedUris-args", "missing arguments", null)
return
}
val uris = MediaStoreImageProvider().getChangedUris(context, sinceGeneration)
result.success(uris)
}

private fun getGeneration(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val generation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
MediaStore.getGeneration(context, MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
null
}
result.success(generation)
}

private fun scanFile(call: MethodCall, result: MethodChannel.Result) {
val path = call.argument<String>("path")
val mimeType = call.argument<String>("mimeType")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls

import android.content.Context
import android.media.MediaFormat
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
Expand Down Expand Up @@ -559,7 +560,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {

// identification of embedded gain map
if (xmpMeta.hasHdrGainMap()) {
flags = flags or MASK_HAS_HDR_GAIN_MAP
flags = flags or MASK_IS_HDR
}
} catch (e: XMPException) {
Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e)
Expand Down Expand Up @@ -797,6 +798,14 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
retriever.getSafeInt(MediaMetadataRetriever.METADATA_KEY_COLOR_TRANSFER) {
if (it == MediaFormat.COLOR_TRANSFER_ST2084 || it == MediaFormat.COLOR_TRANSFER_HLG) {
flags = flags or MASK_IS_HDR
}
}
}

metadataMap[KEY_FLAGS] = flags
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get catalog metadata by MediaMetadataRetriever for uri=$uri", e)
Expand Down Expand Up @@ -1300,7 +1309,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private const val MASK_IS_360 = 1 shl 3
private const val MASK_IS_MULTIPAGE = 1 shl 4
private const val MASK_IS_MOTION_PHOTO = 1 shl 5
private const val MASK_HAS_HDR_GAIN_MAP = 1 shl 6
private const val MASK_IS_HDR = 1 shl 6 // for images: embedded HDR gainmap, for videos: HDR color transfer
private const val XMP_SUBJECTS_SEPARATOR = ";"

// overlay metadata
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()?.map { file -> file.path } ?: listOf() }
val trashItemPaths = trashDirs.flatMap { dir -> dir.listFiles()?.mapNotNull { file -> file?.path } ?: listOf() }
val untrackedPaths = trashItemPaths.filterNot(knownPaths::contains).toList()

result.success(untrackedPaths)
Expand All @@ -150,7 +150,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
}

val vaultDir = File(StorageUtils.getVaultRoot(context), vault)
val vaultItemPaths = vaultDir.listFiles()?.map { file -> file.path } ?: listOf()
val vaultItemPaths = vaultDir.listFiles()?.mapNotNull { file -> file?.path } ?: listOf()
val untrackedPaths = vaultItemPaths.filterNot(knownPaths::contains).toList()

result.success(untrackedPaths)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,26 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
}

init {
onAppResume()
}

fun dispose() {
onAppPause()
}

fun onAppResume() {
Log.i(LOG_TAG, "start listening to Media Store")
context.contentResolver.apply {
registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, contentObserver)
}
}

fun onAppPause() {
Log.i(LOG_TAG, "stop listening to Media Store")
context.contentResolver.unregisterContentObserver(contentObserver)
}

override fun onListen(arguments: Any?, eventSink: EventSink) {
this.eventSink = eventSink
handler = Handler(Looper.getMainLooper())
Expand All @@ -45,10 +59,6 @@ class MediaStoreChangeStreamHandler(private val context: Context) : EventChannel
Log.i(LOG_TAG, "onCancel arguments=$arguments")
}

fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
}

private fun success(uri: String?) {
handler?.post {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
private lateinit var eventSink: EventSink
private lateinit var handler: Handler

private var knownEntries: Map<Int?, Int?>? = null
private var knownEntries: Map<Long?, Int?>? = null
private var directory: String? = null

init {
if (arguments is Map<*, *>) {
@Suppress("unchecked_cast")
knownEntries = arguments["knownEntries"] as Map<Int?, Int?>?
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
directory = arguments["directory"] as String?
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,21 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
}

init {
context.contentResolver.apply {
registerContentObserver(Settings.System.CONTENT_URI, true, contentObserver)
}
onAppResume()
}

fun dispose() {
onAppPause()
}

fun onAppResume() {
Log.i(LOG_TAG, "start listening to system settings")
context.contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, contentObserver)
}

fun onAppPause() {
Log.i(LOG_TAG, "stop listening to system settings")
context.contentResolver.unregisterContentObserver(contentObserver)
}

override fun onListen(arguments: Any?, eventSink: EventSink) {
Expand All @@ -76,10 +88,6 @@ class SettingsChangeStreamHandler(private val context: Context) : EventChannel.S
Log.i(LOG_TAG, "onCancel arguments=$arguments")
}

fun dispose() {
context.contentResolver.unregisterContentObserver(contentObserver)
}

private fun success(settings: FieldMap) {
handler?.post {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ object Helper {

// This seems to cover all known Exif and Xmp date strings
// Note that " : : : : " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html
private val datePatterns = arrayOf(
private val dateFormats = arrayOf(
"yyyy:MM:dd HH:mm:ss",
"yyyy:MM:dd HH:mm",
"yyyy-MM-dd HH:mm:ss",
Expand All @@ -179,7 +179,7 @@ object Helper {
"yyyy-MM",
"yyyyMMdd", // as used in IPTC data
"yyyy"
)
).map { SimpleDateFormat(it, Locale.ROOT) }.toTypedArray()
private val subsecondPattern = Pattern.compile("(\\d\\d:\\d\\d:\\d\\d)(\\.\\d+)")
private val timeZonePattern = Pattern.compile("(Z|[+-]\\d\\d:\\d\\d|[+-]\\d\\d\\d\\d)$")
private val calendar: Calendar = GregorianCalendar()
Expand Down Expand Up @@ -210,11 +210,10 @@ object Helper {
effectiveTimeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replace("Z".toRegex(), ""))
dateString = timeZoneMatcher.replaceAll("")
}
for (datePattern in datePatterns) {
for (dateFormat in dateFormats) {
try {
val parsed = SimpleDateFormat(datePattern, Locale.ROOT).apply {
this.timeZone = effectiveTimeZone ?: TimeZone.getTimeZone("GMT") // don't interpret zone time
}.parse(dateString)
dateFormat.timeZone = effectiveTimeZone ?: TimeZone.getTimeZone("GMT") // don't interpret zone time
val parsed = dateFormat.parse(dateString)
if (parsed != null) {
calendar.time = parsed
if (calendar.get(Calendar.YEAR) < PARSED_DATE_YEAR_MAX) {
Expand Down
Loading

0 comments on commit a02131c

Please sign in to comment.