diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 09725f98b..3cca27c39 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,6 +30,9 @@ + + + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index 773f2ef19..1c4d47dce 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -119,7 +119,9 @@ class MainActivity : FlutterActivity() { when (requestCode) { DOCUMENT_TREE_ACCESS_REQUEST -> onDocumentTreeAccessResult(data, resultCode, requestCode) DELETE_PERMISSION_REQUEST -> onDeletePermissionResult(resultCode) - CREATE_FILE_REQUEST, OPEN_FILE_REQUEST, SELECT_DIRECTORY_REQUEST -> onPermissionResult(requestCode, data?.data) + CREATE_FILE_REQUEST, + OPEN_FILE_REQUEST, + SELECT_DIRECTORY_REQUEST -> onStorageAccessResult(requestCode, data?.data) } } @@ -127,7 +129,7 @@ class MainActivity : FlutterActivity() { private fun onDocumentTreeAccessResult(data: Intent?, resultCode: Int, requestCode: Int) { val treeUri = data?.data if (resultCode != RESULT_OK || treeUri == null) { - onPermissionResult(requestCode, null) + onStorageAccessResult(requestCode, null) return } @@ -138,7 +140,7 @@ class MainActivity : FlutterActivity() { contentResolver.takePersistableUriPermission(treeUri, takeFlags) // resume pending action - onPermissionResult(requestCode, treeUri) + onStorageAccessResult(requestCode, treeUri) } private fun onDeletePermissionResult(resultCode: Int) { @@ -152,9 +154,17 @@ class MainActivity : FlutterActivity() { when (intent?.action) { Intent.ACTION_MAIN -> { intent.getStringExtra("page")?.let { page -> + var filters = intent.getStringArrayExtra("filters")?.toList() + if (filters == null) { + // fallback for shortcuts created on API < 26 + val filterString = intent.getStringExtra("filtersString") + if (filterString != null) { + filters = filterString.split(EXTRA_STRING_ARRAY_SEPARATOR) + } + } return hashMapOf( "page" to page, - "filters" to intent.getStringArrayExtra("filters")?.toList(), + "filters" to filters, ) } } @@ -209,9 +219,13 @@ class MainActivity : FlutterActivity() { private fun setupShortcuts() { // do not use 'route' as extra key, as the Flutter framework acts on it + // shortcut adaptive icons are placed in `mipmap`, not `drawable`, + // so that foreground is rendered at the intended scale + val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + val search = ShortcutInfoCompat.Builder(this, "search") .setShortLabel(getString(R.string.search_shortcut_short_label)) - .setIcon(IconCompat.createWithResource(this, R.mipmap.ic_shortcut_search)) + .setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_search else R.drawable.ic_shortcut_search)) .setIntent( Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java) .putExtra("page", "/search") @@ -220,7 +234,7 @@ class MainActivity : FlutterActivity() { val videos = ShortcutInfoCompat.Builder(this, "videos") .setShortLabel(getString(R.string.videos_shortcut_short_label)) - .setIcon(IconCompat.createWithResource(this, R.mipmap.ic_shortcut_movie)) + .setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_movie else R.drawable.ic_shortcut_movie)) .setIntent( Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java) .putExtra("page", "/collection") @@ -234,18 +248,19 @@ class MainActivity : FlutterActivity() { companion object { private val LOG_TAG = LogUtils.createTag() const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer" + const val EXTRA_STRING_ARRAY_SEPARATOR = "###" const val DOCUMENT_TREE_ACCESS_REQUEST = 1 const val DELETE_PERMISSION_REQUEST = 2 const val CREATE_FILE_REQUEST = 3 const val OPEN_FILE_REQUEST = 4 const val SELECT_DIRECTORY_REQUEST = 5 - // permission request code to pending runnable - val pendingResultHandlers = ConcurrentHashMap() + // request code to pending runnable + val pendingStorageAccessResultHandlers = ConcurrentHashMap() - fun onPermissionResult(requestCode: Int, uri: Uri?) { - Log.d(LOG_TAG, "onPermissionResult with requestCode=$requestCode, uri=$uri") - val handler = pendingResultHandlers.remove(requestCode) ?: return + private fun onStorageAccessResult(requestCode: Int, uri: Uri?) { + Log.d(LOG_TAG, "onStorageAccessResult with requestCode=$requestCode, uri=$uri") + val handler = pendingStorageAccessResultHandlers.remove(requestCode) ?: return if (uri != null) { handler.onGranted(uri) } else { @@ -261,4 +276,4 @@ class MainActivity : FlutterActivity() { // onGranted: user selected a directory/file (with no guarantee that it matches the requested `path`) // onDenied: user cancelled -data class PendingResultHandler(val path: String?, val onGranted: (uri: Uri) -> Unit, val onDenied: () -> Unit) +data class PendingStorageAccessResultHandler(val path: String?, val onGranted: (uri: Uri) -> Unit, val onDenied: () -> Unit) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt index df5d58108..24820960d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt @@ -46,8 +46,12 @@ class SearchSuggestionsProvider : MethodChannel.MethodCallHandler, ContentProvid val matrixCursor = MatrixCursor(columns) context?.let { context -> + // shortcut adaptive icons are placed in `mipmap`, not `drawable`, + // so that foreground is rendered at the intended scale + val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + val searchShortcutTitle = "${context.resources.getString(R.string.search_shortcut_short_label)} $query" - val searchShortcutIcon = context.resourceUri(R.mipmap.ic_shortcut_search) + val searchShortcutIcon = context.resourceUri(if (supportAdaptiveIcon) R.mipmap.ic_shortcut_search else R.drawable.ic_shortcut_search) matrixCursor.addRow(arrayOf(null, null, null, searchShortcutTitle, null, searchShortcutIcon)) runBlocking { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppShortcutHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppShortcutHandler.kt index 24b81b739..452d35213 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppShortcutHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppShortcutHandler.kt @@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls import android.content.Context import android.content.Intent import android.graphics.BitmapFactory +import android.os.Build import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat @@ -52,16 +53,24 @@ class AppShortcutHandler(private val context: Context) : MethodCallHandler { var bitmap = BitmapFactory.decodeByteArray(iconBytes, 0, iconBytes.size) bitmap = centerSquareCrop(context, bitmap, 256) if (bitmap != null) { - icon = IconCompat.createWithBitmap(bitmap) + // adaptive, so the bitmap is used as background and covers the whole icon + icon = IconCompat.createWithAdaptiveBitmap(bitmap) } } if (icon == null) { - icon = IconCompat.createWithResource(context, R.mipmap.ic_shortcut_collection) + // shortcut adaptive icons are placed in `mipmap`, not `drawable`, + // so that foreground is rendered at the intended scale + val supportAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + + icon = IconCompat.createWithResource(context, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_collection else R.drawable.ic_shortcut_collection) } val intent = Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java) .putExtra("page", "/collection") .putExtra("filters", filters.toTypedArray()) + // on API 25, `String[]` or `ArrayList` extras are null when using the shortcut + // so we use a joined `String` as fallback + .putExtra("filtersString", filters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR)) // multiple shortcuts sharing the same ID cannot be created with different labels or icons // so we provide a unique ID for each one, and let the user manage duplicates (i.e. same filter set), if any diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt index cd7e590a4..58f2729cf 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt @@ -7,7 +7,7 @@ import android.os.Handler import android.os.Looper import android.util.Log import deckers.thibault.aves.MainActivity -import deckers.thibault.aves.PendingResultHandler +import deckers.thibault.aves.PendingStorageAccessResultHandler import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.PermissionManager import deckers.thibault.aves.utils.StorageUtils @@ -82,7 +82,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? type = mimeType putExtra(Intent.EXTRA_TITLE, name) } - MainActivity.pendingResultHandlers[MainActivity.CREATE_FILE_REQUEST] = PendingResultHandler(null, { uri -> + MainActivity.pendingStorageAccessResultHandlers[MainActivity.CREATE_FILE_REQUEST] = PendingStorageAccessResultHandler(null, { uri -> GlobalScope.launch(Dispatchers.IO) { try { activity.contentResolver.openOutputStream(uri)?.use { output -> @@ -116,7 +116,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? addCategory(Intent.CATEGORY_OPENABLE) type = mimeType } - MainActivity.pendingResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingResultHandler(null, { uri -> + MainActivity.pendingStorageAccessResultHandlers[MainActivity.OPEN_FILE_REQUEST] = PendingStorageAccessResultHandler(null, { uri -> GlobalScope.launch(Dispatchers.IO) { activity.contentResolver.openInputStream(uri)?.use { input -> val buffer = ByteArray(BUFFER_SIZE) @@ -138,7 +138,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - MainActivity.pendingResultHandlers[MainActivity.SELECT_DIRECTORY_REQUEST] = PendingResultHandler(null, { uri -> + MainActivity.pendingStorageAccessResultHandlers[MainActivity.SELECT_DIRECTORY_REQUEST] = PendingStorageAccessResultHandler(null, { uri -> success(StorageUtils.convertTreeUriToDirPath(activity, uri)) endOfStream() }, { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt index cfb0bb307..96da1ae62 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt @@ -10,7 +10,7 @@ import android.os.storage.StorageManager import android.util.Log import androidx.annotation.RequiresApi import deckers.thibault.aves.MainActivity -import deckers.thibault.aves.PendingResultHandler +import deckers.thibault.aves.PendingStorageAccessResultHandler import deckers.thibault.aves.utils.StorageUtils.PathSegments import java.io.File import java.util.* @@ -35,7 +35,7 @@ object PermissionManager { } if (intent.resolveActivity(activity.packageManager) != null) { - MainActivity.pendingResultHandlers[MainActivity.DOCUMENT_TREE_ACCESS_REQUEST] = PendingResultHandler(path, onGranted, onDenied) + MainActivity.pendingStorageAccessResultHandlers[MainActivity.DOCUMENT_TREE_ACCESS_REQUEST] = PendingStorageAccessResultHandler(path, onGranted, onDenied) activity.startActivityForResult(intent, MainActivity.DOCUMENT_TREE_ACCESS_REQUEST) } else { Log.e(LOG_TAG, "failed to resolve activity for intent=$intent") diff --git a/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_collection.xml b/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_collection.xml new file mode 100644 index 000000000..f20e57d59 --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_collection.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_movie.xml b/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_movie.xml new file mode 100644 index 000000000..c69c3792a --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_movie.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_search.xml b/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_search.xml new file mode 100644 index 000000000..f28c5497f --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi-v21/ic_shortcut_search.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_collection_foreground.xml b/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_collection_foreground.xml index 45b1c8cb2..13e09005a 100644 --- a/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_collection_foreground.xml +++ b/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_collection_foreground.xml @@ -1,15 +1,16 @@ - - - + android:viewportHeight="108"> + + + diff --git a/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_movie_foreground.xml b/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_movie_foreground.xml index 0955cd1d5..84db2f541 100644 --- a/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_movie_foreground.xml +++ b/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_movie_foreground.xml @@ -1,15 +1,16 @@ - - - + android:viewportHeight="108"> + + + diff --git a/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_search_foreground.xml b/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_search_foreground.xml index fb2972b41..13f707135 100644 --- a/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_search_foreground.xml +++ b/android/app/src/main/res/drawable-anydpi-v26/ic_shortcut_search_foreground.xml @@ -1,15 +1,16 @@ - - - + android:viewportHeight="108"> + + + diff --git a/android/app/src/main/res/drawable-v21/ic_shortcut_collection.xml b/android/app/src/main/res/drawable-v21/ic_shortcut_collection.xml new file mode 100644 index 000000000..0f3548712 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/ic_shortcut_collection.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/android/app/src/main/res/drawable-v21/ic_shortcut_movie.xml b/android/app/src/main/res/drawable-v21/ic_shortcut_movie.xml new file mode 100644 index 000000000..9d24cc9e2 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/ic_shortcut_movie.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/android/app/src/main/res/drawable-v21/ic_shortcut_search.xml b/android/app/src/main/res/drawable-v21/ic_shortcut_search.xml new file mode 100644 index 000000000..5bd1ca0b2 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/ic_shortcut_search.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/android/app/src/main/res/drawable-v26/ic_shortcut_collection.xml b/android/app/src/main/res/drawable-v26/ic_shortcut_collection.xml new file mode 100644 index 000000000..8ec076f9f --- /dev/null +++ b/android/app/src/main/res/drawable-v26/ic_shortcut_collection.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v26/ic_shortcut_collection_foreground.xml b/android/app/src/main/res/drawable-v26/ic_shortcut_collection_foreground.xml new file mode 100644 index 000000000..13e09005a --- /dev/null +++ b/android/app/src/main/res/drawable-v26/ic_shortcut_collection_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android/app/src/main/res/drawable-v26/ic_shortcut_movie.xml b/android/app/src/main/res/drawable-v26/ic_shortcut_movie.xml new file mode 100644 index 000000000..8789ba05f --- /dev/null +++ b/android/app/src/main/res/drawable-v26/ic_shortcut_movie.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v26/ic_shortcut_movie_foreground.xml b/android/app/src/main/res/drawable-v26/ic_shortcut_movie_foreground.xml new file mode 100644 index 000000000..84db2f541 --- /dev/null +++ b/android/app/src/main/res/drawable-v26/ic_shortcut_movie_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android/app/src/main/res/drawable-v26/ic_shortcut_search.xml b/android/app/src/main/res/drawable-v26/ic_shortcut_search.xml new file mode 100644 index 000000000..ab1fb94ae --- /dev/null +++ b/android/app/src/main/res/drawable-v26/ic_shortcut_search.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v26/ic_shortcut_search_foreground.xml b/android/app/src/main/res/drawable-v26/ic_shortcut_search_foreground.xml new file mode 100644 index 000000000..13f707135 --- /dev/null +++ b/android/app/src/main/res/drawable-v26/ic_shortcut_search_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml index e069568b3..8ec076f9f 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection_foreground.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection_foreground.xml new file mode 100644 index 000000000..13e09005a --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_collection_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml index a31978c2d..8789ba05f 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie_foreground.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie_foreground.xml new file mode 100644 index 000000000..84db2f541 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_movie_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml index 1ab11ea64..ab1fb94ae 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search_foreground.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search_foreground.xml new file mode 100644 index 000000000..13f707135 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_shortcut_search_foreground.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 9a0f0bc25..6e3fbd50f 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -2,5 +2,6 @@ #FFFFFF #FFFFFF + #455A64 #3f51b5 \ No newline at end of file