#174 screen saver filter pick
This commit is contained in:
parent
59a8dbe311
commit
c418a9c144
46 changed files with 490 additions and 277 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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<String, Any?> {
|
||||
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<String>? {
|
||||
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<List<String>>("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<List<String>>("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<MainActivity>()
|
||||
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<Int, PendingStorageAccessResultHandler>()
|
||||
|
||||
var pendingScopedStoragePermissionCompleter: CompletableFuture<Boolean>? = null
|
||||
|
||||
var pendingCollectionFilterPickHandler: ((filters: List<String>?) -> 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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<String, Any?> {
|
||||
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<WallpaperActivity>()
|
||||
const val VIEWER_CHANNEL = "deckers.thibault/aves/viewer"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<StorageAccessStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/storage_access_stream"
|
||||
private val LOG_TAG = LogUtils.createTag<ActivityResultStreamHandler>()
|
||||
const val CHANNEL = "deckers.thibault/aves/activity_result_stream"
|
||||
private const val BUFFER_SIZE = 2 shl 17 // 256kB
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<CollectionFilter> get screenSaverCollectionFilters => (getStringList(screenSaverCollectionFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
|
||||
|
||||
set screenSaverCollectionFilters(Set<CollectionFilter> 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<String>());
|
||||
} else {
|
||||
|
|
|
@ -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<bool> 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<bool> 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<int> getRecommendedTimeToRead(int originalTimeoutMillis) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||
'originalTimeoutMillis': originalTimeoutMillis,
|
||||
'content': ['icons', 'text']
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ class AccessibilityService {
|
|||
|
||||
static Future<int> getRecommendedTimeToTakeAction(int originalTimeoutMillis) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||
'originalTimeoutMillis': originalTimeoutMillis,
|
||||
'content': ['controls', 'icons', 'text']
|
||||
});
|
||||
|
|
|
@ -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<void> registerCallback() async {
|
||||
try {
|
||||
await platform.invokeMethod('registerCallback', <String, dynamic>{
|
||||
await _platform.invokeMethod('registerCallback', <String, dynamic>{
|
||||
'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(),
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -27,7 +27,7 @@ class AnalysisService {
|
|||
|
||||
static Future<void> startService({required bool force, List<int>? entryIds}) async {
|
||||
try {
|
||||
await platform.invokeMethod('startService', <String, dynamic>{
|
||||
await _platform.invokeMethod('startService', <String, dynamic>{
|
||||
'entryIds': entryIds,
|
||||
'force': force,
|
||||
});
|
||||
|
|
|
@ -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<Set<Package>> getPackages() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getPackages');
|
||||
final result = await _platform.invokeMethod('getPackages');
|
||||
final packages = (result as List).cast<Map>().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<Uint8List> getAppIcon(String packageName, double size) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getAppIcon', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getAppIcon', <String, dynamic>{
|
||||
'packageName': packageName,
|
||||
'sizeDip': size,
|
||||
});
|
||||
|
@ -78,7 +78,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
@override
|
||||
Future<String?> 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<bool> copyToClipboard(String uri, String? label) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('copyToClipboard', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('copyToClipboard', <String, dynamic>{
|
||||
'uri': uri,
|
||||
'label': label,
|
||||
});
|
||||
|
@ -102,7 +102,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
@override
|
||||
Future<bool> edit(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('edit', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('edit', <String, dynamic>{
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
|
@ -116,7 +116,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
@override
|
||||
Future<bool> open(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('open', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('open', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('openMap', <String, dynamic>{
|
||||
'geoUri': geoUri,
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
|
@ -147,7 +147,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
@override
|
||||
Future<bool> setAs(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('setAs', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('setAs', <String, dynamic>{
|
||||
'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<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
|
||||
try {
|
||||
final result = await platform.invokeMethod('share', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('share', <String, dynamic>{
|
||||
'urisByMimeType': urisByMimeType,
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
|
@ -177,7 +177,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
@override
|
||||
Future<bool> shareSingle(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('share', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('share', <String, dynamic>{
|
||||
'urisByMimeType': {
|
||||
mimeType: [uri]
|
||||
},
|
||||
|
@ -207,7 +207,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
);
|
||||
}
|
||||
try {
|
||||
await platform.invokeMethod('pinShortcut', <String, dynamic>{
|
||||
await _platform.invokeMethod('pinShortcut', <String, dynamic>{
|
||||
'label': label,
|
||||
'iconBytes': iconBytes,
|
||||
'filters': filters?.map((filter) => filter.toJson()).toList(),
|
||||
|
|
|
@ -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<void> 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<void> 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<void> 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<void> 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<void> 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<Map> 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<List<Map>> getCodecs() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getCodecs');
|
||||
final result = await _platform.invokeMethod('getCodecs');
|
||||
if (result != null) return (result as List).cast<Map>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
|
@ -68,7 +68,7 @@ class AndroidDebugService {
|
|||
|
||||
static Future<Map> 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<Map> getBitmapFactoryInfo(AvesEntry entry) async {
|
||||
try {
|
||||
// returns map with all data available when decoding image bounds with `BitmapFactory`
|
||||
final result = await platform.invokeMethod('getBitmapFactoryInfo', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getBitmapFactoryInfo', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
});
|
||||
if (result != null) return result as Map;
|
||||
|
@ -92,7 +92,7 @@ class AndroidDebugService {
|
|||
static Future<Map> getContentResolverMetadata(AvesEntry entry) async {
|
||||
try {
|
||||
// returns map with all data available from the content resolver
|
||||
final result = await platform.invokeMethod('getContentResolverMetadata', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getContentResolverMetadata', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ class AndroidDebugService {
|
|||
static Future<Map> getExifInterfaceMetadata(AvesEntry entry) async {
|
||||
try {
|
||||
// returns map with all data available from the `ExifInterface` library
|
||||
final result = await platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -121,7 +121,7 @@ class AndroidDebugService {
|
|||
static Future<Map> getMediaMetadataRetrieverMetadata(AvesEntry entry) async {
|
||||
try {
|
||||
// returns map with all data available from `MediaMetadataRetriever`
|
||||
final result = await platform.invokeMethod('getMediaMetadataRetrieverMetadata', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getMediaMetadataRetrieverMetadata', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
});
|
||||
if (result != null) return result as Map;
|
||||
|
@ -134,7 +134,7 @@ class AndroidDebugService {
|
|||
static Future<Map> 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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getMetadataExtractorSummary', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -149,7 +149,7 @@ class AndroidDebugService {
|
|||
static Future<Map> getPixyMetadata(AvesEntry entry) async {
|
||||
try {
|
||||
// returns map with all data available from the `PixyMeta` library
|
||||
final result = await platform.invokeMethod('getPixyMetadata', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getPixyMetadata', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getTiffStructure', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
});
|
||||
if (result != null) return result as Map;
|
||||
|
|
|
@ -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<Map<String, dynamic>> getCapabilities() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getCapabilities');
|
||||
final result = await _platform.invokeMethod('getCapabilities');
|
||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
|
@ -32,7 +32,7 @@ class PlatformDeviceService implements DeviceService {
|
|||
@override
|
||||
Future<String?> 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<List<Locale>> getLocales() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getLocales');
|
||||
final result = await _platform.invokeMethod('getLocales');
|
||||
if (result != null) {
|
||||
return (result as List).cast<Map>().map((tags) {
|
||||
final language = tags['language'] as String?;
|
||||
|
@ -62,7 +62,7 @@ class PlatformDeviceService implements DeviceService {
|
|||
@override
|
||||
Future<int> 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<bool> 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);
|
||||
|
|
|
@ -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<List<Address>> getAddress(LatLng coordinates, Locale locale) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getAddress', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getAddress', <String, dynamic>{
|
||||
'latitude': coordinates.latitude,
|
||||
'longitude': coordinates.longitude,
|
||||
'locale': locale.toString(),
|
||||
|
|
|
@ -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<void> registerCallback() async {
|
||||
try {
|
||||
await platform.invokeMethod('registerCallback', <String, dynamic>{
|
||||
await _platform.invokeMethod('registerCallback', <String, dynamic>{
|
||||
'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(),
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
|
68
lib/services/intent_service.dart
Normal file
68
lib/services/intent_service.dart
Normal file
|
@ -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<Map<String, dynamic>> 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<String, dynamic>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static Future<void> submitPickedItems(List<String> uris) async {
|
||||
try {
|
||||
await _platform.invokeMethod('submitPickedItems', <String, dynamic>{
|
||||
'uris': uris,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> submitPickedCollectionFilters(Set<CollectionFilter>? filters) async {
|
||||
try {
|
||||
await _platform.invokeMethod('submitPickedCollectionFilters', <String, dynamic>{
|
||||
'filters': filters?.map((filter) => filter.toJson()).toList(),
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Set<CollectionFilter>?> pickCollectionFilters(Set<CollectionFilter>? initialFilters) async {
|
||||
try {
|
||||
final completer = Completer<Set<CollectionFilter>?>();
|
||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'pickCollectionFilters',
|
||||
'initialFilters': initialFilters?.map((filter) => filter.toJson()).toList(),
|
||||
}).listen(
|
||||
(data) {
|
||||
final result = (data as List?)?.cast<String>().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;
|
||||
}
|
||||
}
|
|
@ -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<List<Uint8List>> getExifThumbnails(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getExifThumbnails', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -35,7 +35,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
|||
@override
|
||||
Future<Map> extractMotionPhotoVideo(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('extractMotionPhotoVideo', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('extractMotionPhotoVideo', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -51,7 +51,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
|||
@override
|
||||
Future<Map> extractVideoEmbeddedPicture(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
'displayName': '${entry.bestTitle} • Cover',
|
||||
});
|
||||
|
@ -65,7 +65,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
|||
@override
|
||||
Future<Map> extractXmpDataProp(AvesEntry entry, String? propPath, String? propMimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('extractXmpDataProp', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('extractXmpDataProp', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
|
|
@ -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<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
||||
return {
|
||||
|
@ -136,7 +136,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
@override
|
||||
Future<AvesEntry?> getEntry(String uri, String? mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getEntry', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getEntry', <String, dynamic>{
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
}) as Map;
|
||||
|
@ -181,7 +181,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
final completer = Completer<Uint8List>.sync();
|
||||
final sink = OutputBuffer();
|
||||
var bytesReceived = 0;
|
||||
_byteStreamChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
_byteStream.receiveBroadcastStream(<String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getRegion', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getThumbnail', <String, dynamic>{
|
||||
'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<void> 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<void> cancelFileOp(String opId) async {
|
||||
try {
|
||||
await platform.invokeMethod('cancelFileOp', <String, dynamic>{
|
||||
await _platform.invokeMethod('cancelFileOp', <String, dynamic>{
|
||||
'opId': opId,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -333,7 +333,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required Iterable<AvesEntry> entries,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel
|
||||
return _opStream
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'delete',
|
||||
'id': opId,
|
||||
|
@ -355,7 +355,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required NameConflictStrategy nameConflictStrategy,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel
|
||||
return _opStream
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'move',
|
||||
'id': opId,
|
||||
|
@ -379,7 +379,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required NameConflictStrategy nameConflictStrategy,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel
|
||||
return _opStream
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'export',
|
||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||
|
@ -403,7 +403,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required Map<AvesEntry, String> entriesToNewName,
|
||||
}) {
|
||||
try {
|
||||
return _opStreamChannel
|
||||
return _opStream
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'rename',
|
||||
'id': opId,
|
||||
|
@ -427,7 +427,7 @@ class PlatformMediaFileService implements MediaFileService {
|
|||
required NameConflictStrategy nameConflictStrategy,
|
||||
}) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('captureFrame', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('captureFrame', <String, dynamic>{
|
||||
'uri': entry.uri,
|
||||
'desiredName': desiredName,
|
||||
'exif': exif,
|
||||
|
|
|
@ -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<List<int>> checkObsoleteContentIds(List<int?> knownContentIds) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('checkObsoleteContentIds', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('checkObsoleteContentIds', <String, dynamic>{
|
||||
'knownContentIds': knownContentIds,
|
||||
});
|
||||
return (result as List).cast<int>();
|
||||
|
@ -37,7 +37,7 @@ class PlatformMediaStoreService implements MediaStoreService {
|
|||
@override
|
||||
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('checkObsoletePaths', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('checkObsoletePaths', <String, dynamic>{
|
||||
'knownPathById': knownPathById,
|
||||
});
|
||||
return (result as List).cast<int>();
|
||||
|
@ -50,7 +50,7 @@ class PlatformMediaStoreService implements MediaStoreService {
|
|||
@override
|
||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
|
||||
try {
|
||||
return _streamChannel
|
||||
return _stream
|
||||
.receiveBroadcastStream(<String, dynamic>{
|
||||
'knownEntries': knownEntries,
|
||||
'directory': directory,
|
||||
|
@ -67,7 +67,7 @@ class PlatformMediaStoreService implements MediaStoreService {
|
|||
@override
|
||||
Future<Uri?> scanFile(String path, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('scanFile', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('scanFile', <String, dynamic>{
|
||||
'path': path,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
|
|
|
@ -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<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
||||
return {
|
||||
|
@ -44,7 +44,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
|||
Future<Map<String, dynamic>> rotate(AvesEntry entry, {required bool clockwise}) async {
|
||||
try {
|
||||
// returns map with: 'rotationDegrees' 'isFlipped'
|
||||
final result = await platform.invokeMethod('rotate', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('rotate', <String, dynamic>{
|
||||
'entry': _toPlatformEntryMap(entry),
|
||||
'clockwise': clockwise,
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
|||
Future<Map<String, dynamic>> flip(AvesEntry entry) async {
|
||||
try {
|
||||
// returns map with: 'rotationDegrees' 'isFlipped'
|
||||
final result = await platform.invokeMethod('flip', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('flip', <String, dynamic>{
|
||||
'entry': _toPlatformEntryMap(entry),
|
||||
});
|
||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||
|
@ -76,7 +76,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
|||
@override
|
||||
Future<Map<String, dynamic>> editExifDate(AvesEntry entry, DateModifier modifier) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('editDate', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('editDate', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('editMetadata', <String, dynamic>{
|
||||
'entry': _toPlatformEntryMap(entry),
|
||||
'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)),
|
||||
'autoCorrectTrailerOffset': autoCorrectTrailerOffset,
|
||||
|
@ -115,7 +115,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
|||
@override
|
||||
Future<Map<String, dynamic>> removeTrailerVideo(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
|
||||
'entry': _toPlatformEntryMap(entry),
|
||||
});
|
||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||
|
@ -130,7 +130,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
|||
@override
|
||||
Future<Map<String, dynamic>> removeTypes(AvesEntry entry, Set<MetadataType> types) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('removeTypes', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('removeTypes', <String, dynamic>{
|
||||
'entry': _toPlatformEntryMap(entry),
|
||||
'types': types.map(_toPlatformMetadataType).toList(),
|
||||
});
|
||||
|
|
|
@ -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<Map> getAllMetadata(AvesEntry entry) async {
|
||||
if (entry.isSvg) return {};
|
||||
|
||||
try {
|
||||
final result = await platform.invokeMethod('getAllMetadata', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getAllMetadata', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getOverlayMetadata', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -123,7 +123,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
@override
|
||||
Future<GeoTiffInfo?> getGeoTiffInfo(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getGeoTiffInfo', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getGeoTiffInfo', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -140,7 +140,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
@override
|
||||
Future<MultiPageInfo?> getMultiPageInfo(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getMultiPageInfo', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getMultiPageInfo', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getPanoramaInfo', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
@ -184,7 +184,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
@override
|
||||
Future<List<Map<String, dynamic>>?> getIptc(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getIptc', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getIptc', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
});
|
||||
|
@ -200,7 +200,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
@override
|
||||
Future<AvesXmp?> getXmp(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getXmp', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getXmp', <String, dynamic>{
|
||||
'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', <String, dynamic>{
|
||||
exists = await _platform.invokeMethod('hasContentResolverProp', <String, dynamic>{
|
||||
'prop': prop,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -236,7 +236,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
@override
|
||||
Future<String?> getContentResolverProp(AvesEntry entry, String prop) async {
|
||||
try {
|
||||
return await platform.invokeMethod('getContentResolverProp', <String, dynamic>{
|
||||
return await _platform.invokeMethod('getContentResolverProp', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'prop': prop,
|
||||
|
@ -252,7 +252,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
|||
@override
|
||||
Future<DateTime?> getDate(AvesEntry entry, MetadataField field) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getDate', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getDate', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
|
|
|
@ -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<Set<StorageVolume>> getStorageVolumes() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getStorageVolumes');
|
||||
final result = await _platform.invokeMethod('getStorageVolumes');
|
||||
return (result as List).cast<Map>().map(StorageVolume.fromMap).toSet();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
|
@ -57,7 +57,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<int?> getFreeSpace(StorageVolume volume) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getFreeSpace', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getFreeSpace', <String, dynamic>{
|
||||
'path': volume.path,
|
||||
});
|
||||
return result as int?;
|
||||
|
@ -70,7 +70,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<List<String>> getGrantedDirectories() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getGrantedDirectories');
|
||||
final result = await _platform.invokeMethod('getGrantedDirectories');
|
||||
return (result as List).cast<String>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
|
@ -81,7 +81,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getInaccessibleDirectories', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('getInaccessibleDirectories', <String, dynamic>{
|
||||
'dirPaths': dirPaths.toList(),
|
||||
});
|
||||
if (result != null) {
|
||||
|
@ -96,7 +96,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories() async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('getRestrictedDirectories');
|
||||
final result = await _platform.invokeMethod('getRestrictedDirectories');
|
||||
if (result != null) {
|
||||
return (result as List).cast<Map>().map(VolumeRelativeDirectory.fromMap).toSet();
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<void> revokeDirectoryAccess(String path) async {
|
||||
try {
|
||||
await platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
|
||||
await _platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
|
||||
'path': path,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -122,7 +122,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
|
||||
'dirPaths': dirPaths.toList(),
|
||||
});
|
||||
if (result != null) return result as int;
|
||||
|
@ -135,7 +135,7 @@ class PlatformStorageService implements StorageService {
|
|||
@override
|
||||
Future<bool> 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<bool> canInsertMedia(Set<VolumeRelativeDirectory> directories) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('canInsertMedia', <String, dynamic>{
|
||||
final result = await _platform.invokeMethod('canInsertMedia', <String, dynamic>{
|
||||
'directories': directories.map((v) => v.toMap()).toList(),
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
|
@ -161,7 +161,7 @@ class PlatformStorageService implements StorageService {
|
|||
Future<bool> requestDirectoryAccess(String path) async {
|
||||
try {
|
||||
final completer = Completer<bool>();
|
||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'requestDirectoryAccess',
|
||||
'path': path,
|
||||
}).listen(
|
||||
|
@ -185,7 +185,7 @@ class PlatformStorageService implements StorageService {
|
|||
Future<bool> requestMediaFileAccess(List<String> uris, List<String> mimeTypes) async {
|
||||
try {
|
||||
final completer = Completer<bool>();
|
||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'requestMediaFileAccess',
|
||||
'uris': uris,
|
||||
'mimeTypes': mimeTypes,
|
||||
|
@ -216,7 +216,7 @@ class PlatformStorageService implements StorageService {
|
|||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
|
||||
try {
|
||||
final completer = Completer<bool?>();
|
||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'createFile',
|
||||
'name': name,
|
||||
'mimeType': mimeType,
|
||||
|
@ -242,7 +242,7 @@ class PlatformStorageService implements StorageService {
|
|||
try {
|
||||
final completer = Completer<Uint8List>.sync();
|
||||
final sink = OutputBuffer();
|
||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'openFile',
|
||||
'mimeType': mimeType,
|
||||
}).listen(
|
||||
|
|
|
@ -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<Map<String, dynamic>> 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<String, dynamic>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static Future<void> pick(List<String> uris) async {
|
||||
try {
|
||||
await platform.invokeMethod('pick', <String, dynamic>{
|
||||
'uris': uris,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<bool> set(Uint8List bytes, WallpaperTarget target) async {
|
||||
try {
|
||||
await platform.invokeMethod('setWallpaper', <String, dynamic>{
|
||||
await _platform.invokeMethod('setWallpaper', <String, dynamic>{
|
||||
'bytes': bytes,
|
||||
'home': {WallpaperTarget.home, WallpaperTarget.homeLock}.contains(target),
|
||||
'lock': {WallpaperTarget.lock, WallpaperTarget.homeLock}.contains(target),
|
||||
|
|
|
@ -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<bool> 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<void> keepScreenOn(bool on) async {
|
||||
try {
|
||||
await platform.invokeMethod('keepScreenOn', <String, dynamic>{
|
||||
await _platform.invokeMethod('keepScreenOn', <String, dynamic>{
|
||||
'on': on,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -44,7 +44,7 @@ class PlatformWindowService implements WindowService {
|
|||
@override
|
||||
Future<bool> 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', <String, dynamic>{
|
||||
await _platform.invokeMethod('requestOrientation', <String, dynamic>{
|
||||
'orientation': orientationCode,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
@ -82,7 +82,7 @@ class PlatformWindowService implements WindowService {
|
|||
@override
|
||||
Future<bool> 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<void> setCutoutMode(bool use) async {
|
||||
try {
|
||||
await platform.invokeMethod('setCutoutMode', <String, dynamic>{
|
||||
await _platform.invokeMethod('setCutoutMode', <String, dynamic>{
|
||||
'use': use,
|
||||
});
|
||||
} on PlatformException catch (e, stack) {
|
||||
|
|
|
@ -83,7 +83,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
|||
// the list itself needs to be reassigned
|
||||
List<NavigatorObserver> _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<AvesApp> with WidgetsBindingObserver {
|
|||
case AppMode.pickMultipleMediaExternal:
|
||||
_saveTopEntries();
|
||||
break;
|
||||
case AppMode.pickCollectionFiltersExternal:
|
||||
case AppMode.pickMediaInternal:
|
||||
case AppMode.pickFilterInternal:
|
||||
case AppMode.screenSaver:
|
||||
|
|
|
@ -138,7 +138,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> 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<CollectionAppBar> with SingleTickerPr
|
|||
);
|
||||
}
|
||||
return InteractiveAppBarTitle(
|
||||
onTap: appMode.canSearch ? _goToSearch : null,
|
||||
onTap: appMode.canNavigate ? _goToSearch : null,
|
||||
child: title,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -380,8 +380,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
|
|||
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||
builder: (context, mqPaddingBottom, child) {
|
||||
return Selector<Settings, bool>(
|
||||
selector: (context, s) => s.showBottomNavigationBar,
|
||||
builder: (context, showBottomNavigationBar, child) {
|
||||
selector: (context, s) => s.enableBottomNavigationBar,
|
||||
builder: (context, enableBottomNavigationBar, child) {
|
||||
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||
final navBarHeight = showBottomNavigationBar ? AppBottomNavBar.height : 0;
|
||||
return Selector<SectionedListLayout<AvesEntry>, List<SectionLayout>>(
|
||||
selector: (context, layout) => layout.sectionLayouts,
|
||||
|
|
|
@ -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<CollectionPage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||
final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
|
||||
return MediaQueryDataProvider(
|
||||
child: SelectionProvider<AvesEntry>(
|
||||
|
@ -87,8 +86,10 @@ class _CollectionPageState extends State<CollectionPage> {
|
|||
selector: (context, selection) => selection.selectedItems.isNotEmpty,
|
||||
builder: (context, hasSelection, child) {
|
||||
return Selector<Settings, bool>(
|
||||
selector: (context, s) => s.showBottomNavigationBar,
|
||||
builder: (context, showBottomNavigationBar, child) {
|
||||
selector: (context, s) => s.enableBottomNavigationBar,
|
||||
builder: (context, enableBottomNavigationBar, child) {
|
||||
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||
return NotificationListener<DraggableScrollBarNotification>(
|
||||
onNotification: (notification) {
|
||||
_draggableScrollBarEventStreamController.add(notification.event);
|
||||
|
@ -126,25 +127,7 @@ class _CollectionPageState extends State<CollectionPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: appMode == AppMode.pickMultipleMediaExternal && hasSelection
|
||||
? TooltipTheme(
|
||||
data: TooltipTheme.of(context).copyWith(
|
||||
preferBelow: false,
|
||||
),
|
||||
child: FloatingActionButton(
|
||||
tooltip: context.l10n.collectionPickPageTitle,
|
||||
onPressed: () {
|
||||
final items = context.read<Selection<AvesEntry>>().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<CollectionPage> {
|
|||
);
|
||||
}
|
||||
|
||||
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<ValueNotifier<AppMode>>().value;
|
||||
switch (appMode) {
|
||||
case AppMode.pickMultipleMediaExternal:
|
||||
return hasSelection
|
||||
? fab(
|
||||
tooltip: context.l10n.collectionPickPageTitle,
|
||||
onPressed: () {
|
||||
final items = context.read<Selection<AvesEntry>>().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<void> _checkInitHighlight() async {
|
||||
final highlightTest = widget.highlightTest;
|
||||
if (highlightTest == null) return;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -14,7 +14,7 @@ class FilterBar extends StatefulWidget {
|
|||
FilterBar({
|
||||
super.key,
|
||||
required Set<CollectionFilter> filters,
|
||||
required this.removable,
|
||||
this.removable = false,
|
||||
this.onTap,
|
||||
}) : filters = List<CollectionFilter>.from(filters)..sort();
|
||||
|
||||
|
|
|
@ -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<Selection<AvesEntry>>();
|
||||
|
@ -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:
|
||||
|
|
|
@ -78,7 +78,7 @@ class AvesFilterChip extends StatefulWidget {
|
|||
});
|
||||
|
||||
static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
|
||||
if (context.read<ValueNotifier<AppMode>>().value == AppMode.main) {
|
||||
if (context.read<ValueNotifier<AppMode>>().value.canNavigate) {
|
||||
final actions = [
|
||||
if (filter is AlbumFilter) ChipAction.goToAlbumPage,
|
||||
if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage,
|
||||
|
|
|
@ -61,7 +61,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> 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
|
||||
|
|
|
@ -77,7 +77,7 @@ class _FilterGridAppBarState<T extends CollectionFilter> extends State<FilterGri
|
|||
return AvesAppBar(
|
||||
contentHeight: kToolbarHeight,
|
||||
leading: _buildAppBarLeading(
|
||||
hasDrawer: appMode.hasDrawer,
|
||||
hasDrawer: appMode.canNavigate,
|
||||
isSelecting: isSelecting,
|
||||
),
|
||||
title: _buildAppBarTitle(isSelecting),
|
||||
|
@ -127,7 +127,7 @@ class _FilterGridAppBarState<T extends CollectionFilter> extends State<FilterGri
|
|||
} else {
|
||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||
return InteractiveAppBarTitle(
|
||||
onTap: appMode.canSearch ? _goToSearch : null,
|
||||
onTap: appMode.canNavigate ? _goToSearch : null,
|
||||
child: SourceStateAwareAppBarTitle(
|
||||
title: Text(widget.title),
|
||||
source: source,
|
||||
|
|
|
@ -74,8 +74,10 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MediaQueryDataProvider(
|
||||
child: Selector<Settings, bool>(
|
||||
selector: (context, s) => s.showBottomNavigationBar,
|
||||
builder: (context, showBottomNavigationBar, child) {
|
||||
selector: (context, s) => s.enableBottomNavigationBar,
|
||||
builder: (context, enableBottomNavigationBar, child) {
|
||||
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||
return NotificationListener<DraggableScrollBarNotification>(
|
||||
onNotification: (notification) {
|
||||
_draggableScrollBarEventStreamController.add(notification.event);
|
||||
|
@ -524,8 +526,10 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
|||
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||
builder: (context, mqPaddingBottom, child) {
|
||||
return Selector<Settings, bool>(
|
||||
selector: (context, s) => s.showBottomNavigationBar,
|
||||
builder: (context, showBottomNavigationBar, child) {
|
||||
selector: (context, s) => s.enableBottomNavigationBar,
|
||||
builder: (context, enableBottomNavigationBar, child) {
|
||||
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||
final navBarHeight = showBottomNavigationBar ? AppBottomNavBar.height : 0;
|
||||
return DraggableScrollbar(
|
||||
backgroundColor: Colors.white,
|
||||
|
|
|
@ -50,6 +50,7 @@ class _InteractiveFilterTileState<T extends CollectionFilter> extends State<Inte
|
|||
final appMode = context.read<ValueNotifier<AppMode>>().value;
|
||||
switch (appMode) {
|
||||
case AppMode.main:
|
||||
case AppMode.pickCollectionFiltersExternal:
|
||||
case AppMode.pickSingleMediaExternal:
|
||||
case AppMode.pickMultipleMediaExternal:
|
||||
final selection = context.read<Selection<FilterGridItem<T>>>();
|
||||
|
|
|
@ -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<HomePage> {
|
||||
AvesEntry? _viewerEntry;
|
||||
String? _shortcutRouteName, _shortcutSearchQuery;
|
||||
Set<String>? _shortcutFilters;
|
||||
String? _initialRouteName, _initialSearchQuery;
|
||||
Set<CollectionFilter>? _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<HomePage> {
|
|||
}
|
||||
|
||||
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<HomePage> {
|
|||
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<HomePage> {
|
|||
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<HomePage> {
|
|||
// 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<String>().toSet() : null;
|
||||
}
|
||||
final extraFilters = intentData['filters'];
|
||||
_initialFilters = extraFilters != null ? (extraFilters as List).cast<String>().map(CollectionFilter.fromJson).whereNotNull().toSet() : null;
|
||||
}
|
||||
context.read<ValueNotifier<AppMode>>().value = appMode;
|
||||
unawaited(reportService.setCustomKey('app_mode', appMode.toString()));
|
||||
|
@ -150,6 +154,7 @@ class _HomePageState extends State<HomePage> {
|
|||
|
||||
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<HomePage> {
|
|||
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<CollectionSource>();
|
||||
|
@ -310,7 +315,7 @@ class _HomePageState extends State<HomePage> {
|
|||
searchFieldLabel: context.l10n.searchCollectionFieldHint,
|
||||
source: source,
|
||||
canPop: false,
|
||||
initialQuery: _shortcutSearchQuery,
|
||||
initialQuery: _initialSearchQuery,
|
||||
),
|
||||
);
|
||||
case CollectionPage.routeName:
|
||||
|
|
|
@ -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<DraggableScrollBarEvent> events;
|
||||
|
@ -163,8 +165,10 @@ class NavBarPaddingSliver extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Selector<Settings, bool>(
|
||||
selector: (context, s) => s.showBottomNavigationBar,
|
||||
builder: (context, showBottomNavigationBar, child) {
|
||||
selector: (context, s) => s.enableBottomNavigationBar,
|
||||
builder: (context, enableBottomNavigationBar, child) {
|
||||
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||
return SizedBox(height: showBottomNavigationBar ? AppBottomNavBar.height : 0);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<SlideshowInterval>(
|
||||
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<SlideshowVideoPlayback>(
|
||||
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<Settings, Set<CollectionFilter>>(
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -105,7 +105,7 @@ class _ScreenSaverPageState extends State<ScreenSaverPage> {
|
|||
|
||||
final originalCollection = CollectionLens(
|
||||
source: source,
|
||||
// TODO TLAD [screensaver] custom filters
|
||||
filters: settings.screenSaverCollectionFilters,
|
||||
);
|
||||
var entries = originalCollection.sortedEntries;
|
||||
if (settings.screenSaverVideoPlayback == SlideshowVideoPlayback.skip) {
|
||||
|
|
|
@ -28,7 +28,7 @@ Future<void> configureAndLaunch() async {
|
|||
// navigation
|
||||
..keepScreenOn = KeepScreenOn.always
|
||||
..homePage = HomePageSetting.collection
|
||||
..showBottomNavigationBar = true
|
||||
..enableBottomNavigationBar = true
|
||||
// collection
|
||||
..collectionSectionFactor = EntryGroupFactor.month
|
||||
..collectionSortFactor = EntrySortFactor.date
|
||||
|
|
|
@ -27,7 +27,7 @@ Future<void> configureAndLaunch() async {
|
|||
// navigation
|
||||
..keepScreenOn = KeepScreenOn.always
|
||||
..homePage = HomePageSetting.collection
|
||||
..showBottomNavigationBar = true
|
||||
..enableBottomNavigationBar = true
|
||||
// collection
|
||||
..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions
|
||||
// viewer
|
||||
|
|
Loading…
Reference in a new issue