From c418a9c1446c0589d1b02af62d3b156f26b9b477 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 26 Jun 2022 16:49:59 +0900 Subject: [PATCH] #174 screen saver filter pick --- CHANGELOG.md | 1 + .../deckers/thibault/aves/MainActivity.kt | 95 +++++++++++++------ .../thibault/aves/ScreenSaverService.kt | 2 +- .../thibault/aves/WallpaperActivity.kt | 13 +-- .../aves/channel/calls/AppAdapterHandler.kt | 12 +-- ...dler.kt => ActivityResultStreamHandler.kt} | 21 +++- .../channel/streams/IntentStreamHandler.kt | 2 +- lib/app_mode.dart | 21 +++- lib/model/settings/defaults.dart | 2 +- lib/model/settings/settings.dart | 14 ++- lib/services/accessibility_service.dart | 10 +- lib/services/analysis_service.dart | 6 +- lib/services/android_app_service.dart | 28 +++--- lib/services/android_debug_service.dart | 32 +++---- lib/services/device_service.dart | 12 +-- lib/services/geocoding_service.dart | 4 +- lib/services/global_search.dart | 4 +- lib/services/intent_service.dart | 68 +++++++++++++ lib/services/media/embedded_data_service.dart | 10 +- lib/services/media/media_file_service.dart | 32 +++---- lib/services/media/media_store_service.dart | 12 +-- .../metadata/metadata_edit_service.dart | 14 +-- .../metadata/metadata_fetch_service.dart | 24 ++--- lib/services/storage_service.dart | 30 +++--- lib/services/viewer_service.dart | 27 ------ lib/services/wallpaper_service.dart | 4 +- lib/services/window_service.dart | 14 +-- lib/widgets/aves_app.dart | 3 +- lib/widgets/collection/app_bar.dart | 4 +- lib/widgets/collection/collection_grid.dart | 6 +- lib/widgets/collection/collection_page.dart | 82 +++++++++++----- .../collection/entry_set_action_delegate.dart | 2 +- lib/widgets/collection/filter_bar.dart | 2 +- lib/widgets/collection/grid/tile.dart | 5 +- .../common/identity/aves_filter_chip.dart | 2 +- .../common/action_delegates/chip_set.dart | 2 +- lib/widgets/filter_grids/common/app_bar.dart | 4 +- .../filter_grids/common/filter_grid_page.dart | 12 ++- .../filter_grids/common/filter_tile.dart | 1 + lib/widgets/home_page.dart | 37 ++++---- lib/widgets/navigation/nav_bar/nav_bar.dart | 8 +- .../settings/navigation/navigation.dart | 4 +- .../settings/screen_saver_settings_page.dart | 73 ++++++++++++-- lib/widgets/viewer/screen_saver_page.dart | 2 +- test_driver/driver_screenshots.dart | 2 +- test_driver/driver_shaders.dart | 2 +- 46 files changed, 490 insertions(+), 277 deletions(-) rename android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/{StorageAccessStreamHandler.kt => ActivityResultStreamHandler.kt} (87%) create mode 100644 lib/services/intent_service.dart delete mode 100644 lib/services/viewer_service.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 541d82722..a0824a583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. - Search: `on this day` filter - Stats: histogram and date filters +- Screen saver ### Changed 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 14ec0702f..b6d3fb7db 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -84,7 +84,7 @@ open class MainActivity : FlutterActivity() { StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(this, args) } // - need Activity StreamsChannel(messenger, ImageOpStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageOpStreamHandler(this, args) } - StreamsChannel(messenger, StorageAccessStreamHandler.CHANNEL).setStreamHandlerFactory { args -> StorageAccessStreamHandler(this, args) } + StreamsChannel(messenger, ActivityResultStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ActivityResultStreamHandler(this, args) } // change monitoring: platform -> dart mediaStoreChangeStreamHandler = MediaStoreChangeStreamHandler(this).apply { @@ -99,15 +99,16 @@ open class MainActivity : FlutterActivity() { intentStreamHandler = IntentStreamHandler().apply { EventChannel(messenger, IntentStreamHandler.CHANNEL).setStreamHandler(this) } - // detail fetch: dart -> platform + // intent detail & result: dart -> platform intentDataMap = extractIntentData(intent) - MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler { call, result -> + MethodChannel(messenger, INTENT_CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "getIntentData" -> { result.success(intentDataMap) intentDataMap.clear() } - "pick" -> pick(call) + "submitPickedItems" -> submitPickedItems(call) + "submitPickedCollectionFilters" -> submitPickedCollectionFilters(call) } } @@ -162,27 +163,33 @@ open class MainActivity : FlutterActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { - DOCUMENT_TREE_ACCESS_REQUEST -> onDocumentTreeAccessResult(data, resultCode, requestCode) + DOCUMENT_TREE_ACCESS_REQUEST -> onDocumentTreeAccessResult(requestCode, resultCode, data) DELETE_SINGLE_PERMISSION_REQUEST, MEDIA_WRITE_BULK_PERMISSION_REQUEST -> onScopedStoragePermissionResult(resultCode) CREATE_FILE_REQUEST, OPEN_FILE_REQUEST -> onStorageAccessResult(requestCode, data?.data) + PICK_COLLECTION_FILTERS_REQUEST -> onCollectionFiltersPickResult(resultCode, data) } } - @SuppressLint("WrongConstant", "ObsoleteSdkInt") - private fun onDocumentTreeAccessResult(data: Intent?, resultCode: Int, requestCode: Int) { - val treeUri = data?.data + private fun onCollectionFiltersPickResult(resultCode: Int, intent: Intent?) { + val filters = if (resultCode == RESULT_OK) extractFiltersFromIntent(intent) else null + pendingCollectionFilterPickHandler?.let { it(filters) } + } + + private fun onDocumentTreeAccessResult(requestCode: Int, resultCode: Int, intent: Intent?) { + val treeUri = intent?.data if (resultCode != RESULT_OK || treeUri == null) { onStorageAccessResult(requestCode, null) return } + @SuppressLint("WrongConstant", "ObsoleteSdkInt") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - val canPersist = (data.flags and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0 + val canPersist = (intent.flags and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0 if (canPersist) { // save access permissions across reboots - val takeFlags = (data.flags + val takeFlags = (intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) try { @@ -206,15 +213,8 @@ open class MainActivity : FlutterActivity() { open fun extractIntentData(intent: Intent?): MutableMap { when (intent?.action) { Intent.ACTION_MAIN -> { - intent.getStringExtra(SHORTCUT_KEY_PAGE)?.let { page -> - var filters = intent.getStringArrayExtra(SHORTCUT_KEY_FILTERS_ARRAY)?.toList() - if (filters == null) { - // fallback for shortcuts created on API < 26 - val filterString = intent.getStringExtra(SHORTCUT_KEY_FILTERS_STRING) - if (filterString != null) { - filters = filterString.split(EXTRA_STRING_ARRAY_SEPARATOR) - } - } + 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, @@ -234,7 +234,7 @@ open class MainActivity : FlutterActivity() { } Intent.ACTION_GET_CONTENT, Intent.ACTION_PICK -> { return hashMapOf( - INTENT_DATA_KEY_ACTION to INTENT_ACTION_PICK, + INTENT_DATA_KEY_ACTION to INTENT_ACTION_PICK_ITEMS, INTENT_DATA_KEY_MIME_TYPE to intent.type, INTENT_DATA_KEY_ALLOW_MULTIPLE to (intent.extras?.getBoolean(Intent.EXTRA_ALLOW_MULTIPLE) ?: false), ) @@ -250,6 +250,13 @@ open class MainActivity : FlutterActivity() { INTENT_DATA_KEY_QUERY to intent.getStringExtra(SearchManager.QUERY), ) } + INTENT_ACTION_PICK_COLLECTION_FILTERS -> { + val initialFilters = extractFiltersFromIntent(intent) + return hashMapOf( + INTENT_DATA_KEY_ACTION to INTENT_ACTION_PICK_COLLECTION_FILTERS, + INTENT_DATA_KEY_FILTERS to initialFilters + ) + } Intent.ACTION_RUN -> { // flutter run } @@ -260,7 +267,22 @@ open class MainActivity : FlutterActivity() { return HashMap() } - private fun pick(call: MethodCall) { + private fun extractFiltersFromIntent(intent: Intent?): List? { + intent ?: return null + + val filters = intent.getStringArrayExtra(EXTRA_KEY_FILTERS_ARRAY)?.toList() + if (filters != null) return filters + + // fallback for shortcuts created on API < 26 + val filterString = intent.getStringExtra(EXTRA_KEY_FILTERS_STRING) + if (filterString != null) { + return filterString.split(EXTRA_STRING_ARRAY_SEPARATOR) + } + + return null + } + + private fun submitPickedItems(call: MethodCall) { val pickedUris = call.argument>("uris") if (pickedUris != null && pickedUris.isNotEmpty()) { val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(context, Uri.parse(uriString)) } @@ -284,6 +306,19 @@ open class MainActivity : FlutterActivity() { finish() } + private fun submitPickedCollectionFilters(call: MethodCall) { + val filters = call.argument>("filters") + if (filters != null) { + val intent = Intent() + .putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray()) + .putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR)) + setResult(RESULT_OK, intent) + } else { + setResult(RESULT_CANCELED) + } + finish() + } + @RequiresApi(Build.VERSION_CODES.N_MR1) private fun setupShortcuts() { // do not use 'route' as extra key, as the Flutter framework acts on it @@ -297,7 +332,7 @@ open class MainActivity : FlutterActivity() { .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(SHORTCUT_KEY_PAGE, "/search") + .putExtra(EXTRA_KEY_PAGE, "/search") ) .build() @@ -306,7 +341,7 @@ open class MainActivity : FlutterActivity() { .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(SHORTCUT_KEY_PAGE, "/collection") + .putExtra(EXTRA_KEY_PAGE, "/collection") .putExtra("filters", arrayOf("{\"type\":\"mime\",\"mime\":\"video/*\"}")) ) .build() @@ -320,7 +355,7 @@ open class MainActivity : FlutterActivity() { companion object { private val LOG_TAG = LogUtils.createTag() - const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer" + const val INTENT_CHANNEL = "deckers.thibault/aves/intent" const val EXTRA_STRING_ARRAY_SEPARATOR = "###" const val DOCUMENT_TREE_ACCESS_REQUEST = 1 const val OPEN_FROM_ANALYSIS_SERVICE = 2 @@ -328,6 +363,7 @@ open class MainActivity : FlutterActivity() { const val OPEN_FILE_REQUEST = 4 const val DELETE_SINGLE_PERMISSION_REQUEST = 5 const val MEDIA_WRITE_BULK_PERMISSION_REQUEST = 6 + const val PICK_COLLECTION_FILTERS_REQUEST = 7 const val INTENT_DATA_KEY_ACTION = "action" const val INTENT_DATA_KEY_FILTERS = "filters" @@ -337,22 +373,25 @@ open class MainActivity : FlutterActivity() { const val INTENT_DATA_KEY_URI = "uri" const val INTENT_DATA_KEY_QUERY = "query" - const val INTENT_ACTION_PICK = "pick" + const val INTENT_ACTION_PICK_ITEMS = "pick_items" + const val INTENT_ACTION_PICK_COLLECTION_FILTERS = "pick_collection_filters" const val INTENT_ACTION_SCREEN_SAVER = "screen_saver" const val INTENT_ACTION_SCREEN_SAVER_SETTINGS = "screen_saver_settings" const val INTENT_ACTION_SEARCH = "search" const val INTENT_ACTION_SET_WALLPAPER = "set_wallpaper" const val INTENT_ACTION_VIEW = "view" - const val SHORTCUT_KEY_PAGE = "page" - const val SHORTCUT_KEY_FILTERS_ARRAY = "filters" - const val SHORTCUT_KEY_FILTERS_STRING = "filtersString" + const val EXTRA_KEY_PAGE = "page" + const val EXTRA_KEY_FILTERS_ARRAY = "filters" + const val EXTRA_KEY_FILTERS_STRING = "filtersString" // request code to pending runnable val pendingStorageAccessResultHandlers = ConcurrentHashMap() var pendingScopedStoragePermissionCompleter: CompletableFuture? = null + var pendingCollectionFilterPickHandler: ((filters: List?) -> Unit)? = null + private fun onStorageAccessResult(requestCode: Int, uri: Uri?) { Log.i(LOG_TAG, "onStorageAccessResult with requestCode=$requestCode, uri=$uri") val handler = pendingStorageAccessResultHandlers.remove(requestCode) ?: return diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt index 194be045d..676535e78 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt @@ -116,7 +116,7 @@ class ScreenSaverService : DreamService() { // intent handling // detail fetch: dart -> platform - MethodChannel(messenger, WallpaperActivity.VIEWER_CHANNEL).setMethodCallHandler { call, result -> + MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "getIntentData" -> { result.success(intentDataMap) 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 20f89511c..f0a4580cc 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt @@ -49,7 +49,7 @@ class WallpaperActivity : FlutterActivity() { // intent handling // detail fetch: dart -> platform intentDataMap = extractIntentData(intent) - MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler { call, result -> + MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "getIntentData" -> { result.success(intentDataMap) @@ -73,16 +73,6 @@ class WallpaperActivity : FlutterActivity() { } } - override fun onStop() { - Log.i(LOG_TAG, "onStop") - super.onStop() - } - - override fun onDestroy() { - Log.i(LOG_TAG, "onDestroy") - super.onDestroy() - } - private fun extractIntentData(intent: Intent?): MutableMap { when (intent?.action) { Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> { @@ -108,6 +98,5 @@ class WallpaperActivity : FlutterActivity() { companion object { private val LOG_TAG = LogUtils.createTag() - const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer" } } 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 c5b2d2223..f4851b13f 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 @@ -20,9 +20,9 @@ import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.request.RequestOptions import deckers.thibault.aves.MainActivity import deckers.thibault.aves.MainActivity.Companion.EXTRA_STRING_ARRAY_SEPARATOR -import deckers.thibault.aves.MainActivity.Companion.SHORTCUT_KEY_FILTERS_ARRAY -import deckers.thibault.aves.MainActivity.Companion.SHORTCUT_KEY_FILTERS_STRING -import deckers.thibault.aves.MainActivity.Companion.SHORTCUT_KEY_PAGE +import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_ARRAY +import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_STRING +import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_PAGE import deckers.thibault.aves.R import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend @@ -407,11 +407,11 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { val intent = when { uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java) filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java) - .putExtra(SHORTCUT_KEY_PAGE, "/collection") - .putExtra(SHORTCUT_KEY_FILTERS_ARRAY, filters.toTypedArray()) + .putExtra(EXTRA_KEY_PAGE, "/collection") + .putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray()) // on API 25, `String[]` or `ArrayList` extras are null when using the shortcut // so we use a joined `String` as fallback - .putExtra(SHORTCUT_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR)) + .putExtra(EXTRA_KEY_FILTERS_STRING, filters.joinToString(EXTRA_STRING_ARRAY_SEPARATOR)) else -> { result.error("pin-intent", "failed to build intent", null) return 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/ActivityResultStreamHandler.kt similarity index 87% rename from android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/StorageAccessStreamHandler.kt rename to android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ActivityResultStreamHandler.kt index 33397a1b2..f0466a8e4 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/ActivityResultStreamHandler.kt @@ -22,9 +22,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch -// starting activity to give access with the native dialog +// starting activity to get a result (e.g. storage access via native dialog) // breaks the regular `MethodChannel` so we use a stream channel instead -class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?) : EventChannel.StreamHandler { +class ActivityResultStreamHandler(private val activity: Activity, arguments: Any?) : EventChannel.StreamHandler { private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var eventSink: EventSink private lateinit var handler: Handler @@ -48,6 +48,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? "requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() } "createFile" -> ioScope.launch { createFile() } "openFile" -> ioScope.launch { openFile() } + "pickCollectionFilters" -> pickCollectionFilters() else -> endOfStream() } } @@ -186,6 +187,18 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? } } + private fun pickCollectionFilters() { + val initialFilters = (args["initialFilters"] as List<*>?)?.mapNotNull { if (it is String) it else null } ?: listOf() + val intent = Intent(MainActivity.INTENT_ACTION_PICK_COLLECTION_FILTERS, null, activity, MainActivity::class.java) + .putExtra(MainActivity.EXTRA_KEY_FILTERS_ARRAY, initialFilters.toTypedArray()) + .putExtra(MainActivity.EXTRA_KEY_FILTERS_STRING, initialFilters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR)) + MainActivity.pendingCollectionFilterPickHandler = { filters -> + success(filters) + endOfStream() + } + activity.startActivityForResult(intent, MainActivity.PICK_COLLECTION_FILTERS_REQUEST) + } + override fun onCancel(arguments: Any?) {} private fun success(result: Any?) { @@ -221,8 +234,8 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any? } companion object { - private val LOG_TAG = LogUtils.createTag() - const val CHANNEL = "deckers.thibault/aves/storage_access_stream" + private val LOG_TAG = LogUtils.createTag() + const val CHANNEL = "deckers.thibault/aves/activity_result_stream" private const val BUFFER_SIZE = 2 shl 17 // 256kB } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt index c5861f208..e1734338c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/IntentStreamHandler.kt @@ -20,6 +20,6 @@ class IntentStreamHandler : EventChannel.StreamHandler { } companion object { - const val CHANNEL = "deckers.thibault/aves/intent" + const val CHANNEL = "deckers.thibault/aves/new_intent_stream" } } \ No newline at end of file diff --git a/lib/app_mode.dart b/lib/app_mode.dart index 2f823dbdd..de3cd6455 100644 --- a/lib/app_mode.dart +++ b/lib/app_mode.dart @@ -1,5 +1,6 @@ enum AppMode { main, + pickCollectionFiltersExternal, pickSingleMediaExternal, pickMultipleMediaExternal, pickMediaInternal, @@ -11,13 +12,23 @@ enum AppMode { } extension ExtraAppMode on AppMode { - bool get canSearch => this == AppMode.main || this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal; + bool get canNavigate => { + AppMode.main, + AppMode.pickCollectionFiltersExternal, + AppMode.pickSingleMediaExternal, + AppMode.pickMultipleMediaExternal, + }.contains(this); - bool get canSelectMedia => this == AppMode.main || this == AppMode.pickMultipleMediaExternal; + bool get canSelectMedia => { + AppMode.main, + AppMode.pickMultipleMediaExternal, + }.contains(this); bool get canSelectFilter => this == AppMode.main; - bool get hasDrawer => this == AppMode.main || this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal; - - bool get isPickingMedia => this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal || this == AppMode.pickMediaInternal; + bool get isPickingMedia => { + AppMode.pickSingleMediaExternal, + AppMode.pickMultipleMediaExternal, + AppMode.pickMediaInternal, + }.contains(this); } diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart index 473e1fbb7..c2cf58ffa 100644 --- a/lib/model/settings/defaults.dart +++ b/lib/model/settings/defaults.dart @@ -31,7 +31,7 @@ class SettingsDefaults { static const mustBackTwiceToExit = true; static const keepScreenOn = KeepScreenOn.viewerOnly; static const homePage = HomePageSetting.collection; - static const showBottomNavigationBar = true; + static const enableBottomNavigationBar = true; static const confirmDeleteForever = true; static const confirmMoveToBin = true; static const confirmMoveUndatedItems = true; diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 96c63b72e..6eb69b560 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -60,7 +60,7 @@ class Settings extends ChangeNotifier { static const mustBackTwiceToExitKey = 'must_back_twice_to_exit'; static const keepScreenOnKey = 'keep_screen_on'; static const homePageKey = 'home_page'; - static const showBottomNavigationBarKey = 'show_bottom_navigation_bar'; + static const enableBottomNavigationBarKey = 'show_bottom_navigation_bar'; static const confirmDeleteForeverKey = 'confirm_delete_forever'; static const confirmMoveToBinKey = 'confirm_move_to_bin'; static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items'; @@ -142,6 +142,7 @@ class Settings extends ChangeNotifier { static const screenSaverTransitionKey = 'screen_saver_transition'; static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback'; static const screenSaverIntervalKey = 'screen_saver_interval'; + static const screenSaverCollectionFiltersKey = 'screen_saver_collection_filters'; // slideshow static const slideshowRepeatKey = 'slideshow_loop'; @@ -320,9 +321,9 @@ class Settings extends ChangeNotifier { set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString()); - bool get showBottomNavigationBar => getBoolOrDefault(showBottomNavigationBarKey, SettingsDefaults.showBottomNavigationBar); + bool get enableBottomNavigationBar => getBoolOrDefault(enableBottomNavigationBarKey, SettingsDefaults.enableBottomNavigationBar); - set showBottomNavigationBar(bool newValue) => setAndNotify(showBottomNavigationBarKey, newValue); + set enableBottomNavigationBar(bool newValue) => setAndNotify(enableBottomNavigationBarKey, newValue); bool get confirmDeleteForever => getBoolOrDefault(confirmDeleteForeverKey, SettingsDefaults.confirmDeleteForever); @@ -602,6 +603,10 @@ class Settings extends ChangeNotifier { set screenSaverInterval(SlideshowInterval newValue) => setAndNotify(screenSaverIntervalKey, newValue.toString()); + Set get screenSaverCollectionFilters => (getStringList(screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet(); + + set screenSaverCollectionFilters(Set newValue) => setAndNotify(screenSaverCollectionFiltersKey, newValue.map((filter) => filter.toJson()).toList()); + // slideshow bool get slideshowRepeat => getBoolOrDefault(slideshowRepeatKey, SettingsDefaults.slideshowRepeat); @@ -754,7 +759,7 @@ class Settings extends ChangeNotifier { case isErrorReportingAllowedKey: case enableDynamicColorKey: case enableBlurEffectKey: - case showBottomNavigationBarKey: + case enableBottomNavigationBarKey: case mustBackTwiceToExitKey: case confirmDeleteForeverKey: case confirmMoveToBinKey: @@ -831,6 +836,7 @@ class Settings extends ChangeNotifier { case collectionBrowsingQuickActionsKey: case collectionSelectionQuickActionsKey: case viewerQuickActionsKey: + case screenSaverCollectionFiltersKey: if (newValue is List) { settingsStore.setStringList(key, newValue.cast()); } else { diff --git a/lib/services/accessibility_service.dart b/lib/services/accessibility_service.dart index 433a33077..843e72053 100644 --- a/lib/services/accessibility_service.dart +++ b/lib/services/accessibility_service.dart @@ -2,11 +2,11 @@ import 'package:aves/services/common/services.dart'; import 'package:flutter/services.dart'; class AccessibilityService { - static const platform = MethodChannel('deckers.thibault/aves/accessibility'); + static const _platform = MethodChannel('deckers.thibault/aves/accessibility'); static Future areAnimationsRemoved() async { try { - final result = await platform.invokeMethod('areAnimationsRemoved'); + final result = await _platform.invokeMethod('areAnimationsRemoved'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -16,7 +16,7 @@ class AccessibilityService { static Future hasRecommendedTimeouts() async { try { - final result = await platform.invokeMethod('hasRecommendedTimeouts'); + final result = await _platform.invokeMethod('hasRecommendedTimeouts'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -26,7 +26,7 @@ class AccessibilityService { static Future getRecommendedTimeToRead(int originalTimeoutMillis) async { try { - final result = await platform.invokeMethod('getRecommendedTimeoutMillis', { + final result = await _platform.invokeMethod('getRecommendedTimeoutMillis', { 'originalTimeoutMillis': originalTimeoutMillis, 'content': ['icons', 'text'] }); @@ -39,7 +39,7 @@ class AccessibilityService { static Future getRecommendedTimeToTakeAction(int originalTimeoutMillis) async { try { - final result = await platform.invokeMethod('getRecommendedTimeoutMillis', { + final result = await _platform.invokeMethod('getRecommendedTimeoutMillis', { 'originalTimeoutMillis': originalTimeoutMillis, 'content': ['controls', 'icons', 'text'] }); diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index 7c75aa6e4..7b2615d14 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -13,11 +13,11 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; class AnalysisService { - static const platform = MethodChannel('deckers.thibault/aves/analysis'); + static const _platform = MethodChannel('deckers.thibault/aves/analysis'); static Future registerCallback() async { try { - await platform.invokeMethod('registerCallback', { + await _platform.invokeMethod('registerCallback', { 'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(), }); } on PlatformException catch (e, stack) { @@ -27,7 +27,7 @@ class AnalysisService { static Future startService({required bool force, List? entryIds}) async { try { - await platform.invokeMethod('startService', { + await _platform.invokeMethod('startService', { 'entryIds': entryIds, 'force': force, }); diff --git a/lib/services/android_app_service.dart b/lib/services/android_app_service.dart index d61979773..32af1d6f6 100644 --- a/lib/services/android_app_service.dart +++ b/lib/services/android_app_service.dart @@ -34,9 +34,9 @@ abstract class AndroidAppService { } class PlatformAndroidAppService implements AndroidAppService { - static const platform = MethodChannel('deckers.thibault/aves/app'); + static const _platform = MethodChannel('deckers.thibault/aves/app'); - static final knownAppDirs = { + static final _knownAppDirs = { 'com.kakao.talk': {'KakaoTalkDownload'}, 'com.sony.playmemories.mobile': {'Imaging Edge Mobile'}, 'nekox.messenger': {'NekoX'}, @@ -45,10 +45,10 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future> getPackages() async { try { - final result = await platform.invokeMethod('getPackages'); + final result = await _platform.invokeMethod('getPackages'); final packages = (result as List).cast().map(Package.fromMap).toSet(); // additional info for known directories - knownAppDirs.forEach((packageName, dirs) { + _knownAppDirs.forEach((packageName, dirs) { final package = packages.firstWhereOrNull((package) => package.packageName == packageName); if (package != null) { package.ownedDirs.addAll(dirs); @@ -64,7 +64,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future getAppIcon(String packageName, double size) async { try { - final result = await platform.invokeMethod('getAppIcon', { + final result = await _platform.invokeMethod('getAppIcon', { 'packageName': packageName, 'sizeDip': size, }); @@ -78,7 +78,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future getAppInstaller() async { try { - return await platform.invokeMethod('getAppInstaller'); + return await _platform.invokeMethod('getAppInstaller'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -88,7 +88,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future copyToClipboard(String uri, String? label) async { try { - final result = await platform.invokeMethod('copyToClipboard', { + final result = await _platform.invokeMethod('copyToClipboard', { 'uri': uri, 'label': label, }); @@ -102,7 +102,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future edit(String uri, String mimeType) async { try { - final result = await platform.invokeMethod('edit', { + final result = await _platform.invokeMethod('edit', { 'uri': uri, 'mimeType': mimeType, }); @@ -116,7 +116,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future open(String uri, String mimeType) async { try { - final result = await platform.invokeMethod('open', { + final result = await _platform.invokeMethod('open', { 'uri': uri, 'mimeType': mimeType, }); @@ -134,7 +134,7 @@ class PlatformAndroidAppService implements AndroidAppService { final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude'; try { - final result = await platform.invokeMethod('openMap', { + final result = await _platform.invokeMethod('openMap', { 'geoUri': geoUri, }); if (result != null) return result as bool; @@ -147,7 +147,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future setAs(String uri, String mimeType) async { try { - final result = await platform.invokeMethod('setAs', { + final result = await _platform.invokeMethod('setAs', { 'uri': uri, 'mimeType': mimeType, }); @@ -164,7 +164,7 @@ class PlatformAndroidAppService implements AndroidAppService { // e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats final urisByMimeType = groupBy(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList())); try { - final result = await platform.invokeMethod('share', { + final result = await _platform.invokeMethod('share', { 'urisByMimeType': urisByMimeType, }); if (result != null) return result as bool; @@ -177,7 +177,7 @@ class PlatformAndroidAppService implements AndroidAppService { @override Future shareSingle(String uri, String mimeType) async { try { - final result = await platform.invokeMethod('share', { + final result = await _platform.invokeMethod('share', { 'urisByMimeType': { mimeType: [uri] }, @@ -207,7 +207,7 @@ class PlatformAndroidAppService implements AndroidAppService { ); } try { - await platform.invokeMethod('pinShortcut', { + await _platform.invokeMethod('pinShortcut', { 'label': label, 'iconBytes': iconBytes, 'filters': filters?.map((filter) => filter.toJson()).toList(), diff --git a/lib/services/android_debug_service.dart b/lib/services/android_debug_service.dart index 6c7e445c6..f657470fc 100644 --- a/lib/services/android_debug_service.dart +++ b/lib/services/android_debug_service.dart @@ -4,11 +4,11 @@ import 'package:aves/services/common/services.dart'; import 'package:flutter/services.dart'; class AndroidDebugService { - static const platform = MethodChannel('deckers.thibault/aves/debug'); + static const _platform = MethodChannel('deckers.thibault/aves/debug'); static Future crash() async { try { - await platform.invokeMethod('crash'); + await _platform.invokeMethod('crash'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -16,7 +16,7 @@ class AndroidDebugService { static Future exception() async { try { - await platform.invokeMethod('exception'); + await _platform.invokeMethod('exception'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -24,7 +24,7 @@ class AndroidDebugService { static Future safeException() async { try { - await platform.invokeMethod('safeException'); + await _platform.invokeMethod('safeException'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -32,7 +32,7 @@ class AndroidDebugService { static Future exceptionInCoroutine() async { try { - await platform.invokeMethod('exceptionInCoroutine'); + await _platform.invokeMethod('exceptionInCoroutine'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -40,7 +40,7 @@ class AndroidDebugService { static Future safeExceptionInCoroutine() async { try { - await platform.invokeMethod('safeExceptionInCoroutine'); + await _platform.invokeMethod('safeExceptionInCoroutine'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -48,7 +48,7 @@ class AndroidDebugService { static Future getContextDirs() async { try { - final result = await platform.invokeMethod('getContextDirs'); + final result = await _platform.invokeMethod('getContextDirs'); if (result != null) return result as Map; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -58,7 +58,7 @@ class AndroidDebugService { static Future> getCodecs() async { try { - final result = await platform.invokeMethod('getCodecs'); + final result = await _platform.invokeMethod('getCodecs'); if (result != null) return (result as List).cast(); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -68,7 +68,7 @@ class AndroidDebugService { static Future getEnv() async { try { - final result = await platform.invokeMethod('getEnv'); + final result = await _platform.invokeMethod('getEnv'); if (result != null) return result as Map; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -79,7 +79,7 @@ class AndroidDebugService { static Future getBitmapFactoryInfo(AvesEntry entry) async { try { // returns map with all data available when decoding image bounds with `BitmapFactory` - final result = await platform.invokeMethod('getBitmapFactoryInfo', { + final result = await _platform.invokeMethod('getBitmapFactoryInfo', { 'uri': entry.uri, }); if (result != null) return result as Map; @@ -92,7 +92,7 @@ class AndroidDebugService { static Future getContentResolverMetadata(AvesEntry entry) async { try { // returns map with all data available from the content resolver - final result = await platform.invokeMethod('getContentResolverMetadata', { + final result = await _platform.invokeMethod('getContentResolverMetadata', { 'mimeType': entry.mimeType, 'uri': entry.uri, }); @@ -106,7 +106,7 @@ class AndroidDebugService { static Future getExifInterfaceMetadata(AvesEntry entry) async { try { // returns map with all data available from the `ExifInterface` library - final result = await platform.invokeMethod('getExifInterfaceMetadata', { + final result = await _platform.invokeMethod('getExifInterfaceMetadata', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -121,7 +121,7 @@ class AndroidDebugService { static Future getMediaMetadataRetrieverMetadata(AvesEntry entry) async { try { // returns map with all data available from `MediaMetadataRetriever` - final result = await platform.invokeMethod('getMediaMetadataRetrieverMetadata', { + final result = await _platform.invokeMethod('getMediaMetadataRetrieverMetadata', { 'uri': entry.uri, }); if (result != null) return result as Map; @@ -134,7 +134,7 @@ class AndroidDebugService { static Future getMetadataExtractorSummary(AvesEntry entry) async { try { // returns map with the MIME type and tag count for each directory found by `metadata-extractor` - final result = await platform.invokeMethod('getMetadataExtractorSummary', { + final result = await _platform.invokeMethod('getMetadataExtractorSummary', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -149,7 +149,7 @@ class AndroidDebugService { static Future getPixyMetadata(AvesEntry entry) async { try { // returns map with all data available from the `PixyMeta` library - final result = await platform.invokeMethod('getPixyMetadata', { + final result = await _platform.invokeMethod('getPixyMetadata', { 'mimeType': entry.mimeType, 'uri': entry.uri, }); @@ -164,7 +164,7 @@ class AndroidDebugService { if (entry.mimeType != MimeTypes.tiff) return {}; try { - final result = await platform.invokeMethod('getTiffStructure', { + final result = await _platform.invokeMethod('getTiffStructure', { 'uri': entry.uri, }); if (result != null) return result as Map; diff --git a/lib/services/device_service.dart b/lib/services/device_service.dart index 3360204f2..5bafe40f7 100644 --- a/lib/services/device_service.dart +++ b/lib/services/device_service.dart @@ -16,12 +16,12 @@ abstract class DeviceService { } class PlatformDeviceService implements DeviceService { - static const platform = MethodChannel('deckers.thibault/aves/device'); + static const _platform = MethodChannel('deckers.thibault/aves/device'); @override Future> getCapabilities() async { try { - final result = await platform.invokeMethod('getCapabilities'); + final result = await _platform.invokeMethod('getCapabilities'); if (result != null) return (result as Map).cast(); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -32,7 +32,7 @@ class PlatformDeviceService implements DeviceService { @override Future getDefaultTimeZone() async { try { - return await platform.invokeMethod('getDefaultTimeZone'); + return await _platform.invokeMethod('getDefaultTimeZone'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -42,7 +42,7 @@ class PlatformDeviceService implements DeviceService { @override Future> getLocales() async { try { - final result = await platform.invokeMethod('getLocales'); + final result = await _platform.invokeMethod('getLocales'); if (result != null) { return (result as List).cast().map((tags) { final language = tags['language'] as String?; @@ -62,7 +62,7 @@ class PlatformDeviceService implements DeviceService { @override Future getPerformanceClass() async { try { - final result = await platform.invokeMethod('getPerformanceClass'); + final result = await _platform.invokeMethod('getPerformanceClass'); if (result != null) return result as int; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -73,7 +73,7 @@ class PlatformDeviceService implements DeviceService { @override Future isSystemFilePickerEnabled() async { try { - final result = await platform.invokeMethod('isSystemFilePickerEnabled'); + final result = await _platform.invokeMethod('isSystemFilePickerEnabled'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); diff --git a/lib/services/geocoding_service.dart b/lib/services/geocoding_service.dart index 41512449e..353cea173 100644 --- a/lib/services/geocoding_service.dart +++ b/lib/services/geocoding_service.dart @@ -7,12 +7,12 @@ import 'package:flutter/services.dart'; import 'package:latlong2/latlong.dart'; class GeocodingService { - static const platform = MethodChannel('deckers.thibault/aves/geocoding'); + static const _platform = MethodChannel('deckers.thibault/aves/geocoding'); // geocoding requires Google Play Services static Future> getAddress(LatLng coordinates, Locale locale) async { try { - final result = await platform.invokeMethod('getAddress', { + final result = await _platform.invokeMethod('getAddress', { 'latitude': coordinates.latitude, 'longitude': coordinates.longitude, 'locale': locale.toString(), diff --git a/lib/services/global_search.dart b/lib/services/global_search.dart index 19415e4e2..ee71345f5 100644 --- a/lib/services/global_search.dart +++ b/lib/services/global_search.dart @@ -7,11 +7,11 @@ import 'package:flutter/widgets.dart'; import 'package:intl/date_symbol_data_local.dart'; class GlobalSearch { - static const platform = MethodChannel('deckers.thibault/aves/global_search'); + static const _platform = MethodChannel('deckers.thibault/aves/global_search'); static Future registerCallback() async { try { - await platform.invokeMethod('registerCallback', { + await _platform.invokeMethod('registerCallback', { 'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(), }); } on PlatformException catch (e, stack) { diff --git a/lib/services/intent_service.dart b/lib/services/intent_service.dart new file mode 100644 index 000000000..47d46e691 --- /dev/null +++ b/lib/services/intent_service.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/services.dart'; +import 'package:streams_channel/streams_channel.dart'; + +class IntentService { + static const _platform = MethodChannel('deckers.thibault/aves/intent'); + static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream'); + + static Future> getIntentData() async { + try { + // returns nullable map with 'action' and possibly 'uri' 'mimeType' + final result = await _platform.invokeMethod('getIntentData'); + if (result != null) return (result as Map).cast(); + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + return {}; + } + + static Future submitPickedItems(List uris) async { + try { + await _platform.invokeMethod('submitPickedItems', { + 'uris': uris, + }); + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + } + + static Future submitPickedCollectionFilters(Set? filters) async { + try { + await _platform.invokeMethod('submitPickedCollectionFilters', { + 'filters': filters?.map((filter) => filter.toJson()).toList(), + }); + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + } + + static Future?> pickCollectionFilters(Set? initialFilters) async { + try { + final completer = Completer?>(); + _stream.receiveBroadcastStream({ + 'op': 'pickCollectionFilters', + 'initialFilters': initialFilters?.map((filter) => filter.toJson()).toList(), + }).listen( + (data) { + final result = (data as List?)?.cast().map(CollectionFilter.fromJson).whereNotNull().toSet(); + completer.complete(result); + }, + onError: completer.completeError, + onDone: () { + if (!completer.isCompleted) completer.complete(null); + }, + cancelOnError: true, + ); + // `await` here, so that `completeError` will be caught below + return await completer.future; + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + return null; + } +} diff --git a/lib/services/media/embedded_data_service.dart b/lib/services/media/embedded_data_service.dart index 3e117548d..51c148522 100644 --- a/lib/services/media/embedded_data_service.dart +++ b/lib/services/media/embedded_data_service.dart @@ -15,12 +15,12 @@ abstract class EmbeddedDataService { } class PlatformEmbeddedDataService implements EmbeddedDataService { - static const platform = MethodChannel('deckers.thibault/aves/embedded'); + static const _platform = MethodChannel('deckers.thibault/aves/embedded'); @override Future> getExifThumbnails(AvesEntry entry) async { try { - final result = await platform.invokeMethod('getExifThumbnails', { + final result = await _platform.invokeMethod('getExifThumbnails', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -35,7 +35,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService { @override Future extractMotionPhotoVideo(AvesEntry entry) async { try { - final result = await platform.invokeMethod('extractMotionPhotoVideo', { + final result = await _platform.invokeMethod('extractMotionPhotoVideo', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -51,7 +51,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService { @override Future extractVideoEmbeddedPicture(AvesEntry entry) async { try { - final result = await platform.invokeMethod('extractVideoEmbeddedPicture', { + final result = await _platform.invokeMethod('extractVideoEmbeddedPicture', { 'uri': entry.uri, 'displayName': '${entry.bestTitle} • Cover', }); @@ -65,7 +65,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService { @override Future extractXmpDataProp(AvesEntry entry, String? propPath, String? propMimeType) async { try { - final result = await platform.invokeMethod('extractXmpDataProp', { + final result = await _platform.invokeMethod('extractXmpDataProp', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, diff --git a/lib/services/media/media_file_service.dart b/lib/services/media/media_file_service.dart index ae4542659..2b21d702a 100644 --- a/lib/services/media/media_file_service.dart +++ b/lib/services/media/media_file_service.dart @@ -108,10 +108,10 @@ abstract class MediaFileService { } class PlatformMediaFileService implements MediaFileService { - static const platform = MethodChannel('deckers.thibault/aves/media_file'); - static final StreamsChannel _byteStreamChannel = StreamsChannel('deckers.thibault/aves/media_byte_stream'); - static final StreamsChannel _opStreamChannel = StreamsChannel('deckers.thibault/aves/media_op_stream'); - static const double thumbnailDefaultSize = 64.0; + static const _platform = MethodChannel('deckers.thibault/aves/media_file'); + static final _byteStream = StreamsChannel('deckers.thibault/aves/media_byte_stream'); + static final _opStream = StreamsChannel('deckers.thibault/aves/media_op_stream'); + static const double _thumbnailDefaultSize = 64.0; static Map _toPlatformEntryMap(AvesEntry entry) { return { @@ -136,7 +136,7 @@ class PlatformMediaFileService implements MediaFileService { @override Future getEntry(String uri, String? mimeType) async { try { - final result = await platform.invokeMethod('getEntry', { + final result = await _platform.invokeMethod('getEntry', { 'uri': uri, 'mimeType': mimeType, }) as Map; @@ -181,7 +181,7 @@ class PlatformMediaFileService implements MediaFileService { final completer = Completer.sync(); final sink = OutputBuffer(); var bytesReceived = 0; - _byteStreamChannel.receiveBroadcastStream({ + _byteStream.receiveBroadcastStream({ 'uri': uri, 'mimeType': mimeType, 'rotationDegrees': rotationDegrees ?? 0, @@ -234,7 +234,7 @@ class PlatformMediaFileService implements MediaFileService { return servicePolicy.call( () async { try { - final result = await platform.invokeMethod('getRegion', { + final result = await _platform.invokeMethod('getRegion', { 'uri': uri, 'mimeType': mimeType, 'pageId': pageId, @@ -274,7 +274,7 @@ class PlatformMediaFileService implements MediaFileService { return servicePolicy.call( () async { try { - final result = await platform.invokeMethod('getThumbnail', { + final result = await _platform.invokeMethod('getThumbnail', { 'uri': uri, 'mimeType': mimeType, 'dateModifiedSecs': dateModifiedSecs, @@ -283,7 +283,7 @@ class PlatformMediaFileService implements MediaFileService { 'widthDip': extent, 'heightDip': extent, 'pageId': pageId, - 'defaultSizeDip': thumbnailDefaultSize, + 'defaultSizeDip': _thumbnailDefaultSize, }); if (result != null) return result as Uint8List; } on PlatformException catch (e, stack) { @@ -301,7 +301,7 @@ class PlatformMediaFileService implements MediaFileService { @override Future clearSizedThumbnailDiskCache() async { try { - return platform.invokeMethod('clearSizedThumbnailDiskCache'); + return _platform.invokeMethod('clearSizedThumbnailDiskCache'); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -319,7 +319,7 @@ class PlatformMediaFileService implements MediaFileService { @override Future cancelFileOp(String opId) async { try { - await platform.invokeMethod('cancelFileOp', { + await _platform.invokeMethod('cancelFileOp', { 'opId': opId, }); } on PlatformException catch (e, stack) { @@ -333,7 +333,7 @@ class PlatformMediaFileService implements MediaFileService { required Iterable entries, }) { try { - return _opStreamChannel + return _opStream .receiveBroadcastStream({ 'op': 'delete', 'id': opId, @@ -355,7 +355,7 @@ class PlatformMediaFileService implements MediaFileService { required NameConflictStrategy nameConflictStrategy, }) { try { - return _opStreamChannel + return _opStream .receiveBroadcastStream({ 'op': 'move', 'id': opId, @@ -379,7 +379,7 @@ class PlatformMediaFileService implements MediaFileService { required NameConflictStrategy nameConflictStrategy, }) { try { - return _opStreamChannel + return _opStream .receiveBroadcastStream({ 'op': 'export', 'entries': entries.map(_toPlatformEntryMap).toList(), @@ -403,7 +403,7 @@ class PlatformMediaFileService implements MediaFileService { required Map entriesToNewName, }) { try { - return _opStreamChannel + return _opStream .receiveBroadcastStream({ 'op': 'rename', 'id': opId, @@ -427,7 +427,7 @@ class PlatformMediaFileService implements MediaFileService { required NameConflictStrategy nameConflictStrategy, }) async { try { - final result = await platform.invokeMethod('captureFrame', { + final result = await _platform.invokeMethod('captureFrame', { 'uri': entry.uri, 'desiredName': desiredName, 'exif': exif, diff --git a/lib/services/media/media_store_service.dart b/lib/services/media/media_store_service.dart index 8a6647485..203fd2944 100644 --- a/lib/services/media/media_store_service.dart +++ b/lib/services/media/media_store_service.dart @@ -18,13 +18,13 @@ abstract class MediaStoreService { } class PlatformMediaStoreService implements MediaStoreService { - static const platform = MethodChannel('deckers.thibault/aves/media_store'); - static final StreamsChannel _streamChannel = StreamsChannel('deckers.thibault/aves/media_store_stream'); + static const _platform = MethodChannel('deckers.thibault/aves/media_store'); + static final _stream = StreamsChannel('deckers.thibault/aves/media_store_stream'); @override Future> checkObsoleteContentIds(List knownContentIds) async { try { - final result = await platform.invokeMethod('checkObsoleteContentIds', { + final result = await _platform.invokeMethod('checkObsoleteContentIds', { 'knownContentIds': knownContentIds, }); return (result as List).cast(); @@ -37,7 +37,7 @@ class PlatformMediaStoreService implements MediaStoreService { @override Future> checkObsoletePaths(Map knownPathById) async { try { - final result = await platform.invokeMethod('checkObsoletePaths', { + final result = await _platform.invokeMethod('checkObsoletePaths', { 'knownPathById': knownPathById, }); return (result as List).cast(); @@ -50,7 +50,7 @@ class PlatformMediaStoreService implements MediaStoreService { @override Stream getEntries(Map knownEntries, {String? directory}) { try { - return _streamChannel + return _stream .receiveBroadcastStream({ 'knownEntries': knownEntries, 'directory': directory, @@ -67,7 +67,7 @@ class PlatformMediaStoreService implements MediaStoreService { @override Future scanFile(String path, String mimeType) async { try { - final result = await platform.invokeMethod('scanFile', { + final result = await _platform.invokeMethod('scanFile', { 'path': path, 'mimeType': mimeType, }); diff --git a/lib/services/metadata/metadata_edit_service.dart b/lib/services/metadata/metadata_edit_service.dart index bdc7cd300..6cd48fae4 100644 --- a/lib/services/metadata/metadata_edit_service.dart +++ b/lib/services/metadata/metadata_edit_service.dart @@ -23,7 +23,7 @@ abstract class MetadataEditService { } class PlatformMetadataEditService implements MetadataEditService { - static const platform = MethodChannel('deckers.thibault/aves/metadata_edit'); + static const _platform = MethodChannel('deckers.thibault/aves/metadata_edit'); static Map _toPlatformEntryMap(AvesEntry entry) { return { @@ -44,7 +44,7 @@ class PlatformMetadataEditService implements MetadataEditService { Future> rotate(AvesEntry entry, {required bool clockwise}) async { try { // returns map with: 'rotationDegrees' 'isFlipped' - final result = await platform.invokeMethod('rotate', { + final result = await _platform.invokeMethod('rotate', { 'entry': _toPlatformEntryMap(entry), 'clockwise': clockwise, }); @@ -61,7 +61,7 @@ class PlatformMetadataEditService implements MetadataEditService { Future> flip(AvesEntry entry) async { try { // returns map with: 'rotationDegrees' 'isFlipped' - final result = await platform.invokeMethod('flip', { + final result = await _platform.invokeMethod('flip', { 'entry': _toPlatformEntryMap(entry), }); if (result != null) return (result as Map).cast(); @@ -76,7 +76,7 @@ class PlatformMetadataEditService implements MetadataEditService { @override Future> editExifDate(AvesEntry entry, DateModifier modifier) async { try { - final result = await platform.invokeMethod('editDate', { + final result = await _platform.invokeMethod('editDate', { 'entry': _toPlatformEntryMap(entry), 'dateMillis': modifier.setDateTime?.millisecondsSinceEpoch, 'shiftMinutes': modifier.shiftMinutes, @@ -98,7 +98,7 @@ class PlatformMetadataEditService implements MetadataEditService { bool autoCorrectTrailerOffset = true, }) async { try { - final result = await platform.invokeMethod('editMetadata', { + final result = await _platform.invokeMethod('editMetadata', { 'entry': _toPlatformEntryMap(entry), 'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)), 'autoCorrectTrailerOffset': autoCorrectTrailerOffset, @@ -115,7 +115,7 @@ class PlatformMetadataEditService implements MetadataEditService { @override Future> removeTrailerVideo(AvesEntry entry) async { try { - final result = await platform.invokeMethod('removeTrailerVideo', { + final result = await _platform.invokeMethod('removeTrailerVideo', { 'entry': _toPlatformEntryMap(entry), }); if (result != null) return (result as Map).cast(); @@ -130,7 +130,7 @@ class PlatformMetadataEditService implements MetadataEditService { @override Future> removeTypes(AvesEntry entry, Set types) async { try { - final result = await platform.invokeMethod('removeTypes', { + final result = await _platform.invokeMethod('removeTypes', { 'entry': _toPlatformEntryMap(entry), 'types': types.map(_toPlatformMetadataType).toList(), }); diff --git a/lib/services/metadata/metadata_fetch_service.dart b/lib/services/metadata/metadata_fetch_service.dart index ad756bcf5..59e5a8641 100644 --- a/lib/services/metadata/metadata_fetch_service.dart +++ b/lib/services/metadata/metadata_fetch_service.dart @@ -38,14 +38,14 @@ abstract class MetadataFetchService { } class PlatformMetadataFetchService implements MetadataFetchService { - static const platform = MethodChannel('deckers.thibault/aves/metadata_fetch'); + static const _platform = MethodChannel('deckers.thibault/aves/metadata_fetch'); @override Future getAllMetadata(AvesEntry entry) async { if (entry.isSvg) return {}; try { - final result = await platform.invokeMethod('getAllMetadata', { + final result = await _platform.invokeMethod('getAllMetadata', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -76,7 +76,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { // 'longitude': longitude (double) // 'xmpSubjects': ';' separated XMP subjects (string) // 'xmpTitleDescription': XMP title or XMP description (string) - final result = await platform.invokeMethod('getCatalogMetadata', { + final result = await _platform.invokeMethod('getCatalogMetadata', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'path': entry.path, @@ -106,7 +106,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { try { // returns map with values for: 'aperture' (double), 'exposureTime' (description), 'focalLength' (double), 'iso' (int) - final result = await platform.invokeMethod('getOverlayMetadata', { + final result = await _platform.invokeMethod('getOverlayMetadata', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -123,7 +123,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { @override Future getGeoTiffInfo(AvesEntry entry) async { try { - final result = await platform.invokeMethod('getGeoTiffInfo', { + final result = await _platform.invokeMethod('getGeoTiffInfo', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -140,7 +140,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { @override Future getMultiPageInfo(AvesEntry entry) async { try { - final result = await platform.invokeMethod('getMultiPageInfo', { + final result = await _platform.invokeMethod('getMultiPageInfo', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -167,7 +167,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { // returns map with values for: // 'croppedAreaLeft' (int), 'croppedAreaTop' (int), 'croppedAreaWidth' (int), 'croppedAreaHeight' (int), // 'fullPanoWidth' (int), 'fullPanoHeight' (int) - final result = await platform.invokeMethod('getPanoramaInfo', { + final result = await _platform.invokeMethod('getPanoramaInfo', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -184,7 +184,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { @override Future>?> getIptc(AvesEntry entry) async { try { - final result = await platform.invokeMethod('getIptc', { + final result = await _platform.invokeMethod('getIptc', { 'mimeType': entry.mimeType, 'uri': entry.uri, }); @@ -200,7 +200,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { @override Future getXmp(AvesEntry entry) async { try { - final result = await platform.invokeMethod('getXmp', { + final result = await _platform.invokeMethod('getXmp', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, @@ -222,7 +222,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { if (exists != null) return SynchronousFuture(exists); try { - exists = await platform.invokeMethod('hasContentResolverProp', { + exists = await _platform.invokeMethod('hasContentResolverProp', { 'prop': prop, }); } on PlatformException catch (e, stack) { @@ -236,7 +236,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { @override Future getContentResolverProp(AvesEntry entry, String prop) async { try { - return await platform.invokeMethod('getContentResolverProp', { + return await _platform.invokeMethod('getContentResolverProp', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'prop': prop, @@ -252,7 +252,7 @@ class PlatformMetadataFetchService implements MetadataFetchService { @override Future getDate(AvesEntry entry, MetadataField field) async { try { - final result = await platform.invokeMethod('getDate', { + final result = await _platform.invokeMethod('getDate', { 'mimeType': entry.mimeType, 'uri': entry.uri, 'sizeBytes': entry.sizeBytes, diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 6c7b8e45a..e73f9838a 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -40,13 +40,13 @@ abstract class StorageService { } class PlatformStorageService implements StorageService { - static const platform = MethodChannel('deckers.thibault/aves/storage'); - static final StreamsChannel storageAccessChannel = StreamsChannel('deckers.thibault/aves/storage_access_stream'); + static const _platform = MethodChannel('deckers.thibault/aves/storage'); + static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream'); @override Future> getStorageVolumes() async { try { - final result = await platform.invokeMethod('getStorageVolumes'); + final result = await _platform.invokeMethod('getStorageVolumes'); return (result as List).cast().map(StorageVolume.fromMap).toSet(); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -57,7 +57,7 @@ class PlatformStorageService implements StorageService { @override Future getFreeSpace(StorageVolume volume) async { try { - final result = await platform.invokeMethod('getFreeSpace', { + final result = await _platform.invokeMethod('getFreeSpace', { 'path': volume.path, }); return result as int?; @@ -70,7 +70,7 @@ class PlatformStorageService implements StorageService { @override Future> getGrantedDirectories() async { try { - final result = await platform.invokeMethod('getGrantedDirectories'); + final result = await _platform.invokeMethod('getGrantedDirectories'); return (result as List).cast(); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -81,7 +81,7 @@ class PlatformStorageService implements StorageService { @override Future> getInaccessibleDirectories(Iterable dirPaths) async { try { - final result = await platform.invokeMethod('getInaccessibleDirectories', { + final result = await _platform.invokeMethod('getInaccessibleDirectories', { 'dirPaths': dirPaths.toList(), }); if (result != null) { @@ -96,7 +96,7 @@ class PlatformStorageService implements StorageService { @override Future> getRestrictedDirectories() async { try { - final result = await platform.invokeMethod('getRestrictedDirectories'); + final result = await _platform.invokeMethod('getRestrictedDirectories'); if (result != null) { return (result as List).cast().map(VolumeRelativeDirectory.fromMap).toSet(); } @@ -109,7 +109,7 @@ class PlatformStorageService implements StorageService { @override Future revokeDirectoryAccess(String path) async { try { - await platform.invokeMethod('revokeDirectoryAccess', { + await _platform.invokeMethod('revokeDirectoryAccess', { 'path': path, }); } on PlatformException catch (e, stack) { @@ -122,7 +122,7 @@ class PlatformStorageService implements StorageService { @override Future deleteEmptyDirectories(Iterable dirPaths) async { try { - final result = await platform.invokeMethod('deleteEmptyDirectories', { + final result = await _platform.invokeMethod('deleteEmptyDirectories', { 'dirPaths': dirPaths.toList(), }); if (result != null) return result as int; @@ -135,7 +135,7 @@ class PlatformStorageService implements StorageService { @override Future canRequestMediaFileAccess() async { try { - final result = await platform.invokeMethod('canRequestMediaFileBulkAccess'); + final result = await _platform.invokeMethod('canRequestMediaFileBulkAccess'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -146,7 +146,7 @@ class PlatformStorageService implements StorageService { @override Future canInsertMedia(Set directories) async { try { - final result = await platform.invokeMethod('canInsertMedia', { + final result = await _platform.invokeMethod('canInsertMedia', { 'directories': directories.map((v) => v.toMap()).toList(), }); if (result != null) return result as bool; @@ -161,7 +161,7 @@ class PlatformStorageService implements StorageService { Future requestDirectoryAccess(String path) async { try { final completer = Completer(); - storageAccessChannel.receiveBroadcastStream({ + _stream.receiveBroadcastStream({ 'op': 'requestDirectoryAccess', 'path': path, }).listen( @@ -185,7 +185,7 @@ class PlatformStorageService implements StorageService { Future requestMediaFileAccess(List uris, List mimeTypes) async { try { final completer = Completer(); - storageAccessChannel.receiveBroadcastStream({ + _stream.receiveBroadcastStream({ 'op': 'requestMediaFileAccess', 'uris': uris, 'mimeTypes': mimeTypes, @@ -216,7 +216,7 @@ class PlatformStorageService implements StorageService { Future createFile(String name, String mimeType, Uint8List bytes) async { try { final completer = Completer(); - storageAccessChannel.receiveBroadcastStream({ + _stream.receiveBroadcastStream({ 'op': 'createFile', 'name': name, 'mimeType': mimeType, @@ -242,7 +242,7 @@ class PlatformStorageService implements StorageService { try { final completer = Completer.sync(); final sink = OutputBuffer(); - storageAccessChannel.receiveBroadcastStream({ + _stream.receiveBroadcastStream({ 'op': 'openFile', 'mimeType': mimeType, }).listen( diff --git a/lib/services/viewer_service.dart b/lib/services/viewer_service.dart deleted file mode 100644 index 335bd7b71..000000000 --- a/lib/services/viewer_service.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:aves/services/common/services.dart'; -import 'package:flutter/services.dart'; - -class ViewerService { - static const platform = MethodChannel('deckers.thibault/aves/viewer'); - - static Future> getIntentData() async { - try { - // returns nullable map with 'action' and possibly 'uri' 'mimeType' - final result = await platform.invokeMethod('getIntentData'); - if (result != null) return (result as Map).cast(); - } on PlatformException catch (e, stack) { - await reportService.recordError(e, stack); - } - return {}; - } - - static Future pick(List uris) async { - try { - await platform.invokeMethod('pick', { - 'uris': uris, - }); - } on PlatformException catch (e, stack) { - await reportService.recordError(e, stack); - } - } -} diff --git a/lib/services/wallpaper_service.dart b/lib/services/wallpaper_service.dart index 78d3e1d00..5e3caa750 100644 --- a/lib/services/wallpaper_service.dart +++ b/lib/services/wallpaper_service.dart @@ -5,11 +5,11 @@ import 'package:aves/services/common/services.dart'; import 'package:flutter/services.dart'; class WallpaperService { - static const platform = MethodChannel('deckers.thibault/aves/wallpaper'); + static const _platform = MethodChannel('deckers.thibault/aves/wallpaper'); static Future set(Uint8List bytes, WallpaperTarget target) async { try { - await platform.invokeMethod('setWallpaper', { + await _platform.invokeMethod('setWallpaper', { 'bytes': bytes, 'home': {WallpaperTarget.home, WallpaperTarget.homeLock}.contains(target), 'lock': {WallpaperTarget.lock, WallpaperTarget.homeLock}.contains(target), diff --git a/lib/services/window_service.dart b/lib/services/window_service.dart index 28cc45d6d..1c99b1161 100644 --- a/lib/services/window_service.dart +++ b/lib/services/window_service.dart @@ -17,12 +17,12 @@ abstract class WindowService { } class PlatformWindowService implements WindowService { - static const platform = MethodChannel('deckers.thibault/aves/window'); + static const _platform = MethodChannel('deckers.thibault/aves/window'); @override Future isActivity() async { try { - final result = await platform.invokeMethod('isActivity'); + final result = await _platform.invokeMethod('isActivity'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -33,7 +33,7 @@ class PlatformWindowService implements WindowService { @override Future keepScreenOn(bool on) async { try { - await platform.invokeMethod('keepScreenOn', { + await _platform.invokeMethod('keepScreenOn', { 'on': on, }); } on PlatformException catch (e, stack) { @@ -44,7 +44,7 @@ class PlatformWindowService implements WindowService { @override Future isRotationLocked() async { try { - final result = await platform.invokeMethod('isRotationLocked'); + final result = await _platform.invokeMethod('isRotationLocked'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -71,7 +71,7 @@ class PlatformWindowService implements WindowService { break; } try { - await platform.invokeMethod('requestOrientation', { + await _platform.invokeMethod('requestOrientation', { 'orientation': orientationCode, }); } on PlatformException catch (e, stack) { @@ -82,7 +82,7 @@ class PlatformWindowService implements WindowService { @override Future canSetCutoutMode() async { try { - final result = await platform.invokeMethod('canSetCutoutMode'); + final result = await _platform.invokeMethod('canSetCutoutMode'); if (result != null) return result as bool; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); @@ -93,7 +93,7 @@ class PlatformWindowService implements WindowService { @override Future setCutoutMode(bool use) async { try { - await platform.invokeMethod('setCutoutMode', { + await _platform.invokeMethod('setCutoutMode', { 'use': use, }); } on PlatformException catch (e, stack) { diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 8894d25fb..87e43b8cb 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -83,7 +83,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { // the list itself needs to be reassigned List _navigatorObservers = [AvesApp.pageRouteObserver]; final EventChannel _mediaStoreChangeChannel = const OptionalEventChannel('deckers.thibault/aves/media_store_change'); - final EventChannel _newIntentChannel = const OptionalEventChannel('deckers.thibault/aves/intent'); + final EventChannel _newIntentChannel = const OptionalEventChannel('deckers.thibault/aves/new_intent_stream'); final EventChannel _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events'); final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error'); @@ -228,6 +228,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { case AppMode.pickMultipleMediaExternal: _saveTopEntries(); break; + case AppMode.pickCollectionFiltersExternal: case AppMode.pickMediaInternal: case AppMode.pickFilterInternal: case AppMode.screenSaver: diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart index 5d1624d68..bd14c2eda 100644 --- a/lib/widgets/collection/app_bar.dart +++ b/lib/widgets/collection/app_bar.dart @@ -138,7 +138,7 @@ class _CollectionAppBarState extends State with SingleTickerPr return AvesAppBar( contentHeight: appBarContentHeight, leading: _buildAppBarLeading( - hasDrawer: appMode.hasDrawer, + hasDrawer: appMode.canNavigate, isSelecting: isSelecting, ), title: _buildAppBarTitle(isSelecting), @@ -228,7 +228,7 @@ class _CollectionAppBarState extends State with SingleTickerPr ); } return InteractiveAppBarTitle( - onTap: appMode.canSearch ? _goToSearch : null, + onTap: appMode.canNavigate ? _goToSearch : null, child: title, ); } diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 30005f025..6c9d116e9 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -380,8 +380,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge selector: (context, mq) => mq.effectiveBottomPadding, builder: (context, mqPaddingBottom, child) { return Selector( - selector: (context, s) => s.showBottomNavigationBar, - builder: (context, showBottomNavigationBar, child) { + selector: (context, s) => s.enableBottomNavigationBar, + builder: (context, enableBottomNavigationBar, child) { + final canNavigate = context.select, bool>((v) => v.value.canNavigate); + final showBottomNavigationBar = canNavigate && enableBottomNavigationBar; final navBarHeight = showBottomNavigationBar ? AppBottomNavBar.height : 0; return Selector, List>( selector: (context, layout) => layout.sectionLayouts, diff --git a/lib/widgets/collection/collection_page.dart b/lib/widgets/collection/collection_page.dart index d36b14166..a7b78c924 100644 --- a/lib/widgets/collection/collection_page.dart +++ b/lib/widgets/collection/collection_page.dart @@ -10,7 +10,7 @@ import 'package:aves/model/selection.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/services/viewer_service.dart'; +import 'package:aves/services/intent_service.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/collection/collection_grid.dart'; @@ -79,7 +79,6 @@ class _CollectionPageState extends State { @override Widget build(BuildContext context) { - final appMode = context.watch>().value; final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?; return MediaQueryDataProvider( child: SelectionProvider( @@ -87,8 +86,10 @@ class _CollectionPageState extends State { selector: (context, selection) => selection.selectedItems.isNotEmpty, builder: (context, hasSelection, child) { return Selector( - selector: (context, s) => s.showBottomNavigationBar, - builder: (context, showBottomNavigationBar, child) { + selector: (context, s) => s.enableBottomNavigationBar, + builder: (context, enableBottomNavigationBar, child) { + final canNavigate = context.select, bool>((v) => v.value.canNavigate); + final showBottomNavigationBar = canNavigate && enableBottomNavigationBar; return NotificationListener( onNotification: (notification) { _draggableScrollBarEventStreamController.add(notification.event); @@ -126,25 +127,7 @@ class _CollectionPageState extends State { ), ), ), - floatingActionButton: appMode == AppMode.pickMultipleMediaExternal && hasSelection - ? TooltipTheme( - data: TooltipTheme.of(context).copyWith( - preferBelow: false, - ), - child: FloatingActionButton( - tooltip: context.l10n.collectionPickPageTitle, - onPressed: () { - final items = context.read>().selectedItems; - final uris = items.map((entry) => entry.uri).toList(); - ViewerService.pick(uris); - }, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - child: const Icon(AIcons.apply), - ), - ) - : null, + floatingActionButton: _buildFab(context, hasSelection), drawer: AppDrawer(currentCollection: _collection), bottomNavigationBar: showBottomNavigationBar ? AppBottomNavBar( @@ -164,6 +147,59 @@ class _CollectionPageState extends State { ); } + Widget? _buildFab(BuildContext context, bool hasSelection) { + Widget fab({ + required String tooltip, + required VoidCallback onPressed, + }) { + return TooltipTheme( + data: TooltipTheme.of(context).copyWith( + preferBelow: false, + ), + child: FloatingActionButton( + tooltip: tooltip, + onPressed: onPressed, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + child: const Icon(AIcons.apply), + ), + ); + } + + final appMode = context.watch>().value; + switch (appMode) { + case AppMode.pickMultipleMediaExternal: + return hasSelection + ? fab( + tooltip: context.l10n.collectionPickPageTitle, + onPressed: () { + final items = context.read>().selectedItems; + final uris = items.map((entry) => entry.uri).toList(); + IntentService.submitPickedItems(uris); + }, + ) + : null; + case AppMode.pickCollectionFiltersExternal: + return fab( + tooltip: context.l10n.collectionPickPageTitle, + onPressed: () { + final filters = _collection.filters; + IntentService.submitPickedCollectionFilters(filters); + }, + ); + case AppMode.main: + case AppMode.pickSingleMediaExternal: + case AppMode.pickMediaInternal: + case AppMode.pickFilterInternal: + case AppMode.screenSaver: + case AppMode.setWallpaper: + case AppMode.slideshow: + case AppMode.view: + return null; + } + } + Future _checkInitHighlight() async { final highlightTest = widget.highlightTest; if (highlightTest == null) return; diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 09a719122..438faafda 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -65,7 +65,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware return isSelecting && selectedItemCount == itemCount; // browsing case EntrySetAction.searchCollection: - return appMode.canSearch && !isSelecting; + return appMode.canNavigate && !isSelecting; case EntrySetAction.toggleTitleSearch: return !isSelecting; case EntrySetAction.addShortcut: diff --git a/lib/widgets/collection/filter_bar.dart b/lib/widgets/collection/filter_bar.dart index ee4129c49..b5b6c42af 100644 --- a/lib/widgets/collection/filter_bar.dart +++ b/lib/widgets/collection/filter_bar.dart @@ -14,7 +14,7 @@ class FilterBar extends StatefulWidget { FilterBar({ super.key, required Set filters, - required this.removable, + this.removable = false, this.onTap, }) : filters = List.from(filters)..sort(); diff --git a/lib/widgets/collection/grid/tile.dart b/lib/widgets/collection/grid/tile.dart index 54b86e54f..5f3f3c20a 100644 --- a/lib/widgets/collection/grid/tile.dart +++ b/lib/widgets/collection/grid/tile.dart @@ -3,7 +3,7 @@ import 'package:aves/model/entry.dart'; import 'package:aves/model/selection.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/enums.dart'; -import 'package:aves/services/viewer_service.dart'; +import 'package:aves/services/intent_service.dart'; import 'package:aves/widgets/collection/grid/list_details.dart'; import 'package:aves/widgets/collection/grid/list_details_theme.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; @@ -44,7 +44,7 @@ class InteractiveTile extends StatelessWidget { } break; case AppMode.pickSingleMediaExternal: - ViewerService.pick([entry.uri]); + IntentService.submitPickedItems([entry.uri]); break; case AppMode.pickMultipleMediaExternal: final selection = context.read>(); @@ -53,6 +53,7 @@ class InteractiveTile extends StatelessWidget { case AppMode.pickMediaInternal: Navigator.pop(context, entry); break; + case AppMode.pickCollectionFiltersExternal: case AppMode.pickFilterInternal: case AppMode.screenSaver: case AppMode.setWallpaper: diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 637cca171..41ce5d2d3 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -78,7 +78,7 @@ class AvesFilterChip extends StatefulWidget { }); static Future showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async { - if (context.read>().value == AppMode.main) { + if (context.read>().value.canNavigate) { final actions = [ if (filter is AlbumFilter) ChipAction.goToAlbumPage, if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage, diff --git a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart index 45bbc0831..a9ef0e0c0 100644 --- a/lib/widgets/filter_grids/common/action_delegates/chip_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/chip_set.dart @@ -61,7 +61,7 @@ abstract class ChipSetActionDelegate with FeedbackMi return isSelecting && selectedItemCount == itemCount; // browsing case ChipSetAction.search: - return appMode.canSearch && !isSelecting; + return appMode.canNavigate && !isSelecting; case ChipSetAction.createAlbum: return false; // browsing or selecting diff --git a/lib/widgets/filter_grids/common/app_bar.dart b/lib/widgets/filter_grids/common/app_bar.dart index 0edf0b257..25bb1e244 100644 --- a/lib/widgets/filter_grids/common/app_bar.dart +++ b/lib/widgets/filter_grids/common/app_bar.dart @@ -77,7 +77,7 @@ class _FilterGridAppBarState extends State extends State>().value; return InteractiveAppBarTitle( - onTap: appMode.canSearch ? _goToSearch : null, + onTap: appMode.canNavigate ? _goToSearch : null, child: SourceStateAwareAppBarTitle( title: Text(widget.title), source: source, diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 08938aec0..448cd0167 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -74,8 +74,10 @@ class FilterGridPage extends StatelessWidget { Widget build(BuildContext context) { return MediaQueryDataProvider( child: Selector( - selector: (context, s) => s.showBottomNavigationBar, - builder: (context, showBottomNavigationBar, child) { + selector: (context, s) => s.enableBottomNavigationBar, + builder: (context, enableBottomNavigationBar, child) { + final canNavigate = context.select, bool>((v) => v.value.canNavigate); + final showBottomNavigationBar = canNavigate && enableBottomNavigationBar; return NotificationListener( onNotification: (notification) { _draggableScrollBarEventStreamController.add(notification.event); @@ -524,8 +526,10 @@ class _FilterScrollView extends StatelessWidget { selector: (context, mq) => mq.effectiveBottomPadding, builder: (context, mqPaddingBottom, child) { return Selector( - selector: (context, s) => s.showBottomNavigationBar, - builder: (context, showBottomNavigationBar, child) { + selector: (context, s) => s.enableBottomNavigationBar, + builder: (context, enableBottomNavigationBar, child) { + final canNavigate = context.select, bool>((v) => v.value.canNavigate); + final showBottomNavigationBar = canNavigate && enableBottomNavigationBar; final navBarHeight = showBottomNavigationBar ? AppBottomNavBar.height : 0; return DraggableScrollbar( backgroundColor: Colors.white, diff --git a/lib/widgets/filter_grids/common/filter_tile.dart b/lib/widgets/filter_grids/common/filter_tile.dart index 226389f8c..1de89ffac 100644 --- a/lib/widgets/filter_grids/common/filter_tile.dart +++ b/lib/widgets/filter_grids/common/filter_tile.dart @@ -50,6 +50,7 @@ class _InteractiveFilterTileState extends State>().value; switch (appMode) { case AppMode.main: + case AppMode.pickCollectionFiltersExternal: case AppMode.pickSingleMediaExternal: case AppMode.pickMultipleMediaExternal: final selection = context.read>>(); diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 770f20281..084d9808e 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -13,7 +13,7 @@ import 'package:aves/model/source/enums.dart'; import 'package:aves/services/analysis_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/global_search.dart'; -import 'package:aves/services/viewer_service.dart'; +import 'package:aves/services/intent_service.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; @@ -47,10 +47,11 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { AvesEntry? _viewerEntry; - String? _shortcutRouteName, _shortcutSearchQuery; - Set? _shortcutFilters; + String? _initialRouteName, _initialSearchQuery; + Set? _initialFilters; - static const actionPick = 'pick'; + static const actionPickItems = 'pick_items'; + static const actionPickCollectionFilters = 'pick_collection_filters'; static const actionScreenSaver = 'screen_saver'; static const actionScreenSaverSettings = 'screen_saver_settings'; static const actionSearch = 'search'; @@ -86,7 +87,7 @@ class _HomePageState extends State { } var appMode = AppMode.main; - final intentData = widget.intentData ?? await ViewerService.getIntentData(); + final intentData = widget.intentData ?? await IntentService.getIntentData(); final intentAction = intentData['action']; if (!{actionScreenSaver, actionSetWallpaper}.contains(intentAction)) { @@ -108,7 +109,7 @@ class _HomePageState extends State { appMode = AppMode.view; } break; - case actionPick: + case actionPickItems: // TODO TLAD apply pick mimetype(s) // some apps define multiple types, separated by a space (maybe other signs too, like `,` `;`?) String? pickMimeTypes = intentData['mimeType']; @@ -116,16 +117,19 @@ class _HomePageState extends State { debugPrint('pick mimeType=$pickMimeTypes multiple=$multiple'); appMode = multiple ? AppMode.pickMultipleMediaExternal : AppMode.pickSingleMediaExternal; break; + case actionPickCollectionFilters: + appMode = AppMode.pickCollectionFiltersExternal; + break; case actionScreenSaver: appMode = AppMode.screenSaver; - _shortcutRouteName = ScreenSaverPage.routeName; + _initialRouteName = ScreenSaverPage.routeName; break; case actionScreenSaverSettings: - _shortcutRouteName = ScreenSaverSettingsPage.routeName; + _initialRouteName = ScreenSaverSettingsPage.routeName; break; case actionSearch: - _shortcutRouteName = CollectionSearchDelegate.pageRouteName; - _shortcutSearchQuery = intentData['query']; + _initialRouteName = CollectionSearchDelegate.pageRouteName; + _initialSearchQuery = intentData['query']; break; case actionSetWallpaper: appMode = AppMode.setWallpaper; @@ -138,11 +142,11 @@ class _HomePageState extends State { // do not use 'route' as extra key, as the Flutter framework acts on it final extraRoute = intentData['page']; if (allowedShortcutRoutes.contains(extraRoute)) { - _shortcutRouteName = extraRoute; + _initialRouteName = extraRoute; } - final extraFilters = intentData['filters']; - _shortcutFilters = extraFilters != null ? (extraFilters as List).cast().toSet() : null; } + final extraFilters = intentData['filters']; + _initialFilters = extraFilters != null ? (extraFilters as List).cast().map(CollectionFilter.fromJson).whereNotNull().toSet() : null; } context.read>().value = appMode; unawaited(reportService.setCustomKey('app_mode', appMode.toString())); @@ -150,6 +154,7 @@ class _HomePageState extends State { switch (appMode) { case AppMode.main: + case AppMode.pickCollectionFiltersExternal: case AppMode.pickSingleMediaExternal: case AppMode.pickMultipleMediaExternal: unawaited(GlobalSearch.registerCallback()); @@ -281,8 +286,8 @@ class _HomePageState extends State { routeName = CollectionPage.routeName; break; default: - routeName = _shortcutRouteName ?? settings.homePage.routeName; - filters = (_shortcutFilters ?? {}).map(CollectionFilter.fromJson).toSet(); + routeName = _initialRouteName ?? settings.homePage.routeName; + filters = _initialFilters ?? {}; break; } final source = context.read(); @@ -310,7 +315,7 @@ class _HomePageState extends State { searchFieldLabel: context.l10n.searchCollectionFieldHint, source: source, canPop: false, - initialQuery: _shortcutSearchQuery, + initialQuery: _initialSearchQuery, ), ); case CollectionPage.routeName: diff --git a/lib/widgets/navigation/nav_bar/nav_bar.dart b/lib/widgets/navigation/nav_bar/nav_bar.dart index b947607c0..dc89e4404 100644 --- a/lib/widgets/navigation/nav_bar/nav_bar.dart +++ b/lib/widgets/navigation/nav_bar/nav_bar.dart @@ -1,3 +1,4 @@ +import 'package:aves/app_mode.dart'; import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/settings/settings.dart'; @@ -14,6 +15,7 @@ import 'package:aves/widgets/navigation/nav_bar/nav_item.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:provider/provider.dart'; class AppBottomNavBar extends StatefulWidget { final Stream events; @@ -163,8 +165,10 @@ class NavBarPaddingSliver extends StatelessWidget { Widget build(BuildContext context) { return SliverToBoxAdapter( child: Selector( - selector: (context, s) => s.showBottomNavigationBar, - builder: (context, showBottomNavigationBar, child) { + selector: (context, s) => s.enableBottomNavigationBar, + builder: (context, enableBottomNavigationBar, child) { + final canNavigate = context.select, bool>((v) => v.value.canNavigate); + final showBottomNavigationBar = canNavigate && enableBottomNavigationBar; return SizedBox(height: showBottomNavigationBar ? AppBottomNavBar.height : 0); }, ), diff --git a/lib/widgets/settings/navigation/navigation.dart b/lib/widgets/settings/navigation/navigation.dart index ecbd33b57..530feaeb3 100644 --- a/lib/widgets/settings/navigation/navigation.dart +++ b/lib/widgets/settings/navigation/navigation.dart @@ -60,8 +60,8 @@ class SettingsTileShowBottomNavigationBar extends SettingsTile { @override Widget build(BuildContext context) => SettingsSwitchListTile( - selector: (context, s) => s.showBottomNavigationBar, - onChanged: (v) => settings.showBottomNavigationBar = v, + selector: (context, s) => s.enableBottomNavigationBar, + onChanged: (v) => settings.enableBottomNavigationBar = v, title: title(context), ); } diff --git a/lib/widgets/settings/screen_saver_settings_page.dart b/lib/widgets/settings/screen_saver_settings_page.dart index a8979d122..2bc06b99d 100644 --- a/lib/widgets/settings/screen_saver_settings_page.dart +++ b/lib/widgets/settings/screen_saver_settings_page.dart @@ -1,11 +1,16 @@ +import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/settings/enums/enums.dart'; import 'package:aves/model/settings/enums/slideshow_interval.dart'; import 'package:aves/model/settings/enums/slideshow_video_playback.dart'; import 'package:aves/model/settings/enums/viewer_transition.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/services/intent_service.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/collection/filter_bar.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class ScreenSaverSettingsPage extends StatelessWidget { static const routeName = '/settings/screen_saver'; @@ -14,9 +19,10 @@ class ScreenSaverSettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = context.l10n; return Scaffold( appBar: AppBar( - title: Text(context.l10n.settingsScreenSaverTitle), + title: Text(l10n.settingsScreenSaverTitle), ), body: SafeArea( child: ListView( @@ -26,24 +32,77 @@ class ScreenSaverSettingsPage extends StatelessWidget { getName: (context, v) => v.getName(context), selector: (context, s) => s.screenSaverTransition, onSelection: (v) => settings.screenSaverTransition = v, - tileTitle: context.l10n.settingsSlideshowTransitionTile, - dialogTitle: context.l10n.settingsSlideshowTransitionTitle, + tileTitle: l10n.settingsSlideshowTransitionTile, + dialogTitle: l10n.settingsSlideshowTransitionTitle, ), SettingsSelectionListTile( values: SlideshowInterval.values, getName: (context, v) => v.getName(context), selector: (context, s) => s.screenSaverInterval, onSelection: (v) => settings.screenSaverInterval = v, - tileTitle: context.l10n.settingsSlideshowIntervalTile, - dialogTitle: context.l10n.settingsSlideshowIntervalTitle, + tileTitle: l10n.settingsSlideshowIntervalTile, + dialogTitle: l10n.settingsSlideshowIntervalTitle, ), SettingsSelectionListTile( values: SlideshowVideoPlayback.values, getName: (context, v) => v.getName(context), selector: (context, s) => s.screenSaverVideoPlayback, onSelection: (v) => settings.screenSaverVideoPlayback = v, - tileTitle: context.l10n.settingsSlideshowVideoPlaybackTile, - dialogTitle: context.l10n.settingsSlideshowVideoPlaybackTitle, + tileTitle: l10n.settingsSlideshowVideoPlaybackTile, + dialogTitle: l10n.settingsSlideshowVideoPlaybackTitle, + ), + Selector>( + selector: (context, s) => s.screenSaverCollectionFilters, + builder: (context, filters, child) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final hasSubtitle = filters.isEmpty; + + // size and padding to match `ListTile` + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: (hasSubtitle ? 72.0 : 56.0) + theme.visualDensity.baseSizeAdjustment.dy, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.collectionPageTitle, + style: textTheme.subtitle1!, + ), + if (hasSubtitle) + Text( + l10n.drawerCollectionAll, + style: textTheme.bodyText2!.copyWith(color: textTheme.caption!.color), + ), + ], + ), + const Spacer(), + IconButton( + onPressed: () async { + final selection = await IntentService.pickCollectionFilters(filters); + if (selection != null) { + settings.screenSaverCollectionFilters = selection; + } + }, + icon: const Icon(AIcons.edit), + ), + ], + ), + ), + if (filters.isNotEmpty) FilterBar(filters: filters), + ], + ), + ), + ); + }, ), ], ), diff --git a/lib/widgets/viewer/screen_saver_page.dart b/lib/widgets/viewer/screen_saver_page.dart index cdae83431..32a4cb895 100644 --- a/lib/widgets/viewer/screen_saver_page.dart +++ b/lib/widgets/viewer/screen_saver_page.dart @@ -105,7 +105,7 @@ class _ScreenSaverPageState extends State { final originalCollection = CollectionLens( source: source, - // TODO TLAD [screensaver] custom filters + filters: settings.screenSaverCollectionFilters, ); var entries = originalCollection.sortedEntries; if (settings.screenSaverVideoPlayback == SlideshowVideoPlayback.skip) { diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index e08ed8d56..b13e1e5bc 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -28,7 +28,7 @@ Future configureAndLaunch() async { // navigation ..keepScreenOn = KeepScreenOn.always ..homePage = HomePageSetting.collection - ..showBottomNavigationBar = true + ..enableBottomNavigationBar = true // collection ..collectionSectionFactor = EntryGroupFactor.month ..collectionSortFactor = EntrySortFactor.date diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index 168bb3cc9..4901d9df8 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -27,7 +27,7 @@ Future configureAndLaunch() async { // navigation ..keepScreenOn = KeepScreenOn.always ..homePage = HomePageSetting.collection - ..showBottomNavigationBar = true + ..enableBottomNavigationBar = true // collection ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions // viewer