diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fb3f7929..35a9d80b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.11.4] - 2024-07-09
+
+### Added
+
+- Collection: stack RAW and JPEG with same file names
+- Collection: ask to rename/replace/skip when converting items with name conflict
+- Export: bulk converting motion photos to still images
+- Explorer: view folder tree and filter paths
+
+### Fixed
+
+- switching to PiP when changing device orientation on Android >=13
+- handling wallpaper intent without URI
+- sizing widgets with some launchers on Android >=12
+
## [v1.11.3] - 2024-06-17
### Added
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 7175da16d..bf093bc5a 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -196,9 +196,9 @@ repositories {
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
- implementation "androidx.appcompat:appcompat:1.6.1"
+ implementation "androidx.appcompat:appcompat:1.7.0"
implementation 'androidx.core:core-ktx:1.13.1'
- implementation 'androidx.lifecycle:lifecycle-process:2.8.0'
+ implementation 'androidx.lifecycle:lifecycle-process:2.8.2'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 67ad18af7..6d1cb4a88 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -120,6 +120,7 @@
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
+ android:showWhenLocked="true"
android:supportsRtl="true"
tools:targetApi="tiramisu">
+
@@ -163,6 +165,7 @@
+
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt
index a0c1a0ab9..af4bf81bf 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt
@@ -27,6 +27,10 @@ import deckers.thibault.aves.utils.LogUtils
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
@@ -34,13 +38,17 @@ import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class AnalysisWorker(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters) {
+ private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private var workCont: Continuation? = null
private var flutterEngine: FlutterEngine? = null
private var backgroundChannel: MethodChannel? = null
override suspend fun doWork(): Result {
- createNotificationChannel()
- setForeground(createForegroundInfo())
+ defaultScope.launch {
+ // prevent ANR triggered by slow operations in main thread
+ createNotificationChannel()
+ setForeground(createForegroundInfo())
+ }
suspendCoroutine { cont ->
workCont = cont
onStart()
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
index 81933f251..6e5e695d3 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/HomeWidgetProvider.kt
@@ -12,6 +12,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
+import android.util.SizeF
import android.widget.RemoteViews
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
@@ -40,12 +41,16 @@ class HomeWidgetProvider : AppWidgetProvider() {
for (widgetId in appWidgetIds) {
val widgetInfo = appWidgetManager.getAppWidgetOptions(widgetId)
- defaultScope.launch {
- val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false)
- updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, backgroundProps)
+ goAsync().run {
+ defaultScope.launch {
+ val backgroundProps = getProps(context, widgetId, widgetInfo, drawEntryImage = false)
+ updateWidgetImage(context, appWidgetManager, widgetId, backgroundProps)
+
+ val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false)
+ updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
- val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = false)
- updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
+ finish()
+ }
}
}
}
@@ -61,20 +66,32 @@ class HomeWidgetProvider : AppWidgetProvider() {
imageByteFetchJob = defaultScope.launch {
delay(500)
val imageProps = getProps(context, widgetId, widgetInfo, drawEntryImage = true, reuseEntry = true)
- updateWidgetImage(context, appWidgetManager, widgetId, widgetInfo, imageProps)
+ updateWidgetImage(context, appWidgetManager, widgetId, imageProps)
}
}
private fun getDevicePixelRatio(): Float = Resources.getSystem().displayMetrics.density
- private fun getWidgetSizePx(context: Context, widgetInfo: Bundle): Pair {
- val devicePixelRatio = getDevicePixelRatio()
- val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
- val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
- val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
- val widthPx = (widgetInfo.getInt(widthKey) * devicePixelRatio).roundToInt()
- val heightPx = (widgetInfo.getInt(heightKey) * devicePixelRatio).roundToInt()
- return Pair(widthPx, heightPx)
+ private fun getWidgetSizesDip(context: Context, widgetInfo: Bundle): List {
+ var sizes: List? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, SizeF::class.java)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ @Suppress("DEPRECATION")
+ widgetInfo.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES)
+ } else {
+ null
+ }
+
+ if (sizes.isNullOrEmpty()) {
+ val isPortrait = context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
+ val widthKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
+ val heightKey = if (isPortrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
+ val widthDip = widgetInfo.getInt(widthKey)
+ val heightDip = widgetInfo.getInt(heightKey)
+ sizes = listOf(SizeF(widthDip.toFloat(), heightDip.toFloat()))
+ }
+
+ return sizes.map { size -> hashMapOf("widthDip" to size.width, "heightDip" to size.height) }
}
private suspend fun getProps(
@@ -84,8 +101,11 @@ class HomeWidgetProvider : AppWidgetProvider() {
drawEntryImage: Boolean,
reuseEntry: Boolean = false,
): FieldMap? {
- val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
- if (widthPx == 0 || heightPx == 0) return null
+ val sizesDip = getWidgetSizesDip(context, widgetInfo)
+ if (sizesDip.isEmpty()) return null
+
+ val sizeDip = sizesDip.first()
+ if (sizeDip["widthDip"] == 0 || sizeDip["heightDip"] == 0) return null
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
@@ -98,13 +118,16 @@ class HomeWidgetProvider : AppWidgetProvider() {
FlutterUtils.runOnUiThread {
channel.invokeMethod("drawWidget", hashMapOf(
"widgetId" to widgetId,
- "widthPx" to widthPx,
- "heightPx" to heightPx,
+ "sizesDip" to sizesDip,
"devicePixelRatio" to getDevicePixelRatio(),
"drawEntryImage" to drawEntryImage,
"reuseEntry" to reuseEntry,
"isSystemThemeDark" to isNightModeOn,
- ), object : MethodChannel.Result {
+ ).apply {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
+ }
+ }, object : MethodChannel.Result {
override fun success(result: Any?) {
cont.resume(result)
}
@@ -123,7 +146,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
@Suppress("unchecked_cast")
return props as FieldMap?
} catch (e: Exception) {
- Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId widthPx=$widthPx heightPx=$heightPx", e)
+ Log.e(LOG_TAG, "failed to draw widget for widgetId=$widgetId sizesPx=$sizesDip", e)
}
return null
}
@@ -132,36 +155,83 @@ class HomeWidgetProvider : AppWidgetProvider() {
context: Context,
appWidgetManager: AppWidgetManager,
widgetId: Int,
- widgetInfo: Bundle,
props: FieldMap?,
) {
props ?: return
- val bytes = props["bytes"] as ByteArray?
+ val bytesBySizeDip = (props["bytesBySizeDip"] as List<*>?)?.mapNotNull {
+ if (it is Map<*, *>) {
+ val widthDip = (it["widthDip"] as Number?)?.toFloat()
+ val heightDip = (it["heightDip"] as Number?)?.toFloat()
+ val bytes = it["bytes"] as ByteArray?
+ if (widthDip != null && heightDip != null && bytes != null) {
+ Pair(SizeF(widthDip, heightDip), bytes)
+ } else null
+ } else null
+ }
val updateOnTap = props["updateOnTap"] as Boolean?
- if (bytes == null || updateOnTap == null) {
+ if (bytesBySizeDip == null || updateOnTap == null) {
Log.e(LOG_TAG, "missing arguments")
return
}
- val (widthPx, heightPx) = getWidgetSizePx(context, widgetInfo)
- if (widthPx == 0 || heightPx == 0) return
+ if (bytesBySizeDip.isEmpty()) {
+ Log.e(LOG_TAG, "empty image list")
+ return
+ }
- try {
- val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
- bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
+ val bitmaps = ArrayList()
+
+ fun createRemoteViewsForSize(
+ context: Context,
+ widgetId: Int,
+ sizeDip: SizeF,
+ bytes: ByteArray,
+ updateOnTap: Boolean,
+ ): RemoteViews? {
+ val devicePixelRatio = getDevicePixelRatio()
+ val widthPx = (sizeDip.width * devicePixelRatio).roundToInt()
+ val heightPx = (sizeDip.height * devicePixelRatio).roundToInt()
+
+ try {
+ val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888).also {
+ bitmaps.add(it)
+ it.copyPixelsFromBuffer(ByteBuffer.wrap(bytes))
+ }
- val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
+ val pendingIntent = if (updateOnTap) buildUpdateIntent(context, widgetId) else buildOpenAppIntent(context, widgetId)
- val views = RemoteViews(context.packageName, R.layout.app_widget).apply {
- setImageViewBitmap(R.id.widget_img, bitmap)
- setOnClickPendingIntent(R.id.widget_img, pendingIntent)
+ return RemoteViews(context.packageName, R.layout.app_widget).apply {
+ setImageViewBitmap(R.id.widget_img, bitmap)
+ setOnClickPendingIntent(R.id.widget_img, pendingIntent)
+ }
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "failed to draw widget", e)
}
+ return null
+ }
- appWidgetManager.updateAppWidget(widgetId, views)
- bitmap.recycle()
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // multiple rendering for all possible sizes
+ val views = RemoteViews(
+ bytesBySizeDip.associateBy(
+ { (sizeDip, _) -> sizeDip },
+ { (sizeDip, bytes) -> createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap) },
+ ).filterValues { it != null }.mapValues { (_, view) -> view!! }
+ )
+ appWidgetManager.updateAppWidget(widgetId, views)
+ } else {
+ // single rendering
+ val (sizeDip, bytes) = bytesBySizeDip.first()
+ val views = createRemoteViewsForSize(context, widgetId, sizeDip, bytes, updateOnTap)
+ appWidgetManager.updateAppWidget(widgetId, views)
+ }
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to draw widget", e)
+ } finally {
+ bitmaps.forEach { it.recycle() }
+ bitmaps.clear()
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
index 96dd14303..0166fdff4 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
@@ -10,6 +10,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
+import android.os.TransactionTooLargeException
import android.provider.MediaStore
import android.util.Log
import androidx.annotation.RequiresApi
@@ -21,6 +22,7 @@ import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.AccessibilityHandler
import deckers.thibault.aves.channel.calls.AnalysisHandler
import deckers.thibault.aves.channel.calls.AppAdapterHandler
+import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.DebugHandler
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
@@ -36,6 +38,7 @@ import deckers.thibault.aves.channel.calls.MetadataEditHandler
import deckers.thibault.aves.channel.calls.MetadataFetchHandler
import deckers.thibault.aves.channel.calls.SecurityHandler
import deckers.thibault.aves.channel.calls.StorageHandler
+import deckers.thibault.aves.channel.calls.WallpaperHandler
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.ActivityResultStreamHandler
@@ -135,6 +138,7 @@ open class MainActivity : FlutterFragmentActivity() {
MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
MethodChannel(messenger, MediaEditHandler.CHANNEL).setMethodCallHandler(MediaEditHandler(this))
MethodChannel(messenger, MetadataEditHandler.CHANNEL).setMethodCallHandler(MetadataEditHandler(this))
+ MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
// - need Activity
MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
@@ -168,7 +172,7 @@ open class MainActivity : FlutterFragmentActivity() {
intentDataMap.clear()
}
- "submitPickedItems" -> submitPickedItems(call)
+ "submitPickedItems" -> safe(call, result, ::submitPickedItems)
"submitPickedCollectionFilters" -> submitPickedCollectionFilters(call)
}
}
@@ -301,16 +305,32 @@ open class MainActivity : FlutterFragmentActivity() {
Intent.ACTION_VIEW,
Intent.ACTION_SEND,
MediaStore.ACTION_REVIEW,
+ MediaStore.ACTION_REVIEW_SECURE,
"com.android.camera.action.REVIEW",
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
(intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri ->
// MIME type is optional
val type = intent.type ?: intent.resolveType(this)
- return hashMapOf(
+ val fields = hashMapOf(
INTENT_DATA_KEY_ACTION to INTENT_ACTION_VIEW,
INTENT_DATA_KEY_MIME_TYPE to type,
INTENT_DATA_KEY_URI to uri.toString(),
)
+
+ if (action == MediaStore.ACTION_REVIEW_SECURE) {
+ val uris = ArrayList()
+ intent.clipData?.let { clipData ->
+ for (i in 0 until clipData.itemCount) {
+ clipData.getItemAt(i).uri?.let { uris.add(it.toString()) }
+ }
+ }
+ fields[INTENT_DATA_KEY_SECURE_URIS] = uris
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && intent.hasExtra(MediaStore.EXTRA_BRIGHTNESS)) {
+ fields[INTENT_DATA_KEY_BRIGHTNESS] = intent.getFloatExtra(MediaStore.EXTRA_BRIGHTNESS, 0f)
+ }
+
+ return fields
}
}
@@ -390,28 +410,36 @@ open class MainActivity : FlutterFragmentActivity() {
return null
}
- private fun submitPickedItems(call: MethodCall) {
+ open fun submitPickedItems(call: MethodCall, result: MethodChannel.Result) {
val pickedUris = call.argument>("uris")
- if (!pickedUris.isNullOrEmpty()) {
- val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
- val intent = Intent().apply {
- val firstUri = toUri(pickedUris.first())
- if (pickedUris.size == 1) {
- data = firstUri
- } else {
- clipData = ClipData.newUri(contentResolver, null, firstUri).apply {
- pickedUris.drop(1).forEach {
- addItem(ClipData.Item(toUri(it)))
+ try {
+ if (!pickedUris.isNullOrEmpty()) {
+ val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
+ val intent = Intent().apply {
+ val firstUri = toUri(pickedUris.first())
+ if (pickedUris.size == 1) {
+ data = firstUri
+ } else {
+ clipData = ClipData.newUri(contentResolver, null, firstUri).apply {
+ pickedUris.drop(1).forEach {
+ addItem(ClipData.Item(toUri(it)))
+ }
}
}
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ setResult(RESULT_OK, intent)
+ } else {
+ setResult(RESULT_CANCELED)
+ }
+ finish()
+ } catch (e: Exception) {
+ if (e is TransactionTooLargeException || e.cause is TransactionTooLargeException) {
+ result.error("submitPickedItems-large", "transaction too large with ${pickedUris?.size} URIs", e)
+ } else {
+ result.error("submitPickedItems-exception", "failed to pick ${pickedUris?.size} URIs", e)
}
- setResult(RESULT_OK, intent)
- } else {
- setResult(RESULT_CANCELED)
}
- finish()
}
private fun submitPickedCollectionFilters(call: MethodCall) {
@@ -498,11 +526,13 @@ open class MainActivity : FlutterFragmentActivity() {
const val INTENT_DATA_KEY_ACTION = "action"
const val INTENT_DATA_KEY_ALLOW_MULTIPLE = "allowMultiple"
+ const val INTENT_DATA_KEY_BRIGHTNESS = "brightness"
const val INTENT_DATA_KEY_FILTERS = "filters"
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
const val INTENT_DATA_KEY_PAGE = "page"
const val INTENT_DATA_KEY_QUERY = "query"
const val INTENT_DATA_KEY_SAFE_MODE = "safeMode"
+ const val INTENT_DATA_KEY_SECURE_URIS = "secureUris"
const val INTENT_DATA_KEY_URI = "uri"
const val INTENT_DATA_KEY_WIDGET_ID = "widgetId"
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt
index 4aa32be1a..067ee8e34 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt
@@ -2,132 +2,54 @@ package deckers.thibault.aves
import android.content.Intent
import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import app.loup.streams_channel.StreamsChannel
-import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
-import deckers.thibault.aves.channel.calls.AccessibilityHandler
-import deckers.thibault.aves.channel.calls.DeviceHandler
-import deckers.thibault.aves.channel.calls.EmbeddedDataHandler
-import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
-import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
-import deckers.thibault.aves.channel.calls.MediaSessionHandler
-import deckers.thibault.aves.channel.calls.MetadataFetchHandler
-import deckers.thibault.aves.channel.calls.StorageHandler
-import deckers.thibault.aves.channel.calls.WallpaperHandler
-import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
-import deckers.thibault.aves.channel.calls.window.WindowHandler
-import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
-import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler
+import deckers.thibault.aves.channel.calls.AppAdapterHandler
import deckers.thibault.aves.model.FieldMap
-import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.getParcelableExtraCompat
-import io.flutter.embedding.android.FlutterFragmentActivity
-import io.flutter.embedding.engine.FlutterEngine
-import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
-class WallpaperActivity : FlutterFragmentActivity() {
- private lateinit var intentDataMap: FieldMap
- private lateinit var mediaSessionHandler: MediaSessionHandler
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- Log.i(LOG_TAG, "onCreate intent=$intent")
- intent.extras?.takeUnless { it.isEmpty }?.let {
- Log.i(LOG_TAG, "onCreate intent extras=$it")
- }
- intentDataMap = extractIntentData(intent)
- }
-
- override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
- super.configureFlutterEngine(flutterEngine)
- val messenger = flutterEngine.dartExecutor
-
- // notification: platform -> dart
- val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
- EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
- }
-
- // dart -> platform -> dart
- // - need Context
- mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
- MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
- MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
- MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
- MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
- MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler)
- MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
- MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
- // - need ContextWrapper
- MethodChannel(messenger, AccessibilityHandler.CHANNEL).setMethodCallHandler(AccessibilityHandler(this))
- MethodChannel(messenger, WallpaperHandler.CHANNEL).setMethodCallHandler(WallpaperHandler(this))
- // - need Activity
- MethodChannel(messenger, WindowHandler.CHANNEL).setMethodCallHandler(ActivityWindowHandler(this))
-
- // result streaming: dart -> platform ->->-> dart
- // - need Context
- StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL).setStreamHandlerFactory { args -> ImageByteStreamHandler(this, args) }
-
- // intent handling
- // detail fetch: dart -> platform
- MethodChannel(messenger, MainActivity.INTENT_CHANNEL).setMethodCallHandler { call, result -> onMethodCall(call, result) }
- }
-
- override fun onStart() {
- Log.i(LOG_TAG, "onStart")
- super.onStart()
-
- // as of Flutter v3.0.1, the window `viewInsets` and `viewPadding`
- // are incorrect on startup in some environments (e.g. API 29 emulator),
- // so we manually request to apply the insets to update the window metrics
- Handler(Looper.getMainLooper()).postDelayed({
- window.decorView.requestApplyInsets()
- }, 100)
- }
-
- override fun onDestroy() {
- mediaSessionHandler.dispose()
- super.onDestroy()
- }
-
- private fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
- when (call.method) {
- "getIntentData" -> {
- result.success(intentDataMap)
- intentDataMap.clear()
+class WallpaperActivity : MainActivity() {
+ private var originalIntent: String? = null
+
+ override fun extractIntentData(intent: Intent?): FieldMap {
+ if (intent != null) {
+ when (intent.action) {
+ Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
+ (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri ->
+ // MIME type is optional
+ val type = intent.type ?: intent.resolveType(this)
+ return hashMapOf(
+ INTENT_DATA_KEY_ACTION to INTENT_ACTION_SET_WALLPAPER,
+ INTENT_DATA_KEY_MIME_TYPE to type,
+ INTENT_DATA_KEY_URI to uri.toString(),
+ )
+ }
+
+ // if the media URI is not provided we need to pick one first
+ originalIntent = intent.action
+ intent.action = Intent.ACTION_PICK
+ }
}
}
+
+ return super.extractIntentData(intent)
}
- private fun extractIntentData(intent: Intent?): FieldMap {
- when (intent?.action) {
- Intent.ACTION_ATTACH_DATA, Intent.ACTION_SET_WALLPAPER -> {
- (intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri ->
- // MIME type is optional
- val type = intent.type ?: intent.resolveType(this)
- return hashMapOf(
- MainActivity.INTENT_DATA_KEY_ACTION to MainActivity.INTENT_ACTION_SET_WALLPAPER,
- MainActivity.INTENT_DATA_KEY_MIME_TYPE to type,
- MainActivity.INTENT_DATA_KEY_URI to uri.toString(),
- )
- }
- }
- Intent.ACTION_RUN -> {
- // flutter run
- }
- else -> {
- Log.w(LOG_TAG, "unhandled intent action=${intent?.action}")
+ override fun submitPickedItems(call: MethodCall, result: MethodChannel.Result) {
+ if (originalIntent != null) {
+ val pickedUris = call.argument>("uris")
+ if (!pickedUris.isNullOrEmpty()) {
+ val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
+ onNewIntent(Intent().apply {
+ action = originalIntent
+ data = toUri(pickedUris.first())
+ })
+ } else {
+ setResult(RESULT_CANCELED)
+ finish()
}
+ } else {
+ super.submitPickedItems(call, result)
}
- return HashMap()
- }
-
- companion object {
- private val LOG_TAG = LogUtils.createTag()
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
index 4d0001ce0..baee7c5e8 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
@@ -39,6 +39,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
"deleteEmptyDirectories" -> ioScope.launch { safe(call, result, ::deleteEmptyDirectories) }
"deleteTempDirectory" -> ioScope.launch { safe(call, result, ::deleteTempDirectory) }
+ "deleteExternalCache" -> ioScope.launch { safe(call, result, ::deleteExternalCache) }
"canRequestMediaFileBulkAccess" -> safe(call, result, ::canRequestMediaFileBulkAccess)
"canInsertMedia" -> safe(call, result, ::canInsertMedia)
else -> result.notImplemented()
@@ -49,16 +50,17 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
var internalCache = getFolderSize(context.cacheDir)
internalCache += getFolderSize(context.codeCacheDir)
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
+ val externalFilesDirs = context.getExternalFilesDirs(null)
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
val database = getFolderSize(File(dataDir, "databases"))
val flutter = getFolderSize(File(PathUtils.getDataDirectory(context)))
val vaults = getFolderSize(File(StorageUtils.getVaultRoot(context)))
- val trash = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
+ val trash = externalFilesDirs.mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
val internalData = getFolderSize(dataDir) - internalCache
- val externalData = context.getExternalFilesDirs(null).map(::getFolderSize).sum()
+ val externalData = externalFilesDirs.map(::getFolderSize).sum()
val miscData = internalData + externalData - (database + flutter + vaults + trash)
result.success(
@@ -224,6 +226,11 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
result.success(StorageUtils.deleteTempDirectory(context))
}
+ private fun deleteExternalCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ context.externalCacheDirs.filter { it.exists() }.forEach { it.deleteRecursively() }
+ result.success(true)
+ }
+
private fun canRequestMediaFileBulkAccess(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/NameConflictStrategy.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/NameConflictStrategy.kt
index a6c3bb5a7..21f59a7db 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/NameConflictStrategy.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/NameConflictStrategy.kt
@@ -1,5 +1,7 @@
package deckers.thibault.aves.model
+import java.io.File
+
enum class NameConflictStrategy {
RENAME, REPLACE, SKIP;
@@ -9,4 +11,6 @@ enum class NameConflictStrategy {
return valueOf(name.uppercase())
}
}
-}
\ No newline at end of file
+}
+
+class NameConflictResolution(var nameWithoutExtension: String?, var replacementFile: File?)
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
index be8dc5270..d992f1cef 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
@@ -41,6 +41,7 @@ import deckers.thibault.aves.metadata.xmp.GoogleXMP
import deckers.thibault.aves.model.AvesEntry
import deckers.thibault.aves.model.ExifOrientationOp
import deckers.thibault.aves.model.FieldMap
+import deckers.thibault.aves.model.NameConflictResolution
import deckers.thibault.aves.model.NameConflictStrategy
import deckers.thibault.aves.model.SourceEntry
import deckers.thibault.aves.utils.BitmapUtils
@@ -147,13 +148,14 @@ abstract class ImageProvider {
val oldFile = File(sourcePath)
if (oldFile.nameWithoutExtension != desiredNameWithoutExtension) {
oldFile.parent?.let { dir ->
- resolveTargetFileNameWithoutExtension(
+ val resolution = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = dir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = mimeType,
conflictStrategy = NameConflictStrategy.RENAME,
- )?.let { targetNameWithoutExtension ->
+ )
+ resolution.nameWithoutExtension?.let { targetNameWithoutExtension ->
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
val newFile = File(dir, targetFileName)
if (oldFile != newFile) {
@@ -266,7 +268,7 @@ abstract class ImageProvider {
exportMimeType: String,
): FieldMap {
val sourceMimeType = sourceEntry.mimeType
- val sourceUri = sourceEntry.uri
+ var sourceUri = sourceEntry.uri
val pageId = sourceEntry.pageId
var desiredNameWithoutExtension = if (sourceEntry.path != null) {
@@ -279,13 +281,17 @@ abstract class ImageProvider {
val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId
desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}"
}
- val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
+ val resolution = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = targetDir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = exportMimeType,
conflictStrategy = nameConflictStrategy,
- ) ?: return skippedFieldMap
+ )
+ val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
+ resolution.replacementFile?.let { file ->
+ sourceUri = Uri.fromFile(file)
+ }
val targetMimeType: String
val write: (OutputStream) -> Unit
@@ -391,6 +397,8 @@ abstract class ImageProvider {
} finally {
// clearing Glide target should happen after effectively writing the bitmap
Glide.with(activity).clear(target)
+
+ resolution.replacementFile?.delete()
}
}
@@ -470,7 +478,7 @@ abstract class ImageProvider {
}
val captureMimeType = MimeTypes.JPEG
- val targetNameWithoutExtension = try {
+ val resolution = try {
resolveTargetFileNameWithoutExtension(
contextWrapper = contextWrapper,
dir = targetDir,
@@ -483,6 +491,7 @@ abstract class ImageProvider {
return
}
+ val targetNameWithoutExtension = resolution.nameWithoutExtension
if (targetNameWithoutExtension == null) {
// skip it
callback.onSuccess(skippedFieldMap)
@@ -568,10 +577,13 @@ abstract class ImageProvider {
desiredNameWithoutExtension: String,
mimeType: String,
conflictStrategy: NameConflictStrategy,
- ): String? {
+ ): NameConflictResolution {
+ var resolvedName: String? = desiredNameWithoutExtension
+ var replacementFile: File? = null
+
val extension = extensionFor(mimeType)
val targetFile = File(dir, "$desiredNameWithoutExtension$extension")
- return when (conflictStrategy) {
+ when (conflictStrategy) {
NameConflictStrategy.RENAME -> {
var nameWithoutExtension = desiredNameWithoutExtension
var i = 0
@@ -579,24 +591,28 @@ abstract class ImageProvider {
i++
nameWithoutExtension = "$desiredNameWithoutExtension ($i)"
}
- nameWithoutExtension
+ resolvedName = nameWithoutExtension
}
NameConflictStrategy.REPLACE -> {
if (targetFile.exists()) {
+ // move replaced file to temp storage
+ // so that it can be used as a source for conversion or metadata copy
+ replacementFile = StorageUtils.createTempFile(contextWrapper).apply {
+ targetFile.transferTo(outputStream())
+ }
deletePath(contextWrapper, targetFile.path, mimeType)
}
- desiredNameWithoutExtension
}
NameConflictStrategy.SKIP -> {
if (targetFile.exists()) {
- null
- } else {
- desiredNameWithoutExtension
+ resolvedName = null
}
}
}
+
+ return NameConflictResolution(resolvedName, replacementFile)
}
// cf `MetadataFetchHandler.getCatalogMetadataByMetadataExtractor()` for a more thorough check
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
index faa55eef7..8fc3e5cef 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
@@ -562,13 +562,14 @@ class MediaStoreImageProvider : ImageProvider() {
}
val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")
- val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
+ val resolution = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = targetDir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = mimeType,
conflictStrategy = nameConflictStrategy,
- ) ?: return skippedFieldMap
+ )
+ val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
val sourceDocFile = DocumentFileCompat.fromSingleUri(activity, sourceUri)
val targetPath = createSingle(
diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
index b3da51120..4155b3508 100644
--- a/android/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -6,32 +6,32 @@
diff --git a/android/app/src/main/res/drawable/ic_launcher_mono.xml b/android/app/src/main/res/drawable/ic_launcher_mono.xml
index 605e3e3aa..9f78191d0 100644
--- a/android/app/src/main/res/drawable/ic_launcher_mono.xml
+++ b/android/app/src/main/res/drawable/ic_launcher_mono.xml
@@ -6,32 +6,32 @@
diff --git a/fastlane/metadata/android/en-US/changelogs/115.txt b/fastlane/metadata/android/en-US/changelogs/115.txt
deleted file mode 100644
index 4c8d7cd42..000000000
--- a/fastlane/metadata/android/en-US/changelogs/115.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.6:
-- detect HDR videos (but do not play them in their full HDR glory)
-- removing animations also applies to pop up menus
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11501.txt b/fastlane/metadata/android/en-US/changelogs/11501.txt
deleted file mode 100644
index 4c8d7cd42..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11501.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.6:
-- detect HDR videos (but do not play them in their full HDR glory)
-- removing animations also applies to pop up menus
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/116.txt b/fastlane/metadata/android/en-US/changelogs/116.txt
deleted file mode 100644
index 067ad57ef..000000000
--- a/fastlane/metadata/android/en-US/changelogs/116.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.7:
-- detect HDR videos (but do not play them in their full HDR glory)
-- removing animations also applies to pop up menus
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11601.txt b/fastlane/metadata/android/en-US/changelogs/11601.txt
deleted file mode 100644
index 067ad57ef..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11601.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.7:
-- detect HDR videos (but do not play them in their full HDR glory)
-- removing animations also applies to pop up menus
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/117.txt b/fastlane/metadata/android/en-US/changelogs/117.txt
deleted file mode 100644
index 720ed4ce1..000000000
--- a/fastlane/metadata/android/en-US/changelogs/117.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.8:
-- rename in bulk using tags
-- repeat a section section section of a video
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11701.txt b/fastlane/metadata/android/en-US/changelogs/11701.txt
deleted file mode 100644
index 720ed4ce1..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11701.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.8:
-- rename in bulk using tags
-- repeat a section section section of a video
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/118.txt b/fastlane/metadata/android/en-US/changelogs/118.txt
deleted file mode 100644
index 7e6274add..000000000
--- a/fastlane/metadata/android/en-US/changelogs/118.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.9:
-- rename in bulk using tags
-- repeat a section section section of a video
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11801.txt b/fastlane/metadata/android/en-US/changelogs/11801.txt
deleted file mode 100644
index 7e6274add..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11801.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-In v1.10.9:
-- rename in bulk using tags
-- repeat a section section section of a video
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/119.txt b/fastlane/metadata/android/en-US/changelogs/119.txt
deleted file mode 100644
index d0a2a5a14..000000000
--- a/fastlane/metadata/android/en-US/changelogs/119.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.11.0:
-- watch videos with SRT subtitle files
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/11901.txt b/fastlane/metadata/android/en-US/changelogs/11901.txt
deleted file mode 100644
index d0a2a5a14..000000000
--- a/fastlane/metadata/android/en-US/changelogs/11901.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-In v1.11.0:
-- watch videos with SRT subtitle files
-Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/123.txt b/fastlane/metadata/android/en-US/changelogs/123.txt
new file mode 100644
index 000000000..b1151387e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/123.txt
@@ -0,0 +1,4 @@
+In v1.11.4:
+- explore your collection with the... explorer
+- convert your motion photos to stills in bulk
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/12301.txt b/fastlane/metadata/android/en-US/changelogs/12301.txt
new file mode 100644
index 000000000..b1151387e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/12301.txt
@@ -0,0 +1,4 @@
+In v1.11.4:
+- explore your collection with the... explorer
+- convert your motion photos to stills in bulk
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb
index 025a59a6a..d529fc668 100644
--- a/lib/l10n/app_ar.arb
+++ b/lib/l10n/app_ar.arb
@@ -1195,7 +1195,7 @@
"@collectionActionAddShortcut": {},
"settingsViewerShowMinimap": "إظهار الخريطة المصغرة",
"@settingsViewerShowMinimap": {},
- "settingsCollectionBurstPatternsTile": "أنماط الانفجار",
+ "settingsCollectionBurstPatternsTile": "أنماط الصور المتتابعة",
"@settingsCollectionBurstPatternsTile": {},
"viewerInfoLabelPath": "المسار",
"@viewerInfoLabelPath": {},
@@ -1538,5 +1538,9 @@
"renameProcessorHash": "تجزئة",
"@renameProcessorHash": {},
"chipActionShowCollection": "عرض في المجموعة",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "chipActionGoToExplorerPage": "عرض في المستكشف",
+ "@chipActionGoToExplorerPage": {},
+ "explorerPageTitle": "المستكشف",
+ "@explorerPageTitle": {}
}
diff --git a/lib/l10n/app_be.arb b/lib/l10n/app_be.arb
index 181c2f428..47c4a4934 100644
--- a/lib/l10n/app_be.arb
+++ b/lib/l10n/app_be.arb
@@ -5,7 +5,7 @@
"@welcomeTermsToggle": {},
"welcomeOptional": "Неабавязковыя",
"@welcomeOptional": {},
- "welcomeMessage": "Сардэчна запрашаем ў Aves",
+ "welcomeMessage": "Сардэчна запрашаем у Aves",
"@welcomeMessage": {},
"itemCount": "{count, plural, =1{{count} элемент} other{{count} элементаў}}",
"@itemCount": {
@@ -38,7 +38,7 @@
"@saveTooltip": {},
"doNotAskAgain": "Больш не пытайся",
"@doNotAskAgain": {},
- "chipActionGoToCountryPage": "Паказаць ў Краінах",
+ "chipActionGoToCountryPage": "Паказаць у Краінах",
"@chipActionGoToCountryPage": {},
"chipActionFilterOut": "Адфільтраваць",
"@chipActionFilterOut": {},
@@ -56,27 +56,27 @@
"@sourceStateCataloguing": {},
"chipActionDelete": "Выдаліць",
"@chipActionDelete": {},
- "chipActionGoToAlbumPage": "Паказаць ў Альбомах",
+ "chipActionGoToAlbumPage": "Паказаць у Альбомах",
"@chipActionGoToAlbumPage": {},
"chipActionHide": "Схаваць",
"@chipActionHide": {},
"chipActionCreateVault": "Стварыце сховішча",
"@chipActionCreateVault": {},
- "chipActionGoToPlacePage": "Паказаць ў Лакацыях",
+ "chipActionGoToPlacePage": "Паказаць у Лакацыях",
"@chipActionGoToPlacePage": {},
"chipActionUnpin": "Адмацаваць зверху",
"@chipActionUnpin": {},
- "chipActionGoToTagPage": "Паказаць ў Тэгах",
+ "chipActionGoToTagPage": "Паказаць у Тэгах",
"@chipActionGoToTagPage": {},
"chipActionLock": "Заблакаваць",
"@chipActionLock": {},
- "chipActionSetCover": "Ўсталяваць вокладку",
+ "chipActionSetCover": "Усталяваць вокладку",
"@chipActionSetCover": {},
"chipActionRename": "Перайменаваць",
"@chipActionRename": {},
"chipActionConfigureVault": "Наладзіць сховішча",
"@chipActionConfigureVault": {},
- "entryActionCopyToClipboard": "Скапіяваць ў буфер абмену",
+ "entryActionCopyToClipboard": "Скапіяваць у буфер абмену",
"@entryActionCopyToClipboard": {},
"entryActionDelete": "Выдаліць",
"@entryActionDelete": {},
@@ -120,15 +120,15 @@
"@entryActionRotateScreen": {},
"entryActionViewSource": "Паглядзець крыніцу",
"@entryActionViewSource": {},
- "entryActionConvertMotionPhotoToStillImage": "Пераўтварыць ў нерухомую выяву",
+ "entryActionConvertMotionPhotoToStillImage": "Канвертаваць у статычны малюнак",
"@entryActionConvertMotionPhotoToStillImage": {},
"entryActionViewMotionPhotoVideo": "Адкрыць відэа",
"@entryActionViewMotionPhotoVideo": {},
- "entryActionSetAs": "Ўсталяваць як",
+ "entryActionSetAs": "Усталяваць як",
"@entryActionSetAs": {},
- "entryActionAddFavourite": "Дадаць ў абранае",
+ "entryActionAddFavourite": "Дадаць у абранае",
"@entryActionAddFavourite": {},
- "videoActionUnmute": "Ўключыць гук",
+ "videoActionUnmute": "Уключыць гук",
"@videoActionUnmute": {},
"videoActionCaptureFrame": "Захоп кадра",
"@videoActionCaptureFrame": {},
@@ -188,11 +188,11 @@
"@entryActionEdit": {},
"entryActionOpen": "Адкрыць з дапамогай",
"@entryActionOpen": {},
- "entryActionOpenMap": "Паказаць ў праграме карты",
+ "entryActionOpenMap": "Паказаць у праграме карты",
"@entryActionOpenMap": {},
"videoActionMute": "Адключыць гук",
"@videoActionMute": {},
- "slideshowActionShowInCollection": "Паказаць ў Калекцыі",
+ "slideshowActionShowInCollection": "Паказаць у Калекцыі",
"@slideshowActionShowInCollection": {},
"entryInfoActionEditDate": "Рэдагаваць дату і час",
"@entryInfoActionEditDate": {},
@@ -228,7 +228,7 @@
"@filterTypeSphericalVideoLabel": {},
"filterNoTitleLabel": "Без назвы",
"@filterNoTitleLabel": {},
- "filterOnThisDayLabel": "Ў гэты дзень",
+ "filterOnThisDayLabel": "У гэты дзень",
"@filterOnThisDayLabel": {},
"filterRatingRejectedLabel": "Адхілена",
"@filterRatingRejectedLabel": {},
@@ -363,7 +363,7 @@
"@vaultLockTypePassword": {},
"settingsVideoEnablePip": "Карцінка ў карцінцы",
"@settingsVideoEnablePip": {},
- "videoControlsPlayOutside": "Адкрыць ў іншым прайгравальніку",
+ "videoControlsPlayOutside": "Адкрыць у іншым прайгравальніку",
"@videoControlsPlayOutside": {},
"videoControlsPlay": "Прайграванне",
"@videoControlsPlay": {},
@@ -449,7 +449,7 @@
"@wallpaperTargetHomeLock": {},
"widgetTapUpdateWidget": "Абнавіць віджэт",
"@widgetTapUpdateWidget": {},
- "storageVolumeDescriptionFallbackPrimary": "Ўнутраная памяць",
+ "storageVolumeDescriptionFallbackPrimary": "Унутраная памяць",
"@storageVolumeDescriptionFallbackPrimary": {},
"restrictedAccessDialogMessage": "Гэтай праграме забаронена змяняць файлы ў {directory} «{volume}».\n\nКаб перамясціць элементы ў іншую дырэкторыю, выкарыстоўвайце папярэдне ўсталяваны дыспетчар файлаў або праграму галерэі.",
"@restrictedAccessDialogMessage": {
@@ -465,7 +465,7 @@
}
}
},
- "missingSystemFilePickerDialogMessage": "Сродак выбару сістэмных файлаў адсутнічае або адключаны. Ўключыце яго і паўтарыце спробу.",
+ "missingSystemFilePickerDialogMessage": "Сістэмная праграма выбару файлаў адсутнічае ці адключана. Калі ласка, уключыце яе і паспрабуйце яшчэ раз.",
"@missingSystemFilePickerDialogMessage": {},
"unsupportedTypeDialogMessage": "{count, plural, =1{Гэта аперацыя не падтрымліваецца для элементаў наступнага тыпу: {types}.} other{Гэта аперацыя не падтрымліваецца для элементаў наступных тыпаў: {types}.}}",
"@unsupportedTypeDialogMessage": {
@@ -488,7 +488,7 @@
"@moveUndatedConfirmationDialogMessage": {},
"moveUndatedConfirmationDialogSetDate": "Захаваць даты",
"@moveUndatedConfirmationDialogSetDate": {},
- "videoResumeDialogMessage": "Вы хочаце аднавіць гульню ў {time}?",
+ "videoResumeDialogMessage": "Вы хочаце аднавіць прайграванне на {time}?",
"@videoResumeDialogMessage": {
"placeholders": {
"time": {
@@ -517,15 +517,15 @@
"@configureVaultDialogTitle": {},
"vaultDialogLockTypeLabel": "Тып блакіроўкі",
"@vaultDialogLockTypeLabel": {},
- "pinDialogEnter": "Ўвядзіце PIN-код",
+ "pinDialogEnter": "Увядзіце PIN-код",
"@pinDialogEnter": {},
- "patternDialogEnter": "Ўвядзіце графічны ключ",
+ "patternDialogEnter": "Увядзіце ключ",
"@patternDialogEnter": {},
"patternDialogConfirm": "Пацвердзіце графічны ключ",
"@patternDialogConfirm": {},
"pinDialogConfirm": "Пацвердзіце PIN-код",
"@pinDialogConfirm": {},
- "passwordDialogEnter": "Ўвядзіце пароль",
+ "passwordDialogEnter": "Увядзіце пароль",
"@passwordDialogEnter": {},
"passwordDialogConfirm": "Пацвердзіце пароль",
"@passwordDialogConfirm": {},
@@ -551,7 +551,7 @@
"@mapPointNorthUpTooltip": {},
"viewerInfoLabelCoordinates": "Каардынаты",
"@viewerInfoLabelCoordinates": {},
- "viewerInfoLabelOwner": "Ўладальнік",
+ "viewerInfoLabelOwner": "Уладальнік",
"@viewerInfoLabelOwner": {},
"viewerInfoLabelDuration": "Працягласць",
"@viewerInfoLabelDuration": {},
@@ -577,7 +577,7 @@
"@sourceViewerPageTitle": {},
"panoramaDisableSensorControl": "Адключыць сэнсарнае кіраванне",
"@panoramaDisableSensorControl": {},
- "panoramaEnableSensorControl": "Ўключыць сэнсарнае кіраванне",
+ "panoramaEnableSensorControl": "Уключыць сэнсарнае кіраванне",
"@panoramaEnableSensorControl": {},
"tagPlaceholderPlace": "Месца",
"@tagPlaceholderPlace": {},
@@ -601,7 +601,7 @@
"@videoControlsNone": {},
"viewerErrorUnknown": "Ой!",
"@viewerErrorUnknown": {},
- "viewerSetWallpaperButtonLabel": "ЎСТАНАВІЦЬ ШПАЛЕРЫ",
+ "viewerSetWallpaperButtonLabel": "УСТАНАВІЦЬ ШПАЛЕРЫ",
"@viewerSetWallpaperButtonLabel": {},
"statsTopAlbumsSectionTitle": "Лепшыя альбомы",
"@statsTopAlbumsSectionTitle": {},
@@ -625,7 +625,7 @@
"@mapZoomOutTooltip": {},
"openMapPageTooltip": "Паглядзець на старонцы карты",
"@openMapPageTooltip": {},
- "mapEmptyRegion": "Ў гэтым рэгіёне няма малюнкаў",
+ "mapEmptyRegion": "Няма малюнкаў у гэтым рэгіёне",
"@mapEmptyRegion": {},
"viewerInfoSearchEmpty": "Няма адпаведных ключоў",
"@viewerInfoSearchEmpty": {},
@@ -685,19 +685,19 @@
"@aboutBugCopyInfoInstruction": {},
"vaultBinUsageDialogMessage": "Некаторыя сховішчы выкарыстоўваюць сметніцу.",
"@vaultBinUsageDialogMessage": {},
- "aboutBugSaveLogInstruction": "Захаваць журналы праграмы ў файл",
+ "aboutBugSaveLogInstruction": "Захавайце логі праграмы ў файл",
"@aboutBugSaveLogInstruction": {},
"aboutBugReportInstruction": "Адправіць справаздачу аб памылцы на GitHub разам з журналамі і сістэмнай інфармацыяй",
"@aboutBugReportInstruction": {},
"entryActionCast": "Трансляцыя",
"@entryActionCast": {},
- "hideFilterConfirmationDialogMessage": "Адпаведныя фота і відэа будуць схаваны з вашай калекцыі. Вы можаце убачыць іх зноў ў наладах «Прыватнасць».\n\nВы ўпэўнены, што хочаце іх схаваць?",
+ "hideFilterConfirmationDialogMessage": "Адпаведныя фота і відэа будуць схаваны з вашай калекцыі. Вы можаце паказаць іх зноў у наладах «Прыватнасць».\n\nВы ўпэўнены, што хочаце іх схаваць?",
"@hideFilterConfirmationDialogMessage": {},
"renameEntrySetPagePatternFieldLabel": "Шаблон наймення",
"@renameEntrySetPagePatternFieldLabel": {},
"renameAlbumDialogLabel": "Новая назва",
"@renameAlbumDialogLabel": {},
- "renameAlbumDialogLabelAlreadyExistsHelper": "Каталог ўжо ёсць",
+ "renameAlbumDialogLabelAlreadyExistsHelper": "Каталог ужо існуе",
"@renameAlbumDialogLabelAlreadyExistsHelper": {},
"aboutBugReportButton": "Адправіць справаздачу",
"@aboutBugReportButton": {},
@@ -707,7 +707,7 @@
"@aboutBugSectionTitle": {},
"aboutBugCopyInfoButton": "Скапіяваць",
"@aboutBugCopyInfoButton": {},
- "binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент ў сметніцу?} few{Перамясціць гэтыя {count} элемента ў сметніцу?} other{Перамясціць гэтыя {count} элементаў ў сметніцу?}}",
+ "binEntriesConfirmationDialogMessage": "{count, plural, =1{Перамясціць гэты элемент у сметніцу?} few{Перамясціць гэтыя {count} элемента ў сметніцу?} other{Перамясціць гэтыя {count} элементаў у сметніцу?}}",
"@binEntriesConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -797,7 +797,7 @@
"@settingsCollectionTile": {},
"settingsThemeBrightnessDialogTitle": "Тэма",
"@settingsThemeBrightnessDialogTitle": {},
- "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэты альбом і элемент ў ім?} few{Выдаліць гэты альбом і {count} элементы ў ім?} other{Выдаліць гэты альбом і {count} элементаў ў ім?}}",
+ "deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэты альбом і элемент у ім?} few{Выдаліць гэты альбом і {count} элементы ў ім?} other{Выдаліць гэты альбом і {count} элементаў у ім?}}",
"@deleteSingleAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -819,7 +819,7 @@
"@aboutDataUsageMisc": {},
"albumVideoCaptures": "Відэазапісы",
"@albumVideoCaptures": {},
- "editEntryDateDialogSetCustom": "Ўсталяваць карыстацкую дату",
+ "editEntryDateDialogSetCustom": "Устанавіць дату",
"@editEntryDateDialogSetCustom": {},
"settingsSearchEmpty": "Няма адпаведнай налады",
"@settingsSearchEmpty": {},
@@ -845,7 +845,7 @@
"@collectionSelectSectionTooltip": {},
"aboutLicensesBanner": "Гэта праграма выкарыстоўвае наступныя пакеты і бібліятэкі з адкрытым зыходным кодам.",
"@aboutLicensesBanner": {},
- "dateYesterday": "Ўчора",
+ "dateYesterday": "Учора",
"@dateYesterday": {},
"aboutDataUsageDatabase": "База дадзеных",
"@aboutDataUsageDatabase": {},
@@ -853,7 +853,7 @@
"@tileLayoutMosaic": {},
"collectionDeselectSectionTooltip": "Адмяніць выбар раздзела",
"@collectionDeselectSectionTooltip": {},
- "settingsKeepScreenOnTile": "Трымаць экран ўключаным",
+ "settingsKeepScreenOnTile": "Трымаць экран уключаным",
"@settingsKeepScreenOnTile": {},
"tileLayoutGrid": "Сетка",
"@tileLayoutGrid": {},
@@ -879,11 +879,11 @@
"@videoStreamSelectionDialogAudio": {},
"videoSpeedDialogLabel": "Хуткасць прайгравання",
"@videoSpeedDialogLabel": {},
- "editEntryLocationDialogSetCustom": "Ўстанавіць карыстацкае месцазнаходжанне",
+ "editEntryLocationDialogSetCustom": "Рэдагаваць месцазнаходжанне",
"@editEntryLocationDialogSetCustom": {},
"placeEmpty": "Няма месцаў",
"@placeEmpty": {},
- "editEntryDateDialogExtractFromTitle": "Выняць з загалоўка",
+ "editEntryDateDialogExtractFromTitle": "Выняць з назвы",
"@editEntryDateDialogExtractFromTitle": {},
"aboutLinkLicense": "Ліцэнзія",
"@aboutLinkLicense": {},
@@ -925,7 +925,7 @@
"@drawerAlbumPage": {},
"settingsActionImport": "Імпарт",
"@settingsActionImport": {},
- "locationPickerUseThisLocationButton": "Выкарыстоўваць гэтае месца",
+ "locationPickerUseThisLocationButton": "Выкарыстоўваць гэтае месцазнаходжанне",
"@locationPickerUseThisLocationButton": {},
"collectionGroupNone": "Не групаваць",
"@collectionGroupNone": {},
@@ -937,7 +937,7 @@
"@settingsActionImportDialogTitle": {},
"albumGroupTier": "Па ўзроўні",
"@albumGroupTier": {},
- "drawerCollectionAll": "Ўся калекцыя",
+ "drawerCollectionAll": "Уся калекцыя",
"@drawerCollectionAll": {},
"sortByItemCount": "Па колькасці элементаў",
"@sortByItemCount": {},
@@ -953,7 +953,7 @@
"@albumPickPageTitlePick": {},
"menuActionMap": "Карта",
"@menuActionMap": {},
- "collectionActionMove": "Перамясціць ў альбом",
+ "collectionActionMove": "Перамясціць у альбом",
"@collectionActionMove": {},
"searchAlbumsSectionTitle": "Альбомы",
"@searchAlbumsSectionTitle": {},
@@ -1013,9 +1013,9 @@
"@albumPageTitle": {},
"editEntryLocationDialogTitle": "Месцазнаходжанне",
"@editEntryLocationDialogTitle": {},
- "albumPickPageTitleCopy": "Скапіяваць ў альбом",
+ "albumPickPageTitleCopy": "Капіяваць у альбом",
"@albumPickPageTitleCopy": {},
- "collectionActionCopy": "Скапіяваць ў альбом",
+ "collectionActionCopy": "Скапіяваць у альбом",
"@collectionActionCopy": {},
"viewDialogReverseSortOrder": "Адваротны парадак сартавання",
"@viewDialogReverseSortOrder": {},
@@ -1033,7 +1033,7 @@
"@tagEmpty": {},
"collectionActionShowTitleSearch": "Паказаць фільтр загалоўка",
"@collectionActionShowTitleSearch": {},
- "menuActionSelectAll": "Выбраць ўсё",
+ "menuActionSelectAll": "Выбраць усе",
"@menuActionSelectAll": {},
"settingsConfirmationTile": "Дыялогі пацверджання",
"@settingsConfirmationTile": {},
@@ -1059,7 +1059,7 @@
"@drawerCollectionAnimated": {},
"durationDialogHours": "Гадзіны",
"@durationDialogHours": {},
- "settingsKeepScreenOnDialogTitle": "Трымаць экран ўключаным",
+ "settingsKeepScreenOnDialogTitle": "Трымаць экран уключаным",
"@settingsKeepScreenOnDialogTitle": {},
"drawerPlacePage": "Месцы",
"@drawerPlacePage": {},
@@ -1077,7 +1077,7 @@
"@appExportFavourites": {},
"collectionEmptyImages": "Няма выяў",
"@collectionEmptyImages": {},
- "albumPickPageTitleExport": "Экспартаваць ў альбом",
+ "albumPickPageTitleExport": "Экспарт у альбом",
"@albumPickPageTitleExport": {},
"settingsActionExportDialogTitle": "Экспарт",
"@settingsActionExportDialogTitle": {},
@@ -1127,7 +1127,7 @@
"@viewDialogLayoutSectionTitle": {},
"searchStatesSectionTitle": "Штаты",
"@searchStatesSectionTitle": {},
- "dateThisMonth": "Ў гэтым месяцы",
+ "dateThisMonth": "У гэтым месяцы",
"@dateThisMonth": {},
"aboutPageTitle": "Пра нас",
"@aboutPageTitle": {},
@@ -1141,7 +1141,7 @@
"@genericFailureFeedback": {},
"aboutDataUsageData": "Дадзеныя",
"@aboutDataUsageData": {},
- "aboutDataUsageInternal": "Ўнутраны",
+ "aboutDataUsageInternal": "Унутранае",
"@aboutDataUsageInternal": {},
"albumDownload": "Загрузкі",
"@albumDownload": {},
@@ -1149,7 +1149,7 @@
"@coverDialogTabColor": {},
"genericSuccessFeedback": "Гатова!",
"@genericSuccessFeedback": {},
- "aboutLicensesShowAllButtonLabel": "Паказаць ўсе ліцэнзіі",
+ "aboutLicensesShowAllButtonLabel": "Паказаць усе ліцэнзіі",
"@aboutLicensesShowAllButtonLabel": {},
"sortOrderNewestFirst": "Спачатку самае новае",
"@sortOrderNewestFirst": {},
@@ -1175,7 +1175,7 @@
"@menuActionStats": {},
"appPickDialogTitle": "Выбраць праграму",
"@appPickDialogTitle": {},
- "albumPickPageTitleMove": "Перамясціць ў альбом",
+ "albumPickPageTitleMove": "Перамясціць у альбом",
"@albumPickPageTitleMove": {},
"coverDialogTabCover": "Вокладка",
"@coverDialogTabCover": {},
@@ -1183,7 +1183,7 @@
"@settingsConfirmationBeforeDeleteItems": {},
"settingsConfirmationBeforeMoveUndatedItems": "Спытаць, перш чым перамяшчаць прадметы без даты",
"@settingsConfirmationBeforeMoveUndatedItems": {},
- "settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў ў сметніцу",
+ "settingsConfirmationAfterMoveToBinItems": "Паказваць паведамленне пасля перамяшчэння элементаў у сметніцу",
"@settingsConfirmationAfterMoveToBinItems": {},
"settingsConfirmationBeforeMoveToBinItems": "Спытаць перад тым, як пераносіць элементы ў сметніцу",
"@settingsConfirmationBeforeMoveToBinItems": {},
@@ -1387,7 +1387,7 @@
"@settingsNavigationDrawerTile": {},
"settingsHiddenItemsPageTitle": "Схаваныя элементы",
"@settingsHiddenItemsPageTitle": {},
- "settingsHiddenPathsBanner": "Фатаграфіі і відэа ў гэтых папках або ў любой з іх укладзеных папак не будуць адлюстроўвацца ў вашай калекцыі.",
+ "settingsHiddenPathsBanner": "Фатаграфіі і відэа ў гэтых тэчках або ў любой з іх укладзеных тэчках не будуць адлюстроўвацца ў вашай калекцыі.",
"@settingsHiddenPathsBanner": {},
"settingsViewerShowOverlayOnOpening": "Паказаць на адкрыцці",
"@settingsViewerShowOverlayOnOpening": {},
@@ -1405,7 +1405,7 @@
"@settingsStorageAccessEmpty": {},
"settingsRemoveAnimationsTile": "Выдаліць анімацыі",
"@settingsRemoveAnimationsTile": {},
- "settingsStorageAccessBanner": "Некаторыя каталогі патрабуюць відавочнага дазволу на змяненне файлаў ў іх. Тут вы можаце прагледзець каталогі, да якіх вы раней далі доступ.",
+ "settingsStorageAccessBanner": "Некаторыя каталогі патрабуюць відавочнага дазволу на змяненне файлаў у іх. Тут вы можаце прагледзець каталогі, да якіх вы раней далі доступ.",
"@settingsStorageAccessBanner": {},
"collectionCopySuccessFeedback": "{count, plural, =1{1 элемент скапіяваны} few{{count} элементы скапіявана} other{{count} элементаў скапіявана}}",
"@collectionCopySuccessFeedback": {
@@ -1467,7 +1467,7 @@
"@settingsSubtitleThemeTextPositionTile": {},
"settingsVideoBackgroundModeDialogTitle": "Фонавы рэжым",
"@settingsVideoBackgroundModeDialogTitle": {},
- "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэтыя альбомы і элемент ў іх?} few{Выдаліць гэтыя альбомы і {count} элементы ў іх?} other{Выдаліць гэтыя альбомы і {count} элементаў ў іх?}}",
+ "deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Выдаліць гэтыя альбомы і элемент у іх?} few{Выдаліць гэтыя альбомы і {count} элементы ў іх?} other{Выдаліць гэтыя альбомы і {count} элементаў у іх?}}",
"@deleteMultiAlbumConfirmationDialogMessage": {
"placeholders": {
"count": {}
@@ -1519,24 +1519,28 @@
"minutes": {}
}
},
- "collectionActionSetHome": "Ўсталяваць як галоўную",
+ "collectionActionSetHome": "Усталяваць як галоўную",
"@collectionActionSetHome": {},
- "setHomeCustomCollection": "Ўласная калекцыя",
+ "setHomeCustomCollection": "Уласная калекцыя",
"@setHomeCustomCollection": {},
"settingsThumbnailShowHdrIcon": "Паказаць значок HDR",
"@settingsThumbnailShowHdrIcon": {},
- "videoRepeatActionSetEnd": "Ўсталяваць канец",
+ "videoRepeatActionSetEnd": "Усталяваць канец",
"@videoRepeatActionSetEnd": {},
"stopTooltip": "Спыніць",
"@stopTooltip": {},
"videoActionABRepeat": "Паўтарыць ад А да Б",
"@videoActionABRepeat": {},
- "videoRepeatActionSetStart": "Ўсталяваць пачатак",
+ "videoRepeatActionSetStart": "Усталяваць пачатак",
"@videoRepeatActionSetStart": {},
"renameProcessorHash": "Хэш",
"@renameProcessorHash": {},
"settingsForceWesternArabicNumeralsTile": "Прымусовыя арабскія лічбы",
"@settingsForceWesternArabicNumeralsTile": {},
- "chipActionShowCollection": "Паказаць ў Калекцыі",
- "@chipActionShowCollection": {}
+ "chipActionShowCollection": "Паказаць у Калекцыі",
+ "@chipActionShowCollection": {},
+ "chipActionGoToExplorerPage": "Паказаць у Правадыру",
+ "@chipActionGoToExplorerPage": {},
+ "explorerPageTitle": "Правадыр",
+ "@explorerPageTitle": {}
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 4ceb286db..dae5ad074 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -90,6 +90,7 @@
"chipActionGoToCountryPage": "Show in Countries",
"chipActionGoToPlacePage": "Show in Places",
"chipActionGoToTagPage": "Show in Tags",
+ "chipActionGoToExplorerPage": "Show in Explorer",
"chipActionFilterOut": "Filter out",
"chipActionFilterIn": "Filter in",
"chipActionHide": "Hide",
@@ -771,6 +772,8 @@
"binPageTitle": "Recycle Bin",
+ "explorerPageTitle": "Explorer",
+
"searchCollectionFieldHint": "Search collection",
"searchRecentSectionTitle": "Recent",
"searchDateSectionTitle": "Date",
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 94a36e8d2..db0359da9 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -1380,5 +1380,9 @@
"renameProcessorHash": "Hash",
"@renameProcessorHash": {},
"chipActionShowCollection": "Mostrar en Colección",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "explorerPageTitle": "Explorar",
+ "@explorerPageTitle": {},
+ "chipActionGoToExplorerPage": "Mostrar en el explorador",
+ "@chipActionGoToExplorerPage": {}
}
diff --git a/lib/l10n/app_fi.arb b/lib/l10n/app_fi.arb
index f36e81669..c8b8ad913 100644
--- a/lib/l10n/app_fi.arb
+++ b/lib/l10n/app_fi.arb
@@ -302,5 +302,137 @@
"filterNoDateLabel": "Päiväämätön",
"@filterNoDateLabel": {},
"chipActionShowCollection": "Näytä kokoelmassa",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "widgetDisplayedItemMostRecent": "Viimeisin",
+ "@widgetDisplayedItemMostRecent": {},
+ "otherDirectoryDescription": "“{name}” kansio",
+ "@otherDirectoryDescription": {
+ "placeholders": {
+ "name": {
+ "type": "String",
+ "example": "Pictures",
+ "description": "the name of a specific directory"
+ }
+ }
+ },
+ "videoActionABRepeat": "A-B toisto",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetStart": "Aseta alku",
+ "@videoRepeatActionSetStart": {},
+ "videoRepeatActionSetEnd": "Aseta loppu",
+ "@videoRepeatActionSetEnd": {},
+ "filterTypeRawLabel": "Raw",
+ "@filterTypeRawLabel": {},
+ "filterTypeSphericalVideoLabel": "360° Video",
+ "@filterTypeSphericalVideoLabel": {},
+ "filterTypeGeotiffLabel": "GeoTIFF",
+ "@filterTypeGeotiffLabel": {},
+ "filterMimeVideoLabel": "Video",
+ "@filterMimeVideoLabel": {},
+ "coordinateFormatDms": "DMS",
+ "@coordinateFormatDms": {},
+ "coordinateDms": "{coordinate} {direction}",
+ "@coordinateDms": {
+ "placeholders": {
+ "coordinate": {
+ "type": "String",
+ "example": "38° 41′ 47.72″"
+ },
+ "direction": {
+ "type": "String",
+ "example": "S"
+ }
+ }
+ },
+ "coordinateDmsNorth": "P",
+ "@coordinateDmsNorth": {},
+ "lengthUnitPixel": "px",
+ "@lengthUnitPixel": {},
+ "lengthUnitPercent": "%",
+ "@lengthUnitPercent": {},
+ "mapStyleGoogleNormal": "Google Maps",
+ "@mapStyleGoogleNormal": {},
+ "mapStyleHuaweiNormal": "Petal Maps",
+ "@mapStyleHuaweiNormal": {},
+ "mapStyleHuaweiTerrain": "Petal Maps (Maasto)",
+ "@mapStyleHuaweiTerrain": {},
+ "overlayHistogramRGB": "RGB",
+ "@overlayHistogramRGB": {},
+ "subtitlePositionTop": "Ylhäällä",
+ "@subtitlePositionTop": {},
+ "subtitlePositionBottom": "Alhaalla",
+ "@subtitlePositionBottom": {},
+ "themeBrightnessLight": "Vaalea",
+ "@themeBrightnessLight": {},
+ "themeBrightnessDark": "Tumma",
+ "@themeBrightnessDark": {},
+ "themeBrightnessBlack": "Musta",
+ "@themeBrightnessBlack": {},
+ "unitSystemMetric": "Metrinen",
+ "@unitSystemMetric": {},
+ "unitSystemImperial": "Brittiläinen",
+ "@unitSystemImperial": {},
+ "vaultLockTypePattern": "Kuvio",
+ "@vaultLockTypePattern": {},
+ "vaultLockTypePin": "PIN",
+ "@vaultLockTypePin": {},
+ "vaultLockTypePassword": "Salasana",
+ "@vaultLockTypePassword": {},
+ "settingsVideoEnablePip": "Kuva kuvassa",
+ "@settingsVideoEnablePip": {},
+ "videoControlsPlay": "Toista",
+ "@videoControlsPlay": {},
+ "videoControlsPlayOutside": "Avaa toisella soittimella",
+ "@videoControlsPlayOutside": {},
+ "videoControlsPlaySeek": "Toista & selaa eteen/taakse",
+ "@videoControlsPlaySeek": {},
+ "videoControlsNone": "Ei mitään",
+ "@videoControlsNone": {},
+ "videoLoopModeNever": "Ei koskaan",
+ "@videoLoopModeNever": {},
+ "videoLoopModeShortOnly": "Vain lyhyissä videoissa",
+ "@videoLoopModeShortOnly": {},
+ "videoPlaybackSkip": "Ohita",
+ "@videoPlaybackSkip": {},
+ "videoPlaybackMuted": "Toista mykistettynä",
+ "@videoPlaybackMuted": {},
+ "videoPlaybackWithSound": "Toista äänillä",
+ "@videoPlaybackWithSound": {},
+ "videoResumptionModeAlways": "Aina",
+ "@videoResumptionModeAlways": {},
+ "wallpaperTargetLock": "Lukitusnäyttö",
+ "@wallpaperTargetLock": {},
+ "wallpaperTargetHomeLock": "Koti- ja lukitusnäyttö",
+ "@wallpaperTargetHomeLock": {},
+ "widgetDisplayedItemRandom": "Satunnainen",
+ "@widgetDisplayedItemRandom": {},
+ "focalLength": "{length} mm",
+ "@focalLength": {
+ "placeholders": {
+ "length": {
+ "type": "String",
+ "example": "5.4"
+ }
+ }
+ },
+ "videoActionUnmute": "Poista mykistys",
+ "@videoActionUnmute": {},
+ "coordinateDmsWest": "L",
+ "@coordinateDmsWest": {},
+ "coordinateDmsSouth": "E",
+ "@coordinateDmsSouth": {},
+ "coordinateDmsEast": "I",
+ "@coordinateDmsEast": {},
+ "videoLoopModeAlways": "Aina",
+ "@videoLoopModeAlways": {},
+ "videoResumptionModeNever": "Ei koskaan",
+ "@videoResumptionModeNever": {},
+ "viewerTransitionNone": "Ei mitään",
+ "@viewerTransitionNone": {},
+ "wallpaperTargetHome": "Kotinäyttö",
+ "@wallpaperTargetHome": {},
+ "storageVolumeDescriptionFallbackPrimary": "Sisäinen tallennustila",
+ "@storageVolumeDescriptionFallbackPrimary": {},
+ "storageVolumeDescriptionFallbackNonPrimary": "SD-kortti",
+ "@storageVolumeDescriptionFallbackNonPrimary": {}
}
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index f0e39d841..60c7c600d 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -1380,5 +1380,9 @@
"settingsForceWesternArabicNumeralsTile": "Toujours utiliser les chiffres arabes",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Afficher dans Collection",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "explorerPageTitle": "Explorateur",
+ "@explorerPageTitle": {},
+ "chipActionGoToExplorerPage": "Afficher dans Explorateur",
+ "@chipActionGoToExplorerPage": {}
}
diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb
index 5705a155d..8672c491d 100644
--- a/lib/l10n/app_ko.arb
+++ b/lib/l10n/app_ko.arb
@@ -1380,5 +1380,9 @@
"settingsForceWesternArabicNumeralsTile": "아라비아 숫자 항상 사용",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "미디어 페이지에서 보기",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "explorerPageTitle": "탐색기",
+ "@explorerPageTitle": {},
+ "chipActionGoToExplorerPage": "탐색기 페이지에서 보기",
+ "@chipActionGoToExplorerPage": {}
}
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index 5ab5253b2..7581069c6 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -1538,5 +1538,9 @@
"renameProcessorHash": "Skrót",
"@renameProcessorHash": {},
"chipActionShowCollection": "Pokaż w Kolekcji",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "chipActionGoToExplorerPage": "Pokaż w przeglądarce",
+ "@chipActionGoToExplorerPage": {},
+ "explorerPageTitle": "Przeglądarka",
+ "@explorerPageTitle": {}
}
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index 9424307c2..c7018e5da 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -593,7 +593,7 @@
"@collectionCopySuccessFeedback": {},
"collectionMoveSuccessFeedback": "{count, plural, =1{Перемещён 1 объект} few{Перемещено {count} объекта} other{Перемещено {count} объектов}}",
"@collectionMoveSuccessFeedback": {},
- "collectionRenameSuccessFeedback": "{count, plural, =1{Переименован 1 объект} few{Переименовао {count} объекта} other{Переименовано {count} объектов}}",
+ "collectionRenameSuccessFeedback": "{count, plural, =1{Переименован 1 объект} few{Переименовано {count} объекта} other{Переименовано {count} объектов}}",
"@collectionRenameSuccessFeedback": {},
"collectionEditSuccessFeedback": "{count, plural, =1{Изменён 1 объект} few{Изменено {count} объекта} other{Изменено {count} объектов}}",
"@collectionEditSuccessFeedback": {},
@@ -1380,5 +1380,9 @@
"settingsForceWesternArabicNumeralsTile": "Принудительные арабские цифры",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Показать в Коллекции",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "chipActionGoToExplorerPage": "Показать в проводнике",
+ "@chipActionGoToExplorerPage": {},
+ "explorerPageTitle": "Проводник",
+ "@explorerPageTitle": {}
}
diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb
index 28f294611..4a9c77544 100644
--- a/lib/l10n/app_sk.arb
+++ b/lib/l10n/app_sk.arb
@@ -1526,5 +1526,17 @@
"settingsThumbnailShowHdrIcon": "Zobraziť ikonu HDR",
"@settingsThumbnailShowHdrIcon": {},
"chipActionShowCollection": "Zobraziť v kolekcií",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "videoActionABRepeat": "Opakovanie A-B",
+ "@videoActionABRepeat": {},
+ "videoRepeatActionSetStart": "Nastaviť začiatok",
+ "@videoRepeatActionSetStart": {},
+ "videoRepeatActionSetEnd": "Nastaviť koniec",
+ "@videoRepeatActionSetEnd": {},
+ "settingsForceWesternArabicNumeralsTile": "Vynútiť arabské číslice",
+ "@settingsForceWesternArabicNumeralsTile": {},
+ "stopTooltip": "Zastaviť",
+ "@stopTooltip": {},
+ "renameProcessorHash": "Hash",
+ "@renameProcessorHash": {}
}
diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb
index 0ac680d5b..9d47e4fe5 100644
--- a/lib/l10n/app_tr.arb
+++ b/lib/l10n/app_tr.arb
@@ -1380,5 +1380,9 @@
"renameProcessorHash": "Sağlama",
"@renameProcessorHash": {},
"chipActionShowCollection": "Koleksiyonda göster",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "chipActionGoToExplorerPage": "Gezginde göster",
+ "@chipActionGoToExplorerPage": {},
+ "explorerPageTitle": "Gezgin",
+ "@explorerPageTitle": {}
}
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index bd1bcf700..9ed85d2d8 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -1035,7 +1035,7 @@
"@settingsSubtitleThemeTextAlignmentCenter": {},
"settingsSubtitleThemeTextAlignmentRight": "Праворуч",
"@settingsSubtitleThemeTextAlignmentRight": {},
- "settingsVideoControlsTile": "Управління",
+ "settingsVideoControlsTile": "Елементи керування",
"@settingsVideoControlsTile": {},
"settingsVideoButtonsTile": "Кнопки",
"@settingsVideoButtonsTile": {},
@@ -1049,7 +1049,7 @@
"@settingsSaveSearchHistory": {},
"settingsEnableBin": "Використовувати кошик",
"@settingsEnableBin": {},
- "settingsAllowMediaManagement": "Дозволити управління медіа",
+ "settingsAllowMediaManagement": "Дозволити керування мультимедіа",
"@settingsAllowMediaManagement": {},
"settingsHiddenItemsTile": "Приховані елементи",
"@settingsHiddenItemsTile": {},
@@ -1297,7 +1297,7 @@
"@settingsSlideshowAnimatedZoomEffect": {},
"settingsSubtitleThemeSample": "Це зразок.",
"@settingsSubtitleThemeSample": {},
- "settingsVideoControlsPageTitle": "Управління",
+ "settingsVideoControlsPageTitle": "Елементи керування",
"@settingsVideoControlsPageTitle": {},
"settingsVideoSectionTitle": "Відео",
"@settingsVideoSectionTitle": {},
@@ -1538,5 +1538,9 @@
"settingsForceWesternArabicNumeralsTile": "Примусові арабські цифри",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "Показати у Колекції",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "chipActionGoToExplorerPage": "Показати в провіднику",
+ "@chipActionGoToExplorerPage": {},
+ "explorerPageTitle": "Провідник",
+ "@explorerPageTitle": {}
}
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
index 80d0c3c6e..14bdd20fc 100644
--- a/lib/l10n/app_zh.arb
+++ b/lib/l10n/app_zh.arb
@@ -1380,5 +1380,9 @@
"settingsForceWesternArabicNumeralsTile": "强制使用阿拉伯数字",
"@settingsForceWesternArabicNumeralsTile": {},
"chipActionShowCollection": "在媒体集中显示",
- "@chipActionShowCollection": {}
+ "@chipActionShowCollection": {},
+ "explorerPageTitle": "资源管理器",
+ "@explorerPageTitle": {},
+ "chipActionGoToExplorerPage": "在资源管理器中显示",
+ "@chipActionGoToExplorerPage": {}
}
diff --git a/lib/model/app/contributors.dart b/lib/model/app/contributors.dart
index 5614d2139..5c4204abc 100644
--- a/lib/model/app/contributors.dart
+++ b/lib/model/app/contributors.dart
@@ -91,11 +91,14 @@ class Contributors {
Contributor('cheese', 'deanlemans5646@gmail.com'),
Contributor('Owen Elderbroek', 'o.elderbroek@gmail.com'),
Contributor('Maxi', 'maxitendo01@proton.me'),
+ Contributor('Jerguš Fonfer', 'caro.jf@protonmail.com'),
+ Contributor('elfriob', 'elfriob@ya.ru'),
// Contributor('Alvi Khan', 'aveenalvi@gmail.com'), // Bengali
// Contributor('Htet Oo Hlaing', 'htetoh2006@outlook.com'), // Burmese
// Contributor('Khant', 'khant@users.noreply.hosted.weblate.org'), // Burmese
// Contributor('Grooty12', 'Rasmus@rosendahl-kaa.name'), // Danish
// Contributor('Åzze', 'laitinen.jere222@gmail.com'), // Finnish
+ // Contributor('Olli', 'ollinen@ollit.dev'), // Finnish
// Contributor('Idj', 'joneltmp+goahn@gmail.com'), // Hebrew
// Contributor('Rohit Burman', 'rohitburman31p@rediffmail.com'), // Hindi
// Contributor('AJ07', 'ajaykumarmeena676@gmail.com'), // Hindi
diff --git a/lib/model/device.dart b/lib/model/device.dart
index e2c687e21..3c7a60d7a 100644
--- a/lib/model/device.dart
+++ b/lib/model/device.dart
@@ -63,14 +63,12 @@ class Device {
final auth = LocalAuthentication();
_canAuthenticateUser = await auth.canCheckBiometrics || await auth.isDeviceSupported();
- final floating = Floating();
try {
- _supportPictureInPicture = await floating.isPipAvailable;
+ _supportPictureInPicture = await Floating().isPipAvailable;
} on PlatformException catch (_) {
// as of floating v2.0.0, plugin assumes activity and fails when bound via service
_supportPictureInPicture = false;
}
- floating.dispose();
final capabilities = await deviceService.getCapabilities();
_canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false;
diff --git a/lib/model/entry/entry.dart b/lib/model/entry/entry.dart
index bdf810294..d7b91a58c 100644
--- a/lib/model/entry/entry.dart
+++ b/lib/model/entry/entry.dart
@@ -44,7 +44,8 @@ class AvesEntry with AvesEntryBase {
AddressDetails? _addressDetails;
TrashDetails? trashDetails;
- List? burstEntries;
+ // synthetic stack of related entries, e.g. burst shots or raw/developed pairs
+ List? stackedEntries;
@override
final AChangeNotifier visualChangeNotifier = AChangeNotifier();
@@ -69,7 +70,7 @@ class AvesEntry with AvesEntryBase {
required int? durationMillis,
required this.trashed,
required this.origin,
- this.burstEntries,
+ this.stackedEntries,
}) : id = id ?? 0 {
if (kFlutterMemoryAllocationsEnabled) {
FlutterMemoryAllocations.instance.dispatchObjectCreated(
@@ -93,7 +94,7 @@ class AvesEntry with AvesEntryBase {
int? dateAddedSecs,
int? dateModifiedSecs,
int? origin,
- List? burstEntries,
+ List? stackedEntries,
}) {
final copyEntryId = id ?? this.id;
final copied = AvesEntry(
@@ -114,7 +115,7 @@ class AvesEntry with AvesEntryBase {
durationMillis: durationMillis,
trashed: trashed,
origin: origin ?? this.origin,
- burstEntries: burstEntries ?? this.burstEntries,
+ stackedEntries: stackedEntries ?? this.stackedEntries,
)
..catalogMetadata = _catalogMetadata?.copyWith(id: copyEntryId)
..addressDetails = _addressDetails?.copyWith(id: copyEntryId)
diff --git a/lib/model/entry/extensions/multipage.dart b/lib/model/entry/extensions/multipage.dart
index a978ef0f6..6b32a8e8b 100644
--- a/lib/model/entry/extensions/multipage.dart
+++ b/lib/model/entry/extensions/multipage.dart
@@ -7,9 +7,9 @@ import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
extension ExtraAvesEntryMultipage on AvesEntry {
- bool get isMultiPage => isBurst || ((catalogMetadata?.isMultiPage ?? false) && (isMotionPhoto || !isHdr));
+ bool get isMultiPage => isStack || ((catalogMetadata?.isMultiPage ?? false) && (isMotionPhoto || !isHdr));
- bool get isBurst => burstEntries?.isNotEmpty == true;
+ bool get isStack => stackedEntries?.isNotEmpty == true;
bool get isMotionPhoto => catalogMetadata?.isMotionPhoto ?? false;
@@ -19,10 +19,10 @@ extension ExtraAvesEntryMultipage on AvesEntry {
}
Future getMultiPageInfo() async {
- if (isBurst) {
+ if (isStack) {
return MultiPageInfo(
mainEntry: this,
- pages: burstEntries!
+ pages: stackedEntries!
.mapIndexed((index, entry) => SinglePageInfo(
index: index,
pageId: entry.id,
diff --git a/lib/model/filters/album.dart b/lib/model/filters/album.dart
index d2126c938..53121a7ec 100644
--- a/lib/model/filters/album.dart
+++ b/lib/model/filters/album.dart
@@ -52,13 +52,13 @@ class AlbumFilter extends CoveredCollectionFilter {
String getTooltip(BuildContext context) => album;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
return IconUtils.getAlbumIcon(
context: context,
albumPath: album,
size: size,
) ??
- (showGenericIcon ? Icon(AIcons.album, size: size) : null);
+ (allowGenericIcon ? Icon(AIcons.album, size: size) : null);
}
@override
diff --git a/lib/model/filters/aspect_ratio.dart b/lib/model/filters/aspect_ratio.dart
index b638ef7e4..5fcf6933f 100644
--- a/lib/model/filters/aspect_ratio.dart
+++ b/lib/model/filters/aspect_ratio.dart
@@ -68,7 +68,7 @@ class AspectRatioFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.aspectRatio, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.aspectRatio, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/coordinate.dart b/lib/model/filters/coordinate.dart
index fff9df429..c8ba7f2a7 100644
--- a/lib/model/filters/coordinate.dart
+++ b/lib/model/filters/coordinate.dart
@@ -69,7 +69,7 @@ class CoordinateFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.geoBounds, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.geoBounds, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/date.dart b/lib/model/filters/date.dart
index 2756de2e0..d73b8ab59 100644
--- a/lib/model/filters/date.dart
+++ b/lib/model/filters/date.dart
@@ -122,7 +122,7 @@ class DateFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.date, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.date, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/favourite.dart b/lib/model/filters/favourite.dart
index 2bd813b89..fc8855260 100644
--- a/lib/model/filters/favourite.dart
+++ b/lib/model/filters/favourite.dart
@@ -45,7 +45,7 @@ class FavouriteFilter extends CollectionFilter {
String getLabel(BuildContext context) => context.l10n.filterFavouriteLabel;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.favourite, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.favourite, size: size);
@override
Future color(BuildContext context) {
diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart
index f22270c88..fe52ebbdf 100644
--- a/lib/model/filters/filters.dart
+++ b/lib/model/filters/filters.dart
@@ -133,7 +133,7 @@ abstract class CollectionFilter extends Equatable implements Comparable getLabel(context);
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => null;
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => null;
Future color(BuildContext context) {
final colors = context.read();
diff --git a/lib/model/filters/location.dart b/lib/model/filters/location.dart
index 0b5c06d25..7c29eddf0 100644
--- a/lib/model/filters/location.dart
+++ b/lib/model/filters/location.dart
@@ -89,7 +89,7 @@ class LocationFilter extends CoveredCollectionFilter {
String getLabel(BuildContext context) => _isUnlocated ? context.l10n.filterNoLocationLabel : _location;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
if (_isUnlocated) {
return Icon(AIcons.locationUnlocated, size: size);
}
diff --git a/lib/model/filters/mime.dart b/lib/model/filters/mime.dart
index 86d4840d2..36176697d 100644
--- a/lib/model/filters/mime.dart
+++ b/lib/model/filters/mime.dart
@@ -77,7 +77,7 @@ class MimeFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
@override
Future color(BuildContext context) {
diff --git a/lib/model/filters/missing.dart b/lib/model/filters/missing.dart
index d785bc3c2..1f9bc6bf1 100644
--- a/lib/model/filters/missing.dart
+++ b/lib/model/filters/missing.dart
@@ -70,7 +70,7 @@ class MissingFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/or.dart b/lib/model/filters/or.dart
index 89e62751f..2b6c8d83f 100644
--- a/lib/model/filters/or.dart
+++ b/lib/model/filters/or.dart
@@ -60,8 +60,8 @@ class OrFilter extends CollectionFilter {
String getLabel(BuildContext context) => _filters.map((v) => v.getLabel(context)).join(', ');
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
- return _genericIcon != null ? Icon(_genericIcon, size: size) : _first.iconBuilder(context, size, showGenericIcon: showGenericIcon);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
+ return _genericIcon != null ? Icon(_genericIcon, size: size) : _first.iconBuilder(context, size, allowGenericIcon: allowGenericIcon);
}
@override
diff --git a/lib/model/filters/path.dart b/lib/model/filters/path.dart
index f8b05afc7..4bb6c7cde 100644
--- a/lib/model/filters/path.dart
+++ b/lib/model/filters/path.dart
@@ -1,5 +1,9 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/services/common/services.dart';
+import 'package:aves/theme/icons.dart';
+import 'package:aves/utils/android_file_utils.dart';
+import 'package:aves/view/view.dart';
+import 'package:flutter/widgets.dart';
class PathFilter extends CollectionFilter {
static const type = 'path';
@@ -47,6 +51,19 @@ class PathFilter extends CollectionFilter {
@override
String get universalLabel => path;
+ @override
+ String getLabel(BuildContext context) {
+ final _directory = androidFileUtils.relativeDirectoryFromPath(path);
+ if (_directory == null) return universalLabel;
+ if (_directory.relativeDir.isEmpty) {
+ return _directory.getVolumeDescription(context);
+ }
+ return pContext.split(_directory.relativeDir).last;
+ }
+
+ @override
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.explorer, size: size);
+
@override
String get category => type;
diff --git a/lib/model/filters/placeholder.dart b/lib/model/filters/placeholder.dart
index 9e3263786..4f6145f7f 100644
--- a/lib/model/filters/placeholder.dart
+++ b/lib/model/filters/placeholder.dart
@@ -96,7 +96,7 @@ class PlaceholderFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/query.dart b/lib/model/filters/query.dart
index af136e6ae..e5c47c42b 100644
--- a/lib/model/filters/query.dart
+++ b/lib/model/filters/query.dart
@@ -82,7 +82,7 @@ class QueryFilter extends CollectionFilter {
String get universalLabel => query;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.text, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.text, size: size);
@override
Future color(BuildContext context) {
diff --git a/lib/model/filters/rating.dart b/lib/model/filters/rating.dart
index c9bd56290..ac296d365 100644
--- a/lib/model/filters/rating.dart
+++ b/lib/model/filters/rating.dart
@@ -64,7 +64,7 @@ class RatingFilter extends CollectionFilter {
};
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
return switch (rating) {
-1 => Icon(AIcons.ratingRejected, size: size),
0 => Icon(AIcons.ratingUnrated, size: size),
diff --git a/lib/model/filters/recent.dart b/lib/model/filters/recent.dart
index 57d61e856..57d1b1e52 100644
--- a/lib/model/filters/recent.dart
+++ b/lib/model/filters/recent.dart
@@ -51,7 +51,7 @@ class RecentlyAddedFilter extends CollectionFilter {
String getLabel(BuildContext context) => context.l10n.filterRecentlyAddedLabel;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.dateRecent, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.dateRecent, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart
index cbb723e04..8282ad871 100644
--- a/lib/model/filters/tag.dart
+++ b/lib/model/filters/tag.dart
@@ -47,8 +47,8 @@ class TagFilter extends CoveredCollectionFilter {
String getLabel(BuildContext context) => tag.isEmpty ? context.l10n.filterNoTagLabel : tag;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) {
- return showGenericIcon ? Icon(tag.isEmpty ? AIcons.tagUntagged : AIcons.tag, size: size) : null;
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) {
+ return allowGenericIcon ? Icon(tag.isEmpty ? AIcons.tagUntagged : AIcons.tag, size: size) : null;
}
@override
diff --git a/lib/model/filters/trash.dart b/lib/model/filters/trash.dart
index fc7b10325..095a058f9 100644
--- a/lib/model/filters/trash.dart
+++ b/lib/model/filters/trash.dart
@@ -41,7 +41,7 @@ class TrashFilter extends CollectionFilter {
String getLabel(BuildContext context) => context.l10n.filterBinLabel;
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(AIcons.bin, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(AIcons.bin, size: size);
@override
String get category => type;
diff --git a/lib/model/filters/type.dart b/lib/model/filters/type.dart
index 1299b484e..89e77762d 100644
--- a/lib/model/filters/type.dart
+++ b/lib/model/filters/type.dart
@@ -99,7 +99,7 @@ class TypeFilter extends CollectionFilter {
}
@override
- Widget? iconBuilder(BuildContext context, double size, {bool showGenericIcon = true}) => Icon(_icon, size: size);
+ Widget? iconBuilder(BuildContext context, double size, {bool allowGenericIcon = true}) => Icon(_icon, size: size);
@override
Future color(BuildContext context) {
diff --git a/lib/model/multipage.dart b/lib/model/multipage.dart
index c987965b9..b8dcbb0aa 100644
--- a/lib/model/multipage.dart
+++ b/lib/model/multipage.dart
@@ -32,10 +32,10 @@ class MultiPageInfo {
_pages.insert(0, firstPage.copyWith(isDefault: true));
}
- final burstEntries = mainEntry.burstEntries;
- if (burstEntries != null) {
+ final stackedEntries = mainEntry.stackedEntries;
+ if (stackedEntries != null) {
_pageEntries.addEntries(pages.map((pageInfo) {
- final pageEntry = burstEntries.firstWhere((entry) => entry.uri == pageInfo.uri);
+ final pageEntry = stackedEntries.firstWhere((entry) => entry.uri == pageInfo.uri);
return MapEntry(pageInfo, pageEntry);
}));
}
diff --git a/lib/model/settings/defaults.dart b/lib/model/settings/defaults.dart
index 3efed1af6..1e341cdd4 100644
--- a/lib/model/settings/defaults.dart
+++ b/lib/model/settings/defaults.dart
@@ -1,6 +1,7 @@
import 'package:aves/model/filters/recent.dart';
import 'package:aves/model/naming_pattern.dart';
import 'package:aves/ref/mime_types.dart';
+import 'package:aves/widgets/explorer/explorer_page.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart';
@@ -39,6 +40,7 @@ class SettingsDefaults {
AlbumListPage.routeName,
CountryListPage.routeName,
TagListPage.routeName,
+ ExplorerPage.routeName,
];
// collection
diff --git a/lib/model/settings/enums/home_page.dart b/lib/model/settings/enums/home_page.dart
index cf33a0fc4..548e76f69 100644
--- a/lib/model/settings/enums/home_page.dart
+++ b/lib/model/settings/enums/home_page.dart
@@ -1,4 +1,5 @@
import 'package:aves/widgets/collection/collection_page.dart';
+import 'package:aves/widgets/explorer/explorer_page.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/tags_page.dart';
import 'package:aves_model/aves_model.dart';
@@ -12,6 +13,8 @@ extension ExtraHomePageSetting on HomePageSetting {
return AlbumListPage.routeName;
case HomePageSetting.tags:
return TagListPage.routeName;
+ case HomePageSetting.explorer:
+ return ExplorerPage.routeName;
}
}
}
diff --git a/lib/model/settings/enums/widget_shape.dart b/lib/model/settings/enums/widget_shape.dart
index 28a64f3ab..c38d40662 100644
--- a/lib/model/settings/enums/widget_shape.dart
+++ b/lib/model/settings/enums/widget_shape.dart
@@ -3,11 +3,13 @@ import 'package:aves_model/aves_model.dart';
import 'package:flutter/painting.dart';
extension ExtraWidgetShape on WidgetShape {
- Path path(Size widgetSize, double devicePixelRatio) {
+ static const double _defaultCornerRadius = 24;
+
+ Path path(Size widgetSize, double devicePixelRatio, {double? cornerRadiusPx}) {
final rect = Offset.zero & widgetSize;
switch (this) {
case WidgetShape.rrect:
- return Path()..addRRect(BorderRadius.circular(24 * devicePixelRatio).toRRect(rect));
+ return Path()..addRRect(BorderRadius.circular(cornerRadiusPx ?? (_defaultCornerRadius * devicePixelRatio)).toRRect(rect));
case WidgetShape.circle:
return Path()
..addOval(Rect.fromCircle(
diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart
index 5e05948ff..e5a046b03 100644
--- a/lib/model/settings/settings.dart
+++ b/lib/model/settings/settings.dart
@@ -8,6 +8,7 @@ import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/defaults.dart';
+import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/modules/app.dart';
import 'package:aves/model/settings/modules/collection.dart';
@@ -206,6 +207,8 @@ class Settings with ChangeNotifier, SettingsAccess, AppSettings, DisplaySettings
AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(SettingKeys.accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);
+ bool get animate => accessibilityAnimations.animate;
+
set accessibilityAnimations(AccessibilityAnimations newValue) => set(SettingKeys.accessibilityAnimationsKey, newValue.toString());
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(SettingKeys.timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
diff --git a/lib/model/source/collection_lens.dart b/lib/model/source/collection_lens.dart
index 747e3bd1e..e6329dbd7 100644
--- a/lib/model/source/collection_lens.dart
+++ b/lib/model/source/collection_lens.dart
@@ -3,12 +3,14 @@ import 'dart:collection';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/multipage.dart';
+import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/entry/sort.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/location.dart';
+import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/query.dart';
import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/trash.dart';
@@ -18,6 +20,7 @@ import 'package:aves/model/source/events.dart';
import 'package:aves/model/source/location/location.dart';
import 'package:aves/model/source/section_keys.dart';
import 'package:aves/model/source/tag.dart';
+import 'package:aves/ref/mime_types.dart';
import 'package:aves/utils/collection_utils.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
@@ -34,7 +37,7 @@ class CollectionLens with ChangeNotifier {
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
final List _subscriptions = [];
int? id;
- bool listenToSource, groupBursts, fixedSort;
+ bool listenToSource, stackBursts, stackDevelopedRaws, fixedSort;
List? fixedSelection;
final Set _syntheticEntries = {};
@@ -47,7 +50,8 @@ class CollectionLens with ChangeNotifier {
Set? filters,
this.id,
this.listenToSource = true,
- this.groupBursts = true,
+ this.stackBursts = true,
+ this.stackDevelopedRaws = true,
this.fixedSort = false,
this.fixedSelection,
}) : filters = (filters ?? {}).whereNotNull().toSet(),
@@ -192,30 +196,59 @@ class CollectionLens with ChangeNotifier {
_disposeSyntheticEntries();
_filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry))));
- if (groupBursts) {
- _groupBursts();
+ if (stackBursts) {
+ _stackBursts();
+ }
+ if (stackDevelopedRaws) {
+ _stackDevelopedRaws();
}
}
- void _groupBursts() {
+ void _stackBursts() {
final byBurstKey = groupBy(_filteredSortedEntries, (entry) => entry.getBurstKey(burstPatterns)).whereNotNullKey();
byBurstKey.forEach((burstKey, entries) {
if (entries.length > 1) {
entries.sort(AvesEntrySort.compareByName);
final mainEntry = entries.first;
- final burstEntry = mainEntry.copyWith(burstEntries: entries);
- _syntheticEntries.add(burstEntry);
+ final stackEntry = mainEntry.copyWith(stackedEntries: entries);
+ _syntheticEntries.add(stackEntry);
- entries.skip(1).toList().forEach((subEntry) {
+ entries.skip(1).forEach((subEntry) {
_filteredSortedEntries.remove(subEntry);
});
final index = _filteredSortedEntries.indexOf(mainEntry);
_filteredSortedEntries.removeAt(index);
- _filteredSortedEntries.insert(index, burstEntry);
+ _filteredSortedEntries.insert(index, stackEntry);
}
});
}
+ void _stackDevelopedRaws() {
+ final allRawEntries = _filteredSortedEntries.where((entry) => entry.isRaw).toSet();
+ if (allRawEntries.isNotEmpty) {
+ final allDevelopedEntries = _filteredSortedEntries.where(MimeFilter(MimeTypes.jpeg).test).toSet();
+ final rawEntriesByDir = groupBy(allRawEntries, (entry) => entry.directory);
+ rawEntriesByDir.forEach((dir, dirRawEntries) {
+ if (dir != null) {
+ final dirDevelopedEntries = allDevelopedEntries.where((entry) => entry.directory == dir).toSet();
+ for (final rawEntry in dirRawEntries) {
+ final rawFilename = rawEntry.filenameWithoutExtension;
+ final developedEntry = dirDevelopedEntries.firstWhereOrNull((entry) => entry.filenameWithoutExtension == rawFilename);
+ if (developedEntry != null) {
+ final stackEntry = rawEntry.copyWith(stackedEntries: [rawEntry, developedEntry]);
+ _syntheticEntries.add(stackEntry);
+
+ _filteredSortedEntries.remove(developedEntry);
+ final index = _filteredSortedEntries.indexOf(rawEntry);
+ _filteredSortedEntries.removeAt(index);
+ _filteredSortedEntries.insert(0, stackEntry);
+ }
+ }
+ }
+ });
+ }
+ }
+
void _applySort() {
if (fixedSort) return;
@@ -322,23 +355,52 @@ class CollectionLens with ChangeNotifier {
}
void _onEntryRemoved(Set entries) {
- if (groupBursts) {
- // find impacted burst groups
- final obsoleteBurstEntries = {};
- final burstKeys = entries.map((entry) => entry.getBurstKey(burstPatterns)).whereNotNull().toSet();
- if (burstKeys.isNotEmpty) {
- _filteredSortedEntries.where((entry) => entry.isBurst && burstKeys.contains(entry.getBurstKey(burstPatterns))).forEach((mainEntry) {
- final subEntries = mainEntry.burstEntries!;
+ if (_syntheticEntries.isNotEmpty) {
+ // find impacted stacks
+ final obsoleteStacks = {};
+
+ void _replaceStack(AvesEntry stackEntry, AvesEntry entry) {
+ obsoleteStacks.add(stackEntry);
+ fixedSelection?.replace(stackEntry, entry);
+ _filteredSortedEntries.replace(stackEntry, entry);
+ _sortedEntries?.replace(stackEntry, entry);
+ sections.forEach((key, sectionEntries) => sectionEntries.replace(stackEntry, entry));
+ }
+
+ final stacks = _filteredSortedEntries.where((entry) => entry.isStack).toSet();
+ stacks.forEach((stackEntry) {
+ final subEntries = stackEntry.stackedEntries!;
+ if (subEntries.any(entries.contains)) {
+ final mainEntry = subEntries.first;
+
// remove the deleted sub-entries
subEntries.removeWhere(entries.contains);
- if (subEntries.isEmpty) {
- // remove the burst entry itself
- obsoleteBurstEntries.add(mainEntry);
+
+ switch (subEntries.length) {
+ case 0:
+ // remove the stack itself
+ obsoleteStacks.add(stackEntry);
+ break;
+ case 1:
+ // replace the stack by the last remaining sub-entry
+ _replaceStack(stackEntry, subEntries.first);
+ break;
+ default:
+ // keep the stack with the remaining sub-entries
+ if (!subEntries.contains(mainEntry)) {
+ // recreate the stack with the correct main entry
+ _replaceStack(stackEntry, subEntries.first.copyWith(stackedEntries: subEntries));
+ }
+ break;
}
- // TODO TLAD [burst] recreate the burst main entry if the first sub-entry got deleted
- });
- entries.addAll(obsoleteBurstEntries);
- }
+ }
+ });
+
+ obsoleteStacks.forEach((stackEntry) {
+ _syntheticEntries.remove(stackEntry);
+ stackEntry.dispose();
+ });
+ entries.addAll(obsoleteStacks);
}
// we should remove obsolete entries and sections
diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart
index 8d2b7ea6c..4c2619539 100644
--- a/lib/ref/mime_types.dart
+++ b/lib/ref/mime_types.dart
@@ -134,4 +134,15 @@ class MimeTypes {
}
return null;
}
+
+ static const Map _defaultExtensions = {
+ bmp: '.bmp',
+ gif: '.gif',
+ jpeg: '.jpg',
+ png: '.png',
+ svg: '.svg',
+ webp: '.webp',
+ };
+
+ static String? extensionFor(String mimeType) => _defaultExtensions[mimeType];
}
diff --git a/lib/services/intent_service.dart b/lib/services/intent_service.dart
index 47d46e691..51f18940a 100644
--- a/lib/services/intent_service.dart
+++ b/lib/services/intent_service.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:aves/model/filters/filters.dart';
+import 'package:aves/services/app_service.dart';
import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
import 'package:flutter/services.dart';
@@ -27,7 +28,11 @@ class IntentService {
'uris': uris,
});
} on PlatformException catch (e, stack) {
- await reportService.recordError(e, stack);
+ if (e.code == 'submitPickedItems-large') {
+ throw TooManyItemsException();
+ } else {
+ await reportService.recordError(e, stack);
+ }
}
}
diff --git a/lib/services/media/media_edit_service.dart b/lib/services/media/media_edit_service.dart
index 927c79e72..fd3810e97 100644
--- a/lib/services/media/media_edit_service.dart
+++ b/lib/services/media/media_edit_service.dart
@@ -193,15 +193,17 @@ class PlatformMediaEditService implements MediaEditService {
@immutable
class EntryConvertOptions extends Equatable {
+ final EntryConvertAction action;
final String mimeType;
final bool writeMetadata;
final LengthUnit lengthUnit;
final int width, height, quality;
@override
- List