#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
|
- Search: `on this day` filter
|
||||||
- Stats: histogram and date filters
|
- Stats: histogram and date filters
|
||||||
|
- Screen saver
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(this, args) }
|
StreamsChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandlerFactory { args -> MediaStoreStreamHandler(this, args) }
|
||||||
// - need Activity
|
// - need Activity
|
||||||
StreamsChannel(messenger, ImageOpStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageOpStreamHandler(this, args) }
|
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
|
// change monitoring: platform -> dart
|
||||||
mediaStoreChangeStreamHandler = MediaStoreChangeStreamHandler(this).apply {
|
mediaStoreChangeStreamHandler = MediaStoreChangeStreamHandler(this).apply {
|
||||||
|
@ -99,15 +99,16 @@ open class MainActivity : FlutterActivity() {
|
||||||
intentStreamHandler = IntentStreamHandler().apply {
|
intentStreamHandler = IntentStreamHandler().apply {
|
||||||
EventChannel(messenger, IntentStreamHandler.CHANNEL).setStreamHandler(this)
|
EventChannel(messenger, IntentStreamHandler.CHANNEL).setStreamHandler(this)
|
||||||
}
|
}
|
||||||
// detail fetch: dart -> platform
|
// intent detail & result: dart -> platform
|
||||||
intentDataMap = extractIntentData(intent)
|
intentDataMap = extractIntentData(intent)
|
||||||
MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler { call, result ->
|
MethodChannel(messenger, INTENT_CHANNEL).setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getIntentData" -> {
|
"getIntentData" -> {
|
||||||
result.success(intentDataMap)
|
result.success(intentDataMap)
|
||||||
intentDataMap.clear()
|
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?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
DOCUMENT_TREE_ACCESS_REQUEST -> onDocumentTreeAccessResult(data, resultCode, requestCode)
|
DOCUMENT_TREE_ACCESS_REQUEST -> onDocumentTreeAccessResult(requestCode, resultCode, data)
|
||||||
DELETE_SINGLE_PERMISSION_REQUEST,
|
DELETE_SINGLE_PERMISSION_REQUEST,
|
||||||
MEDIA_WRITE_BULK_PERMISSION_REQUEST -> onScopedStoragePermissionResult(resultCode)
|
MEDIA_WRITE_BULK_PERMISSION_REQUEST -> onScopedStoragePermissionResult(resultCode)
|
||||||
CREATE_FILE_REQUEST,
|
CREATE_FILE_REQUEST,
|
||||||
OPEN_FILE_REQUEST -> onStorageAccessResult(requestCode, data?.data)
|
OPEN_FILE_REQUEST -> onStorageAccessResult(requestCode, data?.data)
|
||||||
|
PICK_COLLECTION_FILTERS_REQUEST -> onCollectionFiltersPickResult(resultCode, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WrongConstant", "ObsoleteSdkInt")
|
private fun onCollectionFiltersPickResult(resultCode: Int, intent: Intent?) {
|
||||||
private fun onDocumentTreeAccessResult(data: Intent?, resultCode: Int, requestCode: Int) {
|
val filters = if (resultCode == RESULT_OK) extractFiltersFromIntent(intent) else null
|
||||||
val treeUri = data?.data
|
pendingCollectionFilterPickHandler?.let { it(filters) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDocumentTreeAccessResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||||
|
val treeUri = intent?.data
|
||||||
if (resultCode != RESULT_OK || treeUri == null) {
|
if (resultCode != RESULT_OK || treeUri == null) {
|
||||||
onStorageAccessResult(requestCode, null)
|
onStorageAccessResult(requestCode, null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WrongConstant", "ObsoleteSdkInt")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
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) {
|
if (canPersist) {
|
||||||
// save access permissions across reboots
|
// save access permissions across reboots
|
||||||
val takeFlags = (data.flags
|
val takeFlags = (intent.flags
|
||||||
and (Intent.FLAG_GRANT_READ_URI_PERMISSION
|
and (Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
|
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
|
||||||
try {
|
try {
|
||||||
|
@ -206,15 +213,8 @@ open class MainActivity : FlutterActivity() {
|
||||||
open fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
|
open fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
Intent.ACTION_MAIN -> {
|
Intent.ACTION_MAIN -> {
|
||||||
intent.getStringExtra(SHORTCUT_KEY_PAGE)?.let { page ->
|
intent.getStringExtra(EXTRA_KEY_PAGE)?.let { page ->
|
||||||
var filters = intent.getStringArrayExtra(SHORTCUT_KEY_FILTERS_ARRAY)?.toList()
|
val filters = extractFiltersFromIntent(intent)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hashMapOf(
|
return hashMapOf(
|
||||||
INTENT_DATA_KEY_PAGE to page,
|
INTENT_DATA_KEY_PAGE to page,
|
||||||
INTENT_DATA_KEY_FILTERS to filters,
|
INTENT_DATA_KEY_FILTERS to filters,
|
||||||
|
@ -234,7 +234,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
}
|
}
|
||||||
Intent.ACTION_GET_CONTENT, Intent.ACTION_PICK -> {
|
Intent.ACTION_GET_CONTENT, Intent.ACTION_PICK -> {
|
||||||
return hashMapOf(
|
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_MIME_TYPE to intent.type,
|
||||||
INTENT_DATA_KEY_ALLOW_MULTIPLE to (intent.extras?.getBoolean(Intent.EXTRA_ALLOW_MULTIPLE) ?: false),
|
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_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 -> {
|
Intent.ACTION_RUN -> {
|
||||||
// flutter run
|
// flutter run
|
||||||
}
|
}
|
||||||
|
@ -260,7 +267,22 @@ open class MainActivity : FlutterActivity() {
|
||||||
return HashMap()
|
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")
|
val pickedUris = call.argument<List<String>>("uris")
|
||||||
if (pickedUris != null && pickedUris.isNotEmpty()) {
|
if (pickedUris != null && pickedUris.isNotEmpty()) {
|
||||||
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(context, Uri.parse(uriString)) }
|
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(context, Uri.parse(uriString)) }
|
||||||
|
@ -284,6 +306,19 @@ open class MainActivity : FlutterActivity() {
|
||||||
finish()
|
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)
|
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||||
private fun setupShortcuts() {
|
private fun setupShortcuts() {
|
||||||
// do not use 'route' as extra key, as the Flutter framework acts on it
|
// 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))
|
.setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_search else R.drawable.ic_shortcut_search))
|
||||||
.setIntent(
|
.setIntent(
|
||||||
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
|
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
|
||||||
.putExtra(SHORTCUT_KEY_PAGE, "/search")
|
.putExtra(EXTRA_KEY_PAGE, "/search")
|
||||||
)
|
)
|
||||||
.build()
|
.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))
|
.setIcon(IconCompat.createWithResource(this, if (supportAdaptiveIcon) R.mipmap.ic_shortcut_movie else R.drawable.ic_shortcut_movie))
|
||||||
.setIntent(
|
.setIntent(
|
||||||
Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java)
|
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/*\"}"))
|
.putExtra("filters", arrayOf("{\"type\":\"mime\",\"mime\":\"video/*\"}"))
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
@ -320,7 +355,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<MainActivity>()
|
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 EXTRA_STRING_ARRAY_SEPARATOR = "###"
|
||||||
const val DOCUMENT_TREE_ACCESS_REQUEST = 1
|
const val DOCUMENT_TREE_ACCESS_REQUEST = 1
|
||||||
const val OPEN_FROM_ANALYSIS_SERVICE = 2
|
const val OPEN_FROM_ANALYSIS_SERVICE = 2
|
||||||
|
@ -328,6 +363,7 @@ open class MainActivity : FlutterActivity() {
|
||||||
const val OPEN_FILE_REQUEST = 4
|
const val OPEN_FILE_REQUEST = 4
|
||||||
const val DELETE_SINGLE_PERMISSION_REQUEST = 5
|
const val DELETE_SINGLE_PERMISSION_REQUEST = 5
|
||||||
const val MEDIA_WRITE_BULK_PERMISSION_REQUEST = 6
|
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_ACTION = "action"
|
||||||
const val INTENT_DATA_KEY_FILTERS = "filters"
|
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_URI = "uri"
|
||||||
const val INTENT_DATA_KEY_QUERY = "query"
|
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 = "screen_saver"
|
||||||
const val INTENT_ACTION_SCREEN_SAVER_SETTINGS = "screen_saver_settings"
|
const val INTENT_ACTION_SCREEN_SAVER_SETTINGS = "screen_saver_settings"
|
||||||
const val INTENT_ACTION_SEARCH = "search"
|
const val INTENT_ACTION_SEARCH = "search"
|
||||||
const val INTENT_ACTION_SET_WALLPAPER = "set_wallpaper"
|
const val INTENT_ACTION_SET_WALLPAPER = "set_wallpaper"
|
||||||
const val INTENT_ACTION_VIEW = "view"
|
const val INTENT_ACTION_VIEW = "view"
|
||||||
|
|
||||||
const val SHORTCUT_KEY_PAGE = "page"
|
const val EXTRA_KEY_PAGE = "page"
|
||||||
const val SHORTCUT_KEY_FILTERS_ARRAY = "filters"
|
const val EXTRA_KEY_FILTERS_ARRAY = "filters"
|
||||||
const val SHORTCUT_KEY_FILTERS_STRING = "filtersString"
|
const val EXTRA_KEY_FILTERS_STRING = "filtersString"
|
||||||
|
|
||||||
// request code to pending runnable
|
// request code to pending runnable
|
||||||
val pendingStorageAccessResultHandlers = ConcurrentHashMap<Int, PendingStorageAccessResultHandler>()
|
val pendingStorageAccessResultHandlers = ConcurrentHashMap<Int, PendingStorageAccessResultHandler>()
|
||||||
|
|
||||||
var pendingScopedStoragePermissionCompleter: CompletableFuture<Boolean>? = null
|
var pendingScopedStoragePermissionCompleter: CompletableFuture<Boolean>? = null
|
||||||
|
|
||||||
|
var pendingCollectionFilterPickHandler: ((filters: List<String>?) -> Unit)? = null
|
||||||
|
|
||||||
private fun onStorageAccessResult(requestCode: Int, uri: Uri?) {
|
private fun onStorageAccessResult(requestCode: Int, uri: Uri?) {
|
||||||
Log.i(LOG_TAG, "onStorageAccessResult with requestCode=$requestCode, uri=$uri")
|
Log.i(LOG_TAG, "onStorageAccessResult with requestCode=$requestCode, uri=$uri")
|
||||||
val handler = pendingStorageAccessResultHandlers.remove(requestCode) ?: return
|
val handler = pendingStorageAccessResultHandlers.remove(requestCode) ?: return
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ScreenSaverService : DreamService() {
|
||||||
|
|
||||||
// intent handling
|
// intent handling
|
||||||
// detail fetch: dart -> platform
|
// detail fetch: dart -> platform
|
||||||
MethodChannel(messenger, WallpaperActivity.VIEWER_CHANNEL).setMethodCallHandler { call, result ->
|
MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getIntentData" -> {
|
"getIntentData" -> {
|
||||||
result.success(intentDataMap)
|
result.success(intentDataMap)
|
||||||
|
|
|
@ -49,7 +49,7 @@ class WallpaperActivity : FlutterActivity() {
|
||||||
// intent handling
|
// intent handling
|
||||||
// detail fetch: dart -> platform
|
// detail fetch: dart -> platform
|
||||||
intentDataMap = extractIntentData(intent)
|
intentDataMap = extractIntentData(intent)
|
||||||
MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler { call, result ->
|
MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getIntentData" -> {
|
"getIntentData" -> {
|
||||||
result.success(intentDataMap)
|
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?> {
|
private fun extractIntentData(intent: Intent?): MutableMap<String, Any?> {
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
|
Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
|
||||||
|
@ -108,6 +98,5 @@ class WallpaperActivity : FlutterActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<WallpaperActivity>()
|
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 com.bumptech.glide.request.RequestOptions
|
||||||
import deckers.thibault.aves.MainActivity
|
import deckers.thibault.aves.MainActivity
|
||||||
import deckers.thibault.aves.MainActivity.Companion.EXTRA_STRING_ARRAY_SEPARATOR
|
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.EXTRA_KEY_FILTERS_ARRAY
|
||||||
import deckers.thibault.aves.MainActivity.Companion.SHORTCUT_KEY_FILTERS_STRING
|
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_FILTERS_STRING
|
||||||
import deckers.thibault.aves.MainActivity.Companion.SHORTCUT_KEY_PAGE
|
import deckers.thibault.aves.MainActivity.Companion.EXTRA_KEY_PAGE
|
||||||
import deckers.thibault.aves.R
|
import deckers.thibault.aves.R
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
||||||
|
@ -407,11 +407,11 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
||||||
val intent = when {
|
val intent = when {
|
||||||
uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java)
|
uri != null -> Intent(Intent.ACTION_VIEW, uri, context, MainActivity::class.java)
|
||||||
filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
filters != null -> Intent(Intent.ACTION_MAIN, null, context, MainActivity::class.java)
|
||||||
.putExtra(SHORTCUT_KEY_PAGE, "/collection")
|
.putExtra(EXTRA_KEY_PAGE, "/collection")
|
||||||
.putExtra(SHORTCUT_KEY_FILTERS_ARRAY, filters.toTypedArray())
|
.putExtra(EXTRA_KEY_FILTERS_ARRAY, filters.toTypedArray())
|
||||||
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
// on API 25, `String[]` or `ArrayList` extras are null when using the shortcut
|
||||||
// so we use a joined `String` as fallback
|
// 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 -> {
|
else -> {
|
||||||
result.error("pin-intent", "failed to build intent", null)
|
result.error("pin-intent", "failed to build intent", null)
|
||||||
return
|
return
|
||||||
|
|
|
@ -22,9 +22,9 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
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
|
// 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 val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
private lateinit var eventSink: EventSink
|
private lateinit var eventSink: EventSink
|
||||||
private lateinit var handler: Handler
|
private lateinit var handler: Handler
|
||||||
|
@ -48,6 +48,7 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
||||||
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
|
"requestMediaFileAccess" -> ioScope.launch { requestMediaFileAccess() }
|
||||||
"createFile" -> ioScope.launch { createFile() }
|
"createFile" -> ioScope.launch { createFile() }
|
||||||
"openFile" -> ioScope.launch { openFile() }
|
"openFile" -> ioScope.launch { openFile() }
|
||||||
|
"pickCollectionFilters" -> pickCollectionFilters()
|
||||||
else -> endOfStream()
|
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?) {}
|
override fun onCancel(arguments: Any?) {}
|
||||||
|
|
||||||
private fun success(result: Any?) {
|
private fun success(result: Any?) {
|
||||||
|
@ -221,8 +234,8 @@ class StorageAccessStreamHandler(private val activity: Activity, arguments: Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<StorageAccessStreamHandler>()
|
private val LOG_TAG = LogUtils.createTag<ActivityResultStreamHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/storage_access_stream"
|
const val CHANNEL = "deckers.thibault/aves/activity_result_stream"
|
||||||
private const val BUFFER_SIZE = 2 shl 17 // 256kB
|
private const val BUFFER_SIZE = 2 shl 17 // 256kB
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,6 +20,6 @@ class IntentStreamHandler : EventChannel.StreamHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL = "deckers.thibault/aves/intent"
|
const val CHANNEL = "deckers.thibault/aves/new_intent_stream"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
enum AppMode {
|
enum AppMode {
|
||||||
main,
|
main,
|
||||||
|
pickCollectionFiltersExternal,
|
||||||
pickSingleMediaExternal,
|
pickSingleMediaExternal,
|
||||||
pickMultipleMediaExternal,
|
pickMultipleMediaExternal,
|
||||||
pickMediaInternal,
|
pickMediaInternal,
|
||||||
|
@ -11,13 +12,23 @@ enum AppMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtraAppMode on 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 canSelectFilter => this == AppMode.main;
|
||||||
|
|
||||||
bool get hasDrawer => this == AppMode.main || this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal;
|
bool get isPickingMedia => {
|
||||||
|
AppMode.pickSingleMediaExternal,
|
||||||
bool get isPickingMedia => this == AppMode.pickSingleMediaExternal || this == AppMode.pickMultipleMediaExternal || this == AppMode.pickMediaInternal;
|
AppMode.pickMultipleMediaExternal,
|
||||||
|
AppMode.pickMediaInternal,
|
||||||
|
}.contains(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class SettingsDefaults {
|
||||||
static const mustBackTwiceToExit = true;
|
static const mustBackTwiceToExit = true;
|
||||||
static const keepScreenOn = KeepScreenOn.viewerOnly;
|
static const keepScreenOn = KeepScreenOn.viewerOnly;
|
||||||
static const homePage = HomePageSetting.collection;
|
static const homePage = HomePageSetting.collection;
|
||||||
static const showBottomNavigationBar = true;
|
static const enableBottomNavigationBar = true;
|
||||||
static const confirmDeleteForever = true;
|
static const confirmDeleteForever = true;
|
||||||
static const confirmMoveToBin = true;
|
static const confirmMoveToBin = true;
|
||||||
static const confirmMoveUndatedItems = true;
|
static const confirmMoveUndatedItems = true;
|
||||||
|
|
|
@ -60,7 +60,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
|
static const mustBackTwiceToExitKey = 'must_back_twice_to_exit';
|
||||||
static const keepScreenOnKey = 'keep_screen_on';
|
static const keepScreenOnKey = 'keep_screen_on';
|
||||||
static const homePageKey = 'home_page';
|
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 confirmDeleteForeverKey = 'confirm_delete_forever';
|
||||||
static const confirmMoveToBinKey = 'confirm_move_to_bin';
|
static const confirmMoveToBinKey = 'confirm_move_to_bin';
|
||||||
static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items';
|
static const confirmMoveUndatedItemsKey = 'confirm_move_undated_items';
|
||||||
|
@ -142,6 +142,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const screenSaverTransitionKey = 'screen_saver_transition';
|
static const screenSaverTransitionKey = 'screen_saver_transition';
|
||||||
static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback';
|
static const screenSaverVideoPlaybackKey = 'screen_saver_video_playback';
|
||||||
static const screenSaverIntervalKey = 'screen_saver_interval';
|
static const screenSaverIntervalKey = 'screen_saver_interval';
|
||||||
|
static const screenSaverCollectionFiltersKey = 'screen_saver_collection_filters';
|
||||||
|
|
||||||
// slideshow
|
// slideshow
|
||||||
static const slideshowRepeatKey = 'slideshow_loop';
|
static const slideshowRepeatKey = 'slideshow_loop';
|
||||||
|
@ -320,9 +321,9 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString());
|
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);
|
bool get confirmDeleteForever => getBoolOrDefault(confirmDeleteForeverKey, SettingsDefaults.confirmDeleteForever);
|
||||||
|
|
||||||
|
@ -602,6 +603,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set screenSaverInterval(SlideshowInterval newValue) => setAndNotify(screenSaverIntervalKey, newValue.toString());
|
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
|
// slideshow
|
||||||
|
|
||||||
bool get slideshowRepeat => getBoolOrDefault(slideshowRepeatKey, SettingsDefaults.slideshowRepeat);
|
bool get slideshowRepeat => getBoolOrDefault(slideshowRepeatKey, SettingsDefaults.slideshowRepeat);
|
||||||
|
@ -754,7 +759,7 @@ class Settings extends ChangeNotifier {
|
||||||
case isErrorReportingAllowedKey:
|
case isErrorReportingAllowedKey:
|
||||||
case enableDynamicColorKey:
|
case enableDynamicColorKey:
|
||||||
case enableBlurEffectKey:
|
case enableBlurEffectKey:
|
||||||
case showBottomNavigationBarKey:
|
case enableBottomNavigationBarKey:
|
||||||
case mustBackTwiceToExitKey:
|
case mustBackTwiceToExitKey:
|
||||||
case confirmDeleteForeverKey:
|
case confirmDeleteForeverKey:
|
||||||
case confirmMoveToBinKey:
|
case confirmMoveToBinKey:
|
||||||
|
@ -831,6 +836,7 @@ class Settings extends ChangeNotifier {
|
||||||
case collectionBrowsingQuickActionsKey:
|
case collectionBrowsingQuickActionsKey:
|
||||||
case collectionSelectionQuickActionsKey:
|
case collectionSelectionQuickActionsKey:
|
||||||
case viewerQuickActionsKey:
|
case viewerQuickActionsKey:
|
||||||
|
case screenSaverCollectionFiltersKey:
|
||||||
if (newValue is List) {
|
if (newValue is List) {
|
||||||
settingsStore.setStringList(key, newValue.cast<String>());
|
settingsStore.setStringList(key, newValue.cast<String>());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,11 +2,11 @@ import 'package:aves/services/common/services.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class AccessibilityService {
|
class AccessibilityService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/accessibility');
|
static const _platform = MethodChannel('deckers.thibault/aves/accessibility');
|
||||||
|
|
||||||
static Future<bool> areAnimationsRemoved() async {
|
static Future<bool> areAnimationsRemoved() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('areAnimationsRemoved');
|
final result = await _platform.invokeMethod('areAnimationsRemoved');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -16,7 +16,7 @@ class AccessibilityService {
|
||||||
|
|
||||||
static Future<bool> hasRecommendedTimeouts() async {
|
static Future<bool> hasRecommendedTimeouts() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('hasRecommendedTimeouts');
|
final result = await _platform.invokeMethod('hasRecommendedTimeouts');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -26,7 +26,7 @@ class AccessibilityService {
|
||||||
|
|
||||||
static Future<int> getRecommendedTimeToRead(int originalTimeoutMillis) async {
|
static Future<int> getRecommendedTimeToRead(int originalTimeoutMillis) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
final result = await _platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||||
'originalTimeoutMillis': originalTimeoutMillis,
|
'originalTimeoutMillis': originalTimeoutMillis,
|
||||||
'content': ['icons', 'text']
|
'content': ['icons', 'text']
|
||||||
});
|
});
|
||||||
|
@ -39,7 +39,7 @@ class AccessibilityService {
|
||||||
|
|
||||||
static Future<int> getRecommendedTimeToTakeAction(int originalTimeoutMillis) async {
|
static Future<int> getRecommendedTimeToTakeAction(int originalTimeoutMillis) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
final result = await _platform.invokeMethod('getRecommendedTimeoutMillis', <String, dynamic>{
|
||||||
'originalTimeoutMillis': originalTimeoutMillis,
|
'originalTimeoutMillis': originalTimeoutMillis,
|
||||||
'content': ['controls', 'icons', 'text']
|
'content': ['controls', 'icons', 'text']
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,11 +13,11 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class AnalysisService {
|
class AnalysisService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/analysis');
|
static const _platform = MethodChannel('deckers.thibault/aves/analysis');
|
||||||
|
|
||||||
static Future<void> registerCallback() async {
|
static Future<void> registerCallback() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('registerCallback', <String, dynamic>{
|
await _platform.invokeMethod('registerCallback', <String, dynamic>{
|
||||||
'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(),
|
'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(),
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -27,7 +27,7 @@ class AnalysisService {
|
||||||
|
|
||||||
static Future<void> startService({required bool force, List<int>? entryIds}) async {
|
static Future<void> startService({required bool force, List<int>? entryIds}) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('startService', <String, dynamic>{
|
await _platform.invokeMethod('startService', <String, dynamic>{
|
||||||
'entryIds': entryIds,
|
'entryIds': entryIds,
|
||||||
'force': force,
|
'force': force,
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,9 +34,9 @@ abstract class AndroidAppService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformAndroidAppService implements 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.kakao.talk': {'KakaoTalkDownload'},
|
||||||
'com.sony.playmemories.mobile': {'Imaging Edge Mobile'},
|
'com.sony.playmemories.mobile': {'Imaging Edge Mobile'},
|
||||||
'nekox.messenger': {'NekoX'},
|
'nekox.messenger': {'NekoX'},
|
||||||
|
@ -45,10 +45,10 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<Set<Package>> getPackages() async {
|
Future<Set<Package>> getPackages() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getPackages');
|
final result = await _platform.invokeMethod('getPackages');
|
||||||
final packages = (result as List).cast<Map>().map(Package.fromMap).toSet();
|
final packages = (result as List).cast<Map>().map(Package.fromMap).toSet();
|
||||||
// additional info for known directories
|
// additional info for known directories
|
||||||
knownAppDirs.forEach((packageName, dirs) {
|
_knownAppDirs.forEach((packageName, dirs) {
|
||||||
final package = packages.firstWhereOrNull((package) => package.packageName == packageName);
|
final package = packages.firstWhereOrNull((package) => package.packageName == packageName);
|
||||||
if (package != null) {
|
if (package != null) {
|
||||||
package.ownedDirs.addAll(dirs);
|
package.ownedDirs.addAll(dirs);
|
||||||
|
@ -64,7 +64,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> getAppIcon(String packageName, double size) async {
|
Future<Uint8List> getAppIcon(String packageName, double size) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getAppIcon', <String, dynamic>{
|
final result = await _platform.invokeMethod('getAppIcon', <String, dynamic>{
|
||||||
'packageName': packageName,
|
'packageName': packageName,
|
||||||
'sizeDip': size,
|
'sizeDip': size,
|
||||||
});
|
});
|
||||||
|
@ -78,7 +78,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<String?> getAppInstaller() async {
|
Future<String?> getAppInstaller() async {
|
||||||
try {
|
try {
|
||||||
return await platform.invokeMethod('getAppInstaller');
|
return await _platform.invokeMethod('getAppInstaller');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<bool> copyToClipboard(String uri, String? label) async {
|
Future<bool> copyToClipboard(String uri, String? label) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('copyToClipboard', <String, dynamic>{
|
final result = await _platform.invokeMethod('copyToClipboard', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'label': label,
|
'label': label,
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<bool> edit(String uri, String mimeType) async {
|
Future<bool> edit(String uri, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('edit', <String, dynamic>{
|
final result = await _platform.invokeMethod('edit', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
});
|
});
|
||||||
|
@ -116,7 +116,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<bool> open(String uri, String mimeType) async {
|
Future<bool> open(String uri, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('open', <String, dynamic>{
|
final result = await _platform.invokeMethod('open', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
});
|
});
|
||||||
|
@ -134,7 +134,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude';
|
final geoUri = 'geo:$latitude,$longitude?q=$latitude,$longitude';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('openMap', <String, dynamic>{
|
final result = await _platform.invokeMethod('openMap', <String, dynamic>{
|
||||||
'geoUri': geoUri,
|
'geoUri': geoUri,
|
||||||
});
|
});
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
|
@ -147,7 +147,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<bool> setAs(String uri, String mimeType) async {
|
Future<bool> setAs(String uri, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('setAs', <String, dynamic>{
|
final result = await _platform.invokeMethod('setAs', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'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
|
// 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()));
|
final urisByMimeType = groupBy<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('share', <String, dynamic>{
|
final result = await _platform.invokeMethod('share', <String, dynamic>{
|
||||||
'urisByMimeType': urisByMimeType,
|
'urisByMimeType': urisByMimeType,
|
||||||
});
|
});
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
|
@ -177,7 +177,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
@override
|
@override
|
||||||
Future<bool> shareSingle(String uri, String mimeType) async {
|
Future<bool> shareSingle(String uri, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('share', <String, dynamic>{
|
final result = await _platform.invokeMethod('share', <String, dynamic>{
|
||||||
'urisByMimeType': {
|
'urisByMimeType': {
|
||||||
mimeType: [uri]
|
mimeType: [uri]
|
||||||
},
|
},
|
||||||
|
@ -207,7 +207,7 @@ class PlatformAndroidAppService implements AndroidAppService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('pinShortcut', <String, dynamic>{
|
await _platform.invokeMethod('pinShortcut', <String, dynamic>{
|
||||||
'label': label,
|
'label': label,
|
||||||
'iconBytes': iconBytes,
|
'iconBytes': iconBytes,
|
||||||
'filters': filters?.map((filter) => filter.toJson()).toList(),
|
'filters': filters?.map((filter) => filter.toJson()).toList(),
|
||||||
|
|
|
@ -4,11 +4,11 @@ import 'package:aves/services/common/services.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class AndroidDebugService {
|
class AndroidDebugService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/debug');
|
static const _platform = MethodChannel('deckers.thibault/aves/debug');
|
||||||
|
|
||||||
static Future<void> crash() async {
|
static Future<void> crash() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('crash');
|
await _platform.invokeMethod('crash');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<void> exception() async {
|
static Future<void> exception() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('exception');
|
await _platform.invokeMethod('exception');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<void> safeException() async {
|
static Future<void> safeException() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('safeException');
|
await _platform.invokeMethod('safeException');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<void> exceptionInCoroutine() async {
|
static Future<void> exceptionInCoroutine() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('exceptionInCoroutine');
|
await _platform.invokeMethod('exceptionInCoroutine');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<void> safeExceptionInCoroutine() async {
|
static Future<void> safeExceptionInCoroutine() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('safeExceptionInCoroutine');
|
await _platform.invokeMethod('safeExceptionInCoroutine');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<Map> getContextDirs() async {
|
static Future<Map> getContextDirs() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getContextDirs');
|
final result = await _platform.invokeMethod('getContextDirs');
|
||||||
if (result != null) return result as Map;
|
if (result != null) return result as Map;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -58,7 +58,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<List<Map>> getCodecs() async {
|
static Future<List<Map>> getCodecs() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getCodecs');
|
final result = await _platform.invokeMethod('getCodecs');
|
||||||
if (result != null) return (result as List).cast<Map>();
|
if (result != null) return (result as List).cast<Map>();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -68,7 +68,7 @@ class AndroidDebugService {
|
||||||
|
|
||||||
static Future<Map> getEnv() async {
|
static Future<Map> getEnv() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getEnv');
|
final result = await _platform.invokeMethod('getEnv');
|
||||||
if (result != null) return result as Map;
|
if (result != null) return result as Map;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -79,7 +79,7 @@ class AndroidDebugService {
|
||||||
static Future<Map> getBitmapFactoryInfo(AvesEntry entry) async {
|
static Future<Map> getBitmapFactoryInfo(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with all data available when decoding image bounds with `BitmapFactory`
|
// 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,
|
'uri': entry.uri,
|
||||||
});
|
});
|
||||||
if (result != null) return result as Map;
|
if (result != null) return result as Map;
|
||||||
|
@ -92,7 +92,7 @@ class AndroidDebugService {
|
||||||
static Future<Map> getContentResolverMetadata(AvesEntry entry) async {
|
static Future<Map> getContentResolverMetadata(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with all data available from the content resolver
|
// 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,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
});
|
});
|
||||||
|
@ -106,7 +106,7 @@ class AndroidDebugService {
|
||||||
static Future<Map> getExifInterfaceMetadata(AvesEntry entry) async {
|
static Future<Map> getExifInterfaceMetadata(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with all data available from the `ExifInterface` library
|
// 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,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -121,7 +121,7 @@ class AndroidDebugService {
|
||||||
static Future<Map> getMediaMetadataRetrieverMetadata(AvesEntry entry) async {
|
static Future<Map> getMediaMetadataRetrieverMetadata(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with all data available from `MediaMetadataRetriever`
|
// 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,
|
'uri': entry.uri,
|
||||||
});
|
});
|
||||||
if (result != null) return result as Map;
|
if (result != null) return result as Map;
|
||||||
|
@ -134,7 +134,7 @@ class AndroidDebugService {
|
||||||
static Future<Map> getMetadataExtractorSummary(AvesEntry entry) async {
|
static Future<Map> getMetadataExtractorSummary(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with the MIME type and tag count for each directory found by `metadata-extractor`
|
// 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,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -149,7 +149,7 @@ class AndroidDebugService {
|
||||||
static Future<Map> getPixyMetadata(AvesEntry entry) async {
|
static Future<Map> getPixyMetadata(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with all data available from the `PixyMeta` library
|
// 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,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
});
|
});
|
||||||
|
@ -164,7 +164,7 @@ class AndroidDebugService {
|
||||||
if (entry.mimeType != MimeTypes.tiff) return {};
|
if (entry.mimeType != MimeTypes.tiff) return {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getTiffStructure', <String, dynamic>{
|
final result = await _platform.invokeMethod('getTiffStructure', <String, dynamic>{
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
});
|
});
|
||||||
if (result != null) return result as Map;
|
if (result != null) return result as Map;
|
||||||
|
|
|
@ -16,12 +16,12 @@ abstract class DeviceService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformDeviceService implements DeviceService {
|
class PlatformDeviceService implements DeviceService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/device');
|
static const _platform = MethodChannel('deckers.thibault/aves/device');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> getCapabilities() async {
|
Future<Map<String, dynamic>> getCapabilities() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getCapabilities');
|
final result = await _platform.invokeMethod('getCapabilities');
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -32,7 +32,7 @@ class PlatformDeviceService implements DeviceService {
|
||||||
@override
|
@override
|
||||||
Future<String?> getDefaultTimeZone() async {
|
Future<String?> getDefaultTimeZone() async {
|
||||||
try {
|
try {
|
||||||
return await platform.invokeMethod('getDefaultTimeZone');
|
return await _platform.invokeMethod('getDefaultTimeZone');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class PlatformDeviceService implements DeviceService {
|
||||||
@override
|
@override
|
||||||
Future<List<Locale>> getLocales() async {
|
Future<List<Locale>> getLocales() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getLocales');
|
final result = await _platform.invokeMethod('getLocales');
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return (result as List).cast<Map>().map((tags) {
|
return (result as List).cast<Map>().map((tags) {
|
||||||
final language = tags['language'] as String?;
|
final language = tags['language'] as String?;
|
||||||
|
@ -62,7 +62,7 @@ class PlatformDeviceService implements DeviceService {
|
||||||
@override
|
@override
|
||||||
Future<int> getPerformanceClass() async {
|
Future<int> getPerformanceClass() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getPerformanceClass');
|
final result = await _platform.invokeMethod('getPerformanceClass');
|
||||||
if (result != null) return result as int;
|
if (result != null) return result as int;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -73,7 +73,7 @@ class PlatformDeviceService implements DeviceService {
|
||||||
@override
|
@override
|
||||||
Future<bool> isSystemFilePickerEnabled() async {
|
Future<bool> isSystemFilePickerEnabled() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('isSystemFilePickerEnabled');
|
final result = await _platform.invokeMethod('isSystemFilePickerEnabled');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
|
|
@ -7,12 +7,12 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
class GeocodingService {
|
class GeocodingService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/geocoding');
|
static const _platform = MethodChannel('deckers.thibault/aves/geocoding');
|
||||||
|
|
||||||
// geocoding requires Google Play Services
|
// geocoding requires Google Play Services
|
||||||
static Future<List<Address>> getAddress(LatLng coordinates, Locale locale) async {
|
static Future<List<Address>> getAddress(LatLng coordinates, Locale locale) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getAddress', <String, dynamic>{
|
final result = await _platform.invokeMethod('getAddress', <String, dynamic>{
|
||||||
'latitude': coordinates.latitude,
|
'latitude': coordinates.latitude,
|
||||||
'longitude': coordinates.longitude,
|
'longitude': coordinates.longitude,
|
||||||
'locale': locale.toString(),
|
'locale': locale.toString(),
|
||||||
|
|
|
@ -7,11 +7,11 @@ import 'package:flutter/widgets.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
|
||||||
class GlobalSearch {
|
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 {
|
static Future<void> registerCallback() async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('registerCallback', <String, dynamic>{
|
await _platform.invokeMethod('registerCallback', <String, dynamic>{
|
||||||
'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(),
|
'callbackHandle': PluginUtilities.getCallbackHandle(_init)?.toRawHandle(),
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} 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 {
|
class PlatformEmbeddedDataService implements EmbeddedDataService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/embedded');
|
static const _platform = MethodChannel('deckers.thibault/aves/embedded');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Uint8List>> getExifThumbnails(AvesEntry entry) async {
|
Future<List<Uint8List>> getExifThumbnails(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{
|
final result = await _platform.invokeMethod('getExifThumbnails', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -35,7 +35,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
||||||
@override
|
@override
|
||||||
Future<Map> extractMotionPhotoVideo(AvesEntry entry) async {
|
Future<Map> extractMotionPhotoVideo(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('extractMotionPhotoVideo', <String, dynamic>{
|
final result = await _platform.invokeMethod('extractMotionPhotoVideo', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -51,7 +51,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
||||||
@override
|
@override
|
||||||
Future<Map> extractVideoEmbeddedPicture(AvesEntry entry) async {
|
Future<Map> extractVideoEmbeddedPicture(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{
|
final result = await _platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'displayName': '${entry.bestTitle} • Cover',
|
'displayName': '${entry.bestTitle} • Cover',
|
||||||
});
|
});
|
||||||
|
@ -65,7 +65,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
||||||
@override
|
@override
|
||||||
Future<Map> extractXmpDataProp(AvesEntry entry, String? propPath, String? propMimeType) async {
|
Future<Map> extractXmpDataProp(AvesEntry entry, String? propPath, String? propMimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('extractXmpDataProp', <String, dynamic>{
|
final result = await _platform.invokeMethod('extractXmpDataProp', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
|
|
@ -108,10 +108,10 @@ abstract class MediaFileService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformMediaFileService implements MediaFileService {
|
class PlatformMediaFileService implements MediaFileService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/media_file');
|
static const _platform = MethodChannel('deckers.thibault/aves/media_file');
|
||||||
static final StreamsChannel _byteStreamChannel = StreamsChannel('deckers.thibault/aves/media_byte_stream');
|
static final _byteStream = StreamsChannel('deckers.thibault/aves/media_byte_stream');
|
||||||
static final StreamsChannel _opStreamChannel = StreamsChannel('deckers.thibault/aves/media_op_stream');
|
static final _opStream = StreamsChannel('deckers.thibault/aves/media_op_stream');
|
||||||
static const double thumbnailDefaultSize = 64.0;
|
static const double _thumbnailDefaultSize = 64.0;
|
||||||
|
|
||||||
static Map<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
static Map<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
||||||
return {
|
return {
|
||||||
|
@ -136,7 +136,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
@override
|
@override
|
||||||
Future<AvesEntry?> getEntry(String uri, String? mimeType) async {
|
Future<AvesEntry?> getEntry(String uri, String? mimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getEntry', <String, dynamic>{
|
final result = await _platform.invokeMethod('getEntry', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
}) as Map;
|
}) as Map;
|
||||||
|
@ -181,7 +181,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
final completer = Completer<Uint8List>.sync();
|
final completer = Completer<Uint8List>.sync();
|
||||||
final sink = OutputBuffer();
|
final sink = OutputBuffer();
|
||||||
var bytesReceived = 0;
|
var bytesReceived = 0;
|
||||||
_byteStreamChannel.receiveBroadcastStream(<String, dynamic>{
|
_byteStream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
'rotationDegrees': rotationDegrees ?? 0,
|
'rotationDegrees': rotationDegrees ?? 0,
|
||||||
|
@ -234,7 +234,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
return servicePolicy.call(
|
return servicePolicy.call(
|
||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getRegion', <String, dynamic>{
|
final result = await _platform.invokeMethod('getRegion', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
'pageId': pageId,
|
'pageId': pageId,
|
||||||
|
@ -274,7 +274,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
return servicePolicy.call(
|
return servicePolicy.call(
|
||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getThumbnail', <String, dynamic>{
|
final result = await _platform.invokeMethod('getThumbnail', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
'dateModifiedSecs': dateModifiedSecs,
|
'dateModifiedSecs': dateModifiedSecs,
|
||||||
|
@ -283,7 +283,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
'widthDip': extent,
|
'widthDip': extent,
|
||||||
'heightDip': extent,
|
'heightDip': extent,
|
||||||
'pageId': pageId,
|
'pageId': pageId,
|
||||||
'defaultSizeDip': thumbnailDefaultSize,
|
'defaultSizeDip': _thumbnailDefaultSize,
|
||||||
});
|
});
|
||||||
if (result != null) return result as Uint8List;
|
if (result != null) return result as Uint8List;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -301,7 +301,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
@override
|
@override
|
||||||
Future<void> clearSizedThumbnailDiskCache() async {
|
Future<void> clearSizedThumbnailDiskCache() async {
|
||||||
try {
|
try {
|
||||||
return platform.invokeMethod('clearSizedThumbnailDiskCache');
|
return _platform.invokeMethod('clearSizedThumbnailDiskCache');
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -319,7 +319,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
@override
|
@override
|
||||||
Future<void> cancelFileOp(String opId) async {
|
Future<void> cancelFileOp(String opId) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('cancelFileOp', <String, dynamic>{
|
await _platform.invokeMethod('cancelFileOp', <String, dynamic>{
|
||||||
'opId': opId,
|
'opId': opId,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -333,7 +333,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
required Iterable<AvesEntry> entries,
|
required Iterable<AvesEntry> entries,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
return _opStreamChannel
|
return _opStream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'delete',
|
'op': 'delete',
|
||||||
'id': opId,
|
'id': opId,
|
||||||
|
@ -355,7 +355,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
return _opStreamChannel
|
return _opStream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'move',
|
'op': 'move',
|
||||||
'id': opId,
|
'id': opId,
|
||||||
|
@ -379,7 +379,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
return _opStreamChannel
|
return _opStream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'export',
|
'op': 'export',
|
||||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||||
|
@ -403,7 +403,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
required Map<AvesEntry, String> entriesToNewName,
|
required Map<AvesEntry, String> entriesToNewName,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
return _opStreamChannel
|
return _opStream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'rename',
|
'op': 'rename',
|
||||||
'id': opId,
|
'id': opId,
|
||||||
|
@ -427,7 +427,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('captureFrame', <String, dynamic>{
|
final result = await _platform.invokeMethod('captureFrame', <String, dynamic>{
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'desiredName': desiredName,
|
'desiredName': desiredName,
|
||||||
'exif': exif,
|
'exif': exif,
|
||||||
|
|
|
@ -18,13 +18,13 @@ abstract class MediaStoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformMediaStoreService implements MediaStoreService {
|
class PlatformMediaStoreService implements MediaStoreService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/media_store');
|
static const _platform = MethodChannel('deckers.thibault/aves/media_store');
|
||||||
static final StreamsChannel _streamChannel = StreamsChannel('deckers.thibault/aves/media_store_stream');
|
static final _stream = StreamsChannel('deckers.thibault/aves/media_store_stream');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<int>> checkObsoleteContentIds(List<int?> knownContentIds) async {
|
Future<List<int>> checkObsoleteContentIds(List<int?> knownContentIds) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('checkObsoleteContentIds', <String, dynamic>{
|
final result = await _platform.invokeMethod('checkObsoleteContentIds', <String, dynamic>{
|
||||||
'knownContentIds': knownContentIds,
|
'knownContentIds': knownContentIds,
|
||||||
});
|
});
|
||||||
return (result as List).cast<int>();
|
return (result as List).cast<int>();
|
||||||
|
@ -37,7 +37,7 @@ class PlatformMediaStoreService implements MediaStoreService {
|
||||||
@override
|
@override
|
||||||
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById) async {
|
Future<List<int>> checkObsoletePaths(Map<int?, String?> knownPathById) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('checkObsoletePaths', <String, dynamic>{
|
final result = await _platform.invokeMethod('checkObsoletePaths', <String, dynamic>{
|
||||||
'knownPathById': knownPathById,
|
'knownPathById': knownPathById,
|
||||||
});
|
});
|
||||||
return (result as List).cast<int>();
|
return (result as List).cast<int>();
|
||||||
|
@ -50,7 +50,7 @@ class PlatformMediaStoreService implements MediaStoreService {
|
||||||
@override
|
@override
|
||||||
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
|
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
|
||||||
try {
|
try {
|
||||||
return _streamChannel
|
return _stream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'knownEntries': knownEntries,
|
'knownEntries': knownEntries,
|
||||||
'directory': directory,
|
'directory': directory,
|
||||||
|
@ -67,7 +67,7 @@ class PlatformMediaStoreService implements MediaStoreService {
|
||||||
@override
|
@override
|
||||||
Future<Uri?> scanFile(String path, String mimeType) async {
|
Future<Uri?> scanFile(String path, String mimeType) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('scanFile', <String, dynamic>{
|
final result = await _platform.invokeMethod('scanFile', <String, dynamic>{
|
||||||
'path': path,
|
'path': path,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ abstract class MetadataEditService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformMetadataEditService implements 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) {
|
static Map<String, dynamic> _toPlatformEntryMap(AvesEntry entry) {
|
||||||
return {
|
return {
|
||||||
|
@ -44,7 +44,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
Future<Map<String, dynamic>> rotate(AvesEntry entry, {required bool clockwise}) async {
|
Future<Map<String, dynamic>> rotate(AvesEntry entry, {required bool clockwise}) async {
|
||||||
try {
|
try {
|
||||||
// returns map with: 'rotationDegrees' 'isFlipped'
|
// returns map with: 'rotationDegrees' 'isFlipped'
|
||||||
final result = await platform.invokeMethod('rotate', <String, dynamic>{
|
final result = await _platform.invokeMethod('rotate', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': _toPlatformEntryMap(entry),
|
||||||
'clockwise': clockwise,
|
'clockwise': clockwise,
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
Future<Map<String, dynamic>> flip(AvesEntry entry) async {
|
Future<Map<String, dynamic>> flip(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
// returns map with: 'rotationDegrees' 'isFlipped'
|
// returns map with: 'rotationDegrees' 'isFlipped'
|
||||||
final result = await platform.invokeMethod('flip', <String, dynamic>{
|
final result = await _platform.invokeMethod('flip', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': _toPlatformEntryMap(entry),
|
||||||
});
|
});
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
|
@ -76,7 +76,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> editExifDate(AvesEntry entry, DateModifier modifier) async {
|
Future<Map<String, dynamic>> editExifDate(AvesEntry entry, DateModifier modifier) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('editDate', <String, dynamic>{
|
final result = await _platform.invokeMethod('editDate', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': _toPlatformEntryMap(entry),
|
||||||
'dateMillis': modifier.setDateTime?.millisecondsSinceEpoch,
|
'dateMillis': modifier.setDateTime?.millisecondsSinceEpoch,
|
||||||
'shiftMinutes': modifier.shiftMinutes,
|
'shiftMinutes': modifier.shiftMinutes,
|
||||||
|
@ -98,7 +98,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
bool autoCorrectTrailerOffset = true,
|
bool autoCorrectTrailerOffset = true,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('editMetadata', <String, dynamic>{
|
final result = await _platform.invokeMethod('editMetadata', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': _toPlatformEntryMap(entry),
|
||||||
'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)),
|
'metadata': metadata.map((type, value) => MapEntry(_toPlatformMetadataType(type), value)),
|
||||||
'autoCorrectTrailerOffset': autoCorrectTrailerOffset,
|
'autoCorrectTrailerOffset': autoCorrectTrailerOffset,
|
||||||
|
@ -115,7 +115,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> removeTrailerVideo(AvesEntry entry) async {
|
Future<Map<String, dynamic>> removeTrailerVideo(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
|
final result = await _platform.invokeMethod('removeTrailerVideo', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': _toPlatformEntryMap(entry),
|
||||||
});
|
});
|
||||||
if (result != null) return (result as Map).cast<String, dynamic>();
|
if (result != null) return (result as Map).cast<String, dynamic>();
|
||||||
|
@ -130,7 +130,7 @@ class PlatformMetadataEditService implements MetadataEditService {
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> removeTypes(AvesEntry entry, Set<MetadataType> types) async {
|
Future<Map<String, dynamic>> removeTypes(AvesEntry entry, Set<MetadataType> types) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('removeTypes', <String, dynamic>{
|
final result = await _platform.invokeMethod('removeTypes', <String, dynamic>{
|
||||||
'entry': _toPlatformEntryMap(entry),
|
'entry': _toPlatformEntryMap(entry),
|
||||||
'types': types.map(_toPlatformMetadataType).toList(),
|
'types': types.map(_toPlatformMetadataType).toList(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,14 +38,14 @@ abstract class MetadataFetchService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformMetadataFetchService implements MetadataFetchService {
|
class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/metadata_fetch');
|
static const _platform = MethodChannel('deckers.thibault/aves/metadata_fetch');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> getAllMetadata(AvesEntry entry) async {
|
Future<Map> getAllMetadata(AvesEntry entry) async {
|
||||||
if (entry.isSvg) return {};
|
if (entry.isSvg) return {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getAllMetadata', <String, dynamic>{
|
final result = await _platform.invokeMethod('getAllMetadata', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -76,7 +76,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
// 'longitude': longitude (double)
|
// 'longitude': longitude (double)
|
||||||
// 'xmpSubjects': ';' separated XMP subjects (string)
|
// 'xmpSubjects': ';' separated XMP subjects (string)
|
||||||
// 'xmpTitleDescription': XMP title or XMP description (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,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'path': entry.path,
|
'path': entry.path,
|
||||||
|
@ -106,7 +106,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// returns map with values for: 'aperture' (double), 'exposureTime' (description), 'focalLength' (double), 'iso' (int)
|
// 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,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -123,7 +123,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
@override
|
@override
|
||||||
Future<GeoTiffInfo?> getGeoTiffInfo(AvesEntry entry) async {
|
Future<GeoTiffInfo?> getGeoTiffInfo(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getGeoTiffInfo', <String, dynamic>{
|
final result = await _platform.invokeMethod('getGeoTiffInfo', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -140,7 +140,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
@override
|
@override
|
||||||
Future<MultiPageInfo?> getMultiPageInfo(AvesEntry entry) async {
|
Future<MultiPageInfo?> getMultiPageInfo(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getMultiPageInfo', <String, dynamic>{
|
final result = await _platform.invokeMethod('getMultiPageInfo', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -167,7 +167,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
// returns map with values for:
|
// returns map with values for:
|
||||||
// 'croppedAreaLeft' (int), 'croppedAreaTop' (int), 'croppedAreaWidth' (int), 'croppedAreaHeight' (int),
|
// 'croppedAreaLeft' (int), 'croppedAreaTop' (int), 'croppedAreaWidth' (int), 'croppedAreaHeight' (int),
|
||||||
// 'fullPanoWidth' (int), 'fullPanoHeight' (int)
|
// 'fullPanoWidth' (int), 'fullPanoHeight' (int)
|
||||||
final result = await platform.invokeMethod('getPanoramaInfo', <String, dynamic>{
|
final result = await _platform.invokeMethod('getPanoramaInfo', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -184,7 +184,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
@override
|
@override
|
||||||
Future<List<Map<String, dynamic>>?> getIptc(AvesEntry entry) async {
|
Future<List<Map<String, dynamic>>?> getIptc(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getIptc', <String, dynamic>{
|
final result = await _platform.invokeMethod('getIptc', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
});
|
});
|
||||||
|
@ -200,7 +200,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
@override
|
@override
|
||||||
Future<AvesXmp?> getXmp(AvesEntry entry) async {
|
Future<AvesXmp?> getXmp(AvesEntry entry) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getXmp', <String, dynamic>{
|
final result = await _platform.invokeMethod('getXmp', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
@ -222,7 +222,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
if (exists != null) return SynchronousFuture(exists);
|
if (exists != null) return SynchronousFuture(exists);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
exists = await platform.invokeMethod('hasContentResolverProp', <String, dynamic>{
|
exists = await _platform.invokeMethod('hasContentResolverProp', <String, dynamic>{
|
||||||
'prop': prop,
|
'prop': prop,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -236,7 +236,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
@override
|
@override
|
||||||
Future<String?> getContentResolverProp(AvesEntry entry, String prop) async {
|
Future<String?> getContentResolverProp(AvesEntry entry, String prop) async {
|
||||||
try {
|
try {
|
||||||
return await platform.invokeMethod('getContentResolverProp', <String, dynamic>{
|
return await _platform.invokeMethod('getContentResolverProp', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'prop': prop,
|
'prop': prop,
|
||||||
|
@ -252,7 +252,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
@override
|
@override
|
||||||
Future<DateTime?> getDate(AvesEntry entry, MetadataField field) async {
|
Future<DateTime?> getDate(AvesEntry entry, MetadataField field) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getDate', <String, dynamic>{
|
final result = await _platform.invokeMethod('getDate', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
|
|
@ -40,13 +40,13 @@ abstract class StorageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformStorageService implements StorageService {
|
class PlatformStorageService implements StorageService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/storage');
|
static const _platform = MethodChannel('deckers.thibault/aves/storage');
|
||||||
static final StreamsChannel storageAccessChannel = StreamsChannel('deckers.thibault/aves/storage_access_stream');
|
static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Set<StorageVolume>> getStorageVolumes() async {
|
Future<Set<StorageVolume>> getStorageVolumes() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getStorageVolumes');
|
final result = await _platform.invokeMethod('getStorageVolumes');
|
||||||
return (result as List).cast<Map>().map(StorageVolume.fromMap).toSet();
|
return (result as List).cast<Map>().map(StorageVolume.fromMap).toSet();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -57,7 +57,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<int?> getFreeSpace(StorageVolume volume) async {
|
Future<int?> getFreeSpace(StorageVolume volume) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getFreeSpace', <String, dynamic>{
|
final result = await _platform.invokeMethod('getFreeSpace', <String, dynamic>{
|
||||||
'path': volume.path,
|
'path': volume.path,
|
||||||
});
|
});
|
||||||
return result as int?;
|
return result as int?;
|
||||||
|
@ -70,7 +70,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getGrantedDirectories() async {
|
Future<List<String>> getGrantedDirectories() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getGrantedDirectories');
|
final result = await _platform.invokeMethod('getGrantedDirectories');
|
||||||
return (result as List).cast<String>();
|
return (result as List).cast<String>();
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -81,7 +81,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths) async {
|
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getInaccessibleDirectories', <String, dynamic>{
|
final result = await _platform.invokeMethod('getInaccessibleDirectories', <String, dynamic>{
|
||||||
'dirPaths': dirPaths.toList(),
|
'dirPaths': dirPaths.toList(),
|
||||||
});
|
});
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
@ -96,7 +96,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories() async {
|
Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getRestrictedDirectories');
|
final result = await _platform.invokeMethod('getRestrictedDirectories');
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return (result as List).cast<Map>().map(VolumeRelativeDirectory.fromMap).toSet();
|
return (result as List).cast<Map>().map(VolumeRelativeDirectory.fromMap).toSet();
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<void> revokeDirectoryAccess(String path) async {
|
Future<void> revokeDirectoryAccess(String path) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
|
await _platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
|
||||||
'path': path,
|
'path': path,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -122,7 +122,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
|
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
|
final result = await _platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
|
||||||
'dirPaths': dirPaths.toList(),
|
'dirPaths': dirPaths.toList(),
|
||||||
});
|
});
|
||||||
if (result != null) return result as int;
|
if (result != null) return result as int;
|
||||||
|
@ -135,7 +135,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<bool> canRequestMediaFileAccess() async {
|
Future<bool> canRequestMediaFileAccess() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('canRequestMediaFileBulkAccess');
|
final result = await _platform.invokeMethod('canRequestMediaFileBulkAccess');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -146,7 +146,7 @@ class PlatformStorageService implements StorageService {
|
||||||
@override
|
@override
|
||||||
Future<bool> canInsertMedia(Set<VolumeRelativeDirectory> directories) async {
|
Future<bool> canInsertMedia(Set<VolumeRelativeDirectory> directories) async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('canInsertMedia', <String, dynamic>{
|
final result = await _platform.invokeMethod('canInsertMedia', <String, dynamic>{
|
||||||
'directories': directories.map((v) => v.toMap()).toList(),
|
'directories': directories.map((v) => v.toMap()).toList(),
|
||||||
});
|
});
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
|
@ -161,7 +161,7 @@ class PlatformStorageService implements StorageService {
|
||||||
Future<bool> requestDirectoryAccess(String path) async {
|
Future<bool> requestDirectoryAccess(String path) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<bool>();
|
final completer = Completer<bool>();
|
||||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'requestDirectoryAccess',
|
'op': 'requestDirectoryAccess',
|
||||||
'path': path,
|
'path': path,
|
||||||
}).listen(
|
}).listen(
|
||||||
|
@ -185,7 +185,7 @@ class PlatformStorageService implements StorageService {
|
||||||
Future<bool> requestMediaFileAccess(List<String> uris, List<String> mimeTypes) async {
|
Future<bool> requestMediaFileAccess(List<String> uris, List<String> mimeTypes) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<bool>();
|
final completer = Completer<bool>();
|
||||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'requestMediaFileAccess',
|
'op': 'requestMediaFileAccess',
|
||||||
'uris': uris,
|
'uris': uris,
|
||||||
'mimeTypes': mimeTypes,
|
'mimeTypes': mimeTypes,
|
||||||
|
@ -216,7 +216,7 @@ class PlatformStorageService implements StorageService {
|
||||||
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
|
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<bool?>();
|
final completer = Completer<bool?>();
|
||||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'createFile',
|
'op': 'createFile',
|
||||||
'name': name,
|
'name': name,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
|
@ -242,7 +242,7 @@ class PlatformStorageService implements StorageService {
|
||||||
try {
|
try {
|
||||||
final completer = Completer<Uint8List>.sync();
|
final completer = Completer<Uint8List>.sync();
|
||||||
final sink = OutputBuffer();
|
final sink = OutputBuffer();
|
||||||
storageAccessChannel.receiveBroadcastStream(<String, dynamic>{
|
_stream.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'openFile',
|
'op': 'openFile',
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
}).listen(
|
}).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';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class WallpaperService {
|
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 {
|
static Future<bool> set(Uint8List bytes, WallpaperTarget target) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('setWallpaper', <String, dynamic>{
|
await _platform.invokeMethod('setWallpaper', <String, dynamic>{
|
||||||
'bytes': bytes,
|
'bytes': bytes,
|
||||||
'home': {WallpaperTarget.home, WallpaperTarget.homeLock}.contains(target),
|
'home': {WallpaperTarget.home, WallpaperTarget.homeLock}.contains(target),
|
||||||
'lock': {WallpaperTarget.lock, WallpaperTarget.homeLock}.contains(target),
|
'lock': {WallpaperTarget.lock, WallpaperTarget.homeLock}.contains(target),
|
||||||
|
|
|
@ -17,12 +17,12 @@ abstract class WindowService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformWindowService implements WindowService {
|
class PlatformWindowService implements WindowService {
|
||||||
static const platform = MethodChannel('deckers.thibault/aves/window');
|
static const _platform = MethodChannel('deckers.thibault/aves/window');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isActivity() async {
|
Future<bool> isActivity() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('isActivity');
|
final result = await _platform.invokeMethod('isActivity');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -33,7 +33,7 @@ class PlatformWindowService implements WindowService {
|
||||||
@override
|
@override
|
||||||
Future<void> keepScreenOn(bool on) async {
|
Future<void> keepScreenOn(bool on) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('keepScreenOn', <String, dynamic>{
|
await _platform.invokeMethod('keepScreenOn', <String, dynamic>{
|
||||||
'on': on,
|
'on': on,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -44,7 +44,7 @@ class PlatformWindowService implements WindowService {
|
||||||
@override
|
@override
|
||||||
Future<bool> isRotationLocked() async {
|
Future<bool> isRotationLocked() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('isRotationLocked');
|
final result = await _platform.invokeMethod('isRotationLocked');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -71,7 +71,7 @@ class PlatformWindowService implements WindowService {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('requestOrientation', <String, dynamic>{
|
await _platform.invokeMethod('requestOrientation', <String, dynamic>{
|
||||||
'orientation': orientationCode,
|
'orientation': orientationCode,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
@ -82,7 +82,7 @@ class PlatformWindowService implements WindowService {
|
||||||
@override
|
@override
|
||||||
Future<bool> canSetCutoutMode() async {
|
Future<bool> canSetCutoutMode() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('canSetCutoutMode');
|
final result = await _platform.invokeMethod('canSetCutoutMode');
|
||||||
if (result != null) return result as bool;
|
if (result != null) return result as bool;
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
|
@ -93,7 +93,7 @@ class PlatformWindowService implements WindowService {
|
||||||
@override
|
@override
|
||||||
Future<void> setCutoutMode(bool use) async {
|
Future<void> setCutoutMode(bool use) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('setCutoutMode', <String, dynamic>{
|
await _platform.invokeMethod('setCutoutMode', <String, dynamic>{
|
||||||
'use': use,
|
'use': use,
|
||||||
});
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
|
|
@ -83,7 +83,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
// the list itself needs to be reassigned
|
// the list itself needs to be reassigned
|
||||||
List<NavigatorObserver> _navigatorObservers = [AvesApp.pageRouteObserver];
|
List<NavigatorObserver> _navigatorObservers = [AvesApp.pageRouteObserver];
|
||||||
final EventChannel _mediaStoreChangeChannel = const OptionalEventChannel('deckers.thibault/aves/media_store_change');
|
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 _analysisCompletionChannel = const OptionalEventChannel('deckers.thibault/aves/analysis_events');
|
||||||
final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error');
|
final EventChannel _errorChannel = const OptionalEventChannel('deckers.thibault/aves/error');
|
||||||
|
|
||||||
|
@ -228,6 +228,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
case AppMode.pickMultipleMediaExternal:
|
case AppMode.pickMultipleMediaExternal:
|
||||||
_saveTopEntries();
|
_saveTopEntries();
|
||||||
break;
|
break;
|
||||||
|
case AppMode.pickCollectionFiltersExternal:
|
||||||
case AppMode.pickMediaInternal:
|
case AppMode.pickMediaInternal:
|
||||||
case AppMode.pickFilterInternal:
|
case AppMode.pickFilterInternal:
|
||||||
case AppMode.screenSaver:
|
case AppMode.screenSaver:
|
||||||
|
|
|
@ -138,7 +138,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
return AvesAppBar(
|
return AvesAppBar(
|
||||||
contentHeight: appBarContentHeight,
|
contentHeight: appBarContentHeight,
|
||||||
leading: _buildAppBarLeading(
|
leading: _buildAppBarLeading(
|
||||||
hasDrawer: appMode.hasDrawer,
|
hasDrawer: appMode.canNavigate,
|
||||||
isSelecting: isSelecting,
|
isSelecting: isSelecting,
|
||||||
),
|
),
|
||||||
title: _buildAppBarTitle(isSelecting),
|
title: _buildAppBarTitle(isSelecting),
|
||||||
|
@ -228,7 +228,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return InteractiveAppBarTitle(
|
return InteractiveAppBarTitle(
|
||||||
onTap: appMode.canSearch ? _goToSearch : null,
|
onTap: appMode.canNavigate ? _goToSearch : null,
|
||||||
child: title,
|
child: title,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -380,8 +380,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
|
||||||
selector: (context, mq) => mq.effectiveBottomPadding,
|
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||||
builder: (context, mqPaddingBottom, child) {
|
builder: (context, mqPaddingBottom, child) {
|
||||||
return Selector<Settings, bool>(
|
return Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.showBottomNavigationBar,
|
selector: (context, s) => s.enableBottomNavigationBar,
|
||||||
builder: (context, showBottomNavigationBar, child) {
|
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;
|
final navBarHeight = showBottomNavigationBar ? AppBottomNavBar.height : 0;
|
||||||
return Selector<SectionedListLayout<AvesEntry>, List<SectionLayout>>(
|
return Selector<SectionedListLayout<AvesEntry>, List<SectionLayout>>(
|
||||||
selector: (context, layout) => layout.sectionLayouts,
|
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/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.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/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/collection/collection_grid.dart';
|
import 'package:aves/widgets/collection/collection_grid.dart';
|
||||||
|
@ -79,7 +79,6 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
|
||||||
final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
|
final liveFilter = _collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: SelectionProvider<AvesEntry>(
|
child: SelectionProvider<AvesEntry>(
|
||||||
|
@ -87,8 +86,10 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
selector: (context, selection) => selection.selectedItems.isNotEmpty,
|
selector: (context, selection) => selection.selectedItems.isNotEmpty,
|
||||||
builder: (context, hasSelection, child) {
|
builder: (context, hasSelection, child) {
|
||||||
return Selector<Settings, bool>(
|
return Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.showBottomNavigationBar,
|
selector: (context, s) => s.enableBottomNavigationBar,
|
||||||
builder: (context, showBottomNavigationBar, child) {
|
builder: (context, enableBottomNavigationBar, child) {
|
||||||
|
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||||
|
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||||
return NotificationListener<DraggableScrollBarNotification>(
|
return NotificationListener<DraggableScrollBarNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
_draggableScrollBarEventStreamController.add(notification.event);
|
_draggableScrollBarEventStreamController.add(notification.event);
|
||||||
|
@ -126,25 +127,7 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: appMode == AppMode.pickMultipleMediaExternal && hasSelection
|
floatingActionButton: _buildFab(context, 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,
|
|
||||||
drawer: AppDrawer(currentCollection: _collection),
|
drawer: AppDrawer(currentCollection: _collection),
|
||||||
bottomNavigationBar: showBottomNavigationBar
|
bottomNavigationBar: showBottomNavigationBar
|
||||||
? AppBottomNavBar(
|
? 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 {
|
Future<void> _checkInitHighlight() async {
|
||||||
final highlightTest = widget.highlightTest;
|
final highlightTest = widget.highlightTest;
|
||||||
if (highlightTest == null) return;
|
if (highlightTest == null) return;
|
||||||
|
|
|
@ -65,7 +65,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
return isSelecting && selectedItemCount == itemCount;
|
return isSelecting && selectedItemCount == itemCount;
|
||||||
// browsing
|
// browsing
|
||||||
case EntrySetAction.searchCollection:
|
case EntrySetAction.searchCollection:
|
||||||
return appMode.canSearch && !isSelecting;
|
return appMode.canNavigate && !isSelecting;
|
||||||
case EntrySetAction.toggleTitleSearch:
|
case EntrySetAction.toggleTitleSearch:
|
||||||
return !isSelecting;
|
return !isSelecting;
|
||||||
case EntrySetAction.addShortcut:
|
case EntrySetAction.addShortcut:
|
||||||
|
|
|
@ -14,7 +14,7 @@ class FilterBar extends StatefulWidget {
|
||||||
FilterBar({
|
FilterBar({
|
||||||
super.key,
|
super.key,
|
||||||
required Set<CollectionFilter> filters,
|
required Set<CollectionFilter> filters,
|
||||||
required this.removable,
|
this.removable = false,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
}) : filters = List<CollectionFilter>.from(filters)..sort();
|
}) : 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/selection.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/enums.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.dart';
|
||||||
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||||
|
@ -44,7 +44,7 @@ class InteractiveTile extends StatelessWidget {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AppMode.pickSingleMediaExternal:
|
case AppMode.pickSingleMediaExternal:
|
||||||
ViewerService.pick([entry.uri]);
|
IntentService.submitPickedItems([entry.uri]);
|
||||||
break;
|
break;
|
||||||
case AppMode.pickMultipleMediaExternal:
|
case AppMode.pickMultipleMediaExternal:
|
||||||
final selection = context.read<Selection<AvesEntry>>();
|
final selection = context.read<Selection<AvesEntry>>();
|
||||||
|
@ -53,6 +53,7 @@ class InteractiveTile extends StatelessWidget {
|
||||||
case AppMode.pickMediaInternal:
|
case AppMode.pickMediaInternal:
|
||||||
Navigator.pop(context, entry);
|
Navigator.pop(context, entry);
|
||||||
break;
|
break;
|
||||||
|
case AppMode.pickCollectionFiltersExternal:
|
||||||
case AppMode.pickFilterInternal:
|
case AppMode.pickFilterInternal:
|
||||||
case AppMode.screenSaver:
|
case AppMode.screenSaver:
|
||||||
case AppMode.setWallpaper:
|
case AppMode.setWallpaper:
|
||||||
|
|
|
@ -78,7 +78,7 @@ class AvesFilterChip extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
static Future<void> showDefaultLongPressMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
|
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 = [
|
final actions = [
|
||||||
if (filter is AlbumFilter) ChipAction.goToAlbumPage,
|
if (filter is AlbumFilter) ChipAction.goToAlbumPage,
|
||||||
if ((filter is LocationFilter && filter.level == LocationLevel.country)) ChipAction.goToCountryPage,
|
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;
|
return isSelecting && selectedItemCount == itemCount;
|
||||||
// browsing
|
// browsing
|
||||||
case ChipSetAction.search:
|
case ChipSetAction.search:
|
||||||
return appMode.canSearch && !isSelecting;
|
return appMode.canNavigate && !isSelecting;
|
||||||
case ChipSetAction.createAlbum:
|
case ChipSetAction.createAlbum:
|
||||||
return false;
|
return false;
|
||||||
// browsing or selecting
|
// browsing or selecting
|
||||||
|
|
|
@ -77,7 +77,7 @@ class _FilterGridAppBarState<T extends CollectionFilter> extends State<FilterGri
|
||||||
return AvesAppBar(
|
return AvesAppBar(
|
||||||
contentHeight: kToolbarHeight,
|
contentHeight: kToolbarHeight,
|
||||||
leading: _buildAppBarLeading(
|
leading: _buildAppBarLeading(
|
||||||
hasDrawer: appMode.hasDrawer,
|
hasDrawer: appMode.canNavigate,
|
||||||
isSelecting: isSelecting,
|
isSelecting: isSelecting,
|
||||||
),
|
),
|
||||||
title: _buildAppBarTitle(isSelecting),
|
title: _buildAppBarTitle(isSelecting),
|
||||||
|
@ -127,7 +127,7 @@ class _FilterGridAppBarState<T extends CollectionFilter> extends State<FilterGri
|
||||||
} else {
|
} else {
|
||||||
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||||
return InteractiveAppBarTitle(
|
return InteractiveAppBarTitle(
|
||||||
onTap: appMode.canSearch ? _goToSearch : null,
|
onTap: appMode.canNavigate ? _goToSearch : null,
|
||||||
child: SourceStateAwareAppBarTitle(
|
child: SourceStateAwareAppBarTitle(
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
source: source,
|
source: source,
|
||||||
|
|
|
@ -74,8 +74,10 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaQueryDataProvider(
|
return MediaQueryDataProvider(
|
||||||
child: Selector<Settings, bool>(
|
child: Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.showBottomNavigationBar,
|
selector: (context, s) => s.enableBottomNavigationBar,
|
||||||
builder: (context, showBottomNavigationBar, child) {
|
builder: (context, enableBottomNavigationBar, child) {
|
||||||
|
final canNavigate = context.select<ValueNotifier<AppMode>, bool>((v) => v.value.canNavigate);
|
||||||
|
final showBottomNavigationBar = canNavigate && enableBottomNavigationBar;
|
||||||
return NotificationListener<DraggableScrollBarNotification>(
|
return NotificationListener<DraggableScrollBarNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
_draggableScrollBarEventStreamController.add(notification.event);
|
_draggableScrollBarEventStreamController.add(notification.event);
|
||||||
|
@ -524,8 +526,10 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
selector: (context, mq) => mq.effectiveBottomPadding,
|
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||||
builder: (context, mqPaddingBottom, child) {
|
builder: (context, mqPaddingBottom, child) {
|
||||||
return Selector<Settings, bool>(
|
return Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.showBottomNavigationBar,
|
selector: (context, s) => s.enableBottomNavigationBar,
|
||||||
builder: (context, showBottomNavigationBar, child) {
|
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;
|
final navBarHeight = showBottomNavigationBar ? AppBottomNavBar.height : 0;
|
||||||
return DraggableScrollbar(
|
return DraggableScrollbar(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
|
|
@ -50,6 +50,7 @@ class _InteractiveFilterTileState<T extends CollectionFilter> extends State<Inte
|
||||||
final appMode = context.read<ValueNotifier<AppMode>>().value;
|
final appMode = context.read<ValueNotifier<AppMode>>().value;
|
||||||
switch (appMode) {
|
switch (appMode) {
|
||||||
case AppMode.main:
|
case AppMode.main:
|
||||||
|
case AppMode.pickCollectionFiltersExternal:
|
||||||
case AppMode.pickSingleMediaExternal:
|
case AppMode.pickSingleMediaExternal:
|
||||||
case AppMode.pickMultipleMediaExternal:
|
case AppMode.pickMultipleMediaExternal:
|
||||||
final selection = context.read<Selection<FilterGridItem<T>>>();
|
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/analysis_service.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/global_search.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/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/routes.dart';
|
import 'package:aves/widgets/common/behaviour/routes.dart';
|
||||||
|
@ -47,10 +47,11 @@ class HomePage extends StatefulWidget {
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
AvesEntry? _viewerEntry;
|
AvesEntry? _viewerEntry;
|
||||||
String? _shortcutRouteName, _shortcutSearchQuery;
|
String? _initialRouteName, _initialSearchQuery;
|
||||||
Set<String>? _shortcutFilters;
|
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 actionScreenSaver = 'screen_saver';
|
||||||
static const actionScreenSaverSettings = 'screen_saver_settings';
|
static const actionScreenSaverSettings = 'screen_saver_settings';
|
||||||
static const actionSearch = 'search';
|
static const actionSearch = 'search';
|
||||||
|
@ -86,7 +87,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
var appMode = AppMode.main;
|
var appMode = AppMode.main;
|
||||||
final intentData = widget.intentData ?? await ViewerService.getIntentData();
|
final intentData = widget.intentData ?? await IntentService.getIntentData();
|
||||||
final intentAction = intentData['action'];
|
final intentAction = intentData['action'];
|
||||||
|
|
||||||
if (!{actionScreenSaver, actionSetWallpaper}.contains(intentAction)) {
|
if (!{actionScreenSaver, actionSetWallpaper}.contains(intentAction)) {
|
||||||
|
@ -108,7 +109,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
appMode = AppMode.view;
|
appMode = AppMode.view;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case actionPick:
|
case actionPickItems:
|
||||||
// TODO TLAD apply pick mimetype(s)
|
// TODO TLAD apply pick mimetype(s)
|
||||||
// some apps define multiple types, separated by a space (maybe other signs too, like `,` `;`?)
|
// some apps define multiple types, separated by a space (maybe other signs too, like `,` `;`?)
|
||||||
String? pickMimeTypes = intentData['mimeType'];
|
String? pickMimeTypes = intentData['mimeType'];
|
||||||
|
@ -116,16 +117,19 @@ class _HomePageState extends State<HomePage> {
|
||||||
debugPrint('pick mimeType=$pickMimeTypes multiple=$multiple');
|
debugPrint('pick mimeType=$pickMimeTypes multiple=$multiple');
|
||||||
appMode = multiple ? AppMode.pickMultipleMediaExternal : AppMode.pickSingleMediaExternal;
|
appMode = multiple ? AppMode.pickMultipleMediaExternal : AppMode.pickSingleMediaExternal;
|
||||||
break;
|
break;
|
||||||
|
case actionPickCollectionFilters:
|
||||||
|
appMode = AppMode.pickCollectionFiltersExternal;
|
||||||
|
break;
|
||||||
case actionScreenSaver:
|
case actionScreenSaver:
|
||||||
appMode = AppMode.screenSaver;
|
appMode = AppMode.screenSaver;
|
||||||
_shortcutRouteName = ScreenSaverPage.routeName;
|
_initialRouteName = ScreenSaverPage.routeName;
|
||||||
break;
|
break;
|
||||||
case actionScreenSaverSettings:
|
case actionScreenSaverSettings:
|
||||||
_shortcutRouteName = ScreenSaverSettingsPage.routeName;
|
_initialRouteName = ScreenSaverSettingsPage.routeName;
|
||||||
break;
|
break;
|
||||||
case actionSearch:
|
case actionSearch:
|
||||||
_shortcutRouteName = CollectionSearchDelegate.pageRouteName;
|
_initialRouteName = CollectionSearchDelegate.pageRouteName;
|
||||||
_shortcutSearchQuery = intentData['query'];
|
_initialSearchQuery = intentData['query'];
|
||||||
break;
|
break;
|
||||||
case actionSetWallpaper:
|
case actionSetWallpaper:
|
||||||
appMode = AppMode.setWallpaper;
|
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
|
// do not use 'route' as extra key, as the Flutter framework acts on it
|
||||||
final extraRoute = intentData['page'];
|
final extraRoute = intentData['page'];
|
||||||
if (allowedShortcutRoutes.contains(extraRoute)) {
|
if (allowedShortcutRoutes.contains(extraRoute)) {
|
||||||
_shortcutRouteName = extraRoute;
|
_initialRouteName = extraRoute;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final extraFilters = intentData['filters'];
|
final extraFilters = intentData['filters'];
|
||||||
_shortcutFilters = extraFilters != null ? (extraFilters as List).cast<String>().toSet() : null;
|
_initialFilters = extraFilters != null ? (extraFilters as List).cast<String>().map(CollectionFilter.fromJson).whereNotNull().toSet() : null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
context.read<ValueNotifier<AppMode>>().value = appMode;
|
context.read<ValueNotifier<AppMode>>().value = appMode;
|
||||||
unawaited(reportService.setCustomKey('app_mode', appMode.toString()));
|
unawaited(reportService.setCustomKey('app_mode', appMode.toString()));
|
||||||
|
@ -150,6 +154,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
|
|
||||||
switch (appMode) {
|
switch (appMode) {
|
||||||
case AppMode.main:
|
case AppMode.main:
|
||||||
|
case AppMode.pickCollectionFiltersExternal:
|
||||||
case AppMode.pickSingleMediaExternal:
|
case AppMode.pickSingleMediaExternal:
|
||||||
case AppMode.pickMultipleMediaExternal:
|
case AppMode.pickMultipleMediaExternal:
|
||||||
unawaited(GlobalSearch.registerCallback());
|
unawaited(GlobalSearch.registerCallback());
|
||||||
|
@ -281,8 +286,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
routeName = CollectionPage.routeName;
|
routeName = CollectionPage.routeName;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
routeName = _shortcutRouteName ?? settings.homePage.routeName;
|
routeName = _initialRouteName ?? settings.homePage.routeName;
|
||||||
filters = (_shortcutFilters ?? {}).map(CollectionFilter.fromJson).toSet();
|
filters = _initialFilters ?? {};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
|
@ -310,7 +315,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
searchFieldLabel: context.l10n.searchCollectionFieldHint,
|
searchFieldLabel: context.l10n.searchCollectionFieldHint,
|
||||||
source: source,
|
source: source,
|
||||||
canPop: false,
|
canPop: false,
|
||||||
initialQuery: _shortcutSearchQuery,
|
initialQuery: _initialSearchQuery,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case CollectionPage.routeName:
|
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/favourite.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/settings/settings.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:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AppBottomNavBar extends StatefulWidget {
|
class AppBottomNavBar extends StatefulWidget {
|
||||||
final Stream<DraggableScrollBarEvent> events;
|
final Stream<DraggableScrollBarEvent> events;
|
||||||
|
@ -163,8 +165,10 @@ class NavBarPaddingSliver extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Selector<Settings, bool>(
|
child: Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.showBottomNavigationBar,
|
selector: (context, s) => s.enableBottomNavigationBar,
|
||||||
builder: (context, showBottomNavigationBar, child) {
|
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);
|
return SizedBox(height: showBottomNavigationBar ? AppBottomNavBar.height : 0);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -60,8 +60,8 @@ class SettingsTileShowBottomNavigationBar extends SettingsTile {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => SettingsSwitchListTile(
|
Widget build(BuildContext context) => SettingsSwitchListTile(
|
||||||
selector: (context, s) => s.showBottomNavigationBar,
|
selector: (context, s) => s.enableBottomNavigationBar,
|
||||||
onChanged: (v) => settings.showBottomNavigationBar = v,
|
onChanged: (v) => settings.enableBottomNavigationBar = v,
|
||||||
title: title(context),
|
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/enums.dart';
|
||||||
import 'package:aves/model/settings/enums/slideshow_interval.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/slideshow_video_playback.dart';
|
||||||
import 'package:aves/model/settings/enums/viewer_transition.dart';
|
import 'package:aves/model/settings/enums/viewer_transition.dart';
|
||||||
import 'package:aves/model/settings/settings.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/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/settings/common/tiles.dart';
|
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ScreenSaverSettingsPage extends StatelessWidget {
|
class ScreenSaverSettingsPage extends StatelessWidget {
|
||||||
static const routeName = '/settings/screen_saver';
|
static const routeName = '/settings/screen_saver';
|
||||||
|
@ -14,9 +19,10 @@ class ScreenSaverSettingsPage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.l10n.settingsScreenSaverTitle),
|
title: Text(l10n.settingsScreenSaverTitle),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
@ -26,24 +32,77 @@ class ScreenSaverSettingsPage extends StatelessWidget {
|
||||||
getName: (context, v) => v.getName(context),
|
getName: (context, v) => v.getName(context),
|
||||||
selector: (context, s) => s.screenSaverTransition,
|
selector: (context, s) => s.screenSaverTransition,
|
||||||
onSelection: (v) => settings.screenSaverTransition = v,
|
onSelection: (v) => settings.screenSaverTransition = v,
|
||||||
tileTitle: context.l10n.settingsSlideshowTransitionTile,
|
tileTitle: l10n.settingsSlideshowTransitionTile,
|
||||||
dialogTitle: context.l10n.settingsSlideshowTransitionTitle,
|
dialogTitle: l10n.settingsSlideshowTransitionTitle,
|
||||||
),
|
),
|
||||||
SettingsSelectionListTile<SlideshowInterval>(
|
SettingsSelectionListTile<SlideshowInterval>(
|
||||||
values: SlideshowInterval.values,
|
values: SlideshowInterval.values,
|
||||||
getName: (context, v) => v.getName(context),
|
getName: (context, v) => v.getName(context),
|
||||||
selector: (context, s) => s.screenSaverInterval,
|
selector: (context, s) => s.screenSaverInterval,
|
||||||
onSelection: (v) => settings.screenSaverInterval = v,
|
onSelection: (v) => settings.screenSaverInterval = v,
|
||||||
tileTitle: context.l10n.settingsSlideshowIntervalTile,
|
tileTitle: l10n.settingsSlideshowIntervalTile,
|
||||||
dialogTitle: context.l10n.settingsSlideshowIntervalTitle,
|
dialogTitle: l10n.settingsSlideshowIntervalTitle,
|
||||||
),
|
),
|
||||||
SettingsSelectionListTile<SlideshowVideoPlayback>(
|
SettingsSelectionListTile<SlideshowVideoPlayback>(
|
||||||
values: SlideshowVideoPlayback.values,
|
values: SlideshowVideoPlayback.values,
|
||||||
getName: (context, v) => v.getName(context),
|
getName: (context, v) => v.getName(context),
|
||||||
selector: (context, s) => s.screenSaverVideoPlayback,
|
selector: (context, s) => s.screenSaverVideoPlayback,
|
||||||
onSelection: (v) => settings.screenSaverVideoPlayback = v,
|
onSelection: (v) => settings.screenSaverVideoPlayback = v,
|
||||||
tileTitle: context.l10n.settingsSlideshowVideoPlaybackTile,
|
tileTitle: l10n.settingsSlideshowVideoPlaybackTile,
|
||||||
dialogTitle: context.l10n.settingsSlideshowVideoPlaybackTitle,
|
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(
|
final originalCollection = CollectionLens(
|
||||||
source: source,
|
source: source,
|
||||||
// TODO TLAD [screensaver] custom filters
|
filters: settings.screenSaverCollectionFilters,
|
||||||
);
|
);
|
||||||
var entries = originalCollection.sortedEntries;
|
var entries = originalCollection.sortedEntries;
|
||||||
if (settings.screenSaverVideoPlayback == SlideshowVideoPlayback.skip) {
|
if (settings.screenSaverVideoPlayback == SlideshowVideoPlayback.skip) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ Future<void> configureAndLaunch() async {
|
||||||
// navigation
|
// navigation
|
||||||
..keepScreenOn = KeepScreenOn.always
|
..keepScreenOn = KeepScreenOn.always
|
||||||
..homePage = HomePageSetting.collection
|
..homePage = HomePageSetting.collection
|
||||||
..showBottomNavigationBar = true
|
..enableBottomNavigationBar = true
|
||||||
// collection
|
// collection
|
||||||
..collectionSectionFactor = EntryGroupFactor.month
|
..collectionSectionFactor = EntryGroupFactor.month
|
||||||
..collectionSortFactor = EntrySortFactor.date
|
..collectionSortFactor = EntrySortFactor.date
|
||||||
|
|
|
@ -27,7 +27,7 @@ Future<void> configureAndLaunch() async {
|
||||||
// navigation
|
// navigation
|
||||||
..keepScreenOn = KeepScreenOn.always
|
..keepScreenOn = KeepScreenOn.always
|
||||||
..homePage = HomePageSetting.collection
|
..homePage = HomePageSetting.collection
|
||||||
..showBottomNavigationBar = true
|
..enableBottomNavigationBar = true
|
||||||
// collection
|
// collection
|
||||||
..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions
|
..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions
|
||||||
// viewer
|
// viewer
|
||||||
|
|
Loading…
Reference in a new issue