diff --git a/.flutter b/.flutter index c07f78888..2ad6cd72c 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit c07f7888888435fd9df505aa2efc38d3cf65681b +Subproject commit 2ad6cd72c040113b47ee9055e722606a490ef0da diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f2f4ffd..706a7352f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [v1.8.3] - 2023-03-13 + +### Added + +- Collection: preview button when selecting items +- Collection: item size in list layout +- Vaults: custom pattern lock +- Video: picture-in-picture +- Video: handle skip next/previous media buttons +- TV: more media controls + +### Changed + +- scroll to show item when navigating from Info page +- upgraded Flutter to stable v3.7.7 + +### Fixed + +- Accessibility: using accessibility services keeping snack bar beyond countdown +- Accessibility: navigation with TalkBack +- Vaults: crash when using fingerprint on older Android versions +- Vaults: sharing multiple items + ## [v1.8.2] - 2023-02-28 ### Added diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ef519cae2..ee04303e8 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -67,6 +67,16 @@ This change eventually prevents building the app with Flutter v3.3.3. + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt index 202e73090..3c0b0d1f6 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt @@ -13,17 +13,20 @@ import deckers.thibault.aves.channel.calls.* 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.utils.FlutterUtils import deckers.thibault.aves.utils.FlutterUtils.enableSoftwareRendering import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.getParcelableExtraCompat import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class WallpaperActivity : FlutterFragmentActivity() { private lateinit var intentDataMap: MutableMap + private lateinit var mediaSessionHandler: MediaSessionHandler override fun onCreate(savedInstanceState: Bundle?) { if (FlutterUtils.isSoftwareRenderingRequired()) { @@ -42,12 +45,19 @@ class WallpaperActivity : FlutterFragmentActivity() { super.configureFlutterEngine(flutterEngine) val messenger = flutterEngine.dartExecutor + // notification: platform -> dart + val mediaCommandStreamHandler = MediaCommandStreamHandler().apply { + EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this) + } + // dart -> platform -> dart // - need Context + mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler) MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this)) MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) + MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler) MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) // - need ContextWrapper @@ -79,6 +89,11 @@ class WallpaperActivity : FlutterFragmentActivity() { } } + override fun onDestroy() { + mediaSessionHandler.dispose() + super.onDestroy() + } + private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "getIntentData" -> { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index c044f710f..18921bfe6 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -285,7 +285,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { return } - val uriList = ArrayList(urisByMimeType.values.flatten().mapNotNull { Uri.parse(it) }) + val uriList = ArrayList(urisByMimeType.values.flatten().mapNotNull { getShareableUri(context, Uri.parse(it)) }) val mimeTypes = urisByMimeType.keys.toTypedArray() // simplify share intent for a single item, as some apps can handle one item but not more @@ -296,7 +296,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { Intent(Intent.ACTION_SEND) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) .setType(mimeType) - .putExtra(Intent.EXTRA_STREAM, getShareableUri(context, uri)) + .putExtra(Intent.EXTRA_STREAM, uri) } else { var mimeType = "*/*" if (mimeTypes.size == 1) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt index 779213671..f78099e0c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaSessionHandler.kt @@ -65,11 +65,13 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand val stateString = call.argument("state") val positionMillis = call.argument("positionMillis")?.toLong() val playbackSpeed = call.argument("playbackSpeed")?.toFloat() + val canSkipToNext = call.argument("canSkipToNext") + val canSkipToPrevious = call.argument("canSkipToPrevious") - if (uri == null || title == null || durationMillis == null || stateString == null || positionMillis == null || playbackSpeed == null) { + if (uri == null || title == null || durationMillis == null || stateString == null || positionMillis == null || playbackSpeed == null || canSkipToNext == null || canSkipToPrevious == null) { result.error( "updateSession-args", "missing arguments: uri=$uri, title=$title, durationMillis=$durationMillis" + - ", stateString=$stateString, positionMillis=$positionMillis, playbackSpeed=$playbackSpeed", null + ", stateString=$stateString, positionMillis=$positionMillis, playbackSpeed=$playbackSpeed, canSkipToNext=$canSkipToNext, canSkipToPrevious=$canSkipToPrevious", null ) return } @@ -90,6 +92,12 @@ class MediaSessionHandler(private val context: Context, private val mediaCommand } else { actions or PlaybackStateCompat.ACTION_PLAY } + if (canSkipToNext) { + actions = actions or PlaybackStateCompat.ACTION_SKIP_TO_NEXT + } + if (canSkipToPrevious) { + actions = actions or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + } val playbackState = PlaybackStateCompat.Builder() .setState( diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt index b81815622..49273098f 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaCommandStreamHandler.kt @@ -46,6 +46,16 @@ class MediaCommandStreamHandler : EventChannel.StreamHandler, MediaSessionCompat success(hashMapOf(KEY_COMMAND to COMMAND_PAUSE)) } + override fun onSkipToNext() { + super.onSkipToNext() + success(hashMapOf(KEY_COMMAND to COMMAND_SKIP_TO_NEXT)) + } + + override fun onSkipToPrevious() { + super.onSkipToPrevious() + success(hashMapOf(KEY_COMMAND to COMMAND_SKIP_TO_PREVIOUS)) + } + override fun onStop() { super.onStop() success(hashMapOf(KEY_COMMAND to COMMAND_STOP)) @@ -70,6 +80,8 @@ class MediaCommandStreamHandler : EventChannel.StreamHandler, MediaSessionCompat const val COMMAND_PLAY = "play" const val COMMAND_PAUSE = "pause" + const val COMMAND_SKIP_TO_NEXT = "skip_to_next" + const val COMMAND_SKIP_TO_PREVIOUS = "skip_to_previous" const val COMMAND_STOP = "stop" const val COMMAND_SEEK = "seek" } 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 2a9171de3..c73694c99 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 @@ -44,8 +44,14 @@ object StorageUtils { const val TRASH_PATH_PLACEHOLDER = "#trash" + // whether the provided path is on one of this app specific directories: + // - /storage/{volume}/Android/data/{package_name}/files + // - /data/user/0/{package_name}/files private fun isAppFile(context: Context, path: String): Boolean { - val dirs = context.getExternalFilesDirs(null).filterNotNull() + val dirs = listOf( + *context.getExternalFilesDirs(null).filterNotNull().toTypedArray(), + context.filesDir, + ) return dirs.any { path.startsWith(it.path) } } diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 25046f02e..edd8ddab8 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -1,6 +1,6 @@ -