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 @@
-